@an5/adapters 0.2.10 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -2
- package/dist/an5Adapter.d.ts +37 -0
- package/dist/an5Adapter.js +190 -65
- package/dist/base/metadata.d.ts +2 -0
- package/dist/base/metadata.js +10 -0
- package/dist/base/sql.d.ts +2 -0
- package/dist/base/sql.js +24 -25
- package/dist/base/types.d.ts +14 -0
- package/dist/googlesheets/adapter.js +16 -8
- package/dist/googlesheets/config.d.ts +13 -0
- package/dist/googlesheets/config.js +5 -1
- package/dist/googlesheets/oauth.d.ts +3 -0
- package/dist/googlesheets/oauth.js +43 -0
- package/dist/googlesheets/parseConnectionString.js +12 -0
- package/dist/googlesheets/tableClient.js +5 -23
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -1
- package/dist/sqlite/browserEngine.d.ts +13 -0
- package/dist/sqlite/browserEngine.js +13 -0
- package/dist/sqlite/engine.d.ts +5 -0
- package/dist/sqlite/engine.js +10 -0
- package/dist/sqlite/index.d.ts +1 -0
- package/dist/sqlite/index.js +1 -0
- package/dist/sqlite/vector.d.ts +155 -0
- package/dist/sqlite/vector.js +436 -0
- package/dotnet/An5Adapter.cs +85 -27
- package/dotnet/Base/Core.cs +14 -0
- package/dotnet/Mssql/MssqlEngine.cs +44 -1
- package/dotnet/Postgres/PostgresEngine.cs +2 -0
- package/dotnet/Sqlite/SqliteEngine.cs +82 -2
- package/dotnet/Sqlite/SqliteVectors.cs +401 -0
- package/golang/an5_adapter.go +84 -32
- package/golang/base/vectors.go +263 -0
- package/golang/sqlite/vector.go +248 -0
- package/golang/sqlite_vector_test.go +288 -0
- package/golang/table_client.go +67 -3
- package/java/pom.xml +177 -0
- package/java/src/main/java/an5/adapters/An5Adapter.java +374 -0
- package/java/src/main/java/an5/adapters/An5Aggregate.java +116 -0
- package/java/src/main/java/an5/adapters/An5GroupBy.java +97 -0
- package/java/src/main/java/an5/adapters/An5Query.java +100 -0
- package/java/src/main/java/an5/adapters/An5TableClient.java +1031 -0
- package/java/src/main/java/an5/adapters/An5ViewClient.java +107 -0
- package/java/src/main/java/an5/adapters/base/Dialect.java +103 -0
- package/java/src/main/java/an5/adapters/base/FieldMeta.java +42 -0
- package/java/src/main/java/an5/adapters/base/JdbcUrls.java +201 -0
- package/java/src/main/java/an5/adapters/base/Metadata.java +195 -0
- package/java/src/main/java/an5/adapters/base/RelationDef.java +26 -0
- package/java/src/main/java/an5/adapters/base/SqlBuilder.java +422 -0
- package/java/src/main/java/an5/adapters/base/SqliteVectors.java +375 -0
- package/java/src/main/java/an5/adapters/base/Vectors.java +123 -0
- package/java/src/test/java/An5AdaptersSmoke.java +733 -0
- package/java/src/test/java/Json.java +174 -0
- package/java/src/test/java/QuerySemantics.java +111 -0
- package/kotlin/build.gradle.kts +145 -0
- package/kotlin/settings.gradle.kts +10 -0
- package/kotlin/src/main/kotlin/an5/adapters/Aggregate.kt +89 -0
- package/kotlin/src/main/kotlin/an5/adapters/An5.kt +91 -0
- package/kotlin/src/main/kotlin/an5/adapters/Query.kt +130 -0
- package/kotlin/src/main/kotlin/an5/adapters/Rows.kt +166 -0
- package/kotlin/src/main/kotlin/an5/adapters/TableClient.kt +169 -0
- package/kotlin/src/main/kotlin/an5/adapters/Types.kt +77 -0
- package/kotlin/src/test/kotlin/an5/adapters/An5Smoke.kt +329 -0
- package/native/sqlite/CMakeLists.txt +12 -0
- package/native/sqlite/README.md +117 -0
- package/native/sqlite/an5_vector.c +209 -0
- package/package.json +40 -9
- package/python/an5_adapter.py +37 -6
- package/python/base/__init__.py +15 -0
- package/python/base/vectors.py +434 -0
- package/python/sqlite/__init__.py +2 -2
- package/python/sqlite/provider.py +21 -1
- package/python/table_client.py +129 -59
- package/rust/src/adapter.rs +342 -24
- package/rust/src/base/mod.rs +7 -0
- package/rust/src/base/vectors.rs +293 -0
- package/rust/src/lib.rs +3 -3
- package/scripts/sqlite-native-build.js +32 -0
- package/swift/Package.swift +22 -0
- package/swift/Sources/An5Adapters/An5Adapter.swift +160 -0
- package/swift/Sources/An5Adapters/Metadata.swift +183 -0
- package/swift/Sources/An5Adapters/Query.swift +105 -0
- package/swift/Sources/An5Adapters/Row+Accessors.swift +129 -0
- package/swift/Sources/An5Adapters/SQLBuilder.swift +359 -0
- package/swift/Sources/An5Adapters/SQLiteDriver.swift +301 -0
- package/swift/Sources/An5Adapters/SqliteVectors.swift +243 -0
- package/swift/Sources/An5Adapters/TableClient.swift +671 -0
- package/swift/Sources/An5Adapters/Types.swift +127 -0
- package/swift/Sources/An5Adapters/Vectors.swift +86 -0
- package/swift/Sources/An5Adapters/ViewClient.swift +50 -0
- package/swift/Sources/CSQLite/module.modulemap +8 -0
- package/swift/Sources/CSQLite/shim.h +9 -0
- package/swift/Tests/An5AdaptersTests/QuerySemanticsTests.swift +51 -0
- package/swift/Tests/An5AdaptersTests/RuntimeSmokeTests.swift +488 -0
- package/typescript/src/an5Adapter.ts +200 -58
- package/typescript/src/base/metadata.ts +8 -0
- package/typescript/src/base/sql.ts +23 -16
- package/typescript/src/base/types.ts +15 -0
- package/typescript/src/googlesheets/adapter.ts +17 -8
- package/typescript/src/googlesheets/config.ts +10 -1
- package/typescript/src/googlesheets/oauth.ts +31 -0
- package/typescript/src/googlesheets/parseConnectionString.ts +8 -0
- package/typescript/src/googlesheets/tableClient.ts +5 -19
- package/typescript/src/index.ts +13 -0
- package/typescript/src/sqlite/browserEngine.ts +24 -0
- package/typescript/src/sqlite/engine.ts +13 -0
- package/typescript/src/sqlite/index.ts +1 -1
- package/typescript/src/sqlite/vector.ts +541 -0
- package/golang/sqlite/sqlite.go +0 -14
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
package an5.adapters.base;
|
|
2
|
+
|
|
3
|
+
import java.util.ArrayList;
|
|
4
|
+
import java.util.Arrays;
|
|
5
|
+
import java.util.HashSet;
|
|
6
|
+
import java.util.LinkedHashMap;
|
|
7
|
+
import java.util.List;
|
|
8
|
+
import java.util.Locale;
|
|
9
|
+
import java.util.Map;
|
|
10
|
+
import java.util.Set;
|
|
11
|
+
|
|
12
|
+
/** Turns a where/order-by tree into SQL and the bind values that go with it. */
|
|
13
|
+
public final class SqlBuilder {
|
|
14
|
+
|
|
15
|
+
private static final Set<String> OPERATOR_KEYS =
|
|
16
|
+
new HashSet<String>(
|
|
17
|
+
Arrays.asList(
|
|
18
|
+
"equals", "in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"));
|
|
19
|
+
|
|
20
|
+
private SqlBuilder() {}
|
|
21
|
+
|
|
22
|
+
/** Where parameters in bind order, with the SQL that reads them. */
|
|
23
|
+
public static final class Where {
|
|
24
|
+
public final String sql;
|
|
25
|
+
public final List<Object> params;
|
|
26
|
+
|
|
27
|
+
Where(String sql, Map<String, Object> params) {
|
|
28
|
+
this.sql = sql;
|
|
29
|
+
this.params = new ArrayList<Object>(params.values());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public boolean isEmpty() {
|
|
33
|
+
return sql == null || sql.isEmpty();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Builds the {@code WHERE} clause for a filter tree.
|
|
39
|
+
*
|
|
40
|
+
* <p>Every value reaches the database as a bind parameter; no value is ever formatted
|
|
41
|
+
* into the SQL. {@code AND}/{@code OR}/{@code NOT} groups are supported, and an empty
|
|
42
|
+
* {@code in: []} becomes {@code 1=0} rather than invalid {@code IN ()} SQL.
|
|
43
|
+
*/
|
|
44
|
+
public static Where parseWhere(
|
|
45
|
+
Map<String, ?> where, Dialect dialect, Map<String, Object> params, String prefix) {
|
|
46
|
+
if (where == null || where.isEmpty()) {
|
|
47
|
+
return new Where("", params);
|
|
48
|
+
}
|
|
49
|
+
String p = prefix == null ? "" : prefix;
|
|
50
|
+
List<String> conditions = new ArrayList<String>();
|
|
51
|
+
|
|
52
|
+
Map<String, Object> flattened = new LinkedHashMap<String, Object>();
|
|
53
|
+
for (Map.Entry<String, ?> entry : where.entrySet()) {
|
|
54
|
+
String key = entry.getKey();
|
|
55
|
+
Object value = entry.getValue();
|
|
56
|
+
if (key.indexOf('_') >= 0 && value instanceof Map && !isOperatorValue(value)) {
|
|
57
|
+
for (Map.Entry<?, ?> nested : ((Map<?, ?>) value).entrySet()) {
|
|
58
|
+
flattened.put(String.valueOf(nested.getKey()), nested.getValue());
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
flattened.put(key, value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (Map.Entry<String, Object> entry : flattened.entrySet()) {
|
|
66
|
+
String key = entry.getKey();
|
|
67
|
+
Object value = entry.getValue();
|
|
68
|
+
|
|
69
|
+
if ("OR".equals(key) && value instanceof List) {
|
|
70
|
+
List<?> items = (List<?>) value;
|
|
71
|
+
List<String> sub = new ArrayList<String>();
|
|
72
|
+
for (int i = 0; i < items.size(); i++) {
|
|
73
|
+
sub.add(orEmpty(parseWhere(asMap(items.get(i)), dialect, params, p + "or" + i + "_").sql));
|
|
74
|
+
}
|
|
75
|
+
conditions.add(sub.isEmpty() ? "1=0" : "(" + join(sub, " OR ") + ")");
|
|
76
|
+
} else if ("AND".equals(key)) {
|
|
77
|
+
List<?> items = value instanceof List ? (List<?>) value : one(value);
|
|
78
|
+
List<String> sub = new ArrayList<String>();
|
|
79
|
+
for (int i = 0; i < items.size(); i++) {
|
|
80
|
+
String sql = parseWhere(asMap(items.get(i)), dialect, params, p + "and" + i + "_").sql;
|
|
81
|
+
if (sql != null && !sql.isEmpty()) {
|
|
82
|
+
sub.add(sql);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!sub.isEmpty()) {
|
|
86
|
+
conditions.add("(" + join(sub, " AND ") + ")");
|
|
87
|
+
}
|
|
88
|
+
} else if ("NOT".equals(key)) {
|
|
89
|
+
List<?> items = value instanceof List ? (List<?>) value : one(value);
|
|
90
|
+
List<String> sub = new ArrayList<String>();
|
|
91
|
+
for (int i = 0; i < items.size(); i++) {
|
|
92
|
+
sub.add(orEmpty(parseWhere(asMap(items.get(i)), dialect, params, p + "not" + i + "_").sql));
|
|
93
|
+
}
|
|
94
|
+
if (!sub.isEmpty()) {
|
|
95
|
+
conditions.add("NOT (" + join(sub, " OR ") + ")");
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
String column = dialect.quoteIdentifier(key);
|
|
99
|
+
String paramName = sanitizeParamName(p + key);
|
|
100
|
+
String placeholder = dialect.placeholder();
|
|
101
|
+
|
|
102
|
+
if (value == null) {
|
|
103
|
+
conditions.add(column + " IS NULL");
|
|
104
|
+
} else if (value instanceof Map) {
|
|
105
|
+
for (String condition : operators((Map<?, ?>) value, key, column, paramName, dialect, params, p)) {
|
|
106
|
+
conditions.add(condition);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
params.put(paramName, value);
|
|
110
|
+
conditions.add(column + " = " + placeholder);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return new Where(join(conditions, " AND "), params);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private static List<String> operators(
|
|
119
|
+
Map<?, ?> value,
|
|
120
|
+
String key,
|
|
121
|
+
String column,
|
|
122
|
+
String paramName,
|
|
123
|
+
Dialect dialect,
|
|
124
|
+
Map<String, Object> params,
|
|
125
|
+
String prefix) {
|
|
126
|
+
List<String> conditions = new ArrayList<String>();
|
|
127
|
+
String placeholder = dialect.placeholder();
|
|
128
|
+
|
|
129
|
+
if (value.containsKey("not")) {
|
|
130
|
+
Object inner = value.get("not");
|
|
131
|
+
if (inner == null) {
|
|
132
|
+
conditions.add(column + " IS NOT NULL");
|
|
133
|
+
} else if (inner instanceof Map) {
|
|
134
|
+
Map<String, Object> nestedParams = new LinkedHashMap<String, Object>();
|
|
135
|
+
String nestedSql =
|
|
136
|
+
parseWhere(
|
|
137
|
+
single(key, inner), dialect, nestedParams, prefix + key + "_not_").sql;
|
|
138
|
+
params.putAll(nestedParams);
|
|
139
|
+
if (nestedSql != null && !nestedSql.isEmpty()) {
|
|
140
|
+
conditions.add("NOT (" + nestedSql + ")");
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
params.put(paramName + "_not", inner);
|
|
144
|
+
conditions.add(column + " <> " + placeholder);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (value.containsKey("equals")) {
|
|
148
|
+
Object equals = value.get("equals");
|
|
149
|
+
if (equals == null) {
|
|
150
|
+
conditions.add(column + " IS NULL");
|
|
151
|
+
} else {
|
|
152
|
+
params.put(paramName + "_eq", equals);
|
|
153
|
+
conditions.add(column + " = " + placeholder);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
String[][] like = {
|
|
157
|
+
{"contains", "%%%s%%", "_co"},
|
|
158
|
+
{"startsWith", "%s%%", "_sw"},
|
|
159
|
+
{"endsWith", "%%%s", "_ew"},
|
|
160
|
+
};
|
|
161
|
+
for (String[] entry : like) {
|
|
162
|
+
if (!value.containsKey(entry[0])) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
Object operand = value.get(entry[0]);
|
|
166
|
+
params.put(
|
|
167
|
+
paramName + entry[2],
|
|
168
|
+
String.format(Locale.ROOT, entry[1], operand == null ? "" : operand));
|
|
169
|
+
conditions.add(column + " LIKE " + placeholder);
|
|
170
|
+
}
|
|
171
|
+
addComparison(conditions, value, "gte", params, paramName, column, placeholder, ">=");
|
|
172
|
+
addComparison(conditions, value, "lte", params, paramName, column, placeholder, "<=");
|
|
173
|
+
addComparison(conditions, value, "gt", params, paramName, column, placeholder, ">");
|
|
174
|
+
addComparison(conditions, value, "lt", params, paramName, column, placeholder, "<");
|
|
175
|
+
|
|
176
|
+
addSet(conditions, value, "in", params, paramName, column, placeholder, " IN (", true);
|
|
177
|
+
addSet(conditions, value, "notIn", params, paramName, column, placeholder, " NOT IN (", false);
|
|
178
|
+
|
|
179
|
+
return conditions;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private static void addComparison(
|
|
183
|
+
List<String> conditions,
|
|
184
|
+
Map<?, ?> value,
|
|
185
|
+
String operator,
|
|
186
|
+
Map<String, Object> params,
|
|
187
|
+
String paramName,
|
|
188
|
+
String column,
|
|
189
|
+
String placeholder,
|
|
190
|
+
String sql) {
|
|
191
|
+
if (!value.containsKey(operator)) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
params.put(paramName + "_" + operator, value.get(operator));
|
|
195
|
+
conditions.add(column + " " + sql + " " + placeholder);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private static void addSet(
|
|
199
|
+
List<String> conditions,
|
|
200
|
+
Map<?, ?> value,
|
|
201
|
+
String operator,
|
|
202
|
+
Map<String, Object> params,
|
|
203
|
+
String paramName,
|
|
204
|
+
String column,
|
|
205
|
+
String placeholder,
|
|
206
|
+
String sql,
|
|
207
|
+
boolean emptyIsFalse) {
|
|
208
|
+
if (!value.containsKey(operator)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
Object raw = value.get(operator);
|
|
212
|
+
List<?> values = raw instanceof List ? (List<?>) raw : raw == null ? new ArrayList<Object>() : one(raw);
|
|
213
|
+
if (values.isEmpty()) {
|
|
214
|
+
// `IN ()` is a syntax error in every dialect. An empty set can match nothing, so it
|
|
215
|
+
// becomes 1=0; the negated form is true of every row, so it becomes 1=1.
|
|
216
|
+
conditions.add(emptyIsFalse ? "1=0" : "1=1");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
List<String> placeholders = new ArrayList<String>();
|
|
220
|
+
for (int i = 0; i < values.size(); i++) {
|
|
221
|
+
params.put(paramName + "_" + operator + i, values.get(i));
|
|
222
|
+
placeholders.add(placeholder);
|
|
223
|
+
}
|
|
224
|
+
conditions.add(column + sql + join(placeholders, ", ") + ")");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Builds the {@code ORDER BY} clause from either a single map or a list of maps. */
|
|
228
|
+
public static String buildOrderBy(Object orderBy, Dialect dialect) {
|
|
229
|
+
if (orderBy == null) {
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
List<?> entries = orderBy instanceof List ? (List<?>) orderBy : one(orderBy);
|
|
233
|
+
List<String> parts = new ArrayList<String>();
|
|
234
|
+
for (Object entry : entries) {
|
|
235
|
+
if (!(entry instanceof Map)) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
for (Map.Entry<?, ?> field : ((Map<?, ?>) entry).entrySet()) {
|
|
239
|
+
String direction = field.getValue() == null ? "" : String.valueOf(field.getValue());
|
|
240
|
+
parts.add(
|
|
241
|
+
dialect.quoteIdentifier(String.valueOf(field.getKey()))
|
|
242
|
+
+ ("DESC".equalsIgnoreCase(direction) ? " DESC" : " ASC"));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return parts.isEmpty() ? "" : "ORDER BY " + join(parts, ", ");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Appends one {@code SET} assignment, expanding the arithmetic update operators. */
|
|
249
|
+
public static void appendUpdateSet(
|
|
250
|
+
List<String> setParts,
|
|
251
|
+
List<Object> values,
|
|
252
|
+
String column,
|
|
253
|
+
Object value,
|
|
254
|
+
Dialect dialect) {
|
|
255
|
+
String quoted = dialect.quoteIdentifier(column);
|
|
256
|
+
String placeholder = dialect.placeholder();
|
|
257
|
+
if (value instanceof Map) {
|
|
258
|
+
Map<?, ?> operations = (Map<?, ?>) value;
|
|
259
|
+
String[][] arithmetic =
|
|
260
|
+
new String[][] {
|
|
261
|
+
{"increment", "+"}, {"decrement", "-"}, {"multiply", "*"}, {"divide", "/"}
|
|
262
|
+
};
|
|
263
|
+
for (String[] pair : arithmetic) {
|
|
264
|
+
if (operations.containsKey(pair[0])) {
|
|
265
|
+
setParts.add(quoted + " = " + quoted + " " + pair[1] + " " + placeholder);
|
|
266
|
+
values.add(operations.get(pair[0]));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (operations.containsKey("set")) {
|
|
271
|
+
setParts.add(quoted + " = " + placeholder);
|
|
272
|
+
values.add(operations.get("set"));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
setParts.add(quoted + " = " + placeholder);
|
|
277
|
+
values.add(value);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Quotes a possibly schema-qualified table name.
|
|
282
|
+
*
|
|
283
|
+
* <p>An already-bracketed name is never returned as-is: that quoting is only correct on
|
|
284
|
+
* MSSQL, so the brackets are stripped and re-applied for the target dialect instead of
|
|
285
|
+
* letting one dialect's quoting follow the name into another.
|
|
286
|
+
*/
|
|
287
|
+
public static String quoteTable(String table, Dialect dialect) {
|
|
288
|
+
List<String> parts = splitQualified(table);
|
|
289
|
+
if (dialect == Dialect.SQLITE
|
|
290
|
+
&& parts.size() == 2
|
|
291
|
+
&& unquote(parts.get(0)).toLowerCase(Locale.ROOT).equals("dbo")) {
|
|
292
|
+
parts = parts.subList(1, parts.size());
|
|
293
|
+
}
|
|
294
|
+
if (parts.isEmpty()) {
|
|
295
|
+
parts = new ArrayList<String>();
|
|
296
|
+
parts.add(table == null ? "" : table);
|
|
297
|
+
}
|
|
298
|
+
List<String> quoted = new ArrayList<String>();
|
|
299
|
+
for (String part : parts) {
|
|
300
|
+
quoted.add(dialect.quoteIdentifier(part));
|
|
301
|
+
}
|
|
302
|
+
return join(quoted, ".");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Splits {@code schema.table} without cutting a dot inside brackets or quotes.
|
|
307
|
+
*
|
|
308
|
+
* <p>A plain split on {@code "."} turns {@code [dbo].[users]} into two bracketed parts
|
|
309
|
+
* only for MSSQL; the other dialects need the brackets removed before re-quoting.
|
|
310
|
+
*/
|
|
311
|
+
private static List<String> splitQualified(String table) {
|
|
312
|
+
List<String> parts = new ArrayList<String>();
|
|
313
|
+
StringBuilder buffer = new StringBuilder();
|
|
314
|
+
char open = 0;
|
|
315
|
+
char close = 0;
|
|
316
|
+
for (int i = 0; i < table.length(); i++) {
|
|
317
|
+
char ch = table.charAt(i);
|
|
318
|
+
if (open != 0) {
|
|
319
|
+
buffer.append(ch);
|
|
320
|
+
if (ch == close) {
|
|
321
|
+
open = 0;
|
|
322
|
+
close = 0;
|
|
323
|
+
}
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (ch == '[' || ch == '"') {
|
|
327
|
+
open = ch;
|
|
328
|
+
close = ch == '[' ? ']' : '"';
|
|
329
|
+
buffer.append(ch);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (ch == '.') {
|
|
333
|
+
parts.add(buffer.toString());
|
|
334
|
+
buffer.setLength(0);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
buffer.append(ch);
|
|
338
|
+
}
|
|
339
|
+
parts.add(buffer.toString());
|
|
340
|
+
|
|
341
|
+
List<String> kept = new ArrayList<String>();
|
|
342
|
+
for (String part : parts) {
|
|
343
|
+
if (!part.isEmpty()) {
|
|
344
|
+
kept.add(part);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return kept;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private static String unquote(String name) {
|
|
351
|
+
String value = name;
|
|
352
|
+
if (value.length() >= 2 && value.startsWith("[") && value.endsWith("]")) {
|
|
353
|
+
value = value.substring(1, value.length() - 1);
|
|
354
|
+
}
|
|
355
|
+
if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
|
|
356
|
+
value = value.substring(1, value.length() - 1);
|
|
357
|
+
}
|
|
358
|
+
return value;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** A bind parameter name that is a legal identifier in every dialect. */
|
|
362
|
+
public static String sanitizeParamName(String name) {
|
|
363
|
+
StringBuilder cleaned = new StringBuilder();
|
|
364
|
+
for (int i = 0; i < name.length(); i++) {
|
|
365
|
+
char ch = name.charAt(i);
|
|
366
|
+
cleaned.append(Character.isLetterOrDigit(ch) || ch == '_' ? ch : '_');
|
|
367
|
+
}
|
|
368
|
+
String value = cleaned.toString();
|
|
369
|
+
if (value.isEmpty() || !Character.isLetter(value.charAt(0))) {
|
|
370
|
+
return "p_" + value;
|
|
371
|
+
}
|
|
372
|
+
return value;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private static boolean isOperatorValue(Object value) {
|
|
376
|
+
if (!(value instanceof Map)) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
for (Object key : ((Map<?, ?>) value).keySet()) {
|
|
380
|
+
if (OPERATOR_KEYS.contains(String.valueOf(key))) {
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private static Map<String, Object> single(String key, Object value) {
|
|
388
|
+
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
|
389
|
+
map.put(key, value);
|
|
390
|
+
return map;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private static Map<String, ?> asMap(Object value) {
|
|
394
|
+
if (value instanceof Map) {
|
|
395
|
+
@SuppressWarnings("unchecked")
|
|
396
|
+
Map<String, Object> cast = (Map<String, Object>) value;
|
|
397
|
+
return cast;
|
|
398
|
+
}
|
|
399
|
+
return new LinkedHashMap<String, Object>();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private static List<Object> one(Object value) {
|
|
403
|
+
List<Object> list = new ArrayList<Object>();
|
|
404
|
+
list.add(value);
|
|
405
|
+
return list;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private static String orEmpty(String sql) {
|
|
409
|
+
return sql == null || sql.isEmpty() ? "1=1" : sql;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private static String join(List<String> parts, String separator) {
|
|
413
|
+
StringBuilder out = new StringBuilder();
|
|
414
|
+
for (int i = 0; i < parts.size(); i++) {
|
|
415
|
+
if (i > 0) {
|
|
416
|
+
out.append(separator);
|
|
417
|
+
}
|
|
418
|
+
out.append(parts.get(i));
|
|
419
|
+
}
|
|
420
|
+
return out.toString();
|
|
421
|
+
}
|
|
422
|
+
}
|