/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.service.text;
import com.foundationdb.ais.model.AkibanInformationSchema;
import com.foundationdb.ais.model.Column;
import com.foundationdb.ais.model.FullTextIndex;
import com.foundationdb.ais.model.Group;
import com.foundationdb.ais.model.IndexColumn;
import com.foundationdb.ais.model.IndexName;
import com.foundationdb.ais.model.Table;
import com.foundationdb.qp.operator.API;
import com.foundationdb.qp.operator.Operator;
import com.foundationdb.qp.rowtype.*;
import com.foundationdb.qp.rowtype.TableRowType;
import com.foundationdb.qp.util.SchemaCache;
import com.foundationdb.server.error.NoSuchIndexException;
import com.foundationdb.server.error.NoSuchTableException;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class FullTextIndexInfo
{
private final FullTextIndexShared shared;
private FullTextIndex index;
private Schema schema;
private TableRowType indexedRowType;
private HKeyRowType hKeyRowType;
private Map<Column,IndexedField> fieldsByColumn;
private Map<RowType,List<IndexedField>> fieldsByRowType;
private String defaultFieldName;
private Operator plan;
public FullTextIndexInfo(FullTextIndexShared shared) {
this.shared = shared;
}
public void init(AkibanInformationSchema ais) {
IndexName name = shared.getName();
Table table = ais.getTable(name.getFullTableName());
if (table == null) {
throw new NoSuchTableException(name.getFullTableName());
}
index = table.getFullTextIndex(name.getName());
if (index == null) {
NoSuchIndexException ret = new NoSuchIndexException(name.getName());
ret.printStackTrace();
throw ret;
}
schema = SchemaCache.globalSchema(ais);
indexedRowType = schema.tableRowType(table);
hKeyRowType = schema.newHKeyRowType(table.hKey());
fieldsByColumn = new HashMap<>(index.getKeyColumns().size());
for (IndexColumn indexColumn : index.getKeyColumns()) {
Column column = indexColumn.getColumn();
IndexedField indexedField = new IndexedField(column);
fieldsByColumn.put(column, indexedField);
if (defaultFieldName == null) {
defaultFieldName = indexedField.getName();
}
}
fieldsByRowType = new HashMap<>();
for (Map.Entry<Column,IndexedField> entry : fieldsByColumn.entrySet()) {
TableRowType rowType = schema.tableRowType(entry.getKey().getTable());
List<IndexedField> fields = fieldsByRowType.get(rowType);
if (fields == null) {
fields = new ArrayList<>();
fieldsByRowType.put(rowType, fields);
}
fields.add(entry.getValue());
}
plan = computePlan();
}
public FullTextIndex getIndex() {
return index;
}
public Schema getSchema() {
return schema;
}
public TableRowType getIndexedRowType() {
return indexedRowType;
}
public HKeyRowType getHKeyRowType() {
return hKeyRowType;
}
public Map<Column,IndexedField> getFieldsByColumn() {
return fieldsByColumn;
}
public Map<RowType,List<IndexedField>> getFieldsByRowType() {
return fieldsByRowType;
}
public Set<String> getCasePreservingFieldNames() {
Set<String> result = new HashSet<>();
for (IndexedField field : fieldsByColumn.values()) {
if (field.isCasePreserving()) {
result.add(field.getName());
}
}
return result;
}
public String getDefaultFieldName() {
return defaultFieldName;
}
public Set<RowType> getRowTypes() {
Set<RowType> rowTypes = new HashSet<>(fieldsByRowType.keySet());
rowTypes.add(indexedRowType);
return rowTypes;
}
public Operator fullScan() {
Operator plan = API.groupScan_Default(indexedRowType.table().getGroup());
Set<RowType> rowTypes = getRowTypes();
plan = API.filter_Default(plan, rowTypes);
return plan;
}
private Operator computePlan()
{
Operator ret = null;
Group group = indexedRowType.table().getGroup();
Set<TableRowType> ancestors = new HashSet<>();
boolean hasDesc = false;
for (IndexColumn ic : index.getKeyColumns())
{
Table colTable = ic.getColumn().getTable();
if (!hasDesc && !colTable.equals(indexedRowType.table()))
// if any column in the index def belongs to a table
// that is a descendant of this indexed row's table
// (meaning this indexed row has descendant(s))
hasDesc = colTable.isDescendantOf(indexedRowType.table());
// if the indexed table is a child of this column's table
// (meaning this indexed row has parent(s))
// collect all ancestor's rowtype
if (indexedRowType.table().isDescendantOf(colTable))
ancestors.add(schema.tableRowType(colTable));
}
if (hasDesc)
{
ancestors.remove(indexedRowType);
ret = API.branchLookup_Nested(group,
hKeyRowType,
indexedRowType,
API.InputPreservationOption.DISCARD_INPUT,
0);
if (!ancestors.isEmpty())
{
ret = API.groupLookup_Default(ret,
group,
indexedRowType,
ancestors,
API.InputPreservationOption.KEEP_INPUT,
1);
}
}
else
{
// has at least one ancestor (which is itself)
ancestors.add(indexedRowType);
ret = API.ancestorLookup_Nested(group,
hKeyRowType,
ancestors,
0, 1);
}
return ret;
}
/**
* @return the operator plan to get to every row related to this index row
*/
public Operator getOperator()
{
return plan;
}
public Analyzer getAnalyzer() {
Analyzer analyzer;
synchronized (shared) {
analyzer = shared.getAnalyzer();
if (analyzer == null) {
analyzer = new SelectiveCaseAnalyzer(shared.getCasePreservingFieldNames());
}
}
return analyzer;
}
public StandardQueryParser getParser() {
StandardQueryParser parser;
synchronized (shared) {
parser = shared.getParser();
if (parser == null) {
parser = new StandardQueryParser(getAnalyzer());
}
shared.setParser(parser);
}
return parser;
}
protected Searcher getSearcher() throws IOException {
Searcher searcher;
synchronized (shared) {
searcher = shared.getSearcher();
if (searcher == null) {
searcher = new Searcher(shared, getAnalyzer());
}
shared.setSearcher(searcher);
}
return searcher;
}
public Indexer getIndexer() throws IOException {
Indexer indexer;
synchronized (shared) {
indexer = shared.getIndexer();
if (indexer == null) {
indexer = new Indexer(shared, getAnalyzer());
shared.setIndexer(indexer);
}
}
return indexer;
}
public void deletePath() {
File path = shared.getPath();
// no doc to delete
if (!path.exists() || path.listFiles() == null)
return;
for (File f : path.listFiles()) {
f.delete();
}
path.delete();
}
public void commitIndexer() throws IOException {
shared.getIndexer().getWriter().commit();
}
public void rollbackIndexer() throws IOException {
synchronized (shared) {
Indexer indexer = shared.getIndexer();
if(indexer != null) {
try {
indexer.getWriter().rollback();
} finally {
// Rollback causes the writer to be closed. Always get rid of it.
shared.setIndexer(null);
}
}
}
}
public void close() throws IOException {
shared.close();
}
}