Skip to content

Commit b30e9bf

Browse files
authored
Merge pull request #153 from domaframework/literal-support-for-builder
クエリビルダでリテラルの埋め込みをサポート
2 parents 4bf4bac + 67f11c0 commit b30e9bf

File tree

11 files changed

+215
-10
lines changed

11 files changed

+215
-10
lines changed

docs/sources/query-builder/index.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@
1515
どのクエリビルダも、インスタンスは ``Config`` 型の引数をとる
1616
``static`` な ``newInstance`` メソッドで生成できます。
1717
インスタンスには、 ``sql`` メソッドでSQL文字列の断片を、
18-
``param`` メソッドでパラメータの型とパラメータを渡せます。
18+
``param`` メソッドと ``literal`` メソッドでパラメータの型とパラメータを渡せます。
19+
20+
``param`` メソッドで渡されたパラメータは ``PreparedStatement`` のバインド変数として扱われます。
21+
22+
``literal`` メソッドで渡されたパラメータはSQLにリテラルとして埋め込まれます。
23+
このメソッドでパラメータが渡された場合、SQLインジェクション対策としてのエスケープ処理は実施されません。
24+
しかし、SQLインジェクションを防ぐためにパラメータの値にシングルクォテーションを含めることは禁止しています。
1925

2026
検索
2127
====
@@ -33,6 +39,8 @@
3339
builder.sql("salary");
3440
builder.sql("from Emp");
3541
builder.sql("where");
42+
builder.sql("job_type = ").literal(String.class, "fulltime");
43+
builder.sql("and");
3644
builder.sql("name like ").param(String.class, "S%");
3745
builder.sql("and");
3846
builder.sql("age > ").param(int.class, 20);

src/main/java/org/seasar/doma/jdbc/builder/BuildingHelper.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.seasar.doma.jdbc.builder;
1717

18-
import static org.seasar.doma.internal.util.AssertionUtil.*;
18+
import static org.seasar.doma.internal.util.AssertionUtil.assertUnreachable;
1919

2020
import java.util.ArrayList;
2121
import java.util.LinkedList;
@@ -72,18 +72,18 @@ List<Param> getParams() {
7272

7373
SqlNode getSqlNode() {
7474
StringBuilder buf = new StringBuilder(200);
75-
@SuppressWarnings("unused")
76-
int index = 1;
7775
for (Item item : items) {
7876
switch (item.kind) {
7977
case SQL:
8078
buf.append(item.sql);
8179
break;
8280
case PARAM:
8381
buf.append("/*");
82+
if (item.param.literal) {
83+
buf.append("^");
84+
}
8485
buf.append(item.param.name);
8586
buf.append("*/0");
86-
index++;
8787
break;
8888
default:
8989
assertUnreachable();

src/main/java/org/seasar/doma/jdbc/builder/DeleteBuilder.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,34 @@ public <P> DeleteBuilder param(Class<P> paramClass, P param) {
143143
if (paramClass == null) {
144144
throw new DomaNullPointerException("paramClass");
145145
}
146-
helper.appendParam(new Param(paramClass, param, paramIndex));
146+
return appendParam(paramClass, param, false);
147+
}
148+
149+
/**
150+
* リテラルとしてパラメータを追加します。
151+
* <p>
152+
* パラメータの型には、基本型とドメインクラスを指定できます。
153+
*
154+
* @param <P>
155+
* パラメータの型
156+
* @param paramClass
157+
* パラメータのクラス
158+
* @param param
159+
* パラメータ
160+
* @return このインスタンス
161+
* @throws DomaNullPointerException
162+
* {@code parameterClass} が {@code null} の場合
163+
*/
164+
public <P> DeleteBuilder literal(Class<P> paramClass, P param) {
165+
if (paramClass == null) {
166+
throw new DomaNullPointerException("paramClass");
167+
}
168+
return appendParam(paramClass, param, true);
169+
}
170+
171+
private <P> DeleteBuilder appendParam(Class<P> paramClass, P param,
172+
boolean literal) {
173+
helper.appendParam(new Param(paramClass, param, paramIndex, literal));
147174
paramIndex.increment();
148175
return new SubsequentDeleteBuilder(helper, query, paramIndex);
149176
}

src/main/java/org/seasar/doma/jdbc/builder/InsertBuilder.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,34 @@ public <P> InsertBuilder param(Class<P> paramClass, P param) {
142142
if (paramClass == null) {
143143
throw new DomaNullPointerException("paramClass");
144144
}
145-
helper.appendParam(new Param(paramClass, param, paramIndex));
145+
return appendParam(paramClass, param, false);
146+
}
147+
148+
/**
149+
* リテラルとしてパラメータを追加します。
150+
* <p>
151+
* パラメータの型には、基本型とドメインクラスを指定できます。
152+
*
153+
* @param <P>
154+
* パラメータの型
155+
* @param paramClass
156+
* パラメータのクラス
157+
* @param param
158+
* パラメータ
159+
* @return このインスタンス
160+
* @throws DomaNullPointerException
161+
* {@code parameterClass} が {@code null} の場合
162+
*/
163+
public <P> InsertBuilder literal(Class<P> paramClass, P param) {
164+
if (paramClass == null) {
165+
throw new DomaNullPointerException("paramClass");
166+
}
167+
return appendParam(paramClass, param, true);
168+
}
169+
170+
private <P> InsertBuilder appendParam(Class<P> paramClass, P param,
171+
boolean literal) {
172+
helper.appendParam(new Param(paramClass, param, paramIndex, literal));
146173
paramIndex.increment();
147174
return new SubsequentInsertBuilder(helper, query, paramIndex);
148175
}

src/main/java/org/seasar/doma/jdbc/builder/Param.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ class Param {
2727

2828
final Object param;
2929

30-
Param(Class<?> paramClass, Object param, ParamIndex index) {
30+
final boolean literal;
31+
32+
Param(Class<?> paramClass, Object param, ParamIndex index, boolean literal) {
3133
this.paramClass = paramClass;
3234
this.param = param;
3335
this.name = "p" + index.getValue();
36+
this.literal = literal;
3437
}
3538

3639
}

src/main/java/org/seasar/doma/jdbc/builder/SelectBuilder.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,34 @@ public <P> SelectBuilder param(Class<P> paramClass, P param) {
189189
if (paramClass == null) {
190190
throw new DomaNullPointerException("paramClass");
191191
}
192-
helper.appendParam(new Param(paramClass, param, paramIndex));
192+
return appendParam(paramClass, param, false);
193+
}
194+
195+
/**
196+
* リテラルとしてパラメータを追加します。
197+
* <p>
198+
* パラメータの型には、基本型とドメインクラスを指定できます。
199+
*
200+
* @param <P>
201+
* パラメータの型
202+
* @param paramClass
203+
* パラメータのクラス
204+
* @param param
205+
* パラメータ
206+
* @return このインスタンス
207+
* @throws DomaNullPointerException
208+
* {@code paramClass} が {@code null} の場合
209+
*/
210+
public <P> SelectBuilder literal(Class<P> paramClass, P param) {
211+
if (paramClass == null) {
212+
throw new DomaNullPointerException("paramClass");
213+
}
214+
return appendParam(paramClass, param, true);
215+
}
216+
217+
private <P> SelectBuilder appendParam(Class<P> paramClass, P param,
218+
boolean literal) {
219+
helper.appendParam(new Param(paramClass, param, paramIndex, literal));
193220
paramIndex.increment();
194221
return new SubsequentSelectBuilder(config, helper, query, paramIndex);
195222
}

src/main/java/org/seasar/doma/jdbc/builder/UpdateBuilder.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,34 @@ public <P> UpdateBuilder param(Class<P> paramClass, P param) {
146146
if (paramClass == null) {
147147
throw new DomaNullPointerException("paramClass");
148148
}
149-
helper.appendParam(new Param(paramClass, param, paramIndex));
149+
return appendParam(paramClass, param, false);
150+
}
151+
152+
/**
153+
* リテラルとしてパラメータを追加します。
154+
* <p>
155+
* パラメータの型には、基本型とドメインクラスを指定できます。
156+
*
157+
* @param <P>
158+
* パラメータの型
159+
* @param paramClass
160+
* パラメータのクラス
161+
* @param param
162+
* パラメータ
163+
* @return このインスタンス
164+
* @throws DomaNullPointerException
165+
* {@code parameterClass} が {@code null} の場合
166+
*/
167+
public <P> UpdateBuilder literal(Class<P> paramClass, P param) {
168+
if (paramClass == null) {
169+
throw new DomaNullPointerException("paramClass");
170+
}
171+
return appendParam(paramClass, param, true);
172+
}
173+
174+
private <P> UpdateBuilder appendParam(Class<P> paramClass, P param,
175+
boolean literal) {
176+
helper.appendParam(new Param(paramClass, param, paramIndex, literal));
150177
paramIndex.increment();
151178
return new SubsequentUpdateBuilder(helper, query, paramIndex);
152179
}

src/test/java/org/seasar/doma/jdbc/builder/DeleteBuilderTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,19 @@ public void testGetSql() throws Exception {
5050
builder.execute();
5151
}
5252

53+
public void testLiterall() throws Exception {
54+
DeleteBuilder builder = DeleteBuilder.newInstance(new MockConfig());
55+
builder.sql("delete from Emp");
56+
builder.sql("where");
57+
builder.sql("name = ").literal(String.class, "aaa");
58+
builder.sql("and");
59+
builder.sql("salary = ").literal(int.class, 10);
60+
61+
String sql = String.format("delete from Emp%n" + "where%n"
62+
+ "name = 'aaa'%n" + "and%n" + "salary = 10");
63+
assertEquals(sql, builder.getSql().getRawSql());
64+
65+
builder.execute();
66+
}
67+
5368
}

src/test/java/org/seasar/doma/jdbc/builder/InsertBuilderTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,19 @@ public void testGetSql() throws Exception {
5050
builder.execute();
5151
}
5252

53+
public void testLiteral() throws Exception {
54+
InsertBuilder builder = InsertBuilder.newInstance(new MockConfig());
55+
builder.sql("insert into Emp");
56+
builder.sql("(name, salary)");
57+
builder.sql("values (");
58+
builder.literal(String.class, "SMITH").sql(", ");
59+
builder.literal(int.class, 100).sql(")");
60+
61+
String sql = String.format("insert into Emp%n" + "(name, salary)%n"
62+
+ "values ('SMITH', 100)");
63+
assertEquals(sql, builder.getSql().getRawSql());
64+
65+
builder.execute();
66+
}
67+
5368
}

src/test/java/org/seasar/doma/jdbc/builder/SelectBuilderTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.seasar.doma.DomaIllegalArgumentException;
2424
import org.seasar.doma.MapKeyNamingType;
2525
import org.seasar.doma.internal.jdbc.mock.MockConfig;
26+
import org.seasar.doma.jdbc.JdbcException;
27+
import org.seasar.doma.message.Message;
2628

2729
import example.domain.PhoneNumber;
2830
import example.entity.Emp;
@@ -179,4 +181,39 @@ public void testGetResultList_Basic() throws Exception {
179181
assertNotNull(list);
180182
}
181183

184+
public void testLiteral() throws Exception {
185+
SelectBuilder builder = SelectBuilder.newInstance(new MockConfig());
186+
builder.sql("select");
187+
builder.sql("id").sql(",");
188+
builder.sql("name").sql(",");
189+
builder.sql("salary");
190+
builder.sql("from Emp");
191+
builder.sql("where");
192+
builder.sql("name = ").literal(String.class, "aaa");
193+
builder.sql("and");
194+
builder.sql("age > ").param(int.class, 20);
195+
String sql = String.format("select%n" + "id,%n" + "name,%n"
196+
+ "salary%n" + "from Emp%n" + "where%n" + "name = 'aaa'%n"
197+
+ "and%n" + "age > ?");
198+
assertEquals(sql, builder.getSql().getRawSql());
199+
}
200+
201+
public void testLiteral_singleQuoteIncluded() throws Exception {
202+
SelectBuilder builder = SelectBuilder.newInstance(new MockConfig());
203+
builder.sql("select");
204+
builder.sql("id").sql(",");
205+
builder.sql("name").sql(",");
206+
builder.sql("salary");
207+
builder.sql("from Emp");
208+
builder.sql("where");
209+
builder.sql("code = ").literal(String.class, "a'aa");
210+
builder.sql("and");
211+
builder.sql("age > ").param(int.class, 20);
212+
try {
213+
builder.getSql();
214+
} catch (JdbcException e) {
215+
assertEquals(Message.DOMA2224, e.getMessageResource());
216+
}
217+
}
218+
182219
}

0 commit comments

Comments
 (0)