From 0ccd8bdb82d4432363bcaf9711be0d6c1766d1a3 Mon Sep 17 00:00:00 2001 From: Par Winzell Date: Wed, 31 Dec 2008 00:25:29 +0000 Subject: [PATCH] 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> return value. The purpose of all this is to create function (or expression, rather) indexes, which PostgreSQL does quite happily. --- .../samskivert/depot/annotation/Index.java | 9 +- .../samskivert/depot/impl/BindVisitor.java | 18 +++- .../samskivert/depot/impl/BuildVisitor.java | 54 +++++++++- .../depot/impl/DepotMarshaller.java | 100 ++++++++++++++++-- .../depot/impl/ExpressionVisitor.java | 6 +- .../samskivert/depot/impl/HSQLBuilder.java | 22 +++- .../samskivert/depot/impl/MySQLBuilder.java | 31 +++++- .../com/samskivert/depot/impl/SQLBuilder.java | 11 +- 8 files changed, 227 insertions(+), 24 deletions(-) diff --git a/src/java/com/samskivert/depot/annotation/Index.java b/src/java/com/samskivert/depot/annotation/Index.java index 23f89bd..17ec791 100644 --- a/src/java/com/samskivert/depot/annotation/Index.java +++ b/src/java/com/samskivert/depot/annotation/Index.java @@ -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: + *
public static List> indexNameExpression ()
+ * which should return the index's defining expressions and whether each one is ascending + * or descending. */ + boolean complex () default false; } diff --git a/src/java/com/samskivert/depot/impl/BindVisitor.java b/src/java/com/samskivert/depot/impl/BindVisitor.java index 1bcfe64..ac6eb22 100644 --- a/src/java/com/samskivert/depot/impl/BindVisitor.java +++ b/src/java/com/samskivert/depot/impl/BindVisitor.java @@ -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 createIndexClause) + { + for (Tuple field : createIndexClause.getFields()) { + field.left.accept(this); + } + } + + public void visit (DropIndexClause createIndexClause) + { + // do nothing + } + protected BindVisitor (DepotTypes types, Connection conn, PreparedStatement stmt) { _types = types; diff --git a/src/java/com/samskivert/depot/impl/BuildVisitor.java b/src/java/com/samskivert/depot/impl/BuildVisitor.java index 9826565..54b4bd3 100644 --- a/src/java/com/samskivert/depot/impl/BuildVisitor.java +++ b/src/java/com/samskivert/depot/impl/BuildVisitor.java @@ -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 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 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 dropIndexClause) + { + _builder.append("drop index "); + appendIdentifier(dropIndexClause.getName()); + } + protected abstract void appendIdentifier (String field); protected void appendTableName (Class 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, Map> _definitions= Maps.newHashMap(); + /** Set this to non-null to suppress table abbreviations from being prepended for a class. */ + protected Class _defaultType; + /** A flag that's set to true for inner SELECT's */ protected boolean _innerClause = false; diff --git a/src/java/com/samskivert/depot/impl/DepotMarshaller.java b/src/java/com/samskivert/depot/impl/DepotMarshaller.java index 9eb78c7..eb664b0 100644 --- a/src/java/com/samskivert/depot/impl/DepotMarshaller.java +++ b/src/java/com/samskivert/depot/impl/DepotMarshaller.java @@ -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 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 }); } - 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 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 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 return metaData; } + private CreateIndexClause buildComplexIndex (String ixName, boolean unique, String localIxName) + throws SQLException + { + List> 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>(); + definition.add(new Tuple((SQLExpression)result, Order.ASC)); + + } else if (result instanceof List) { + @SuppressWarnings("unchecked") + List> def = (List>)result; + definition = def; + + } else { + throw new IllegalArgumentException("Method '" + methName + + "' must return SQLExpression or " + "List>"); + } + + return new CreateIndexClause(_pClass, ixName, unique, definition); + } + + private CreateIndexClause buildSimpleIndex (String ixName, boolean unique, String[] fields) + throws SQLException + { + List> definition = new ArrayList>(); + + for (String field : fields) { + ColumnExp column = new ColumnExp(_pClass, field); + definition.add(new Tuple(column, Order.ASC)); + } + + return new CreateIndexClause(_pClass, ixName, unique, definition); + } + // translate an array of field names to an array of column names protected String[] fieldsToColumns (String[] fields) { diff --git a/src/java/com/samskivert/depot/impl/ExpressionVisitor.java b/src/java/com/samskivert/depot/impl/ExpressionVisitor.java index 559e36a..9d329bf 100644 --- a/src/java/com/samskivert/depot/impl/ExpressionVisitor.java +++ b/src/java/com/samskivert/depot/impl/ExpressionVisitor.java @@ -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 updateClause); public void visit (DeleteClause deleteClause); public void visit (InsertClause insertClause); + public void visit (CreateIndexClause createIndexClause); + public void visit (DropIndexClause dropIndexClause); } diff --git a/src/java/com/samskivert/depot/impl/HSQLBuilder.java b/src/java/com/samskivert/depot/impl/HSQLBuilder.java index b661a8b..03a227e 100644 --- a/src/java/com/samskivert/depot/impl/HSQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/HSQLBuilder.java @@ -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 createIndexClause) + { + for (Tuple 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: diff --git a/src/java/com/samskivert/depot/impl/MySQLBuilder.java b/src/java/com/samskivert/depot/impl/MySQLBuilder.java index eb4b05e..649de98 100644 --- a/src/java/com/samskivert/depot/impl/MySQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/MySQLBuilder.java @@ -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 createIndexClause) + { + for (Tuple 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 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); diff --git a/src/java/com/samskivert/depot/impl/SQLBuilder.java b/src/java/com/samskivert/depot/impl/SQLBuilder.java index b55134d..9beeda3 100644 --- a/src/java/com/samskivert/depot/impl/SQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/SQLBuilder.java @@ -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; } /**