Skip to content

Issue 652 - Parametrized include #331

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 2, 2015
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
73 changes: 69 additions & 4 deletions src/main/java/org/apache/ibatis/builder/xml/XMLIncludeTransformer.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.apache.ibatis.builder.xml;

import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.parsing.PropertyParser;
Expand All @@ -23,6 +24,8 @@
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.Properties;

/**
* @author Frank D. Martinez [mnesarco]
*/
Expand All @@ -37,9 +40,39 @@ public XMLIncludeTransformer(Configuration configuration, MapperBuilderAssistant
}

public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
Properties configurationVariables = configuration.getVariables();
if (configurationVariables != null) {
variablesContext.putAll(configurationVariables);
}
applyIncludes(source, variablesContext);
}

/**
* Recursively apply includes through all SQL fragments.
* @param source Include node in DOM tree
* @param variablesContext Current context for static variables with values
*/
private void applyIncludes(Node source, final Properties variablesContext) {
if (source.getNodeName().equals("include")) {
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"));
applyIncludes(toInclude);
// new full context for included SQL - contains inherited context and new variables from current include node
Properties fullContext;

String refid = getStringAttribute(source, "refid");
// replace variables in include refid value
refid = PropertyParser.parse(refid, variablesContext);
Node toInclude = findSqlFragment(refid);
Properties newVariablesContext = getVariablesContext(source, variablesContext);
if (!newVariablesContext.isEmpty()) {
// merge contexts
fullContext = new Properties();
fullContext.putAll(variablesContext);
fullContext.putAll(newVariablesContext);
} else {
// no new context - use inherited fully
fullContext = variablesContext;
}
applyIncludes(toInclude, fullContext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part could be simpler if replacing variables in the subroutine.
What do you think?

Properties fullContext;
Properties childContext = getVariablesContext(source, variablesContext);
if (childContext.isEmpty()) {
  fullContext = variablesContext;
} else {
  fullContext = new Properties();
  fullContext.putAll(variablesContext);
  fullContext.putAll(childContext);
}
applyIncludes(toInclude, fullContext);

I could be missing something, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I wanted to save creation of another Properties object but it probably does not make much sense and better to make it simpler.

if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
toInclude = source.getOwnerDocument().importNode(toInclude, true);
}
Expand All @@ -51,13 +84,18 @@ public void applyIncludes(Node source) {
} else if (source.getNodeType() == Node.ELEMENT_NODE) {
NodeList children = source.getChildNodes();
for (int i=0; i<children.getLength(); i++) {
applyIncludes(children.item(i));
applyIncludes(children.item(i), variablesContext);
}
} else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) {
// replace variables in all attribute values
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
} else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {
// replace variables ins all text nodes
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
}
}

private Node findSqlFragment(String refid) {
refid = PropertyParser.parse(refid, configuration.getVariables());
refid = builderAssistant.applyCurrentNamespace(refid, true);
try {
XNode nodeToInclude = configuration.getSqlFragments().get(refid);
Expand All @@ -70,4 +108,31 @@ private Node findSqlFragment(String refid) {
private String getStringAttribute(Node node, String name) {
return node.getAttributes().getNamedItem(name).getNodeValue();
}

/**
* Read placholders and their values from include node definition.
* @param node Include node instance
* @param inheritedVariablesContext Current context used for replace variables in new variables values
* @return variables context from include instance (no inherited values)
*/
private Properties getVariablesContext(Node node, Properties inheritedVariablesContext) {
Properties variablesContext = new Properties();
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node n = children.item(i);
if (n.getNodeType() == Node.ELEMENT_NODE) {
String name = getStringAttribute(n, "name");
String value = getStringAttribute(n, "value");
// Replace variables inside
value = PropertyParser.parse(value, inheritedVariablesContext);
// Push new value
Object originalValue = variablesContext.put(name, value);
if (originalValue != null) {
throw new BuilderException("Variable " + name + " defined twice in the same include definition");
}
}
}
return variablesContext;
}

}
2 changes: 1 addition & 1 deletion src/main/java/org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ lang CDATA #IMPLIED

<!-- Dynamic -->

<!ELEMENT include EMPTY>
<!ELEMENT include (property+)?>
<!ATTLIST include
refid CDATA #REQUIRED
>
Expand Down
35 changes: 30 additions & 5 deletions src/site/xdoc/sqlmap-xml.xml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -485,19 +485,44 @@ ps.setInt(1,id);]]></source>
<subsection name="sql">
<p>
This element can be used to define a reusable fragment of SQL code that can be
included in other statements. For example:
included in other statements. It can be statically (during load phase) parametrized. Different property values can
vary in include instances. For example:
</p>

<source><![CDATA[<sql id="userColumns"> id,username,password </sql>]]></source>
<source><![CDATA[<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>]]></source>

<p>
The SQL fragment can then be included in another statement, for example:
</p>

<source><![CDATA[<select id="selectUsers" resultType="map">
select <include refid="userColumns"/>
from some_table
where id = #{id}
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>]]></source>

<p>
Property value can be also used in include refid attribute or property values inside include clause, for example:
</p>

<source><![CDATA[<sql id="sometable">
${prefix}Table
</sql>

<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>]]></source>
</subsection>

Expand Down
6 changes: 4 additions & 2 deletions src/test/java/org/apache/ibatis/builder/PostMapper.xml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
</select>

<sql id="byBlogId">
<if test="blog_id != null">blog_id = #{blog_id}</if>
<if test="blog_id != null">${prefix}_id = #{blog_id}</if>
</sql>

<select id="findPost" resultType="org.apache.ibatis.domain.blog.Post">
Expand All @@ -102,7 +102,9 @@
</foreach>
</if>
<trim prefix="AND">
<include refid="byBlogId"/>
<include refid="byBlogId">
<property name="prefix" value="blog"/>
</include>
</trim>
</otherwise>
</choose>
Expand Down
26 changes: 26 additions & 0 deletions src/test/java/org/apache/ibatis/submitted/includes/CreateDB.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--
-- Copyright 2009-2015 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
--
-- 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.
--

drop table SomeTable if exists;

create table SomeTable (
id int,
field1 varchar(20),
field2 varchar(20),
field3 varchar(20)
);

insert into SomeTable (id, field1, field2, field3) values(1, 'a', 'b', 'c');
46 changes: 39 additions & 7 deletions src/test/java/org/apache/ibatis/submitted/includes/IncludeTest.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,63 @@
package org.apache.ibatis.submitted.includes;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import static org.junit.Assert.assertNotNull;

import org.junit.BeforeClass;
import org.junit.Test;

import java.io.Reader;
import java.sql.Connection;
import java.util.Map;

import org.apache.ibatis.session.SqlSession;
import org.junit.Assert;

public class IncludeTest {

private static SqlSessionFactory sqlSessionFactory;

@BeforeClass
public static void setUp() throws Exception {
// create a SqlSessionFactory
Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/includes/MapperConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
reader.close();

// populate in-memory database
SqlSession session = sqlSessionFactory.openSession();
Connection conn = session.getConnection();
reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/includes/CreateDB.sql");
ScriptRunner runner = new ScriptRunner(conn);
runner.setLogWriter(null);
runner.runScript(reader);
reader.close();
session.close();
}

@Test
public void testIncludes() throws Exception {
String resource = "org/apache/ibatis/submitted/includes/MapperConfig.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlMapper = builder.build(reader);
assertNotNull(sqlMapper);

final SqlSession sqlSession = sqlMapper.openSession();
final SqlSession sqlSession = sqlSessionFactory.openSession();
try {
final Integer result = sqlSession.selectOne("org.apache.ibatis.submitted.includes.mapper.selectWithProperty");
Assert.assertEquals(Integer.valueOf(1), result);
} finally {
sqlSession.close();
}
}

@Test
public void testParametrizedIncludes() throws Exception {
final SqlSession sqlSession = sqlSessionFactory.openSession();
try {
final Map<String, Object> result = sqlSession.selectOne("org.apache.ibatis.submitted.includes.mapper.select");
//Assert.assertEquals(Integer.valueOf(1), result);
} finally {
sqlSession.close();
}
}

}
20 changes: 16 additions & 4 deletions src/test/java/org/apache/ibatis/submitted/includes/Mapper.xml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,23 @@

<mapper namespace="org.apache.ibatis.submitted.includes.mapper">
<sql id="sometable">
SomeTable
${prefix}Table
</sql>

<sql id="someinclude">
<include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
<include refid="org.apache.ibatis.submitted.includes.fragments.select"/>
<include refid="someinclude">
<property name="include_target" value="org.apache.ibatis.submitted.includes.fragments.select"/>
</include>
field1, field2, field3
from<include refid="sometable"/>
from
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>

<select id="selectWithProperty" resultType="_int">
Expand All @@ -38,7 +48,9 @@

<update id="update" parameterType="map">
<include refid="org.apache.ibatis.submitted.includes.fragments.update"/>
<include refid="org.apache.ibatis.submitted.includes.mapper.sometable"/>
<include refid="org.apache.ibatis.submitted.includes.mapper.sometable">
<property name="prefix" value="Some"/>
</include>
set Field2 = #{field2,jdbcType=INTEGER},
Field3 = #{field3,jdbcType=VARCHAR},
where field1 = #{field1,jdbcType=INTEGER}
Expand Down