You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2014/11/02 16:36:53 UTC

[2/6] CAY-1966 SQLTemplate/SQLSelect positional parameter binding

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/ResultDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/ResultDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/ResultDirective.java
new file mode 100644
index 0000000..8a3a5e4
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/ResultDirective.java
@@ -0,0 +1,206 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.velocity;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.directive.Directive;
+import org.apache.velocity.runtime.parser.node.Node;
+import org.apache.cayenne.access.jdbc.ColumnDescriptor;
+import org.apache.cayenne.util.Util;
+
+/**
+ * A custom Velocity directive to describe a ResultSet column. There are the following
+ * possible invocation formats inside the template:
+ * 
+ * <pre>
+ *       #result(column_name) - e.g. #result('ARTIST_ID')
+ *       #result(column_name java_type) - e.g. #result('ARTIST_ID' 'String')
+ *       #result(column_name java_type column_alias) - e.g. #result('ARTIST_ID' 'String' 'ID')
+ *       #result(column_name java_type column_alias data_row_key) - e.g. #result('ARTIST_ID' 'String' 'ID' 'toArtist.ID')
+ * </pre>
+ * 
+ * <p>
+ * 'data_row_key' is needed if SQL 'column_alias' is not appropriate as a DataRow key on
+ * the Cayenne side. One common case when this happens is when a DataRow retrieved from a
+ * query is mapped using joint prefetch keys. In this case DataRow must use DB_PATH
+ * expressions for joint column keys, and their format is incompatible with most databases
+ * alias format.
+ * </p>
+ * <p>
+ * Most common Java types used in JDBC can be specified without a package. This includes
+ * all numeric types, primitives, String, SQL dates, BigDecimal and BigInteger.
+ * </p>
+ * 
+ * @since 1.1
+ */
+public class ResultDirective extends Directive {
+
+    private static final Map<String, String> typesGuess;
+
+    static {
+        // init default types
+        typesGuess = new HashMap<String, String>();
+
+        // primitives
+        typesGuess.put("long", Long.class.getName());
+        typesGuess.put("double", Double.class.getName());
+        typesGuess.put("byte", Byte.class.getName());
+        typesGuess.put("boolean", Boolean.class.getName());
+        typesGuess.put("float", Float.class.getName());
+        typesGuess.put("short", Short.class.getName());
+        typesGuess.put("int", Integer.class.getName());
+
+        // numeric
+        typesGuess.put("Long", Long.class.getName());
+        typesGuess.put("Double", Double.class.getName());
+        typesGuess.put("Byte", Byte.class.getName());
+        typesGuess.put("Boolean", Boolean.class.getName());
+        typesGuess.put("Float", Float.class.getName());
+        typesGuess.put("Short", Short.class.getName());
+        typesGuess.put("Integer", Integer.class.getName());
+
+        // other
+        typesGuess.put("String", String.class.getName());
+        typesGuess.put("Date", Date.class.getName());
+        typesGuess.put("Time", Time.class.getName());
+        typesGuess.put("Timestamp", Timestamp.class.getName());
+        typesGuess.put("BigDecimal", BigDecimal.class.getName());
+        typesGuess.put("BigInteger", BigInteger.class.getName());
+    }
+
+    @Override
+    public String getName() {
+        return "result";
+    }
+
+    @Override
+    public int getType() {
+        return LINE;
+    }
+
+    @Override
+    public boolean render(InternalContextAdapter context, Writer writer, Node node)
+            throws IOException, ResourceNotFoundException, ParseErrorException,
+            MethodInvocationException {
+
+        String column = getChildAsString(context, node, 0);
+        if (column == null) {
+            throw new ParseErrorException("Column name expected at line "
+                    + node.getLine()
+                    + ", column "
+                    + node.getColumn());
+        }
+
+        String alias = getChildAsString(context, node, 2);
+        String dataRowKey = getChildAsString(context, node, 3);
+
+        // determine what we want to name this column in a resulting DataRow...
+        String label = (!Util.isEmptyString(dataRowKey)) ? dataRowKey : (!Util
+                .isEmptyString(alias)) ? alias : null;
+
+        ColumnDescriptor columnDescriptor = new ColumnDescriptor();
+        columnDescriptor.setName(column);
+        columnDescriptor.setDataRowKey(label);
+
+        String type = getChildAsString(context, node, 1);
+        if (type != null) {
+            columnDescriptor.setJavaClass(guessType(type));
+        }
+
+        // TODO: andrus 6/27/2007 - this is an unofficial jdbcType parameter that is added
+        // temporarily pending CAY-813 implementation for the sake of EJBQL query...
+        Object jdbcType = getChild(context, node, 4);
+        if (jdbcType instanceof Number) {
+            columnDescriptor.setJdbcType(((Number) jdbcType).intValue());
+        }
+
+        writer.write(column);
+
+        // append column alias if needed.
+
+        // Note that if table aliases are used, this logic will result in SQL like
+        // "t0.ARTIST_NAME AS ARTIST_NAME". Doing extra regex matching to handle this
+        // won't probably buy us much.
+        if (!Util.isEmptyString(alias) && !alias.equals(column)) {
+            writer.write(" AS ");
+            writer.write(alias);
+        }
+
+        bindResult(context, columnDescriptor);
+        return true;
+    }
+
+    protected Object getChild(InternalContextAdapter context, Node node, int i)
+            throws MethodInvocationException {
+        return (i >= 0 && i < node.jjtGetNumChildren()) ? node.jjtGetChild(i).value(
+                context) : null;
+    }
+
+    /**
+     * Returns a directive argument at a given index converted to String.
+     * 
+     * @since 1.2
+     */
+    protected String getChildAsString(InternalContextAdapter context, Node node, int i)
+            throws MethodInvocationException {
+        Object value = getChild(context, node, i);
+        return (value != null) ? value.toString() : null;
+    }
+
+    /**
+     * Converts "short" type notation to the fully qualified class name. Right now
+     * supports all major standard SQL types, including primitives. All other types are
+     * expected to be fully qualified, and are not converted.
+     */
+    protected String guessType(String type) {
+        String guessed = typesGuess.get(type);
+        return guessed != null ? guessed : type;
+    }
+
+    /**
+     * Adds value to the list of result columns in the context.
+     */
+    protected void bindResult(
+            InternalContextAdapter context,
+            ColumnDescriptor columnDescriptor) {
+
+        Collection<Object> resultColumns = (Collection<Object>) context
+                .getInternalUserContext()
+                .get(SQLTemplateProcessor.RESULT_COLUMNS_LIST_KEY);
+
+        if (resultColumns != null) {
+            resultColumns.add(columnDescriptor);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateProcessor.java
new file mode 100644
index 0000000..71974d3
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateProcessor.java
@@ -0,0 +1,155 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.velocity;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.jdbc.ColumnDescriptor;
+import org.apache.cayenne.access.jdbc.ParameterBinding;
+import org.apache.cayenne.access.jdbc.SQLStatement;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.context.InternalContextAdapterImpl;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeInstance;
+import org.apache.velocity.runtime.log.NullLogChute;
+import org.apache.velocity.runtime.parser.ParseException;
+import org.apache.velocity.runtime.parser.node.SimpleNode;
+
+/**
+ * Processor for SQL velocity templates.
+ * 
+ * @see org.apache.cayenne.query.SQLTemplate
+ * @since 4.0
+ */
+public class SQLTemplateProcessor {
+
+	private static RuntimeInstance sharedRuntime;
+
+	static final String BINDINGS_LIST_KEY = "bindings";
+	static final String RESULT_COLUMNS_LIST_KEY = "resultColumns";
+	static final String HELPER_KEY = "helper";
+
+	private static final SQLTemplateRenderingUtils sharedUtils = new SQLTemplateRenderingUtils();
+
+	RuntimeInstance velocityRuntime;
+	SQLTemplateRenderingUtils renderingUtils;
+
+	static {
+		initVelocityRuntime();
+	}
+
+	private static void initVelocityRuntime() {
+		// init static velocity engine
+		sharedRuntime = new RuntimeInstance();
+
+		// set null logger
+		sharedRuntime.addProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new NullLogChute());
+
+		sharedRuntime.addProperty(RuntimeConstants.RESOURCE_MANAGER_CLASS, SQLTemplateResourceManager.class.getName());
+		sharedRuntime.addProperty("userdirective", BindDirective.class.getName());
+		sharedRuntime.addProperty("userdirective", BindEqualDirective.class.getName());
+		sharedRuntime.addProperty("userdirective", BindNotEqualDirective.class.getName());
+		sharedRuntime.addProperty("userdirective", BindObjectEqualDirective.class.getName());
+		sharedRuntime.addProperty("userdirective", BindObjectNotEqualDirective.class.getName());
+		sharedRuntime.addProperty("userdirective", ResultDirective.class.getName());
+		sharedRuntime.addProperty("userdirective", ChainDirective.class.getName());
+		sharedRuntime.addProperty("userdirective", ChunkDirective.class.getName());
+		try {
+			sharedRuntime.init();
+		} catch (Exception ex) {
+			throw new CayenneRuntimeException("Error setting up Velocity RuntimeInstance.", ex);
+		}
+	}
+
+	public SQLTemplateProcessor() {
+		this.velocityRuntime = sharedRuntime;
+		this.renderingUtils = sharedUtils;
+	}
+
+	public SQLTemplateProcessor(RuntimeInstance velocityRuntime, SQLTemplateRenderingUtils renderingUtils) {
+		this.velocityRuntime = velocityRuntime;
+		this.renderingUtils = renderingUtils;
+	}
+
+	/**
+	 * Builds and returns a SQLStatement based on SQL template and a set of
+	 * parameters. During rendering, VelocityContext exposes the following as
+	 * variables: all parameters in the map, {@link SQLTemplateRenderingUtils}
+	 * as a "helper" variable and SQLStatement object as "statement" variable.
+	 */
+	public SQLStatement processTemplate(String template, Map<String, ?> parameters) throws Exception {
+		// have to make a copy of parameter map since we are gonna modify it..
+		Map<String, Object> internalParameters = (parameters != null && !parameters.isEmpty()) ? new HashMap<String, Object>(
+				parameters) : new HashMap<String, Object>(5);
+
+		List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
+		List<ColumnDescriptor> results = new ArrayList<ColumnDescriptor>();
+		internalParameters.put(BINDINGS_LIST_KEY, bindings);
+		internalParameters.put(RESULT_COLUMNS_LIST_KEY, results);
+		internalParameters.put(HELPER_KEY, renderingUtils);
+
+		String sql = buildStatement(new VelocityContext(internalParameters), template);
+
+		ParameterBinding[] bindingsArray = new ParameterBinding[bindings.size()];
+		bindings.toArray(bindingsArray);
+
+		ColumnDescriptor[] resultsArray = new ColumnDescriptor[results.size()];
+		results.toArray(resultsArray);
+
+		return new SQLStatement(sql, resultsArray, bindingsArray);
+	}
+
+	String buildStatement(VelocityContext context, String template) throws Exception {
+		// Note: this method is a reworked version of
+		// org.apache.velocity.app.Velocity.evaluate(..)
+		// cleaned up to avoid using any Velocity singletons
+
+		StringWriter out = new StringWriter(template.length());
+		SimpleNode nodeTree = null;
+
+		try {
+			nodeTree = velocityRuntime.parse(new StringReader(template), template);
+		} catch (ParseException pex) {
+			throw new CayenneRuntimeException("Error parsing template '" + template + "' : " + pex.getMessage());
+		}
+
+		if (nodeTree == null) {
+			throw new CayenneRuntimeException("Error parsing template " + template);
+		}
+
+		// ... not sure what InternalContextAdapter is for...
+		InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
+		ica.pushCurrentTemplateName(template);
+
+		try {
+			nodeTree.init(ica, velocityRuntime);
+			nodeTree.render(ica, out);
+			return out.toString();
+		} finally {
+			ica.popCurrentTemplateName();
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateRenderingUtils.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateRenderingUtils.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateRenderingUtils.java
new file mode 100644
index 0000000..14b8646
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateRenderingUtils.java
@@ -0,0 +1,37 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.velocity;
+
+import org.apache.cayenne.exp.ExpressionFactory;
+
+/**
+ * Implements utility methods used inside Velocity templates when rendering
+ * SQLTemplates.
+ * 
+ * @since 1.1
+ */
+public class SQLTemplateRenderingUtils {
+	/**
+	 * Returns the result of evaluation of expression with object.
+	 */
+	public Object cayenneExp(Object object, String expression) {
+		return ExpressionFactory.exp(expression).evaluate(object);
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateResourceManager.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateResourceManager.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateResourceManager.java
new file mode 100644
index 0000000..e80cd46
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/SQLTemplateResourceManager.java
@@ -0,0 +1,106 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.velocity;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Map;
+
+import org.apache.commons.collections.ExtendedProperties;
+import org.apache.commons.collections.map.LRUMap;
+import org.apache.velocity.Template;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.runtime.resource.Resource;
+import org.apache.velocity.runtime.resource.ResourceManager;
+import org.apache.velocity.runtime.resource.loader.ResourceLoader;
+
+/**
+ * An implementation of the Velocity ResourceManager and ResourceLoader that
+ * creates templates from in-memory Strings.
+ * 
+ * @since 1.1
+ */
+// class must be public since it is instantiated by Velocity via reflection.
+public class SQLTemplateResourceManager
+    extends ResourceLoader
+    implements ResourceManager {
+
+    protected Map<String, Template> templateCache;
+
+    public void initialize(RuntimeServices rs) throws Exception {
+        super.rsvc = rs;
+        this.templateCache = new LRUMap(100);
+    }
+
+    public void clearCache() {
+        templateCache.clear();
+    }
+
+    /**
+     * Returns a Velocity Resource which is a Template for the given SQL.
+     */
+    public Resource getResource(String resourceName, int resourceType, String encoding)
+        throws ResourceNotFoundException, ParseErrorException, Exception {
+
+        synchronized (templateCache) {
+            Template resource = templateCache.get(resourceName);
+
+            if (resource == null) {
+                resource = new Template();
+                resource.setRuntimeServices(rsvc);
+                resource.setResourceLoader(this);
+                resource.setName(resourceName);
+                resource.setEncoding(encoding);
+                resource.process();
+
+                templateCache.put(resourceName, resource);
+            }
+
+            return resource;
+        }
+    }
+
+    public String getLoaderNameForResource(String resourceName) {
+        return getClass().getName();
+    }
+
+    @Override
+    public long getLastModified(Resource resource) {
+        return -1;
+    }
+
+    @Override
+    public InputStream getResourceStream(String source)
+        throws ResourceNotFoundException {
+        return new ByteArrayInputStream(source.getBytes());
+    }
+
+    @Override
+    public void init(ExtendedProperties configuration) {
+
+    }
+
+    @Override
+    public boolean isSourceModified(Resource resource) {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/BindDirectiveIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/BindDirectiveIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/BindDirectiveIT.java
deleted file mode 100644
index e9beaa8..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/BindDirectiveIT.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.jdbc;
-
-import org.apache.cayenne.DataRow;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.MockOperationObserver;
-import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
-import org.apache.cayenne.dba.JdbcAdapter;
-import org.apache.cayenne.dba.oracle.OracleAdapter;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.log.JdbcEventLogger;
-import org.apache.cayenne.query.CapsStrategy;
-import org.apache.cayenne.query.SQLTemplate;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.test.jdbc.DBHelper;
-import org.apache.cayenne.testdo.testmap.Artist;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-
-import java.sql.Connection;
-import java.sql.Timestamp;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static org.mockito.Mockito.mock;
-
-/**
- * Tests BindDirective for passed null parameters and for not passed parameters
- */
-@UseServerRuntime(ServerCase.TESTMAP_PROJECT)
-public class BindDirectiveIT extends ServerCase {
-
-    @Inject
-    private ServerCaseDataSourceFactory dataSourceFactory;
-
-    @Inject
-    private JdbcAdapter adapter;
-
-    @Inject
-    private ObjectContext context;
-
-    @Inject
-    private DBHelper dbHelper;
-
-    @Inject
-    private JdbcEventLogger logger;
-
-    @Override
-    protected void setUpAfterInjection() throws Exception {
-        dbHelper.deleteAll("PAINTING_INFO");
-        dbHelper.deleteAll("PAINTING");
-        dbHelper.deleteAll("ARTIST_EXHIBIT");
-        dbHelper.deleteAll("ARTIST_GROUP");
-        dbHelper.deleteAll("ARTIST");
-    }
-
-    public void testBindTimestamp() throws Exception {
-        Map<String, Object> parameters = new HashMap<String, Object>();
-        parameters.put("id", new Integer(1));
-        parameters.put("name", "ArtistWithDOB");
-        Calendar cal = Calendar.getInstance();
-        cal.clear();
-        cal.set(2010, 2, 8);
-        parameters.put("dob", new Timestamp(cal.getTime().getTime()));
-
-        // without JDBC usage
-        Map<String, ?> row = performInsertForParameters(parameters, false, 1);
-        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
-        assertEquals(cal.getTime(), row.get("DATE_OF_BIRTH"));
-        assertNotNull(row.get("DATE_OF_BIRTH"));
-        assertEquals(Date.class, row.get("DATE_OF_BIRTH").getClass());
-    }
-
-    public void testBindSQLDate() throws Exception {
-        Map<String, Object> parameters = new HashMap<String, Object>();
-        parameters.put("id", new Integer(1));
-        parameters.put("name", "ArtistWithDOB");
-        Calendar cal = Calendar.getInstance();
-        cal.clear();
-        cal.set(2010, 2, 8);
-        parameters.put("dob", new java.sql.Date(cal.getTime().getTime()));
-
-        // without JDBC usage
-        Map<String, ?> row = performInsertForParameters(parameters, false, 1);
-        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
-        assertEquals(parameters.get("dob"), row.get("DATE_OF_BIRTH"));
-        assertNotNull(row.get("DATE_OF_BIRTH"));
-        assertEquals(Date.class, row.get("DATE_OF_BIRTH").getClass());
-    }
-
-    public void testBindUtilDate() throws Exception {
-        Map<String, Object> parameters = new HashMap<String, Object>();
-        parameters.put("id", new Integer(1));
-        parameters.put("name", "ArtistWithDOB");
-        Calendar cal = Calendar.getInstance();
-        cal.clear();
-        cal.set(2010, 2, 8);
-        parameters.put("dob", cal.getTime());
-
-        // without JDBC usage
-        Map<String, ?> row = performInsertForParameters(parameters, false, 1);
-        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
-        assertEquals(parameters.get("dob"), row.get("DATE_OF_BIRTH"));
-        assertNotNull(row.get("DATE_OF_BIRTH"));
-        assertEquals(Date.class, row.get("DATE_OF_BIRTH").getClass());
-    }
-
-    public void testBindingForCollection() throws Exception {
-
-        // insert 3 artists
-        for (int i = 1; i < 4; i++) {
-            Map<String, Object> parameters = new HashMap<String, Object>();
-            parameters.put("id", new Long(i));
-            parameters.put("name", "Artist" + i);
-            performInsertForParameters(parameters, true, i);
-        }
-
-        // now select only with names: Artist1 and Artist3
-        Set<String> artistNames = new HashSet<String>();
-        artistNames.add("Artist1");
-        artistNames.add("Artist3");
-        String sql = "SELECT * FROM ARTIST WHERE ARTIST_NAME in (#bind($ARTISTNAMES))";
-        SQLTemplate query = new SQLTemplate(Artist.class, sql);
-        
-        // customize for DB's that require trimming CHAR spaces 
-        query.setTemplate(OracleAdapter.class.getName(), "SELECT * FROM ARTIST WHERE RTRIM(ARTIST_NAME) in (#bind($ARTISTNAMES))");
-        
-        query.setColumnNamesCapitalization(CapsStrategy.UPPER);
-        query.setParameters(Collections.singletonMap("ARTISTNAMES", artistNames));
-        List<DataRow> result = context.performQuery(query);
-        assertEquals(2, result.size());
-    }
-
-    public void testBindForPassedNullParam() throws Exception {
-        Map<String, Object> parameters = new HashMap<String, Object>();
-        parameters.put("id", new Long(1));
-        parameters.put("name", "ArtistWithoutDOB");
-        // passing null in parameter
-        parameters.put("dob", null);
-
-        // without JDBC usage
-        Map<String, ?> row = performInsertForParameters(parameters, false, 1);
-        assertEquals(parameters.get("id"), row.get("ARTIST_ID"));
-        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
-        assertEquals(parameters.get("dob"), row.get("DATE_OF_BIRTH"));
-        assertNull(row.get("DATE_OF_BIRTH"));
-    }
-
-    public void testBindWithJDBCForPassedNullParam() throws Exception {
-        Map<String, Object> parameters = new HashMap<String, Object>();
-        parameters.put("id", new Long(1));
-        parameters.put("name", "ArtistWithoutDOB");
-        // passing null in parameter
-        parameters.put("dob", null);
-
-        // use JDBC
-        Map<String, ?> row = performInsertForParameters(parameters, true, 1);
-        assertEquals(parameters.get("id"), row.get("ARTIST_ID"));
-        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
-        assertEquals(parameters.get("dob"), row.get("DATE_OF_BIRTH"));
-        assertNull(row.get("DATE_OF_BIRTH"));
-    }
-
-    public void testBindForNotPassedParam() throws Exception {
-        Map<String, Object> parameters = new HashMap<String, Object>();
-        parameters.put("id", new Long(1));
-        parameters.put("name", "ArtistWithoutDOB");
-        // not passing parameter parameters.put("dob", not passed!);
-
-        // without JDBC usage
-        Map<String, ?> row = performInsertForParameters(parameters, false, 1);
-        assertEquals(parameters.get("id"), row.get("ARTIST_ID"));
-        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
-        // parameter should be passed as null
-        assertNull(row.get("DATE_OF_BIRTH"));
-    }
-
-    public void testBindWithJDBCForNotPassedParam() throws Exception {
-        Map<String, Object> parameters = new HashMap<String, Object>();
-        parameters.put("id", new Long(1));
-        parameters.put("name", "ArtistWithoutDOB");
-        // not passing parameter parameters.put("dob", not passed!);
-
-        // use JDBC
-        Map<String, ?> row = performInsertForParameters(parameters, true, 1);
-        assertEquals(parameters.get("id"), row.get("ARTIST_ID"));
-        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
-        // parameter should be passed as null
-        assertNull(row.get("DATE_OF_BIRTH"));
-    }
-
-    /**
-     * Inserts row for given parameters
-     * 
-     * @return inserted row
-     */
-    private Map<String, ?> performInsertForParameters(
-            Map<String, Object> parameters,
-            boolean useJDBCType,
-            int expectedRowCount) throws Exception {
-
-        String templateString;
-        if (useJDBCType) {
-            templateString = "INSERT INTO ARTIST (ARTIST_ID, ARTIST_NAME, DATE_OF_BIRTH) "
-                    + "VALUES (#bind($id), #bind($name), #bind($dob 'DATE'))";
-        }
-        else {
-            templateString = "INSERT INTO ARTIST (ARTIST_ID, ARTIST_NAME, DATE_OF_BIRTH) "
-                    + "VALUES (#bind($id), #bind($name), #bind($dob))";
-        }
-        SQLTemplate template = new SQLTemplate(Object.class, templateString);
-
-        template.setParameters(parameters);
-
-        DataNode node = new DataNode();
-        node.setEntityResolver(context.getEntityResolver());
-        node.setRowReaderFactory(mock(RowReaderFactory.class));
-        node.setAdapter(adapter);
-        SQLTemplateAction action = new SQLTemplateAction(template, node);
-
-        Connection c = dataSourceFactory.getSharedDataSource().getConnection();
-        try {
-            MockOperationObserver observer = new MockOperationObserver();
-            action.performAction(c, observer);
-
-            int[] batches = observer.countsForQuery(template);
-            assertNotNull(batches);
-            assertEquals(1, batches.length);
-            assertEquals(1, batches[0]);
-        }
-        finally {
-            c.close();
-        }
-
-        SelectQuery query = new SelectQuery(Artist.class);
-        query.setFetchingDataRows(true);
-
-        List<DataRow> data = context.performQuery(query);
-        assertEquals(expectedRowCount, data.size());
-        return data.get(0);
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/MockupRuntimeServices.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/MockupRuntimeServices.java b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/MockupRuntimeServices.java
deleted file mode 100644
index b272dfa..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/MockupRuntimeServices.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import java.io.Reader;
-
-import org.apache.velocity.runtime.RuntimeInstance;
-import org.apache.velocity.runtime.parser.ParseException;
-import org.apache.velocity.runtime.parser.node.SimpleNode;
-
-/**
- */
-class MockupRuntimeServices extends RuntimeInstance {
-
-    @Override
-    public SimpleNode parse(Reader reader, String templateName, boolean dumpNamespace)
-        throws ParseException {
-        return new SimpleNode(1);
-    }
-
-    @Override
-    public SimpleNode parse(Reader reader, String templateName) throws ParseException {
-        return new SimpleNode(1);
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/ResultDirectiveIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/ResultDirectiveIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/ResultDirectiveIT.java
deleted file mode 100644
index 7b9bce4..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/ResultDirectiveIT.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.jdbc;
-
-import org.apache.cayenne.DataRow;
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.MockOperationObserver;
-import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
-import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.apache.cayenne.dba.JdbcAdapter;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.query.CapsStrategy;
-import org.apache.cayenne.query.SQLTemplate;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.test.jdbc.DBHelper;
-import org.apache.cayenne.testdo.testmap.Artist;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-
-import java.sql.Connection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static org.mockito.Mockito.mock;
-
-/**
- * Test for Result directive to check if we could use ResultDitrective optionally.
- */
-@UseServerRuntime(ServerCase.TESTMAP_PROJECT)
-public class ResultDirectiveIT extends ServerCase {
-
-    @Inject
-    private ServerRuntime runtime;
-
-    @Inject
-    private DBHelper dbHelper;
-
-    @Inject
-    private JdbcAdapter dbAdapter;
-
-    @Override
-    protected void setUpAfterInjection() throws Exception {
-        dbHelper.deleteAll("PAINTING_INFO");
-        dbHelper.deleteAll("PAINTING");
-        dbHelper.deleteAll("PAINTING1");
-        dbHelper.deleteAll("ARTIST_EXHIBIT");
-        dbHelper.deleteAll("ARTIST_GROUP");
-        dbHelper.deleteAll("ARTIST");
-        dbHelper.deleteAll("EXHIBIT");
-        dbHelper.deleteAll("GALLERY");
-    }
-
-    public void testWithoutResultDirective() throws Exception {
-        String sql = "SELECT ARTIST_ID, ARTIST_NAME FROM ARTIST";
-        Map<String, Object> artist = insertArtist();
-        Map<String, Object> selectResult = selectForQuery(sql);
-
-        assertEquals(artist.get("ARTIST_ID"), selectResult.get("ARTIST_ID"));
-        assertEquals(artist.get("ARTIST_NAME"), selectResult.get("ARTIST_NAME"));
-    }
-
-    public void testWithOnlyResultDirective() throws Exception {
-        String sql = "SELECT #result('ARTIST_ID' 'java.lang.Integer'),"
-                + " #result('ARTIST_NAME' 'java.lang.String')"
-                + " FROM ARTIST";
-        Map<String, Object> artist = insertArtist();
-        Map<String, Object> selectResult = selectForQuery(sql);
-
-        assertEquals(artist.get("ARTIST_ID"), selectResult.get("ARTIST_ID"));
-        assertEquals(artist.get("ARTIST_NAME"), selectResult
-                .get("ARTIST_NAME")
-                .toString()
-                .trim());
-    }
-
-    public void testWithMixedDirectiveUse1() throws Exception {
-        String sql = "SELECT ARTIST_ID,"
-                + " #result('ARTIST_NAME' 'java.lang.String')"
-                + " FROM ARTIST";
-        Map<String, Object> artist = insertArtist();
-        Map<String, Object> selectResult = selectForQuery(sql);
-
-        assertEquals(artist.get("ARTIST_ID"), selectResult.get("ARTIST_ID"));
-        assertEquals(artist.get("ARTIST_NAME"), selectResult
-                .get("ARTIST_NAME")
-                .toString()
-                .trim());
-    }
-
-    public void testWithMixedDirectiveUse2() throws Exception {
-        String sql = "SELECT #result('ARTIST_ID' 'java.lang.Integer'),"
-                + " ARTIST_NAME "
-                + " FROM ARTIST";
-        Map<String, Object> artist = insertArtist();
-        Map<String, Object> selectResult = selectForQuery(sql);
-
-        assertEquals(artist.get("ARTIST_ID"), selectResult.get("ARTIST_ID"));
-        assertEquals(artist.get("ARTIST_NAME"), selectResult.get("ARTIST_NAME"));
-    }
-
-    private Map<String, Object> selectForQuery(String sql) {
-        SQLTemplate template = new SQLTemplate(Artist.class, sql);
-        template.setColumnNamesCapitalization(CapsStrategy.UPPER);
-        MockOperationObserver observer = new MockOperationObserver();
-        runtime.getDataDomain().performQueries(
-                Collections.singletonList(template),
-                observer);
-
-        List<Map<String, Object>> data = observer.rowsForQuery(template);
-        assertEquals(1, data.size());
-        Map<String, Object> row = data.get(0);
-        return row;
-    }
-
-    /**
-     * Inserts one Artist
-     * 
-     * @return Inserted Artist as a DataRow
-     */
-    private Map<String, Object> insertArtist() throws Exception {
-        Map<String, Object> parameters = new HashMap<String, Object>();
-        parameters.put("id", new Integer(1));
-        parameters.put("name", "ArtistToTestResult");
-        String templateString = "INSERT INTO ARTIST (ARTIST_ID, ARTIST_NAME, DATE_OF_BIRTH) "
-                + "VALUES (#bind($id), #bind($name), #bind($dob))";
-
-        SQLTemplate template = new SQLTemplate(Object.class, templateString);
-
-        template.setParameters(parameters);
-
-        DataNode node = new DataNode();
-        node.setEntityResolver(runtime.getDataDomain().getEntityResolver());
-        node.setRowReaderFactory(mock(RowReaderFactory.class));
-        node.setAdapter(dbAdapter);
-        
-        SQLTemplateAction action = new SQLTemplateAction(template, node);
-
-        Connection c = runtime
-                .getDataDomain()
-                .getDataNodes()
-                .iterator()
-                .next()
-                .getDataSource()
-                .getConnection();
-        try {
-            MockOperationObserver observer = new MockOperationObserver();
-            action.performAction(c, observer);
-
-            int[] batches = observer.countsForQuery(template);
-            assertNotNull(batches);
-            assertEquals(1, batches.length);
-            assertEquals(1, batches[0]);
-        }
-        finally {
-            c.close();
-        }
-
-        MockOperationObserver observer = new MockOperationObserver();
-        SelectQuery query = new SelectQuery(Artist.class);
-        runtime
-                .getDataDomain()
-                .performQueries(Collections.singletonList(query), observer);
-
-        List<?> data = observer.rowsForQuery(query);
-        assertEquals(1, data.size());
-        DataRow row = (DataRow) data.get(0);
-        return row;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorChainTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorChainTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorChainTest.java
deleted file mode 100644
index 69e5bf4..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorChainTest.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import org.junit.Test;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
-
-public class SQLTemplateProcessorChainTest {
-
-    @Test
-    public void testProcessTemplateNoChunks() throws Exception {
-        // whatever is inside the chain, it should render as empty if there
-        // is no chunks...
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                "#chain(' AND ') #end",
-                Collections.EMPTY_MAP);
-
-        assertEquals("", compiled.getSql());
-
-        compiled = new SQLTemplateProcessor().processTemplate(
-                "#chain(' AND ') garbage #end",
-                Collections.EMPTY_MAP);
-
-        assertEquals("", compiled.getSql());
-
-        compiled = new SQLTemplateProcessor().processTemplate(
-                "#chain(' AND ' 'PREFIX') #end",
-                Collections.EMPTY_MAP);
-
-        assertEquals("", compiled.getSql());
-        compiled = new SQLTemplateProcessor().processTemplate(
-                "#chain(' AND ' 'PREFIX') garbage #end",
-                Collections.EMPTY_MAP);
-
-        assertEquals("", compiled.getSql());
-    }
-
-    @Test
-    public void testProcessTemplateFullChain() throws Exception {
-        String template = "#chain(' OR ')"
-                + "#chunk($a)$a#end"
-                + "#chunk($b)$b#end"
-                + "#chunk($c)$c#end"
-                + "#end";
-
-        Map map = new HashMap();
-        map.put("a", "[A]");
-        map.put("b", "[B]");
-        map.put("c", "[C]");
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(template, map);
-        assertEquals("[A] OR [B] OR [C]", compiled.getSql());
-    }
-
-    @Test
-    public void testProcessTemplateFullChainAndPrefix() throws Exception {
-        String template = "#chain(' OR ' 'WHERE ')"
-                + "#chunk($a)$a#end"
-                + "#chunk($b)$b#end"
-                + "#chunk($c)$c#end"
-                + "#end";
-
-        Map map = new HashMap();
-        map.put("a", "[A]");
-        map.put("b", "[B]");
-        map.put("c", "[C]");
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(template, map);
-        assertEquals("WHERE [A] OR [B] OR [C]", compiled.getSql());
-    }
-
-    @Test
-    public void testProcessTemplatePartialChainMiddle() throws Exception {
-        String template = "#chain(' OR ' 'WHERE ')"
-                + "#chunk($a)$a#end"
-                + "#chunk($b)$b#end"
-                + "#chunk($c)$c#end"
-                + "#end";
-
-        Map map = new HashMap();
-        map.put("a", "[A]");
-        map.put("c", "[C]");
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(template, map);
-        assertEquals("WHERE [A] OR [C]", compiled.getSql());
-    }
-
-    @Test
-    public void testProcessTemplatePartialChainStart() throws Exception {
-        String template = "#chain(' OR ' 'WHERE ')"
-                + "#chunk($a)$a#end"
-                + "#chunk($b)$b#end"
-                + "#chunk($c)$c#end"
-                + "#end";
-
-        Map map = new HashMap();
-        map.put("b", "[B]");
-        map.put("c", "[C]");
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(template, map);
-        assertEquals("WHERE [B] OR [C]", compiled.getSql());
-    }
-
-    @Test
-    public void testProcessTemplatePartialChainEnd() throws Exception {
-        String template = "#chain(' OR ' 'WHERE ')"
-                + "#chunk($a)$a#end"
-                + "#chunk($b)$b#end"
-                + "#chunk($c)$c#end"
-                + "#end";
-
-        Map map = new HashMap();
-        map.put("a", "[A]");
-        map.put("b", "[B]");
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(template, map);
-        assertEquals("WHERE [A] OR [B]", compiled.getSql());
-    }
-
-    @Test
-    public void testProcessTemplateChainWithGarbage() throws Exception {
-        String template = "#chain(' OR ' 'WHERE ')"
-                + "#chunk($a)$a#end"
-                + " some other stuff"
-                + "#chunk($c)$c#end"
-                + "#end";
-
-        Map map = new HashMap();
-        map.put("a", "[A]");
-        map.put("c", "[C]");
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(template, map);
-        assertEquals("WHERE [A] some other stuff OR [C]", compiled.getSql());
-    }
-
-    @Test
-    public void testProcessTemplateChainUnconditionalChunks() throws Exception {
-        String template = "#chain(' OR ' 'WHERE ')"
-                + "#chunk()C1#end"
-                + "#chunk()C2#end"
-                + "#chunk()C3#end"
-                + "#end";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                template,
-                Collections.EMPTY_MAP);
-        assertEquals("WHERE C1 OR C2 OR C3", compiled.getSql());
-    }
-
-    @Test
-    public void testProcessTemplateEmptyChain() throws Exception {
-        String template = "#chain(' OR ' 'WHERE ')"
-                + "#chunk($a)$a#end"
-                + "#chunk($b)$b#end"
-                + "#chunk($c)$c#end"
-                + "#end";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                template,
-                Collections.EMPTY_MAP);
-        assertEquals("", compiled.getSql());
-    }
-
-    @Test
-    public void testProcessTemplateWithFalseOrZero1() throws Exception {
-        String template = "#chain(' OR ' 'WHERE ')"
-                + "#chunk($a)[A]#end"
-                + "#chunk($b)[B]#end"
-                + "#chunk($c)$c#end"
-                + "#end";
-
-        Map map = new HashMap();
-        map.put("a", false);
-        map.put("b", 0);
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(template, map);
-        assertEquals("WHERE [A] OR [B]", compiled.getSql());
-    }
-
-    @Test
-    public void testProcessTemplateWithFalseOrZero2() throws Exception {
-        String template = "#chain(' OR ' 'WHERE ')"
-                + "#chunk($a)$a#end"
-                + "#chunk($b)$b#end"
-                + "#chunk($c)$c#end"
-                + "#end";
-
-        Map map = new HashMap();
-        map.put("a", false);
-        map.put("b", 0);
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(template, map);
-        assertEquals("WHERE false OR 0", compiled.getSql());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorSelectTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorSelectTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorSelectTest.java
deleted file mode 100644
index bc498c8..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorSelectTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import org.junit.Test;
-
-import java.util.Collections;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-public class SQLTemplateProcessorSelectTest {
-
-    @Test
-    public void testProcessTemplateUnchanged() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                Collections.EMPTY_MAP);
-
-        assertEquals(sqlTemplate, compiled.getSql());
-        assertEquals(0, compiled.getBindings().length);
-        assertEquals(0, compiled.getResultColumns().length);
-    }
-
-    @Test
-    public void testProcessSelectTemplate1() throws Exception {
-        String sqlTemplate = "SELECT #result('A') FROM ME";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                Collections.EMPTY_MAP);
-
-        assertEquals("SELECT A FROM ME", compiled.getSql());
-        assertEquals(0, compiled.getBindings().length);
-        assertEquals(1, compiled.getResultColumns().length);
-        assertEquals("A", compiled.getResultColumns()[0].getName());
-        assertNull(compiled.getResultColumns()[0].getJavaClass());
-    }
-
-    @Test
-    public void testProcessSelectTemplate2() throws Exception {
-        String sqlTemplate = "SELECT #result('A' 'String') FROM ME";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                Collections.EMPTY_MAP);
-
-        assertEquals("SELECT A FROM ME", compiled.getSql());
-        assertEquals(0, compiled.getBindings().length);
-
-        assertEquals(1, compiled.getResultColumns().length);
-        assertEquals("A", compiled.getResultColumns()[0].getName());
-        assertEquals("java.lang.String", compiled.getResultColumns()[0].getJavaClass());
-    }
-
-    @Test
-    public void testProcessSelectTemplate3() throws Exception {
-        String sqlTemplate = "SELECT #result('A' 'String' 'B') FROM ME";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                Collections.EMPTY_MAP);
-
-        assertEquals("SELECT A AS B FROM ME", compiled.getSql());
-        assertEquals(0, compiled.getBindings().length);
-
-        assertEquals(1, compiled.getResultColumns().length);
-        ColumnDescriptor column = compiled.getResultColumns()[0];
-        assertEquals("A", column.getName());
-        assertEquals("B", column.getDataRowKey());
-        assertEquals("java.lang.String", column.getJavaClass());
-    }
-
-    @Test
-    public void testProcessSelectTemplate4() throws Exception {
-        String sqlTemplate = "SELECT #result('A'), #result('B'), #result('C') FROM ME";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                Collections.EMPTY_MAP);
-
-        assertEquals("SELECT A, B, C FROM ME", compiled.getSql());
-        assertEquals(0, compiled.getBindings().length);
-
-        assertEquals(3, compiled.getResultColumns().length);
-        assertEquals("A", compiled.getResultColumns()[0].getName());
-        assertEquals("B", compiled.getResultColumns()[1].getName());
-        assertEquals("C", compiled.getResultColumns()[2].getName());
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorTest.java
deleted file mode 100644
index a45c068..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessorTest.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import org.apache.cayenne.CayenneDataObject;
-import org.apache.cayenne.DataObject;
-import org.apache.cayenne.ObjectId;
-import org.junit.Test;
-
-import java.sql.Types;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- */
-public class SQLTemplateProcessorTest {
-
-    @Test
-    public void testProcessTemplateUnchanged1() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                Collections.EMPTY_MAP);
-
-        assertEquals(sqlTemplate, compiled.getSql());
-        assertEquals(0, compiled.getBindings().length);
-    }
-
-    @Test
-    public void testProcessTemplateUnchanged2() throws Exception {
-        String sqlTemplate = "SELECT a.b as XYZ FROM $SYSTEM_TABLE";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                Collections.EMPTY_MAP);
-
-        assertEquals(sqlTemplate, compiled.getSql());
-        assertEquals(0, compiled.getBindings().length);
-    }
-
-    @Test
-    public void testProcessTemplateSimpleDynamicContent() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME WHERE $a";
-
-        Map map = Collections.singletonMap("a", "VALUE_OF_A");
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                map);
-
-        assertEquals("SELECT * FROM ME WHERE VALUE_OF_A", compiled.getSql());
-
-        // bindings are not populated, since no "bind" macro is used.
-        assertEquals(0, compiled.getBindings().length);
-    }
-
-    @Test
-    public void testProcessTemplateBind() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME WHERE "
-                + "COLUMN1 = #bind($a 'VARCHAR') AND COLUMN2 = #bind($b 'INTEGER')";
-        Map map = Collections.singletonMap("a", "VALUE_OF_A");
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                map);
-
-        assertEquals("SELECT * FROM ME WHERE COLUMN1 = ? AND COLUMN2 = ?", compiled
-                .getSql());
-        assertEquals(2, compiled.getBindings().length);
-        assertBindingValue("VALUE_OF_A", compiled.getBindings()[0]);
-        assertBindingValue(null, compiled.getBindings()[1]);
-    }
-
-    @Test
-    public void testProcessTemplateBindGuessVarchar() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN1 = #bind($a)";
-        Map map = Collections.singletonMap("a", "VALUE_OF_A");
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                map);
-
-        assertEquals(1, compiled.getBindings().length);
-        assertBindingType(Types.VARCHAR, compiled.getBindings()[0]);
-    }
-
-    @Test
-    public void testProcessTemplateBindGuessInteger() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN1 = #bind($a)";
-        Map map = Collections.singletonMap("a", new Integer(4));
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                map);
-
-        assertEquals(1, compiled.getBindings().length);
-        assertBindingType(Types.INTEGER, compiled.getBindings()[0]);
-    }
-
-    @Test
-    public void testProcessTemplateBindEqual() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN #bindEqual($a 'VARCHAR')";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                Collections.EMPTY_MAP);
-
-        assertEquals("SELECT * FROM ME WHERE COLUMN IS NULL", compiled.getSql());
-        assertEquals(0, compiled.getBindings().length);
-
-        Map map = Collections.singletonMap("a", "VALUE_OF_A");
-
-        compiled = new SQLTemplateProcessor().processTemplate(sqlTemplate, map);
-
-        assertEquals("SELECT * FROM ME WHERE COLUMN = ?", compiled.getSql());
-        assertEquals(1, compiled.getBindings().length);
-        assertBindingValue("VALUE_OF_A", compiled.getBindings()[0]);
-    }
-
-    @Test
-    public void testProcessTemplateBindNotEqual() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN #bindNotEqual($a 'VARCHAR')";
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                Collections.EMPTY_MAP);
-
-        assertEquals("SELECT * FROM ME WHERE COLUMN IS NOT NULL", compiled.getSql());
-        assertEquals(0, compiled.getBindings().length);
-
-        Map map = Collections.singletonMap("a", "VALUE_OF_A");
-
-        compiled = new SQLTemplateProcessor().processTemplate(sqlTemplate, map);
-
-        assertEquals("SELECT * FROM ME WHERE COLUMN <> ?", compiled.getSql());
-        assertEquals(1, compiled.getBindings().length);
-        assertBindingValue("VALUE_OF_A", compiled.getBindings()[0]);
-    }
-
-    @Test
-    public void testProcessTemplateID() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN1 = #bind($helper.cayenneExp($a, 'db:ID_COLUMN'))";
-
-        DataObject dataObject = new CayenneDataObject();
-        dataObject.setObjectId(new ObjectId("T", "ID_COLUMN", 5));
-
-        Map map = Collections.singletonMap("a", dataObject);
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                map);
-
-        assertEquals("SELECT * FROM ME WHERE COLUMN1 = ?", compiled.getSql());
-        assertEquals(1, compiled.getBindings().length);
-        assertBindingValue(new Integer(5), compiled.getBindings()[0]);
-    }
-
-    @Test
-    public void testProcessTemplateNotEqualID() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME WHERE "
-                + "COLUMN1 #bindNotEqual($helper.cayenneExp($a, 'db:ID_COLUMN1')) "
-                + "AND COLUMN2 #bindNotEqual($helper.cayenneExp($a, 'db:ID_COLUMN2'))";
-
-        Map idMap = new HashMap();
-        idMap.put("ID_COLUMN1", new Integer(3));
-        idMap.put("ID_COLUMN2", "aaa");
-        ObjectId id = new ObjectId("T", idMap);
-        DataObject dataObject = new CayenneDataObject();
-        dataObject.setObjectId(id);
-
-        Map map = Collections.singletonMap("a", dataObject);
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                map);
-
-        assertEquals("SELECT * FROM ME WHERE COLUMN1 <> ? AND COLUMN2 <> ?", compiled
-                .getSql());
-        assertEquals(2, compiled.getBindings().length);
-        assertBindingValue(new Integer(3), compiled.getBindings()[0]);
-        assertBindingValue("aaa", compiled.getBindings()[1]);
-    }
-
-    @Test
-    public void testProcessTemplateConditions() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME #if($a) WHERE COLUMN1 > #bind($a)#end";
-
-        Map map = Collections.singletonMap("a", "VALUE_OF_A");
-
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                map);
-
-        assertEquals("SELECT * FROM ME  WHERE COLUMN1 > ?", compiled.getSql());
-        assertEquals(1, compiled.getBindings().length);
-        assertBindingValue("VALUE_OF_A", compiled.getBindings()[0]);
-
-        compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                Collections.EMPTY_MAP);
-
-        assertEquals("SELECT * FROM ME ", compiled.getSql());
-        assertEquals(0, compiled.getBindings().length);
-    }
-
-    @Test
-    public void testProcessTemplateBindCollection() throws Exception {
-        String sqlTemplate = "SELECT * FROM ME WHERE COLUMN IN (#bind($list 'VARCHAR'))";
-
-        Map map = new HashMap();
-        map.put("list", Arrays.asList("a", "b", "c"));
-        SQLStatement compiled = new SQLTemplateProcessor().processTemplate(
-                sqlTemplate,
-                map);
-
-        assertEquals("SELECT * FROM ME WHERE COLUMN IN (?,?,?)", compiled.getSql());
-        assertEquals(3, compiled.getBindings().length);
-
-        compiled = new SQLTemplateProcessor().processTemplate(sqlTemplate, map);
-        assertBindingValue("a", compiled.getBindings()[0]);
-        assertBindingValue("b", compiled.getBindings()[1]);
-        assertBindingValue("c", compiled.getBindings()[2]);
-    }
-
-    protected void assertBindingValue(Object expectedValue, Object binding) {
-        assertTrue("Not a binding!", binding instanceof ParameterBinding);
-        assertEquals(expectedValue, ((ParameterBinding) binding).getValue());
-    }
-
-    protected void assertBindingType(int expectedType, Object binding) {
-        assertTrue("Not a binding!", binding instanceof ParameterBinding);
-        assertEquals(expectedType, ((ParameterBinding) binding).getJdbcType());
-    }
-
-    protected void assertBindingPrecision(int expectedPrecision, Object binding) {
-        assertTrue("Not a binding!", binding instanceof ParameterBinding);
-        assertEquals(expectedPrecision, ((ParameterBinding) binding).getScale());
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManagerTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManagerTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManagerTest.java
deleted file mode 100644
index ad7bf61..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManagerTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import org.apache.velocity.Template;
-import org.apache.velocity.runtime.RuntimeConstants;
-import org.apache.velocity.runtime.resource.Resource;
-import org.apache.velocity.runtime.resource.ResourceManager;
-import org.junit.Test;
-
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-public class SQLTemplateResourceManagerTest {
-
-    @Test
-    public void testFetResource() throws Exception {
-        SQLTemplateResourceManager rm = new SQLTemplateResourceManager();
-        rm.initialize(new MockupRuntimeServices());
-
-        Resource resource = rm.getResource(
-                "abc",
-                ResourceManager.RESOURCE_TEMPLATE,
-                RuntimeConstants.ENCODING_DEFAULT);
-
-        assertTrue(resource instanceof Template);
-
-        // must be cached...
-        assertSame(resource, rm.getResource(
-                "abc",
-                ResourceManager.RESOURCE_TEMPLATE,
-                RuntimeConstants.ENCODING_DEFAULT));
-
-        // new resource must be different
-        assertNotSame(resource, rm.getResource(
-                "xyz",
-                ResourceManager.RESOURCE_TEMPLATE,
-                RuntimeConstants.ENCODING_DEFAULT));
-
-        // after clearing cache, resource must be refreshed
-        rm.clearCache();
-        assertNotSame(resource, rm.getResource(
-                "abc",
-                ResourceManager.RESOURCE_TEMPLATE,
-                RuntimeConstants.ENCODING_DEFAULT));
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/test/java/org/apache/cayenne/velocity/BindDirectiveIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/velocity/BindDirectiveIT.java b/cayenne-server/src/test/java/org/apache/cayenne/velocity/BindDirectiveIT.java
new file mode 100644
index 0000000..78e55e8
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/velocity/BindDirectiveIT.java
@@ -0,0 +1,268 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.velocity;
+
+import org.apache.cayenne.DataRow;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.MockOperationObserver;
+import org.apache.cayenne.access.jdbc.SQLTemplateAction;
+import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
+import org.apache.cayenne.dba.JdbcAdapter;
+import org.apache.cayenne.dba.oracle.OracleAdapter;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.query.CapsStrategy;
+import org.apache.cayenne.query.SQLTemplate;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+
+import java.sql.Connection;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests BindDirective for passed null parameters and for not passed parameters
+ */
+@UseServerRuntime(ServerCase.TESTMAP_PROJECT)
+public class BindDirectiveIT extends ServerCase {
+
+    @Inject
+    private ServerCaseDataSourceFactory dataSourceFactory;
+
+    @Inject
+    private JdbcAdapter adapter;
+
+    @Inject
+    private ObjectContext context;
+
+    @Inject
+    private DBHelper dbHelper;
+
+    @Inject
+    private JdbcEventLogger logger;
+
+    @Override
+    protected void setUpAfterInjection() throws Exception {
+        dbHelper.deleteAll("PAINTING_INFO");
+        dbHelper.deleteAll("PAINTING");
+        dbHelper.deleteAll("ARTIST_EXHIBIT");
+        dbHelper.deleteAll("ARTIST_GROUP");
+        dbHelper.deleteAll("ARTIST");
+    }
+
+    public void testBindTimestamp() throws Exception {
+        Map<String, Object> parameters = new HashMap<String, Object>();
+        parameters.put("id", new Integer(1));
+        parameters.put("name", "ArtistWithDOB");
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(2010, 2, 8);
+        parameters.put("dob", new Timestamp(cal.getTime().getTime()));
+
+        // without JDBC usage
+        Map<String, ?> row = performInsertForParameters(parameters, false, 1);
+        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
+        assertEquals(cal.getTime(), row.get("DATE_OF_BIRTH"));
+        assertNotNull(row.get("DATE_OF_BIRTH"));
+        assertEquals(Date.class, row.get("DATE_OF_BIRTH").getClass());
+    }
+
+    public void testBindSQLDate() throws Exception {
+        Map<String, Object> parameters = new HashMap<String, Object>();
+        parameters.put("id", new Integer(1));
+        parameters.put("name", "ArtistWithDOB");
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(2010, 2, 8);
+        parameters.put("dob", new java.sql.Date(cal.getTime().getTime()));
+
+        // without JDBC usage
+        Map<String, ?> row = performInsertForParameters(parameters, false, 1);
+        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
+        assertEquals(parameters.get("dob"), row.get("DATE_OF_BIRTH"));
+        assertNotNull(row.get("DATE_OF_BIRTH"));
+        assertEquals(Date.class, row.get("DATE_OF_BIRTH").getClass());
+    }
+
+    public void testBindUtilDate() throws Exception {
+        Map<String, Object> parameters = new HashMap<String, Object>();
+        parameters.put("id", new Integer(1));
+        parameters.put("name", "ArtistWithDOB");
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(2010, 2, 8);
+        parameters.put("dob", cal.getTime());
+
+        // without JDBC usage
+        Map<String, ?> row = performInsertForParameters(parameters, false, 1);
+        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
+        assertEquals(parameters.get("dob"), row.get("DATE_OF_BIRTH"));
+        assertNotNull(row.get("DATE_OF_BIRTH"));
+        assertEquals(Date.class, row.get("DATE_OF_BIRTH").getClass());
+    }
+
+    public void testBindingForCollection() throws Exception {
+
+        // insert 3 artists
+        for (int i = 1; i < 4; i++) {
+            Map<String, Object> parameters = new HashMap<String, Object>();
+            parameters.put("id", new Long(i));
+            parameters.put("name", "Artist" + i);
+            performInsertForParameters(parameters, true, i);
+        }
+
+        // now select only with names: Artist1 and Artist3
+        Set<String> artistNames = new HashSet<String>();
+        artistNames.add("Artist1");
+        artistNames.add("Artist3");
+        String sql = "SELECT * FROM ARTIST WHERE ARTIST_NAME in (#bind($ARTISTNAMES))";
+        SQLTemplate query = new SQLTemplate(Artist.class, sql);
+        
+        // customize for DB's that require trimming CHAR spaces 
+        query.setTemplate(OracleAdapter.class.getName(), "SELECT * FROM ARTIST WHERE RTRIM(ARTIST_NAME) in (#bind($ARTISTNAMES))");
+        
+        query.setColumnNamesCapitalization(CapsStrategy.UPPER);
+        query.setParams(Collections.singletonMap("ARTISTNAMES", artistNames));
+        List<DataRow> result = context.performQuery(query);
+        assertEquals(2, result.size());
+    }
+
+    public void testBindForPassedNullParam() throws Exception {
+        Map<String, Object> parameters = new HashMap<String, Object>();
+        parameters.put("id", new Long(1));
+        parameters.put("name", "ArtistWithoutDOB");
+        // passing null in parameter
+        parameters.put("dob", null);
+
+        // without JDBC usage
+        Map<String, ?> row = performInsertForParameters(parameters, false, 1);
+        assertEquals(parameters.get("id"), row.get("ARTIST_ID"));
+        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
+        assertEquals(parameters.get("dob"), row.get("DATE_OF_BIRTH"));
+        assertNull(row.get("DATE_OF_BIRTH"));
+    }
+
+    public void testBindWithJDBCForPassedNullParam() throws Exception {
+        Map<String, Object> parameters = new HashMap<String, Object>();
+        parameters.put("id", new Long(1));
+        parameters.put("name", "ArtistWithoutDOB");
+        // passing null in parameter
+        parameters.put("dob", null);
+
+        // use JDBC
+        Map<String, ?> row = performInsertForParameters(parameters, true, 1);
+        assertEquals(parameters.get("id"), row.get("ARTIST_ID"));
+        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
+        assertEquals(parameters.get("dob"), row.get("DATE_OF_BIRTH"));
+        assertNull(row.get("DATE_OF_BIRTH"));
+    }
+
+    public void testBindForNotPassedParam() throws Exception {
+        Map<String, Object> parameters = new HashMap<String, Object>();
+        parameters.put("id", new Long(1));
+        parameters.put("name", "ArtistWithoutDOB");
+        // not passing parameter parameters.put("dob", not passed!);
+
+        // without JDBC usage
+        Map<String, ?> row = performInsertForParameters(parameters, false, 1);
+        assertEquals(parameters.get("id"), row.get("ARTIST_ID"));
+        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
+        // parameter should be passed as null
+        assertNull(row.get("DATE_OF_BIRTH"));
+    }
+
+    public void testBindWithJDBCForNotPassedParam() throws Exception {
+        Map<String, Object> parameters = new HashMap<String, Object>();
+        parameters.put("id", new Long(1));
+        parameters.put("name", "ArtistWithoutDOB");
+        // not passing parameter parameters.put("dob", not passed!);
+
+        // use JDBC
+        Map<String, ?> row = performInsertForParameters(parameters, true, 1);
+        assertEquals(parameters.get("id"), row.get("ARTIST_ID"));
+        assertEquals(parameters.get("name"), row.get("ARTIST_NAME"));
+        // parameter should be passed as null
+        assertNull(row.get("DATE_OF_BIRTH"));
+    }
+
+    /**
+     * Inserts row for given parameters
+     * 
+     * @return inserted row
+     */
+    private Map<String, ?> performInsertForParameters(
+            Map<String, Object> parameters,
+            boolean useJDBCType,
+            int expectedRowCount) throws Exception {
+
+        String templateString;
+        if (useJDBCType) {
+            templateString = "INSERT INTO ARTIST (ARTIST_ID, ARTIST_NAME, DATE_OF_BIRTH) "
+                    + "VALUES (#bind($id), #bind($name), #bind($dob 'DATE'))";
+        }
+        else {
+            templateString = "INSERT INTO ARTIST (ARTIST_ID, ARTIST_NAME, DATE_OF_BIRTH) "
+                    + "VALUES (#bind($id), #bind($name), #bind($dob))";
+        }
+        SQLTemplate template = new SQLTemplate(Object.class, templateString);
+
+        template.setParams(parameters);
+
+        DataNode node = new DataNode();
+        node.setEntityResolver(context.getEntityResolver());
+        node.setRowReaderFactory(mock(RowReaderFactory.class));
+        node.setAdapter(adapter);
+        SQLTemplateAction action = new SQLTemplateAction(template, node);
+
+        Connection c = dataSourceFactory.getSharedDataSource().getConnection();
+        try {
+            MockOperationObserver observer = new MockOperationObserver();
+            action.performAction(c, observer);
+
+            int[] batches = observer.countsForQuery(template);
+            assertNotNull(batches);
+            assertEquals(1, batches.length);
+            assertEquals(1, batches[0]);
+        }
+        finally {
+            c.close();
+        }
+
+        SelectQuery query = new SelectQuery(Artist.class);
+        query.setFetchingDataRows(true);
+
+        List<DataRow> data = context.performQuery(query);
+        assertEquals(expectedRowCount, data.size());
+        return data.get(0);
+    }
+}