diff --git a/pom.xml b/pom.xml
index 1c8135f..87aaddb 100755
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,11 @@
commons-lang3
3.14.0
+
+ ognl
+ ognl
+ 3.4.2
+
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java
index 15822b8..2e4eaf5 100644
--- a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java
@@ -8,10 +8,15 @@
public class DynamicContext {
private final StringJoiner sqlBuilder = new StringJoiner(" ");
+ private Object parameterObject;
public DynamicContext() {
}
+ public DynamicContext(Object parameterObject) {
+ this.parameterObject = parameterObject;
+ }
+
public void appendSql(String sql) {
sqlBuilder.add(sql);
}
@@ -19,4 +24,12 @@ public void appendSql(String sql) {
public String getSql() {
return sqlBuilder.toString().trim();
}
+
+ public Object getParameterObject() {
+ return parameterObject;
+ }
+
+ public void setParameterObject(Object parameterObject) {
+ this.parameterObject = parameterObject;
+ }
}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java
new file mode 100644
index 0000000..dd3009b
--- /dev/null
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java
@@ -0,0 +1,30 @@
+package org.apache.ibatis.scripting.xmltags;
+
+import org.apache.ibatis.builder.SqlSourceBuilder;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.SqlSource;
+import org.apache.ibatis.session.Configuration;
+
+/**
+ * @author furious 2024/4/16
+ */
+public class DynamicSqlSource implements SqlSource {
+
+ private final Configuration configuration;
+ private final SqlNode rootSqlNode;
+
+ public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
+ this.configuration = configuration;
+ this.rootSqlNode = rootSqlNode;
+ }
+
+ @Override
+ public BoundSql getBoundSql(Object parameterObject) {
+ DynamicContext context = new DynamicContext(parameterObject);
+ rootSqlNode.apply(context);
+ SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
+ Class> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
+ SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType);
+ return sqlSource.getBoundSql(parameterObject);
+ }
+}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/IfSqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/IfSqlNode.java
new file mode 100644
index 0000000..80a2415
--- /dev/null
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/IfSqlNode.java
@@ -0,0 +1,30 @@
+package org.apache.ibatis.scripting.xmltags;
+
+import ognl.Ognl;
+
+/**
+ * @author furious 2024/4/16
+ */
+public class IfSqlNode implements SqlNode {
+
+ private final String test;
+ private final SqlNode contents;
+
+ public IfSqlNode(String test, SqlNode contents) {
+ this.test = test;
+ this.contents = contents;
+ }
+
+ @Override
+ public boolean apply(DynamicContext context) {
+ try {
+ Object value = Ognl.getValue(Ognl.parseExpression(test), context.getParameterObject());
+ if (value instanceof Boolean && (Boolean) value) {
+ contents.apply(context);
+ }
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/TrimSqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/TrimSqlNode.java
new file mode 100644
index 0000000..1d057a4
--- /dev/null
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/TrimSqlNode.java
@@ -0,0 +1,89 @@
+package org.apache.ibatis.scripting.xmltags;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @author furious 2024/4/16
+ */
+public class TrimSqlNode implements SqlNode {
+
+ private final SqlNode contents;
+ private final String prefix;
+ private final List prefixesToOverride;
+
+ public TrimSqlNode(SqlNode contents, String prefix, String prefixesToOverride) {
+ this.contents = contents;
+ this.prefix = prefix;
+ this.prefixesToOverride = parseOverrides(prefixesToOverride);
+ }
+
+ @Override
+ public boolean apply(DynamicContext context) {
+ FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
+ boolean result = contents.apply(filteredDynamicContext);
+ filteredDynamicContext.applyAll();
+ return result;
+ }
+
+ private static List parseOverrides(String overrides) {
+ if (StringUtils.isEmpty(overrides)) {
+ return Collections.emptyList();
+ }
+ return Arrays.stream(overrides.split("\\|")).collect(Collectors.toList());
+ }
+
+ private class FilteredDynamicContext extends DynamicContext {
+ private final DynamicContext delegate;
+ private StringBuilder sqlBuffer = new StringBuilder();
+
+ public FilteredDynamicContext(DynamicContext delegate) {
+ this.delegate = delegate;
+ }
+
+ public void applyAll() {
+ sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
+ String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase();
+ if (trimmedUppercaseSql.length() > 0) {
+ applyPrefix(sqlBuffer, trimmedUppercaseSql);
+ }
+ delegate.appendSql(sqlBuffer.toString());
+ }
+
+ @Override
+ public void appendSql(String sql) {
+ sqlBuffer.append(sql);
+ }
+
+ @Override
+ public String getSql() {
+ return delegate.getSql();
+ }
+
+ @Override
+ public Object getParameterObject() {
+ return delegate.getParameterObject();
+ }
+
+ private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
+ if (Objects.nonNull(prefixesToOverride)) {
+ for (String remove : prefixesToOverride) {
+ if (trimmedUppercaseSql.startsWith(remove)) {
+ sql.delete(0, remove.trim().length());
+ break;
+ }
+ }
+ }
+ if (prefix != null) {
+ sql.insert(0, " ");
+ sql.insert(0, prefix);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java
index b0a82c5..5646254 100644
--- a/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java
+++ b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java
@@ -1,9 +1,12 @@
package org.apache.ibatis.scripting.xmltags;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import org.apache.ibatis.builder.BaseBuilder;
+import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.scripting.defaults.RawSqlSource;
@@ -18,6 +21,8 @@ public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private final Class> parameterType;
+ private boolean isDynamic;
+ private final Map nodeHandlerMap = new HashMap<>();
public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, null);
@@ -27,10 +32,15 @@ public XMLScriptBuilder(Configuration configuration, XNode context, Class> par
super(configuration);
this.context = context;
this.parameterType = parameterType;
+ nodeHandlerMap.put("if", new IfHandler());
+ nodeHandlerMap.put("trim", new TrimHandler());
}
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
+ if (isDynamic) {
+ return new DynamicSqlSource(configuration, rootSqlNode);
+ }
return new RawSqlSource(configuration, rootSqlNode, parameterType);
}
@@ -41,8 +51,49 @@ protected MixedSqlNode parseDynamicTags(XNode node) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.TEXT_NODE) {
contents.add(new StaticTextSqlNode(child.getBody()));
+ } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {
+ String nodeName = child.getNode().getNodeName();
+ NodeHandler handler = nodeHandlerMap.get(nodeName);
+ if (handler == null) {
+ throw new BuilderException("not support " + nodeName + " NodeHandler");
+ }
+ handler.handleNode(child, contents);
+ isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
+
+ private interface NodeHandler {
+ void handleNode(XNode nodeToHandle, List targetContents);
+ }
+
+ /**
+ * 处理 标签
+ */
+ private class IfHandler implements NodeHandler {
+
+ @Override
+ public void handleNode(XNode nodeToHandle, List targetContents) {
+ MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
+ String test = nodeToHandle.getAttribute("test");
+ IfSqlNode ifSqlNode = new IfSqlNode(test, mixedSqlNode);
+ targetContents.add(ifSqlNode);
+ }
+ }
+
+ /**
+ * 处理 标签
+ */
+ private class TrimHandler implements NodeHandler {
+
+ @Override
+ public void handleNode(XNode nodeToHandle, List targetContents) {
+ MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
+ String prefix = nodeToHandle.getAttribute("prefix");
+ String prefixOverrides = nodeToHandle.getAttribute("prefixOverrides");
+ TrimSqlNode trim = new TrimSqlNode(mixedSqlNode, prefix, prefixOverrides);
+ targetContents.add(trim);
+ }
+ }
}