Skip to content

Commit

Permalink
When processing DynamicSqlSource, evaluate param values in scripting …
Browse files Browse the repository at this point in the history
…phase

- Evaluated param values are stored in `ParameterMapping` and later used in DefaultParameterHandler
- There is no change when processing RawSqlSource
- Removed unused `injectionFilter` from TextSqlNode (mybatisgh-117)

This should fix mybatis#2754 .

This might also fix mybatis#206 and mybatis#575 , but with this patch, it still is not possible to invoke a method with parameter inside a parameter reference like `#{_parameter.mymethod(_parameter.value)}`.
It might be possible to accept OGNL expression in a param reference (e.g. `#{${_parameter.mymethod(_parameter.value)}}`), but I'm not sure if that's a good idea.
  • Loading branch information
harawata committed Dec 17, 2022
1 parent 17a9930 commit 584990a
Show file tree
Hide file tree
Showing 15 changed files with 383 additions and 251 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright 2009-2022 the original author or authors.
*
* 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
*
* https://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.ibatis.builder;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;

public class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
private final List<ParameterMapping> parameterMappings;
private final Class<?> parameterType;
private final MetaObject metaParameters;
private final Object parameterObject;
private final boolean paramExists;

public ParameterMappingTokenHandler(List<ParameterMapping> parameterMappings, Configuration configuration,
Object parameterObject, Class<?> parameterType, Map<String, Object> additionalParameters, boolean paramExists) {
super(configuration);
this.parameterType = parameterObject == null
? (parameterType == null ? Object.class : parameterType)
: parameterObject.getClass();
this.metaParameters = configuration.newMetaObject(additionalParameters);
this.parameterObject = parameterObject;
this.paramExists = paramExists;
this.parameterMappings = parameterMappings;
}

public ParameterMappingTokenHandler(List<ParameterMapping> parameterMappings, Configuration configuration,
Class<?> parameterType,
Map<String, Object> additionalParameters) {
super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
this.parameterObject = null;
this.paramExists = false;
this.parameterMappings = parameterMappings;
}

public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}

@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}

private ParameterMapping buildParameterMapping(String content) {
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
PropertyTokenizer propertyTokenizer = new PropertyTokenizer(property);
Class<?> propertyType;
if (metaParameters.hasGetter(propertyTokenizer.getName())) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
propertyType = Object.class;
} else {
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
ParameterMode mode = null;
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
mode = resolveParameterMode(value);
builder.mode(mode);
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
// Do Nothing
} else if ("expression".equals(name)) {
throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content
+ "}. Valid properties are " + PARAMETER_PROPERTIES);
}
}
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
if (!ParameterMode.OUT.equals(mode) && paramExists) {
if (metaParameters.hasGetter(propertyTokenizer.getName())) {
builder.value(metaParameters.getValue(property));
} else if (parameterObject == null) {
builder.value(null);
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
builder.value(parameterObject);
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
builder.value(metaObject.getValue(property));
}
}
return builder.build();
}

private Map<String, String> parseParameterMapping(String content) {
try {
return new ParameterExpression(content);
} catch (BuilderException ex) {
throw ex;
} catch (Exception ex) {
throw new BuilderException("Parsing error was found in mapping #{" + content
+ "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
}
}
}
118 changes: 8 additions & 110 deletions src/main/java/org/apache/ibatis/builder/SqlSourceBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,27 @@
*/
package org.apache.ibatis.builder;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;

/**
* @author Clinton Begin
*/
public class SqlSourceBuilder extends BaseBuilder {
public class SqlSourceBuilder {

private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

public SqlSourceBuilder(Configuration configuration) {
super(configuration);
private SqlSourceBuilder() {
super();
}

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
public static SqlSource buildSqlSource(Configuration configuration, String sql,
List<ParameterMapping> parameterMappings) {
return new StaticSqlSource(configuration,
configuration.isShrinkWhitespacesInSql() ? SqlSourceBuilder.removeExtraWhitespaces(sql) : sql,
parameterMappings);
}

public static String removeExtraWhitespaces(String original) {
Expand All @@ -66,92 +52,4 @@ public static String removeExtraWhitespaces(String original) {
return builder.toString();
}

private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

private final List<ParameterMapping> parameterMappings = new ArrayList<>();
private final Class<?> parameterType;
private final MetaObject metaParameters;

public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
}

public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}

@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}

private ParameterMapping buildParameterMapping(String content) {
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class<?> propertyType;
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
propertyType = Object.class;
} else {
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
// Do Nothing
} else if ("expression".equals(name)) {
throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + PARAMETER_PROPERTIES);
}
}
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
}

private Map<String, String> parseParameterMapping(String content) {
try {
return new ParameterExpression(content);
} catch (BuilderException ex) {
throw ex;
} catch (Exception ex) {
throw new BuilderException("Parsing error was found in mapping #{" + content + "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
}
}
}

}
4 changes: 3 additions & 1 deletion src/main/java/org/apache/ibatis/executor/BaseExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@ public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBo
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
if (parameterMapping.hasValue()) {
value = parameterMapping.getValue();
} else if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/org/apache/ibatis/mapping/ParameterMapping.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class ParameterMapping {
private String resultMapId;
private String jdbcTypeName;
private String expression;
private Object value;
private boolean hasValue;

private ParameterMapping() {
}
Expand Down Expand Up @@ -99,6 +101,12 @@ public Builder expression(String expression) {
return this;
}

public Builder value(Object value) {
parameterMapping.value = value;
parameterMapping.hasValue = true;
return this;
}

public ParameterMapping build() {
resolveTypeHandler();
validate();
Expand Down Expand Up @@ -207,6 +215,14 @@ public String getExpression() {
return expression;
}

public Object getValue() {
return value;
}

public boolean hasValue() {
return hasValue;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ParameterMapping{");
Expand All @@ -220,6 +236,7 @@ public String toString() {
sb.append(", resultMapId='").append(resultMapId).append('\'');
sb.append(", jdbcTypeName='").append(jdbcTypeName).append('\'');
sb.append(", expression='").append(expression).append('\'');
sb.append(", value='").append(value).append('\'');
sb.append('}');
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,7 @@ public void setParameters(PreparedStatement ps) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
Object value = getValue(parameterMapping);
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
Expand All @@ -93,4 +82,21 @@ public void setParameters(PreparedStatement ps) {
}
}

private Object getValue(ParameterMapping parameterMapping) {
if (parameterMapping.hasValue()) {
return parameterMapping.getValue();
}
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
return boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
return null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
return parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
return metaObject.getValue(propertyName);
}
}

}

0 comments on commit 584990a

Please sign in to comment.