diff --git a/src/java/com/samskivert/jdbc/depot/BindVisitor.java b/src/java/com/samskivert/jdbc/depot/BindVisitor.java index 110cc6c..cc7d09c 100644 --- a/src/java/com/samskivert/jdbc/depot/BindVisitor.java +++ b/src/java/com/samskivert/jdbc/depot/BindVisitor.java @@ -44,7 +44,7 @@ import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.jdbc.depot.expression.ValueExp; import com.samskivert.jdbc.depot.operator.Conditionals.In; import com.samskivert.jdbc.depot.operator.Conditionals.IsNull; -import com.samskivert.jdbc.depot.operator.Conditionals.Match; +import com.samskivert.jdbc.depot.operator.Conditionals.FullTextMatch; import com.samskivert.jdbc.depot.operator.Logic.Not; import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator; import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator; @@ -128,7 +128,7 @@ public class BindVisitor implements ExpressionVisitor } } - public void visit (Match match) throws Exception + public void visit (FullTextMatch match) throws Exception { // we never get here } diff --git a/src/java/com/samskivert/jdbc/depot/BuildVisitor.java b/src/java/com/samskivert/jdbc/depot/BuildVisitor.java index 2108425..2e0e675 100644 --- a/src/java/com/samskivert/jdbc/depot/BuildVisitor.java +++ b/src/java/com/samskivert/jdbc/depot/BuildVisitor.java @@ -46,7 +46,7 @@ import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.jdbc.depot.expression.ValueExp; import com.samskivert.jdbc.depot.operator.Conditionals.In; import com.samskivert.jdbc.depot.operator.Conditionals.IsNull; -import com.samskivert.jdbc.depot.operator.Conditionals.Match; +import com.samskivert.jdbc.depot.operator.Conditionals.FullTextMatch; import com.samskivert.jdbc.depot.operator.Logic.Not; import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator; import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator; @@ -197,11 +197,8 @@ public abstract class BuildVisitor implements ExpressionVisitor _builder.append(")"); } - public void visit (Match match) - throws Exception - { - throw new IllegalArgumentException("Match() is MySQL-specific."); - } + public abstract void visit (FullTextMatch match) + throws Exception; public void visit (ColumnExp columnExp) throws Exception @@ -456,6 +453,8 @@ public abstract class BuildVisitor implements ExpressionVisitor { _builder.append("delete from "); appendTableName(deleteClause.getPersistentClass()); + _builder.append(" as "); + appendTableAbbreviation(deleteClause.getPersistentClass()); _builder.append(" "); deleteClause.getWhereClause().accept(this); } diff --git a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java index 8154d7b..74cc70d 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java +++ b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java @@ -40,6 +40,7 @@ import java.util.logging.Level; import com.samskivert.io.PersistenceException; import com.samskivert.jdbc.depot.annotation.Computed; import com.samskivert.jdbc.depot.annotation.Entity; +import com.samskivert.jdbc.depot.annotation.FullTextIndex; import com.samskivert.jdbc.depot.annotation.GeneratedValue; import com.samskivert.jdbc.depot.annotation.Id; import com.samskivert.jdbc.depot.annotation.Index; @@ -95,6 +96,14 @@ public class DepotMarshaller context.tableGenerators.put(generator.name(), generator); } + // if there are FTS indexes in the Table, map those out here for future use + Table table = pclass.getAnnotation(Table.class); + if (table != null) { + for (FullTextIndex fts : table.fullTextIndexes()) { + _fullTextIndexes.put(fts.name(), fts); + } + } + // introspect on the class and create marshallers for persistent fields ArrayList fields = new ArrayList(); for (Field field : _pclass.getFields()) { @@ -195,6 +204,14 @@ public class DepotMarshaller _allFields = fields.toArray(new String[fields.size()]); } + /** + * Returns the persistent class this is object is a marshaller for. + */ + public Class getPersistentClass () + { + return _pclass; + } + /** * Returns the name of the table in which persistent instances of our class are stored. By * default this is the classname of the persistent object without the package. @@ -220,6 +237,14 @@ public class DepotMarshaller return _columnFields; } + /** + * Return the {@link FullTextIndex} registered under the given name, or null if none. + */ + public FullTextIndex getFullTextIndex (String name) + { + return _fullTextIndexes.get(name); + } + /** * Returns the {@link FieldMarshaller} for a named field on our persistent class. */ @@ -448,17 +473,17 @@ public class DepotMarshaller * be created. If the schema version specified by the persistent object is newer than the * database schema, it will be migrated. */ - protected void init (PersistenceContext ctx) + protected void init (final PersistenceContext ctx) throws PersistenceException { - SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.TRIVIAL); - if (_initialized) { // sanity check throw new IllegalStateException( "Cannot re-initialize marshaller [type=" + _pclass + "]."); } _initialized = true; + final SQLBuilder builder = ctx.getSQLBuilder(new DepotTypes(ctx, _pclass)); + // perform the context-sensitive initialization of the field marshallers for (FieldMarshaller fm : _fields.values()) { fm.init(builder); @@ -494,7 +519,7 @@ public class DepotMarshaller UniqueConstraint[] uCons = table.uniqueConstraints(); uniqueConstraintColumns = new String[uCons.length][]; for (int kk = 0; kk < uCons.length; kk ++) { - String[] columns = uCons[kk].columnNames(); + String[] columns = uCons[kk].fieldNames(); for (int ii = 0; ii < columns.length; ii ++) { FieldMarshaller fm = getFieldMarshaller(columns[ii]); if (fm == null) { @@ -542,8 +567,13 @@ public class DepotMarshaller primaryKeyColumns[ii] = _pkColumns.get(ii).getColumnName(); } } - liaison.createTableIfMissing(conn, getTableName(), columns, fDeclarations, - fUniqueConstraintColumns, primaryKeyColumns); + if (liaison.createTableIfMissing(conn, getTableName(), columns, fDeclarations, + fUniqueConstraintColumns, primaryKeyColumns)) { + for (FullTextIndex fti : _fullTextIndexes.values()) { + builder.addFullTextSearch(conn, DepotMarshaller.this, fti); + } + } + updateVersion(conn, liaison, _schemaVersion); return 0; } @@ -863,6 +893,9 @@ public class DepotMarshaller /** The fields of our object with directly corresponding table columns. */ protected String[] _columnFields; + /** A mapping of all of the full text index annotations for our persistent record. */ + protected Map _fullTextIndexes = new HashMap(); + /** The version of our persistent object schema as specified in the class definition. */ protected int _schemaVersion = -1; diff --git a/src/java/com/samskivert/jdbc/depot/DepotRepository.java b/src/java/com/samskivert/jdbc/depot/DepotRepository.java index cee88ec..e4b1037 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotRepository.java +++ b/src/java/com/samskivert/jdbc/depot/DepotRepository.java @@ -24,7 +24,6 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; diff --git a/src/java/com/samskivert/jdbc/depot/DepotTypes.java b/src/java/com/samskivert/jdbc/depot/DepotTypes.java index 134fd8d..620ae9a 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotTypes.java +++ b/src/java/com/samskivert/jdbc/depot/DepotTypes.java @@ -22,11 +22,10 @@ package com.samskivert.jdbc.depot; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -69,41 +68,11 @@ public class DepotTypes return new DepotTypes(ctx, classSet); } - public String getTableName (Class cl) - { - return _classMap.get(cl).getTableName(); - } - - public String getTableAbbreviation (Class cl) - { - int ix = _classList.indexOf(cl); - if (ix < 0) { - throw new IllegalArgumentException("Unknown persistence class: " + cl); - } - return "T" + (ix+1); - } - - public String getColumnName (Class cl, String field) - { - return getMarshaller(cl).getFieldMarshaller(field).getColumnName(); - } - - public DepotMarshaller getMarshaller (Class cl) - { - return _classMap.get(cl); - } - - public void addClass (PersistenceContext ctx, Class type) - throws PersistenceException - { - if (_classMap.containsKey(type)) { - return; - } - _classList.add(type); - _classMap.put(type, ctx.getMarshaller(type)); - } - - protected DepotTypes (PersistenceContext ctx, Set> others) + /** + * Create a new DepotTypes with the given {@link PersistenceContext} and a collection of + * persistent record classes. + */ + public DepotTypes (PersistenceContext ctx, Collection> others) throws PersistenceException { for (Class c : others) { @@ -111,14 +80,114 @@ public class DepotTypes } } + /** + * Create a new DepotTypes with the given {@link PersistenceContext} and the given + * persistent record. + */ + public DepotTypes (PersistenceContext ctx, Class pClass) + throws PersistenceException + { + addClass(ctx, pClass); + } + + /** + * Return the full table name of the given persistent class, which must have been previously + * registered with this object. + */ + public String getTableName (Class cl) + { + return _classMap.get(cl).getTableName(); + } + + /** + * Return the current abbreviation by which we refer to the table associated with the given + * persistent record -- which must have been previously registered with this object. If the + * useTableAbbreviations flag is false, we return the full table name instead. + */ + public String getTableAbbreviation (Class cl) + { + if (_useTableAbbreviations) { + Integer ix = _classIx.get(cl); + if (ix == null) { + throw new IllegalArgumentException("Unknown persistence class: " + cl); + } + return "T" + (ix+1); + } + return getTableName(cl); + } + + /** + * Return the associated database column of the given field of the given persistent class, + * or null if there is no associated marshaller, or if the field is unknown on the class, or + * if the field does not directly associate with a column. + */ + public String getColumnName (Class cl, String field) + { + DepotMarshaller dm = getMarshaller(cl); + if (dm != null) { + FieldMarshaller fm = dm.getFieldMarshaller(field); + if (fm != null) { + return fm.getColumnName(); + } + } + return null; + } + + /** + * Return the {@link DepotMarshaller} associated with the given persistent class, or null + * if the class has not been previously registered with this object. + */ + public DepotMarshaller getMarshaller (Class cl) + { + return _classMap.get(cl); + } + + /** + * Register a new persistent class with this object. + */ + public void addClass (PersistenceContext ctx, Class type) + throws PersistenceException + { + if (_classMap.containsKey(type)) { + return; + } + _classMap.put(type, ctx.getMarshaller(type)); + _classIx.put(type, _classIx.size()); + } + + /** + * Return the value of the useTableAbbreviations flag, which governs the behaviour when + * referencing columns during SQL construction. Normally, this flag is on, and tables are + * referenced as e.g. T1.itemId, but there are cases of weak/broken SQL where abbreviations + * may not be brought into play. In these cases we prepend the full table name. + */ + public boolean getUseTableAbbreviations () + { + return _useTableAbbreviations; + } + + /** + * Sets the value of the useTableAbbreviations flag, which governs the behaviour when + * referencing columns during SQL construction. Normally, this flag is on, and tables are + * referenced as e.g. T1.itemId, but there are cases of weak/broken SQL where abbreviations + * may not be brought into play. In these cases we prepend the full table name. + */ + public void setUseTableAbbreviations (boolean doUse) + { + _useTableAbbreviations = doUse; + } + + // constructor used to create TRIVIAL protected DepotTypes () { } - /** A list of referenced classes, used to generate table abbreviations. */ - protected List> _classList = - new ArrayList>(); + /** Classes mapped to integers, used for table abbreviation indexing. */ + protected Map _classIx = new HashMap(); /** Classes mapped to marshallers, used for table names and field lists. */ protected Map _classMap = new HashMap(); + + /** When false, override the normal table abbreviations and return full table names instead. */ + protected boolean _useTableAbbreviations = true; } diff --git a/src/java/com/samskivert/jdbc/depot/MySQLBuilder.java b/src/java/com/samskivert/jdbc/depot/MySQLBuilder.java index 719759e..e3940ca 100644 --- a/src/java/com/samskivert/jdbc/depot/MySQLBuilder.java +++ b/src/java/com/samskivert/jdbc/depot/MySQLBuilder.java @@ -22,11 +22,14 @@ package com.samskivert.jdbc.depot; import java.sql.Blob; import java.sql.Clob; +import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; +import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; +import com.samskivert.jdbc.JDBCUtil; import com.samskivert.jdbc.depot.FieldMarshaller.BooleanMarshaller; import com.samskivert.jdbc.depot.FieldMarshaller.ByteArrayMarshaller; import com.samskivert.jdbc.depot.FieldMarshaller.ByteEnumMarshaller; @@ -38,44 +41,54 @@ import com.samskivert.jdbc.depot.FieldMarshaller.IntMarshaller; import com.samskivert.jdbc.depot.FieldMarshaller.LongMarshaller; import com.samskivert.jdbc.depot.FieldMarshaller.ObjectMarshaller; import com.samskivert.jdbc.depot.FieldMarshaller.ShortMarshaller; +import com.samskivert.jdbc.depot.annotation.FullTextIndex; +import com.samskivert.jdbc.depot.clause.DeleteClause; import com.samskivert.jdbc.depot.expression.ColumnExp; -import com.samskivert.jdbc.depot.operator.Conditionals.Match; +import com.samskivert.jdbc.depot.operator.Conditionals.FullTextMatch; public class MySQLBuilder extends SQLBuilder { public class MSBuildVisitor extends BuildVisitor { - protected MSBuildVisitor (DepotTypes types) - { - super(types); - } - @Override - public void visit (Match match) + public void visit (FullTextMatch match) throws Exception { _builder.append("match("); - ColumnExp[] columns = match.getColumns(); - for (int ii = 0; ii < columns.length; ii ++) { + Class pClass = match.getPersistentRecord(); + FullTextIndex fts = _types.getMarshaller(pClass).getFullTextIndex(match.getName()); + String[] fields = fts.fieldNames(); + for (int ii = 0; ii < fields.length; ii ++) { if (ii > 0) { _builder.append(", "); } - columns[ii].accept(this); + new ColumnExp(pClass, fields[ii]).accept(this); } - _builder.append(") against (?"); - switch (match.getMode()) { - case BOOLEAN: - _builder.append(" in boolean mode"); - break; - case NATURAL_LANGUAGE: - _builder.append(" in natural language mode"); - break; + _builder.append(") against (? in boolean mode)"); + } + + @Override + public void visit (DeleteClause deleteClause) + throws Exception + { + _builder.append("delete from "); + appendTableName(deleteClause.getPersistentClass()); + _builder.append(" "); + + // MySQL can't do DELETE FROM SomeTable AS T1, so we turn off abbreviations briefly. + boolean savedFlag = _types.getUseTableAbbreviations(); + _types.setUseTableAbbreviations(false); + try { + deleteClause.getWhereClause().accept(this); + } finally { + _types.setUseTableAbbreviations(savedFlag); } - if (match.isQueryExpansion()) { - _builder.append(" with query expansion"); - } - _builder.append(")"); + } + + protected MSBuildVisitor (DepotTypes types) + { + super(types); } protected void appendTableName (Class type) @@ -107,7 +120,7 @@ public class MySQLBuilder } @Override - public void visit (Match match) + public void visit (FullTextMatch match) throws Exception { _stmt.setString(_argIdx ++, match.getQuery()); @@ -119,6 +132,34 @@ public class MySQLBuilder super(types); } + @Override + public boolean addFullTextSearch ( + Connection conn, DepotMarshaller marshaller, FullTextIndex fts) + throws SQLException + { + Class pClass = marshaller.getPersistentClass(); + StringBuilder update = new StringBuilder("ALTER TABLE "). + append(marshaller.getTableName()).append(" ADD FULLTEXT INDEX ftsIx_"). + append(fts.name()).append(" ("); + String[] fields = fts.fieldNames(); + for (int ii = 0; ii < fields.length; ii ++) { + if (ii > 0) { + update.append(", "); + } + update.append(_types.getColumnName(pClass, fields[ii])); + } + update.append(")"); + + PreparedStatement stmt = null; + try { + stmt = conn.prepareStatement(update.toString()); + stmt.executeUpdate(); + } finally { + JDBCUtil.close(stmt); + } + return true; + } + @Override protected BuildVisitor getBuildVisitor () { diff --git a/src/java/com/samskivert/jdbc/depot/PostgreSQLBuilder.java b/src/java/com/samskivert/jdbc/depot/PostgreSQLBuilder.java index c530b5c..a1b1fbd 100644 --- a/src/java/com/samskivert/jdbc/depot/PostgreSQLBuilder.java +++ b/src/java/com/samskivert/jdbc/depot/PostgreSQLBuilder.java @@ -22,11 +22,17 @@ package com.samskivert.jdbc.depot; import java.sql.Blob; import java.sql.Clob; +import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; +import com.samskivert.jdbc.DatabaseLiaison; +import com.samskivert.jdbc.JDBCUtil; +import com.samskivert.jdbc.LiaisonRegistry; import com.samskivert.jdbc.depot.FieldMarshaller.BooleanMarshaller; import com.samskivert.jdbc.depot.FieldMarshaller.ByteArrayMarshaller; import com.samskivert.jdbc.depot.FieldMarshaller.ByteEnumMarshaller; @@ -38,12 +44,24 @@ import com.samskivert.jdbc.depot.FieldMarshaller.IntMarshaller; import com.samskivert.jdbc.depot.FieldMarshaller.LongMarshaller; import com.samskivert.jdbc.depot.FieldMarshaller.ObjectMarshaller; import com.samskivert.jdbc.depot.FieldMarshaller.ShortMarshaller; +import com.samskivert.jdbc.depot.annotation.FullTextIndex; +import com.samskivert.jdbc.depot.operator.Conditionals.FullTextMatch; + +import com.samskivert.Log; public class PostgreSQLBuilder extends SQLBuilder { public class PGBuildVisitor extends BuildVisitor { + @Override + public void visit (FullTextMatch match) + throws Exception + { + appendField("ftsCol_" + match.getName()); + _builder.append(" @@ TO_TSQUERY('default', ?)"); + } + protected PGBuildVisitor (DepotTypes types) { super(types); @@ -72,6 +90,13 @@ public class PostgreSQLBuilder public class PGBindVisitor extends BindVisitor { + @Override + public void visit (FullTextMatch match) + throws Exception + { + _stmt.setString(_argIdx ++, match.getQuery()); + } + protected PGBindVisitor (DepotTypes types, PreparedStatement stmt) { super(types, stmt); @@ -83,6 +108,71 @@ public class PostgreSQLBuilder super(types); } + @Override + public boolean addFullTextSearch ( + Connection conn, DepotMarshaller marshaller, FullTextIndex fts) + throws SQLException + { + Class pClass = marshaller.getPersistentClass(); + DatabaseLiaison liaison = LiaisonRegistry.getLiaison(conn); + + String[] fields = fts.fieldNames(); + + String table = marshaller.getTableName(); + String column = "ftsCol_" + fts.name(); + String index = "ftsIx_" + fts.name(); + String trigger = "ftsTrig_" + fts.name(); + + // build the UPDATE + StringBuilder initColumn = new StringBuilder("UPDATE "). + append(liaison.tableSQL(table)).append(" SET ").append(liaison.columnSQL(column)). + append(" = TO_TSVECTOR('default', "); + + for (int ii = 0; ii < fields.length; ii ++) { + if (ii > 0) { + initColumn.append(" || ' ' || "); + } + initColumn.append("COALESCE("). + append(liaison.columnSQL(_types.getColumnName(pClass, fields[ii]))). + append(", '')"); + } + initColumn.append(")"); + + // 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.columnSQL(column)).append(", "); + + for (int ii = 0; ii < fields.length; ii ++) { + if (ii > 0) { + createTrigger.append(", "); + } + createTrigger.append(liaison.columnSQL(_types.getColumnName(pClass, fields[ii]))); + } + createTrigger.append(")"); + + // 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(")"); + + Statement stmt = conn.createStatement(); + try { + Log.info( + "Adding full-text search column, index and trigger: " + column + ", " + + index + ", " + trigger); + liaison.addColumn(conn, table, column, "TSVECTOR", true); + stmt.executeUpdate(initColumn.toString()); + stmt.executeUpdate(createIndex.toString()); + stmt.executeUpdate(createTrigger.toString()); + + } finally { + JDBCUtil.close(stmt); + } + return true; + } + @Override protected BuildVisitor getBuildVisitor () { diff --git a/src/java/com/samskivert/jdbc/depot/SQLBuilder.java b/src/java/com/samskivert/jdbc/depot/SQLBuilder.java index 83a080a..c3b9ecb 100644 --- a/src/java/com/samskivert/jdbc/depot/SQLBuilder.java +++ b/src/java/com/samskivert/jdbc/depot/SQLBuilder.java @@ -26,8 +26,10 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import com.samskivert.jdbc.depot.annotation.Column; +import com.samskivert.jdbc.depot.annotation.FullTextIndex; import com.samskivert.jdbc.depot.annotation.GeneratedValue; import com.samskivert.jdbc.depot.clause.QueryClause; +import com.samskivert.jdbc.depot.operator.Conditionals; /** * At the heart of Depot's SQL generation, this object constructs two {@link ExpressionVisitor} @@ -171,6 +173,17 @@ public abstract class SQLBuilder return builder.toString(); } + /** + * Add full-text search capabilities, as defined by the provided {@link FullTextIndex}, on + * the table associated with the given {@link DepotMarshaller}. This is a highly database + * specific operation and must thus be implemented by each dialect subclass. + * + * {@see Conditionals.FullTextIndex} + */ + public abstract boolean addFullTextSearch ( + Connection conn, DepotMarshaller marshaller, FullTextIndex fts) + throws SQLException; + /** * Overridden by subclasses to create a dialect-specific {@link BuildVisitor}. */ diff --git a/src/java/com/samskivert/jdbc/depot/annotation/FullTextIndex.java b/src/java/com/samskivert/jdbc/depot/annotation/FullTextIndex.java new file mode 100644 index 0000000..386fb4c --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/annotation/FullTextIndex.java @@ -0,0 +1,44 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2007 Michael Bayne, Pär Winzell +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc.depot.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to specify that a full text index is to be included in the generated DDL + * for a table. + */ +@Target(value={}) +@Retention(value=RetentionPolicy.RUNTIME) +public @interface FullTextIndex +{ + /** + * An identifier for this index, unique with the scope of the record. + */ + public String name (); + + /** + * An array of the field names that should be indexed. + */ + public String[] fieldNames (); +} diff --git a/src/java/com/samskivert/jdbc/depot/annotation/Table.java b/src/java/com/samskivert/jdbc/depot/annotation/Table.java index 242da93..b74bb6c 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/Table.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/Table.java @@ -40,4 +40,9 @@ public @interface Table * Defaults to no additional constraints. */ public UniqueConstraint[] uniqueConstraints () default {}; + + /** + * Full-text search indexes defined on this entity, if any. Defaults to none. + */ + public FullTextIndex[] fullTextIndexes () default {}; } diff --git a/src/java/com/samskivert/jdbc/depot/annotation/UniqueConstraint.java b/src/java/com/samskivert/jdbc/depot/annotation/UniqueConstraint.java index dde86fb..1cbb8d2 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/UniqueConstraint.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/UniqueConstraint.java @@ -33,7 +33,7 @@ import java.lang.annotation.Target; public @interface UniqueConstraint { /** - * An array of the column names that make up the constraint + * An array of the field names that make up the constraint */ - public String[] columnNames () default {}; + public String[] fieldNames () default {}; } diff --git a/src/java/com/samskivert/jdbc/depot/expression/ExpressionVisitor.java b/src/java/com/samskivert/jdbc/depot/expression/ExpressionVisitor.java index df349c0..acb24c1 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/ExpressionVisitor.java +++ b/src/java/com/samskivert/jdbc/depot/expression/ExpressionVisitor.java @@ -40,7 +40,7 @@ import com.samskivert.jdbc.depot.clause.Where; import com.samskivert.jdbc.depot.operator.Conditionals.In; import com.samskivert.jdbc.depot.operator.Conditionals.IsNull; -import com.samskivert.jdbc.depot.operator.Conditionals.Match; +import com.samskivert.jdbc.depot.operator.Conditionals.FullTextMatch; import com.samskivert.jdbc.depot.operator.Logic.Not; import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator; import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator; @@ -70,7 +70,7 @@ public interface ExpressionVisitor throws Exception; public void visit (In in) throws Exception; - public void visit (Match match) + public void visit (FullTextMatch match) throws Exception; public void visit (ColumnExp columnExp) throws Exception; diff --git a/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java b/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java index debaa02..692dfab 100644 --- a/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java +++ b/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java @@ -247,39 +247,33 @@ public abstract class Conditionals } } - /** The MySQL 'match (...) against (...)' operator. */ - @Deprecated - public static class Match + /** + * An attempt at a dialect-agnostic full-text search condition, such as MySQL's MATCH() and + * PostgreSQL's @@ TO_TSQUERY(...) abilities. + */ + public static class FullTextMatch implements SQLOperator { - public enum Mode { DEFAULT, BOOLEAN, NATURAL_LANGUAGE }; - - public Match (String query, Mode mode, boolean queryExpansion, ColumnExp... columns) + public FullTextMatch (Class pClass, String name, String query) { + _pClass = pClass; + _name = name; _query = query; - _queryExpansion = queryExpansion; - _mode = mode; - _columns = columns; } + public Class getPersistentRecord () + { + return _pClass; + } + public String getQuery () { return _query; } - public Mode getMode () + public String getName () { - return _mode; - } - - public boolean isQueryExpansion () - { - return _queryExpansion; - } - - public ColumnExp[] getColumns () - { - return _columns; + return _name; } // from SQLExpression @@ -291,14 +285,10 @@ public abstract class Conditionals // from SQLExpression public void addClasses (Collection> classSet) { - for (ColumnExp column : _columns) { - column.addClasses(classSet); - } } + protected Class _pClass; + protected String _name; protected String _query; - protected Mode _mode; - protected boolean _queryExpansion; - protected ColumnExp[] _columns; } }