In-memory Lucene fulltext search support.

上级 0163a2a2
...@@ -18,7 +18,8 @@ Change Log ...@@ -18,7 +18,8 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>Fulltext search: UUID primary keys are now supported. <ul><li>Fulltext search: in-memory Lucene indexes are now supported.
</li><li>Fulltext search: UUID primary keys are now supported.
</li><li>Apache Tomcat 7.x will now longer log a warning when unloading the web application, if using a connection pool. </li><li>Apache Tomcat 7.x will now longer log a warning when unloading the web application, if using a connection pool.
</li><li>H2 Console: support the Midori browser (for Debian / Raspberry Pi) </li><li>H2 Console: support the Midori browser (for Debian / Raspberry Pi)
</li><li>When opening a remote session, don't open a temporary file if the trace level is set to zero </li><li>When opening a remote session, don't open a temporary file if the trace level is set to zero
......
...@@ -48,6 +48,7 @@ import org.apache.lucene.search.ScoreDoc; ...@@ -48,6 +48,7 @@ import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.Directory; import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version; import org.apache.lucene.util.Version;
import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriter;
//*/ //*/
...@@ -71,6 +72,8 @@ public class FullTextLucene extends FullText { ...@@ -71,6 +72,8 @@ public class FullTextLucene extends FullText {
private static final String LUCENE_FIELD_MODIFIED = "_modified"; private static final String LUCENE_FIELD_MODIFIED = "_modified";
private static final String LUCENE_FIELD_COLUMN_PREFIX = "_"; private static final String LUCENE_FIELD_COLUMN_PREFIX = "_";
private static final String IN_MEMORY_PERFIX = "mem:";
/** /**
* Initializes full text search functionality for this database. This adds * Initializes full text search functionality for this database. This adds
* the following Java functions to the database: * the following Java functions to the database:
...@@ -262,8 +265,7 @@ public class FullTextLucene extends FullText { ...@@ -262,8 +265,7 @@ public class FullTextLucene extends FullText {
access.modifier = new IndexModifier(path, analyzer, recreate); access.modifier = new IndexModifier(path, analyzer, recreate);
//*/ //*/
//## LUCENE3 ## //## LUCENE3 ##
File f = new File(path); Directory indexDir = path.startsWith(IN_MEMORY_PERFIX) ? new RAMDirectory() : FSDirectory.open(new File(path));
Directory indexDir = FSDirectory.open(f);
boolean recreate = !IndexReader.indexExists(indexDir); boolean recreate = !IndexReader.indexExists(indexDir);
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30); Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
IndexWriter writer = new IndexWriter(indexDir, analyzer, IndexWriter writer = new IndexWriter(indexDir, analyzer,
...@@ -296,7 +298,12 @@ public class FullTextLucene extends FullText { ...@@ -296,7 +298,12 @@ public class FullTextLucene extends FullText {
rs.next(); rs.next();
String path = rs.getString(1); String path = rs.getString(1);
if (path == null) { if (path == null) {
throw throwException("Fulltext search for in-memory databases is not supported."); /*## LUCENE2 ##
throw throwException("Fulltext search for in-memory databases is not supported with Lucene 2. Please use Lucene 3 instead.");
//*/
//## LUCENE3 ##
return IN_MEMORY_PERFIX + conn.getCatalog();
//*/
} }
int index = path.lastIndexOf(':'); int index = path.lastIndexOf(':');
// position 1 means a windows drive letter is used, ignore that // position 1 means a windows drive letter is used, ignore that
...@@ -336,7 +343,9 @@ public class FullTextLucene extends FullText { ...@@ -336,7 +343,9 @@ public class FullTextLucene extends FullText {
if (access != null) { if (access != null) {
removeIndexAccess(access, path); removeIndexAccess(access, path);
} }
FileUtils.deleteRecursive(path, false); if (!path.startsWith(IN_MEMORY_PERFIX)) {
FileUtils.deleteRecursive(path, false);
}
} }
/** /**
...@@ -547,12 +556,12 @@ public class FullTextLucene extends FullText { ...@@ -547,12 +556,12 @@ public class FullTextLucene extends FullText {
if (newRow != null) { if (newRow != null) {
// update // update
if (hasChanged(oldRow, newRow, indexColumns)) { if (hasChanged(oldRow, newRow, indexColumns)) {
delete(oldRow); delete(oldRow, false);
insert(newRow, true); insert(newRow, true);
} }
} else { } else {
// delete // delete
delete(oldRow); delete(oldRow, true);
} }
} else if (newRow != null) { } else if (newRow != null) {
// insert // insert
...@@ -665,13 +674,7 @@ public class FullTextLucene extends FullText { ...@@ -665,13 +674,7 @@ public class FullTextLucene extends FullText {
try { try {
indexAccess.writer.addDocument(doc); indexAccess.writer.addDocument(doc);
if (commitIndex) { if (commitIndex) {
indexAccess.writer.commit(); commitIndex();
// recreate Searcher with the IndexWriter's reader.
indexAccess.searcher.close();
indexAccess.reader.close();
IndexReader reader = indexAccess.writer.getReader();
indexAccess.reader = reader;
indexAccess.searcher = new IndexSearcher(reader);
} }
} catch (IOException e) { } catch (IOException e) {
throw convertException(e); throw convertException(e);
...@@ -683,8 +686,9 @@ public class FullTextLucene extends FullText { ...@@ -683,8 +686,9 @@ public class FullTextLucene extends FullText {
* Delete a row from the index. * Delete a row from the index.
* *
* @param row the row * @param row the row
* @param commitIndex whether to commit the changes to the Lucene index
*/ */
protected void delete(Object[] row) throws SQLException { protected void delete(Object[] row, boolean commitIndex) throws SQLException {
String query = getQuery(row); String query = getQuery(row);
try { try {
Term term = new Term(LUCENE_FIELD_QUERY, query); Term term = new Term(LUCENE_FIELD_QUERY, query);
...@@ -693,6 +697,9 @@ public class FullTextLucene extends FullText { ...@@ -693,6 +697,9 @@ public class FullTextLucene extends FullText {
//*/ //*/
//## LUCENE3 ## //## LUCENE3 ##
indexAccess.writer.deleteDocuments(term); indexAccess.writer.deleteDocuments(term);
if (commitIndex) {
commitIndex();
}
//*/ //*/
} catch (IOException e) { } catch (IOException e) {
throw convertException(e); throw convertException(e);
......
...@@ -12,11 +12,14 @@ import java.sql.PreparedStatement; ...@@ -12,11 +12,14 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Random; import java.util.Random;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.UUID; import java.util.UUID;
import org.h2.fulltext.FullText; import org.h2.fulltext.FullText;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.TestAll;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.Task; import org.h2.util.Task;
...@@ -45,9 +48,6 @@ public class TestFullText extends TestBase { ...@@ -45,9 +48,6 @@ public class TestFullText extends TestBase {
testNativeFeatures(); testNativeFeatures();
testTransaction(false); testTransaction(false);
testCreateDrop(); testCreateDrop();
if (config.memory) {
return;
}
testStreamLob(); testStreamLob();
test(false, "VARCHAR"); test(false, "VARCHAR");
test(false, "CLOB"); test(false, "CLOB");
...@@ -76,34 +76,46 @@ public class TestFullText extends TestBase { ...@@ -76,34 +76,46 @@ public class TestFullText extends TestBase {
deleteDb("fullTextReopen"); deleteDb("fullTextReopen");
} }
private void close(Collection<Connection> conns) throws SQLException {
for (Connection conn : conns) {
conn.close();
}
}
private Connection getConnection(String name, Collection<Connection> conns) throws SQLException {
Connection conn = getConnection(name);
conns.add(conn);
return conn;
}
private void testAutoAnalyze() throws SQLException { private void testAutoAnalyze() throws SQLException {
if (config.memory) {
return;
}
deleteDb("fullTextNative"); deleteDb("fullTextNative");
Connection conn; Connection conn;
Statement stat; Statement stat;
conn = getConnection("fullTextNative"); ArrayList<Connection> conns = new ArrayList<Connection>();
conn = getConnection("fullTextNative", conns);
stat = conn.createStatement(); stat = conn.createStatement();
stat.execute("create alias if not exists ft_init for \"org.h2.fulltext.FullText.init\""); stat.execute("create alias if not exists ft_init for \"org.h2.fulltext.FullText.init\"");
stat.execute("call ft_init()"); stat.execute("call ft_init()");
stat.execute("create table test(id int primary key, name varchar)"); stat.execute("create table test(id int primary key, name varchar)");
stat.execute("call ft_create_index('PUBLIC', 'TEST', 'NAME')"); stat.execute("call ft_create_index('PUBLIC', 'TEST', 'NAME')");
conn.close();
conn = getConnection("fullTextNative"); if (!config.memory) {
conn.close();
}
conn = getConnection("fullTextNative", conns);
stat = conn.createStatement(); stat = conn.createStatement();
stat.execute("insert into test select x, 'x' from system_range(1, 3000)"); stat.execute("insert into test select x, 'x' from system_range(1, 3000)");
conn.close(); close(conns);
} }
private void testNativeFeatures() throws SQLException { private void testNativeFeatures() throws SQLException {
if (config.memory) {
return;
}
deleteDb("fullTextNative"); deleteDb("fullTextNative");
Connection conn = getConnection("fullTextNative"); ArrayList<Connection> conns = new ArrayList<Connection>();
Connection conn = getConnection("fullTextNative", conns);
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
stat.execute("CREATE ALIAS IF NOT EXISTS FT_INIT FOR \"org.h2.fulltext.FullText.init\""); stat.execute("CREATE ALIAS IF NOT EXISTS FT_INIT FOR \"org.h2.fulltext.FullText.init\"");
stat.execute("CALL FT_INIT()"); stat.execute("CALL FT_INIT()");
...@@ -137,8 +149,10 @@ public class TestFullText extends TestBase { ...@@ -137,8 +149,10 @@ public class TestFullText extends TestBase {
rs = stat.executeQuery("SELECT * FROM FT_SEARCH('this', 0, 0)"); rs = stat.executeQuery("SELECT * FROM FT_SEARCH('this', 0, 0)");
assertFalse(rs.next()); assertFalse(rs.next());
conn.close(); if (!config.memory) {
conn = getConnection("fullTextNative"); conn.close();
}
conn = getConnection("fullTextNative", conns);
stat = conn.createStatement(); stat = conn.createStatement();
conn.setAutoCommit(false); conn.setAutoCommit(false);
stat.execute("delete from test"); stat.execute("delete from test");
...@@ -148,8 +162,7 @@ public class TestFullText extends TestBase { ...@@ -148,8 +162,7 @@ public class TestFullText extends TestBase {
rs = stat.executeQuery("SELECT * FROM FT_SEARCH_DATA('Welcome', 0, 0)"); rs = stat.executeQuery("SELECT * FROM FT_SEARCH_DATA('Welcome', 0, 0)");
assertTrue(rs.next()); assertTrue(rs.next());
conn.setAutoCommit(true); conn.setAutoCommit(true);
close(conns);
conn.close();
} }
private void testUuidPrimaryKey(boolean lucene) throws SQLException { private void testUuidPrimaryKey(boolean lucene) throws SQLException {
...@@ -178,13 +191,11 @@ public class TestFullText extends TestBase { ...@@ -178,13 +191,11 @@ public class TestFullText extends TestBase {
} }
private void testTransaction(boolean lucene) throws SQLException { private void testTransaction(boolean lucene) throws SQLException {
if (config.memory) {
return;
}
String prefix = lucene ? "FTL" : "FT"; String prefix = lucene ? "FTL" : "FT";
deleteDb("fullTextTransaction"); deleteDb("fullTextTransaction");
FileUtils.deleteRecursive(getBaseDir() + "/fullTextTransaction", false); FileUtils.deleteRecursive(getBaseDir() + "/fullTextTransaction", false);
Connection conn = getConnection("fullTextTransaction"); ArrayList<Connection> conns = new ArrayList<Connection>();
Connection conn = getConnection("fullTextTransaction", conns);
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
String className = lucene ? "FullTextLucene" : "FullText"; String className = lucene ? "FullTextLucene" : "FullText";
stat.execute("CREATE ALIAS IF NOT EXISTS " + prefix + "_INIT FOR \"org.h2.fulltext." + className + ".init\""); stat.execute("CREATE ALIAS IF NOT EXISTS " + prefix + "_INIT FOR \"org.h2.fulltext." + className + ".init\"");
...@@ -199,15 +210,17 @@ public class TestFullText extends TestBase { ...@@ -199,15 +210,17 @@ public class TestFullText extends TestBase {
conn.setAutoCommit(false); conn.setAutoCommit(false);
stat.execute("insert into test values(2, 'Hello Moon!')"); stat.execute("insert into test values(2, 'Hello Moon!')");
conn.rollback(); conn.rollback();
conn.close(); if (!config.memory) {
conn = getConnection("fullTextTransaction"); conn.close();
}
conn = getConnection("fullTextTransaction", conns);
stat = conn.createStatement(); stat = conn.createStatement();
rs = stat.executeQuery("SELECT * FROM " + prefix + "_SEARCH('Hello', 0, 0)"); rs = stat.executeQuery("SELECT * FROM " + prefix + "_SEARCH('Hello', 0, 0)");
assertTrue(rs.next()); assertTrue(rs.next());
rs = stat.executeQuery("SELECT * FROM " + prefix + "_SEARCH('Moon', 0, 0)"); rs = stat.executeQuery("SELECT * FROM " + prefix + "_SEARCH('Moon', 0, 0)");
assertFalse(rs.next()); assertFalse(rs.next());
FullText.dropAll(conn); FullText.dropAll(conn);
conn.close(); close(conns);
deleteDb("fullTextTransaction"); deleteDb("fullTextTransaction");
FileUtils.deleteRecursive(getBaseDir() + "/fullTextTransaction", false); FileUtils.deleteRecursive(getBaseDir() + "/fullTextTransaction", false);
} }
...@@ -216,12 +229,13 @@ public class TestFullText extends TestBase { ...@@ -216,12 +229,13 @@ public class TestFullText extends TestBase {
final String prefix = lucene ? "FTL" : "FT"; final String prefix = lucene ? "FTL" : "FT";
trace("Testing multithreaded " + prefix); trace("Testing multithreaded " + prefix);
deleteDb("fullText"); deleteDb("fullText");
ArrayList<Connection> conns = new ArrayList<Connection>();
int len = 2; int len = 2;
Task[] task = new Task[len]; Task[] task = new Task[len];
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
// final Connection conn = // final Connection conn =
// getConnection("fullText;MULTI_THREADED=1;LOCK_TIMEOUT=10000"); // getConnection("fullText;MULTI_THREADED=1;LOCK_TIMEOUT=10000");
final Connection conn = getConnection("fullText"); final Connection conn = getConnection("fullText", conns);
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
String className = lucene ? "FullTextLucene" : "FullText"; String className = lucene ? "FullTextLucene" : "FullText";
stat.execute("CREATE ALIAS IF NOT EXISTS " + prefix + "_INIT FOR \"org.h2.fulltext." + className + ".init\""); stat.execute("CREATE ALIAS IF NOT EXISTS " + prefix + "_INIT FOR \"org.h2.fulltext." + className + ".init\"");
...@@ -257,7 +271,9 @@ public class TestFullText extends TestBase { ...@@ -257,7 +271,9 @@ public class TestFullText extends TestBase {
} }
} }
trace("closing connection"); trace("closing connection");
conn.close(); if (!config.memory) {
conn.close();
}
trace("completed thread " + Thread.currentThread()); trace("completed thread " + Thread.currentThread());
} }
}; };
...@@ -274,6 +290,7 @@ public class TestFullText extends TestBase { ...@@ -274,6 +290,7 @@ public class TestFullText extends TestBase {
t.get(); t.get();
trace("done joining " + t); trace("done joining " + t);
} }
close(conns);
} }
private void testStreamLob() throws SQLException { private void testStreamLob() throws SQLException {
...@@ -331,6 +348,9 @@ public class TestFullText extends TestBase { ...@@ -331,6 +348,9 @@ public class TestFullText extends TestBase {
} }
private void testReopen(boolean lucene) throws SQLException { private void testReopen(boolean lucene) throws SQLException {
if (config.memory) {
return;
}
String prefix = lucene ? "FTL" : "FT"; String prefix = lucene ? "FTL" : "FT";
deleteDb("fullTextReopen"); deleteDb("fullTextReopen");
FileUtils.deleteRecursive(getBaseDir() + "/fullTextReopen", false); FileUtils.deleteRecursive(getBaseDir() + "/fullTextReopen", false);
...@@ -408,7 +428,8 @@ public class TestFullText extends TestBase { ...@@ -408,7 +428,8 @@ public class TestFullText extends TestBase {
return; return;
} }
deleteDb("fullText"); deleteDb("fullText");
Connection conn = getConnection("fullText"); ArrayList<Connection> conns = new ArrayList<Connection>();
Connection conn = getConnection("fullText", conns);
String prefix = lucene ? "FTL_" : "FT_"; String prefix = lucene ? "FTL_" : "FT_";
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
String className = lucene ? "FullTextLucene" : "FullText"; String className = lucene ? "FullTextLucene" : "FullText";
...@@ -491,15 +512,16 @@ public class TestFullText extends TestBase { ...@@ -491,15 +512,16 @@ public class TestFullText extends TestBase {
assertFalse(rs.next()); assertFalse(rs.next());
} }
conn.close(); if (!config.memory) {
conn.close();
}
conn = getConnection("fullText"); conn = getConnection("fullText", conns);
stat = conn.createStatement(); stat = conn.createStatement();
stat.executeQuery("SELECT * FROM " + prefix + "SEARCH('World', 0, 0)"); stat.executeQuery("SELECT * FROM " + prefix + "SEARCH('World', 0, 0)");
stat.execute("CALL " + prefix + "DROP_ALL()"); stat.execute("CALL " + prefix + "DROP_ALL()");
conn.close(); close(conns);
} }
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论