Full text index support from Zell, also a fix for deletion.
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<T extends PersistentRecord>
|
||||
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<String> fields = new ArrayList<String>();
|
||||
for (Field field : _pclass.getFields()) {
|
||||
@@ -195,6 +204,14 @@ public class DepotMarshaller<T extends PersistentRecord>
|
||||
_allFields = fields.toArray(new String[fields.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the persistent class this is object is a marshaller for.
|
||||
*/
|
||||
public Class<T> 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<T extends PersistentRecord>
|
||||
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<T extends PersistentRecord>
|
||||
* 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<T extends PersistentRecord>
|
||||
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<T extends PersistentRecord>
|
||||
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<T extends PersistentRecord>
|
||||
/** 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<String, FullTextIndex> _fullTextIndexes = new HashMap<String, FullTextIndex>();
|
||||
|
||||
/** The version of our persistent object schema as specified in the class definition. */
|
||||
protected int _schemaVersion = -1;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<? extends PersistentRecord> cl)
|
||||
{
|
||||
return _classMap.get(cl).getTableName();
|
||||
}
|
||||
|
||||
public String getTableAbbreviation (Class<? extends PersistentRecord> cl)
|
||||
{
|
||||
int ix = _classList.indexOf(cl);
|
||||
if (ix < 0) {
|
||||
throw new IllegalArgumentException("Unknown persistence class: " + cl);
|
||||
}
|
||||
return "T" + (ix+1);
|
||||
}
|
||||
|
||||
public String getColumnName (Class<? extends PersistentRecord> cl, String field)
|
||||
{
|
||||
return getMarshaller(cl).getFieldMarshaller(field).getColumnName();
|
||||
}
|
||||
|
||||
public DepotMarshaller getMarshaller (Class cl)
|
||||
{
|
||||
return _classMap.get(cl);
|
||||
}
|
||||
|
||||
public void addClass (PersistenceContext ctx, Class <? extends PersistentRecord> type)
|
||||
throws PersistenceException
|
||||
{
|
||||
if (_classMap.containsKey(type)) {
|
||||
return;
|
||||
}
|
||||
_classList.add(type);
|
||||
_classMap.put(type, ctx.getMarshaller(type));
|
||||
}
|
||||
|
||||
protected DepotTypes (PersistenceContext ctx, Set<Class<? extends PersistentRecord>> others)
|
||||
/**
|
||||
* Create a new DepotTypes with the given {@link PersistenceContext} and a collection of
|
||||
* persistent record classes.
|
||||
*/
|
||||
public DepotTypes (PersistenceContext ctx, Collection<Class<? extends PersistentRecord>> others)
|
||||
throws PersistenceException
|
||||
{
|
||||
for (Class<? extends PersistentRecord> 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<? extends PersistentRecord> 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<? extends PersistentRecord> 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<? extends PersistentRecord> 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<? extends PersistentRecord> 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<? extends PersistentRecord> cl)
|
||||
{
|
||||
return _classMap.get(cl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new persistent class with this object.
|
||||
*/
|
||||
public void addClass (PersistenceContext ctx, Class <? extends PersistentRecord> 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<Class<? extends PersistentRecord>> _classList =
|
||||
new ArrayList<Class<? extends PersistentRecord>>();
|
||||
/** Classes mapped to integers, used for table abbreviation indexing. */
|
||||
protected Map<Class, Integer> _classIx = new HashMap<Class, Integer>();
|
||||
|
||||
/** Classes mapped to marshallers, used for table names and field lists. */
|
||||
protected Map<Class, DepotMarshaller> _classMap = new HashMap<Class, DepotMarshaller>();
|
||||
|
||||
/** When false, override the normal table abbreviations and return full table names instead. */
|
||||
protected boolean _useTableAbbreviations = true;
|
||||
}
|
||||
|
||||
@@ -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<? extends PersistentRecord> 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<? extends PersistentRecord> 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<? extends PersistentRecord> 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 <T extends PersistentRecord> boolean addFullTextSearch (
|
||||
Connection conn, DepotMarshaller<T> marshaller, FullTextIndex fts)
|
||||
throws SQLException
|
||||
{
|
||||
Class<T> 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 ()
|
||||
{
|
||||
|
||||
@@ -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 <T extends PersistentRecord> boolean addFullTextSearch (
|
||||
Connection conn, DepotMarshaller<T> marshaller, FullTextIndex fts)
|
||||
throws SQLException
|
||||
{
|
||||
Class<T> 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 ()
|
||||
{
|
||||
|
||||
@@ -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 <T extends PersistentRecord> boolean addFullTextSearch (
|
||||
Connection conn, DepotMarshaller<T> marshaller, FullTextIndex fts)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Overridden by subclasses to create a dialect-specific {@link BuildVisitor}.
|
||||
*/
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<? extends PersistentRecord> pClass, String name, String query)
|
||||
{
|
||||
_pClass = pClass;
|
||||
_name = name;
|
||||
_query = query;
|
||||
_queryExpansion = queryExpansion;
|
||||
_mode = mode;
|
||||
_columns = columns;
|
||||
}
|
||||
|
||||
public Class<? extends PersistentRecord> 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<Class<? extends PersistentRecord>> classSet)
|
||||
{
|
||||
for (ColumnExp column : _columns) {
|
||||
column.addClasses(classSet);
|
||||
}
|
||||
}
|
||||
|
||||
protected Class<? extends PersistentRecord> _pClass;
|
||||
protected String _name;
|
||||
protected String _query;
|
||||
protected Mode _mode;
|
||||
protected boolean _queryExpansion;
|
||||
protected ColumnExp[] _columns;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user