• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

future-architect / uroborosql / #743

28 Jun 2024 04:17PM UTC coverage: 90.988% (+0.5%) from 90.484%
#743

push

web-flow
add paramIfNotEmpty method (#318)

* v0.x to master merge

* fix nanoseconds diff (java8 to java11)

* eclipse cleanup and  optimize import

* eclipse format

* optimize Imports

* remove unused annotation

* library version up

* migrate v0.x to master
- update java version 8 to 11
- update junit4 to junit5
- library version update

* fix test failed

* remove unused annotation

* fixed bug

* use var

* fix java8 coding style

* Refactoring TransactionContextManager

* Refactoring SqlAgent

* 途中コミット

* fix testcase

* fix review

* fix javadoc comments

* merge v0.x PR

* cleanup code

* cleanup test code

* change build status badge

* - agent.query, update, proc, batch にそれぞれSupplierを引数にとるメソッドを追加
- SqlQuery.paramとSqlEntityUpdate.setにFunctionを受け取るメソッドを追加

* testcaseの整形

* - SqlFluent と ExtractionCondition の分離
- Arrays.asList -> List.of への変更
- テストの整形

* - v0.x系の不具合対応の追いつき
- ログ出力の整理

* - SqlKindの整理(ENTITY_XXXの追加)
- REPL_LOGの追加
- Deprecatedメソッドの削除(SqlAgent)
- SqlAgent, ExecutionContextでsetterをfluent APIに変更

* DB接続URLの修正

* add and fix testcases.

* add event testcases.

* fix typo

* add paramIfNotEmpty method and StringUtils rename ObjectUtils

* fix review comments.

* remove unused import

1695 of 1958 new or added lines in 97 files covered. (86.57%)

26 existing lines in 10 files now uncovered.

8249 of 9066 relevant lines covered (90.99%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.97
/src/main/java/jp/co/future/uroborosql/parser/SqlParserImpl.java
1
/**
2
 * Copyright (c) 2017-present, Future Corporation
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
package jp.co.future.uroborosql.parser;
8

9
import java.util.Stack;
10
import java.util.regex.Pattern;
11

12
import jp.co.future.uroborosql.exception.EndCommentNotFoundRuntimeException;
13
import jp.co.future.uroborosql.exception.IfConditionNotFoundRuntimeException;
14
import jp.co.future.uroborosql.expr.ExpressionParser;
15
import jp.co.future.uroborosql.node.BeginNode;
16
import jp.co.future.uroborosql.node.BindVariableNode;
17
import jp.co.future.uroborosql.node.ContainerNode;
18
import jp.co.future.uroborosql.node.ElseNode;
19
import jp.co.future.uroborosql.node.EmbeddedValueNode;
20
import jp.co.future.uroborosql.node.IfNode;
21
import jp.co.future.uroborosql.node.Node;
22
import jp.co.future.uroborosql.node.ParenBindVariableNode;
23
import jp.co.future.uroborosql.node.PrefixSqlNode;
24
import jp.co.future.uroborosql.node.SqlNode;
25
import jp.co.future.uroborosql.utils.ObjectUtils;
26

27
/**
28
 * SQL解析処理実装クラス
29
 *
30
 * @author H.Sugimoto
31
 */
32
public class SqlParserImpl implements SqlParser {
33
        /** SQLトークナイザ */
34
        private final SqlTokenizer tokenizer;
35

36
        /** ExpressionParser */
37
        private final ExpressionParser expressionParser;
38

39
        /** バインドコメントを実行時SQLに出力するかどうか */
40
        private final boolean outputBindComment;
41

42
        /** ノードのスタック */
43
        private final Stack<Node> nodeStack = new Stack<>();
1✔
44

45
        /** 終端文字削除用の正規表現 */
46
        private static final Pattern PATTERN = Pattern.compile(";$");
1✔
47

48
        /** 解析位置 */
49
        private int position;
50

51
        /**
52
         * SqlParserImplのコンストラクタ
53
         *
54
         * @param sql パースするSQL
55
         * @param expressionParser 評価式のパーサー
56
         * @param removeTerminator 終端文字(;)を除去するかどうか
57
         * @param outputBindComment バインド変数置換後にバインド変数のコメント文字列を出力するかどうか
58
         */
59
        public SqlParserImpl(final String sql, final ExpressionParser expressionParser, final boolean removeTerminator,
60
                        final boolean outputBindComment) {
1✔
61
                var s = sql.trim();
1✔
62
                if (removeTerminator) {
1✔
63
                        s = PATTERN.matcher(s).replaceFirst("");
1✔
64
                }
65
                this.tokenizer = new SqlTokenizerImpl(s);
1✔
66
                this.expressionParser = expressionParser;
1✔
67
                this.outputBindComment = outputBindComment;
1✔
68
        }
1✔
69

70
        /**
71
         * {@inheritDoc}
72
         *
73
         * @see jp.co.future.uroborosql.parser.SqlParser#parse()
74
         */
75
        @Override
76
        public ContextTransformer parse() {
77
                push(new ContainerNode(0, 0));
1✔
78
                while (TokenType.EOF != tokenizer.next()) {
1✔
79
                        parseToken();
1✔
80
                }
81
                return new ContextTransformer(pop());
1✔
82
        }
83

84
        /**
85
         * トークン解析
86
         */
87
        @SuppressWarnings("incomplete-switch")
88
        protected void parseToken() {
89
                switch (tokenizer.getTokenType()) {
1✔
90
                case SQL:
91
                        parseSql();
1✔
92
                        break;
1✔
93
                case COMMENT:
94
                        parseComment();
1✔
95
                        break;
1✔
96
                case ELSE:
97
                        parseElse(tokenizer.getToken().length());
1✔
98
                        break;
1✔
99
                case BIND_VARIABLE:
UNCOV
100
                        parseBindVariable();
×
101
                        break;
102
                }
103
        }
1✔
104

105
        /**
106
         * SQL解析
107
         */
108
        protected void parseSql() {
109
                var sql = tokenizer.getToken();
1✔
110
                var node = peek();
1✔
111
                if ((node instanceof IfNode || node instanceof ElseNode) && node.getChildSize() == 0) {
1✔
112

113
                        SqlTokenizer st = new SqlTokenizerImpl(sql);
1✔
114
                        st.skipWhitespace();
1✔
115
                        var token = st.skipToken();
1✔
116
                        st.skipWhitespace();
1✔
117
                        if ("AND".equalsIgnoreCase(token) || "OR".equalsIgnoreCase(token)) {
1✔
118
                                node.addChild(new PrefixSqlNode(this.position, st.getBefore(), st.getAfter()));
1✔
119
                        } else {
120
                                node.addChild(new SqlNode(this.position, sql));
1✔
121
                        }
122
                } else {
1✔
123
                        node.addChild(new SqlNode(this.position, sql));
1✔
124
                }
125
                this.position = this.tokenizer.getPosition();
1✔
126
        }
1✔
127

128
        /**
129
         * コメント解析
130
         */
131
        protected void parseComment() {
132
                var comment = tokenizer.getToken();
1✔
133
                if (isTargetComment(comment)) {
1✔
134
                        if (isIfComment(comment)) {
1✔
135
                                parseIf();
1✔
136
                        } else if (isElIfComment(comment)) {
1✔
137
                                parseElIf();
1✔
138
                        } else if (isElseComment(comment)) {
1✔
139
                                parseElse(comment.length() + 4);
1✔
140
                        } else if (isBeginComment(comment)) {
1✔
141
                                parseBegin();
1✔
142
                        } else if (isEndComment(comment)) {
1✔
143
                                return;
×
144
                        } else {
145
                                parseCommentBindVariable();
1✔
146
                        }
147
                } else {
148
                        parseNormalComment();
1✔
149
                }
150
        }
1✔
151

152
        /**
153
         * 通常コメント解析
154
         */
155
        protected void parseNormalComment() {
156
                var comment = tokenizer.getToken();
1✔
157
                var node = new SqlNode(Math.max(this.position - 2, 0), "/*" + comment + "*/");
1✔
158
                this.position = this.tokenizer.getPosition();
1✔
159
                peek().addChild(node);
1✔
160
        }
1✔
161

162
        /**
163
         * IF文解析
164
         */
165
        protected void parseIf() {
166
                var condition = tokenizer.getToken().substring(2);
1✔
167
                if (ObjectUtils.isBlank(condition)) {
1✔
168
                        throw new IfConditionNotFoundRuntimeException();
×
169
                }
170
                var ifNode = new IfNode(expressionParser, Math.max(this.position - 2, 0), condition);
1✔
171
                this.position = this.tokenizer.getPosition();
1✔
172
                peek().addChild(ifNode);
1✔
173
                push(ifNode);
1✔
174
                parseEnd();
1✔
175
        }
1✔
176

177
        /**
178
         * IF文解析
179
         */
180
        protected void parseElIf() {
181
                var condition = tokenizer.getToken().substring(4);
1✔
182
                if (ObjectUtils.isBlank(condition)) {
1✔
183
                        throw new IfConditionNotFoundRuntimeException();
×
184
                }
185
                var elifNode = new IfNode(expressionParser, Math.max(this.position - 2, 0), condition);
1✔
186
                this.position = this.tokenizer.getPosition();
1✔
187
                var ifNode = (IfNode) pop();
1✔
188
                ifNode.setElseIfNode(elifNode);
1✔
189
                push(elifNode);
1✔
190

191
                // parseEnd();
192
        }
1✔
193

194
        /**
195
         * BEGIN文解析
196
         */
197
        protected void parseBegin() {
198
                var beginNode = new BeginNode(Math.max(this.position - 2, 0));
1✔
199
                this.position = this.tokenizer.getPosition();
1✔
200
                peek().addChild(beginNode);
1✔
201
                push(beginNode);
1✔
202
                parseEnd();
1✔
203
        }
1✔
204

205
        /**
206
         * END文解析
207
         */
208
        protected void parseEnd() {
209
                while (TokenType.EOF != tokenizer.next()) {
1✔
210
                        if (tokenizer.getTokenType() == TokenType.COMMENT && isEndComment(tokenizer.getToken())) {
1✔
211

212
                                pop();
1✔
213
                                this.position = this.tokenizer.getPosition();
1✔
214
                                return;
1✔
215
                        }
216
                        parseToken();
1✔
217
                }
218
                throw new EndCommentNotFoundRuntimeException();
1✔
219
        }
220

221
        /**
222
         * ELSE文解析
223
         *
224
         * @param length ELSE文の長さ
225
         */
226
        protected void parseElse(final int length) {
227
                var parent = peek();
1✔
228
                if (!(parent instanceof IfNode)) {
1✔
229
                        return;
×
230
                }
231
                var ifNode = (IfNode) pop();
1✔
232
                var elseNode = new ElseNode(Math.max(this.position - 2, 0), length);
1✔
233
                this.position = this.tokenizer.getPosition();
1✔
234
                ifNode.setElseNode(elseNode);
1✔
235
                push(elseNode);
1✔
236
                tokenizer.skipWhitespace();
1✔
237
        }
1✔
238

239
        /**
240
         * バインド変数解析
241
         */
242
        protected void parseCommentBindVariable() {
243
                var expr = tokenizer.getToken();
1✔
244
                var s = tokenizer.skipToken();
1✔
245
                if (s.startsWith("(") && s.endsWith(")")) {
1✔
246
                        peek().addChild(new ParenBindVariableNode(expressionParser, Math.max(this.position - 2, 0), expr, s,
1✔
247
                                        outputBindComment));
248
                } else if (expr.startsWith("#")) {
1✔
249
                        peek().addChild(new EmbeddedValueNode(expressionParser, Math.max(this.position - 2, 0), expr.substring(1),
1✔
250
                                        true, s));
251
                } else if (expr.startsWith("$")) {
1✔
252
                        peek().addChild(
1✔
253
                                        new EmbeddedValueNode(expressionParser, Math.max(this.position - 2, 0), expr.substring(1), s));
1✔
254
                } else {
255
                        peek().addChild(
1✔
256
                                        new BindVariableNode(expressionParser, Math.max(this.position - 2, 0), expr, s, outputBindComment));
1✔
257
                }
258
                this.position = this.tokenizer.getPosition();
1✔
259
        }
1✔
260

261
        /**
262
         * バインド変数解析
263
         */
264
        protected void parseBindVariable() {
UNCOV
265
                var expr = tokenizer.getToken();
×
UNCOV
266
                peek().addChild(new BindVariableNode(expressionParser, this.position, expr, null, outputBindComment));
×
UNCOV
267
                this.position = this.tokenizer.getPosition();
×
UNCOV
268
        }
×
269

270
        /**
271
         * ノードPOP
272
         *
273
         * @return Nodeオブジェクト
274
         */
275
        protected Node pop() {
276
                return nodeStack.pop();
1✔
277
        }
278

279
        /**
280
         * ノードPEEK
281
         *
282
         * @return Nodeオブジェクト
283
         */
284
        protected Node peek() {
285
                return nodeStack.peek();
1✔
286
        }
287

288
        /**
289
         * ノードPUSH
290
         *
291
         * @param node Nodeオブジェクト
292
         */
293
        protected void push(final Node node) {
294
                nodeStack.push(node);
1✔
295
        }
1✔
296

297
        /**
298
         * 解析対象コメント判定
299
         *
300
         * @param comment
301
         * @return 解析対象の場合は<code>true</code>
302
         */
303
        private static boolean isTargetComment(final String comment) {
304
                return comment != null
1✔
305
                                && comment.length() > 0
1✔
306
                                && (Character.isJavaIdentifierStart(comment.charAt(0)) || '#' == comment.charAt(0) || '(' == comment
1✔
307
                                                .charAt(0));
1✔
308
        }
309

310
        /**
311
         * IFコメント判定
312
         *
313
         * @param comment
314
         * @return IFコメントの場合は<code>true</code>
315
         */
316
        private static boolean isIfComment(final String comment) {
317
                return comment.startsWith("IF");
1✔
318
        }
319

320
        /**
321
         * ELSEIFコメント判定
322
         *
323
         * @param comment
324
         * @return ELSEIFコメントの場合は<code>true</code>
325
         */
326
        private static boolean isElIfComment(final String comment) {
327
                return comment.startsWith("ELIF");
1✔
328
        }
329

330
        /**
331
         * ELSEコメント判定
332
         *
333
         * @param comment
334
         * @return ELSEコメントの場合は<code>true</code>
335
         */
336
        private static boolean isElseComment(final String comment) {
337
                return comment.startsWith("ELSE");
1✔
338
        }
339

340
        /**
341
         * BEGINコメント判定
342
         *
343
         * @param content
344
         * @return BEGINコメントの場合は<code>true</code>
345
         */
346
        private static boolean isBeginComment(final String content) {
347
                return content != null && "BEGIN".equals(content);
1✔
348
        }
349

350
        /**
351
         * ENDコメント判定
352
         *
353
         * @param content
354
         * @return ENDコメントの場合は<code>true</code>
355
         */
356
        private static boolean isEndComment(final String content) {
357
                return content != null && "END".equals(content);
1✔
358
        }
359

360
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc