Adapt Depot's full-text search capabilities for PostgreSQL 8.3. This is a fairly minimal change, excepting support for associating FullTextIndex configurations with PostgreSQL search configurations. The legacy stuff to support 8.2 is unlikely to last long.

This commit is contained in:
Par Winzell
2009-03-26 20:14:45 +00:00
parent 51e43b6545
commit 34599bc760
4 changed files with 73 additions and 15 deletions
@@ -32,6 +32,13 @@ import java.lang.annotation.Target;
@Retention(value=RetentionPolicy.RUNTIME) @Retention(value=RetentionPolicy.RUNTIME)
public @interface FullTextIndex public @interface FullTextIndex
{ {
public enum Configuration {
/** For searching generic strings; makes no linguistic/semantic assumptions. */
Simple,
/** For English text search; does basic stemming. */
English
}
/** /**
* An identifier for this index, unique with the scope of the record. * An identifier for this index, unique with the scope of the record.
*/ */
@@ -41,4 +48,9 @@ public @interface FullTextIndex
* An array of the field names that should be indexed. * An array of the field names that should be indexed.
*/ */
public String[] fields (); public String[] fields ();
/**
* What parser/dictionary to use for this index.
*/
public Configuration configuration () default Configuration.English;
} }
@@ -525,7 +525,9 @@ public abstract class BuildVisitor implements ExpressionVisitor
} }
comma = true; comma = true;
_builder.append("(");
field.left.accept(this); field.left.accept(this);
_builder.append(")");
if (field.right == Order.DESC) { if (field.right == Order.DESC) {
// ascending is default, print nothing unless explicitly descending // ascending is default, print nothing unless explicitly descending
_builder.append(" desc"); _builder.append(" desc");
@@ -40,6 +40,7 @@ import com.samskivert.util.StringUtil;
import com.samskivert.depot.DatabaseException; import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.PersistentRecord; import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.FullTextIndex; import com.samskivert.depot.annotation.FullTextIndex;
import com.samskivert.depot.annotation.FullTextIndex.Configuration;
import com.samskivert.depot.expression.EpochSeconds; import com.samskivert.depot.expression.EpochSeconds;
import com.samskivert.depot.operator.Conditionals.FullText; import com.samskivert.depot.operator.Conditionals.FullText;
@@ -48,17 +49,25 @@ import static com.samskivert.Log.log;
public class PostgreSQLBuilder public class PostgreSQLBuilder
extends SQLBuilder extends SQLBuilder
{ {
// Are we running with PostgreSQL 8.3's new FTS engine, or the old one?
// TODO: Rip out when no longer needed.
public final static boolean PG83 = Boolean.getBoolean("com.samskivert.depot.pg83");
public class PGBuildVisitor extends BuildVisitor public class PGBuildVisitor extends BuildVisitor
{ {
@Override public void visit (FullText.Match match) { @Override public void visit (FullText.Match match) {
appendIdentifier("ftsCol_" + match.getDefinition().getName()); appendIdentifier("ftsCol_" + match.getDefinition().getName());
_builder.append(" @@ to_tsquery('default', ?)"); _builder.append(" @@ to_tsquery('").
append(translateFTConfig(getFTIndex(match.getDefinition()).configuration())).
append("', ?)");
} }
@Override public void visit (FullText.Rank rank) { @Override public void visit (FullText.Rank rank) {
_builder.append("rank("); _builder.append(PG83 ? "ts_rank" : "rank").append("(");
appendIdentifier("ftsCol_" + rank.getDefinition().getName()); appendIdentifier("ftsCol_" + rank.getDefinition().getName());
_builder.append(", to_tsquery('default', ?), 32)"); _builder.append(", to_tsquery('").
append(translateFTConfig(getFTIndex(rank.getDefinition()).configuration())).
append("', ?), 32)");
} }
@Override public void visit (EpochSeconds epochSeconds) { @Override public void visit (EpochSeconds epochSeconds) {
@@ -73,13 +82,19 @@ public class PostgreSQLBuilder
// _builder.append(" = any (?)"); // _builder.append(" = any (?)");
// } // }
protected PGBuildVisitor (DepotTypes types) { protected FullTextIndex getFTIndex (FullText definition)
super(types); {
DepotMarshaller<?> marsh = _types.getMarshaller(definition.getPersistentClass());
return marsh.getFullTextIndex(definition.getName());
} }
@Override protected void appendIdentifier (String field) { @Override protected void appendIdentifier (String field) {
_builder.append("\"").append(field).append("\""); _builder.append("\"").append(field).append("\"");
} }
protected PGBuildVisitor (DepotTypes types) {
super(types);
}
} }
public class PGBindVisitor extends BindVisitor public class PGBindVisitor extends BindVisitor
@@ -181,7 +196,9 @@ public class PostgreSQLBuilder
// build the UPDATE // build the UPDATE
StringBuilder initColumn = new StringBuilder("UPDATE "). StringBuilder initColumn = new StringBuilder("UPDATE ").
append(liaison.tableSQL(table)).append(" SET ").append(liaison.columnSQL(column)). append(liaison.tableSQL(table)).append(" SET ").append(liaison.columnSQL(column)).
append(" = TO_TSVECTOR('default', "); append(" = TO_TSVECTOR('").
append(translateFTConfig(fts.configuration())).
append("', ");
for (int ii = 0; ii < fields.length; ii ++) { for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) { if (ii > 0) {
@@ -193,11 +210,20 @@ public class PostgreSQLBuilder
} }
initColumn.append(")"); initColumn.append(")");
String triggerFun = PG83 ? "tsvector_update_trigger" : "tsearch2";
// build the CREATE TRIGGER // build the CREATE TRIGGER
StringBuilder createTrigger = new StringBuilder("CREATE TRIGGER "). StringBuilder createTrigger = new StringBuilder("CREATE TRIGGER ").
append(liaison.columnSQL(trigger)).append(" BEFORE UPDATE OR INSERT ON "). append(liaison.columnSQL(trigger)).append(" BEFORE UPDATE OR INSERT ON ").
append(liaison.tableSQL(table)).append(" FOR EACH ROW EXECUTE PROCEDURE tsearch2("). append(liaison.tableSQL(table)).
append(" FOR EACH ROW EXECUTE PROCEDURE ").append(triggerFun).append("(").
append(liaison.columnSQL(column)).append(", "); append(liaison.columnSQL(column)).append(", ");
if (PG83) {
createTrigger.append("'").
append(translateFTConfig(fts.configuration())).
append("', ");
}
for (int ii = 0; ii < fields.length; ii ++) { for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) { if (ii > 0) {
@@ -210,7 +236,8 @@ public class PostgreSQLBuilder
// build the CREATE INDEX // build the CREATE INDEX
StringBuilder createIndex = new StringBuilder("CREATE INDEX "). StringBuilder createIndex = new StringBuilder("CREATE INDEX ").
append(liaison.columnSQL(index)).append(" ON " ).append(liaison.tableSQL(table)). append(liaison.columnSQL(index)).append(" ON " ).append(liaison.tableSQL(table)).
append(" USING GIST(").append(liaison.columnSQL(column)).append(")"); append(" USING ").append(PG83 ? "GIN" : "GIST").append("(").
append(liaison.columnSQL(column)).append(")");
Statement stmt = conn.createStatement(); Statement stmt = conn.createStatement();
try { try {
@@ -307,4 +334,23 @@ public class PostgreSQLBuilder
throw new IllegalArgumentException("Unknown field marshaller type: " + fm.getClass()); throw new IllegalArgumentException("Unknown field marshaller type: " + fm.getClass());
} }
} }
// Translate the mildly abstracted full-text parser/dictionary configuration support
// in FullText to actual PostgreSQL configuration identifiers.
protected static String translateFTConfig (Configuration configuration)
{
// legacy support
if (!PG83) {
return "default";
}
switch(configuration) {
case Simple:
return "pg_catalog.simple";
case English:
return "pg_catalog.english";
default:
throw new IllegalArgumentException(
"Unknown full text configuration: " + configuration);
}
}
} }
@@ -358,8 +358,6 @@ public abstract class Conditionals
protected SQLExpression _elseExp; protected SQLExpression _elseExp;
} }
/** /**
* An attempt at a dialect-agnostic full-text search condition, such as MySQL's MATCH() and * An attempt at a dialect-agnostic full-text search condition, such as MySQL's MATCH() and
* PostgreSQL's @@ TO_TSQUERY(...) abilities. * PostgreSQL's @@ TO_TSQUERY(...) abilities.
@@ -440,15 +438,15 @@ public abstract class Conditionals
return _pClass; return _pClass;
} }
public String getQuery ()
{
return _query;
}
public String getName () public String getName ()
{ {
return _name; return _name;
} }
public String getQuery ()
{
return _query;
}
protected String toString (String subType) protected String toString (String subType)
{ {