Skip to content

Commit de3663e

Browse files
committed
resolves dynamic inclusion.
1 parent 1d9ffd3 commit de3663e

8 files changed

Lines changed: 195 additions & 63 deletions

File tree

hyperquery/src/main/java/org/slowcoders/hyperquery/core/QMapperView.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.slowcoders.hyperquery.impl.HModel;
44
import org.slowcoders.hyperquery.impl.HSchema;
55
import org.slowcoders.hyperquery.impl.ViewResolver;
6+
import org.w3c.dom.Node;
67

78
import java.util.Map;
89

@@ -13,7 +14,7 @@ public class QMapperView<R extends QRecord<?>> extends HModel {
1314
private final String namespace;
1415
private final String sqlId;
1516
private final Map<String, String> properties;
16-
private String query;
17+
private Object query;
1718

1819
public QMapperView(Class<R> recordType, Class<?> mapper, String sqlId) {
1920
this(recordType, mapper.getName(), sqlId);
@@ -40,7 +41,7 @@ protected HSchema loadSchema() {
4041
}
4142

4243
@Override
43-
protected String getTableExpression(ViewResolver viewResolver) {
44+
protected Object getTableExpression(ViewResolver viewResolver) {
4445
if (query == null) {
4546
query = viewResolver.resolveView(namespace, sqlId, properties);
4647
}
@@ -51,4 +52,12 @@ protected String getTableExpression(ViewResolver viewResolver) {
5152
protected String getTableName() {
5253
return "";
5354
}
55+
56+
public String getMapperId() {
57+
return namespace + '.' + sqlId;
58+
}
59+
60+
public final Map<String, String> getProperties() {
61+
return properties;
62+
}
5463
}

hyperquery/src/main/java/org/slowcoders/hyperquery/impl/HModel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public void initialize() {
1717

1818
protected abstract String getTableName();
1919

20-
protected String getTableExpression(ViewResolver viewResolver) { return null;}
20+
protected Object getTableExpression(ViewResolver viewResolver) { return ""; }
2121

2222
protected QAttribute getAttribute(String property) {
2323
return null;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.slowcoders.hyperquery.impl;
2+
3+
import org.apache.ibatis.parsing.XNode;
4+
5+
public class HQuery {
6+
XNode with;
7+
String query;
8+
9+
public HQuery(XNode rootSqlNode, String sql) {
10+
this.query = sql;
11+
this.with = rootSqlNode;
12+
}
13+
}

hyperquery/src/main/java/org/slowcoders/hyperquery/impl/QStore.java

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
import org.apache.ibatis.builder.xml.XMLIncludeTransformer;
55
import org.apache.ibatis.mapping.*;
66
import org.apache.ibatis.parsing.GenericTokenParser;
7-
import org.apache.ibatis.parsing.TokenHandler;
87
import org.apache.ibatis.parsing.XNode;
9-
import org.apache.ibatis.scripting.defaults.RawSqlSource;
108
import org.apache.ibatis.scripting.xmltags.*;
119
import org.apache.ibatis.session.Configuration;
12-
import org.apache.ibatis.type.SimpleTypeRegistry;
1310
import org.mybatis.spring.SqlSessionTemplate;
1411
import org.slowcoders.hyperquery.core.*;
12+
import org.w3c.dom.Document;
13+
import org.w3c.dom.Element;
1514
import org.w3c.dom.Node;
1615
import org.w3c.dom.NodeList;
1716

@@ -39,11 +38,11 @@ public <E extends QEntity<E>, R extends QRecord<E>> R selectOne(Class<R> resultT
3938

4039
public <E extends QEntity<E>, R extends QRecord<E>> List<R> selectList(HModel view, Class<R> resultType, QFilter<E> filter) {
4140
SqlBuilder gen = new SqlBuilder(view, resultType, filter, this);
42-
String sql = gen.build();
41+
HQuery node = gen.build();
4342

44-
String id = registerMapper(null, gen.getRootSchema(), resultType);
43+
String id = registerMapper(node.with, resultType);
4544

46-
HFilter._sql.set(sql);
45+
HFilter._sql.set(node.query);
4746
HFilter._session.set(getCurrentSessionInfo());
4847

4948
Object res = sqlSessionTemplate.selectList(id, filter);
@@ -96,17 +95,25 @@ private ResultMap createNestedResultMap(Class<?> clazz, String resultMapId, Stri
9695
return new ResultMap.Builder(configuration, resultMapId, clazz, resultMappings, true).build();
9796
}
9897

99-
String registerMapper(SqlSource sqlSource, HModel relation, Class<?> resultType) {
98+
String registerMapper(XNode sqlNode, Class<?> resultType) {
10099
String id = repositoryType.getName() + ".__select__." + resultType.getName();
101100

102101
if (!configuration.hasStatement(id)) {
103-
ResultMap inlineResultMap = createNestedResultMap(resultType, id + "-Inline", "");
104-
List<ResultMap> __resultMaps = new ArrayList<>();
105-
__resultMaps.add(inlineResultMap);
106102

107103
String root_id = repositoryType.getName() + ".__select__";
108104
MappedStatement root_ms = configuration.getMappedStatement(root_id);
109-
if (sqlSource == null) sqlSource = root_ms.getSqlSource();
105+
106+
SqlSource sqlSource;
107+
if (sqlNode == null) {
108+
sqlSource = root_ms.getSqlSource();
109+
} else {
110+
sqlSource = new XMLScriptBuilder(configuration, sqlNode).parseScriptNode();
111+
}
112+
113+
ResultMap inlineResultMap = createNestedResultMap(resultType, id + "-Inline", "");
114+
List<ResultMap> __resultMaps = new ArrayList<>();
115+
__resultMaps.add(inlineResultMap);
116+
110117
MappedStatement.Builder builder = new MappedStatement.Builder(configuration, id, sqlSource, root_ms.getSqlCommandType())
111118
.resource(root_ms.getResource())
112119
.fetchSize(root_ms.getFetchSize())
@@ -132,7 +139,7 @@ String registerMapper(SqlSource sqlSource, HModel relation, Class<?> resultType)
132139
return id;
133140
}
134141

135-
public String resolveView(String namespace, String sqlFragmentId, Map<String, String> properties) {
142+
public Object resolveView(String namespace, String sqlFragmentId, Map<String, String> properties) {
136143
String mapperId = namespace + "." + sqlFragmentId;
137144
if (configuration.hasStatement(mapperId)) {
138145
throw new IllegalArgumentException("Only <sql> fragments can be used to create View.");
@@ -143,9 +150,46 @@ public String resolveView(String namespace, String sqlFragmentId, Map<String, St
143150
builderAssistant.setCurrentNamespace(namespace);
144151
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
145152
XNode fr = configuration.getSqlFragments().get(mapperId);
146-
includeParser.applyIncludes(fr.getNode());
147-
if (!checkValidXmlQuery(fr.getNode())) {
148-
throw new IllegalArgumentException("View expression should not contain conditional expression like <if>, <choose>, <foreach>. But current is \n" + fr.toString());
153+
if (fr == null) {
154+
throw new IllegalArgumentException("<sql> fragments is not found. " + mapperId);
155+
}
156+
157+
Document doc = fr.getNode().getOwnerDocument();
158+
Element include = doc.createElement("include");
159+
include.setAttribute("refid", mapperId);
160+
for (Map.Entry<String, String> entry : properties.entrySet()) {
161+
Element property = doc.createElement("property");
162+
property.setAttribute("name", entry.getKey());
163+
property.setAttribute("value", entry.getValue());
164+
include.appendChild(property);
165+
}
166+
Element sqlNode = doc.createElement("sql");
167+
include.setAttribute("id", mapperId + "-sql");
168+
sqlNode.appendChild(include);
169+
170+
171+
includeParser.applyIncludes(sqlNode);
172+
if (!checkValidXmlQuery(sqlNode)) {
173+
return sqlNode;
174+
// GenericTokenParser tokenReplacer1 = new GenericTokenParser("#{", "}", properties::get);
175+
// GenericTokenParser tokenReplacer2 = new GenericTokenParser("${", "}", properties::get);
176+
// Node child = ((Node)fr.getNode()).getFirstChild();
177+
// for (; child != null; child = child.getNextSibling()) {
178+
// if (child.getNodeType() == Node.TEXT_NODE) {
179+
// String text = tokenReplacer1.parse(child.getTextContent());
180+
// text = tokenReplacer2.parse(text);
181+
// child.setTextContent(text);
182+
// }
183+
// }
184+
// return fr.getNode();
185+
// throw new IllegalArgumentException("View expression should not contain conditional expression like <if>, <choose>, <foreach>. But current is \n" + fr.toString());
186+
}
187+
188+
if (false) {
189+
// BoundSql bsql = sqlSource.getBoundSql(mapperParams);
190+
mapperId = registerMapper(fr, /*resultType*/null);
191+
Object res = sqlSessionTemplate.selectList(mapperId, /*mapperParams*/null);
192+
//System.out.println(res);
149193
}
150194

151195
String sql = fr.getNode().getTextContent();
@@ -160,7 +204,8 @@ private boolean checkValidXmlQuery(Node node) {
160204
}
161205
NodeList children = node.getChildNodes();
162206
for (int i = 0; i < children.getLength(); i++) {
163-
checkValidXmlQuery(children.item(i));
207+
if (!checkValidXmlQuery(children.item(i)))
208+
return false;
164209
}
165210
return true;
166211
}

hyperquery/src/main/java/org/slowcoders/hyperquery/impl/SqlBuilder.java

Lines changed: 91 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package org.slowcoders.hyperquery.impl;
22

3+
import org.apache.ibatis.parsing.GenericTokenParser;
4+
import org.apache.ibatis.parsing.XNode;
5+
import org.apache.ibatis.parsing.XPathParser;
36
import org.slowcoders.hyperquery.core.*;
47
import org.slowcoders.hyperquery.util.SqlWriter;
8+
import org.w3c.dom.Node;
59

610
import java.lang.reflect.Field;
711
import java.util.*;
@@ -18,6 +22,25 @@ public class SqlBuilder extends ViewNode {
1822
private ViewNode currView = this;
1923
private JoinNode currNode;
2024

25+
private SqlWriter sbWith = new SqlWriter().write("WITH ");
26+
private SqlWriter sbQuery = new SqlWriter();
27+
28+
private XPathParser xpathParser;
29+
30+
private final Node rootNode;
31+
private final XNode rootSqlNode;
32+
33+
private static final String emptyXml = """
34+
<?xml version="1.0" encoding="UTF-8" ?>
35+
<sql/>""";
36+
37+
static String ss = """
38+
<!DOCTYPE mapper
39+
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
40+
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
41+
42+
""";
43+
2144
public <R extends QRecord<E>, E extends QEntity<E>> SqlBuilder(HModel schema, Class<R> resultType, QFilter<E> filter, ViewResolver viewResolver ) {
2245
this.rootSchema = schema;
2346
this.resultType = resultType;
@@ -27,34 +50,49 @@ public <R extends QRecord<E>, E extends QEntity<E>> SqlBuilder(HModel schema, Cl
2750
if (filter != null && HSchema.getSchema(filter.getClass(), false) != this.rootSchema.loadSchema()) {
2851
throw new IllegalArgumentException("Filter type is not related to result type.");
2952
}
53+
this.xpathParser = new XPathParser(emptyXml);
54+
this.rootSqlNode = xpathParser.evalNode("/sql");
55+
this.rootNode = rootSqlNode.getNode();
3056
}
3157

3258
public final HModel getRootSchema() {
3359
return rootSchema;
3460
}
3561

36-
public String build() {
62+
public HQuery build() {
3763
List<ColumnMapping> columnMappings = parseSelect(rootSchema, resultType, "");
3864
QCriteria criteria = QCriteria.parse(this, filter, "@");
3965

4066
String where = criteria.toString();
41-
SqlWriter sbWith = new SqlWriter().write("WITH ");
42-
SqlWriter sb = new SqlWriter();
4367

44-
genTableView(sbWith, "t_0", currNode);
45-
genSelections(sb);
68+
genTableView("t_0", currNode);
69+
genSelections();
4670
String baseTable = currNode.views.isEmpty() ? rootSchema.getTableName() : "";
47-
sb.write("from ").write(baseTable).write(" t_0").write('\n');
48-
genJoin(sb, sbWith, currView.joins);
49-
if (sbWith.length() > 5) {
71+
sbQuery.write("from ").write(baseTable).write(" t_0").write('\n');
72+
genJoin(currView.joins);
73+
74+
boolean needMapper = rootNode.hasChildNodes();
75+
if (sbWith.length() > 5 || needMapper) {
5076
sbWith.shrinkLength(2);
5177
sbWith.writeln();
52-
sbWith.write(sb.toString());
53-
sb = sbWith;
78+
if (needMapper) {
79+
sbWith.write("${__sql__}");
80+
addTextNode(sbWith.reset());
81+
} else {
82+
sbWith.write(sbQuery.reset());
83+
sbQuery = sbWith;
84+
}
5485
}
55-
sb.write("WHERE ").write(where);
56-
String sql = sb.toString();
57-
return sql;
86+
87+
sbQuery.write("WHERE ").write(where);
88+
String sql = sbQuery.toString();
89+
// return sql;
90+
return new HQuery(needMapper ? rootSqlNode : null, sql);
91+
}
92+
93+
void addTextNode(String s) {
94+
Node text = rootNode.getOwnerDocument().createTextNode(s);
95+
rootNode.appendChild(text);
5896
}
5997

6098

@@ -143,58 +181,70 @@ private <T> T resolveQualifiedAlias(String path, IdentifierHandler<T> handler) {
143181
return res;
144182
}
145183

146-
private void genSelections(SqlWriter sb) {
147-
sb.write("SELECT ");
148-
sb.incTab();
184+
private void genSelections() {
185+
sbQuery.write("SELECT ");
186+
sbQuery.incTab();
149187
for (ColumnMapping col : columnMappings) {
150-
sb.write(col.columnName).write(" as \"").write(col.fieldName).write("\",\n");
188+
sbQuery.write(col.columnName).write(" as \"").write(col.fieldName).write("\",\n");
151189
}
152-
sb.shrinkLength(2);
153-
sb.decTab();
154-
sb.write('\n');
190+
sbQuery.shrinkLength(2);
191+
sbQuery.decTab();
192+
sbQuery.write('\n');
155193
}
156194

157195

158-
private void genJoin(SqlWriter sb, SqlWriter sbWith, Map<String, JoinNode> joinNodes) {
196+
private void genJoin(Map<String, JoinNode> joinNodes) {
159197
for (Map.Entry<String, JoinNode> entry : joinNodes.entrySet()) {
160198
String alias = entry.getKey();
161199
JoinNode node = entry.getValue();
162-
String tableName = genTableView(sbWith, alias, node);
163-
sb.write("left join ").write(tableName).write(" ").write(alias);
164-
sb.write("\n on ").write(node.joinCriteria).write('\n');
200+
String tableName = genTableView(alias, node);
201+
sbQuery.write("left join ").write(tableName).write(" ").write(alias);
202+
sbQuery.write("\n on ").write(node.joinCriteria).write('\n');
165203
}
166204
}
167205

168-
private String genTableView(SqlWriter sb, String alias, JoinNode node) {
206+
private String genTableView(String alias, JoinNode node) {
169207
String tableName = node.model.getTableName();
170208
if (tableName.isEmpty()) {
171-
sb.write(alias).write(" AS (\n");
172-
sb.incTab();
173-
sb.write(node.model.getTableExpression(viewResolver));
174-
sb.decTab();
175-
sb.write("), ");
209+
sbWith.write(alias).write(" AS (\n");
210+
sbWith.incTab();
211+
Object expr = node.model.getTableExpression(viewResolver);
212+
if (expr instanceof Node) {
213+
if (sbWith.length() > 0) {
214+
addTextNode(sbWith.reset());
215+
}
216+
Node child = ((Node)expr).getFirstChild();
217+
for (; child != null; child = child.getNextSibling()) {
218+
Node newChild = rootNode.getOwnerDocument().importNode(child, true);
219+
rootNode.appendChild(newChild);
220+
}
221+
} else {
222+
sbWith.write(expr.toString());
223+
}
224+
sbWith.decTab();
225+
sbWith.write("), ");
176226
return tableName;
177227
}
178228

179229
if (node.views.isEmpty()) return tableName;
180230

181231
for (int idx = node.views.size(); --idx >= 0;) {
182232
ViewNode attrMap = node.views.get(idx);
183-
sb.write(alias);
184-
if (idx > 0) sb.write('_').write(idx);
185-
sb.write(" AS (\n");
186-
sb.write("SELECT ").write(alias).write(".*");
187-
sb.incTab();
233+
sbWith.write(alias);
234+
if (idx > 0) sbWith.write('_').write(idx);
235+
sbWith.write(" AS (\n");
236+
sbWith.write("SELECT ").write(alias).write(".*");
237+
sbWith.incTab();
188238
for (Map.Entry<String, String> attr : attrMap.usedAttributes.entrySet()) {
189239
String name = attr.getKey();
190240
String expr = attr.getValue();
191-
sb.write("\n, ").write(expr).write(" as ").write(name);
241+
sbWith.write("\n, ").write(expr).write(" as ").write(name);
192242
}
193-
sb.decTab();
194-
sb.write("\nFROM ").write(tableName);
195-
sb.write(" AS ").write(alias).write("\n");
196-
genJoin(sb, null, attrMap.joins);
197-
sb.write("\n), ");
243+
sbWith.decTab();
244+
sbWith.write("\nFROM ").write(tableName);
245+
sbWith.write(" AS ").write(alias).write("\n");
246+
genJoin(attrMap.joins);
247+
sbWith.write("\n), ");
198248
tableName = alias + '_' + (idx);
199249
}
200250
return "";

0 commit comments

Comments
 (0)