View Javadoc
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          // was originally using regular expressions, but they didn't work well
50          // for ignoring parameter-like strings inside quotes.
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("?"); // replace the parameter with a question mark
109                     }
110                     names.add(name);
111                     i += name.length(); // skip past the end if the parameter
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     // Visible for testing
131     static boolean isFollowedOrPrefixedByColon(String sql, int i) {
132         return ':' == sql.charAt(i + 1) || (i > 0 && ':' == sql.charAt(i - 1));
133     }
134 
135 }