Extend our Index annotation to accept a 'complex' attribute. If an index ixFoo is marked complex for a given record, Depot will call the static method ixFooDefinition on that record class and expect a List<Tuple<SQLExpression, OrderBy.Order>> return value.

The purpose of all this is to create function (or expression, rather) indexes, which PostgreSQL does quite happily.
This commit is contained in:
Par Winzell
2008-12-31 00:25:29 +00:00
parent e3aed39330
commit 0ccd8bdb82
8 changed files with 227 additions and 24 deletions
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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
@@ -39,4 +39,11 @@ public @interface Index
/** Defines the fields on which the index operates. */
String[] fields () default {};
/** Whether or not this is a complex index. If true, a static method
* must be defined on the record that declares this index of the signature:
* <pre>public static List<Tuple<SQLExpression, OrderBy.Order>> indexNameExpression ()</pre>
* which should return the index's defining expressions and whether each one is ascending
* or descending. */
boolean complex () default false;
}
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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
@@ -32,7 +32,9 @@ import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.Key;
import com.samskivert.depot.MultiKey;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.CreateIndexClause;
import com.samskivert.depot.clause.DeleteClause;
import com.samskivert.depot.clause.DropIndexClause;
import com.samskivert.depot.clause.FieldDefinition;
import com.samskivert.depot.clause.ForUpdate;
import com.samskivert.depot.clause.FromOverride;
@@ -41,6 +43,7 @@ import com.samskivert.depot.clause.InsertClause;
import com.samskivert.depot.clause.Join;
import com.samskivert.depot.clause.Limit;
import com.samskivert.depot.clause.OrderBy;
import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.clause.UpdateClause;
import com.samskivert.depot.clause.WhereClause;
@@ -59,6 +62,7 @@ import com.samskivert.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.depot.operator.SQLOperator.MultiOperator;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Tuple;
/**
* Implements the base functionality of the argument-binding pass of {@link SQLBuilder}. Dialectal
@@ -267,6 +271,18 @@ public class BindVisitor implements ExpressionVisitor
deleteClause.getWhereClause().accept(this);
}
public void visit (CreateIndexClause<? extends PersistentRecord> createIndexClause)
{
for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) {
field.left.accept(this);
}
}
public void visit (DropIndexClause<? extends PersistentRecord> createIndexClause)
{
// do nothing
}
protected BindVisitor (DepotTypes types, Connection conn, PreparedStatement stmt)
{
_types = types;
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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
@@ -30,7 +30,9 @@ import com.samskivert.depot.Key;
import com.samskivert.depot.MultiKey;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.Computed;
import com.samskivert.depot.clause.CreateIndexClause;
import com.samskivert.depot.clause.DeleteClause;
import com.samskivert.depot.clause.DropIndexClause;
import com.samskivert.depot.clause.FieldDefinition;
import com.samskivert.depot.clause.FieldOverride;
import com.samskivert.depot.clause.ForUpdate;
@@ -43,6 +45,7 @@ import com.samskivert.depot.clause.OrderBy;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.clause.UpdateClause;
import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.EpochSeconds;
import com.samskivert.depot.expression.FunctionExp;
@@ -56,6 +59,7 @@ import com.samskivert.depot.operator.Conditionals.IsNull;
import com.samskivert.depot.operator.Logic.Not;
import com.samskivert.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.depot.operator.SQLOperator.MultiOperator;
import com.samskivert.util.Tuple;
/**
* Implements the base functionality of the SQL-building pass of {@link SQLBuilder}. Dialectal
@@ -476,6 +480,45 @@ public abstract class BuildVisitor implements ExpressionVisitor
_builder.append(")");
}
public void visit (CreateIndexClause<? extends PersistentRecord> createIndexClause)
{
_builder.append("create ");
if (createIndexClause.isUnique()) {
_builder.append("unique ");
}
_builder.append("index ");
appendIdentifier(createIndexClause.getName());
_builder.append(" on ");
appendTableName(createIndexClause.getPersistentClass());
_builder.append(" (");
// turn off table abbreviations here
_defaultType = createIndexClause.getPersistentClass();
boolean comma = false;
for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) {
if (comma) {
_builder.append(", ");
}
comma = true;
field.left.accept(this);
if (field.right == Order.DESC) {
// ascending is default, print nothing unless explicitly descending
_builder.append(" desc");
}
}
// turn them back on
_defaultType = null;
_builder.append(")");
}
public void visit (DropIndexClause<? extends PersistentRecord> dropIndexClause)
{
_builder.append("drop index ");
appendIdentifier(dropIndexClause.getName());
}
protected abstract void appendIdentifier (String field);
protected void appendTableName (Class<? extends PersistentRecord> type)
@@ -588,8 +631,10 @@ public abstract class BuildVisitor implements ExpressionVisitor
// if we get this far we hopefully have a table to select from
if (tableClass != null) {
appendTableAbbreviation(tableClass);
_builder.append(".");
if (_defaultType != tableClass) {
appendTableAbbreviation(tableClass);
_builder.append(".");
}
appendIdentifier(fm.getColumnName());
return;
}
@@ -613,6 +658,9 @@ public abstract class BuildVisitor implements ExpressionVisitor
protected Map<Class<? extends PersistentRecord>, Map<String, FieldDefinition>> _definitions=
Maps.newHashMap();
/** Set this to non-null to suppress table abbreviations from being prepended for a class. */
protected Class<? extends PersistentRecord> _defaultType;
/** A flag that's set to true for inner SELECT's */
protected boolean _innerClause = false;
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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
@@ -21,6 +21,7 @@
package com.samskivert.depot.impl;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
@@ -30,6 +31,7 @@ import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -53,10 +55,17 @@ import com.samskivert.depot.annotation.Index;
import com.samskivert.depot.annotation.TableGenerator;
import com.samskivert.depot.annotation.Transient;
import com.samskivert.depot.annotation.UniqueConstraint;
import com.samskivert.depot.clause.CreateIndexClause;
import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.Tuple;
import static com.samskivert.depot.Log.log;
@@ -716,10 +725,11 @@ public class DepotMarshaller<T extends PersistentRecord>
declarations, uniqueConCols, primaryKeyColumns);
// add its indexen
for (Index idx : indexen) {
liaison.addIndexToTable(
conn, getTableName(), fieldsToColumns(idx.fields()),
getTableName() + "_" + idx.name(), idx.unique());
for (Index index : indexen) {
String ixName = getTableName() + "_" + index.name();
execute(conn, builder, index.complex() ?
buildComplexIndex(ixName, index.unique(), index.name()) :
buildSimpleIndex(ixName, index.unique(), index.fields()));
}
// create our value generators
@@ -737,7 +747,22 @@ public class DepotMarshaller<T extends PersistentRecord>
});
}
protected TableMetaData runMigrations (PersistenceContext ctx, TableMetaData metaData,
protected int execute (Connection conn, SQLBuilder builder, QueryClause clause)
throws SQLException
{
if (builder.newQuery(clause)) {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
}
return 0;
}
protected TableMetaData runMigrations (final PersistenceContext ctx, TableMetaData metaData,
final SQLBuilder builder, int currentVersion)
throws DatabaseException
{
@@ -840,9 +865,9 @@ public class DepotMarshaller<T extends PersistentRecord>
ctx.invoke(new Modifier() {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.addIndexToTable(
conn, getTableName(), fieldsToColumns(index.fields()),
ixName, index.unique());
execute(conn, builder, index.complex() ?
buildComplexIndex(ixName, index.unique(), index.name()) :
buildSimpleIndex(ixName, index.unique(), index.fields()));
return 0;
}
});
@@ -876,7 +901,9 @@ public class DepotMarshaller<T extends PersistentRecord>
ctx.invoke(new Modifier() {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
liaison.addIndexToTable(conn, getTableName(), colArr, fName, true);
if (!liaison.tableContainsIndex(conn, getTableName(), fName)) {
execute(conn, builder, buildSimpleIndex(fName, true, colArr));
}
return 0;
}
});
@@ -953,6 +980,59 @@ public class DepotMarshaller<T extends PersistentRecord>
return metaData;
}
private CreateIndexClause<T> buildComplexIndex (String ixName, boolean unique, String localIxName)
throws SQLException
{
List<Tuple<SQLExpression, Order>> definition;
String methName = localIxName + "Definition";
Method method;
try {
method = _pClass.getMethod(methName);
} catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException(
"Index flagged as complex, but no defining method '" + methName
+ "' found.", nsme);
}
Object result;
try {
result = method.invoke(null);
} catch (Exception e) {
throw new IllegalArgumentException(
"Error calling index definition method '" + methName + "'", e);
}
if (result instanceof SQLExpression) {
definition = new ArrayList<Tuple<SQLExpression,Order>>();
definition.add(new Tuple<SQLExpression, Order>((SQLExpression)result, Order.ASC));
} else if (result instanceof List) {
@SuppressWarnings("unchecked")
List<Tuple<SQLExpression, Order>> def = (List<Tuple<SQLExpression, Order>>)result;
definition = def;
} else {
throw new IllegalArgumentException("Method '" + methName
+ "' must return SQLExpression or " + "List<Tuple<SQLExpression, Order>>");
}
return new CreateIndexClause<T>(_pClass, ixName, unique, definition);
}
private CreateIndexClause<T> buildSimpleIndex (String ixName, boolean unique, String[] fields)
throws SQLException
{
List<Tuple<SQLExpression, Order>> definition = new ArrayList<Tuple<SQLExpression,Order>>();
for (String field : fields) {
ColumnExp column = new ColumnExp(_pClass, field);
definition.add(new Tuple<SQLExpression, Order>(column, Order.ASC));
}
return new CreateIndexClause<T>(_pClass, ixName, unique, definition);
}
// translate an array of field names to an array of column names
protected String[] fieldsToColumns (String[] fields)
{
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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
@@ -24,7 +24,9 @@ import com.samskivert.depot.Key;
import com.samskivert.depot.MultiKey;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.CreateIndexClause;
import com.samskivert.depot.clause.DeleteClause;
import com.samskivert.depot.clause.DropIndexClause;
import com.samskivert.depot.clause.FieldDefinition;
import com.samskivert.depot.clause.ForUpdate;
import com.samskivert.depot.clause.FromOverride;
@@ -81,4 +83,6 @@ public interface ExpressionVisitor
public void visit (UpdateClause<? extends PersistentRecord> updateClause);
public void visit (DeleteClause<? extends PersistentRecord> deleteClause);
public void visit (InsertClause<? extends PersistentRecord> insertClause);
public void visit (CreateIndexClause<? extends PersistentRecord> createIndexClause);
public void visit (DropIndexClause<? extends PersistentRecord> dropIndexClause);
}
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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
@@ -20,6 +20,8 @@
package com.samskivert.depot.impl;
import static com.samskivert.Log.log;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
@@ -36,10 +38,13 @@ import com.google.common.collect.Lists;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.Tuple;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.FullTextIndex;
import com.samskivert.depot.annotation.GeneratedValue;
import com.samskivert.depot.clause.CreateIndexClause;
import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.EpochSeconds;
import com.samskivert.depot.expression.FunctionExp;
@@ -111,6 +116,19 @@ public class HSQLBuilder
_builder.append(", '1970-01-01')");
}
@Override
public void visit (CreateIndexClause<? extends PersistentRecord> createIndexClause)
{
for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) {
if (!(field.left instanceof ColumnExp)) {
log.warning("This database can't handle complex indexes. Aborting creation.",
"ixName", createIndexClause.getName());
return;
}
}
super.visit(createIndexClause);
}
protected HBuildVisitor (DepotTypes types)
{
super(types);
@@ -184,7 +202,7 @@ public class HSQLBuilder
protected void maybeMutateForGeneratedValue (GeneratedValue genValue, ColumnDefinition column)
{
// HSQL's IDENTITY() implementation does not take the form of a type, as MySQL's
// and PostgreSQL's conveniently shared SERIAL alias, nor as MySQL's original
// and PostgreSQL's conveniently shared SERIAL alias, nor as MySQL's original
// AUTO_INCREMENT modifier -- but as a default value, which admittedly makes sense
switch (genValue.strategy()) {
case AUTO:
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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
@@ -32,13 +32,18 @@ import java.sql.Timestamp;
import java.util.Set;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.util.Tuple;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.FullTextIndex;
import com.samskivert.depot.clause.CreateIndexClause;
import com.samskivert.depot.clause.DeleteClause;
import com.samskivert.depot.clause.DropIndexClause;
import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.EpochSeconds;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FieldMarshaller.BooleanMarshaller;
import com.samskivert.depot.impl.FieldMarshaller.ByteArrayMarshaller;
import com.samskivert.depot.impl.FieldMarshaller.ByteEnumMarshaller;
@@ -97,6 +102,30 @@ public class MySQLBuilder
_builder.append(")");
}
@Override
public void visit (CreateIndexClause<? extends PersistentRecord> createIndexClause)
{
for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) {
if (!(field.left instanceof ColumnExp)) {
log.warning("This database can't handle complex indexes. Aborting creation.",
"ixName", createIndexClause.getName());
return;
}
}
super.visit(createIndexClause);
}
@Override
public void visit (DropIndexClause<? extends PersistentRecord> dropIndexClause)
{
// MySQL's indexes are scoped on the table, not on the database, and the
// SQL syntax reflects it: DROP INDEX fooIx on fooTable
_builder.append("drop index ");
appendIdentifier(dropIndexClause.getName());
_builder.append(" on ");
appendTableName(dropIndexClause.getPersistentClass());
}
protected MSBuildVisitor (DepotTypes types)
{
super(types);
@@ -3,7 +3,7 @@
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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
@@ -53,15 +53,16 @@ public abstract class SQLBuilder
/**
* Construct an entirely new SQL query relative to our configured {@link DepotTypes} data.
* This method may be called multiple times, each time beginning a new query, and should be
* followed up by a call to {@link #prepare(Connection)} which creates, configures and
* returns the actual {@link PreparedStatement} to execute.
* This method may be called multiple times, each time beginning a new query, and will return
* true if SQL was generated. If so, a call to {@link #prepare(Connection)} should follow,
* which creates, configures and returns the actual {@link PreparedStatement} to execute.
*/
public void newQuery (QueryClause clause)
public boolean newQuery (QueryClause clause)
{
_clause = clause;
_buildVisitor = getBuildVisitor();
_clause.accept(_buildVisitor);
return _buildVisitor.getQuery().trim().length() > 0;
}
/**