@leonardovida-md/drizzle-neo-duckdb 1.2.0 → 1.2.2
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 +2 -1
- package/dist/duckdb-introspect.mjs +562 -73
- package/dist/index.mjs +554 -65
- package/dist/sql/ast-transformer.d.ts +18 -0
- package/dist/sql/visitors/column-qualifier.d.ts +5 -0
- package/dist/sql/visitors/generate-series-alias.d.ts +13 -0
- package/dist/sql/visitors/union-with-hoister.d.ts +11 -0
- package/package.json +2 -2
- package/src/sql/ast-transformer.ts +120 -18
- package/src/sql/visitors/column-qualifier.ts +393 -85
- package/src/sql/visitors/generate-series-alias.ts +291 -0
- package/src/sql/visitors/union-with-hoister.ts +106 -0
package/dist/index.mjs
CHANGED
|
@@ -1065,22 +1065,30 @@ function getTableSource(from) {
|
|
|
1065
1065
|
if ("table" in from && from.table) {
|
|
1066
1066
|
return {
|
|
1067
1067
|
name: from.table,
|
|
1068
|
-
alias: from.as ?? null
|
|
1068
|
+
alias: from.as ?? null,
|
|
1069
|
+
schema: "db" in from ? from.db ?? null : null
|
|
1069
1070
|
};
|
|
1070
1071
|
}
|
|
1071
1072
|
if ("expr" in from && from.as) {
|
|
1072
1073
|
return {
|
|
1073
1074
|
name: from.as,
|
|
1074
|
-
alias: from.as
|
|
1075
|
+
alias: from.as,
|
|
1076
|
+
schema: null
|
|
1075
1077
|
};
|
|
1076
1078
|
}
|
|
1077
1079
|
return null;
|
|
1078
1080
|
}
|
|
1079
1081
|
function getQualifier(source) {
|
|
1080
|
-
return
|
|
1082
|
+
return {
|
|
1083
|
+
table: source.alias ?? source.name,
|
|
1084
|
+
schema: source.schema
|
|
1085
|
+
};
|
|
1081
1086
|
}
|
|
1082
1087
|
function isUnqualifiedColumnRef(expr) {
|
|
1083
|
-
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && !(
|
|
1088
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && (!("table" in expr) || !expr.table);
|
|
1089
|
+
}
|
|
1090
|
+
function isQualifiedColumnRef(expr) {
|
|
1091
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && "table" in expr && !!expr.table;
|
|
1084
1092
|
}
|
|
1085
1093
|
function getColumnName(col) {
|
|
1086
1094
|
if (typeof col.column === "string") {
|
|
@@ -1091,29 +1099,80 @@ function getColumnName(col) {
|
|
|
1091
1099
|
}
|
|
1092
1100
|
return null;
|
|
1093
1101
|
}
|
|
1094
|
-
function
|
|
1102
|
+
function applyQualifier(col, qualifier) {
|
|
1103
|
+
col.table = qualifier.table;
|
|
1104
|
+
if (!("schema" in col) || !col.schema) {
|
|
1105
|
+
col.schema = qualifier.schema;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
function unwrapColumnRef(expr) {
|
|
1109
|
+
if (!expr || typeof expr !== "object")
|
|
1110
|
+
return null;
|
|
1111
|
+
if ("type" in expr && expr.type === "column_ref") {
|
|
1112
|
+
return expr;
|
|
1113
|
+
}
|
|
1114
|
+
if ("expr" in expr && expr.expr) {
|
|
1115
|
+
return unwrapColumnRef(expr.expr);
|
|
1116
|
+
}
|
|
1117
|
+
if ("ast" in expr && expr.ast && typeof expr.ast === "object") {
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
if ("args" in expr && expr.args) {
|
|
1121
|
+
const args = expr.args;
|
|
1122
|
+
if (args.expr) {
|
|
1123
|
+
return unwrapColumnRef(args.expr);
|
|
1124
|
+
}
|
|
1125
|
+
if (args.value && args.value.length === 1) {
|
|
1126
|
+
return unwrapColumnRef(args.value[0]);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
function isBinaryExpr(expr) {
|
|
1132
|
+
return !!expr && typeof expr === "object" && "type" in expr && expr.type === "binary_expr";
|
|
1133
|
+
}
|
|
1134
|
+
function walkOnClause(expr, leftQualifier, rightQualifier, ambiguousColumns) {
|
|
1095
1135
|
if (!expr || typeof expr !== "object")
|
|
1096
1136
|
return false;
|
|
1097
1137
|
let transformed = false;
|
|
1098
|
-
if (expr
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1138
|
+
if (isBinaryExpr(expr)) {
|
|
1139
|
+
const left = expr.left;
|
|
1140
|
+
const right = expr.right;
|
|
1141
|
+
const leftCol = unwrapColumnRef(left);
|
|
1142
|
+
const rightCol = unwrapColumnRef(right);
|
|
1143
|
+
const leftUnqualified = leftCol ? isUnqualifiedColumnRef(leftCol) : false;
|
|
1144
|
+
const rightUnqualified = rightCol ? isUnqualifiedColumnRef(rightCol) : false;
|
|
1145
|
+
const leftQualified = leftCol ? isQualifiedColumnRef(leftCol) : false;
|
|
1146
|
+
const rightQualified = rightCol ? isQualifiedColumnRef(rightCol) : false;
|
|
1147
|
+
const leftColName = leftCol ? getColumnName(leftCol) : null;
|
|
1148
|
+
const rightColName = rightCol ? getColumnName(rightCol) : null;
|
|
1149
|
+
if (expr.operator === "=" && leftColName && rightColName && leftColName === rightColName) {
|
|
1150
|
+
if (leftUnqualified && rightUnqualified) {
|
|
1151
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1152
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1153
|
+
ambiguousColumns.add(leftColName);
|
|
1154
|
+
transformed = true;
|
|
1155
|
+
} else if (leftQualified && rightUnqualified) {
|
|
1156
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1157
|
+
ambiguousColumns.add(rightColName);
|
|
1158
|
+
transformed = true;
|
|
1159
|
+
} else if (leftUnqualified && rightQualified) {
|
|
1160
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1161
|
+
ambiguousColumns.add(leftColName);
|
|
1162
|
+
transformed = true;
|
|
1111
1163
|
}
|
|
1112
1164
|
}
|
|
1113
|
-
if (expr.operator === "
|
|
1114
|
-
|
|
1115
|
-
|
|
1165
|
+
if (expr.operator === "=" && leftCol && rightCol && leftColName && rightColName && leftColName !== rightColName) {
|
|
1166
|
+
if (leftQualified && rightUnqualified && !rightColName.includes(".")) {
|
|
1167
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1168
|
+
transformed = true;
|
|
1169
|
+
} else if (leftUnqualified && rightQualified && !leftColName.includes(".")) {
|
|
1170
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1171
|
+
transformed = true;
|
|
1172
|
+
}
|
|
1116
1173
|
}
|
|
1174
|
+
transformed = walkOnClause(isBinaryExpr(expr.left) ? expr.left : expr.left, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1175
|
+
transformed = walkOnClause(isBinaryExpr(expr.right) ? expr.right : expr.right, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1117
1176
|
}
|
|
1118
1177
|
return transformed;
|
|
1119
1178
|
}
|
|
@@ -1124,12 +1183,12 @@ function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns)
|
|
|
1124
1183
|
if (isUnqualifiedColumnRef(expr)) {
|
|
1125
1184
|
const colName = getColumnName(expr);
|
|
1126
1185
|
if (colName && ambiguousColumns.has(colName)) {
|
|
1127
|
-
expr
|
|
1186
|
+
applyQualifier(expr, defaultQualifier);
|
|
1128
1187
|
transformed = true;
|
|
1129
1188
|
}
|
|
1130
1189
|
return transformed;
|
|
1131
1190
|
}
|
|
1132
|
-
if (
|
|
1191
|
+
if (isBinaryExpr(expr)) {
|
|
1133
1192
|
const binary = expr;
|
|
1134
1193
|
transformed = qualifyAmbiguousInExpression(binary.left, defaultQualifier, ambiguousColumns) || transformed;
|
|
1135
1194
|
transformed = qualifyAmbiguousInExpression(binary.right, defaultQualifier, ambiguousColumns) || transformed;
|
|
@@ -1146,48 +1205,117 @@ function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns)
|
|
|
1146
1205
|
transformed = qualifyAmbiguousInExpression(args.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1147
1206
|
}
|
|
1148
1207
|
}
|
|
1208
|
+
if ("over" in expr && expr.over && typeof expr.over === "object") {
|
|
1209
|
+
const over = expr.over;
|
|
1210
|
+
if (Array.isArray(over.partition)) {
|
|
1211
|
+
for (const part of over.partition) {
|
|
1212
|
+
transformed = qualifyAmbiguousInExpression(part, defaultQualifier, ambiguousColumns) || transformed;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (Array.isArray(over.orderby)) {
|
|
1216
|
+
for (const order of over.orderby) {
|
|
1217
|
+
transformed = qualifyAmbiguousInExpression(order, defaultQualifier, ambiguousColumns) || transformed;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1149
1221
|
return transformed;
|
|
1150
1222
|
}
|
|
1223
|
+
function hasUnqualifiedColumns(expr) {
|
|
1224
|
+
if (!expr || typeof expr !== "object")
|
|
1225
|
+
return false;
|
|
1226
|
+
if ("type" in expr && expr.type === "binary_expr") {
|
|
1227
|
+
const left = expr.left;
|
|
1228
|
+
const right = expr.right;
|
|
1229
|
+
const leftCol = unwrapColumnRef(left);
|
|
1230
|
+
const rightCol = unwrapColumnRef(right);
|
|
1231
|
+
if (isUnqualifiedColumnRef(left) || isUnqualifiedColumnRef(right) || leftCol && isUnqualifiedColumnRef(leftCol) || rightCol && isUnqualifiedColumnRef(rightCol)) {
|
|
1232
|
+
return true;
|
|
1233
|
+
}
|
|
1234
|
+
if (isBinaryExpr(expr.left) && hasUnqualifiedColumns(expr.left))
|
|
1235
|
+
return true;
|
|
1236
|
+
if (isBinaryExpr(expr.right) && hasUnqualifiedColumns(expr.right))
|
|
1237
|
+
return true;
|
|
1238
|
+
}
|
|
1239
|
+
if ("args" in expr && expr.args) {
|
|
1240
|
+
const args = expr.args;
|
|
1241
|
+
if (args.expr && isUnqualifiedColumnRef(args.expr))
|
|
1242
|
+
return true;
|
|
1243
|
+
if (args.value) {
|
|
1244
|
+
for (const arg of args.value) {
|
|
1245
|
+
if (isUnqualifiedColumnRef(arg))
|
|
1246
|
+
return true;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1151
1252
|
function walkSelect(select) {
|
|
1152
1253
|
let transformed = false;
|
|
1153
1254
|
const ambiguousColumns = new Set;
|
|
1154
1255
|
if (Array.isArray(select.from) && select.from.length >= 2) {
|
|
1155
1256
|
const firstSource = getTableSource(select.from[0]);
|
|
1156
|
-
const defaultQualifier = firstSource ? getQualifier(firstSource) :
|
|
1257
|
+
const defaultQualifier = firstSource ? getQualifier(firstSource) : null;
|
|
1157
1258
|
let prevSource = firstSource;
|
|
1259
|
+
let hasAnyUnqualified = false;
|
|
1158
1260
|
for (const from of select.from) {
|
|
1159
1261
|
if ("join" in from) {
|
|
1160
1262
|
const join = from;
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
const rightQualifier = getQualifier(currentSource);
|
|
1165
|
-
transformed = walkOnClause(join.on, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1166
|
-
}
|
|
1167
|
-
prevSource = currentSource;
|
|
1168
|
-
} else {
|
|
1169
|
-
const source = getTableSource(from);
|
|
1170
|
-
if (source) {
|
|
1171
|
-
prevSource = source;
|
|
1263
|
+
if (join.on && hasUnqualifiedColumns(join.on)) {
|
|
1264
|
+
hasAnyUnqualified = true;
|
|
1265
|
+
break;
|
|
1172
1266
|
}
|
|
1173
1267
|
}
|
|
1174
|
-
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1175
|
-
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1176
|
-
}
|
|
1177
1268
|
}
|
|
1178
|
-
if (
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1269
|
+
if (!hasAnyUnqualified) {
|
|
1270
|
+
for (const from of select.from) {
|
|
1271
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1272
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
} else {
|
|
1276
|
+
for (const from of select.from) {
|
|
1277
|
+
if ("join" in from) {
|
|
1278
|
+
const join = from;
|
|
1279
|
+
const currentSource = getTableSource(join);
|
|
1280
|
+
if (join.on && prevSource && currentSource) {
|
|
1281
|
+
const leftQualifier = getQualifier(prevSource);
|
|
1282
|
+
const rightQualifier = getQualifier(currentSource);
|
|
1283
|
+
transformed = walkOnClause(join.on, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1284
|
+
}
|
|
1285
|
+
if (join.using && prevSource && currentSource) {
|
|
1286
|
+
for (const usingCol of join.using) {
|
|
1287
|
+
if (typeof usingCol === "string") {
|
|
1288
|
+
ambiguousColumns.add(usingCol);
|
|
1289
|
+
} else if ("value" in usingCol) {
|
|
1290
|
+
ambiguousColumns.add(String(usingCol.value));
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
prevSource = currentSource;
|
|
1295
|
+
} else {
|
|
1296
|
+
const source = getTableSource(from);
|
|
1297
|
+
if (source) {
|
|
1298
|
+
prevSource = source;
|
|
1183
1299
|
}
|
|
1184
1300
|
}
|
|
1301
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1302
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1303
|
+
}
|
|
1185
1304
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1305
|
+
if (ambiguousColumns.size > 0 && defaultQualifier) {
|
|
1306
|
+
if (Array.isArray(select.columns)) {
|
|
1307
|
+
for (const col of select.columns) {
|
|
1308
|
+
if ("expr" in col) {
|
|
1309
|
+
transformed = qualifyAmbiguousInExpression(col.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
transformed = qualifyAmbiguousInExpression(select.where, defaultQualifier, ambiguousColumns) || transformed;
|
|
1314
|
+
if (Array.isArray(select.orderby)) {
|
|
1315
|
+
for (const order of select.orderby) {
|
|
1316
|
+
if (order.expr) {
|
|
1317
|
+
transformed = qualifyAmbiguousInExpression(order.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1318
|
+
}
|
|
1191
1319
|
}
|
|
1192
1320
|
}
|
|
1193
1321
|
}
|
|
@@ -1212,6 +1340,323 @@ function qualifyJoinColumns(ast) {
|
|
|
1212
1340
|
for (const stmt of statements) {
|
|
1213
1341
|
if (stmt.type === "select") {
|
|
1214
1342
|
transformed = walkSelect(stmt) || transformed;
|
|
1343
|
+
} else if (stmt.type === "insert") {
|
|
1344
|
+
const insert = stmt;
|
|
1345
|
+
if (insert.values && typeof insert.values === "object" && "type" in insert.values && insert.values.type === "select") {
|
|
1346
|
+
transformed = walkSelect(insert.values) || transformed;
|
|
1347
|
+
}
|
|
1348
|
+
} else if (stmt.type === "update") {
|
|
1349
|
+
const update = stmt;
|
|
1350
|
+
const mainSource = update.table?.[0] ? getTableSource(update.table[0]) : null;
|
|
1351
|
+
const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
|
|
1352
|
+
const fromSources = update.from ?? [];
|
|
1353
|
+
const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
|
|
1354
|
+
if (update.where && defaultQualifier && firstFrom) {
|
|
1355
|
+
const ambiguous = new Set;
|
|
1356
|
+
transformed = walkOnClause(update.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
|
|
1357
|
+
transformed = qualifyAmbiguousInExpression(update.where, defaultQualifier, ambiguous) || transformed;
|
|
1358
|
+
}
|
|
1359
|
+
if (Array.isArray(update.returning) && defaultQualifier) {
|
|
1360
|
+
for (const ret of update.returning) {
|
|
1361
|
+
transformed = qualifyAmbiguousInExpression(ret, defaultQualifier, new Set) || transformed;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
} else if (stmt.type === "delete") {
|
|
1365
|
+
const del = stmt;
|
|
1366
|
+
const mainSource = del.table?.[0] ? getTableSource(del.table[0]) : null;
|
|
1367
|
+
const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
|
|
1368
|
+
const fromSources = del.from ?? [];
|
|
1369
|
+
const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
|
|
1370
|
+
if (del.where && defaultQualifier && firstFrom) {
|
|
1371
|
+
const ambiguous = new Set;
|
|
1372
|
+
transformed = walkOnClause(del.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
|
|
1373
|
+
transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, ambiguous) || transformed;
|
|
1374
|
+
} else if (del.where && defaultQualifier) {
|
|
1375
|
+
transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, new Set) || transformed;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
return transformed;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// src/sql/visitors/generate-series-alias.ts
|
|
1383
|
+
function getColumnName2(col) {
|
|
1384
|
+
if (typeof col.column === "string") {
|
|
1385
|
+
return col.column;
|
|
1386
|
+
}
|
|
1387
|
+
if (col.column && "expr" in col.column && col.column.expr?.value) {
|
|
1388
|
+
return String(col.column.expr.value);
|
|
1389
|
+
}
|
|
1390
|
+
return null;
|
|
1391
|
+
}
|
|
1392
|
+
function isColumnRef(expr) {
|
|
1393
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref";
|
|
1394
|
+
}
|
|
1395
|
+
function isBinaryExpr2(expr) {
|
|
1396
|
+
return !!expr && typeof expr === "object" && "type" in expr && expr.type === "binary_expr";
|
|
1397
|
+
}
|
|
1398
|
+
function getGenerateSeriesAliases(from) {
|
|
1399
|
+
const aliases = new Set;
|
|
1400
|
+
if (!from || !Array.isArray(from))
|
|
1401
|
+
return aliases;
|
|
1402
|
+
for (const f of from) {
|
|
1403
|
+
if ("expr" in f && f.expr && typeof f.expr === "object") {
|
|
1404
|
+
const exprObj = f.expr;
|
|
1405
|
+
if (exprObj.type === "function" && "name" in exprObj) {
|
|
1406
|
+
const nameObj = exprObj.name;
|
|
1407
|
+
const nameParts = nameObj?.name;
|
|
1408
|
+
const fnName = nameParts?.[0]?.value;
|
|
1409
|
+
if (typeof fnName === "string" && fnName.toLowerCase() === "generate_series") {
|
|
1410
|
+
const alias = typeof f.as === "string" ? f.as : null;
|
|
1411
|
+
if (alias && !alias.includes("(")) {
|
|
1412
|
+
aliases.add(alias);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return aliases;
|
|
1419
|
+
}
|
|
1420
|
+
function rewriteAliasColumnRef(col, alias) {
|
|
1421
|
+
col.table = alias;
|
|
1422
|
+
col.column = { expr: { type: "default", value: "generate_series" } };
|
|
1423
|
+
}
|
|
1424
|
+
function walkExpression2(expr, aliases) {
|
|
1425
|
+
if (!expr || typeof expr !== "object")
|
|
1426
|
+
return false;
|
|
1427
|
+
let transformed = false;
|
|
1428
|
+
const exprObj = expr;
|
|
1429
|
+
if (isColumnRef(expr)) {
|
|
1430
|
+
if (!("table" in expr) || !expr.table) {
|
|
1431
|
+
const colName = getColumnName2(expr);
|
|
1432
|
+
if (colName && aliases.has(colName)) {
|
|
1433
|
+
rewriteAliasColumnRef(expr, colName);
|
|
1434
|
+
transformed = true;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return transformed;
|
|
1438
|
+
}
|
|
1439
|
+
if (isBinaryExpr2(expr)) {
|
|
1440
|
+
const binary = expr;
|
|
1441
|
+
transformed = walkExpression2(binary.left, aliases) || transformed;
|
|
1442
|
+
transformed = walkExpression2(binary.right, aliases) || transformed;
|
|
1443
|
+
return transformed;
|
|
1444
|
+
}
|
|
1445
|
+
if (exprObj.type === "unary_expr" && exprObj.expr) {
|
|
1446
|
+
transformed = walkExpression2(exprObj.expr, aliases) || transformed;
|
|
1447
|
+
}
|
|
1448
|
+
if (exprObj.type === "cast" && exprObj.expr) {
|
|
1449
|
+
transformed = walkExpression2(exprObj.expr, aliases) || transformed;
|
|
1450
|
+
}
|
|
1451
|
+
if (exprObj.type === "case") {
|
|
1452
|
+
if (exprObj.expr) {
|
|
1453
|
+
transformed = walkExpression2(exprObj.expr, aliases) || transformed;
|
|
1454
|
+
}
|
|
1455
|
+
if (Array.isArray(exprObj.args)) {
|
|
1456
|
+
for (const whenClause of exprObj.args) {
|
|
1457
|
+
if (whenClause.cond) {
|
|
1458
|
+
transformed = walkExpression2(whenClause.cond, aliases) || transformed;
|
|
1459
|
+
}
|
|
1460
|
+
if (whenClause.result) {
|
|
1461
|
+
transformed = walkExpression2(whenClause.result, aliases) || transformed;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if ("args" in exprObj && exprObj.args) {
|
|
1467
|
+
const args = exprObj.args;
|
|
1468
|
+
if (Array.isArray(args.value)) {
|
|
1469
|
+
for (const arg of args.value) {
|
|
1470
|
+
transformed = walkExpression2(arg, aliases) || transformed;
|
|
1471
|
+
}
|
|
1472
|
+
} else if (args.expr) {
|
|
1473
|
+
transformed = walkExpression2(args.expr, aliases) || transformed;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if ("over" in exprObj && exprObj.over && typeof exprObj.over === "object") {
|
|
1477
|
+
const over = exprObj.over;
|
|
1478
|
+
if (Array.isArray(over.partition)) {
|
|
1479
|
+
for (const part of over.partition) {
|
|
1480
|
+
transformed = walkExpression2(part, aliases) || transformed;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
if (Array.isArray(over.orderby)) {
|
|
1484
|
+
for (const order of over.orderby) {
|
|
1485
|
+
transformed = walkExpression2(order, aliases) || transformed;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
if ("ast" in exprObj && exprObj.ast) {
|
|
1490
|
+
const subAst = exprObj.ast;
|
|
1491
|
+
if (subAst.type === "select") {
|
|
1492
|
+
transformed = walkSelect2(subAst) || transformed;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
if (exprObj.type === "expr_list" && Array.isArray(exprObj.value)) {
|
|
1496
|
+
for (const item of exprObj.value) {
|
|
1497
|
+
transformed = walkExpression2(item, aliases) || transformed;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return transformed;
|
|
1501
|
+
}
|
|
1502
|
+
function walkFrom2(from, aliases) {
|
|
1503
|
+
if (!from || !Array.isArray(from))
|
|
1504
|
+
return false;
|
|
1505
|
+
let transformed = false;
|
|
1506
|
+
for (const f of from) {
|
|
1507
|
+
if ("join" in f) {
|
|
1508
|
+
const join = f;
|
|
1509
|
+
transformed = walkExpression2(join.on, aliases) || transformed;
|
|
1510
|
+
}
|
|
1511
|
+
if ("expr" in f && f.expr && "ast" in f.expr) {
|
|
1512
|
+
transformed = walkSelect2(f.expr.ast) || transformed;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return transformed;
|
|
1516
|
+
}
|
|
1517
|
+
function walkSelect2(select) {
|
|
1518
|
+
let transformed = false;
|
|
1519
|
+
const aliases = getGenerateSeriesAliases(select.from);
|
|
1520
|
+
if (select.with) {
|
|
1521
|
+
for (const cte of select.with) {
|
|
1522
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1523
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1524
|
+
transformed = walkSelect2(cteSelect) || transformed;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
transformed = walkFrom2(select.from, aliases) || transformed;
|
|
1529
|
+
transformed = walkExpression2(select.where, aliases) || transformed;
|
|
1530
|
+
if (select.having) {
|
|
1531
|
+
if (Array.isArray(select.having)) {
|
|
1532
|
+
for (const h of select.having) {
|
|
1533
|
+
transformed = walkExpression2(h, aliases) || transformed;
|
|
1534
|
+
}
|
|
1535
|
+
} else {
|
|
1536
|
+
transformed = walkExpression2(select.having, aliases) || transformed;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (Array.isArray(select.columns)) {
|
|
1540
|
+
for (const col of select.columns) {
|
|
1541
|
+
if ("expr" in col) {
|
|
1542
|
+
transformed = walkExpression2(col.expr, aliases) || transformed;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (Array.isArray(select.groupby)) {
|
|
1547
|
+
for (const g of select.groupby) {
|
|
1548
|
+
transformed = walkExpression2(g, aliases) || transformed;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
if (Array.isArray(select.orderby)) {
|
|
1552
|
+
for (const order of select.orderby) {
|
|
1553
|
+
if (order.expr) {
|
|
1554
|
+
transformed = walkExpression2(order.expr, aliases) || transformed;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
if (select._orderby) {
|
|
1559
|
+
for (const order of select._orderby) {
|
|
1560
|
+
if (order.expr) {
|
|
1561
|
+
transformed = walkExpression2(order.expr, aliases) || transformed;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
if (select._next) {
|
|
1566
|
+
transformed = walkSelect2(select._next) || transformed;
|
|
1567
|
+
}
|
|
1568
|
+
return transformed;
|
|
1569
|
+
}
|
|
1570
|
+
function rewriteGenerateSeriesAliases(ast) {
|
|
1571
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1572
|
+
let transformed = false;
|
|
1573
|
+
for (const stmt of statements) {
|
|
1574
|
+
if (stmt.type === "select") {
|
|
1575
|
+
transformed = walkSelect2(stmt) || transformed;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return transformed;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// src/sql/visitors/union-with-hoister.ts
|
|
1582
|
+
function getCteName(cte) {
|
|
1583
|
+
const nameObj = cte.name;
|
|
1584
|
+
if (!nameObj)
|
|
1585
|
+
return null;
|
|
1586
|
+
const value = nameObj.value;
|
|
1587
|
+
if (typeof value === "string")
|
|
1588
|
+
return value;
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
function hoistWithInSelect(select) {
|
|
1592
|
+
if (!select.set_op || !select._next)
|
|
1593
|
+
return false;
|
|
1594
|
+
const arms = [];
|
|
1595
|
+
let current = select;
|
|
1596
|
+
while (current && current.type === "select") {
|
|
1597
|
+
arms.push(current);
|
|
1598
|
+
current = current._next;
|
|
1599
|
+
}
|
|
1600
|
+
const mergedWith = [];
|
|
1601
|
+
const seen = new Set;
|
|
1602
|
+
let hasWithBeyondFirst = false;
|
|
1603
|
+
for (const arm of arms) {
|
|
1604
|
+
if (arm.with && arm.with.length > 0) {
|
|
1605
|
+
if (arm !== arms[0]) {
|
|
1606
|
+
hasWithBeyondFirst = true;
|
|
1607
|
+
}
|
|
1608
|
+
for (const cte of arm.with) {
|
|
1609
|
+
const cteName = getCteName(cte);
|
|
1610
|
+
if (!cteName)
|
|
1611
|
+
return false;
|
|
1612
|
+
if (seen.has(cteName)) {
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
seen.add(cteName);
|
|
1616
|
+
mergedWith.push(cte);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
if (!hasWithBeyondFirst)
|
|
1621
|
+
return false;
|
|
1622
|
+
arms[0].with = mergedWith;
|
|
1623
|
+
if ("parentheses_symbol" in arms[0]) {
|
|
1624
|
+
arms[0].parentheses_symbol = false;
|
|
1625
|
+
}
|
|
1626
|
+
for (let i = 1;i < arms.length; i++) {
|
|
1627
|
+
arms[i].with = null;
|
|
1628
|
+
}
|
|
1629
|
+
return true;
|
|
1630
|
+
}
|
|
1631
|
+
function walkSelect3(select) {
|
|
1632
|
+
let transformed = false;
|
|
1633
|
+
if (select.with) {
|
|
1634
|
+
for (const cte of select.with) {
|
|
1635
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1636
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1637
|
+
transformed = walkSelect3(cteSelect) || transformed;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (Array.isArray(select.from)) {
|
|
1642
|
+
for (const from of select.from) {
|
|
1643
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1644
|
+
transformed = walkSelect3(from.expr.ast) || transformed;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
transformed = hoistWithInSelect(select) || transformed;
|
|
1649
|
+
if (select._next) {
|
|
1650
|
+
transformed = walkSelect3(select._next) || transformed;
|
|
1651
|
+
}
|
|
1652
|
+
return transformed;
|
|
1653
|
+
}
|
|
1654
|
+
function hoistUnionWith(ast) {
|
|
1655
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1656
|
+
let transformed = false;
|
|
1657
|
+
for (const stmt of statements) {
|
|
1658
|
+
if (stmt.type === "select") {
|
|
1659
|
+
transformed = walkSelect3(stmt) || transformed;
|
|
1215
1660
|
}
|
|
1216
1661
|
}
|
|
1217
1662
|
return transformed;
|
|
@@ -1220,29 +1665,73 @@ function qualifyJoinColumns(ast) {
|
|
|
1220
1665
|
// src/sql/ast-transformer.ts
|
|
1221
1666
|
var { Parser } = nodeSqlParser;
|
|
1222
1667
|
var parser = new Parser;
|
|
1668
|
+
var CACHE_SIZE = 500;
|
|
1669
|
+
var transformCache = new Map;
|
|
1670
|
+
function getCachedOrTransform(query, transform) {
|
|
1671
|
+
const cached = transformCache.get(query);
|
|
1672
|
+
if (cached) {
|
|
1673
|
+
transformCache.delete(query);
|
|
1674
|
+
transformCache.set(query, cached);
|
|
1675
|
+
return cached;
|
|
1676
|
+
}
|
|
1677
|
+
const result = transform();
|
|
1678
|
+
if (transformCache.size >= CACHE_SIZE) {
|
|
1679
|
+
const oldestKey = transformCache.keys().next().value;
|
|
1680
|
+
if (oldestKey) {
|
|
1681
|
+
transformCache.delete(oldestKey);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
transformCache.set(query, result);
|
|
1685
|
+
return result;
|
|
1686
|
+
}
|
|
1687
|
+
var DEBUG_ENV = "DRIZZLE_DUCKDB_DEBUG_AST";
|
|
1688
|
+
function hasJoin(query) {
|
|
1689
|
+
return /\bjoin\b/i.test(query);
|
|
1690
|
+
}
|
|
1691
|
+
function debugLog(message, payload) {
|
|
1692
|
+
if (process?.env?.[DEBUG_ENV]) {
|
|
1693
|
+
console.debug("[duckdb-ast]", message, payload ?? "");
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1223
1696
|
function transformSQL(query) {
|
|
1224
1697
|
const needsArrayTransform = query.includes("@>") || query.includes("<@") || query.includes("&&");
|
|
1225
|
-
const needsJoinTransform = query.
|
|
1226
|
-
|
|
1698
|
+
const needsJoinTransform = hasJoin(query) || /\bupdate\b/i.test(query) || /\bdelete\b/i.test(query);
|
|
1699
|
+
const needsUnionTransform = /\bunion\b/i.test(query) || /\bintersect\b/i.test(query) || /\bexcept\b/i.test(query);
|
|
1700
|
+
const needsGenerateSeriesTransform = /\bgenerate_series\b/i.test(query);
|
|
1701
|
+
if (!needsArrayTransform && !needsJoinTransform && !needsUnionTransform && !needsGenerateSeriesTransform) {
|
|
1227
1702
|
return { sql: query, transformed: false };
|
|
1228
1703
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1704
|
+
return getCachedOrTransform(query, () => {
|
|
1705
|
+
try {
|
|
1706
|
+
const ast = parser.astify(query, { database: "PostgreSQL" });
|
|
1707
|
+
let transformed = false;
|
|
1708
|
+
if (needsArrayTransform) {
|
|
1709
|
+
transformed = transformArrayOperators(ast) || transformed;
|
|
1710
|
+
}
|
|
1711
|
+
if (needsJoinTransform) {
|
|
1712
|
+
transformed = qualifyJoinColumns(ast) || transformed;
|
|
1713
|
+
}
|
|
1714
|
+
if (needsGenerateSeriesTransform) {
|
|
1715
|
+
transformed = rewriteGenerateSeriesAliases(ast) || transformed;
|
|
1716
|
+
}
|
|
1717
|
+
if (needsUnionTransform) {
|
|
1718
|
+
transformed = hoistUnionWith(ast) || transformed;
|
|
1719
|
+
}
|
|
1720
|
+
if (!transformed) {
|
|
1721
|
+
debugLog("AST parsed but no transformation applied", {
|
|
1722
|
+
join: needsJoinTransform
|
|
1723
|
+
});
|
|
1724
|
+
return { sql: query, transformed: false };
|
|
1725
|
+
}
|
|
1726
|
+
const transformedSql = parser.sqlify(ast, { database: "PostgreSQL" });
|
|
1727
|
+
return { sql: transformedSql, transformed: true };
|
|
1728
|
+
} catch (err) {
|
|
1729
|
+
debugLog("AST transform failed; returning original SQL", {
|
|
1730
|
+
error: err.message
|
|
1731
|
+
});
|
|
1239
1732
|
return { sql: query, transformed: false };
|
|
1240
1733
|
}
|
|
1241
|
-
|
|
1242
|
-
return { sql: transformedSql, transformed: true };
|
|
1243
|
-
} catch {
|
|
1244
|
-
return { sql: query, transformed: false };
|
|
1245
|
-
}
|
|
1734
|
+
});
|
|
1246
1735
|
}
|
|
1247
1736
|
|
|
1248
1737
|
// src/dialect.ts
|