Skip to content

Commit 9bde6a7

Browse files
authored
Merge pull request #150 from domaframework/literal-variable-comment
リテラル変数コメントの機能を追加
2 parents 74a9808 + 1e4df12 commit 9bde6a7

File tree

17 files changed

+455
-98
lines changed

17 files changed

+455
-98
lines changed

docs/sources/sql.rst

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,55 @@ Dao インタフェースのメソッドと対応する SQL の例は次のと
221221
where
222222
salary = /* dto.getTaxedSalary() */1234
223223
224+
リテラル変数コメント
225+
--------------------
226+
227+
リテラル変数を示す式コメントを *リテラル変数* コメントと呼びます。
228+
リテラル変数は、 SQLのリテラルの形式に変換された後にSQLに埋め込まれます。
229+
リテラルの形式に変換とは、文字列型をシングルクォートで囲むなどを指します。
230+
この変換にはSQLインジェクション対策としてのエスケープ処理は含まれません。
231+
232+
SQL インジェクションを防ぐため、リテラル変数の値にシングルクォテーションを含めることは禁止しています。
233+
234+
リテラル変数は ``/*^~*/`` というブロックコメントで囲んで示します。
235+
リテラル変数の名前はDaoメソッドのパラメータ名に対応します。
236+
対応するパラメータの型は :doc:`basic` もしくは :doc:`domain` でなければいけません。
237+
リテラル変数コメントの直後にはテスト用データを指定する必要があります。
238+
ただし、テスト用データは実行時には使用されません。
239+
240+
Dao インタフェースのメソッドと対応する SQL の例は次のとおりです。
241+
242+
.. code-block:: java
243+
244+
Employee selectByCode(String code);
245+
246+
.. code-block:: sql
247+
248+
select * from employee where code = /*^ code */'test'
249+
250+
Dao の呼び出し例は次の通りです。
251+
252+
.. code-block:: java
253+
254+
EmployeeDao dao = new EmployeeDaoImpl();
255+
List<Employee> list = dao.selectByCode("abc");
256+
257+
発行される SQL は次のようになります。
258+
259+
.. code-block:: sql
260+
261+
select * from employee where code = 'abc'
262+
263+
記法が異なることを除けば、使い方はバインド変数コメントと同様です。
264+
265+
.. note::
266+
267+
リテラル変数コメントは、実行計画を固定するなどあえてバインド変数の使用を避けたい場合に利用できます。
268+
224269
埋め込み変数コメント
225270
--------------------
226271

227-
埋め込み変数を示す式コメントを埋め込み変数コメントと呼びます
272+
埋め込み変数を示す式コメントを *埋め込み変数* コメントと呼びます
228273
埋め込み変数の値は SQL を組み立てる際に SQL の一部として直接埋め込まれます。
229274

230275
SQL インジェクションを防ぐため、埋め込み変数の値に以下の値を含めることは禁止しています。
@@ -236,7 +281,7 @@ SQL インジェクションを防ぐため、埋め込み変数の値に以下
236281

237282
埋め込み変数は ``/*#~*/`` というブロックコメントで示します。
238283
埋め込み変数の名前は Dao メソッドのパラメータ名にマッピングされます。
239-
埋め込み変数は ``ORDER BY`` 句など SQL の一部をプログラムで組み立てたい場合に使用できます。
284+
240285
Dao のメソッドと対応する SQL の例は次のとおりです。
241286

242287
.. code-block:: java
@@ -262,6 +307,10 @@ Dao の呼び出し例は次の通りです。
262307
263308
select * from employee where salary > ? order by salary asc, employee_name
264309
310+
.. note::
311+
312+
埋め込み変数コメントは、 ORDER BY 句など SQL の一部をプログラムで組み立てたい場合に使用できます。
313+
265314
条件コメント
266315
------------
267316

src/main/java/org/seasar/doma/internal/apt/SqlValidator.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@
4646
import org.seasar.doma.internal.jdbc.sql.node.ForBlockNode;
4747
import org.seasar.doma.internal.jdbc.sql.node.ForNode;
4848
import org.seasar.doma.internal.jdbc.sql.node.IfNode;
49+
import org.seasar.doma.internal.jdbc.sql.node.LiteralVariableNode;
4950
import org.seasar.doma.internal.jdbc.sql.node.PopulateNode;
5051
import org.seasar.doma.internal.jdbc.sql.node.SqlLocation;
52+
import org.seasar.doma.internal.jdbc.sql.node.ValueNode;
5153
import org.seasar.doma.jdbc.SqlNode;
5254
import org.seasar.doma.message.Message;
5355

@@ -115,20 +117,29 @@ public void validate(SqlNode sqlNode) {
115117

116118
@Override
117119
public Void visitBindVariableNode(BindVariableNode node, Void p) {
120+
return visitValueNode(node, p);
121+
}
122+
123+
@Override
124+
public Void visitLiteralVariableNode(LiteralVariableNode node, Void p) {
125+
return visitValueNode(node, p);
126+
}
127+
128+
protected Void visitValueNode(ValueNode node, Void p) {
118129
SqlLocation location = node.getLocation();
119130
String variableName = node.getVariableName();
120131
TypeDeclaration typeDeclaration = validateExpressionVariable(location,
121132
variableName);
122133
if (node.getWordNode() != null) {
123-
if (!isBindable(typeDeclaration)) {
134+
if (!isScalar(typeDeclaration)) {
124135
String sql = getSql(location);
125136
throw new AptException(Message.DOMA4153, env, methodElement,
126137
new Object[] { path, sql, location.getLineNumber(),
127138
location.getPosition(), variableName,
128139
typeDeclaration.getBinaryName() });
129140
}
130141
} else {
131-
if (!isBindableIterable(typeDeclaration)) {
142+
if (!isScalarIterable(typeDeclaration)) {
132143
String sql = getSql(location);
133144
env.getMessager().printMessage(Kind.NOTE,
134145
parameterTypeMap.toString());
@@ -142,13 +153,13 @@ public Void visitBindVariableNode(BindVariableNode node, Void p) {
142153
return null;
143154
}
144155

145-
protected boolean isBindable(TypeDeclaration typeDeclaration) {
156+
protected boolean isScalar(TypeDeclaration typeDeclaration) {
146157
TypeMirror typeMirror = typeDeclaration.getType();
147158
return BasicCtType.newInstance(typeMirror, env) != null
148159
|| DomainCtType.newInstance(typeMirror, env) != null;
149160
}
150161

151-
protected boolean isBindableIterable(TypeDeclaration typeDeclaration) {
162+
protected boolean isScalarIterable(TypeDeclaration typeDeclaration) {
152163
TypeMirror typeMirror = typeDeclaration.getType();
153164
IterableCtType iterableCtType = IterableCtType.newInstance(typeMirror,
154165
env);

src/main/java/org/seasar/doma/internal/jdbc/sql/NodePreparedSqlBuilder.java

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.StringJoiner;
2525
import java.util.function.BiConsumer;
26+
import java.util.function.Consumer;
2627
import java.util.function.Function;
2728
import java.util.function.Supplier;
2829
import java.util.regex.Matcher;
@@ -56,6 +57,7 @@
5657
import org.seasar.doma.internal.jdbc.sql.node.HavingClauseNode;
5758
import org.seasar.doma.internal.jdbc.sql.node.IfBlockNode;
5859
import org.seasar.doma.internal.jdbc.sql.node.IfNode;
60+
import org.seasar.doma.internal.jdbc.sql.node.LiteralVariableNode;
5961
import org.seasar.doma.internal.jdbc.sql.node.LogicalOperatorNode;
6062
import org.seasar.doma.internal.jdbc.sql.node.OptionClauseNode;
6163
import org.seasar.doma.internal.jdbc.sql.node.OrderByClauseNode;
@@ -68,6 +70,7 @@
6870
import org.seasar.doma.internal.jdbc.sql.node.SqlLocation;
6971
import org.seasar.doma.internal.jdbc.sql.node.UpdateClauseNode;
7072
import org.seasar.doma.internal.jdbc.sql.node.UpdateStatementNode;
73+
import org.seasar.doma.internal.jdbc.sql.node.ValueNode;
7174
import org.seasar.doma.internal.jdbc.sql.node.WhereClauseNode;
7275
import org.seasar.doma.internal.jdbc.sql.node.WhitespaceNode;
7376
import org.seasar.doma.internal.jdbc.sql.node.WordNode;
@@ -81,6 +84,7 @@
8184
import org.seasar.doma.jdbc.SqlNode;
8285
import org.seasar.doma.jdbc.SqlNodeVisitor;
8386
import org.seasar.doma.message.Message;
87+
import org.seasar.doma.wrapper.WrapperVisitor;
8488

8589
/**
8690
* @author taedium
@@ -199,21 +203,45 @@ public Void visitCommentNode(CommentNode node, Context p) {
199203

200204
@Override
201205
public Void visitBindVariableNode(BindVariableNode node, Context p) {
206+
return visitValueNode(node, p, p::addBindValue);
207+
}
208+
209+
@Override
210+
public Void visitLiteralVariableNode(final LiteralVariableNode node,
211+
Context p) {
212+
Consumer<Scalar<?, ?>> validator = (scalar) -> {
213+
Object value = scalar.get();
214+
if (value == null) {
215+
return;
216+
}
217+
String text = value.toString();
218+
if (text.indexOf('\'') > -1) {
219+
SqlLocation location = node.getLocation();
220+
throw new JdbcException(Message.DOMA2224, location.getSql(),
221+
location.getLineNumber(), location.getPosition(),
222+
node.getText());
223+
}
224+
};
225+
return visitValueNode(node, p, validator.andThen(p::addLiteralValue));
226+
}
227+
228+
protected Void visitValueNode(ValueNode node, Context p,
229+
Consumer<Scalar<?, ?>> valueHandler) {
202230
SqlLocation location = node.getLocation();
203231
String name = node.getVariableName();
204232
EvaluationResult result = p.evaluate(location, name);
205233
Object value = result.getValue();
206234
Class<?> valueClass = result.getValueClass();
207235
p.setAvailable(true);
208236
if (node.isWordNodeIgnored()) {
209-
handleSingleBindVarialbeNode(node, p, value, valueClass);
237+
handleSingleValueNode(node, p, value, valueClass, valueHandler);
210238
} else if (node.isParensNodeIgnored()) {
211239
ParensNode parensNode = node.getParensNode();
212240
OtherNode openedFragmentNode = parensNode.getOpenedFragmentNode();
213241
openedFragmentNode.accept(this, p);
214242
if (Iterable.class.isAssignableFrom(valueClass)) {
215-
handleIterableBindVarialbeNode(node, p, (Iterable<?>) value,
216-
valueClass);
243+
handleIterableValueNode(node, p, (Iterable<?>) value,
244+
valueClass, valueHandler);
217245
} else {
218246
throw new JdbcException(Message.DOMA2112, location.getSql(),
219247
location.getLineNumber(), location.getPosition(),
@@ -273,16 +301,17 @@ protected boolean startsWithClauseKeyword(String fragment) {
273301
return matcher.lookingAt();
274302
}
275303

276-
protected Void handleSingleBindVarialbeNode(BindVariableNode node,
277-
Context p, Object value, Class<?> valueClass) {
304+
protected Void handleSingleValueNode(ValueNode node, Context p,
305+
Object value, Class<?> valueClass, Consumer<Scalar<?, ?>> consumer) {
278306
Supplier<Scalar<?, ?>> supplier = wrap(node.getLocation(),
279307
node.getText(), value, valueClass);
280-
p.addBindValue(supplier.get());
308+
consumer.accept(supplier.get());
281309
return null;
282310
}
283311

284-
protected void handleIterableBindVarialbeNode(BindVariableNode node,
285-
Context p, Iterable<?> values, Class<?> valueClass) {
312+
protected void handleIterableValueNode(ValueNode node, Context p,
313+
Iterable<?> values, Class<?> valueClass,
314+
Consumer<Scalar<?, ?>> consumer) {
286315
int index = 0;
287316
for (Object v : values) {
288317
if (v == null) {
@@ -293,7 +322,7 @@ protected void handleIterableBindVarialbeNode(BindVariableNode node,
293322
}
294323
Supplier<Scalar<?, ?>> supplier = wrap(node.getLocation(),
295324
node.getText(), v, v.getClass());
296-
p.addBindValue(supplier.get());
325+
consumer.accept(supplier.get());
297326
p.appendRawSql(", ");
298327
p.appendFormattedSql(", ");
299328
index++;
@@ -635,7 +664,7 @@ public Void visitFragmentNode(FragmentNode node, Context p) {
635664

636665
@Override
637666
public Void visitParensNode(ParensNode node, Context p) {
638-
if (node.isAttachedWithBindVariable()) {
667+
if (node.isAttachedWithValue()) {
639668
return null;
640669
}
641670
Context context = new Context(p);
@@ -758,6 +787,15 @@ protected CharSequence getFormattedSqlBuf() {
758787
return formattedSqlBuf;
759788
}
760789

790+
protected <BASIC, CONTAINER> void addLiteralValue(
791+
Scalar<BASIC, CONTAINER> scalar) {
792+
String literal = scalar.getWrapper().accept(
793+
config.getDialect().getSqlLogFormattingVisitor(),
794+
formattingFunction, null);
795+
rawSqlBuf.append(literal);
796+
formattedSqlBuf.append(literal);
797+
}
798+
761799
protected <BASIC, CONTAINER> void addBindValue(
762800
Scalar<BASIC, CONTAINER> scalar) {
763801
appendParameterInternal(new ScalarInParameter<BASIC, CONTAINER>(
@@ -819,4 +857,9 @@ public String toString() {
819857
return rawSqlBuf.toString();
820858
}
821859
}
860+
861+
protected static class LiteralValueVisitor implements
862+
WrapperVisitor<String, Void, Void, RuntimeException> {
863+
864+
}
822865
}

src/main/java/org/seasar/doma/internal/jdbc/sql/SimpleSqlNodeVisitor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.seasar.doma.internal.jdbc.sql.node.HavingClauseNode;
3434
import org.seasar.doma.internal.jdbc.sql.node.IfBlockNode;
3535
import org.seasar.doma.internal.jdbc.sql.node.IfNode;
36+
import org.seasar.doma.internal.jdbc.sql.node.LiteralVariableNode;
3637
import org.seasar.doma.internal.jdbc.sql.node.LogicalOperatorNode;
3738
import org.seasar.doma.internal.jdbc.sql.node.OptionClauseNode;
3839
import org.seasar.doma.internal.jdbc.sql.node.OrderByClauseNode;
@@ -150,6 +151,11 @@ public R visitIfNode(IfNode node, P p) {
150151
return defaultAction(node, p);
151152
}
152153

154+
@Override
155+
public R visitLiteralVariableNode(LiteralVariableNode node, P p) {
156+
return defaultAction(node, p);
157+
}
158+
153159
@Override
154160
public R visitLogicalOperatorNode(LogicalOperatorNode node, P p) {
155161
return defaultAction(node, p);

0 commit comments

Comments
 (0)