From 34599bc7600550f956cffee6a412fc0a05302624 Mon Sep 17 00:00:00 2001 From: Par Winzell Date: Thu, 26 Mar 2009 20:14:45 +0000 Subject: [PATCH] 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. --- .../depot/annotation/FullTextIndex.java | 12 ++++ .../samskivert/depot/impl/BuildVisitor.java | 2 + .../depot/impl/PostgreSQLBuilder.java | 62 ++++++++++++++++--- .../depot/operator/Conditionals.java | 12 ++-- 4 files changed, 73 insertions(+), 15 deletions(-) diff --git a/src/java/com/samskivert/depot/annotation/FullTextIndex.java b/src/java/com/samskivert/depot/annotation/FullTextIndex.java index 59df80c..8b2e95d 100644 --- a/src/java/com/samskivert/depot/annotation/FullTextIndex.java +++ b/src/java/com/samskivert/depot/annotation/FullTextIndex.java @@ -32,6 +32,13 @@ import java.lang.annotation.Target; @Retention(value=RetentionPolicy.RUNTIME) 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. */ @@ -41,4 +48,9 @@ public @interface FullTextIndex * An array of the field names that should be indexed. */ public String[] fields (); + + /** + * What parser/dictionary to use for this index. + */ + public Configuration configuration () default Configuration.English; } diff --git a/src/java/com/samskivert/depot/impl/BuildVisitor.java b/src/java/com/samskivert/depot/impl/BuildVisitor.java index efa8dac..20e94f5 100644 --- a/src/java/com/samskivert/depot/impl/BuildVisitor.java +++ b/src/java/com/samskivert/depot/impl/BuildVisitor.java @@ -525,7 +525,9 @@ public abstract class BuildVisitor implements ExpressionVisitor } comma = true; + _builder.append("("); field.left.accept(this); + _builder.append(")"); if (field.right == Order.DESC) { // ascending is default, print nothing unless explicitly descending _builder.append(" desc"); diff --git a/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java b/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java index 32a13d2..c7cf19b 100644 --- a/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java @@ -40,6 +40,7 @@ import com.samskivert.util.StringUtil; import com.samskivert.depot.DatabaseException; import com.samskivert.depot.PersistentRecord; import com.samskivert.depot.annotation.FullTextIndex; +import com.samskivert.depot.annotation.FullTextIndex.Configuration; import com.samskivert.depot.expression.EpochSeconds; import com.samskivert.depot.operator.Conditionals.FullText; @@ -48,17 +49,25 @@ import static com.samskivert.Log.log; public class PostgreSQLBuilder 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 { @Override public void visit (FullText.Match match) { 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) { - _builder.append("rank("); + _builder.append(PG83 ? "ts_rank" : "rank").append("("); 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) { @@ -73,13 +82,19 @@ public class PostgreSQLBuilder // _builder.append(" = any (?)"); // } - protected PGBuildVisitor (DepotTypes types) { - super(types); + protected FullTextIndex getFTIndex (FullText definition) + { + DepotMarshaller marsh = _types.getMarshaller(definition.getPersistentClass()); + return marsh.getFullTextIndex(definition.getName()); } @Override protected void appendIdentifier (String field) { _builder.append("\"").append(field).append("\""); } + + protected PGBuildVisitor (DepotTypes types) { + super(types); + } } public class PGBindVisitor extends BindVisitor @@ -181,7 +196,9 @@ public class PostgreSQLBuilder // build the UPDATE StringBuilder initColumn = new StringBuilder("UPDATE "). 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 ++) { if (ii > 0) { @@ -193,11 +210,20 @@ public class PostgreSQLBuilder } initColumn.append(")"); + String triggerFun = PG83 ? "tsvector_update_trigger" : "tsearch2"; + // build the CREATE TRIGGER StringBuilder createTrigger = new StringBuilder("CREATE TRIGGER "). 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(", "); + + if (PG83) { + createTrigger.append("'"). + append(translateFTConfig(fts.configuration())). + append("', "); + } for (int ii = 0; ii < fields.length; ii ++) { if (ii > 0) { @@ -210,7 +236,8 @@ public class PostgreSQLBuilder // build the CREATE INDEX StringBuilder createIndex = new StringBuilder("CREATE INDEX "). 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(); try { @@ -307,4 +334,23 @@ public class PostgreSQLBuilder 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); + } + } } diff --git a/src/java/com/samskivert/depot/operator/Conditionals.java b/src/java/com/samskivert/depot/operator/Conditionals.java index aec40de..c7fd102 100644 --- a/src/java/com/samskivert/depot/operator/Conditionals.java +++ b/src/java/com/samskivert/depot/operator/Conditionals.java @@ -358,8 +358,6 @@ public abstract class Conditionals protected SQLExpression _elseExp; } - - /** * An attempt at a dialect-agnostic full-text search condition, such as MySQL's MATCH() and * PostgreSQL's @@ TO_TSQUERY(...) abilities. @@ -440,15 +438,15 @@ public abstract class Conditionals return _pClass; } - public String getQuery () - { - return _query; - } - public String getName () { return _name; } + + public String getQuery () + { + return _query; + } protected String toString (String subType) {