1 package org.davidmoten.rx.jdbc;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.stream.Collectors;
9 import java.util.stream.IntStream;
10
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13
14 final class SqlInfo {
15
16 private static final Logger log = LoggerFactory.getLogger(SqlInfo.class);
17 private final String sql;
18 private final List<String> names;
19 private final int numQuestionMarks;
20
21 SqlInfo(String sql, List<String> names) {
22 this.sql = sql;
23 this.names = names;
24 if (names.isEmpty()) {
25 numQuestionMarks = Util.countQuestionMarkParameters(sql);
26 } else {
27 numQuestionMarks = 0;
28 }
29
30 }
31
32 String sql() {
33 return sql;
34 }
35
36 int numParameters() {
37 if (names.isEmpty()) {
38 return numQuestionMarks;
39 } else {
40 return names.size();
41 }
42 }
43
44 List<String> names() {
45 return names;
46 }
47
48 static SqlInfo parse(String namedSql, List<Parameter> parameters) {
49
50
51 List<String> names = new ArrayList<String>();
52 String sql = collectNamesAndConvertToQuestionMarks(namedSql, names, parameters);
53 log.debug("sqlAfterSubs={}", sql);
54 return new SqlInfo(sql, names);
55 }
56
57 static SqlInfo parse(String namedSql) {
58 return parse(namedSql, Collections.emptyList());
59 }
60
61 private static String collectNamesAndConvertToQuestionMarks(String namedSql, List<String> names,
62 List<Parameter> parameters) {
63
64 Map<String, Parameter> map = new HashMap<>();
65 for (Parameter p : parameters) {
66 if (p.hasName()) {
67 map.put(p.name(), p);
68 }
69 }
70 int length = namedSql.length();
71 StringBuilder parsedQuery = new StringBuilder(length);
72 boolean inSingleQuote = false;
73 boolean inDoubleQuote = false;
74 int count = 0;
75 for (int i = 0; i < length; i++) {
76 char c = namedSql.charAt(i);
77 StringBuilder s = new StringBuilder();
78 if (inSingleQuote) {
79 if (c == '\'') {
80 inSingleQuote = false;
81 }
82 s.append(c);
83 } else if (inDoubleQuote) {
84 if (c == '"') {
85 inDoubleQuote = false;
86 }
87 s.append(c);
88 } else {
89 if (c == '\'') {
90 inSingleQuote = true;
91 s.append(c);
92 } else if (c == '"') {
93 inDoubleQuote = true;
94 s.append(c);
95 } else if (c == ':' && i + 1 < length && !isFollowedOrPrefixedByColon(namedSql, i)
96 && Character.isJavaIdentifierStart(namedSql.charAt(i + 1))) {
97 count++;
98 int j = i + 2;
99 while (j < length && Character.isJavaIdentifierPart(namedSql.charAt(j))) {
100 j++;
101 }
102 String name = namedSql.substring(i + 1, j);
103 if (!parameters.isEmpty()) {
104 Parameter p = map.get(name);
105 s.append(IntStream.range(0, p.size()).mapToObj(x -> "?")
106 .collect(Collectors.joining(",")));
107 } else {
108 s.append("?");
109 }
110 names.add(name);
111 i += name.length();
112 } else if (c == '?') {
113 count++;
114 if (!parameters.isEmpty()) {
115 Parameter p = parameters.get(count - 1);
116 s.append(IntStream.range(0, p.size()).mapToObj(x -> "?")
117 .collect(Collectors.joining(",")));
118 } else {
119 s.append(c);
120 }
121 } else {
122 s.append(c);
123 }
124 }
125 parsedQuery.append(s.toString());
126 }
127 return parsedQuery.toString();
128 }
129
130
131 static boolean isFollowedOrPrefixedByColon(String sql, int i) {
132 return ':' == sql.charAt(i + 1) || (i > 0 && ':' == sql.charAt(i - 1));
133 }
134
135 }