diff --git a/src/java/com/samskivert/jdbc/depot/BindVisitor.java b/src/java/com/samskivert/jdbc/depot/BindVisitor.java new file mode 100644 index 0000000..110cc6c --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/BindVisitor.java @@ -0,0 +1,269 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006-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; + +import java.sql.PreparedStatement; +import java.util.Map; + +import com.samskivert.jdbc.depot.Key.WhereCondition; +import com.samskivert.jdbc.depot.clause.DeleteClause; +import com.samskivert.jdbc.depot.clause.FieldOverride; +import com.samskivert.jdbc.depot.clause.ForUpdate; +import com.samskivert.jdbc.depot.clause.FromOverride; +import com.samskivert.jdbc.depot.clause.GroupBy; +import com.samskivert.jdbc.depot.clause.InsertClause; +import com.samskivert.jdbc.depot.clause.Join; +import com.samskivert.jdbc.depot.clause.Limit; +import com.samskivert.jdbc.depot.clause.OrderBy; +import com.samskivert.jdbc.depot.clause.SelectClause; +import com.samskivert.jdbc.depot.clause.UpdateClause; +import com.samskivert.jdbc.depot.clause.Where; +import com.samskivert.jdbc.depot.expression.ColumnExp; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; +import com.samskivert.jdbc.depot.expression.FunctionExp; +import com.samskivert.jdbc.depot.expression.LiteralExp; +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.Logic.Not; +import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator; +import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator; + +/** + * Implements the base functionality of the argument-binding pass of {@link SQLBuilder}. Dialectal + * subclasses of this should be created and returned from {@link SQLBuilder#getBindVisitor()}. + * + * This class is intimately paired with {#link BuildVisitor}. + */ +public class BindVisitor implements ExpressionVisitor +{ + public void visit (FromOverride override) + throws Exception + { + } + + public void visit (FieldOverride fieldOverride) + throws Exception + { + } + + public void visit (WhereCondition whereCondition) + throws Exception + { + Comparable[] values = whereCondition.getValues(); + for (int ii = 0; ii < values.length; ii ++) { + if (values[ii] != null) { + _stmt.setObject(_argIdx ++, values[ii]); + } + } + } + + public void visit (Key key) + throws Exception + { + key.condition.accept(this); + } + + public void visit (MultiKey key) + throws Exception + { + for (Map.Entry entry : key.getSingleFieldsMap().entrySet()) { + if (entry.getValue() != null) { + _stmt.setObject(_argIdx ++, entry.getValue()); + } + } + Comparable[] values = key.getMultiValues(); + for (int ii = 0; ii < values.length; ii++) { + _stmt.setObject(_argIdx ++, values[ii]); + } + } + + public void visit (FunctionExp functionExp) + throws Exception + { + visit(functionExp.getArguments()); + } + + public void visit (MultiOperator multiOperator) + throws Exception + { + visit(multiOperator.getConditions()); + } + + public void visit (BinaryOperator binaryOperator) throws Exception + { + binaryOperator.getLeftHandSide().accept(this); + binaryOperator.getRightHandSide().accept(this); + } + + public void visit (IsNull isNull) throws Exception + { + } + + public void visit (In in) throws Exception + { + Comparable[] values = in.getValues(); + for (int ii = 0; ii < values.length; ii++) { + _stmt.setObject(_argIdx ++, values[ii]); + } + } + + public void visit (Match match) throws Exception + { + // we never get here + } + + public void visit (ColumnExp columnExp) throws Exception + { + // no arguments + } + + public void visit (Not not) throws Exception + { + not.getCondition().accept(this); + } + + public void visit (GroupBy groupBy) throws Exception + { + visit(groupBy.getValues()); + } + + public void visit (ForUpdate forUpdate) throws Exception + { + // do nothing + } + + public void visit (OrderBy orderBy) throws Exception + { + visit(orderBy.getValues()); + } + + public void visit (Where where) throws Exception + { + where.getCondition().accept(this); + } + + public void visit (Join join) throws Exception + { + join.getJoinCondition().accept(this); + } + + public void visit (Limit limit) throws Exception + { + _stmt.setObject(_argIdx++, limit.getCount()); + _stmt.setObject(_argIdx++, limit.getOffset()); + } + + public void visit (LiteralExp literalExp) throws Exception + { + // do nothing + } + + public void visit (ValueExp valueExp) throws Exception + { + // workaround for postgres bug, fixed in next release: + // http://archives.postgresql.org/pgsql-jdbc/2007-06/msg00080.php + if (valueExp.getValue() instanceof Byte) { + _stmt.setByte(_argIdx ++, (Byte) valueExp.getValue()); + } else { + _stmt.setObject(_argIdx ++, valueExp.getValue()); + } + } + + public void visit (SelectClause selectClause) + throws Exception + { + for (Join clause : selectClause.getJoinClauses()) { + clause.accept(this); + } + if (selectClause.getWhereClause() != null) { + selectClause.getWhereClause().accept(this); + } + if (selectClause.getGroupBy() != null) { + selectClause.getGroupBy().accept(this); + } + if (selectClause.getOrderBy() != null) { + selectClause.getOrderBy().accept(this); + } + if (selectClause.getLimit() != null) { + selectClause.getLimit().accept(this); + } + if (selectClause.getForUpdate() != null) { + selectClause.getForUpdate().accept(this); + } + } + + public void visit (UpdateClause updateClause) throws Exception + { + DepotMarshaller marsh = _types.getMarshaller(updateClause.getPersistentClass()); + + // bind the update arguments + String[] fields = updateClause.getFields(); + Object pojo = updateClause.getPojo(); + if (pojo != null) { + for (int ii = 0; ii < fields.length; ii ++) { + marsh.getFieldMarshaller(fields[ii]).readFromObject(pojo, _stmt, _argIdx++); + } + } else { + visit(updateClause.getValues()); + } + updateClause.getWhereClause().accept(this); + } + + public void visit (DeleteClause deleteClause) throws Exception + { + deleteClause.getWhereClause().accept(this); + } + + public void visit (InsertClause insertClause) throws Exception + { + DepotMarshaller marsh = _types.getMarshaller(insertClause.getPersistentClass()); + + Object pojo = insertClause.getPojo(); + String ixField = insertClause.getIndexField(); + for (String field : marsh.getColumnFieldNames()) { + if (ixField == null || !ixField.equals(field)) { + marsh.getFieldMarshaller(field).readFromObject(pojo, _stmt, _argIdx ++); + } + } + } + + protected BindVisitor (DepotTypes types, PreparedStatement stmt) + { + _types = types; + _stmt = stmt; + _argIdx = 1; + } + + protected void visit (SQLExpression[] expressions) + throws Exception + { + for (int ii = 0; ii < expressions.length; ii ++) { + expressions[ii].accept(this); + } + } + + protected DepotTypes _types; + protected PreparedStatement _stmt; + protected int _argIdx; +} diff --git a/src/java/com/samskivert/jdbc/depot/BuildVisitor.java b/src/java/com/samskivert/jdbc/depot/BuildVisitor.java new file mode 100644 index 0000000..2108425 --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/BuildVisitor.java @@ -0,0 +1,516 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006-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; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +import com.samskivert.jdbc.depot.Key.WhereCondition; +import com.samskivert.jdbc.depot.annotation.Computed; +import com.samskivert.jdbc.depot.clause.DeleteClause; +import com.samskivert.jdbc.depot.clause.FieldOverride; +import com.samskivert.jdbc.depot.clause.ForUpdate; +import com.samskivert.jdbc.depot.clause.FromOverride; +import com.samskivert.jdbc.depot.clause.GroupBy; +import com.samskivert.jdbc.depot.clause.InsertClause; +import com.samskivert.jdbc.depot.clause.Join; +import com.samskivert.jdbc.depot.clause.Limit; +import com.samskivert.jdbc.depot.clause.OrderBy; +import com.samskivert.jdbc.depot.clause.SelectClause; +import com.samskivert.jdbc.depot.clause.UpdateClause; +import com.samskivert.jdbc.depot.clause.Where; +import com.samskivert.jdbc.depot.expression.ColumnExp; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; +import com.samskivert.jdbc.depot.expression.FunctionExp; +import com.samskivert.jdbc.depot.expression.LiteralExp; +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.Logic.Not; +import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator; +import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator; + +/** + * Implements the base functionality of the SQL-building pass of {@link SQLBuilder}. Dialectal + * subclasses of this should be created and returned from {@link SQLBuilder#getBuildVisitor()}. + * + * This class is intimately paired with {#link BindVisitor}. + */ +public abstract class BuildVisitor implements ExpressionVisitor +{ + public String getQuery () + { + return _builder.toString(); + } + + public void visit (FromOverride override) + throws Exception + { + _builder.append(" from " ); + List> from = override.getFromClasses(); + for (int ii = 0; ii < from.size(); ii++) { + if (ii > 0) { + _builder.append(", "); + } + appendTableName(from.get(ii)); + _builder.append(" as "); + appendTableAbbreviation(from.get(ii)); + } + } + + public void visit (FieldOverride fieldOverride) + throws Exception + { + fieldOverride.getOverride().accept(this); + _builder.append(" as "); + appendField(fieldOverride.getField()); + } + + public void visit (WhereCondition whereCondition) + throws Exception + { + String[] keyFields = Key.getKeyFields(whereCondition.getPersistentClass()); + Comparable[] values = whereCondition.getValues(); + for (int ii = 0; ii < keyFields.length; ii ++) { + if (ii > 0) { + _builder.append(" and "); + } + appendColumn(whereCondition.getPersistentClass(), keyFields[ii]); + _builder.append(values[ii] == null ? " is null " : " = ? "); + } + } + + public void visit (Key key) + throws Exception + { + _builder.append(" where "); + key.condition.accept(this); + } + + public void visit (MultiKey key) + throws Exception + { + _builder.append(" where "); + boolean first = true; + for (Map.Entry entry : key.getSingleFieldsMap().entrySet()) { + if (first) { + first = false; + } else { + _builder.append(" and "); + } + appendColumn(key.getPersistentClass(), entry.getKey()); + _builder.append(entry.getValue() == null ? " is null " : " = ? "); + } + if (!first) { + _builder.append(" and "); + } + appendColumn(key.getPersistentClass(), key.getMultiField()); + _builder.append(" in ("); + + Comparable[] values = key.getMultiValues(); + for (int ii = 0; ii < values.length; ii ++) { + if (ii > 0) { + _builder.append(", "); + } + _builder.append("?"); + } + _builder.append(")"); + } + + public void visit (FunctionExp functionExp) + throws Exception + { + _builder.append(functionExp.getFunction()); + _builder.append("("); + SQLExpression[] arguments = functionExp.getArguments(); + for (int ii = 0; ii < arguments.length; ii ++) { + if (ii > 0) { + _builder.append(", "); + } + arguments[ii].accept(this); + } + _builder.append(")"); + } + + public void visit (MultiOperator multiOperator) + throws Exception + { + SQLExpression[] conditions = multiOperator.getConditions(); + for (int ii = 0; ii < conditions.length; ii++) { + if (ii > 0) { + _builder.append(" ").append(multiOperator.operator()).append(" "); + } + _builder.append("("); + conditions[ii].accept(this); + _builder.append(")"); + } + } + + public void visit (BinaryOperator binaryOperator) + throws Exception + { + binaryOperator.getLeftHandSide().accept(this); + _builder.append(binaryOperator.operator()); + binaryOperator.getRightHandSide().accept(this); + } + + public void visit (IsNull isNull) + throws Exception + { + isNull.getColumn().accept(this); + _builder.append(" is null"); + } + + public void visit (In in) + throws Exception + { + in.getColumn().accept(this); + _builder.append(" in ("); + Comparable[] values = in.getValues(); + for (int ii = 0; ii < values.length; ii ++) { + if (ii > 0) { + _builder.append(", "); + } + _builder.append("?"); + } + _builder.append(")"); + } + + public void visit (Match match) + throws Exception + { + throw new IllegalArgumentException("Match() is MySQL-specific."); + } + + public void visit (ColumnExp columnExp) + throws Exception + { + appendTableAbbreviation(columnExp.getPersistentClass()); + _builder.append("."); + appendColumn(columnExp.getPersistentClass(), columnExp.getField()); + } + + public void visit (Not not) + throws Exception + { + _builder.append(" not ("); + not.getCondition().accept(this); + _builder.append(")"); + } + + public void visit (GroupBy groupBy) + throws Exception + { + _builder.append(" group by "); + + SQLExpression[] values = groupBy.getValues(); + for (int ii = 0; ii < values.length; ii++) { + if (ii > 0) { + _builder.append(", "); + } + values[ii].accept(this); + } + } + + public void visit (ForUpdate forUpdate) + throws Exception + { + _builder.append(" for update "); + } + + public void visit (OrderBy orderBy) + throws Exception + { + _builder.append(" order by "); + + SQLExpression[] values = orderBy.getValues(); + OrderBy.Order[] orders = orderBy.getOrders(); + for (int ii = 0; ii < values.length; ii++) { + if (ii > 0) { + _builder.append(", "); + } + values[ii].accept(this); + _builder.append(" ").append(orders[ii]); + } + } + + public void visit (Where where) + throws Exception + { + _builder.append(" where "); + where.getCondition().accept(this); + } + + public void visit (Join join) + throws Exception + { + switch (join.getType()) { + case INNER: + _builder.append(" inner join " ); + break; + case LEFT_OUTER: + _builder.append(" left outer join " ); + break; + case RIGHT_OUTER: + _builder.append(" right outer join " ); + break; + } + appendTableName(join.getJoinClass()); + _builder.append(" as "); + appendTableAbbreviation(join.getJoinClass()); + _builder.append(" on "); + join.getJoinCondition().accept(this); + } + + public void visit (Limit limit) + throws Exception + { + _builder.append(" limit ? offset ? "); + } + + public void visit (LiteralExp literalExp) + throws Exception + { + _builder.append(literalExp.getText()); + } + + public void visit (ValueExp valueExp) + throws Exception + { + _builder.append("?"); + } + + public void visit (SelectClause selectClause) + throws Exception + { + Class pClass = selectClause.getPersistentClass(); + boolean isInner = _innerClause; + _innerClause = true; + + if (isInner) { + _builder.append("("); + } + _builder.append("select "); + + Computed entityComputed = pClass.getAnnotation(Computed.class); + + // iterate over the fields we're filling in and figure out whence each one comes + boolean skip = true; + for (String field : selectClause.getFields()) { + if (!skip) { + _builder.append(", "); + } + skip = false; + + // first, see if there's a field override + FieldOverride override = selectClause.lookupOverride(field); + if (override != null) { + override.accept(this); + continue; + } + + // figure out the class we're selecting from unless we're otherwise overriden: + // for a concrete record, simply use the corresponding table; for a computed one, + // default to the shadowed concrete record, or null if there isn't one + + Class tableClass; + if (entityComputed == null) { + tableClass = pClass; + } else if (!PersistentRecord.class.equals(entityComputed.shadowOf())) { + tableClass = entityComputed.shadowOf(); + } else { + tableClass = null; + } + + // handle the field-level @Computed annotation, if there is one + FieldMarshaller fm = _types.getMarshaller(pClass).getFieldMarshaller(field); + if (fm == null) { + throw new IllegalArgumentException( + "could not find marshaller for field: " + field); + } + Computed fieldComputed = fm.getComputed(); + if (fieldComputed != null) { + // check if the computed field has a literal SQL definition + if (fieldComputed.fieldDefinition().length() > 0) { + _builder.append(fieldComputed.fieldDefinition()).append(" as "); + appendField(field); + continue; + } + + // or if we can simply ignore the field + if (!fieldComputed.required()) { + skip = true; + continue; + } + + // else see if there's an overriding shadowOf definition + if (fieldComputed.shadowOf() != null) { + tableClass = fieldComputed.shadowOf(); + } + } + + // if we get this far we hopefully have a table to select from + if (tableClass != null) { + appendTableAbbreviation(tableClass); + _builder.append("."); + appendColumn(tableClass, field); + continue; + } + + // else owie + throw new IllegalArgumentException( + "Persistent field has no definition [class=" + + selectClause.getPersistentClass() + ", field=" + field + "]"); + } + + if (selectClause.getFromOverride() != null) { + selectClause.getFromOverride().accept(this); + + } else if (_types.getTableName(pClass) != null) { + _builder.append(" from "); + appendTableName(pClass); + _builder.append(" as "); + appendTableAbbreviation(pClass); + + } else { + throw new SQLException("Query on @Computed entity with no FromOverrideClause."); + } + + for (Join clause : selectClause.getJoinClauses()) { + clause.accept(this); + } + if (selectClause.getWhereClause() != null) { + selectClause.getWhereClause().accept(this); + } + if (selectClause.getGroupBy() != null) { + selectClause.getGroupBy().accept(this); + } + if (selectClause.getOrderBy() != null) { + selectClause.getOrderBy().accept(this); + } + if (selectClause.getLimit() != null) { + selectClause.getLimit().accept(this); + } + if (selectClause.getForUpdate() != null) { + selectClause.getForUpdate().accept(this); + } + if (isInner) { + _builder.append(")"); + } + } + + public void visit (UpdateClause updateClause) + throws Exception + { + Class pClass = updateClause.getPersistentClass(); + _innerClause = true; + + _builder.append("update "); + appendTableName(pClass); + _builder.append(" as "); + appendTableAbbreviation(pClass); + _builder.append(" set "); + + String[] fields = updateClause.getFields(); + Object pojo = updateClause.getPojo(); + SQLExpression[] values = updateClause.getValues(); + for (int ii = 0; ii < fields.length; ii ++) { + if (ii > 0) { + _builder.append(", "); + } + appendColumn(pClass, fields[ii]); + + _builder.append(" = "); + if (pojo != null) { + _builder.append("?"); + } else { + values[ii].accept(this); + } + } + updateClause.getWhereClause().accept(this); + } + + public void visit (DeleteClause deleteClause) + throws Exception + { + _builder.append("delete from "); + appendTableName(deleteClause.getPersistentClass()); + _builder.append(" "); + deleteClause.getWhereClause().accept(this); + } + + public void visit (InsertClause insertClause) + throws Exception + { + Class pClass = insertClause.getPersistentClass(); + DepotMarshaller marsh = _types.getMarshaller(pClass); + _innerClause = true; + + String[] fields = marsh.getColumnFieldNames(); + + _builder.append("insert into "); + appendTableName(insertClause.getPersistentClass()); + _builder.append(" ("); + for (int ii = 0; ii < fields.length; ii ++) { + if (ii > 0) { + _builder.append(", "); + } + appendColumn(pClass, fields[ii]); + } + _builder.append(") values("); + + String ixField = insertClause.getIndexField(); + for (int ii = 0; ii < fields.length; ii++) { + if (ii > 0) { + _builder.append(", "); + } + if (ixField != null && ixField.equals(fields[ii])) { + _builder.append("DEFAULT"); + } else { + _builder.append("?"); + } + } + _builder.append(")"); + } + + protected abstract void appendTableName (Class type); + protected abstract void appendTableAbbreviation (Class type); + protected abstract void appendColumn (Class type, String field); + protected abstract void appendField (String field); + + protected BuildVisitor (DepotTypes types) + { + _types = types; + _builder = new StringBuilder(); + _innerClause = false; + } + + protected DepotTypes _types; + + /** A StringBuilder to hold the constructed SQL. */ + protected StringBuilder _builder; + + /** A flag that's set to true for inner SELECT's */ + protected boolean _innerClause; +} diff --git a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java index 3b62cbd..8154d7b 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java +++ b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -48,11 +48,8 @@ import com.samskivert.jdbc.depot.annotation.TableGenerator; import com.samskivert.jdbc.depot.annotation.Transient; import com.samskivert.jdbc.depot.annotation.UniqueConstraint; -import com.samskivert.jdbc.JDBCUtil; -import com.samskivert.jdbc.depot.clause.Where; +import com.samskivert.jdbc.DatabaseLiaison; import com.samskivert.util.ArrayUtil; -import com.samskivert.util.ListUtil; -import com.samskivert.util.StringUtil; import static com.samskivert.jdbc.depot.Log.log; @@ -75,7 +72,6 @@ public class DepotMarshaller _pclass = pclass; Entity entity = pclass.getAnnotation(Entity.class); - Table table = null; // see if this is a computed entity Computed computed = pclass.getAnnotation(Computed.class); @@ -89,11 +85,7 @@ public class DepotMarshaller if (entity.name().length() > 0) { _tableName = entity.name(); } - _postamble = entity.postamble(); } - - // check for a Table annotation, for unique constraints - table = pclass.getAnnotation(Table.class); } // if the entity defines a new TableGenerator, map that in our static table as those are @@ -127,8 +119,8 @@ public class DepotMarshaller } FieldMarshaller fm = FieldMarshaller.createMarshaller(field); - _fields.put(fm.getColumnName(), fm); - fields.add(fm.getColumnName()); + _fields.put(field.getName(), fm); + fields.add(field.getName()); // check to see if this is our primary key if (field.getAnnotation(Id.class) != null) { @@ -181,7 +173,8 @@ public class DepotMarshaller switch(gv.strategy()) { case AUTO: case IDENTITY: - _keyGenerator = new IdentityKeyGenerator(); + _keyGenerator = new IdentityKeyGenerator( + gv, getTableName(), keyField.getColumnName()); break; case TABLE: @@ -191,7 +184,8 @@ public class DepotMarshaller throw new IllegalArgumentException( "Unknown generator [generator=" + name + "]"); } - _keyGenerator = new TableKeyGenerator(generator); + _keyGenerator = new TableKeyGenerator( + generator, gv, getTableName(), keyField.getColumnName()); break; } } @@ -199,54 +193,6 @@ public class DepotMarshaller // generate our full list of fields/columns for use in queries _allFields = fields.toArray(new String[fields.size()]); - - // if we're a computed entity, stop here - if (_tableName == null) { - return; - } - - // figure out the list of fields that correspond to actual table columns and generate the - // SQL used to create and migrate our table (unless we're a computed entity) - _columnFields = new String[_allFields.length]; - int jj = 0; - for (int ii = 0; ii < _allFields.length; ii++) { - // include all persistent non-computed fields - String colDef = _fields.get(_allFields[ii]).getColumnDefinition(); - if (colDef != null) { - _columnFields[jj] = _allFields[ii]; - _declarations.add(colDef); - jj ++; - } - } - _columnFields = ArrayUtil.splice(_columnFields, jj); - - // determine whether we have any index definitions - if (entity != null) { - for (Index index : entity.indices()) { - // TODO: delegate this to a database specific SQL generator - _declarations.add(index.type() + " index " + index.name() + - " (" + StringUtil.join(index.columns(), ", ") + ")"); - } - } - - // add any unique constraints given - if (table != null) { - for (UniqueConstraint constraint : table.uniqueConstraints()) { - _declarations.add( - "UNIQUE (" + StringUtil.join(constraint.columnNames(), ", ") + ")"); - } - } - - // add the primary key, if we have one - if (hasPrimaryKey()) { - _declarations.add("PRIMARY KEY (" + getJoinedKeyColumns() + ")"); - } - - // if we did not find a schema version field, complain - if (_schemaVersion < 0) { - log.warning("Unable to read " + _pclass.getName() + "." + SCHEMA_VERSION_FIELD + - ". Schema migration disabled."); - } } /** @@ -265,7 +211,15 @@ public class DepotMarshaller { return _allFields; } - + + /** + * Returns all the persistent fields that correspond to concrete table columns. + */ + public String[] getColumnFieldNames () + { + return _columnFields; + } + /** * Returns the {@link FieldMarshaller} for a named field on our persistent class. */ @@ -273,7 +227,7 @@ public class DepotMarshaller { return _fields.get(fieldName); } - + /** * Returns true if our persistent object defines a primary key. */ @@ -283,14 +237,23 @@ public class DepotMarshaller } /** - * Return the names of the columns that constitute the primary key of our associated - * persistent record. + * Returns the {@link KeyGenerator} used to generate primary keys for this persistent object, + * or null if it does not use a key generator. */ - public String[] getPrimaryKeyColumns () + public KeyGenerator getKeyGenerator () + { + return _keyGenerator; + } + + /** + * Return the names of the columns that constitute the primary key of our associated persistent + * record. + */ + public String[] getPrimaryKeyFields () { String[] pkcols = new String[_pkColumns.size()]; for (int ii = 0; ii < pkcols.length; ii ++) { - pkcols[ii] = _pkColumns.get(ii).getColumnName(); + pkcols[ii] = _pkColumns.get(ii).getField().getName(); } return pkcols; } @@ -359,11 +322,11 @@ public class DepotMarshaller throw new UnsupportedOperationException( getClass().getName() + " does not define a primary key"); } - String[] columns = new String[_pkColumns.size()]; + String[] fields = new String[_pkColumns.size()]; for (int ii = 0; ii < _pkColumns.size(); ii++) { - columns[ii] = _pkColumns.get(ii).getColumnName(); + fields[ii] = _pkColumns.get(ii).getField().getName(); } - return new Key(_pclass, columns, values); + return new Key(_pclass, fields, values); } /** @@ -416,7 +379,7 @@ public class DepotMarshaller // then create and populate the persistent object T po = _pclass.newInstance(); for (FieldMarshaller fm : _fields.values()) { - if (!fields.contains(fm.getField().getName())) { + if (!fields.contains(fm.getColumnName())) { // this field was not in the result set, make sure that's OK if (fm.getComputed() != null && !fm.getComputed().required()) { continue; @@ -438,46 +401,6 @@ public class DepotMarshaller } } - /** - * Creates a statement that will insert the supplied persistent object into the database. - */ - public PreparedStatement createInsert (Connection conn, Object po) - throws SQLException - { - requireNotComputed("insert rows into"); - - try { - StringBuilder insert = new StringBuilder(); - insert.append("insert into ").append(getTableName()); - insert.append(" (").append(StringUtil.join(_columnFields, ",")); - insert.append(")").append(" values("); - for (int ii = 0; ii < _columnFields.length; ii++) { - if (ii > 0) { - insert.append(", "); - } - insert.append("?"); - } - insert.append(")"); - - // TODO: handle primary key, nullable fields specially? - PreparedStatement pstmt = conn.prepareStatement(insert.toString()); - int idx = 0; - for (String field : _columnFields) { - _fields.get(field).readFromObject(po, pstmt, ++idx); - } - return pstmt; - - } catch (SQLException sqe) { - // pass this on through - throw sqe; - - } catch (Exception e) { - String errmsg = "Failed to marshall persistent object [pclass=" + - _pclass.getName() + "]"; - throw (SQLException)new SQLException(errmsg).initCause(e); - } - } - /** * Fills in the primary key just assigned to the supplied persistence object by the execution * of the results of {@link #createInsert}. @@ -485,7 +408,8 @@ public class DepotMarshaller * @return the newly assigned primary key or null if the object does not use primary keys or * this is not the right time to assign the key. */ - public Key assignPrimaryKey (Connection conn, Object po, boolean postFactum) + public Key assignPrimaryKey ( + Connection conn, DatabaseLiaison liaison, Object po, boolean postFactum) throws SQLException { // if we have no primary key or no generator, then we're done @@ -499,7 +423,7 @@ public class DepotMarshaller } try { - int nextValue = _keyGenerator.nextGeneratedValue(conn); + int nextValue = _keyGenerator.nextGeneratedValue(conn, liaison); _pkColumns.get(0).getField().set(po, nextValue); return makePrimaryKey(nextValue); } catch (Exception e) { @@ -508,131 +432,6 @@ public class DepotMarshaller } } - /** - * Creates a statement that will update the supplied persistent object using the supplied key. - */ - public PreparedStatement createUpdate (Connection conn, Object po, Where key) - throws SQLException - { - return createUpdate(conn, po, key, _columnFields); - } - - /** - * Creates a statement that will update the supplied persistent object - * using the supplied key. - */ - public PreparedStatement createUpdate ( - Connection conn, Object po, Where key, String[] modifiedFields) - throws SQLException - { - requireNotComputed("update rows in"); - - StringBuilder update = new StringBuilder(); - update.append("update ").append(getTableName()).append(" set "); - int idx = 0; - for (String field : modifiedFields) { - if (idx++ > 0) { - update.append(", "); - } - update.append(field).append(" = ?"); - } - key.appendClause(null, update); - - try { - PreparedStatement pstmt = conn.prepareStatement(update.toString()); - idx = 0; - // bind the update arguments - for (String field : modifiedFields) { - _fields.get(field).readFromObject(po, pstmt, ++idx); - } - // now bind the key arguments - key.bindClauseArguments(pstmt, ++idx); - return pstmt; - - } catch (SQLException sqe) { - // pass this on through - throw sqe; - - } catch (Exception e) { - String errmsg = "Failed to marshall persistent object " + - "[pclass=" + _pclass.getName() + "]"; - throw (SQLException)new SQLException(errmsg).initCause(e); - } - } - - /** - * Creates a statement that will update the specified set of fields for all persistent objects - * that match the supplied key. - */ - public PreparedStatement createPartialUpdate ( - Connection conn, Where key, String[] modifiedFields, Object[] modifiedValues) - throws SQLException - { - requireNotComputed("update rows in"); - - StringBuilder update = new StringBuilder(); - update.append("update ").append(getTableName()).append(" set "); - int idx = 0; - for (String field : modifiedFields) { - if (idx++ > 0) { - update.append(", "); - } - update.append(field).append(" = ?"); - } - key.appendClause(null, update); - - PreparedStatement pstmt = conn.prepareStatement(update.toString()); - idx = 0; - // bind the update arguments - for (Object value : modifiedValues) { - // TODO: use the field marshaller? - pstmt.setObject(++idx, value); - } - // now bind the key arguments - key.bindClauseArguments(pstmt, ++idx); - return pstmt; - } - - /** - * Creates a statement that will delete all rows matching the supplied key. - */ - public PreparedStatement createDelete (Connection conn, Where key) - throws SQLException - { - requireNotComputed("delete rows from"); - - StringBuilder query = new StringBuilder("delete from " + getTableName()); - key.appendClause(null, query); - PreparedStatement pstmt = conn.prepareStatement(query.toString()); - key.bindClauseArguments(pstmt, 1); - return pstmt; - } - - /** - * Creates a statement that will update the specified set of fields, using the supplied literal - * SQL values, for all persistent objects that match the supplied key. - */ - public PreparedStatement createLiteralUpdate ( - Connection conn, Where key, String[] modifiedFields, Object[] modifiedValues) - throws SQLException - { - requireNotComputed("update rows in"); - - StringBuilder update = new StringBuilder(); - update.append("update ").append(getTableName()).append(" set "); - for (int ii = 0; ii < modifiedFields.length; ii++) { - if (ii > 0) { - update.append(", "); - } - update.append(modifiedFields[ii]).append(" = "); - update.append(modifiedValues[ii]); - } - key.appendClause(null, update); - - PreparedStatement pstmt = conn.prepareStatement(update.toString()); - key.bindClauseArguments(pstmt, 1); - return pstmt; - } /** * This is called by the persistence context to register a migration for the entity managed by @@ -652,47 +451,122 @@ public class DepotMarshaller protected void init (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; + // perform the context-sensitive initialization of the field marshallers + for (FieldMarshaller fm : _fields.values()) { + fm.init(builder); + } + + // figure out the list of fields that correspond to actual table columns and generate the + // SQL used to create and migrate our table (unless we're a computed entity) + _columnFields = new String[_allFields.length]; + String[] declarations = new String[_allFields.length]; + int jj = 0; + for (int ii = 0; ii < _allFields.length; ii++) { + @SuppressWarnings("unchecked") FieldMarshaller fm = _fields.get(_allFields[ii]); + // include all persistent non-computed fields + String colDef = fm.getColumnDefinition(); + if (colDef != null) { + _columnFields[jj] = _allFields[ii]; + declarations[jj] = colDef; + jj ++; + } + } + _columnFields = ArrayUtil.splice(_columnFields, jj); + declarations = ArrayUtil.splice(declarations, jj); + // if we have no table (i.e. we're a computed entity), we have nothing to create if (getTableName() == null) { return; } + // add any additional unique constraints + String[][] uniqueConstraintColumns = null; + Table table = _pclass.getAnnotation(Table.class); + if (table != null) { + UniqueConstraint[] uCons = table.uniqueConstraints(); + uniqueConstraintColumns = new String[uCons.length][]; + for (int kk = 0; kk < uCons.length; kk ++) { + String[] columns = uCons[kk].columnNames(); + for (int ii = 0; ii < columns.length; ii ++) { + FieldMarshaller fm = getFieldMarshaller(columns[ii]); + if (fm == null) { + throw new IllegalArgumentException( + "Unknown field in @UniqueConstraint: " + columns[ii]); + } + columns[ii] = fm.getColumnName(); + } + uniqueConstraintColumns[kk] = columns; + } + } + + // if we did not find a schema version field, complain + if (_schemaVersion < 0) { + log.warning("Unable to read " + _pclass.getName() + "." + SCHEMA_VERSION_FIELD + + ". Schema migration disabled."); + } + // check to see if our schema version table exists, create it if not ctx.invoke(new Modifier() { - public int invoke (Connection conn) throws SQLException { - JDBCUtil.createTableIfMissing( + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + liaison.createTableIfMissing( conn, SCHEMA_VERSION_TABLE, - new String[] { "persistentClass VARCHAR(255) NOT NULL", - "version INTEGER NOT NULL" }, ""); + new String[] { "persistentClass", "version" }, + new String[] { "VARCHAR(255) NOT NULL", "INTEGER NOT NULL" }, + null, + new String[] { "persistentClass" }); return 0; } }); // now create the table for our persistent class if it does not exist + final String[] fDeclarations = declarations; + final String[][] fUniqueConstraintColumns = uniqueConstraintColumns; ctx.invoke(new Modifier() { - public int invoke (Connection conn) throws SQLException { - if (!JDBCUtil.tableExists(conn, getTableName())) { - log.info("Creating table " + getTableName() + " (" + _declarations + ") " + - _postamble); - String[] definition = _declarations.toArray(new String[_declarations.size()]); - JDBCUtil.createTableIfMissing(conn, getTableName(), definition, _postamble); - updateVersion(conn, _schemaVersion); + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + String[] columns = new String[_columnFields.length]; + for (int ii = 0; ii < columns.length; ii ++) { + columns[ii] = _fields.get(_columnFields[ii]).getColumnName(); } + String[] primaryKeyColumns = null; + if (_pkColumns != null) { + primaryKeyColumns = new String[_pkColumns.size()]; + for (int ii = 0; ii < primaryKeyColumns.length; ii ++) { + primaryKeyColumns[ii] = _pkColumns.get(ii).getColumnName(); + } + } + liaison.createTableIfMissing(conn, getTableName(), columns, fDeclarations, + fUniqueConstraintColumns, primaryKeyColumns); + updateVersion(conn, liaison, _schemaVersion); return 0; } }); + // ensure that all indices are created + final Entity entity = _pclass.getAnnotation(Entity.class); + if (entity != null && entity.indices().length > 0) { + ctx.invoke(new Modifier() { + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + for (Index idx : entity.indices()) { + liaison.addIndexToTable(conn, getTableName(), idx.columns(), idx.name()); + } + return entity.indices().length; + } + }); + } + // if we have a key generator, initialize that too if (_keyGenerator != null) { ctx.invoke(new Modifier() { - public int invoke (Connection conn) throws SQLException { - _keyGenerator.init(conn); + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + _keyGenerator.init(conn, liaison); return 0; } }); @@ -706,9 +580,12 @@ public class DepotMarshaller // make sure the versions match int currentVersion = ctx.invoke(new Modifier() { - public int invoke (Connection conn) throws SQLException { - String query = "select version from " + SCHEMA_VERSION_TABLE + - " where persistentClass = '" + getTableName() + "'"; + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + String query = + " select " + liaison.columnSQL("version") + + " from " + liaison.tableSQL(SCHEMA_VERSION_TABLE) + + " where " + liaison.columnSQL("persistentClass") + + " = '" + getTableName() + "'"; Statement stmt = conn.createStatement(); try { ResultSet rs = stmt.executeQuery(query); @@ -723,6 +600,12 @@ public class DepotMarshaller return; } + if (true) { + log.warning("Automatic schema migration is temporarily disabled."); + verifySchemasMatch(ctx); + return; + } + // otherwise try to migrate the schema log.info("Migrating " + getTableName() + " from " + currentVersion + " to " + _schemaVersion + "..."); @@ -736,35 +619,29 @@ public class DepotMarshaller } } - // enumerate all of the columns now that we've run our pre-migrations - Set columns = new HashSet(); - Map> indexColumns = new HashMap>(); - loadMetaData(ctx, columns, indexColumns); + // enumerate all columns and indexes now that we've run our pre-migrations + TableMetaData metaData = loadMetaData(ctx); // this is a little silly, but we need a copy for name disambiguation later - Set indicesCopy = new HashSet(indexColumns.keySet()); + Set indicesCopy = new HashSet(metaData.indexColumns.keySet()); // add any missing columns for (String fname : _columnFields) { - FieldMarshaller fmarsh = _fields.get(fname); - if (columns.remove(fmarsh.getColumnName())) { + @SuppressWarnings("unchecked") final FieldMarshaller fmarsh = _fields.get(fname); + if (metaData.tableColumns.remove(fmarsh.getColumnName())) { continue; } // otherwise add the column - String coldef = fmarsh.getColumnDefinition(); - String query = "alter table " + getTableName() + " add column " + coldef; - - // try to add it to the appropriate spot - int fidx = ListUtil.indexOf(_allFields, fmarsh.getColumnName()); - if (fidx == 0) { - query += " first"; - } else { - query += " after " + _allFields[fidx-1]; - } - - log.info("Adding column to " + getTableName() + ": " + coldef); - ctx.invoke(new Modifier.Simple(query)); + final String coldef = fmarsh.getColumnDefinition(); + log.info("Adding column to " + getTableName() + ": " + + fmarsh.getColumnName() + " " + coldef); + ctx.invoke(new Modifier.Simple() { + protected String createQuery (DatabaseLiaison liaison) { + return "alter table " + liaison.tableSQL(getTableName()) + + " add column " + liaison.columnSQL(fmarsh.getColumnName()) + " " + coldef; + } + }); // if the column is a TIMESTAMP or DATETIME column, we need to run a special query to // update all existing rows to the current time because MySQL annoyingly assigns @@ -773,75 +650,84 @@ public class DepotMarshaller // cannot accept CURRENT_TIME or NOW() defaults at all. if (coldef.toLowerCase().indexOf(" timestamp") != -1 || coldef.toLowerCase().indexOf(" datetime") != -1) { - query = "update " + getTableName() + " set " + fmarsh.getColumnName() + " = NOW()"; - log.info("Assigning current time to column: " + query); - ctx.invoke(new Modifier.Simple(query)); - } - } - - // add or remove the primary key as needed - if (hasPrimaryKey() && !indexColumns.containsKey("PRIMARY")) { - String pkdef = "primary key (" + getJoinedKeyColumns() + ")"; - log.info("Adding primary key to " + getTableName() + ": " + pkdef); - ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " add " + pkdef)); - - } else if (!hasPrimaryKey() && indexColumns.containsKey("PRIMARY")) { - indexColumns.remove("PRIMARY"); - log.info("Dropping primary from " + getTableName()); - ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " drop primary key")); - } - - // add any missing indices - Entity entity = _pclass.getAnnotation(Entity.class); - for (Index index : (entity == null ? new Index[0] : entity.indices())) { - if (indexColumns.containsKey(index.name())) { - indexColumns.remove(index.name()); - continue; - } - String indexdef = "create " + index.type() + " index " + index.name() + - " on " + getTableName() + " (" + StringUtil.join(index.columns(), ", ") + ")"; - log.info("Adding index: " + indexdef); - ctx.invoke(new Modifier.Simple(indexdef)); - } - - // to get the @Table(uniqueIndices...) indices, we use our clever set of column name sets - Set> uniqueIndices = new HashSet>(indexColumns.values()); - Table table; - if (getTableName() != null && (table = _pclass.getAnnotation(Table.class)) != null) { - Set colSet = new HashSet(); - for (UniqueConstraint constraint : table.uniqueConstraints()) { - // for each given UniqueConstraint, build a new column set - colSet.clear(); - for (String column : constraint.columnNames()) { - colSet.add(column); - } - // and check if the table contained this set - if (uniqueIndices.contains(colSet)) { - continue; // good, carry on - } - - // else build the index; we'll use mysql's convention of naming it after a column, - // with possible _N disambiguation; luckily we made a copy of the index names! - String indexName = colSet.iterator().next(); - if (indicesCopy.contains(indexName)) { - int num = 1; - indexName += "_"; - while (indicesCopy.contains(indexName + num)) { - num ++; + log.info("Assigning current time to " + fmarsh.getColumnName() + "."); + ctx.invoke(new Modifier.Simple() { + protected String createQuery (DatabaseLiaison liaison) { + // TODO: is NOW() standard SQL? + return "update " + liaison.tableSQL(getTableName()) + + " set " + liaison.columnSQL(fmarsh.getColumnName()) + " = NOW()"; } - indexName += num; - } - - String[] columnArr = colSet.toArray(new String[colSet.size()]); - String indexdef = "create unique index " + indexName + " on " + - getTableName() + " (" + StringUtil.join(columnArr, ", ") + ")"; - log.info("Adding unique index: " + indexdef); - ctx.invoke(new Modifier.Simple(indexdef)); + }); } } + // TODO: This needs more work to be database-independent, I think. Assuming the index name + // is going to be PRIMARY seems like a MySQL-ism, and we'll do the rest of the index work + // when we do that. + +// // add or remove the primary key as needed +// if (hasPrimaryKey() && !indexColumns.containsKey("PRIMARY")) { +// String pkdef = "primary key (" + getJoinedKeyColumns() + ")"; +// log.info("Adding primary key to " + getTableName() + ": " + pkdef); +// ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " add " + pkdef)); +// +// } else if (!hasPrimaryKey() && indexColumns.containsKey("PRIMARY")) { +// indexColumns.remove("PRIMARY"); +// log.info("Dropping primary from " + getTableName()); +// ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " drop primary key")); +// } +// +// // add any missing indices +// for (Index index : (entity == null ? new Index[0] : entity.indices())) { +// if (indexColumns.containsKey(index.name())) { +// indexColumns.remove(index.name()); +// continue; +// } +// String indexdef = +// "create " + index.type() + " index " + _liaison.indexSQL(index.name()) + +// " on " + _liaison.tableSQL(getTableName()) + " (" + StringUtil.join(index.columns(), ", ") + ")"; +// log.info("Adding index: " + indexdef); +// ctx.invoke(new Modifier.Simple(indexdef)); +// } +// +// // to get the @Table(uniqueIndices...) indices, we use our clever set of column name sets +// Set> uniqueIndices = new HashSet>(indexColumns.values()); +// Table table; +// if (getTableName() != null && (table = _pclass.getAnnotation(Table.class)) != null) { +// Set colSet = new HashSet(); +// for (UniqueConstraint constraint : table.uniqueConstraints()) { +// // for each given UniqueConstraint, build a new column set +// colSet.clear(); +// for (String column : constraint.columnNames()) { +// colSet.add(column); +// } +// // and check if the table contained this set +// if (uniqueIndices.contains(colSet)) { +// continue; // good, carry on +// } +// +// // else build the index; we'll use mysql's convention of naming it after a column, +// // with possible _N disambiguation; luckily we made a copy of the index names! +// String indexName = colSet.iterator().next(); +// if (indicesCopy.contains(indexName)) { +// int num = 1; +// indexName += "_"; +// while (indicesCopy.contains(indexName + num)) { +// num ++; +// } +// indexName += num; +// } +// +// String[] columnArr = colSet.toArray(new String[colSet.size()]); +// String indexdef = "create unique index " + indexName + " on " + +// getTableName() + " (" + StringUtil.join(columnArr, ", ") + ")"; +// log.info("Adding unique index: " + indexdef); +// ctx.invoke(new Modifier.Simple(indexdef)); +// } +// } + // we do not auto-remove columns but rather require that EntityMigration.Drop records be - // registered by hand to avoid accidentally causin the loss of data + // registered by hand to avoid accidentally causing the loss of data // we don't auto-remove indices either because we'd have to sort out the potentially // complex origins of an index (which might be because of a @Unique column or maybe the @@ -858,87 +744,96 @@ public class DepotMarshaller // record our new version in the database ctx.invoke(new Modifier() { - public int invoke (Connection conn) throws SQLException { - updateVersion(conn, _schemaVersion); + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + updateVersion(conn, liaison, _schemaVersion); return 0; } }); } - /** - * Loads up the metadata for our database table: the names of our columns and indices. - */ - protected void loadMetaData (PersistenceContext ctx, final Set columns, - final Map> indexColumns) + protected TableMetaData loadMetaData (PersistenceContext ctx) throws PersistenceException { - ctx.invoke(new Modifier() { - public int invoke (Connection conn) throws SQLException { - DatabaseMetaData meta = conn.getMetaData(); - ResultSet rs = meta.getColumns(null, null, getTableName(), "%"); - while (rs.next()) { - columns.add(rs.getString("COLUMN_NAME")); - } - - if (indexColumns != null) { - rs = meta.getIndexInfo(null, null, getTableName(), false, false); - while (rs.next()) { - String indexName = rs.getString("INDEX_NAME"); - Set set = indexColumns.get(indexName); - if (rs.getBoolean("NON_UNIQUE")) { - // not a unique index: just make sure there's an entry in the keyset - if (set == null) { - indexColumns.put(indexName, null); - } - - } else { - // for unique indices we collect the column names - if (set == null) { - set = new HashSet(); - indexColumns.put(indexName, set); - } - set.add(rs.getString("COLUMN_NAME")); - } - } - } - - return 0; + return ctx.invoke(new Query.TrivialQuery() { + public TableMetaData invoke (Connection conn, DatabaseLiaison liaison) + throws SQLException { + return new TableMetaData(conn.getMetaData()); } }); } + protected class TableMetaData + { + public Set tableColumns = new HashSet(); + public Map> indexColumns = new HashMap>(); + public String pkName; + public Set pkColumns = new HashSet(); + + public TableMetaData (DatabaseMetaData meta) + throws SQLException + { + ResultSet rs = meta.getColumns(null, null, getTableName(), "%"); + while (rs.next()) { + tableColumns.add(rs.getString("COLUMN_NAME")); + } + + rs = meta.getIndexInfo(null, null, getTableName(), false, false); + while (rs.next()) { + String indexName = rs.getString("INDEX_NAME"); + Set set = indexColumns.get(indexName); + if (rs.getBoolean("NON_UNIQUE")) { + // not a unique index: just make sure there's an entry in the keyset + if (set == null) { + indexColumns.put(indexName, null); + } + + } else { + // for unique indices we collect the column names + if (set == null) { + set = new HashSet(); + indexColumns.put(indexName, set); + } + set.add(rs.getString("COLUMN_NAME")); + } + } + + rs = meta.getPrimaryKeys(null, null, getTableName()); + while (rs.next()) { + pkName = rs.getString("PK_NAME"); + pkColumns.add(rs.getString("COLUMN_NAME")); + } + } + } + /** * Checks that there are no database columns for which we no longer have Java fields. */ protected void verifySchemasMatch (PersistenceContext ctx) throws PersistenceException { - Set columns = new HashSet(); - loadMetaData(ctx, columns, null); + TableMetaData meta = loadMetaData(ctx); for (String fname : _columnFields) { FieldMarshaller fmarsh = _fields.get(fname); - columns.remove(fmarsh.getColumnName()); + meta.tableColumns.remove(fmarsh.getColumnName()); } - for (String column : columns) { + for (String column : meta.tableColumns) { log.warning(getTableName() + " contains stale column '" + column + "'."); } } - protected String getJoinedKeyColumns () - { - return StringUtil.join(getPrimaryKeyColumns(), ", "); - } - - protected void updateVersion (Connection conn, int version) + protected void updateVersion (Connection conn, DatabaseLiaison liaison, int version) throws SQLException { - String update = "update " + SCHEMA_VERSION_TABLE + - " set version = " + version + " where persistentClass = '" + getTableName() + "'"; - String insert = "insert into " + SCHEMA_VERSION_TABLE + - " values('" + getTableName() + "', " + version + ")"; + String update = + "update " + liaison.tableSQL(SCHEMA_VERSION_TABLE) + + " set " + liaison.columnSQL("version") + " = " + version + + " where " + liaison.columnSQL("persistentClass") + " = '" + getTableName() + "'"; Statement stmt = conn.createStatement(); try { if (stmt.executeUpdate(update) == 0) { + String insert = + "insert into " + liaison.tableSQL(SCHEMA_VERSION_TABLE) + + " values('" + getTableName() + "', " + version + ")"; stmt.executeUpdate(insert); } } finally { @@ -946,15 +841,6 @@ public class DepotMarshaller } } - protected void requireNotComputed (String action) - throws SQLException - { - if (getTableName() == null) { - throw new IllegalArgumentException( - "Can't " + action + " computed entities [class=" + _pclass + "]"); - } - } - /** The persistent object class that we manage. */ protected Class _pclass; @@ -962,10 +848,10 @@ public class DepotMarshaller protected String _tableName; /** A field marshaller for each persistent field in our object. */ - protected HashMap _fields = new HashMap(); + protected Map _fields = new HashMap(); - /** The field marshallers for our persistent object's primary key columns - * or null if it did not define a primary key. */ + /** The field marshallers for our persistent object's primary key columns or null if it did not + * define a primary key. */ protected ArrayList _pkColumns; /** The generator to use for auto-generating primary key values, or null. */ @@ -977,16 +863,9 @@ public class DepotMarshaller /** The fields of our object with directly corresponding table columns. */ protected String[] _columnFields; - /** The version of our persistent object schema as specified in the class - * definition. */ + /** The version of our persistent object schema as specified in the class definition. */ protected int _schemaVersion = -1; - /** Used when creating and migrating our table schema. */ - protected ArrayList _declarations = new ArrayList(); - - /** Used when creating and migrating our table schema. */ - protected String _postamble = ""; - /** Indicates that we have been initialized (created or migrated our tables). */ protected boolean _initialized; diff --git a/src/java/com/samskivert/jdbc/depot/DepotRepository.java b/src/java/com/samskivert/jdbc/depot/DepotRepository.java index c81fcd7..c9734b5 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotRepository.java +++ b/src/java/com/samskivert/jdbc/depot/DepotRepository.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -29,15 +29,22 @@ import java.util.List; import java.util.Map; import com.samskivert.io.PersistenceException; +import com.samskivert.util.ArrayUtil; + import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.jdbc.DatabaseLiaison; import com.samskivert.jdbc.JDBCUtil; + import com.samskivert.jdbc.depot.Modifier.*; +import com.samskivert.jdbc.depot.clause.DeleteClause; import com.samskivert.jdbc.depot.clause.FieldOverride; import com.samskivert.jdbc.depot.clause.FromOverride; +import com.samskivert.jdbc.depot.clause.InsertClause; import com.samskivert.jdbc.depot.clause.Join; import com.samskivert.jdbc.depot.clause.QueryClause; -import com.samskivert.jdbc.depot.clause.Where; -import com.samskivert.util.ArrayUtil; +import com.samskivert.jdbc.depot.clause.UpdateClause; +import com.samskivert.jdbc.depot.expression.SQLExpression; +import com.samskivert.jdbc.depot.expression.ValueExp; /** * Provides a base for classes that provide access to persistent objects. Also defines the @@ -163,18 +170,36 @@ public class DepotRepository protected int insert (T record) throws PersistenceException { - final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass()); - final Key key = marsh.getPrimaryKey(record, false); + @SuppressWarnings("unchecked") final Class pClass = (Class) record.getClass(); + final DepotMarshaller marsh = _ctx.getMarshaller(pClass); + Key key = marsh.getPrimaryKey(record, false); + + DepotTypes types = DepotTypes.getDepotTypes(_ctx); + types.addClass(_ctx, pClass); + final SQLBuilder builder = _ctx.getSQLBuilder(types); + // key will be null if record was supplied without a primary key return _ctx.invoke(new CachingModifier(record, key, key) { - public int invoke (Connection conn) throws SQLException { + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { // update our modifier's key so that it can cache our results - updateKey(marsh.assignPrimaryKey(conn, _result, false)); - PreparedStatement stmt = marsh.createInsert(conn, _result); + if (_key == null) { + updateKey(marsh.assignPrimaryKey(conn, liaison, _result, false)); + } + + String ixField = null; + if (_key == null && marsh.hasPrimaryKey() && marsh.getKeyGenerator() != null && + marsh.getKeyGenerator() instanceof IdentityKeyGenerator) { + ixField = ((IdentityKeyGenerator) marsh.getKeyGenerator()).getColumn(); + } + builder.newQuery(new InsertClause(pClass, _result, ixField)); + + PreparedStatement stmt = builder.prepare(conn); try { int mods = stmt.executeUpdate(); // check again in case we have a post-factum key generator - updateKey(marsh.assignPrimaryKey(conn, _result, true)); + if (_key == null) { + updateKey(marsh.assignPrimaryKey(conn, liaison, _result, true)); + } return mods; } finally { JDBCUtil.close(stmt); @@ -192,14 +217,22 @@ public class DepotRepository protected int update (T record) throws PersistenceException { - final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass()); - final Key key = marsh.getPrimaryKey(record); + @SuppressWarnings("unchecked") Class pClass = (Class) record.getClass(); + requireNotComputed(pClass, "update"); + + DepotMarshaller marsh = _ctx.getMarshaller(pClass); + Key key = marsh.getPrimaryKey(record); if (key == null) { throw new IllegalArgumentException("Can't update record with null primary key."); } + + UpdateClause update = new UpdateClause(pClass, key, marsh._columnFields, record); + final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update)); + builder.newQuery(update); + return _ctx.invoke(new CachingModifier(record, key, key) { - public int invoke (Connection conn) throws SQLException { - PreparedStatement stmt = marsh.createUpdate(conn, _result, key); + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + PreparedStatement stmt = builder.prepare(conn); try { return stmt.executeUpdate(); } finally { @@ -220,14 +253,26 @@ public class DepotRepository protected int update (T record, final String... modifiedFields) throws PersistenceException { - final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass()); - final Key key = marsh.getPrimaryKey(record); + @SuppressWarnings("unchecked") + Class pClass = (Class) record.getClass(); + + requireNotComputed(pClass, "updatePartial"); + + DepotMarshaller marsh = _ctx.getMarshaller(pClass); + Key key = marsh.getPrimaryKey(record); + if (key == null) { throw new IllegalArgumentException("Can't update record with null primary key."); } + + UpdateClause update = new UpdateClause(pClass, key, modifiedFields, record); + + final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update)); + builder.newQuery(update); + return _ctx.invoke(new CachingModifier(record, key, key) { - public int invoke (Connection conn) throws SQLException { - PreparedStatement stmt = marsh.createUpdate(conn, _result, key, modifiedFields); + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + PreparedStatement stmt = builder.prepare(conn); // clear out _result so that we don't rewrite this partial record to the cache _result = null; try { @@ -336,21 +381,23 @@ public class DepotRepository * @return the number of rows modified by this action. */ protected int updatePartial ( - Class type, final Where key, CacheInvalidator invalidator, Object... fieldsValues) + Class type, final WhereClause key, CacheInvalidator invalidator, Object... fieldsValues) throws PersistenceException { // separate the arguments into keys and values final String[] fields = new String[fieldsValues.length/2]; - final Object[] values = new Object[fields.length]; + final SQLExpression[] values = new SQLExpression[fields.length]; for (int ii = 0, idx = 0; ii < fields.length; ii++) { fields[ii] = (String)fieldsValues[idx++]; - values[ii] = fieldsValues[idx++]; + values[ii] = new ValueExp(fieldsValues[idx++]); } + UpdateClause update = new UpdateClause(type, key, fields, values); + final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update)); + builder.newQuery(update); - final DepotMarshaller marsh = _ctx.getMarshaller(type); return _ctx.invoke(new Modifier(invalidator) { - public int invoke (Connection conn) throws SQLException { - PreparedStatement stmt = marsh.createPartialUpdate(conn, key, fields, values); + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + PreparedStatement stmt = builder.prepare(conn); try { return stmt.executeUpdate(); } finally { @@ -378,11 +425,11 @@ public class DepotRepository * @return the number of rows modified by this action. */ protected int updateLiteral ( - Class type, Comparable primaryKey, String... fieldsValues) + Class type, Comparable primaryKey, Map fieldsToValues) throws PersistenceException { Key key = _ctx.getMarshaller(type).makePrimaryKey(primaryKey); - return updateLiteral(type, key, key, fieldsValues); + return updateLiteral(type, key, key, fieldsToValues); } /** @@ -404,11 +451,11 @@ public class DepotRepository */ protected int updateLiteral ( Class type, String ix1, Comparable val1, String ix2, Comparable val2, - String... fieldsValues) + Map fieldsToValues) throws PersistenceException { Key key = new Key(type, ix1, val1, ix2, val2); - return updateLiteral(type, key, key, fieldsValues); + return updateLiteral(type, key, key, fieldsToValues); } /** @@ -430,11 +477,11 @@ public class DepotRepository */ protected int updateLiteral ( Class type, String ix1, Comparable val1, String ix2, Comparable val2, - String ix3, Comparable val3, String... fieldsValues) + String ix3, Comparable val3, Map fieldsToValues) throws PersistenceException { Key key = new Key(type, ix1, val1, ix2, val2, ix3, val3); - return updateLiteral(type, key, key, fieldsValues); + return updateLiteral(type, key, key, fieldsToValues); } /** @@ -455,21 +502,29 @@ public class DepotRepository * @return the number of rows modified by this action. */ protected int updateLiteral ( - Class type, final Where key, CacheInvalidator invalidator, String... fieldsValues) + Class type, final WhereClause key, CacheInvalidator invalidator, + Map fieldsToValues) throws PersistenceException { + requireNotComputed(type, "updateLiteral"); + // separate the arguments into keys and values - final String[] fields = new String[fieldsValues.length/2]; - final String[] values = new String[fields.length]; - for (int ii = 0, idx = 0; ii < fields.length; ii++) { - fields[ii] = fieldsValues[idx++]; - values[ii] = fieldsValues[idx++]; + final String[] fields = new String[fieldsToValues.size()]; + final SQLExpression[] values = new SQLExpression[fields.length]; + int ii = 0; + for (Map.Entry entry : fieldsToValues.entrySet()) { + fields[ii] = entry.getKey(); + values[ii] = entry.getValue(); + ii ++; } - final DepotMarshaller marsh = _ctx.getMarshaller(type); + UpdateClause update = new UpdateClause(type, key, fields, values); + final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update)); + builder.newQuery(update); + return _ctx.invoke(new Modifier(invalidator) { - public int invoke (Connection conn) throws SQLException { - PreparedStatement stmt = marsh.createLiteralUpdate(conn, key, fields, values); + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + PreparedStatement stmt = builder.prepare(conn); try { return stmt.executeUpdate(); } finally { @@ -489,17 +544,25 @@ public class DepotRepository protected boolean store (T record) throws PersistenceException { - final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass()); - final Key key = marsh.hasPrimaryKey() ? marsh.getPrimaryKey(record) : null; + @SuppressWarnings("unchecked") final Class pClass = (Class) record.getClass(); + requireNotComputed(pClass, "store"); + + final DepotMarshaller marsh = _ctx.getMarshaller(pClass); + Key key = marsh.hasPrimaryKey() ? marsh.getPrimaryKey(record) : null; + final UpdateClause update = + new UpdateClause(pClass, key, marsh._columnFields, record); + final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update)); + builder.newQuery(update); + final boolean[] created = new boolean[1]; _ctx.invoke(new CachingModifier(record, key, key) { - public int invoke (Connection conn) throws SQLException { + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { PreparedStatement stmt = null; try { - // if our primary key isn't null, update rather than insert the record - // before been persisted and insert - if (key != null) { - stmt = marsh.createUpdate(conn, _result, key); + // if our primary key isn't null, update rather than insert the record before + // been persisted and insert + if (_key != null) { + stmt = builder.prepare(conn); int mods = stmt.executeUpdate(); if (mods > 0) { return mods; @@ -509,10 +572,22 @@ public class DepotRepository // if the update modified zero rows or the primary key was obviously unset, do // an insertion - updateKey(marsh.assignPrimaryKey(conn, _result, false)); - stmt = marsh.createInsert(conn, _result); + if (_key == null) { + updateKey(marsh.assignPrimaryKey(conn, liaison, _result, false)); + } + + String ixField = null; + if (_key == null && marsh.hasPrimaryKey() && marsh.getKeyGenerator() != null && + marsh.getKeyGenerator() instanceof IdentityKeyGenerator) { + ixField = ((IdentityKeyGenerator) marsh.getKeyGenerator()).getColumn(); + } + builder.newQuery(new InsertClause(pClass, _result, ixField)); + + stmt = builder.prepare(conn); int mods = stmt.executeUpdate(); - updateKey(marsh.assignPrimaryKey(conn, _result, true)); + if (_key == null) { + updateKey(marsh.assignPrimaryKey(conn, liaison, _result, true)); + } created[0] = true; return mods; @@ -571,13 +646,16 @@ public class DepotRepository * @return the number of rows deleted by this action. */ protected int deleteAll ( - Class type, final Where key, CacheInvalidator invalidator) + Class type, final WhereClause key, CacheInvalidator invalidator) throws PersistenceException { - final DepotMarshaller marsh = _ctx.getMarshaller(type); + DeleteClause delete = new DeleteClause(type, key); + final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, delete)); + builder.newQuery(delete); + return _ctx.invoke(new Modifier(invalidator) { - public int invoke (Connection conn) throws SQLException { - PreparedStatement stmt = marsh.createDelete(conn, key); + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + PreparedStatement stmt = builder.prepare(conn); try { return stmt.executeUpdate(); } finally { @@ -587,25 +665,18 @@ public class DepotRepository }); } - protected static abstract class CollectionQuery implements Query + // make sure the given type corresponds to a concrete class + protected void requireNotComputed (Class type, String action) + throws PersistenceException { - public CollectionQuery (CacheKey key) - throws PersistenceException - { - _key = key; + DepotMarshaller marsh = _ctx.getMarshaller(type); + if (marsh == null) { + throw new PersistenceException("Unknown persistent type [class=" + type + "]"); } - - public CacheKey getCacheKey () - { - return _key; + if (marsh.getTableName() == null) { + throw new PersistenceException( + "Can't " + action + " computed entities [class=" + type + "]"); } - - public void updateCache (PersistenceContext ctx, T result) - { - ctx.cacheStore(_key, result); - } - - protected CacheKey _key; } protected PersistenceContext _ctx; diff --git a/src/java/com/samskivert/jdbc/depot/DepotTypes.java b/src/java/com/samskivert/jdbc/depot/DepotTypes.java index db1ce76..134fd8d 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotTypes.java +++ b/src/java/com/samskivert/jdbc/depot/DepotTypes.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -20,52 +20,60 @@ package com.samskivert.jdbc.depot; +import java.sql.SQLException; + import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.samskivert.io.PersistenceException; +import com.samskivert.jdbc.depot.clause.QueryClause; +import com.samskivert.jdbc.depot.expression.SQLExpression; /** - * Primarily an implementation of {@link QueryBuilderContext}, this class holds the persistent - * classes used in the context of a single query and maps them to {@link DepotMarshaller} - * instances as well as table names and table abbreviations. - * - * The main motivation for breaking this functionality out into its own class is to encapsulate - * the operation that throws {@link PersistenceException} as separate from the operations that - * throw {@link SQLException}. + * Maintains a record of the persistent classes brought into the context of the associated SQL, + * i.e. any class associated with a concrete table that would appear in FROM or JOIN clauses or as + * the target of an UPDATE or an INSERT or any other place where a table abbreviation could be + * constructed. + * + * The main motivation for breaking this functionality out into its own class is to encapsulate the + * operation that throws {@link PersistenceException} as separate from the operations that throw + * {@link SQLException}. Once this class has been constructed, it may be used to create {@link + * SQLBuilder} instances without any {@link PersistenceException} worries. */ -public class DepotTypes - implements QueryBuilderContext +public class DepotTypes { - public DepotTypes (PersistenceContext ctx, - Class main, - Set> others) + /** A trivial instance that is accessible in places where we want the dialectal benefits of the + * SQLBuilder without really requiring per-persistent-class context. */ + public static DepotTypes TRIVIAL = new DepotTypes(); + + /** + * Conveniently constructs a {@link DepotTypes} object given {@link QueryClause} objects, which + * are interrogated for their class definition sets through {@link SQLExpression#addClasses}. + */ + public static DepotTypes getDepotTypes ( + PersistenceContext ctx, QueryClause... clauses) throws PersistenceException { - _mainType = main; - _classMap = new HashMap(); - - for (Class c : others) { - _classMap.put(c, ctx.getMarshaller(c)); + Set> classSet = + new HashSet>(); + for (QueryClause clause : clauses) { + if (clause != null) { + clause.addClasses(classSet); + } } - _classList = new ArrayList>(others); + return new DepotTypes(ctx, classSet); } - public Class getMainType () - { - return _mainType; - } - - // from ConstructedQuery public String getTableName (Class cl) { return _classMap.get(cl).getTableName(); } - // from ConstructedQuery public String getTableAbbreviation (Class cl) { int ix = _classList.indexOf(cl); @@ -75,32 +83,42 @@ public class DepotTypes 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 String getMainTableName () + public void addClass (PersistenceContext ctx, Class type) + throws PersistenceException { - return getTableName(_mainType); + if (_classMap.containsKey(type)) { + return; + } + _classList.add(type); + _classMap.put(type, ctx.getMarshaller(type)); } - public DepotMarshaller getMainMarshaller () + protected DepotTypes (PersistenceContext ctx, Set> others) + throws PersistenceException { - return getMarshaller(_mainType); + for (Class c : others) { + addClass(ctx, c); + } } - public String getMainTableAbbreviation () + protected DepotTypes () { - return getTableAbbreviation(_mainType); } - /** The persistent class to instantiate for the results. */ - protected Class _mainType; - /** A list of referenced classes, used to generate table abbreviations. */ - protected List> _classList; + protected List> _classList = + new ArrayList>(); /** Classes mapped to marshallers, used for table names and field lists. */ - protected Map _classMap; + protected Map _classMap = new HashMap(); } diff --git a/src/java/com/samskivert/jdbc/depot/EntityMigration.java b/src/java/com/samskivert/jdbc/depot/EntityMigration.java index 1a905f0..01397a8 100644 --- a/src/java/com/samskivert/jdbc/depot/EntityMigration.java +++ b/src/java/com/samskivert/jdbc/depot/EntityMigration.java @@ -22,10 +22,11 @@ package com.samskivert.jdbc.depot; import java.sql.Connection; import java.sql.SQLException; -import java.sql.Statement; -import java.util.HashMap; +import java.util.Map; +import com.samskivert.jdbc.DatabaseLiaison; import com.samskivert.jdbc.JDBCUtil; +import com.samskivert.jdbc.LiaisonRegistry; import static com.samskivert.jdbc.depot.Log.log; @@ -47,21 +48,15 @@ public abstract class EntityMigration extends Modifier _columnName = columnName; } - public int invoke (Connection conn) throws SQLException { + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { if (!JDBCUtil.tableContainsColumn(conn, _tableName, _columnName)) { // we'll accept this inconsistency log.warning(_tableName + "." + _columnName + " already dropped."); return 0; } - Statement stmt = conn.createStatement(); - try { - log.info("Dropping '" + _columnName + "' from " + _tableName); - return stmt.executeUpdate( - "alter table " + _tableName + " drop column " + _columnName); - } finally { - stmt.close(); - } + log.info("Dropping '" + _columnName + "' from " + _tableName); + return liaison.dropColumn(conn, _tableName, _columnName) ? 1 : 0; } protected String _columnName; @@ -78,7 +73,7 @@ public abstract class EntityMigration extends Modifier _newColumnName = newColumnName; } - public int invoke (Connection conn) throws SQLException { + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { if (!JDBCUtil.tableContainsColumn(conn, _tableName, _oldColumnName)) { if (JDBCUtil.tableContainsColumn(conn, _tableName, _newColumnName)) { // we'll accept this inconsistency @@ -97,60 +92,46 @@ public abstract class EntityMigration extends Modifier _tableName + " already contains '" + _newColumnName + "'"); } - Statement stmt = conn.createStatement(); - try { - log.info("Changing '" + _oldColumnName + "' to '" + _newColumnDef + "' in " + - _tableName); - return stmt.executeUpdate("alter table " + _tableName + " change column " + - _oldColumnName + " " + _newColumnDef); - } finally { - stmt.close(); - } + log.info("Renaming '" + _oldColumnName + "' to '" + _newColumnName + "' in: " + + _tableName); + return liaison.renameColumn(conn, _tableName, _oldColumnName, _newColumnName) ? 1 : 0; } public boolean runBeforeDefault () { return false; } - protected void init (String tableName, HashMap marshallers) { - super.init(tableName, marshallers); - _newColumnDef = marshallers.get(_newColumnName).getColumnDefinition(); - } - - protected String _oldColumnName, _newColumnName, _newColumnDef; + protected String _oldColumnName, _newColumnName; } /** - * A convenient migration for changing the type of an existing column. + * A convenient migration for changing the type of an existing field. NOTE: This object is + * instantiated with the name of a persistent field, not the name of a database column. These + * can be very different things for classes that use @Column annotations. */ public static class Retype extends EntityMigration { - public Retype (int targetVersion, String columnName) { + public Retype (int targetVersion, String fieldName) { super(targetVersion); - _columnName = columnName; + _fieldName = fieldName; } - public int invoke (Connection conn) throws SQLException { - Statement stmt = conn.createStatement(); - try { - log.info("Updating type of '" + _columnName + "' in " + _tableName); - return stmt.executeUpdate("alter table " + _tableName + " change column " + - _columnName + " " + _newColumnDef); - } finally { - stmt.close(); - } + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { + log.info("Updating type of '" + _fieldName + "' in " + _tableName); + return liaison.changeColumn(conn, _tableName, _fieldName, _newColumnDef) ? 1 : 0; } public boolean runBeforeDefault () { return false; } - protected void init (String tableName, HashMap marshallers) { + protected void init (String tableName, Map marshallers) { super.init(tableName, marshallers); - _newColumnDef = marshallers.get(_columnName).getColumnDefinition(); + _columnName = marshallers.get(_fieldName).getColumnName(); + _newColumnDef = marshallers.get(_fieldName).getColumnDefinition(); } - protected String _columnName, _newColumnDef; + protected String _fieldName, _columnName, _newColumnDef; } /** @@ -184,7 +165,7 @@ public abstract class EntityMigration extends Modifier * migration has been determined to be runnable so one cannot rely on this method having been * called in {@link #shouldRunMigration}. */ - protected void init (String tableName, HashMap marshallers) + protected void init (String tableName, Map marshallers) { _tableName = tableName; } diff --git a/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java b/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java index 2484fca..aaa3a92 100644 --- a/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java +++ b/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -33,6 +33,7 @@ import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; +import com.samskivert.io.PersistenceException; import com.samskivert.jdbc.depot.annotation.Column; import com.samskivert.jdbc.depot.annotation.Computed; import com.samskivert.jdbc.depot.annotation.GeneratedValue; @@ -106,10 +107,20 @@ public abstract class FieldMarshaller "Cannot marshall field of type '" + ftype.getName() + "'."); } - marshaller.init(field); + marshaller.create(field); return marshaller; } + /** + * Initializes this field marshaller with a SQL builder which it uses to construct its column + * definition according to the appropriate database dialect. + */ + public void init (SQLBuilder builder) + throws PersistenceException + { + _columnDefinition = builder.buildColumnDefinition(this); + } + /** * Returns the {@link Field} handled by this marshaller. */ @@ -194,98 +205,30 @@ public abstract class FieldMarshaller writeToObject(po, getFromSet(rset)); } - /** - * Returns the type used in the SQL column definition for this field. - */ - public abstract String getColumnType (); - - protected void init (Field field) + protected void create (Field field) { _field = field; _columnName = field.getName(); - // read our column metadata from the annotation (if it exists); annoyingly we can't create - // a Column instance to read the defaults so we have to duplicate them here - int length = 255; - boolean nullable = false; - boolean unique = false; - String defval = ""; Column column = _field.getAnnotation(Column.class); if (column != null) { - nullable = column.nullable(); - unique = column.unique(); - length = column.length(); - defval = column.defaultValue(); if (!StringUtil.isBlank(column.name())) { _columnName = column.name(); } } _computed = field.getAnnotation(Computed.class); - // if this field is @Computed, it has no SQL definition if (_computed != null) { - _columnDefinition = null; return; } - // create our SQL column definition - StringBuilder builder = new StringBuilder(); - if (column != null && !StringUtil.isBlank(column.columnDefinition())) { - builder.append(column.columnDefinition()); - - } else { - builder.append(getColumnName()); - String type = getColumnType(); - builder.append(" ").append(type); - - // if this is a VARCHAR field, add the length - if (type.equals("VARCHAR") || type.equals("VARBINARY")) { - builder.append("(").append(length).append(")"); - } - - // TODO: handle precision and scale - - // handle nullability and uniqueness - if (!nullable) { - builder.append(" NOT NULL"); - } - if (unique) { - builder.append(" UNIQUE"); - } - - // append the default value if one was specified - if (defval.length() > 0) { - builder.append(" DEFAULT ").append(defval); - } - } - - // handle primary keyness if (field.getAnnotation(Id.class) != null) { // figure out how we're going to generate our primary key values _generatedValue = field.getAnnotation(GeneratedValue.class); - if (_generatedValue != null) { - switch (_generatedValue.strategy()) { - case AUTO: - case IDENTITY: - builder.append(" AUTO_INCREMENT"); - break; - case SEQUENCE: // TODO - throw new IllegalArgumentException( - "SEQUENCE key generation strategy not yet supported."); - case TABLE: - // nothing to do here, it'll be handled later - break; - } - } } - - _columnDefinition = builder.toString(); } protected static class BooleanMarshaller extends FieldMarshaller { - public String getColumnType () { - return "TINYINT"; - } public Boolean getFromObject (Object po) throws IllegalArgumentException, IllegalAccessException { return _field.getBoolean(po); @@ -305,9 +248,6 @@ public abstract class FieldMarshaller } protected static class ByteMarshaller extends FieldMarshaller { - public String getColumnType () { - return "TINYINT"; - } public Byte getFromObject (Object po) throws IllegalArgumentException, IllegalAccessException { return _field.getByte(po); @@ -327,9 +267,6 @@ public abstract class FieldMarshaller } protected static class ShortMarshaller extends FieldMarshaller { - public String getColumnType () { - return "SMALLINT"; - } public Short getFromObject (Object po) throws IllegalArgumentException, IllegalAccessException { return _field.getShort(po); @@ -349,9 +286,6 @@ public abstract class FieldMarshaller } protected static class IntMarshaller extends FieldMarshaller { - public String getColumnType () { - return "INTEGER"; - } public Integer getFromObject (Object po) throws IllegalArgumentException, IllegalAccessException { return _field.getInt(po); @@ -371,9 +305,6 @@ public abstract class FieldMarshaller } protected static class LongMarshaller extends FieldMarshaller { - public String getColumnType () { - return "BIGINT"; - } public Long getFromObject (Object po) throws IllegalArgumentException, IllegalAccessException { return _field.getLong(po); @@ -393,9 +324,6 @@ public abstract class FieldMarshaller } protected static class FloatMarshaller extends FieldMarshaller { - public String getColumnType () { - return "FLOAT"; - } public Float getFromObject (Object po) throws IllegalArgumentException, IllegalAccessException { return _field.getFloat(po); @@ -415,9 +343,6 @@ public abstract class FieldMarshaller } protected static class DoubleMarshaller extends FieldMarshaller { - public String getColumnType () { - return "DOUBLE"; - } public Double getFromObject (Object po) throws IllegalArgumentException, IllegalAccessException { return _field.getDouble(po); @@ -437,37 +362,6 @@ public abstract class FieldMarshaller } protected static class ObjectMarshaller extends FieldMarshaller { - public String getColumnType () { - Class ftype = _field.getType(); - if (ftype.equals(Byte.class)) { - return "TINYINT"; - } else if (ftype.equals(Short.class)) { - return "SMALLINT"; - } else if (ftype.equals(Integer.class)) { - return "INTEGER"; - } else if (ftype.equals(Long.class)) { - return "BIGINT"; - } else if (ftype.equals(Float.class)) { - return "FLOAT"; - } else if (ftype.equals(Double.class)) { - return "DOUBLE"; - } else if (ftype.equals(String.class)) { - return "VARCHAR"; - } else if (ftype.equals(Date.class)) { - return "DATE"; - } else if (ftype.equals(Time.class)) { - return "DATETIME"; - } else if (ftype.equals(Timestamp.class)) { - return "TIMESTAMP"; - } else if (ftype.equals(Blob.class)) { - return "BLOB"; - } else if (ftype.equals(Clob.class)) { - return "CLOB"; - } else { - throw new IllegalArgumentException( - "Don't know how to create SQL for " + ftype + "."); - } - } public Object getFromObject (Object po) throws IllegalArgumentException, IllegalAccessException { return _field.get(po); @@ -503,9 +397,6 @@ public abstract class FieldMarshaller throws SQLException { ps.setBytes(column, value); } - public String getColumnType () { - return "VARBINARY"; - } } protected static class IntArrayMarshaller extends FieldMarshaller { @@ -525,9 +416,6 @@ public abstract class FieldMarshaller throws SQLException { ps.setObject(column, value); } - public String getColumnType () { - return "BLOB"; - } } protected static class ByteEnumMarshaller extends FieldMarshaller { @@ -545,10 +433,6 @@ public abstract class FieldMarshaller } } - public String getColumnType () { - return "TINYINT"; - } - public ByteEnum getFromObject (Object po) throws IllegalArgumentException, IllegalAccessException { return (ByteEnum) _field.get(po); diff --git a/src/java/com/samskivert/jdbc/depot/FindAllQuery.java b/src/java/com/samskivert/jdbc/depot/FindAllQuery.java index 018bb23..4475c34 100644 --- a/src/java/com/samskivert/jdbc/depot/FindAllQuery.java +++ b/src/java/com/samskivert/jdbc/depot/FindAllQuery.java @@ -33,14 +33,18 @@ import net.sf.ehcache.Cache; import net.sf.ehcache.Element; import com.samskivert.io.PersistenceException; +import com.samskivert.util.ArrayUtil; + +import com.samskivert.jdbc.DatabaseLiaison; import com.samskivert.jdbc.JDBCUtil; -import com.samskivert.jdbc.depot.operator.Logic.*; + import com.samskivert.jdbc.depot.clause.FieldOverride; import com.samskivert.jdbc.depot.clause.Join; import com.samskivert.jdbc.depot.clause.QueryClause; +import com.samskivert.jdbc.depot.clause.SelectClause; import com.samskivert.jdbc.depot.clause.Where; import com.samskivert.jdbc.depot.expression.SQLExpression; -import com.samskivert.util.ArrayUtil; +import com.samskivert.jdbc.depot.operator.Logic.*; /** * This class implements the functionality required by {@link DepotRepository#findAll): fetch @@ -58,18 +62,20 @@ public abstract class FindAllQuery throws PersistenceException { super(ctx, type); - _types = SQLQueryBuilder.getDepotTypes(ctx, type, clauses); + DepotTypes types = DepotTypes.getDepotTypes(ctx, clauses); + types.addClass(ctx, type); + _builder = _ctx.getSQLBuilder(types); _clauses = clauses; } - public List invoke (Connection conn) throws SQLException { - SQLQueryBuilder keyFieldQuery = new SQLQueryBuilder( - _ctx, _types, _marsh.getPrimaryKeyColumns(), _clauses); - PreparedStatement stmt = keyFieldQuery.prepare(conn); - + public List invoke (Connection conn, DatabaseLiaison liaison) throws SQLException + { Map, T> entities = new HashMap, T>(); List> allKeys = new ArrayList>(); List> fetchKeys = new ArrayList>(); + + _builder.newQuery(new SelectClause(_type, _marsh.getPrimaryKeyFields(), _clauses)); + PreparedStatement stmt = _builder.prepare(conn); try { ResultSet rs = stmt.executeQuery(); while (rs.next()) { @@ -97,26 +103,28 @@ public abstract class FindAllQuery if (fetchKeys.size() > 0) { int jj = 0; - QueryClause[] newClauses = new QueryClause[_clauses.length + 1]; // a select subset of query clauses are preserved for the entity query + QueryClause[] newClauses = new QueryClause[_clauses.length + 1]; for (int ii = 0; ii < _clauses.length; ii ++) { if (_clauses[ii] instanceof Join || _clauses[ii] instanceof FieldOverride) { newClauses[jj ++] = _clauses[ii]; } } - SQLExpression[] keyArray = fetchKeys.toArray(new SQLExpression[0]); + + SQLExpression[] keyArray = new SQLExpression[fetchKeys.size()]; + for (int ii = 0; ii < keyArray.length; ii ++) { + keyArray[ii] = fetchKeys.get(ii).condition; + } // add our special key-matching where clause newClauses[jj ++] = new Where(new Or(keyArray)); newClauses = ArrayUtil.splice(newClauses, jj); // build the new query - SQLQueryBuilder entityQuery = new SQLQueryBuilder( - _ctx, _types, _marsh.getFieldNames(), newClauses); + _builder.newQuery(new SelectClause(_type, _marsh.getFieldNames(), newClauses)); + stmt = _builder.prepare(conn); // and execute it - stmt = entityQuery.prepare(conn); - try { ResultSet rs = stmt.executeQuery(); for (Key key : fetchKeys) { @@ -138,7 +146,6 @@ public abstract class FindAllQuery return result; } - protected DepotTypes _types; protected QueryClause[] _clauses; } @@ -151,33 +158,32 @@ public abstract class FindAllQuery throws PersistenceException { super(ctx, type); - DepotTypes> types = SQLQueryBuilder.getDepotTypes(ctx, type, clauses); - _builder = new SQLQueryBuilder>(ctx, types, _marsh.getFieldNames(), clauses); + SelectClause select = new SelectClause(type, _marsh.getFieldNames(), clauses); + _builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, select)); + _builder.newQuery(select); } - public List invoke (Connection conn) throws SQLException { - PreparedStatement stmt = _builder.prepare(conn); - + public List invoke (Connection conn, DatabaseLiaison liaison) throws SQLException + { List result = new ArrayList(); + PreparedStatement stmt = _builder.prepare(conn); try { ResultSet rs = stmt.executeQuery(); while (rs.next()) { result.add(_marsh.createObject(rs)); } - } finally { JDBCUtil.close(stmt); } return result; } - - protected SQLQueryBuilder> _builder; } public FindAllQuery (PersistenceContext ctx, Class type) throws PersistenceException { _ctx = ctx; + _type = type; _marsh = _ctx.getMarshaller(type); } @@ -216,5 +222,7 @@ public abstract class FindAllQuery } protected PersistenceContext _ctx; + protected SQLBuilder _builder; protected DepotMarshaller _marsh; + protected Class _type; } diff --git a/src/java/com/samskivert/jdbc/depot/FindOneQuery.java b/src/java/com/samskivert/jdbc/depot/FindOneQuery.java index 9b6d88f..e83fa6c 100644 --- a/src/java/com/samskivert/jdbc/depot/FindOneQuery.java +++ b/src/java/com/samskivert/jdbc/depot/FindOneQuery.java @@ -26,8 +26,11 @@ import java.sql.ResultSet; import java.sql.SQLException; import com.samskivert.io.PersistenceException; + +import com.samskivert.jdbc.DatabaseLiaison; import com.samskivert.jdbc.JDBCUtil; import com.samskivert.jdbc.depot.clause.QueryClause; +import com.samskivert.jdbc.depot.clause.SelectClause; /** * The implementation of {@link DepotRepository#find) functionality. @@ -39,21 +42,23 @@ public class FindOneQuery throws PersistenceException { _marsh = ctx.getMarshaller(type); - DepotTypes types = SQLQueryBuilder.getDepotTypes(ctx, type, clauses); - _builder = new SQLQueryBuilder(ctx, types, _marsh.getFieldNames(), clauses); + _select = new SelectClause(type, _marsh.getFieldNames(), clauses); + _builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select)); + _builder.newQuery(_select); } // from Query public CacheKey getCacheKey () { - if (_builder.where != null && _builder.where instanceof CacheKey) { - return (CacheKey) _builder.where; + WhereClause where = _select.getWhereClause(); + if (where != null && where instanceof CacheKey) { + return (CacheKey) where; } return null; } // from Query - public T invoke (Connection conn) throws SQLException { + public T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { PreparedStatement stmt = _builder.prepare(conn); try { T result = null; @@ -96,5 +101,6 @@ public class FindOneQuery } protected DepotMarshaller _marsh; - protected SQLQueryBuilder _builder; + protected SelectClause _select; + protected SQLBuilder _builder; } diff --git a/src/java/com/samskivert/jdbc/depot/IdentityKeyGenerator.java b/src/java/com/samskivert/jdbc/depot/IdentityKeyGenerator.java index b33de6a..394b473 100644 --- a/src/java/com/samskivert/jdbc/depot/IdentityKeyGenerator.java +++ b/src/java/com/samskivert/jdbc/depot/IdentityKeyGenerator.java @@ -21,17 +21,22 @@ package com.samskivert.jdbc.depot; import java.sql.Connection; -import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; -import com.samskivert.jdbc.JDBCUtil; +import com.samskivert.jdbc.DatabaseLiaison; +import com.samskivert.jdbc.LiaisonRegistry; +import com.samskivert.jdbc.depot.annotation.GeneratedValue; /** * Generates primary keys using an identity column. */ -public class IdentityKeyGenerator implements KeyGenerator +public class IdentityKeyGenerator extends KeyGenerator { + public IdentityKeyGenerator (GeneratedValue gv, String table, String column) + { + super(gv, table, column); + } + // from interface KeyGenerator public boolean isPostFactum () { @@ -39,27 +44,16 @@ public class IdentityKeyGenerator implements KeyGenerator } // from interface KeyGenerator - public void init (Connection conn) + public void init (Connection conn, DatabaseLiaison liaison) throws SQLException { - // nothing to do here + liaison.initializeGenerator(conn, _table, _column, _initialValue, _allocationSize); } // from interface KeyGenerator - public int nextGeneratedValue (Connection conn) + public int nextGeneratedValue (Connection conn, DatabaseLiaison liaison) throws SQLException { - // load up the last inserted ID mysql style - Statement stmt = conn.createStatement(); - try { - ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()"); - if (rs.next()) { - return rs.getInt(1); - } - throw new SQLException("Failed to generate next ID"); - - } finally { - JDBCUtil.close(stmt); - } + return liaison.lastInsertedId(conn, _table, _column); } } diff --git a/src/java/com/samskivert/jdbc/depot/Key.java b/src/java/com/samskivert/jdbc/depot/Key.java index 68277c4..918633c 100644 --- a/src/java/com/samskivert/jdbc/depot/Key.java +++ b/src/java/com/samskivert/jdbc/depot/Key.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -22,8 +22,6 @@ package com.samskivert.jdbc.depot; import java.io.Serializable; import java.lang.reflect.Field; -import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -31,20 +29,77 @@ import java.util.HashMap; import java.util.Map; import com.samskivert.jdbc.depot.annotation.Id; -import com.samskivert.jdbc.depot.clause.Where; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.util.StringUtil; /** - * A special form of {@link Where} clause that uniquely specifies a single database row and + * A special form of {@link WhereClause} that uniquely specifies a single database row and * thus also a single persistent object. Because it implements both {@link CacheKey} and * {@link CacheInvalidator} it also uniquely indexes into the cache and knows how to invalidate * itself upon modification. This class is created by many {@link DepotMarshaller} methods as * a convenience, and may also be instantiated explicitly. */ -public class Key extends Where +public class Key extends WhereClause implements SQLExpression, CacheKey, CacheInvalidator, Serializable { + /** An expression that contains our key columns and values. */ + public static class WhereCondition implements SQLExpression + { + public WhereCondition (Class pClass, Comparable[] values) + { + _pClass = pClass; + _values = values; + } + + // from SQLExpression + public void accept (ExpressionVisitor builder) + throws Exception + { + builder.visit(this); + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + } + + public Class getPersistentClass () + { + return _pClass; + } + + public Comparable[] getValues () + { + return _values; + } + + @Override + public boolean equals (Object obj) + { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + WhereCondition other = (WhereCondition) obj; + return _pClass == other._pClass && Arrays.equals(_values, other._values); + } + + @Override + public int hashCode () + { + return _pClass.hashCode() ^ Arrays.hashCode(_values); + } + + protected Class _pClass; + protected Comparable[] _values; + } + + /** The expression that identifies our row. */ + public final WhereCondition condition; + /** * Constructs a new single-column {@code Key} with the given value. */ @@ -76,10 +131,6 @@ public class Key extends Where */ public Key (Class pClass, String[] fields, Comparable[] values) { - // TODO: make Where an interface so we don't have to do this ugly super call - super(null); - _pClass = pClass; - if (fields.length != values.length) { throw new IllegalArgumentException("Field and Value arrays must be of equal length."); } @@ -94,69 +145,41 @@ public class Key extends Where String[] keyFields = getKeyFields(pClass); // now extract the values in field order and ensure none are extra or missing - _values = new Comparable[fields.length]; + Comparable[] newValues = new Comparable[fields.length]; for (int ii = 0; ii < keyFields.length; ii++) { - _values[ii] = map.remove(keyFields[ii]); + newValues[ii] = map.remove(keyFields[ii]); // make sure we were provided with a value for this primary key field - if (_values[ii] == null) { + if (newValues[ii] == null) { throw new IllegalArgumentException("Missing value for key field: " + keyFields[ii]); } } + // finally make sure we were not given any fields that are not in fact primary key fields if (map.size() > 0) { throw new IllegalArgumentException( "Non-key columns given: " + StringUtil.join(map.keySet().toArray(), ", ")); } + + condition = new WhereCondition(pClass, newValues); } - // from QueryClause + // from SQLExpression public void addClasses (Collection> classSet) { - classSet.add(_pClass); - } - - // from QueryClause - public void appendClause (QueryBuilderContext query, StringBuilder builder) - { - builder.append(" where "); - appendExpression(query, builder); - } - - // from QueryClause - public int bindClauseArguments (PreparedStatement pstmt, int argIdx) - throws SQLException - { - for (int ii = 0; ii < _values.length; ii ++) { - if (_values[ii] != null) { - pstmt.setObject(argIdx ++, _values[ii]); - } - } - return argIdx; + classSet.add(condition.getPersistentClass()); } // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) + public void accept (ExpressionVisitor builder) + throws Exception { - String[] keyFields = getKeyFields(_pClass); - for (int ii = 0; ii < keyFields.length; ii ++) { - if (ii > 0) { - builder.append(" and "); - } - builder.append(keyFields[ii]).append(_values[ii] == null ? " is null " : " = ? "); - } - } - - // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException - { - return bindClauseArguments(pstmt, argIdx); + builder.visit(this); } // from CacheKey public String getCacheId () { - return _pClass.getName(); + return condition.getPersistentClass().getName(); } // from CacheKey @@ -171,12 +194,6 @@ public class Key extends Where ctx.cacheInvalidate(this); } - @Override - public int hashCode () - { - return _pClass.hashCode() ^ Arrays.hashCode(_values); - } - @Override public boolean equals (Object obj) { @@ -186,17 +203,24 @@ public class Key extends Where if (obj == null || getClass() != obj.getClass()) { return false; } - Key other = (Key) obj; - return _pClass == other._pClass && Arrays.equals(_values, other._values); + return condition.equals(((Key) obj).condition); + } + + @Override + public int hashCode () + { + return condition.hashCode(); } @Override public String toString () { - StringBuilder builder = new StringBuilder("[Key pClass=" + _pClass.getName()); - String[] keyFields = getKeyFields(_pClass); + StringBuilder builder = + new StringBuilder("[Key pClass=").append(condition.getPersistentClass().getName()); + String[] keyFields = getKeyFields(condition.getPersistentClass()); for (int ii = 0; ii < keyFields.length; ii ++) { - builder.append(", ").append(keyFields[ii]).append("=").append(_values[ii]); + builder.append(", ").append(keyFields[ii]).append("="). + append(condition.getValues()[ii]); } builder.append("]"); return builder.toString(); @@ -222,9 +246,6 @@ public class Key extends Where return fields; } - protected Class _pClass; - protected Comparable[] _values; - /** A (never expiring) cache of primary key field names for all persistent classes (of which * there are merely dozens, so we don't need to worry about expiring). */ protected static HashMap,String[]> _keyFields = new HashMap,String[]>(); diff --git a/src/java/com/samskivert/jdbc/depot/KeyGenerator.java b/src/java/com/samskivert/jdbc/depot/KeyGenerator.java index dbdfbc0..cbbc96c 100644 --- a/src/java/com/samskivert/jdbc/depot/KeyGenerator.java +++ b/src/java/com/samskivert/jdbc/depot/KeyGenerator.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -23,18 +23,58 @@ package com.samskivert.jdbc.depot; import java.sql.Connection; import java.sql.SQLException; +import com.samskivert.jdbc.DatabaseLiaison; +import com.samskivert.jdbc.depot.annotation.GeneratedValue; + /** * Defines the interface to our primary key generators. */ -public interface KeyGenerator +public abstract class KeyGenerator { - /** If true, this key generator will be run after the insert statement, if false, it will be - * run before. */ - public boolean isPostFactum (); + public KeyGenerator (GeneratedValue gv, String table, String column) + { + _allocationSize = gv.allocationSize(); + _initialValue = gv.initialValue(); + _table = table; + _column = column; + } - /** Prepares the generator for operation. */ - public void init (Connection conn) throws SQLException; + /** + * If true, this key generator will be run after the insert statement, if false, it will be run + * before. + */ + public abstract boolean isPostFactum (); - /** Fetch/generate the next primary key value. */ - public int nextGeneratedValue (Connection conn) throws SQLException; + /** + * Prepares the generator for operation. + */ + public abstract void init (Connection conn, DatabaseLiaison liaison) + throws SQLException; + + /** + * Fetch/generate the next primary key value. + */ + public abstract int nextGeneratedValue (Connection conn, DatabaseLiaison liaison) + throws SQLException; + + /** + * Returns the name of the table for which we're generating a key value. + */ + public String getTable () + { + return _table; + } + + /** + * Returns the name of the column for which we're generating a key value. + */ + public String getColumn () + { + return _column; + } + + protected int _initialValue; + protected int _allocationSize; + protected String _table; + protected String _column; } diff --git a/src/java/com/samskivert/jdbc/depot/Modifier.java b/src/java/com/samskivert/jdbc/depot/Modifier.java index 3b30a43..7924965 100644 --- a/src/java/com/samskivert/jdbc/depot/Modifier.java +++ b/src/java/com/samskivert/jdbc/depot/Modifier.java @@ -24,6 +24,8 @@ import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; +import com.samskivert.jdbc.DatabaseLiaison; + /** * Encapsulates a modification of persistent objects. */ @@ -33,23 +35,22 @@ public abstract class Modifier * A simple modifier that executes a single SQL statement. No cache flushing is done as a * result of this operation. */ - public static class Simple extends Modifier + public abstract static class Simple extends Modifier { - public Simple (String query) { + public Simple () { super(null); - _query = query; } - public int invoke (Connection conn) throws SQLException { + public int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { Statement stmt = conn.createStatement(); try { - return stmt.executeUpdate(_query); + return stmt.executeUpdate(createQuery(liaison)); } finally { stmt.close(); } } - protected String _query; + protected abstract String createQuery (DatabaseLiaison liaison); } /** @@ -98,10 +99,10 @@ public abstract class Modifier } /** - * Overriden to perform the actual database modifications represented by this object; - * should return the number of modified rows. + * Overriden to perform the actual database modifications represented by this object; should + * return the number of modified rows. */ - public abstract int invoke (Connection conn) throws SQLException; + public abstract int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException; /** * Constructs a {@link Modifier} without a cache invalidator. diff --git a/src/java/com/samskivert/jdbc/depot/MultiKey.java b/src/java/com/samskivert/jdbc/depot/MultiKey.java index 4783f3f..4fc5681 100644 --- a/src/java/com/samskivert/jdbc/depot/MultiKey.java +++ b/src/java/com/samskivert/jdbc/depot/MultiKey.java @@ -20,21 +20,19 @@ package com.samskivert.jdbc.depot; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.samskivert.jdbc.depot.clause.Where; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; /** * A special form of {@link Where} clause that specifies an explicit range of database rows. It * does not implement {@link CacheKey} but it does implement {@link CacheInvalidator} which means * it can be sent into e.g. {@link DepotRepository#deleteAll) and have it clean up after itself. */ -public class MultiKey extends Where +public class MultiKey extends WhereClause implements CacheInvalidator { /** @@ -69,8 +67,6 @@ public class MultiKey extends Where public MultiKey (Class pClass, String[] sFields, Comparable[] sValues, String mField, Comparable[] mValues) { - // TODO - super(null); if (sFields.length != sValues.length) { throw new IllegalArgumentException( "Key field and values arrays must be of equal length."); @@ -84,52 +80,16 @@ public class MultiKey extends Where } } - @Override // from QueryClause + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception + { + builder.visit(this); + } + + // from SQLExpression public void addClasses (Collection> classSet) { - classSet.add(_pClass); - } - - @Override // from Where - public void appendClause (QueryBuilderContext query, StringBuilder builder) - { - builder.append(" where "); - boolean first = true; - for (Map.Entry entry : _map.entrySet()) { - if (first) { - first = false; - } else { - builder.append(" and "); - } - builder.append(entry.getKey()); - builder.append(entry.getValue() == null ? " is null " : " = ? "); - } - if (!first) { - builder.append(" and "); - } - builder.append(_mField).append(" in ("); - for (int ii = 0; ii < _mValues.length; ii ++) { - if (ii > 0) { - builder.append(", "); - } - builder.append("?"); - } - builder.append(")"); - } - - @Override // from Where - public int bindClauseArguments (PreparedStatement pstmt, int argIdx) - throws SQLException - { - for (Map.Entry entry : _map.entrySet()) { - if (entry.getValue() != null) { - pstmt.setObject(argIdx ++, entry.getValue()); - } - } - for (int ii = 0; ii < _mValues.length; ii++) { - pstmt.setObject(argIdx ++, _mValues[ii]); - } - return argIdx; + // nothing to add } // from CacheInvalidator @@ -142,8 +102,28 @@ public class MultiKey extends Where } } - protected Class _pClass; - protected HashMap _map; + public Class getPersistentClass () + { + return _pClass; + } + + public Map getSingleFieldsMap () + { + return _map; + } + + public String getMultiField () + { + return _mField; + } + + public Comparable[] getMultiValues () + { + return _mValues; + } + protected String _mField; protected Comparable[] _mValues; + protected Class _pClass; + protected HashMap _map; } diff --git a/src/java/com/samskivert/jdbc/depot/MySQLBuilder.java b/src/java/com/samskivert/jdbc/depot/MySQLBuilder.java new file mode 100644 index 0000000..719759e --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/MySQLBuilder.java @@ -0,0 +1,191 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006 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; + +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.Time; +import java.sql.Timestamp; + +import com.samskivert.jdbc.depot.FieldMarshaller.BooleanMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.ByteArrayMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.ByteEnumMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.ByteMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.DoubleMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.FloatMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.IntArrayMarshaller; +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.expression.ColumnExp; +import com.samskivert.jdbc.depot.operator.Conditionals.Match; + +public class MySQLBuilder + extends SQLBuilder +{ + public class MSBuildVisitor extends BuildVisitor + { + protected MSBuildVisitor (DepotTypes types) + { + super(types); + } + + @Override + public void visit (Match match) + throws Exception + { + _builder.append("match("); + ColumnExp[] columns = match.getColumns(); + for (int ii = 0; ii < columns.length; ii ++) { + if (ii > 0) { + _builder.append(", "); + } + columns[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; + } + if (match.isQueryExpansion()) { + _builder.append(" with query expansion"); + } + _builder.append(")"); + } + + protected void appendTableName (Class type) + { + _builder.append(_types.getTableName(type)); + } + + protected void appendTableAbbreviation (Class type) + { + _builder.append(_types.getTableAbbreviation(type)); + } + + protected void appendColumn (Class type, String field) + { + _builder.append(_types.getColumnName(type, field)); + } + + protected void appendField (String field) + { + _builder.append(field); + } + } + + public class MSBindVisitor extends BindVisitor + { + protected MSBindVisitor (DepotTypes types, PreparedStatement stmt) + { + super(types, stmt); + } + + @Override + public void visit (Match match) + throws Exception + { + _stmt.setString(_argIdx ++, match.getQuery()); + } + } + + public MySQLBuilder (DepotTypes types) + { + super(types); + } + + @Override + protected BuildVisitor getBuildVisitor () + { + return new MSBuildVisitor(_types); + } + + @Override + protected BindVisitor getBindVisitor (PreparedStatement stmt) + { + return new MSBindVisitor(_types, stmt); + } + + @Override + protected String getColumnType (FieldMarshaller fm) + { + if (fm instanceof ByteMarshaller) { + return "TINYINT"; + } else if (fm instanceof ShortMarshaller) { + return "SMALLINT"; + } else if (fm instanceof IntMarshaller) { + return "INTEGER"; + } else if (fm instanceof LongMarshaller) { + return "BIGINT"; + } else if (fm instanceof FloatMarshaller) { + return "FLOAT"; + } else if (fm instanceof DoubleMarshaller) { + return "DOUBLE"; + } else if (fm instanceof ObjectMarshaller) { + Class ftype = fm.getField().getType(); + if (ftype.equals(Byte.class)) { + return "SMALLINT"; + } else if (ftype.equals(Short.class)) { + return "SMALLINT"; + } else if (ftype.equals(Integer.class)) { + return "INTEGER"; + } else if (ftype.equals(Long.class)) { + return "BIGINT"; + } else if (ftype.equals(Float.class)) { + return "FLOAT"; + } else if (ftype.equals(Double.class)) { + return "DOUBLE"; + } else if (ftype.equals(String.class)) { + return "VARCHAR"; + } else if (ftype.equals(Date.class)) { + return "DATE"; + } else if (ftype.equals(Time.class)) { + return "DATETIME"; + } else if (ftype.equals(Timestamp.class)) { + return "DATETIME"; + } else if (ftype.equals(Blob.class)) { + return "BYTEA"; + } else if (ftype.equals(Clob.class)) { + return "TEXT"; + } else { + throw new IllegalArgumentException( + "Don't know how to create SQL for " + ftype + "."); + } + } else if (fm instanceof ByteArrayMarshaller) { + return "VARBINARY"; + } else if (fm instanceof IntArrayMarshaller) { + return "BLOB"; + } else if (fm instanceof ByteEnumMarshaller) { + return "TINYINT"; + } else if (fm instanceof BooleanMarshaller) { + return "TINYINT"; + } else { + throw new IllegalArgumentException("Unknown field marshaller type: " + fm.getClass()); + } + } +} diff --git a/src/java/com/samskivert/jdbc/depot/PersistenceContext.java b/src/java/com/samskivert/jdbc/depot/PersistenceContext.java index 162f75b..4d6eeba 100644 --- a/src/java/com/samskivert/jdbc/depot/PersistenceContext.java +++ b/src/java/com/samskivert/jdbc/depot/PersistenceContext.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -40,8 +40,11 @@ import com.samskivert.io.PersistenceException; import com.samskivert.util.StringUtil; import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.jdbc.DatabaseLiaison; import com.samskivert.jdbc.DuplicateKeyException; - +import com.samskivert.jdbc.LiaisonRegistry; +import com.samskivert.jdbc.MySQLLiaison; +import com.samskivert.jdbc.PostgreSQLLiaison; /** * Defines a scope in which global annotations are shared. @@ -124,6 +127,23 @@ public class PersistenceContext _ident = ident; _conprov = conprov; _cachemgr = CacheManager.getInstance(); + _liaison = LiaisonRegistry.getLiaison(conprov.getURL(ident)); + } + + /** + * Create and return a new {@link SQLBuilder} for the appropriate dialect. + * + * TODO: At some point perhaps use a more elegant way of discerning our dialect. + */ + public SQLBuilder getSQLBuilder (DepotTypes types) + { + if (_liaison instanceof PostgreSQLLiaison) { + return new PostgreSQLBuilder(types); + } + if (_liaison instanceof MySQLLiaison) { + return new MySQLBuilder(types); + } + throw new IllegalArgumentException("Unknown liaison type: " + _liaison.getClass()); } /** @@ -399,25 +419,21 @@ public class PersistenceContext Connection conn = _conprov.getConnection(_ident, isReadOnlyQuery); try { if (isReadOnlyQuery) { - // if this becomes more complex than this single statement, - // then this should turn into a method call that contains - // the complexity - return query.invoke(conn); + // if this becomes more complex than this single statement, then this should turn + // into a method call that contains the complexity + return query.invoke(conn, _liaison); } else { - // if this becomes more complex than this single statement, - // then this should turn into a method call that contains - // the complexity - return modifier.invoke(conn); + // if this becomes more complex than this single statement, then this should turn + // into a method call that contains the complexity + return modifier.invoke(conn, _liaison); } } catch (SQLException sqe) { if (!isReadOnlyQuery) { - // convert this exception to a DuplicateKeyException if - // appropriate - String msg = sqe.getMessage(); - if (msg != null && msg.indexOf("Duplicate entry") != -1) { - throw new DuplicateKeyException(msg); + // convert this exception to a DuplicateKeyException if appropriate + if (_liaison.isDuplicateRowException(sqe)) { + throw new DuplicateKeyException(sqe.getMessage()); } } @@ -425,7 +441,7 @@ public class PersistenceContext _conprov.connectionFailed(_ident, isReadOnlyQuery, conn, sqe); conn = null; - if (retryOnTransientFailure && isTransientException(sqe)) { + if (retryOnTransientFailure && _liaison.isTransientException(sqe)) { // the MySQL JDBC driver has the annoying habit of including // the embedded exception stack trace in the message of their // outer exception; if I want a fucking stack trace, I'll call @@ -448,21 +464,10 @@ public class PersistenceContext return invoke(query, modifier, false); } - /** - * Check whether the specified exception is a transient failure that can be retried. - */ - protected boolean isTransientException (SQLException sqe) - { - // TODO: this is MySQL specific. This was snarfed from MySQLLiaison. - String msg = sqe.getMessage(); - return (msg != null && (msg.indexOf("Lost connection") != -1 || - msg.indexOf("link failure") != -1 || - msg.indexOf("Broken pipe") != -1)); - } - protected String _ident; protected ConnectionProvider _conprov; protected CacheManager _cachemgr; + protected DatabaseLiaison _liaison; protected Map>> _listenerSets = new HashMap>>(); diff --git a/src/java/com/samskivert/jdbc/depot/PostgreSQLBuilder.java b/src/java/com/samskivert/jdbc/depot/PostgreSQLBuilder.java new file mode 100644 index 0000000..c530b5c --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/PostgreSQLBuilder.java @@ -0,0 +1,155 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006 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; + +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.Time; +import java.sql.Timestamp; + +import com.samskivert.jdbc.depot.FieldMarshaller.BooleanMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.ByteArrayMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.ByteEnumMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.ByteMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.DoubleMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.FloatMarshaller; +import com.samskivert.jdbc.depot.FieldMarshaller.IntArrayMarshaller; +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; + +public class PostgreSQLBuilder + extends SQLBuilder +{ + public class PGBuildVisitor extends BuildVisitor + { + protected PGBuildVisitor (DepotTypes types) + { + super(types); + } + + protected void appendTableName (Class type) + { + _builder.append("\"").append(_types.getTableName(type)).append("\""); + } + + protected void appendTableAbbreviation (Class type) + { + _builder.append("\"").append(_types.getTableAbbreviation(type)).append("\""); + } + + protected void appendColumn (Class type, String field) + { + _builder.append("\"").append(_types.getColumnName(type, field)).append("\""); + } + + protected void appendField (String field) + { + _builder.append("\"").append(field).append("\""); + } + } + + public class PGBindVisitor extends BindVisitor + { + protected PGBindVisitor (DepotTypes types, PreparedStatement stmt) + { + super(types, stmt); + } + } + + public PostgreSQLBuilder (DepotTypes types) + { + super(types); + } + + @Override + protected BuildVisitor getBuildVisitor () + { + return new PGBuildVisitor(_types); + } + + @Override + protected BindVisitor getBindVisitor (PreparedStatement stmt) + { + return new PGBindVisitor(_types, stmt); + } + + @Override + protected String getColumnType (FieldMarshaller fm) + { + if (fm instanceof ByteMarshaller) { + return "SMALLINT"; + } else if (fm instanceof ShortMarshaller) { + return "SMALLINT"; + } else if (fm instanceof IntMarshaller) { + return "INTEGER"; + } else if (fm instanceof LongMarshaller) { + return "BIGINT"; + } else if (fm instanceof FloatMarshaller) { + return "FLOAT"; + } else if (fm instanceof DoubleMarshaller) { + return "DOUBLE"; + } else if (fm instanceof ObjectMarshaller) { + Class ftype = fm.getField().getType(); + if (ftype.equals(Byte.class)) { + return "SMALLINT"; + } else if (ftype.equals(Short.class)) { + return "SMALLINT"; + } else if (ftype.equals(Integer.class)) { + return "INTEGER"; + } else if (ftype.equals(Long.class)) { + return "BIGINT"; + } else if (ftype.equals(Float.class)) { + return "FLOAT"; + } else if (ftype.equals(Double.class)) { + return "DOUBLE"; + } else if (ftype.equals(String.class)) { + return "VARCHAR"; + } else if (ftype.equals(Date.class)) { + return "DATE"; + } else if (ftype.equals(Time.class)) { + return "TIME"; + } else if (ftype.equals(Timestamp.class)) { + return "TIMESTAMP"; + } else if (ftype.equals(Blob.class)) { + return "BYTEA"; + } else if (ftype.equals(Clob.class)) { + return "TEXT"; + } else { + throw new IllegalArgumentException( + "Don't know how to create SQL for " + ftype + "."); + } + } else if (fm instanceof ByteArrayMarshaller) { + return "BYTEA"; + } else if (fm instanceof IntArrayMarshaller) { + return "BYTEA"; + } else if (fm instanceof ByteEnumMarshaller) { + return "SMALLINT"; + } else if (fm instanceof BooleanMarshaller) { + return "BOOLEAN"; + } else { + throw new IllegalArgumentException("Unknown field marshaller type: " + fm.getClass()); + } + } +} diff --git a/src/java/com/samskivert/jdbc/depot/Query.java b/src/java/com/samskivert/jdbc/depot/Query.java index 7382731..e54b8a0 100644 --- a/src/java/com/samskivert/jdbc/depot/Query.java +++ b/src/java/com/samskivert/jdbc/depot/Query.java @@ -23,11 +23,30 @@ package com.samskivert.jdbc.depot; import java.sql.Connection; import java.sql.SQLException; +import com.samskivert.jdbc.DatabaseLiaison; + /** * The base of all read-only queries. */ public interface Query { + /** A simple base class for non-complex queries. */ + public abstract class TrivialQuery implements Query + { + public abstract T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException; + + public CacheKey getCacheKey () { + return null; + } + + public T transformCacheHit (CacheKey key, T value) { + return value; + } + + public void updateCache (PersistenceContext ctx, T result) { + } + } + /** * Any query may elect to utilize the built-in cache by returning a non-null {@link CacheKey} * in this method. This is done automatically by the {@link DepotRepository} when looking up @@ -43,7 +62,7 @@ public interface Query /** * Performs the actual JDBC operations associated with this query. */ - public T invoke (Connection conn) + public T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException; /** diff --git a/src/java/com/samskivert/jdbc/depot/QueryBuilderContext.java b/src/java/com/samskivert/jdbc/depot/QueryBuilderContext.java deleted file mode 100644 index fa1ed0e..0000000 --- a/src/java/com/samskivert/jdbc/depot/QueryBuilderContext.java +++ /dev/null @@ -1,43 +0,0 @@ -// -// $Id$ -// -// samskivert library - useful routines for java programs -// Copyright (C) 2006-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; - -/** - * This type is used during the construction of SQL queries to map persistent classes - * to table names and table abbreviations. Its primary implementation is {@link DepotTypes}. - */ -public interface QueryBuilderContext -{ - /** - * Maps a class referenced by this query to its associated table. - * - * This method is called by individual clauses. - */ - public String getTableName (Class cl); - - /** - * Maps a class referenced by this query to the abbreviation used for its associated table. The - * abbreviation is meaningful only within the scope of this particular query. - * - * This method is called by individual clauses. - */ - public String getTableAbbreviation (Class cl); -} diff --git a/src/java/com/samskivert/jdbc/depot/SQLBuilder.java b/src/java/com/samskivert/jdbc/depot/SQLBuilder.java new file mode 100644 index 0000000..83a080a --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/SQLBuilder.java @@ -0,0 +1,195 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006-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; + +import java.lang.reflect.Field; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import com.samskivert.jdbc.depot.annotation.Column; +import com.samskivert.jdbc.depot.annotation.GeneratedValue; +import com.samskivert.jdbc.depot.clause.QueryClause; + +/** + * At the heart of Depot's SQL generation, this object constructs two {@link ExpressionVisitor} + * objects and executes them, one after another; the first one constructs SQL as it recurses, the + * other binds arguments in the {@link PreparedStatement}. This class must be subclassed by the + * database dialects we wish to support. + */ +public abstract class SQLBuilder +{ + public SQLBuilder (DepotTypes types) + { + _types = types; + } + + /** + * 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. + */ + public void newQuery (QueryClause clause) + { + _clause = clause; + _buildVisitor = getBuildVisitor(); + + try { + _clause.accept(_buildVisitor); + } catch (Exception e) { + throw new RuntimeException("Failed to build SQL", e); + } + } + + /** + * After {@link #newQuery(QueryClause)} has been executed, this method is run to recurse + * through the {@link QueryClause} structure, setting the {@link PreparedStatement} arguments + * that were defined in the generated SQL. + * + * This method throws {@link SQLException} and is thus meant to be called from within + * {@link Query#invoke(Connection)} and {@link Modifier#invoke(Connection)}. + */ + public PreparedStatement prepare (Connection conn) + throws SQLException + { + if (_buildVisitor == null) { + throw new IllegalArgumentException("Cannot prepare query until it's been built."); + } + PreparedStatement stmt = conn.prepareStatement(_buildVisitor.getQuery()); + _bindVisitor = getBindVisitor(stmt); + + try { + _clause.accept(_bindVisitor); + } catch (SQLException se) { + throw se; + } catch (Exception e) { + throw new RuntimeException("Failed to find SQL parameters", e); + } + return stmt; + } + + /** + * Generates the SQL needed to construct a database column for field represented by the given + * {@link FieldMarshaller}. + * + * TODO: This method should be split into several parts that are more easily overridden on a + * case-by-case basis in the dialectal subclasses. + */ + public String buildColumnDefinition (FieldMarshaller fm) + { + // if this field is @Computed, it has no SQL definition + if (fm.getComputed() != null) { + return null; + } + + Field field = fm.getField(); + + // read our column metadata from the annotation (if it exists); annoyingly we can't create + // a Column instance to read the defaults so we have to duplicate them here + int length = 255; + boolean nullable = false; + boolean unique = false; + String type = ""; + String defval = ""; + Column column = field.getAnnotation(Column.class); + if (column != null) { + nullable = column.nullable(); + unique = column.unique(); + length = column.length(); + type = column.type(); + defval = column.defaultValue(); + } + + // create our SQL column definition + StringBuilder builder = new StringBuilder(); + boolean typeDone = false; + + // handle primary keyness + GeneratedValue genValue = fm.getGeneratedValue(); + if (genValue != null) { + switch (genValue.strategy()) { + case AUTO: + case IDENTITY: + builder.append(" SERIAL UNIQUE"); + typeDone = true; + break; + case SEQUENCE: // TODO + throw new IllegalArgumentException( + "SEQUENCE key generation strategy not yet supported."); + case TABLE: + // nothing to do here, it'll be handled later + break; + } + } + + if (!typeDone) { + if (type.length() == 0) { + type = getColumnType(fm); + } + builder.append(" ").append(type); + + // if this is a VARCHAR field, add the length + if (type.equals("VARCHAR") || type.equals("VARBINARY")) { + builder.append("(").append(length).append(")"); + } + + // TODO: handle precision and scale + + // handle nullability and uniqueness + if (!nullable) { + builder.append(" NOT NULL"); + } + if (unique) { + builder.append(" UNIQUE"); + } + + // append the default value if one was specified + if (defval.length() > 0) { + builder.append(" DEFAULT ").append(defval); + } + } + + return builder.toString(); + } + + /** + * Overridden by subclasses to create a dialect-specific {@link BuildVisitor}. + */ + protected abstract BuildVisitor getBuildVisitor (); + + /** + * Overridden by subclasses to create a dialect-specific {@link BindVisitor}. + */ + protected abstract BindVisitor getBindVisitor (PreparedStatement stmt); + + /** + * Overridden by subclasses to figure the dialect-specific SQL type of the given field. + */ + protected abstract String getColumnType (FieldMarshaller fm); + + /** The class that maps persistent classes to marshallers. */ + protected DepotTypes _types; + + protected QueryClause _clause; + protected BuildVisitor _buildVisitor; + protected BindVisitor _bindVisitor; +} diff --git a/src/java/com/samskivert/jdbc/depot/SQLQueryBuilder.java b/src/java/com/samskivert/jdbc/depot/SQLQueryBuilder.java deleted file mode 100644 index d876e18..0000000 --- a/src/java/com/samskivert/jdbc/depot/SQLQueryBuilder.java +++ /dev/null @@ -1,288 +0,0 @@ -// -// $Id$ -// -// samskivert library - useful routines for java programs -// Copyright (C) 2006-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; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.samskivert.io.PersistenceException; -import com.samskivert.jdbc.depot.annotation.Computed; -import com.samskivert.jdbc.depot.clause.FieldOverride; -import com.samskivert.jdbc.depot.clause.ForUpdate; -import com.samskivert.jdbc.depot.clause.FromOverride; -import com.samskivert.jdbc.depot.clause.GroupBy; -import com.samskivert.jdbc.depot.clause.Join; -import com.samskivert.jdbc.depot.clause.Limit; -import com.samskivert.jdbc.depot.clause.OrderBy; -import com.samskivert.jdbc.depot.clause.QueryClause; -import com.samskivert.jdbc.depot.clause.Where; - -/** - * Builds actual SQL given a main persistent type and some {@link QueryClause} objects. - */ -public class SQLQueryBuilder -{ - /** The from override clause, if any. */ - public FromOverride fromOverride; - - /** The where clause. */ - public Where where; - - /** A list of join clauses, each potentially referencing a new class. */ - public List joinClauses = new ArrayList(); - - /** The order by clause, if any. */ - public OrderBy orderBy; - - /** The group by clause, if any. */ - public GroupBy groupBy; - - /** The limit clause, if any. */ - public Limit limit; - - /** The For Update clause, if any. */ - public ForUpdate forUpdate; - - /** - * Constructs a {@link DepotTypes} object for your convenience given similar arguments used to - * construct a {@link SQLQueryBuilder}. This method mainly offloads the task of interrogating - * the various clauses for what persistent classes they introduce into the query. - */ - public static DepotTypes getDepotTypes ( - PersistenceContext ctx, Class type, QueryClause... clauses) - throws PersistenceException - { - Set> classSet = - new HashSet>(); - if (type != null) { - classSet.add(type); - } - - for (QueryClause clause : clauses) { - if (clause != null) { - clause.addClasses(classSet); - } - } - - return new DepotTypes(ctx, type, classSet); - } - - /** - * Creates a new Query object to generate one or more instances of the specified persistent - * class, as dictated by the key and query clauses. A persistence context is supplied for - * instantiation of marshallers, which may trigger table creations and schema migrations. - */ - public SQLQueryBuilder (PersistenceContext ctx, DepotTypes types, String[] fields, - QueryClause... clauses) - { - _builder = new StringBuilder("select "); - _types = types; - - // iterate over the clauses and sort them into the different types we understand - for (QueryClause clause : clauses) { - if (clause == null) { - continue; - } - if (clause instanceof Where) { - if (where != null) { - throw new IllegalArgumentException( - "Query can't contain multiple Where clauses."); - } - where = (Where) clause; - - } else if (clause instanceof FromOverride) { - if (fromOverride != null) { - throw new IllegalArgumentException( - "Query can't contain multiple FromOverride clauses."); - } - fromOverride = (FromOverride) clause; - - } else if (clause instanceof Join) { - joinClauses.add((Join) clause); - - } else if (clause instanceof FieldOverride) { - _disMap.put(((FieldOverride) clause).getField(), - ((FieldOverride) clause)); - - } else if (clause instanceof OrderBy) { - if (orderBy != null) { - throw new IllegalArgumentException( - "Query can't contain multiple OrderBy clauses."); - } - orderBy = (OrderBy) clause; - - } else if (clause instanceof GroupBy) { - if (groupBy != null) { - throw new IllegalArgumentException( - "Query can't contain multiple GroupBy clauses."); - } - groupBy = (GroupBy) clause; - - } else if (clause instanceof Limit) { - if (limit != null) { - throw new IllegalArgumentException( - "Query can't contain multiple Limit clauses."); - } - limit = (Limit) clause; - - } else if (clause instanceof ForUpdate) { - if (forUpdate != null) { - throw new IllegalArgumentException( - "Query can't contain multiple For Update clauses."); - } - forUpdate = (ForUpdate) clause; - } - } - - Computed entityComputed = _types.getMainType().getAnnotation(Computed.class); - - // iterate over the fields we're filling in and figure out whence each one comes - boolean skip = true; - for (String field : fields) { - if (!skip) { - _builder.append(", "); - } - skip = false; - - // first, see if there's a field override - FieldOverride clause1 = _disMap.get(field); - if (clause1 != null) { - clause1.appendClause(_types, _builder); - continue; - } - - // figure out the class we're selecting from unless we're otherwise overriden: - // for a concrete record, simply use the corresponding table; for a computed one, - // default to the shadowed concrete record, or null if there isn't one - Class tableClass = - entityComputed == null ? _types.getMainType() : entityComputed.shadowOf(); - - // handle the field-level @Computed annotation, if there is one - Computed fieldComputed = - _types.getMainMarshaller().getFieldMarshaller(field).getComputed(); - if (fieldComputed != null) { - // check if the computed field has a literal SQL definition - if (fieldComputed.fieldDefinition().length() > 0) { - _builder.append(fieldComputed.fieldDefinition()).append(" as ").append(field); - continue; - } - - // or if we can simply ignore the field - if (!fieldComputed.required()) { - skip = true; - continue; - } - - // else see if there's an overriding shadowOf definition - if (fieldComputed.shadowOf() != null) { - tableClass = fieldComputed.shadowOf(); - } - } - - // if we get this far we hopefully have a table to select from - if (tableClass != null) { - String tableName = _types.getTableAbbreviation(tableClass); - _builder.append(tableName).append(".").append(field); - continue; - } - - // else owie - throw new IllegalArgumentException( - "Persistent field has no definition [class=" + _types.getMainType() + - ", field=" + field + "]"); - } - } - - public PreparedStatement prepare (Connection conn) - throws SQLException - { - if (fromOverride != null) { - fromOverride.appendClause(_types, _builder); - - } else if (_types.getMainTableName() != null) { - _builder.append(" from ").append(_types.getMainTableName()). - append(" as ").append(_types.getMainTableAbbreviation()); - - } else { - throw new SQLException("Query on @Computed entity with no FromOverrideClause."); - } - - for (Join clause : joinClauses) { - clause.appendClause(_types, _builder); - } - if (where != null) { - where.appendClause(_types, _builder); - } - if (groupBy != null) { - groupBy.appendClause(_types, _builder); - } - if (orderBy != null) { - orderBy.appendClause(_types, _builder); - } - if (limit != null) { - limit.appendClause(_types, _builder); - } - if (forUpdate != null) { - forUpdate.appendClause(_types, _builder); - } - - PreparedStatement pstmt = conn.prepareStatement(_builder.toString()); - int argIdx = 1; - for (Join clause : joinClauses) { - argIdx = clause.bindClauseArguments(pstmt, argIdx); - } - if (where != null) { - argIdx = where.bindClauseArguments(pstmt, argIdx); - } - if (groupBy != null) { - argIdx = groupBy.bindClauseArguments(pstmt, argIdx); - } - if (orderBy != null) { - argIdx = orderBy.bindClauseArguments(pstmt, argIdx); - } - if (limit != null) { - argIdx = limit.bindClauseArguments(pstmt, argIdx); - } - if (forUpdate != null) { - argIdx = forUpdate.bindClauseArguments(pstmt, argIdx); - } - - return pstmt; - } - - /** A StringBuilder to hold the constructed SQL. */ - protected StringBuilder _builder; - - /** The class that maps persistent classes to marshallers. */ - protected DepotTypes _types; - - /** Persistent class fields mapped to field override clauses. */ - protected Map _disMap = new HashMap(); -} diff --git a/src/java/com/samskivert/jdbc/depot/TableKeyGenerator.java b/src/java/com/samskivert/jdbc/depot/TableKeyGenerator.java index 8a700c3..7392030 100644 --- a/src/java/com/samskivert/jdbc/depot/TableKeyGenerator.java +++ b/src/java/com/samskivert/jdbc/depot/TableKeyGenerator.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -25,23 +25,25 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import com.samskivert.jdbc.depot.annotation.GeneratedValue; import com.samskivert.jdbc.depot.annotation.TableGenerator; +import com.samskivert.jdbc.DatabaseLiaison; import com.samskivert.jdbc.JDBCUtil; +import com.samskivert.jdbc.LiaisonRegistry; /** - * Generates primary keys using an external table + * Generates primary keys using an external table . */ -public class TableKeyGenerator implements KeyGenerator +public class TableKeyGenerator extends KeyGenerator { - public TableKeyGenerator (TableGenerator annotation) + public TableKeyGenerator (TableGenerator tg, GeneratedValue gv, String table, String column) { - _table = defStr(annotation.table(), "IdSequences"); - _pkColumnName = defStr(annotation.pkColumnName(), "sequence"); - _pkColumnValue = defStr(annotation.pkColumnValue(), "default"); - _valueColumnName = defStr(annotation.valueColumnName(), "value"); - _allocationSize = annotation.allocationSize(); - _initialValue = annotation.initialValue(); + super(gv, table, column); + _valueTable = defStr(tg.table(), "IdSequences"); + _pkColumnName = defStr(tg.pkColumnName(), "sequence"); + _pkColumnValue = defStr(tg.pkColumnValue(), "default"); + _valueColumnName = defStr(tg.valueColumnName(), "value"); } // from interface KeyGenerator @@ -51,20 +53,23 @@ public class TableKeyGenerator implements KeyGenerator } // from interface KeyGenerator - public void init (Connection conn) + public void init (Connection conn, DatabaseLiaison liaison) throws SQLException { // make sure our table exists - JDBCUtil.createTableIfMissing(conn, _table, new String[] { - _pkColumnName + " VARCHAR(255) PRIMARY KEY", - _valueColumnName + " INTEGER NOT NULL" - }, ""); + liaison.createTableIfMissing( + conn, _valueTable, + new String[] { _pkColumnName, _valueColumnName }, + new String[] { "VARCHAR(255)", "INTEGER NOT NULL" }, + null, + new String[] { _pkColumnName }); // and also that there's a row in it for us PreparedStatement stmt = null; try { stmt = conn.prepareStatement( - "SELECT * FROM " + _table + " WHERE " + _pkColumnName + " = ?"); + " SELECT * FROM " + liaison.tableSQL(_valueTable) + + " WHERE " + liaison.columnSQL(_pkColumnName) + " = ?"); stmt.setString(1, _pkColumnValue); if (stmt.executeQuery().next()) { return; @@ -72,8 +77,10 @@ public class TableKeyGenerator implements KeyGenerator JDBCUtil.close(stmt); stmt = null; - stmt = conn.prepareStatement("INSERT INTO " + _table + " SET " + - _pkColumnName + " = ?, " + _valueColumnName + " = ?"); + stmt = conn.prepareStatement( + " INSERT INTO " + liaison.tableSQL(_valueTable) + " (" + + liaison.columnSQL(_pkColumnName) + ", " + liaison.columnSQL(_valueColumnName) + + ") VALUES (?, ?)"); stmt.setString(1, _pkColumnValue); stmt.setInt(2, _initialValue); stmt.executeUpdate(); @@ -84,27 +91,32 @@ public class TableKeyGenerator implements KeyGenerator } // from interface KeyGenerator - public int nextGeneratedValue (Connection conn) + public int nextGeneratedValue (Connection conn, DatabaseLiaison liaison) throws SQLException { PreparedStatement stmt = null; try { - String query = "SELECT " + _valueColumnName + " FROM " + _table + - " WHERE " + _pkColumnName + "= ? FOR UPDATE"; + // TODO: Make this lockless! + String query = + " SELECT " + liaison.columnSQL(_valueColumnName) + + " FROM " + liaison.tableSQL(_valueTable) + + " WHERE " + liaison.columnSQL(_pkColumnName) + " = ? FOR UPDATE"; stmt = conn.prepareStatement(query); stmt.setString(1, _pkColumnValue); ResultSet rs = stmt.executeQuery(); if (!rs.next()) { - throw new SQLException("Failed to find next primary key value [table=" + _table + - ", column=" + _valueColumnName + + throw new SQLException("Failed to find next primary key value " + + "[table=" + _valueTable + ", column=" + _valueColumnName + ", where=" + _pkColumnName + "=" + _pkColumnValue + "]"); } int val = rs.getInt(1); JDBCUtil.close(stmt); - stmt = conn.prepareStatement("UPDATE " + _table + " SET " + _valueColumnName + " = ? " + - "WHERE " + _pkColumnName + " = ?"); + stmt = conn.prepareStatement( + " UPDATE " + liaison.tableSQL(_valueTable) + + " SET " + liaison.columnSQL(_valueColumnName) + " = ? " + + " WHERE " + liaison.columnSQL(_pkColumnName) + " = ?"); stmt.setInt(1, val + _allocationSize); stmt.setString(2, _pkColumnValue); stmt.executeUpdate(); @@ -126,10 +138,8 @@ public class TableKeyGenerator implements KeyGenerator return value; } - protected String _table; + protected String _valueTable; protected String _pkColumnName; protected String _pkColumnValue; protected String _valueColumnName; - protected int _initialValue; - protected int _allocationSize; } diff --git a/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java b/src/java/com/samskivert/jdbc/depot/WhereClause.java similarity index 78% rename from src/java/com/samskivert/jdbc/depot/ConstructedQuery.java rename to src/java/com/samskivert/jdbc/depot/WhereClause.java index bd3930a..bbd4026 100644 --- a/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java +++ b/src/java/com/samskivert/jdbc/depot/WhereClause.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -18,3 +18,13 @@ // 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; + +import com.samskivert.jdbc.depot.clause.QueryClause; + +/** + * Currently only exists as a type without any functionality of its own. + */ +public abstract class WhereClause extends QueryClause +{ +} diff --git a/src/java/com/samskivert/jdbc/depot/annotation/Column.java b/src/java/com/samskivert/jdbc/depot/annotation/Column.java index 12c4de0..193cbfe 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/Column.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/Column.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -50,14 +50,13 @@ public @interface Column * Whether the database column is nullable. Note: this default differs from the value * used by the EJB3 persistence framework. */ - boolean nullable () default false; + boolean nullable () default false; /** - * The SQL fragment that is used when generating the DDL for the column. Defaults to the - * generated SQL to create a column of the inferred type. + * The SQL type that is used when generating the DDL for the column. */ - String columnDefinition () default ""; - + String type() default ""; + /** * The column length. (Applies to String and byte[] columns.) */ diff --git a/src/java/com/samskivert/jdbc/depot/annotation/Computed.java b/src/java/com/samskivert/jdbc/depot/annotation/Computed.java index e9d7ef8..5c11585 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/Computed.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/Computed.java @@ -25,13 +25,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; /** * Marks a field as computed, meaning it is ignored for schema purposes and it does not directly - * correspond to a column in a table, thus its value must be overridden in the {@link - * QueryBuilderContext}. + * correspond to a column in a table. */ @Retention(value=RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.TYPE }) diff --git a/src/java/com/samskivert/jdbc/depot/annotation/Entity.java b/src/java/com/samskivert/jdbc/depot/annotation/Entity.java index 1d44a59..1efc315 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/Entity.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/Entity.java @@ -37,8 +37,4 @@ public @interface Entity /** Defines indices to add to this entity's table. */ Index[] indices () default {}; - - /** Additional SQL to be added when creating this entity's table for the first time. You can - * specify a MySQL table storage type for example. */ - String postamble () default ""; } diff --git a/src/java/com/samskivert/jdbc/depot/annotation/GeneratedValue.java b/src/java/com/samskivert/jdbc/depot/annotation/GeneratedValue.java index 958acc8..3bded5d 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/GeneratedValue.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/GeneratedValue.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -40,4 +40,18 @@ public @interface GeneratedValue /** Identifies the strategy to be used to generate this value. */ GenerationType strategy () default GenerationType.AUTO; + + /** + * The initial value to be used when allocating id numbers from the generator. The default + * initial value is 1. Note: this default differs from the value used by the EJB3 + * persistence framework. + */ + int initialValue () default 1; + + /** + * The amount to increment by when allocating id numbers from the generator. The default + * allocation size is 1. Note: this default differs from the value used by the EJB3 + * persistence framework. + */ + int allocationSize () default 1; } diff --git a/src/java/com/samskivert/jdbc/depot/annotation/TableGenerator.java b/src/java/com/samskivert/jdbc/depot/annotation/TableGenerator.java index c0a1e3b..78e014b 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/TableGenerator.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/TableGenerator.java @@ -63,18 +63,4 @@ public @interface TableGenerator * the primary key column of the generator table */ String pkColumnValue () default ""; - - /** - * The initial value to be used when allocating id numbers from the generator. The default - * initial value is 1. Note: this default differs from the value used by the EJB3 - * persistence framework. - */ - int initialValue () default 1; - - /** - * The amount to increment by when allocating id numbers from the generator. The default - * allocation size is 1. Note: this default differs from the value used by the EJB3 - * persistence framework. - */ - int allocationSize () default 1; } diff --git a/src/java/com/samskivert/jdbc/depot/clause/DeleteClause.java b/src/java/com/samskivert/jdbc/depot/clause/DeleteClause.java new file mode 100644 index 0000000..c00167b --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/clause/DeleteClause.java @@ -0,0 +1,67 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006 Michael Bayne +// +// 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.clause; + +import java.util.Collection; + +import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.WhereClause; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; + +/** + * Builds actual SQL given a main persistent type and some {@link QueryClause} objects. + */ +public class DeleteClause extends QueryClause +{ + public DeleteClause (Class pClass, WhereClause where) + { + _pClass = pClass; + _where = where; + } + + public Class getPersistentClass () + { + return _pClass; + } + + public WhereClause getWhereClause () + { + return _where; + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + classSet.add(_pClass); + } + + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception + { + builder.visit(this); + } + + /** The type of persistent record on which we operate. */ + protected Class _pClass; + + /** The where clause. */ + protected WhereClause _where; +} diff --git a/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java b/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java index 53c76ed..5d625b3 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java +++ b/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java @@ -20,14 +20,12 @@ package com.samskivert.jdbc.depot.clause; -import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.Collection; import com.samskivert.io.PersistenceException; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; import com.samskivert.jdbc.depot.expression.ColumnExp; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; import com.samskivert.jdbc.depot.expression.LiteralExp; import com.samskivert.jdbc.depot.expression.SQLExpression; @@ -68,11 +66,22 @@ public class FieldOverride extends QueryClause return _field; } - // from QueryClause - public void appendClause (QueryBuilderContext query, StringBuilder builder) + public SQLExpression getOverride () { - _override.appendExpression(query, builder); - builder.append(" as ").append(_field); + return _override; + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + _override.addClasses(classSet); + } + + // from SQLExpression + public void accept (ExpressionVisitor visitor) + throws Exception + { + visitor.visit(this); } /** The name of the field on the persistent object to override. */ @@ -80,4 +89,5 @@ public class FieldOverride extends QueryClause /** The overriding expression. */ protected SQLExpression _override; + } diff --git a/src/java/com/samskivert/jdbc/depot/clause/ForUpdate.java b/src/java/com/samskivert/jdbc/depot/clause/ForUpdate.java index 56985e3..8ca4917 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/ForUpdate.java +++ b/src/java/com/samskivert/jdbc/depot/clause/ForUpdate.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -20,21 +20,24 @@ package com.samskivert.jdbc.depot.clause; -import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; /** * Represents a FOR UPDATE clause. */ public class ForUpdate extends QueryClause { - // from QueryClause - public void appendClause (QueryBuilderContext query, StringBuilder builder) + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception + { + builder.visit(this); + } + + // from SQLExpression + public void addClasses (Collection> classSet) { - builder.append(" for update "); } } diff --git a/src/java/com/samskivert/jdbc/depot/clause/FromOverride.java b/src/java/com/samskivert/jdbc/depot/clause/FromOverride.java index c3fc5e6..0ad1b42 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/FromOverride.java +++ b/src/java/com/samskivert/jdbc/depot/clause/FromOverride.java @@ -20,14 +20,13 @@ package com.samskivert.jdbc.depot.clause; -import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import com.samskivert.io.PersistenceException; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; /** * Completely overrides the FROM clause, if it exists. @@ -54,27 +53,25 @@ public class FromOverride extends QueryClause _fromClasses.addAll(fromClasses); } - // from QueryClause - public void addClasses (Collection> classSet) + public List> getFromClasses () { - classSet.addAll(_fromClasses); + return _fromClasses; } - // from QueryClause - public void appendClause (QueryBuilderContext query, StringBuilder builder) + // from SQLExpression + public void addClasses (Collection> classSet) { - builder.append(" from " ); - for (int ii = 0; ii < _fromClasses.size(); ii++) { - if (ii > 0) { - builder.append(", "); - } - builder.append(query.getTableName(_fromClasses.get(ii))). - append(" as ").append(query.getTableAbbreviation(_fromClasses.get(ii))); - } + classSet.addAll(getFromClasses()); + } + + // from SQLExpression + public void accept (ExpressionVisitor builder) + throws Exception + { + builder.visit(this); } /** The classes of the tables we're selecting from. */ - protected ArrayList> _fromClasses = + protected List> _fromClasses = new ArrayList>(); - } diff --git a/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java b/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java index fa8f07a..7583e40 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java +++ b/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -20,12 +20,10 @@ package com.samskivert.jdbc.depot.clause; -import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; import com.samskivert.jdbc.depot.expression.SQLExpression; /** @@ -38,28 +36,24 @@ public class GroupBy extends QueryClause _values = values; } - // from QueryClause - public void appendClause (QueryBuilderContext query, StringBuilder builder) + public SQLExpression[] getValues () { - builder.append(" group by "); - for (int ii = 0; ii < _values.length; ii++) { - if (ii > 0) { - builder.append(", "); - } - _values[ii].appendExpression(query, builder); - } + return _values; } - // from QueryClause - public int bindClauseArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception { - for (int ii = 0; ii < _values.length; ii++) { - argIdx = _values[ii].bindExpressionArguments(pstmt, argIdx); - } - return argIdx; + builder.visit(this); + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + // I can't imagine a GROUP BY clause bringing in new tables... ? } /** The expressions that are generated for the clause. */ protected SQLExpression[] _values; + } diff --git a/src/java/com/samskivert/jdbc/depot/clause/InsertClause.java b/src/java/com/samskivert/jdbc/depot/clause/InsertClause.java new file mode 100644 index 0000000..bde7dc9 --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/clause/InsertClause.java @@ -0,0 +1,74 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006 Michael Bayne +// +// 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.clause; + +import java.util.Collection; + +import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; + +/** + * Builds actual SQL given a main persistent type and some {@link QueryClause} objects. + */ +public class InsertClause extends QueryClause +{ + public InsertClause (Class pClass, Object pojo, String ixField) + { + _pClass = pClass; + _pojo = pojo; + _ixField = ixField; + } + + public Class getPersistentClass () + { + return _pClass; + } + + public Object getPojo () + { + return _pojo; + } + + public String getIndexField () + { + return _ixField; + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + classSet.add(_pClass); + // If we add SQLExpression[] values INSERT, remember to recurse into them here. + } + + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception + { + builder.visit(this); + } + + protected Class _pClass; + + /** The object from which to fetch values, or null. */ + protected Object _pojo; + + protected String _ixField; +} diff --git a/src/java/com/samskivert/jdbc/depot/clause/Join.java b/src/java/com/samskivert/jdbc/depot/clause/Join.java index 57c369b..3438416 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/Join.java +++ b/src/java/com/samskivert/jdbc/depot/clause/Join.java @@ -20,16 +20,14 @@ package com.samskivert.jdbc.depot.clause; -import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.Collection; import com.samskivert.io.PersistenceException; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; import com.samskivert.jdbc.depot.expression.ColumnExp; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; import com.samskivert.jdbc.depot.expression.SQLExpression; -import com.samskivert.jdbc.depot.operator.Conditionals.*; +import com.samskivert.jdbc.depot.operator.Conditionals.Equals; /** * Represents a JOIN. @@ -50,7 +48,7 @@ public class Join extends QueryClause public Join (ColumnExp primary, ColumnExp join) throws PersistenceException { - _joinClass = join.pClass; + _joinClass = join.getPersistentClass(); _joinCondition = new Equals(primary, join); } @@ -60,12 +58,6 @@ public class Join extends QueryClause _joinCondition = joinCondition; } - // from QueryClause - public void addClasses (Collection> classSet) - { - classSet.add(_joinClass); - } - /** * Configures the type of join to be performed. */ @@ -75,30 +67,31 @@ public class Join extends QueryClause return this; } - // from QueryClause - public void appendClause (QueryBuilderContext query, StringBuilder builder) + public Type getType () { - switch (_type) { - case INNER: - builder.append(" inner join " ); - break; - case LEFT_OUTER: - builder.append(" left outer join " ); - break; - case RIGHT_OUTER: - builder.append(" right outer join " ); - break; - } - builder.append(query.getTableName(_joinClass)).append(" as "); - builder.append(query.getTableAbbreviation(_joinClass)).append(" on "); - _joinCondition.appendExpression(query, builder); + return _type; } - // from QueryClause - public int bindClauseArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + public Class getJoinClass () { - return _joinCondition.bindExpressionArguments(pstmt, argIdx); + return _joinClass; + } + + public SQLExpression getJoinCondition () + { + return _joinCondition; + } + + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception + { + builder.visit(this); + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + classSet.add(_joinClass); } /** Indicates the type of join to be performed. */ diff --git a/src/java/com/samskivert/jdbc/depot/clause/Limit.java b/src/java/com/samskivert/jdbc/depot/clause/Limit.java index f68689c..3da4b3a 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/Limit.java +++ b/src/java/com/samskivert/jdbc/depot/clause/Limit.java @@ -20,12 +20,10 @@ package com.samskivert.jdbc.depot.clause; -import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; /** * Represents a LIMIT/OFFSET clause, for pagination. @@ -38,21 +36,27 @@ public class Limit extends QueryClause _count = count; } - // from QueryClause - public void appendClause (QueryBuilderContext query, StringBuilder builder) + public int getOffset () { - builder.append(" limit ? offset ? "); + return _offset; + } + + public int getCount () + { + return _count; } - // from QueryClause - public int bindClauseArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception { - pstmt.setObject(argIdx++, _count); - pstmt.setObject(argIdx++, _offset); - return argIdx; + builder.visit(this); } - + + // from SQLExpression + public void addClasses (Collection> classSet) + { + } + /** The first row of the result set to return. */ protected int _offset; diff --git a/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java b/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java index e957a01..a362321 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java +++ b/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java @@ -20,13 +20,10 @@ package com.samskivert.jdbc.depot.clause; -import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; -import com.samskivert.jdbc.depot.expression.ColumnExp; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; import com.samskivert.jdbc.depot.expression.LiteralExp; import com.samskivert.jdbc.depot.expression.SQLExpression; @@ -46,22 +43,6 @@ public class OrderBy extends QueryClause return ascending(new LiteralExp("rand()")); } - /** - * Creates and returns an ascending order by clause on the supplied column. - */ - public static OrderBy ascending (String column) - { - return ascending(new ColumnExp(null, column)); - } - - /** - * Creates and returns a descending order by clause on the supplied column. - */ - public static OrderBy descending (String column) - { - return descending(new ColumnExp(null, column)); - } - /** * Creates and returns an ascending order by clause on the supplied expression. */ @@ -84,32 +65,34 @@ public class OrderBy extends QueryClause _orders = orders; } - // from QueryClause - public void appendClause (QueryBuilderContext query, StringBuilder builder) + public SQLExpression[] getValues () { - builder.append(" order by "); - for (int ii = 0; ii < _values.length; ii++) { - if (ii > 0) { - builder.append(", "); - } - _values[ii].appendExpression(query, builder); - builder.append(" ").append(_orders[ii]); - } + return _values; + } + + public Order[] getOrders () + { + return _orders; } - // from QueryClause - public int bindClauseArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception { - for (int ii = 0; ii < _values.length; ii++) { - argIdx = _values[ii].bindExpressionArguments(pstmt, argIdx); - } - return argIdx; + builder.visit(this); } + // from SQLExpression + public void addClasses (Collection> classSet) + { + for (SQLExpression expression : _values) { + expression.addClasses(classSet); + } + } + /** The expressions that are generated for the clause. */ protected SQLExpression[] _values; /** Whether the ordering is to be ascending or descending. */ protected Order[] _orders; + } diff --git a/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java b/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java index 1291992..37a653c 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java +++ b/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java @@ -20,39 +20,11 @@ package com.samskivert.jdbc.depot.clause; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.Collection; - -import com.samskivert.jdbc.depot.QueryBuilderContext; -import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.expression.SQLExpression; /** * Represents a piece or modifier of an SQL query. */ -public abstract class QueryClause +public abstract class QueryClause implements SQLExpression { - /** - * Adds all persistent classes referenced by this clause, if any, to the supplied set. - */ - public void addClasses (Collection> classSet) - { - } - - /** - * Constructs the SQL form of this query clause. The implementor is expected to call methods on - * the Query object to e.g. resolve current table abbreviations associated with classes. - */ - public abstract void appendClause (QueryBuilderContext query, StringBuilder builder); - - /** - * Binds any objects that were referenced in the generated SQL. For each ? that appears in the - * SQL, precisely one parameter must be claimed and bound in this method, and argIdx - * incremented and returned. The default implementation binds nothing. - */ - public int bindClauseArguments (PreparedStatement pstmt, int argIdx) - throws SQLException - { - return argIdx; - } } diff --git a/src/java/com/samskivert/jdbc/depot/clause/SelectClause.java b/src/java/com/samskivert/jdbc/depot/clause/SelectClause.java new file mode 100644 index 0000000..fbc44a3 --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/clause/SelectClause.java @@ -0,0 +1,212 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006 Michael Bayne +// +// 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.clause; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.WhereClause; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; + +/** + * Builds actual SQL given a main persistent type and some {@link QueryClause} objects. + */ +public class SelectClause extends QueryClause +{ + /** + * Creates a new Query object to generate one or more instances of the specified persistent + * class, as dictated by the key and query clauses. A persistence context is supplied for + * instantiation of marshallers, which may trigger table creations and schema migrations. + */ + public SelectClause (Class pClass, String[] fields, QueryClause... clauses) + { + _pClass = pClass; + _fields = fields; + + // iterate over the clauses and sort them into the different types we understand + for (QueryClause clause : clauses) { + if (clause == null) { + continue; + } + if (clause instanceof WhereClause) { + if (_where != null) { + throw new IllegalArgumentException( + "Query can't contain multiple Where clauses."); + } + _where = (WhereClause) clause; + + } else if (clause instanceof FromOverride) { + if (_fromOverride != null) { + throw new IllegalArgumentException( + "Query can't contain multiple FromOverride clauses."); + } + _fromOverride = (FromOverride) clause; + + } else if (clause instanceof Join) { + _joinClauses.add((Join) clause); + + } else if (clause instanceof FieldOverride) { + _disMap.put(((FieldOverride) clause).getField(), ((FieldOverride) clause)); + + } else if (clause instanceof OrderBy) { + if (_orderBy != null) { + throw new IllegalArgumentException( + "Query can't contain multiple OrderBy clauses."); + } + _orderBy = (OrderBy) clause; + + } else if (clause instanceof GroupBy) { + if (_groupBy != null) { + throw new IllegalArgumentException( + "Query can't contain multiple GroupBy clauses."); + } + _groupBy = (GroupBy) clause; + + } else if (clause instanceof Limit) { + if (_limit != null) { + throw new IllegalArgumentException( + "Query can't contain multiple Limit clauses."); + } + _limit = (Limit) clause; + + } else if (clause instanceof ForUpdate) { + if (_forUpdate != null) { + throw new IllegalArgumentException( + "Query can't contain multiple For Update clauses."); + } + _forUpdate = (ForUpdate) clause; + } + } + } + + public FieldOverride lookupOverride (String field) + { + return _disMap.get(field); + } + + public Collection getFieldOverrides () + { + return _disMap.values(); + } + + public Class getPersistentClass () + { + return _pClass; + } + + public String[] getFields () + { + return _fields; + } + + public FromOverride getFromOverride () + { + return _fromOverride; + } + + public WhereClause getWhereClause () + { + return _where; + } + + public List getJoinClauses () + { + return _joinClauses; + } + + public OrderBy getOrderBy () + { + return _orderBy; + } + + public GroupBy getGroupBy () + { + return _groupBy; + } + + public Limit getLimit () + { + return _limit; + } + + public ForUpdate getForUpdate () + { + return _forUpdate; + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + classSet.add(_pClass); + if (_fromOverride != null) { + _fromOverride.addClasses(classSet); + } + if (_where != null) { + _where.addClasses(classSet); + } + for (Join join : _joinClauses) { + join.addClasses(classSet); + } + for (FieldOverride override : _disMap.values()) { + override.addClasses(classSet); + } + } + + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception + { + builder.visit(this); + } + + /** Persistent class fields mapped to field override clauses. */ + protected Map _disMap = new HashMap(); + + /** The persistent class this select defines. */ + protected Class _pClass; + + /** The persistent fields to select. */ + protected String[] _fields; + + /** The from override clause, if any. */ + protected FromOverride _fromOverride; + + /** The where clause. */ + protected WhereClause _where; + + /** A list of join clauses, each potentially referencing a new class. */ + protected List _joinClauses = new ArrayList(); + + /** The order by clause, if any. */ + protected OrderBy _orderBy; + + /** The group by clause, if any. */ + protected GroupBy _groupBy; + + /** The limit clause, if any. */ + protected Limit _limit; + + /** The For Update clause, if any. */ + protected ForUpdate _forUpdate; +} diff --git a/src/java/com/samskivert/jdbc/depot/clause/UpdateClause.java b/src/java/com/samskivert/jdbc/depot/clause/UpdateClause.java new file mode 100644 index 0000000..986fa56 --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/clause/UpdateClause.java @@ -0,0 +1,114 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006 Michael Bayne +// +// 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.clause; + +import java.util.Collection; + +import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.WhereClause; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; +import com.samskivert.jdbc.depot.expression.SQLExpression; + +/** + * Builds actual SQL given a main persistent type and some {@link QueryClause} objects. + */ +public class UpdateClause extends QueryClause +{ + public UpdateClause (Class pClass, WhereClause where, + String[] fields, T pojo) + { + _pClass = pClass; + _where = where; + _fields = fields; + _values = null; + _pojo = pojo; + } + + public UpdateClause (Class pClass, WhereClause where, + String[] fields, SQLExpression[] values) + { + _pClass = pClass; + _fields = fields; + _where = where; + _values = values; + _pojo = null; + } + + public WhereClause getWhereClause () + { + return _where; + } + + public String[] getFields () + { + return _fields; + } + + public SQLExpression[] getValues () + { + return _values; + } + + public Object getPojo () + { + return _pojo; + } + + public Class getPersistentClass () + { + return _pClass; + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + classSet.add(_pClass); + if (_where != null) { + _where.addClasses(classSet); + } + if (_values != null) { + for (int ii = 0; ii < _values.length; ii ++) { + _values[ii].addClasses(classSet); + } + } + } + + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception + { + builder.visit(this); + } + + /** The class we're updating. */ + protected Class _pClass; + + /** The where clause. */ + protected WhereClause _where; + + /** The persistent fields to update. */ + protected String[] _fields; + + /** The field values, or null. */ + protected SQLExpression[] _values; + + /** The object from which to fetch values, or null. */ + protected Object _pojo; +} diff --git a/src/java/com/samskivert/jdbc/depot/clause/Where.java b/src/java/com/samskivert/jdbc/depot/clause/Where.java index b0c29b1..a6a7fa3 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/Where.java +++ b/src/java/com/samskivert/jdbc/depot/clause/Where.java @@ -20,14 +20,12 @@ package com.samskivert.jdbc.depot.clause; -import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; -import com.samskivert.jdbc.depot.clause.QueryClause; +import com.samskivert.jdbc.depot.WhereClause; import com.samskivert.jdbc.depot.expression.ColumnExp; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.jdbc.depot.expression.ValueExp; import com.samskivert.jdbc.depot.operator.Conditionals.Equals; @@ -38,13 +36,8 @@ import com.samskivert.jdbc.depot.operator.Logic.And; * Represents a where clause: the condition can be any comparison operator or logical combination * thereof. */ -public class Where extends QueryClause +public class Where extends WhereClause { - public Where (String index, Comparable value) - { - this(new ColumnExp(index), value); - } - public Where (ColumnExp column, Comparable value) { this(new ColumnExp[] { column }, new Comparable[] { value }); @@ -64,18 +57,6 @@ public class Where extends QueryClause new Comparable[] { value1, value2, value3 }); } - public Where (String index1, Comparable value1, String index2, Comparable value2) - { - this(new ColumnExp(index1), value1, new ColumnExp(index2), value2); - } - - public Where (String index1, Comparable value1, String index2, Comparable value2, - String index3, Comparable value3) - { - this(new ColumnExp(index1), value1, new ColumnExp(index2), value2, - new ColumnExp(index3), value3); - } - public Where (ColumnExp[] columns, Comparable[] values) { this(toCondition(columns, values)); @@ -86,20 +67,23 @@ public class Where extends QueryClause _condition = condition; } - // from QueryClause - public void appendClause (QueryBuilderContext query, StringBuilder builder) + public SQLExpression getCondition () { - builder.append(" where "); - _condition.appendExpression(query, builder); + return _condition; } - // from QueryClause - public int bindClauseArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception { - return _condition.bindExpressionArguments(pstmt, argIdx); + builder.visit(this); } + // from SQLExpression + public void addClasses (Collection> classSet) + { + _condition.addClasses(classSet); + } + protected static SQLExpression toCondition (ColumnExp[] columns, Comparable[] values) { SQLExpression[] comparisons = new SQLExpression[columns.length]; diff --git a/src/java/com/samskivert/jdbc/depot/expression/ColumnExp.java b/src/java/com/samskivert/jdbc/depot/expression/ColumnExp.java index 9cd53b4..68946b7 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/ColumnExp.java +++ b/src/java/com/samskivert/jdbc/depot/expression/ColumnExp.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -20,53 +20,48 @@ package com.samskivert.jdbc.depot.expression; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; /** - * An expression identifying a column of a class, e.g. GameRecord.itemId. If no class is given, - * no disambiguation occurs in the generated SQL. + * An expression that unambiguously identifies a field of a class, e.g. GameRecord.itemId. */ public class ColumnExp implements SQLExpression { - /** The table that hosts the column we reference, or null. */ - final public Class pClass; - - /** The name of the column we reference. */ - final public String pColumn; - - public ColumnExp (String column) - { - this(null, column); - } - - public ColumnExp (Class c, String column) + public ColumnExp (Class pClass, String field) { super(); - pClass = c; - this.pColumn = column; + _pClass = pClass; + _pField = field; } // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) + public void accept (ExpressionVisitor builder) throws Exception { - if (pClass == null || query == null) { - builder.append(pColumn); - } else { - String tRef = query.getTableAbbreviation(pClass); - builder.append(tRef).append(".").append(pColumn); - } + builder.visit(this); } // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + public void addClasses (Collection> classSet) { - return argIdx; + classSet.add(_pClass); } + public Class getPersistentClass () + { + return _pClass; + } + + public String getField () + { + return _pField; + } + + /** The table that hosts the column we reference, or null. */ + protected final Class _pClass; + + /** The name of the column we reference. */ + protected final String _pField; } diff --git a/src/java/com/samskivert/jdbc/depot/expression/ExpressionVisitor.java b/src/java/com/samskivert/jdbc/depot/expression/ExpressionVisitor.java new file mode 100644 index 0000000..df349c0 --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/expression/ExpressionVisitor.java @@ -0,0 +1,103 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006 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.expression; + +import com.samskivert.jdbc.depot.Key.WhereCondition; +import com.samskivert.jdbc.depot.Key; +import com.samskivert.jdbc.depot.MultiKey; +import com.samskivert.jdbc.depot.PersistentRecord; + +import com.samskivert.jdbc.depot.clause.DeleteClause; +import com.samskivert.jdbc.depot.clause.FieldOverride; +import com.samskivert.jdbc.depot.clause.ForUpdate; +import com.samskivert.jdbc.depot.clause.FromOverride; +import com.samskivert.jdbc.depot.clause.GroupBy; +import com.samskivert.jdbc.depot.clause.InsertClause; +import com.samskivert.jdbc.depot.clause.Join; +import com.samskivert.jdbc.depot.clause.Limit; +import com.samskivert.jdbc.depot.clause.OrderBy; +import com.samskivert.jdbc.depot.clause.SelectClause; +import com.samskivert.jdbc.depot.clause.UpdateClause; +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.Logic.Not; +import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator; +import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator; + +/** + * Enumerates visitation methods for every possible SQL expression type. + */ +public interface ExpressionVisitor +{ + public void visit (FieldOverride fieldOverride) + throws Exception; + public void visit (WhereCondition whereCondition) + throws Exception; + public void visit (Key key) + throws Exception; + public void visit (MultiKey key) + throws Exception; + public void visit (FunctionExp functionExp) + throws Exception; + public void visit (FromOverride fromOverride) + throws Exception; + public void visit (MultiOperator multiOperator) + throws Exception; + public void visit (BinaryOperator binaryOperator) + throws Exception; + public void visit (IsNull isNull) + throws Exception; + public void visit (In in) + throws Exception; + public void visit (Match match) + throws Exception; + public void visit (ColumnExp columnExp) + throws Exception; + public void visit (Not not) + throws Exception; + public void visit (GroupBy groupBy) + throws Exception; + public void visit (ForUpdate forUpdate) + throws Exception; + public void visit (OrderBy orderBy) + throws Exception; + public void visit (Where where) + throws Exception; + public void visit (Join join) + throws Exception; + public void visit (Limit limit) + throws Exception; + public void visit (LiteralExp literalExp) + throws Exception; + public void visit (ValueExp valueExp) + throws Exception; + public void visit (SelectClause selectClause) + throws Exception; + public void visit (UpdateClause updateClause) + throws Exception; + public void visit (DeleteClause deleteClause) + throws Exception; + public void visit (InsertClause insertClause) + throws Exception; +} diff --git a/src/java/com/samskivert/jdbc/depot/expression/FunctionExp.java b/src/java/com/samskivert/jdbc/depot/expression/FunctionExp.java index 12dba6d..c2834d3 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/FunctionExp.java +++ b/src/java/com/samskivert/jdbc/depot/expression/FunctionExp.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -20,16 +20,14 @@ package com.samskivert.jdbc.depot.expression; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.PersistentRecord; /** * An expression for a function, e.g. FLOOR(blah). */ -public class FunctionExp - implements SQLExpression +public class FunctionExp implements SQLExpression { /** * Create a new FunctionExp with the given function and arguments. @@ -41,27 +39,28 @@ public class FunctionExp } // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) + public void accept (ExpressionVisitor builder) + throws Exception { - builder.append(_function); - builder.append("("); - for (int ii = 0; ii < _arguments.length; ii ++) { - if (ii > 0) { - builder.append(", "); - } - _arguments[ii].appendExpression(query, builder); - } - builder.append(")"); + builder.visit(this); } // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + public void addClasses (Collection> classSet) { for (int ii = 0; ii < _arguments.length; ii ++) { - argIdx = _arguments[ii].bindExpressionArguments(pstmt, argIdx); + _arguments[ii].addClasses(classSet); } - return argIdx; + } + + public String getFunction () + { + return _function; + } + + public SQLExpression[] getArguments () + { + return _arguments; } /** The literal name of this function, e.g. FLOOR */ diff --git a/src/java/com/samskivert/jdbc/depot/expression/LiteralExp.java b/src/java/com/samskivert/jdbc/depot/expression/LiteralExp.java index 8c9c759..f15bd70 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/LiteralExp.java +++ b/src/java/com/samskivert/jdbc/depot/expression/LiteralExp.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -20,10 +20,9 @@ package com.samskivert.jdbc.depot.expression; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.PersistentRecord; /** * An expression for things we don't support natively, e.g. COUNT(*). @@ -38,16 +37,19 @@ public class LiteralExp } // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) + public void accept (ExpressionVisitor builder) throws Exception { - builder.append(_text); + builder.visit(this); } // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + public void addClasses (Collection> classSet) { - return argIdx; + } + + public String getText () + { + return _text; } /** The literal text of this expression, e.g. COUNT(*) */ diff --git a/src/java/com/samskivert/jdbc/depot/expression/SQLExpression.java b/src/java/com/samskivert/jdbc/depot/expression/SQLExpression.java index f8c5727..fd3dd42 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/SQLExpression.java +++ b/src/java/com/samskivert/jdbc/depot/expression/SQLExpression.java @@ -20,10 +20,10 @@ package com.samskivert.jdbc.depot.expression; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.SQLBuilder; /** * Represents an SQL expression, e.g. column name, function, or constant. @@ -31,16 +31,19 @@ import com.samskivert.jdbc.depot.QueryBuilderContext; public interface SQLExpression { /** - * Construct the SQL form of this expression. The implementor is invited to call methods on the - * Query object to e.g. resolve the current table abbreviations associated with classes. + * Most uses of this class have been implemented with a visitor pattern. Create your own + * {@link ExpressionVisitor} and call this method with it. + * + * @see SQLBuilder */ - public void appendExpression (QueryBuilderContext query, StringBuilder builder); + public void accept (ExpressionVisitor builder) + throws Exception; /** - * Bind any objects that were referenced in the generated SQL. For each ? that appears in the - * SQL, precisely one parameter must be claimed and bound in this method, and argIdx - * incremented and returned. + * Adds all persistent classes that are brought into the SQL context by this clause: FROM + * clauses, JOINs, UPDATEs, anything that could create a new table abbreviation. This method + * should recurse into any subordinate state that may in turn bring in new classes so that + * sub-queries work correctly. */ - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException; + public void addClasses (Collection> classSet); } diff --git a/src/java/com/samskivert/jdbc/depot/expression/ValueExp.java b/src/java/com/samskivert/jdbc/depot/expression/ValueExp.java index cdbe056..b306377 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/ValueExp.java +++ b/src/java/com/samskivert/jdbc/depot/expression/ValueExp.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -20,36 +20,37 @@ package com.samskivert.jdbc.depot.expression; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.PersistentRecord; /** - * A Java value that is bound as a parameter to the query, e.g. 1 or 'abc'. + * A Java value that is bound as a parameter to the query, e.g. 1 or 'abc'. */ public class ValueExp implements SQLExpression { - public ValueExp (Comparable _value) + public ValueExp (Object _value) { this._value = _value; } // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) + public void accept (ExpressionVisitor builder) throws Exception { - builder.append("?"); + builder.visit(this); } // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + public void addClasses (Collection> classSet) { - pstmt.setObject(argIdx ++, _value); - return argIdx; + } + + public Object getValue () + { + return _value; } /** The value to be bound to the SQL parameters. */ - protected Comparable _value; + protected Object _value; } diff --git a/src/java/com/samskivert/jdbc/depot/operator/Arithmetic.java b/src/java/com/samskivert/jdbc/depot/operator/Arithmetic.java index 319ca5f..f1d8858 100644 --- a/src/java/com/samskivert/jdbc/depot/operator/Arithmetic.java +++ b/src/java/com/samskivert/jdbc/depot/operator/Arithmetic.java @@ -20,7 +20,6 @@ package com.samskivert.jdbc.depot.operator; -import com.samskivert.jdbc.depot.expression.ColumnExp; import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator; @@ -34,11 +33,6 @@ public abstract class Arithmetic /** The SQL '+' operator. */ public static class Add extends BinaryOperator { - public Add (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public Add (SQLExpression column, Comparable value) { super(column, value); @@ -50,7 +44,7 @@ public abstract class Arithmetic } @Override - protected String operator() + public String operator() { return "+"; } @@ -59,11 +53,6 @@ public abstract class Arithmetic /** The SQL '-' operator. */ public static class Sub extends BinaryOperator { - public Sub (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public Sub (SQLExpression column, Comparable value) { super(column, value); @@ -75,7 +64,7 @@ public abstract class Arithmetic } @Override - protected String operator() + public String operator() { return "-"; } @@ -84,11 +73,6 @@ public abstract class Arithmetic /** The SQL '*' operator. */ public static class Mul extends BinaryOperator { - public Mul (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public Mul (SQLExpression column, Comparable value) { super(column, value); @@ -100,7 +84,7 @@ public abstract class Arithmetic } @Override - protected String operator() + public String operator() { return "*"; } @@ -109,11 +93,6 @@ public abstract class Arithmetic /** The SQL '/' operator. */ public static class Div extends BinaryOperator { - public Div (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public Div (SQLExpression column, Comparable value) { super(column, value); @@ -125,7 +104,7 @@ public abstract class Arithmetic } @Override - protected String operator() + public String operator() { return "/"; } @@ -134,11 +113,6 @@ public abstract class Arithmetic /** The SQL '&' operator. */ public static class BitAnd extends BinaryOperator { - public BitAnd (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public BitAnd (SQLExpression column, Comparable value) { super(column, value); @@ -150,7 +124,7 @@ public abstract class Arithmetic } @Override - protected String operator() + public String operator() { return "&"; } @@ -159,11 +133,6 @@ public abstract class Arithmetic /** The SQL '|' operator. */ public static class BitOr extends BinaryOperator { - public BitOr (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public BitOr (SQLExpression column, Comparable value) { super(column, value); @@ -175,7 +144,7 @@ public abstract class Arithmetic } @Override - protected String operator() + public String operator() { return "|"; } diff --git a/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java b/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java index 67290db..debaa02 100644 --- a/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java +++ b/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java @@ -3,7 +3,7 @@ // // samskivert library - useful routines for java programs // Copyright (C) 2006-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 @@ -20,15 +20,12 @@ package com.samskivert.jdbc.depot.operator; -import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; import com.samskivert.jdbc.depot.expression.ColumnExp; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; import com.samskivert.jdbc.depot.expression.SQLExpression; -import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator; /** * A convenient container for implementations of conditional operators. Classes that value brevity @@ -56,31 +53,28 @@ public abstract class Conditionals _column = column; } - // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) + public ColumnExp getColumn() { - _column.appendExpression(query, builder); - builder.append(" is null"); + return _column; } // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + public void accept (ExpressionVisitor builder) throws Exception + { + builder.visit(this); + } + + // from SQLExpression + public void addClasses (Collection> classSet) { - return argIdx; } protected ColumnExp _column; } /** The SQL '=' operator. */ - public static class Equals extends BinaryOperator + public static class Equals extends SQLOperator.BinaryOperator { - public Equals (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public Equals (SQLExpression column, Comparable value) { super(column, value); @@ -92,20 +86,15 @@ public abstract class Conditionals } @Override - protected String operator() + public String operator() { return "="; } } /** The SQL '<' operator. */ - public static class LessThan extends BinaryOperator + public static class LessThan extends SQLOperator.BinaryOperator { - public LessThan (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public LessThan (SQLExpression column, Comparable value) { super(column, value); @@ -117,20 +106,15 @@ public abstract class Conditionals } @Override - protected String operator() + public String operator() { return "<"; } } /** The SQL '<=' operator. */ - public static class LessThanEquals extends BinaryOperator + public static class LessThanEquals extends SQLOperator.BinaryOperator { - public LessThanEquals (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public LessThanEquals (SQLExpression column, Comparable value) { super(column, value); @@ -142,20 +126,15 @@ public abstract class Conditionals } @Override - protected String operator() + public String operator() { return "<="; } } /** The SQL '>' operator. */ - public static class GreaterThan extends BinaryOperator + public static class GreaterThan extends SQLOperator.BinaryOperator { - public GreaterThan (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public GreaterThan (SQLExpression column, Comparable value) { super(column, value); @@ -167,20 +146,15 @@ public abstract class Conditionals } @Override - protected String operator() + public String operator() { return ">"; } } /** The SQL '>=' operator. */ - public static class GreaterThanEquals extends BinaryOperator + public static class GreaterThanEquals extends SQLOperator.BinaryOperator { - public GreaterThanEquals (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public GreaterThanEquals (SQLExpression column, Comparable value) { super(column, value); @@ -192,7 +166,7 @@ public abstract class Conditionals } @Override - protected String operator() + public String operator() { return ">="; } @@ -202,16 +176,6 @@ public abstract class Conditionals public static class In implements SQLOperator { - public In (String pColumn, Comparable... values) - { - this(new ColumnExp(null, pColumn), values); - } - - public In (String pColumn, Collection values) - { - this(new ColumnExp(null, pColumn), values.toArray(new Comparable[values.size()])); - } - public In (Class pClass, String pColumn, Comparable... values) { this(new ColumnExp(pClass, pColumn), values); @@ -232,107 +196,40 @@ public abstract class Conditionals _values = values; } - // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) + public In (ColumnExp pColumn, Collection values) { - _column.appendExpression(query, builder); - builder.append(" in ("); - for (int ii = 0; ii < _values.length; ii ++) { - if (ii > 0) { - builder.append(", "); - } - builder.append("?"); - } - builder.append(")"); + this(pColumn, values.toArray(new Comparable[values.size()])); + } + + public ColumnExp getColumn () + { + return _column; + } + + public Comparable[] getValues () + { + return _values; } // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + public void accept (ExpressionVisitor builder) throws Exception { - for (int ii = 0; ii < _values.length; ii++) { - pstmt.setObject(argIdx ++, _values[ii]); - } - return argIdx; + builder.visit(this); + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + _column.addClasses(classSet); } protected ColumnExp _column; protected Comparable[] _values; } - /** The MySQL 'match (...) against (...)' operator. */ - public static class Match - implements SQLOperator - { - public enum Mode { DEFAULT, BOOLEAN, NATURAL_LANGUAGE }; - - public Match (String query, Mode mode, boolean queryExpansion, String... pColumns) - { - _query = query; - _mode = mode; - _queryExpansion = queryExpansion; - _columns = new ColumnExp[pColumns.length]; - for (int ii = 0; ii < pColumns.length; ii++) { - _columns[ii] = new ColumnExp(null, pColumns[ii]); - } - } - - public Match (String query, Mode mode, boolean queryExpansion, ColumnExp... columns) - { - _query = query; - _queryExpansion = queryExpansion; - _mode = mode; - _columns = columns; - } - - // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) - { - builder.append("match("); - int idx = 0; - for (ColumnExp column : _columns) { - if (idx++ > 0) { - builder.append(", "); - } - column.appendExpression(query, builder); - } - builder.append(") against (?"); - switch (_mode) { - case BOOLEAN: - builder.append(" in boolean mode"); - break; - case NATURAL_LANGUAGE: - builder.append(" in natural language mode"); - break; - } - if (_queryExpansion) { - builder.append(" with query expansion"); - } - builder.append(")"); - } - - // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException - { - pstmt.setString(argIdx++, _query); - return argIdx; - } - - protected String _query; - protected Mode _mode; - protected boolean _queryExpansion; - protected ColumnExp[] _columns; - } - /** The SQL ' like ' operator. */ - public static class Like extends BinaryOperator + public static class Like extends SQLOperator.BinaryOperator { - public Like (String pColumn, Comparable value) - { - super(new ColumnExp(pColumn), value); - } - public Like (SQLExpression column, Comparable value) { super(column, value); @@ -344,9 +241,64 @@ public abstract class Conditionals } @Override - protected String operator() + public String operator() { return " like "; } } + + /** The MySQL 'match (...) against (...)' operator. */ + @Deprecated + public static class Match + implements SQLOperator + { + public enum Mode { DEFAULT, BOOLEAN, NATURAL_LANGUAGE }; + + public Match (String query, Mode mode, boolean queryExpansion, ColumnExp... columns) + { + _query = query; + _queryExpansion = queryExpansion; + _mode = mode; + _columns = columns; + } + + public String getQuery () + { + return _query; + } + + public Mode getMode () + { + return _mode; + } + + public boolean isQueryExpansion () + { + return _queryExpansion; + } + + public ColumnExp[] getColumns () + { + return _columns; + } + + // from SQLExpression + public void accept (ExpressionVisitor builder) throws Exception + { + builder.visit(this); + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + for (ColumnExp column : _columns) { + column.addClasses(classSet); + } + } + + protected String _query; + protected Mode _mode; + protected boolean _queryExpansion; + protected ColumnExp[] _columns; + } } diff --git a/src/java/com/samskivert/jdbc/depot/operator/Logic.java b/src/java/com/samskivert/jdbc/depot/operator/Logic.java index 2bf6c53..a080ece 100644 --- a/src/java/com/samskivert/jdbc/depot/operator/Logic.java +++ b/src/java/com/samskivert/jdbc/depot/operator/Logic.java @@ -20,12 +20,11 @@ package com.samskivert.jdbc.depot.operator; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; import com.samskivert.jdbc.depot.expression.SQLExpression; -import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator; /** * A convenient container for implementations of logical operators. Classes that value brevity @@ -37,7 +36,7 @@ public abstract class Logic /** * Represents a condition that is false iff all its subconditions are false. */ - public static class Or extends MultiOperator + public static class Or extends SQLOperator.MultiOperator { public Or (SQLExpression... conditions) { @@ -45,7 +44,7 @@ public abstract class Logic } @Override - protected String operator() + public String operator() { return "or"; } @@ -54,7 +53,7 @@ public abstract class Logic /** * Represents a condition that is true iff all its subconditions are true. */ - public static class And extends MultiOperator + public static class And extends SQLOperator.MultiOperator { public And (SQLExpression... conditions) { @@ -62,7 +61,7 @@ public abstract class Logic } @Override - protected String operator() + public String operator() { return "and"; } @@ -76,25 +75,26 @@ public abstract class Logic { public Not (SQLExpression condition) { - super(); _condition = condition; } - - // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) + + public SQLExpression getCondition () { - builder.append(" not ("); - _condition.appendExpression(query, builder); - builder.append(")"); + return _condition; } // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + public void accept (ExpressionVisitor builder) throws Exception { - return _condition.bindExpressionArguments(pstmt, argIdx); + builder.visit(this); } + // from SQLExpression + public void addClasses (Collection> classSet) + { + _condition.addClasses(classSet); + } + protected SQLExpression _condition; } } diff --git a/src/java/com/samskivert/jdbc/depot/operator/SQLOperator.java b/src/java/com/samskivert/jdbc/depot/operator/SQLOperator.java index ce9bf18..d1af066 100644 --- a/src/java/com/samskivert/jdbc/depot/operator/SQLOperator.java +++ b/src/java/com/samskivert/jdbc/depot/operator/SQLOperator.java @@ -20,10 +20,10 @@ package com.samskivert.jdbc.depot.operator; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.PersistentRecord; +import com.samskivert.jdbc.depot.expression.ExpressionVisitor; import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.jdbc.depot.expression.ValueExp; @@ -42,37 +42,32 @@ public interface SQLOperator extends SQLExpression { public MultiOperator (SQLExpression ... conditions) { - super(); _conditions = conditions; } // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) + public void accept (ExpressionVisitor builder) throws Exception { - for (int ii = 0; ii < _conditions.length; ii++) { - if (ii > 0) { - builder.append(" ").append(operator()).append(" "); - } - builder.append("("); - _conditions[ii].appendExpression(query, builder); - builder.append(")"); + builder.visit(this); + } + + // from SQLExpression + public void addClasses (Collection> classSet) + { + for (int ii = 0; ii < _conditions.length; ii ++) { + _conditions[ii].addClasses(classSet); } } - // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + public SQLExpression[] getConditions () { - for (int ii = 0; ii < _conditions.length; ii++) { - argIdx = _conditions[ii].bindExpressionArguments(pstmt, argIdx); - } - return argIdx; + return _conditions; } /** * Returns the text infix to be used to join expressions together. */ - protected abstract String operator (); + public abstract String operator (); protected SQLExpression[] _conditions; } @@ -94,26 +89,32 @@ public interface SQLOperator extends SQLExpression } // from SQLExpression - public void appendExpression (QueryBuilderContext query, StringBuilder builder) + public void accept (ExpressionVisitor builder) throws Exception { - _lhs.appendExpression(query, builder); - builder.append(operator()); - _rhs.appendExpression(query, builder); + builder.visit(this); } // from SQLExpression - public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) - throws SQLException + public void addClasses (Collection> classSet) { - argIdx = _lhs.bindExpressionArguments(pstmt, argIdx); - argIdx = _rhs.bindExpressionArguments(pstmt, argIdx); - return argIdx; + _lhs.addClasses(classSet); + _rhs.addClasses(classSet); } - + /** * Returns the string representation of the operator. */ - protected abstract String operator(); + public abstract String operator(); + + public SQLExpression getLeftHandSide () + { + return _lhs; + } + + public SQLExpression getRightHandSide () + { + return _rhs; + } protected SQLExpression _lhs; protected SQLExpression _rhs;