diff --git a/src/java/com/samskivert/depot/impl/BindVisitor.java b/src/java/com/samskivert/depot/impl/BindVisitor.java deleted file mode 100644 index 7b0460e..0000000 --- a/src/java/com/samskivert/depot/impl/BindVisitor.java +++ /dev/null @@ -1,350 +0,0 @@ -// -// $Id$ -// -// Depot library - a Java relational persistence library -// Copyright (C) 2006-2008 Michael Bayne and Pär Winzell -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation; either version 2.1 of the License, or -// (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.depot.impl; - -import java.nio.ByteBuffer; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.Map; -import java.util.Set; - -import com.samskivert.util.StringUtil; -import com.samskivert.util.Tuple; - -import com.samskivert.depot.ByteEnum; -import com.samskivert.depot.DatabaseException; -import com.samskivert.depot.Key; -import com.samskivert.depot.MultiKey; -import com.samskivert.depot.PersistentRecord; - -import com.samskivert.depot.clause.FieldDefinition; -import com.samskivert.depot.clause.ForUpdate; -import com.samskivert.depot.clause.FromOverride; -import com.samskivert.depot.clause.GroupBy; -import com.samskivert.depot.clause.InsertClause; -import com.samskivert.depot.clause.Join; -import com.samskivert.depot.clause.Limit; -import com.samskivert.depot.clause.OrderBy.Order; -import com.samskivert.depot.clause.OrderBy; -import com.samskivert.depot.clause.SelectClause; -import com.samskivert.depot.clause.WhereClause; - -import com.samskivert.depot.expression.ColumnExp; -import com.samskivert.depot.expression.EpochSeconds; -import com.samskivert.depot.expression.FunctionExp; -import com.samskivert.depot.expression.LiteralExp; -import com.samskivert.depot.expression.SQLExpression; -import com.samskivert.depot.expression.ValueExp; - -import com.samskivert.depot.operator.Conditionals.Case; -import com.samskivert.depot.operator.Conditionals.Exists; -import com.samskivert.depot.operator.Conditionals.FullText; -import com.samskivert.depot.operator.Conditionals.In; -import com.samskivert.depot.operator.Conditionals.IsNull; -import com.samskivert.depot.operator.Logic.Not; -import com.samskivert.depot.operator.SQLOperator.BinaryOperator; -import com.samskivert.depot.operator.SQLOperator.MultiOperator; - -import com.samskivert.depot.impl.clause.CreateIndexClause; -import com.samskivert.depot.impl.clause.DeleteClause; -import com.samskivert.depot.impl.clause.DropIndexClause; -import com.samskivert.depot.impl.clause.UpdateClause; - -/** - * 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) - { - // nothing needed - } - - public void visit (FieldDefinition fieldOverride) - { - // nothing needed - } - - public void visit (Key.Expression key) - { - for (Comparable value : key.getValues()) { - if (value != null) { - writeValueToStatement(value); - } - } - } - - public void visit (MultiKey key) - { - for (Map.Entry> entry : key.getSingleFieldsMap().entrySet()) { - if (entry.getValue() != null) { - writeValueToStatement(entry.getValue()); - } - } - Comparable[] values = key.getMultiValues(); - for (int ii = 0; ii < values.length; ii++) { - writeValueToStatement(values[ii]); - } - } - - public void visit (FunctionExp functionExp) - { - visit(functionExp.getArguments()); - } - - public void visit (EpochSeconds epochSeconds) - { - epochSeconds.getArgument().accept(this); - } - - public void visit (MultiOperator multiOperator) - { - visit(multiOperator.getConditions()); - } - - public void visit (BinaryOperator binaryOperator) - { - binaryOperator.getLeftHandSide().accept(this); - binaryOperator.getRightHandSide().accept(this); - } - - public void visit (IsNull isNull) - { - } - - public void visit (In in) - { - Comparable[] values = in.getValues(); - for (int ii = 0; ii < values.length; ii++) { - writeValueToStatement(values[ii]); - } - } - - public void visit (FullText.Match match) - { - // we never get here - } - - public void visit (FullText.Rank rank) - { - // we never get here - } - - public void visit (Case caseExp) - { - for (Tuple tuple : caseExp.getWhenExps()) { - tuple.left.accept(this); - tuple.right.accept(this); - } - SQLExpression elseExp = caseExp.getElseExp(); - if (elseExp != null) { - elseExp.accept(this); - } - } - - public void visit (ColumnExp columnExp) - { - // no arguments - } - - public void visit (Not not) - { - not.getCondition().accept(this); - } - - public void visit (GroupBy groupBy) - { - visit(groupBy.getValues()); - } - - public void visit (ForUpdate forUpdate) - { - // do nothing - } - - public void visit (OrderBy orderBy) - { - visit(orderBy.getValues()); - } - - public void visit (WhereClause where) - { - where.getWhereExpression().accept(this); - } - - public void visit (Join join) - { - join.getJoinCondition().accept(this); - } - - public void visit (Limit limit) - { - try { - _stmt.setInt(_argIdx++, limit.getCount()); - _stmt.setInt(_argIdx++, limit.getOffset()); - } catch (SQLException sqe) { - throw new DatabaseException("Failed to configure statement with limit clause " + - "[count=" + limit.getCount() + - ", offset=" + limit.getOffset() + "]", sqe); - } - } - - public void visit (LiteralExp literalExp) - { - // do nothing - } - - public void visit (ValueExp valueExp) - { - writeValueToStatement(valueExp.getValue()); - } - - public void visit (Exists exists) - { - exists.getSubClause().accept(this); - } - - public void visit (SelectClause selectClause) - { - 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) - { - DepotMarshaller marsh = _types.getMarshaller(updateClause.getPersistentClass()); - - // bind the update arguments - Object pojo = updateClause.getPojo(); - if (pojo != null) { - for (String field : updateClause.getFields()) { - try { - marsh.getFieldMarshaller(field).getAndWriteToStatement(_stmt, _argIdx++, pojo); - } catch (Exception e) { - throw new DatabaseException( - "Failed to read field from persistent record and write it to prepared " + - "statement [field=" + field + "]", e); - } - } - } else { - visit(updateClause.getValues()); - } - updateClause.getWhereClause().accept(this); - } - - public void visit (InsertClause insertClause) - { - DepotMarshaller marsh = _types.getMarshaller(insertClause.getPersistentClass()); - Object pojo = insertClause.getPojo(); - Set idFields = insertClause.getIdentityFields(); - for (String field : marsh.getColumnFieldNames()) { - if (!idFields.contains(field)) { - try { - marsh.getFieldMarshaller(field).getAndWriteToStatement(_stmt, _argIdx++, pojo); - } catch (Exception e) { - throw new DatabaseException( - "Failed to read field from persistent record and write it to prepared " + - "statement [field=" + field + "]", e); - } - } - } - } - - public void visit (DeleteClause deleteClause) - { - deleteClause.getWhereClause().accept(this); - } - - public void visit (CreateIndexClause createIndexClause) - { - for (Tuple field : createIndexClause.getFields()) { - field.left.accept(this); - } - } - - public void visit (DropIndexClause createIndexClause) - { - // do nothing - } - - protected BindVisitor (DepotTypes types, Connection conn, PreparedStatement stmt) - { - _types = types; - _conn = conn; - _stmt = stmt; - _argIdx = 1; - } - - protected void visit (SQLExpression[] expressions) - { - for (int ii = 0; ii < expressions.length; ii ++) { - expressions[ii].accept(this); - } - } - - // write the value to the next argument slot in the prepared statement - protected void writeValueToStatement (Object value) - { - try { - // TODO: how can we abstract this fieldless marshalling - if (value instanceof ByteEnum) { - // byte enums require special conversion - _stmt.setByte(_argIdx++, ((ByteEnum)value).toByte()); - } else if (value instanceof int[]) { - // int arrays require conversion to byte arrays - int[] data = (int[])value; - ByteBuffer bbuf = ByteBuffer.allocate(data.length * 4); - bbuf.asIntBuffer().put(data); - _stmt.setObject(_argIdx++, bbuf.array()); - } else { - _stmt.setObject(_argIdx++, value); - } - } catch (SQLException sqe) { - throw new DatabaseException("Failed to write value to statement [idx=" + (_argIdx-1) + - ", value=" + StringUtil.safeToString(value) + "]", sqe); - } - } - - protected DepotTypes _types; - protected Connection _conn; - protected PreparedStatement _stmt; - protected int _argIdx; -} diff --git a/src/java/com/samskivert/depot/impl/BuildVisitor.java b/src/java/com/samskivert/depot/impl/BuildVisitor.java index 20e94f5..e6688a4 100644 --- a/src/java/com/samskivert/depot/impl/BuildVisitor.java +++ b/src/java/com/samskivert/depot/impl/BuildVisitor.java @@ -20,14 +20,18 @@ package com.samskivert.depot.impl; +import java.nio.ByteBuffer; +import java.sql.PreparedStatement; import java.util.List; import java.util.Map; import java.util.Set; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.samskivert.util.Tuple; +import com.samskivert.depot.ByteEnum; import com.samskivert.depot.Key; import com.samskivert.depot.MultiKey; import com.samskivert.depot.PersistentRecord; @@ -67,8 +71,6 @@ import com.samskivert.depot.impl.clause.UpdateClause; /** * 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 { @@ -77,6 +79,11 @@ public abstract class BuildVisitor implements ExpressionVisitor return _builder.toString(); } + public Iterable getBindables () + { + return _bindables; + } + public void visit (FromOverride override) { _builder.append(" from " ); @@ -121,7 +128,12 @@ public abstract class BuildVisitor implements ExpressionVisitor _enableOverrides = true; appendRhsColumn(pClass, keyFields[ii]); _enableOverrides = saved; - _builder.append(values[ii] == null ? " is null " : " = ? "); + if (values[ii] == null) { + _builder.append(" is null "); + } else { + _builder.append(" = "); + bindValue(values[ii]); + } } } @@ -141,7 +153,12 @@ public abstract class BuildVisitor implements ExpressionVisitor _enableOverrides = true; appendRhsColumn(key.getPersistentClass(), entry.getKey()); _enableOverrides = saved; - _builder.append(entry.getValue() == null ? " is null " : " = ? "); + if (entry.getValue() == null) { + _builder.append(" is null "); + } else { + _builder.append(" = "); + bindValue(entry.getValue()); + } } if (!first) { _builder.append(" and "); @@ -154,7 +171,7 @@ public abstract class BuildVisitor implements ExpressionVisitor if (ii > 0) { _builder.append(", "); } - _builder.append("?"); + bindValue(values[ii]); } _builder.append(")"); } @@ -210,7 +227,7 @@ public abstract class BuildVisitor implements ExpressionVisitor if (ii > 0) { _builder.append(", "); } - _builder.append("?"); + bindValue(values[ii]); } _builder.append(")"); } @@ -304,7 +321,8 @@ public abstract class BuildVisitor implements ExpressionVisitor public void visit (Limit limit) { - _builder.append(" limit ? offset ? "); + _builder.append(" limit ").append(limit.getCount()). + append(" offset ").append(limit.getOffset()); } public void visit (LiteralExp literalExp) @@ -314,7 +332,7 @@ public abstract class BuildVisitor implements ExpressionVisitor public void visit (ValueExp valueExp) { - _builder.append("?"); + bindValue(valueExp.getValue()); } public void visit (Exists exists) @@ -446,7 +464,8 @@ public abstract class BuildVisitor implements ExpressionVisitor _builder.append(" = "); if (pojo != null) { - _builder.append("?"); + bindField(pClass, fields[ii], pojo); + } else { values[ii].accept(this); } @@ -467,6 +486,7 @@ public abstract class BuildVisitor implements ExpressionVisitor public void visit (InsertClause insertClause) { Class pClass = insertClause.getPersistentClass(); + Object pojo = insertClause.getPojo(); DepotMarshaller marsh = _types.getMarshaller(pClass); _innerClause = true; @@ -499,7 +519,7 @@ public abstract class BuildVisitor implements ExpressionVisitor _builder.append(", "); } comma = true; - _builder.append("?"); + bindField(pClass, field, pojo); } _builder.append(")"); } @@ -545,6 +565,20 @@ public abstract class BuildVisitor implements ExpressionVisitor appendIdentifier(dropIndexClause.getName()); } + protected void bindValue (Object object) + { + _bindables.add(newBindable(object)); + _builder.append("?"); + } + + protected void bindField (Class pClass, String field, Object pojo) + { + final DepotMarshaller marshaller = _types.getMarshaller(pClass); + + _bindables.add(newBindable(marshaller, field, pojo)); + _builder.append("?"); + } + protected abstract void appendIdentifier (String field); protected void appendTableName (Class type) @@ -677,6 +711,9 @@ public abstract class BuildVisitor implements ExpressionVisitor protected DepotTypes _types; + /** For each SQL parameter ? we add an {@link Comparable} to bind to this list. */ + protected List _bindables = Lists.newLinkedList(); + /** A StringBuilder to hold the constructed SQL. */ protected StringBuilder _builder = new StringBuilder(); @@ -693,4 +730,40 @@ public abstract class BuildVisitor implements ExpressionVisitor protected boolean _enableOverrides = false; protected boolean _enableAliasing = false; + + protected static interface Bindable + { + void doBind (PreparedStatement stmt, int argIx) throws Exception; + } + + protected static Bindable newBindable ( + final DepotMarshaller marshaller, final String field, final Object pojo) + { + return new Bindable() { + public void doBind (PreparedStatement stmt, int argIx) throws Exception { + marshaller.getFieldMarshaller(field).getAndWriteToStatement(stmt, argIx, pojo); + } + }; + } + + protected static Bindable newBindable (final Object value) + { + return new Bindable() { + public void doBind (PreparedStatement stmt, int argIx) throws Exception { + // TODO: how can we abstract this fieldless marshalling + if (value instanceof ByteEnum) { + // byte enums require special conversion + stmt.setByte(argIx++, ((ByteEnum)value).toByte()); + } else if (value instanceof int[]) { + // int arrays require conversion to byte arrays + int[] data = (int[])value; + ByteBuffer bbuf = ByteBuffer.allocate(data.length * 4); + bbuf.asIntBuffer().put(data); + stmt.setObject(argIx++, bbuf.array()); + } else { + stmt.setObject(argIx++, value); + } + } + }; + } } diff --git a/src/java/com/samskivert/depot/impl/HSQLBuilder.java b/src/java/com/samskivert/depot/impl/HSQLBuilder.java index 9e2cefb..a53aff0 100644 --- a/src/java/com/samskivert/depot/impl/HSQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/HSQLBuilder.java @@ -26,7 +26,6 @@ import java.sql.Blob; import java.sql.Clob; import java.sql.Connection; import java.sql.Date; -import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; @@ -116,14 +115,14 @@ public class HSQLBuilder super.visit(operator); return; } - + _builder.append(op).append("("); boolean virgin = true; for (SQLExpression bit: operator.getConditions()) { if (!virgin) { _builder.append(", "); } - _builder.append("?"); + bit.accept(this); virgin = false; } _builder.append(")"); @@ -159,18 +158,6 @@ public class HSQLBuilder } } - public class HBindVisitor extends BindVisitor - { - protected HBindVisitor (DepotTypes types, Connection conn, PreparedStatement stmt) { - super(types, conn, stmt); - } - - @Override public void visit (FullText.Match match) { - _ftsCondition.accept(this); - _ftsCondition = null; - } - } - public HSQLBuilder (DepotTypes types) { super(types); @@ -212,12 +199,6 @@ public class HSQLBuilder return new HBuildVisitor(_types); } - @Override - protected BindVisitor getBindVisitor (Connection conn, PreparedStatement stmt) - { - return new HBindVisitor(_types, conn, stmt); - } - @Override protected void maybeMutateForGeneratedValue (GeneratedValue genValue, ColumnDefinition column) { diff --git a/src/java/com/samskivert/depot/impl/MySQLBuilder.java b/src/java/com/samskivert/depot/impl/MySQLBuilder.java index a587078..153a4c6 100644 --- a/src/java/com/samskivert/depot/impl/MySQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/MySQLBuilder.java @@ -24,7 +24,6 @@ import java.sql.Blob; import java.sql.Clob; import java.sql.Connection; import java.sql.Date; -import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.sql.Time; @@ -34,7 +33,6 @@ import java.util.Set; import com.samskivert.jdbc.JDBCUtil; import com.samskivert.util.Tuple; -import com.samskivert.depot.DatabaseException; import com.samskivert.depot.PersistentRecord; import com.samskivert.depot.annotation.FullTextIndex; import com.samskivert.depot.clause.OrderBy.Order; @@ -154,33 +152,9 @@ public class MySQLBuilder } new ColumnExp(pClass, fields[ii]).accept(this); } - _builder.append(") against (? in boolean mode)"); - } - } - - public class MSBindVisitor extends BindVisitor - { - protected MSBindVisitor (DepotTypes types, Connection conn, PreparedStatement stmt) { - super(types, conn, stmt); - } - - @Override public void visit (FullText.Match match) { - bindMatch(match.getDefinition()); - } - - @Override public void visit (FullText.Rank rank) { - bindMatch(rank.getDefinition()); - } - - protected void bindMatch (FullText fullText) - { - try { - _stmt.setString(_argIdx++, fullText.getQuery()); - } catch (SQLException sqe) { - throw new DatabaseException( - "Failed to configure full-text match column [idx=" + (_argIdx-1) + - ", match=" + fullText + "]", sqe); - } + _builder.append(") against ("); + bindValue(fullText.getQuery()); + _builder.append(" in boolean mode)"); } } @@ -247,12 +221,6 @@ public class MySQLBuilder return new MSBuildVisitor(_types); } - @Override - protected BindVisitor getBindVisitor (Connection conn, PreparedStatement stmt) - { - return new MSBindVisitor(_types, conn, stmt); - } - @Override protected String getColumnType (FieldMarshaller fm, int length) { diff --git a/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java b/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java index e4aad2a..df1394f 100644 --- a/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/PostgreSQLBuilder.java @@ -3,7 +3,7 @@ // // Depot library - a Java relational persistence library // Copyright (C) 2006-2008 Michael Bayne and Pär Winzell -// +// // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or @@ -24,7 +24,6 @@ import java.sql.Blob; import java.sql.Clob; import java.sql.Connection; import java.sql.Date; -import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.sql.Time; @@ -37,7 +36,6 @@ import com.samskivert.jdbc.LiaisonRegistry; import com.samskivert.util.ArrayUtil; import com.samskivert.util.StringUtil; -import com.samskivert.depot.DatabaseException; import com.samskivert.depot.PersistentRecord; import com.samskivert.depot.annotation.FullTextIndex; import com.samskivert.depot.annotation.FullTextIndex.Configuration; @@ -59,7 +57,9 @@ public class PostgreSQLBuilder appendIdentifier("ftsCol_" + match.getDefinition().getName()); _builder.append(" @@ to_tsquery('"). append(translateFTConfig(getFTIndex(match.getDefinition()).configuration())). - append("', ?)"); + append("', "); + bindValue(massageFTQuery(match.getDefinition())); + _builder.append(")"); } @Override public void visit (FullText.Rank rank) { @@ -70,7 +70,9 @@ public class PostgreSQLBuilder // TODO: The normalization parameter is really quite important, and should // TODO: perhaps be configurable, but for the moment we hard-code it to 1: // TODO: "divides the rank by the 1 + logarithm of the document length" - append("', ?), 1)"); + append("', "); + bindValue(massageFTQuery(rank.getDefinition())); + _builder.append("), 1)"); } @Override public void visit (EpochSeconds epochSeconds) { @@ -85,6 +87,29 @@ public class PostgreSQLBuilder // _builder.append(" = any (?)"); // } +// @Override public void visit (In in) { +// Comparable[] values = in.getValues(); +// try { +// _stmt.setObject( +// _argIdx++, _conn.createArrayOf(getElementType(values), (Object[])values)); +// } catch (SQLException sqe) { +// throw new DatabaseException( +// "Failed to write value to statement [idx=" + (_argIdx-1) + +// ", values=" + StringUtil.safeToString(values) + "]", sqe); +// } +// } + +// protected String getElementType (Comparable[] values) { +// if (values instanceof Integer[]) { +// return "integer"; +// } else if (values instanceof String[]) { +// return "character varying"; +// } else { +// throw new DatabaseException( +// "Don't know how to make Postgres array for " + values.getClass()); +// } +// } + protected FullTextIndex getFTIndex (FullText definition) { DepotMarshaller marsh = _types.getMarshaller(definition.getPersistentClass()); @@ -94,76 +119,13 @@ public class PostgreSQLBuilder @Override protected void appendIdentifier (String field) { _builder.append("\"").append(field).append("\""); } - + +// TODO: enable when we can require 1.6 support protected PGBuildVisitor (DepotTypes types) { super(types); } } - public class PGBindVisitor extends BindVisitor - { - @Override public void visit (FullText.Rank rank) { - String query = massageQuery(rank.getDefinition()); - try { - _stmt.setString(_argIdx++, query); - } catch (SQLException sqe) { - throw new DatabaseException("Failed to configure full-text match column " + - "[idx=" + (_argIdx-1) + ", query=" + query + "]", sqe); - } - } - - @Override public void visit (FullText.Match match) { - String query = massageQuery(match.getDefinition()); - try { - _stmt.setString(_argIdx++, query); - } catch (SQLException sqe) { - throw new DatabaseException("Failed to configure full-text match column " + - "[idx=" + (_argIdx-1) + ", query=" + query + "]", sqe); - } - } - - protected String massageQuery (FullText match) - { - // The tsearch2 engine takes queries on the form - // (foo&bar)|goop - // so in this first simple implementation, we just take the user query, chop it into - // words by space/punctuation and 'or' those together like so: - // 'ho! who goes there?' -> 'ho|who|goes|there' - String[] searchTerms = match.getQuery().toLowerCase().split("\\W+"); - if (searchTerms.length > 0 && searchTerms[0].length() == 0) { - searchTerms = ArrayUtil.splice(searchTerms, 0, 1); - } - return StringUtil.join(searchTerms, "|"); - } - -// TODO: enable when we can require 1.6 support -// @Override public void visit (In in) { -// Comparable[] values = in.getValues(); -// try { -// _stmt.setObject( -// _argIdx++, _conn.createArrayOf(getElementType(values), (Object[])values)); -// } catch (SQLException sqe) { -// throw new DatabaseException( -// "Failed to write value to statement [idx=" + (_argIdx-1) + -// ", values=" + StringUtil.safeToString(values) + "]", sqe); -// } -// } - -// protected String getElementType (Comparable[] values) { -// if (values instanceof Integer[]) { -// return "integer"; -// } else if (values instanceof String[]) { -// return "character varying"; -// } else { -// throw new DatabaseException( -// "Don't know how to make Postgres array for " + values.getClass()); -// } -// } - - protected PGBindVisitor (DepotTypes types, Connection conn, PreparedStatement stmt) { - super(types, conn, stmt); - } - } public PostgreSQLBuilder (DepotTypes types) { @@ -214,14 +176,14 @@ public class PostgreSQLBuilder initColumn.append(")"); String triggerFun = PG83 ? "tsvector_update_trigger" : "tsearch2"; - + // build the CREATE TRIGGER StringBuilder createTrigger = new StringBuilder("CREATE TRIGGER "). append(liaison.columnSQL(trigger)).append(" BEFORE UPDATE OR INSERT ON "). append(liaison.tableSQL(table)). append(" FOR EACH ROW EXECUTE PROCEDURE ").append(triggerFun).append("("). append(liaison.columnSQL(column)).append(", "); - + if (PG83) { createTrigger.append("'"). append(translateFTConfig(fts.configuration())). @@ -271,12 +233,6 @@ public class PostgreSQLBuilder return new PGBuildVisitor(_types); } - @Override - protected BindVisitor getBindVisitor (Connection conn, PreparedStatement stmt) - { - return new PGBindVisitor(_types, conn, stmt); - } - @Override protected String getColumnType (FieldMarshaller fm, int length) { @@ -337,7 +293,22 @@ public class PostgreSQLBuilder throw new IllegalArgumentException("Unknown field marshaller type: " + fm.getClass()); } } - + + protected static String massageFTQuery (FullText match) + { + // The tsearch2 engine takes queries on the form + // (foo&bar)|goop + // so in this first simple implementation, we just take the user query, chop it into + // words by space/punctuation and 'or' those together like so: + // 'ho! who goes there?' -> 'ho|who|goes|there' + String[] searchTerms = match.getQuery().toLowerCase().split("\\W+"); + if (searchTerms.length > 0 && searchTerms[0].length() == 0) { + searchTerms = ArrayUtil.splice(searchTerms, 0, 1); + } + return StringUtil.join(searchTerms, "|"); + } + + // Translate the mildly abstracted full-text parser/dictionary configuration support // in FullText to actual PostgreSQL configuration identifiers. protected static String translateFTConfig (Configuration configuration) diff --git a/src/java/com/samskivert/depot/impl/SQLBuilder.java b/src/java/com/samskivert/depot/impl/SQLBuilder.java index 9beeda3..ae097af 100644 --- a/src/java/com/samskivert/depot/impl/SQLBuilder.java +++ b/src/java/com/samskivert/depot/impl/SQLBuilder.java @@ -39,10 +39,8 @@ import com.samskivert.depot.clause.QueryClause; import static com.samskivert.depot.Log.log; /** - * 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. + * At the heart of Depot's SQL generation, this object constructs an {@link ExpressionVisitor} + * object and executes it, constructing SQL and parameter bindings as it recurses. */ public abstract class SQLBuilder { @@ -81,8 +79,16 @@ public abstract class SQLBuilder } PreparedStatement stmt = conn.prepareStatement(_buildVisitor.getQuery()); - _bindVisitor = getBindVisitor(conn, stmt); - _clause.accept(_bindVisitor); + + int argIx = 1; + for (BuildVisitor.Bindable bindable : _buildVisitor.getBindables()) { + try { + bindable.doBind(stmt, argIx); + } catch (Exception e) { + log.warning("Failed to bind statement argument", "argIx", argIx); + } + argIx ++; + } if (PersistenceContext.DEBUG) { log.info("SQL: " + stmt.toString()); @@ -213,11 +219,6 @@ public abstract class SQLBuilder */ protected abstract BuildVisitor getBuildVisitor (); - /** - * Overridden by subclasses to create a dialect-specific {@link BindVisitor}. - */ - protected abstract BindVisitor getBindVisitor (Connection conn, PreparedStatement stmt); - /** * Overridden by subclasses to figure the dialect-specific SQL type of the given field. * @param length @@ -229,5 +230,4 @@ public abstract class SQLBuilder protected QueryClause _clause; protected BuildVisitor _buildVisitor; - protected BindVisitor _bindVisitor; }