Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions src/main/java/org/mybatis/scripting/velocity/InDirective.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright 2012 MyBatis.org.
*
* Licensed 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.mybatis.scripting.velocity;

import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;

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.exception.TemplateInitException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.directive.StopCommand;
import org.apache.velocity.runtime.parser.ParserTreeConstants;
import org.apache.velocity.runtime.parser.node.ASTReference;
import org.apache.velocity.runtime.parser.node.ASTStringLiteral;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.util.introspection.Info;

/**
* #in($collection $item COLUMN).
*
*/
public class InDirective extends RepeatDirective {

/** Immutable fields */
private String var;
private String open = "(";
private String close = ")";
private String separator = ", ";
private String column = "";

@Override
public String getName() {
return "in";
}

@Override
public void init(RuntimeServices rs, InternalContextAdapter context, Node node) throws TemplateInitException {
super.init(rs, context, node);
final int n = node.jjtGetNumChildren() - 1;
for (int i = 1; i < n; i++) {
Node child = node.jjtGetChild(i);
if (i == 1) {
if (child.getType() == ParserTreeConstants.JJTREFERENCE) {
var = ((ASTReference) child).getRootString();
} else {
throw new TemplateInitException("Syntax error", getTemplateName(), getLine(), getColumn());
}
} else if (child.getType() == ParserTreeConstants.JJTSTRINGLITERAL) {
String value = (String) ((ASTStringLiteral)child).value(context);
switch (i) {
case 2:
this.column = value;
break;
}
} else {
throw new TemplateInitException("Syntax error", getTemplateName(), getLine(), getColumn());
}
}
uberInfo = new Info(this.getTemplateName(), getLine(), getColumn());
}

@Override
public boolean render(InternalContextAdapter context, Writer writer, Node node) throws IOException, ResourceNotFoundException,
ParseErrorException, MethodInvocationException {
Object listObject = node.jjtGetChild(0).value(context);
if (listObject == null) {
return false;
}

Iterator<?> iterator = null;

try {
iterator = this.rsvc.getUberspect().getIterator(listObject, uberInfo);
} catch (RuntimeException e) {
throw e;
} catch (Exception ee) {
String msg = "Error getting iterator for #in at " + uberInfo;
rsvc.getLog().error(msg, ee);
throw new VelocityException(msg, ee);
}

if (iterator == null) {
throw new VelocityException("Invalid collection");
}

int counter = 0;
Object o = context.get(this.var);

ParameterMappingCollector collector = (ParameterMappingCollector) context.get(SQLScriptSource.MAPPING_COLLECTOR_KEY);
String savedItemKey = collector.getItemKey();
collector.setItemKey(this.var);
RepeatScope foreach = new RepeatScope(this, context.get(getName()), var);
context.put(getName(), foreach);

NullHolderContext nullHolderContext = null;
Object value = null;
writer.append(this.open);
while (iterator.hasNext()) {

if(counter % MAX_IN_CLAUSE_SIZE == 0) {
writer.append(this.open); // Group begins
writer.append(this.column);
writer.append(" IN ");
writer.append(this.open); // In starts
}

value = iterator.next();
put(context, this.var, value);
foreach.index++;
foreach.hasNext = iterator.hasNext();

try {
if (value == null) {
if (nullHolderContext == null) {
nullHolderContext = new NullHolderContext(this.var, context);
}
node.jjtGetChild(node.jjtGetNumChildren() - 1).render(nullHolderContext, writer);
} else {
node.jjtGetChild(node.jjtGetNumChildren() - 1).render(context, writer);
}
} catch (StopCommand stop) {
if (stop.isFor(this)) {
break;
} else {
clean(context, o, collector, savedItemKey);
throw stop;
}
}
counter++;


if((counter > 0 && counter % MAX_IN_CLAUSE_SIZE == 0) || !iterator.hasNext()) {
writer.append(this.close); // In ends
writer.append(this.close); // Group ends
if(iterator.hasNext()) {
writer.append(" OR ");
}
} else if(iterator.hasNext()) {
writer.append(this.separator);
}

}

writer.append(this.close);
clean(context, o, collector, savedItemKey);
return true;
}

@Override
public int getType() {
return BLOCK;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
*/
public class RepeatDirective extends Directive {

protected static final int MAX_IN_CLAUSE_SIZE = 1000;

/** Immutable fields */
private String var;
private String open = "";
Expand Down Expand Up @@ -149,7 +151,7 @@ public boolean render(InternalContextAdapter context, Writer writer, Node node)
}

counter++;
maxNbrLoopsExceeded = counter >= 1000;
maxNbrLoopsExceeded = counter >= MAX_IN_CLAUSE_SIZE;

if (i.hasNext() && !maxNbrLoopsExceeded) {
writer.append(separator);
Expand Down Expand Up @@ -236,7 +238,7 @@ protected static class NullHolderContext extends ChainedInternalContextAdapter {
private String loopVariableKey = "";
private boolean active = true;

private NullHolderContext(String key, InternalContextAdapter context) {
protected NullHolderContext(String key, InternalContextAdapter context) {
super(context);
if (key != null) {
loopVariableKey = key;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class VelocityFacade {
private static final String DIRECTIVES = TrimDirective.class.getName()
+ "," + WhereDirective.class.getName()
+ "," + SetDirective.class.getName()
+ "," + InDirective.class.getName()
+ "," + RepeatDirective.class.getName();

private static final RuntimeInstance engine;
Expand Down
130 changes: 130 additions & 0 deletions src/test/java/org/mybatis/scripting/velocity/InDirectiveTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright 2012 MyBatis.org.
*
* Licensed 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.mybatis.scripting.velocity;

import static org.junit.Assert.assertEquals;

import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Properties;

import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.junit.BeforeClass;
import org.junit.Test;

public class InDirectiveTest {

static VelocityContext ctxt;
static VelocityEngine velocity;

@BeforeClass
public static void setUpClass() throws Exception {
Properties p = new Properties();
p.setProperty("userdirective", InDirective.class.getName());
velocity = new VelocityEngine();
velocity.init(p);
ctxt = new VelocityContext();
ctxt.put(SQLScriptSource.MAPPING_COLLECTOR_KEY,
new ParameterMappingCollector(new ParameterMapping[]{}, new HashMap<String, Object>(), new Configuration()));
StringWriter writer = new StringWriter();
velocity.evaluate(ctxt, writer, "WARM", "1+1");
}

@Test
public void ensureInClauseHasOne() throws Exception {
StringWriter w = new StringWriter();
ctxt.put("list", Collections.singletonList("?"));
velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end");
String result = w.toString();
assertEquals(result.split("\\?").length -1, 1);
assertEquals(result.split("IN").length -1, 1);
}

@Test
public void ensureInClauseHasTwo() throws Exception {
StringWriter w = new StringWriter();
ctxt.put("list", Arrays.asList("?", "?"));
velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end");
String result = w.toString();
assertEquals(result.split("\\?").length -1, 2);
}

@Test
public void ensureInClauseHasOneThousand() throws Exception {
StringWriter w = new StringWriter();
String[] arr = new String[1000];
Arrays.fill(arr, "?");
ctxt.put("list", Arrays.asList(arr));
velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end");
String result = w.toString();
assertEquals(result.split("\\?").length -1, 1000);
assertEquals(result.split("OR").length -1, 0);
}

@Test
public void ensureInClauseHasOneThousandAndOne() throws Exception {
StringWriter w = new StringWriter();
String[] arr = new String[1001];
Arrays.fill(arr, "?");
ctxt.put("list", Arrays.asList(arr));
velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end");
String result = w.toString();
assertEquals(result.split("\\?").length -1, 1001);
assertEquals(result.split("OR").length -1, 1);
}

@Test
public void ensureInClauseHasTwoThousand() throws Exception {
StringWriter w = new StringWriter();
String[] arr = new String[2000];
Arrays.fill(arr, "?");
ctxt.put("list", Arrays.asList(arr));
velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end");
String result = w.toString();
assertEquals(result.split("\\?").length -1, 2000);
assertEquals(result.split("OR").length -1, 1);
}

@Test
public void ensureInClauseHasTwoThousandAndOne() throws Exception {
StringWriter w = new StringWriter();
String[] arr = new String[2001];
Arrays.fill(arr, "?");
ctxt.put("list", Arrays.asList(arr));
velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end");
String result = w.toString();
assertEquals(result.split("\\?").length -1, 2001);
assertEquals(result.split("OR").length -1, 2);
}

@Test
public void ensureInClauseHasThreeThousandAndOne() throws Exception {
StringWriter w = new StringWriter();
String[] arr = new String[3001];
Arrays.fill(arr, "?");
ctxt.put("list", Arrays.asList(arr));
velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end");
String result = w.toString();
assertEquals(result.split("\\?").length -1, 3001);
assertEquals(result.split("OR").length -1, 3);
}

}
Loading