/**
* 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.FullTextIndex;
import com.foundationdb.qp.operator.Operator;
import static com.foundationdb.qp.operator.API.cursor;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.rowtype.RowType;
import com.foundationdb.qp.util.SchemaCache;
import com.foundationdb.server.service.servicemanager.GuicedServiceManager;
import com.foundationdb.server.service.transaction.TransactionService.CloseableTransaction;
import org.junit.Before;
import org.junit.Test;
public class FullTextIndexServiceIT extends FullTextIndexServiceITBase
{
public static final String SCHEMA = "test";
@Override
protected GuicedServiceManager.BindingsConfigurationProvider serviceBindingsProvider() {
return super.serviceBindingsProvider()
.bindAndRequire(FullTextIndexService.class, FullTextIndexServiceImpl.class);
}
@Before
public void createData() {
c = createTable(SCHEMA, "c",
"cid INT PRIMARY KEY NOT NULL",
"name VARCHAR(128) COLLATE en_us_ci");
o = createTable(SCHEMA, "o",
"oid INT PRIMARY KEY NOT NULL",
"cid INT NOT NULL",
"GROUPING FOREIGN KEY(cid) REFERENCES c(cid)",
"order_date DATE");
i = createTable(SCHEMA, "i",
"iid INT PRIMARY KEY NOT NULL",
"oid INT NOT NULL",
"GROUPING FOREIGN KEY(oid) REFERENCES o(oid)",
"sku VARCHAR(10) NOT NULL");
a = createTable(SCHEMA, "a",
"aid INT PRIMARY KEY NOT NULL",
"cid INT NOT NULL",
"GROUPING FOREIGN KEY(cid) REFERENCES c(cid)",
"state CHAR(2)");
writeRow(c, 1, "Fred Flintstone");
writeRow(o, 101, 1, "2012-12-12");
writeRow(i, 10101, 101, "ABCD");
writeRow(i, 10102, 101, "1234");
writeRow(o, 102, 1, "2013-01-01");
writeRow(a, 101, 1, "MA");
writeRow(c, 2, "Barney Rubble");
writeRow(a, 201, 2, "NY");
writeRow(c, 3, "Wilma Flintstone");
writeRow(o, 301, 3, "2010-04-01");
writeRow(a, 301, 3, "MA");
writeRow(a, 302, 3, "ME");
schema = SchemaCache.globalSchema(ais());
adapter = newStoreAdapter();
queryContext = queryContext(adapter);
queryBindings = queryContext.createBindings();
}
@Test
public void testUpdate() throws InterruptedException
{
/*
Test plans:
1 - do a search, confirm that the rows come back as expected
2 - (With update worker enabled)
+ insert new rows
+ wait for the updates to be done
+ do the search again and confirm that the new rows are found
3 - (With update worker NOT enable)
+ disable update worker
+ insert new rows
+ do the search again and confirm that the new rows are NOT found
*/
//CREATE INDEX cust_ft ON customers(FULL_TEXT(name, addresses.state, items.sku))
// part 1
FullTextIndex index = createFullTextIndex(
SCHEMA, "c", "idx_c",
"name", "i.sku", "a.state");
RowType rowType = rowType("c");
Row[] expected1 = new Row[]
{
row(rowType, 1L),
row(rowType, 3L)
};
FullTextQueryBuilder builder = new FullTextQueryBuilder(index, ais(), queryContext);
ftScanAndCompare(builder, "flintstone", 15, expected1);
ftScanAndCompare(builder, "state:MA", 15, expected1);
// part 2
// write new rows
writeRow(c, 4, "John Watson");
writeRow(c, 5, "Sherlock Flintstone");
writeRow(c, 6, "Mycroft Holmes");
writeRow(c, 7, "Flintstone Lestrade");
waitUpdate();
Row expected2[] = new Row[]
{
row(rowType, 1L),
row(rowType, 3L),
row(rowType, 5L),
row(rowType, 7L)
};
// confirm new changes
ftScanAndCompare(builder, "flintstone", 15, expected2);
// part 3
fullTextImpl.disableUpdateWorker();
writeRow(c, 8, "Flintstone Hudson");
writeRow(c, 9, "Jim Flintstone");
// The worker has been disabled, waitOn should return immediately
waitUpdate();
// confirm that new rows are not found (ie., expected2 still works)
ftScanAndCompare(builder, "flintstone", 15, expected2);
fullTextImpl.enableUpdateWorker();
waitUpdate();
// now the rows should be seen.
// (Because disabling the worker does not stop the changes fron being recorded)
Row expected3[] = new Row[]
{
row(rowType, 1L),
row(rowType, 3L),
row(rowType, 5L),
row(rowType, 7L),
row(rowType, 8L),
row(rowType, 9L)
};
ftScanAndCompare(builder, "flintstone", 15, expected3);
}
@Test
public void cDown() throws InterruptedException {
FullTextIndex index = createFullTextIndex(
SCHEMA, "c", "idx_c",
"name", "i.sku", "a.state");
RowType rowType = rowType("c");
Row[] expected = new Row[] {
row(rowType, 1L),
row(rowType, 3L)
};
FullTextQueryBuilder builder = new FullTextQueryBuilder(index, ais(), queryContext);
ftScanAndCompare(builder, "flintstone", 10, expected);
ftScanAndCompare(builder, "state:MA", 10, expected);
}
@Test
public void respondsToDropSchema() throws Exception {
FullTextIndex index = createFullTextIndex(
SCHEMA, "c", "idx_c",
"name", "i.sku", "a.state");
RowType rowType = rowType("c");
Row[] expected = new Row[] {
row(rowType, 1L),
row(rowType, 3L)
};
FullTextQueryBuilder builder = new FullTextQueryBuilder(index, ais(), queryContext);
ftScanAndCompare(builder, "flintstone", 10, expected);
ftScanAndCompare(builder, "state:MA", 10, expected);
ddl().dropSchema(session(), SCHEMA);
c = createTable(SCHEMA, "c",
"cid INT PRIMARY KEY NOT NULL",
"name VARCHAR(128) COLLATE en_us_ci");
index = createFullTextIndex(SCHEMA, "c", "idx_c", "name");
expected = new Row[] {};
builder = new FullTextQueryBuilder(index, ais(), queryContext);
ftScanAndCompare(builder, "flintstone", 10, expected);
}
@Test
public void oUpDown() throws InterruptedException {
FullTextIndex index = createFullTextIndex(
SCHEMA, "o", "idx_o",
"c.name", "i.sku");
RowType rowType = rowType("o");
Row[] expected = new Row[] {
row(rowType, 1L, 101L)
};
FullTextQueryBuilder builder = new FullTextQueryBuilder(index, ais(), queryContext);
ftScanAndCompare(builder, "name:Flintstone AND sku:1234", 10, expected);
}
@Test
public void testTruncate() throws InterruptedException {
FullTextIndex index = createFullTextIndex(SCHEMA, "c", "idx_c", "name", "i.sku", "a.state");
final int limit = 15;
RowType rowType = rowType("c");
String nameQuery = "flintstone";
Row[] nameExpected = new Row[] { row(rowType, 1L), row(rowType, 3L) };
String stateQuery = "state:MA";
Row[] stateExpected = new Row[] { row(rowType, 1L), row(rowType, 3L) };
String skuQuery = "sku:1234";
Row[] skuExpected = new Row[] { row(rowType, 1L) };
Row[] emptyExpected = {};
FullTextQueryBuilder builder = new FullTextQueryBuilder(index, ais(), queryContext);
ftScanAndCompare(builder, nameQuery, limit, nameExpected);
ftScanAndCompare(builder, stateQuery, limit, stateExpected);
ftScanAndCompare(builder, skuQuery, limit, skuExpected);
dml().truncateTable(session(), a);
waitUpdate();
ftScanAndCompare(builder, nameQuery, limit, nameExpected);
ftScanAndCompare(builder, stateQuery, limit, emptyExpected);
ftScanAndCompare(builder, skuQuery, limit, skuExpected);
dml().truncateTable(session(), o);
waitUpdate();
ftScanAndCompare(builder, nameQuery, limit, nameExpected);
ftScanAndCompare(builder, stateQuery, limit, emptyExpected);
ftScanAndCompare(builder, skuQuery, limit, emptyExpected); // Non-cascading key, connection to c1 is unknown
dml().truncateTable(session(), i);
waitUpdate();
ftScanAndCompare(builder, nameQuery, limit, nameExpected);
ftScanAndCompare(builder, stateQuery, limit, emptyExpected);
ftScanAndCompare(builder, skuQuery, limit, emptyExpected);
dml().truncateTable(session(), c);
waitUpdate();
ftScanAndCompare(builder, nameQuery, limit, emptyExpected);
ftScanAndCompare(builder, stateQuery, limit, emptyExpected);
ftScanAndCompare(builder, skuQuery, limit, emptyExpected);
}
protected RowType rowType(String tableName) {
return schema.newHKeyRowType(ais().getTable(SCHEMA, tableName).hKey());
}
private void ftScanAndCompare(FullTextQueryBuilder builder, String query, int limit, Row[] expected) {
try(CloseableTransaction txn = txnService().beginCloseableTransaction(session())) {
Operator plan = builder.scanOperator(query, limit);
compareRows(expected, cursor(plan, queryContext, queryBindings));
txn.commit();
}
}
}