@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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { DuckDBInstance as DuckDBInstance3 } from "@duckdb/node-api";
|
|
5
5
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import
|
|
7
|
+
import process2 from "node:process";
|
|
8
8
|
|
|
9
9
|
// src/driver.ts
|
|
10
10
|
import { DuckDBInstance as DuckDBInstance2 } from "@duckdb/node-api";
|
|
@@ -1014,22 +1014,30 @@ function getTableSource(from) {
|
|
|
1014
1014
|
if ("table" in from && from.table) {
|
|
1015
1015
|
return {
|
|
1016
1016
|
name: from.table,
|
|
1017
|
-
alias: from.as ?? null
|
|
1017
|
+
alias: from.as ?? null,
|
|
1018
|
+
schema: "db" in from ? from.db ?? null : null
|
|
1018
1019
|
};
|
|
1019
1020
|
}
|
|
1020
1021
|
if ("expr" in from && from.as) {
|
|
1021
1022
|
return {
|
|
1022
1023
|
name: from.as,
|
|
1023
|
-
alias: from.as
|
|
1024
|
+
alias: from.as,
|
|
1025
|
+
schema: null
|
|
1024
1026
|
};
|
|
1025
1027
|
}
|
|
1026
1028
|
return null;
|
|
1027
1029
|
}
|
|
1028
1030
|
function getQualifier(source) {
|
|
1029
|
-
return
|
|
1031
|
+
return {
|
|
1032
|
+
table: source.alias ?? source.name,
|
|
1033
|
+
schema: source.schema
|
|
1034
|
+
};
|
|
1030
1035
|
}
|
|
1031
1036
|
function isUnqualifiedColumnRef(expr) {
|
|
1032
|
-
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && !(
|
|
1037
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && (!("table" in expr) || !expr.table);
|
|
1038
|
+
}
|
|
1039
|
+
function isQualifiedColumnRef(expr) {
|
|
1040
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref" && "table" in expr && !!expr.table;
|
|
1033
1041
|
}
|
|
1034
1042
|
function getColumnName(col) {
|
|
1035
1043
|
if (typeof col.column === "string") {
|
|
@@ -1040,29 +1048,80 @@ function getColumnName(col) {
|
|
|
1040
1048
|
}
|
|
1041
1049
|
return null;
|
|
1042
1050
|
}
|
|
1043
|
-
function
|
|
1051
|
+
function applyQualifier(col, qualifier) {
|
|
1052
|
+
col.table = qualifier.table;
|
|
1053
|
+
if (!("schema" in col) || !col.schema) {
|
|
1054
|
+
col.schema = qualifier.schema;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function unwrapColumnRef(expr) {
|
|
1058
|
+
if (!expr || typeof expr !== "object")
|
|
1059
|
+
return null;
|
|
1060
|
+
if ("type" in expr && expr.type === "column_ref") {
|
|
1061
|
+
return expr;
|
|
1062
|
+
}
|
|
1063
|
+
if ("expr" in expr && expr.expr) {
|
|
1064
|
+
return unwrapColumnRef(expr.expr);
|
|
1065
|
+
}
|
|
1066
|
+
if ("ast" in expr && expr.ast && typeof expr.ast === "object") {
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
if ("args" in expr && expr.args) {
|
|
1070
|
+
const args = expr.args;
|
|
1071
|
+
if (args.expr) {
|
|
1072
|
+
return unwrapColumnRef(args.expr);
|
|
1073
|
+
}
|
|
1074
|
+
if (args.value && args.value.length === 1) {
|
|
1075
|
+
return unwrapColumnRef(args.value[0]);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
function isBinaryExpr(expr) {
|
|
1081
|
+
return !!expr && typeof expr === "object" && "type" in expr && expr.type === "binary_expr";
|
|
1082
|
+
}
|
|
1083
|
+
function walkOnClause(expr, leftQualifier, rightQualifier, ambiguousColumns) {
|
|
1044
1084
|
if (!expr || typeof expr !== "object")
|
|
1045
1085
|
return false;
|
|
1046
1086
|
let transformed = false;
|
|
1047
|
-
if (expr
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1087
|
+
if (isBinaryExpr(expr)) {
|
|
1088
|
+
const left = expr.left;
|
|
1089
|
+
const right = expr.right;
|
|
1090
|
+
const leftCol = unwrapColumnRef(left);
|
|
1091
|
+
const rightCol = unwrapColumnRef(right);
|
|
1092
|
+
const leftUnqualified = leftCol ? isUnqualifiedColumnRef(leftCol) : false;
|
|
1093
|
+
const rightUnqualified = rightCol ? isUnqualifiedColumnRef(rightCol) : false;
|
|
1094
|
+
const leftQualified = leftCol ? isQualifiedColumnRef(leftCol) : false;
|
|
1095
|
+
const rightQualified = rightCol ? isQualifiedColumnRef(rightCol) : false;
|
|
1096
|
+
const leftColName = leftCol ? getColumnName(leftCol) : null;
|
|
1097
|
+
const rightColName = rightCol ? getColumnName(rightCol) : null;
|
|
1098
|
+
if (expr.operator === "=" && leftColName && rightColName && leftColName === rightColName) {
|
|
1099
|
+
if (leftUnqualified && rightUnqualified) {
|
|
1100
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1101
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1102
|
+
ambiguousColumns.add(leftColName);
|
|
1103
|
+
transformed = true;
|
|
1104
|
+
} else if (leftQualified && rightUnqualified) {
|
|
1105
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1106
|
+
ambiguousColumns.add(rightColName);
|
|
1107
|
+
transformed = true;
|
|
1108
|
+
} else if (leftUnqualified && rightQualified) {
|
|
1109
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1110
|
+
ambiguousColumns.add(leftColName);
|
|
1111
|
+
transformed = true;
|
|
1060
1112
|
}
|
|
1061
1113
|
}
|
|
1062
|
-
if (expr.operator === "
|
|
1063
|
-
|
|
1064
|
-
|
|
1114
|
+
if (expr.operator === "=" && leftCol && rightCol && leftColName && rightColName && leftColName !== rightColName) {
|
|
1115
|
+
if (leftQualified && rightUnqualified && !rightColName.includes(".")) {
|
|
1116
|
+
applyQualifier(rightCol, rightQualifier);
|
|
1117
|
+
transformed = true;
|
|
1118
|
+
} else if (leftUnqualified && rightQualified && !leftColName.includes(".")) {
|
|
1119
|
+
applyQualifier(leftCol, leftQualifier);
|
|
1120
|
+
transformed = true;
|
|
1121
|
+
}
|
|
1065
1122
|
}
|
|
1123
|
+
transformed = walkOnClause(isBinaryExpr(expr.left) ? expr.left : expr.left, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1124
|
+
transformed = walkOnClause(isBinaryExpr(expr.right) ? expr.right : expr.right, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1066
1125
|
}
|
|
1067
1126
|
return transformed;
|
|
1068
1127
|
}
|
|
@@ -1073,12 +1132,12 @@ function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns)
|
|
|
1073
1132
|
if (isUnqualifiedColumnRef(expr)) {
|
|
1074
1133
|
const colName = getColumnName(expr);
|
|
1075
1134
|
if (colName && ambiguousColumns.has(colName)) {
|
|
1076
|
-
expr
|
|
1135
|
+
applyQualifier(expr, defaultQualifier);
|
|
1077
1136
|
transformed = true;
|
|
1078
1137
|
}
|
|
1079
1138
|
return transformed;
|
|
1080
1139
|
}
|
|
1081
|
-
if (
|
|
1140
|
+
if (isBinaryExpr(expr)) {
|
|
1082
1141
|
const binary = expr;
|
|
1083
1142
|
transformed = qualifyAmbiguousInExpression(binary.left, defaultQualifier, ambiguousColumns) || transformed;
|
|
1084
1143
|
transformed = qualifyAmbiguousInExpression(binary.right, defaultQualifier, ambiguousColumns) || transformed;
|
|
@@ -1095,48 +1154,117 @@ function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns)
|
|
|
1095
1154
|
transformed = qualifyAmbiguousInExpression(args.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1096
1155
|
}
|
|
1097
1156
|
}
|
|
1157
|
+
if ("over" in expr && expr.over && typeof expr.over === "object") {
|
|
1158
|
+
const over = expr.over;
|
|
1159
|
+
if (Array.isArray(over.partition)) {
|
|
1160
|
+
for (const part of over.partition) {
|
|
1161
|
+
transformed = qualifyAmbiguousInExpression(part, defaultQualifier, ambiguousColumns) || transformed;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
if (Array.isArray(over.orderby)) {
|
|
1165
|
+
for (const order of over.orderby) {
|
|
1166
|
+
transformed = qualifyAmbiguousInExpression(order, defaultQualifier, ambiguousColumns) || transformed;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1098
1170
|
return transformed;
|
|
1099
1171
|
}
|
|
1172
|
+
function hasUnqualifiedColumns(expr) {
|
|
1173
|
+
if (!expr || typeof expr !== "object")
|
|
1174
|
+
return false;
|
|
1175
|
+
if ("type" in expr && expr.type === "binary_expr") {
|
|
1176
|
+
const left = expr.left;
|
|
1177
|
+
const right = expr.right;
|
|
1178
|
+
const leftCol = unwrapColumnRef(left);
|
|
1179
|
+
const rightCol = unwrapColumnRef(right);
|
|
1180
|
+
if (isUnqualifiedColumnRef(left) || isUnqualifiedColumnRef(right) || leftCol && isUnqualifiedColumnRef(leftCol) || rightCol && isUnqualifiedColumnRef(rightCol)) {
|
|
1181
|
+
return true;
|
|
1182
|
+
}
|
|
1183
|
+
if (isBinaryExpr(expr.left) && hasUnqualifiedColumns(expr.left))
|
|
1184
|
+
return true;
|
|
1185
|
+
if (isBinaryExpr(expr.right) && hasUnqualifiedColumns(expr.right))
|
|
1186
|
+
return true;
|
|
1187
|
+
}
|
|
1188
|
+
if ("args" in expr && expr.args) {
|
|
1189
|
+
const args = expr.args;
|
|
1190
|
+
if (args.expr && isUnqualifiedColumnRef(args.expr))
|
|
1191
|
+
return true;
|
|
1192
|
+
if (args.value) {
|
|
1193
|
+
for (const arg of args.value) {
|
|
1194
|
+
if (isUnqualifiedColumnRef(arg))
|
|
1195
|
+
return true;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return false;
|
|
1200
|
+
}
|
|
1100
1201
|
function walkSelect(select) {
|
|
1101
1202
|
let transformed = false;
|
|
1102
1203
|
const ambiguousColumns = new Set;
|
|
1103
1204
|
if (Array.isArray(select.from) && select.from.length >= 2) {
|
|
1104
1205
|
const firstSource = getTableSource(select.from[0]);
|
|
1105
|
-
const defaultQualifier = firstSource ? getQualifier(firstSource) :
|
|
1206
|
+
const defaultQualifier = firstSource ? getQualifier(firstSource) : null;
|
|
1106
1207
|
let prevSource = firstSource;
|
|
1208
|
+
let hasAnyUnqualified = false;
|
|
1107
1209
|
for (const from of select.from) {
|
|
1108
1210
|
if ("join" in from) {
|
|
1109
1211
|
const join = from;
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const rightQualifier = getQualifier(currentSource);
|
|
1114
|
-
transformed = walkOnClause(join.on, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1115
|
-
}
|
|
1116
|
-
prevSource = currentSource;
|
|
1117
|
-
} else {
|
|
1118
|
-
const source = getTableSource(from);
|
|
1119
|
-
if (source) {
|
|
1120
|
-
prevSource = source;
|
|
1212
|
+
if (join.on && hasUnqualifiedColumns(join.on)) {
|
|
1213
|
+
hasAnyUnqualified = true;
|
|
1214
|
+
break;
|
|
1121
1215
|
}
|
|
1122
1216
|
}
|
|
1123
|
-
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1124
|
-
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1125
|
-
}
|
|
1126
1217
|
}
|
|
1127
|
-
if (
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1218
|
+
if (!hasAnyUnqualified) {
|
|
1219
|
+
for (const from of select.from) {
|
|
1220
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1221
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
} else {
|
|
1225
|
+
for (const from of select.from) {
|
|
1226
|
+
if ("join" in from) {
|
|
1227
|
+
const join = from;
|
|
1228
|
+
const currentSource = getTableSource(join);
|
|
1229
|
+
if (join.on && prevSource && currentSource) {
|
|
1230
|
+
const leftQualifier = getQualifier(prevSource);
|
|
1231
|
+
const rightQualifier = getQualifier(currentSource);
|
|
1232
|
+
transformed = walkOnClause(join.on, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
|
|
1233
|
+
}
|
|
1234
|
+
if (join.using && prevSource && currentSource) {
|
|
1235
|
+
for (const usingCol of join.using) {
|
|
1236
|
+
if (typeof usingCol === "string") {
|
|
1237
|
+
ambiguousColumns.add(usingCol);
|
|
1238
|
+
} else if ("value" in usingCol) {
|
|
1239
|
+
ambiguousColumns.add(String(usingCol.value));
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
prevSource = currentSource;
|
|
1244
|
+
} else {
|
|
1245
|
+
const source = getTableSource(from);
|
|
1246
|
+
if (source) {
|
|
1247
|
+
prevSource = source;
|
|
1132
1248
|
}
|
|
1133
1249
|
}
|
|
1250
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1251
|
+
transformed = walkSelect(from.expr.ast) || transformed;
|
|
1252
|
+
}
|
|
1134
1253
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1254
|
+
if (ambiguousColumns.size > 0 && defaultQualifier) {
|
|
1255
|
+
if (Array.isArray(select.columns)) {
|
|
1256
|
+
for (const col of select.columns) {
|
|
1257
|
+
if ("expr" in col) {
|
|
1258
|
+
transformed = qualifyAmbiguousInExpression(col.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
transformed = qualifyAmbiguousInExpression(select.where, defaultQualifier, ambiguousColumns) || transformed;
|
|
1263
|
+
if (Array.isArray(select.orderby)) {
|
|
1264
|
+
for (const order of select.orderby) {
|
|
1265
|
+
if (order.expr) {
|
|
1266
|
+
transformed = qualifyAmbiguousInExpression(order.expr, defaultQualifier, ambiguousColumns) || transformed;
|
|
1267
|
+
}
|
|
1140
1268
|
}
|
|
1141
1269
|
}
|
|
1142
1270
|
}
|
|
@@ -1161,6 +1289,323 @@ function qualifyJoinColumns(ast) {
|
|
|
1161
1289
|
for (const stmt of statements) {
|
|
1162
1290
|
if (stmt.type === "select") {
|
|
1163
1291
|
transformed = walkSelect(stmt) || transformed;
|
|
1292
|
+
} else if (stmt.type === "insert") {
|
|
1293
|
+
const insert = stmt;
|
|
1294
|
+
if (insert.values && typeof insert.values === "object" && "type" in insert.values && insert.values.type === "select") {
|
|
1295
|
+
transformed = walkSelect(insert.values) || transformed;
|
|
1296
|
+
}
|
|
1297
|
+
} else if (stmt.type === "update") {
|
|
1298
|
+
const update = stmt;
|
|
1299
|
+
const mainSource = update.table?.[0] ? getTableSource(update.table[0]) : null;
|
|
1300
|
+
const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
|
|
1301
|
+
const fromSources = update.from ?? [];
|
|
1302
|
+
const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
|
|
1303
|
+
if (update.where && defaultQualifier && firstFrom) {
|
|
1304
|
+
const ambiguous = new Set;
|
|
1305
|
+
transformed = walkOnClause(update.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
|
|
1306
|
+
transformed = qualifyAmbiguousInExpression(update.where, defaultQualifier, ambiguous) || transformed;
|
|
1307
|
+
}
|
|
1308
|
+
if (Array.isArray(update.returning) && defaultQualifier) {
|
|
1309
|
+
for (const ret of update.returning) {
|
|
1310
|
+
transformed = qualifyAmbiguousInExpression(ret, defaultQualifier, new Set) || transformed;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
} else if (stmt.type === "delete") {
|
|
1314
|
+
const del = stmt;
|
|
1315
|
+
const mainSource = del.table?.[0] ? getTableSource(del.table[0]) : null;
|
|
1316
|
+
const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
|
|
1317
|
+
const fromSources = del.from ?? [];
|
|
1318
|
+
const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
|
|
1319
|
+
if (del.where && defaultQualifier && firstFrom) {
|
|
1320
|
+
const ambiguous = new Set;
|
|
1321
|
+
transformed = walkOnClause(del.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
|
|
1322
|
+
transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, ambiguous) || transformed;
|
|
1323
|
+
} else if (del.where && defaultQualifier) {
|
|
1324
|
+
transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, new Set) || transformed;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return transformed;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// src/sql/visitors/generate-series-alias.ts
|
|
1332
|
+
function getColumnName2(col) {
|
|
1333
|
+
if (typeof col.column === "string") {
|
|
1334
|
+
return col.column;
|
|
1335
|
+
}
|
|
1336
|
+
if (col.column && "expr" in col.column && col.column.expr?.value) {
|
|
1337
|
+
return String(col.column.expr.value);
|
|
1338
|
+
}
|
|
1339
|
+
return null;
|
|
1340
|
+
}
|
|
1341
|
+
function isColumnRef(expr) {
|
|
1342
|
+
return typeof expr === "object" && expr !== null && "type" in expr && expr.type === "column_ref";
|
|
1343
|
+
}
|
|
1344
|
+
function isBinaryExpr2(expr) {
|
|
1345
|
+
return !!expr && typeof expr === "object" && "type" in expr && expr.type === "binary_expr";
|
|
1346
|
+
}
|
|
1347
|
+
function getGenerateSeriesAliases(from) {
|
|
1348
|
+
const aliases = new Set;
|
|
1349
|
+
if (!from || !Array.isArray(from))
|
|
1350
|
+
return aliases;
|
|
1351
|
+
for (const f of from) {
|
|
1352
|
+
if ("expr" in f && f.expr && typeof f.expr === "object") {
|
|
1353
|
+
const exprObj = f.expr;
|
|
1354
|
+
if (exprObj.type === "function" && "name" in exprObj) {
|
|
1355
|
+
const nameObj = exprObj.name;
|
|
1356
|
+
const nameParts = nameObj?.name;
|
|
1357
|
+
const fnName = nameParts?.[0]?.value;
|
|
1358
|
+
if (typeof fnName === "string" && fnName.toLowerCase() === "generate_series") {
|
|
1359
|
+
const alias = typeof f.as === "string" ? f.as : null;
|
|
1360
|
+
if (alias && !alias.includes("(")) {
|
|
1361
|
+
aliases.add(alias);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return aliases;
|
|
1368
|
+
}
|
|
1369
|
+
function rewriteAliasColumnRef(col, alias) {
|
|
1370
|
+
col.table = alias;
|
|
1371
|
+
col.column = { expr: { type: "default", value: "generate_series" } };
|
|
1372
|
+
}
|
|
1373
|
+
function walkExpression2(expr, aliases) {
|
|
1374
|
+
if (!expr || typeof expr !== "object")
|
|
1375
|
+
return false;
|
|
1376
|
+
let transformed = false;
|
|
1377
|
+
const exprObj = expr;
|
|
1378
|
+
if (isColumnRef(expr)) {
|
|
1379
|
+
if (!("table" in expr) || !expr.table) {
|
|
1380
|
+
const colName = getColumnName2(expr);
|
|
1381
|
+
if (colName && aliases.has(colName)) {
|
|
1382
|
+
rewriteAliasColumnRef(expr, colName);
|
|
1383
|
+
transformed = true;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
return transformed;
|
|
1387
|
+
}
|
|
1388
|
+
if (isBinaryExpr2(expr)) {
|
|
1389
|
+
const binary = expr;
|
|
1390
|
+
transformed = walkExpression2(binary.left, aliases) || transformed;
|
|
1391
|
+
transformed = walkExpression2(binary.right, aliases) || transformed;
|
|
1392
|
+
return transformed;
|
|
1393
|
+
}
|
|
1394
|
+
if (exprObj.type === "unary_expr" && exprObj.expr) {
|
|
1395
|
+
transformed = walkExpression2(exprObj.expr, aliases) || transformed;
|
|
1396
|
+
}
|
|
1397
|
+
if (exprObj.type === "cast" && exprObj.expr) {
|
|
1398
|
+
transformed = walkExpression2(exprObj.expr, aliases) || transformed;
|
|
1399
|
+
}
|
|
1400
|
+
if (exprObj.type === "case") {
|
|
1401
|
+
if (exprObj.expr) {
|
|
1402
|
+
transformed = walkExpression2(exprObj.expr, aliases) || transformed;
|
|
1403
|
+
}
|
|
1404
|
+
if (Array.isArray(exprObj.args)) {
|
|
1405
|
+
for (const whenClause of exprObj.args) {
|
|
1406
|
+
if (whenClause.cond) {
|
|
1407
|
+
transformed = walkExpression2(whenClause.cond, aliases) || transformed;
|
|
1408
|
+
}
|
|
1409
|
+
if (whenClause.result) {
|
|
1410
|
+
transformed = walkExpression2(whenClause.result, aliases) || transformed;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
if ("args" in exprObj && exprObj.args) {
|
|
1416
|
+
const args = exprObj.args;
|
|
1417
|
+
if (Array.isArray(args.value)) {
|
|
1418
|
+
for (const arg of args.value) {
|
|
1419
|
+
transformed = walkExpression2(arg, aliases) || transformed;
|
|
1420
|
+
}
|
|
1421
|
+
} else if (args.expr) {
|
|
1422
|
+
transformed = walkExpression2(args.expr, aliases) || transformed;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
if ("over" in exprObj && exprObj.over && typeof exprObj.over === "object") {
|
|
1426
|
+
const over = exprObj.over;
|
|
1427
|
+
if (Array.isArray(over.partition)) {
|
|
1428
|
+
for (const part of over.partition) {
|
|
1429
|
+
transformed = walkExpression2(part, aliases) || transformed;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
if (Array.isArray(over.orderby)) {
|
|
1433
|
+
for (const order of over.orderby) {
|
|
1434
|
+
transformed = walkExpression2(order, aliases) || transformed;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
if ("ast" in exprObj && exprObj.ast) {
|
|
1439
|
+
const subAst = exprObj.ast;
|
|
1440
|
+
if (subAst.type === "select") {
|
|
1441
|
+
transformed = walkSelect2(subAst) || transformed;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (exprObj.type === "expr_list" && Array.isArray(exprObj.value)) {
|
|
1445
|
+
for (const item of exprObj.value) {
|
|
1446
|
+
transformed = walkExpression2(item, aliases) || transformed;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
return transformed;
|
|
1450
|
+
}
|
|
1451
|
+
function walkFrom2(from, aliases) {
|
|
1452
|
+
if (!from || !Array.isArray(from))
|
|
1453
|
+
return false;
|
|
1454
|
+
let transformed = false;
|
|
1455
|
+
for (const f of from) {
|
|
1456
|
+
if ("join" in f) {
|
|
1457
|
+
const join = f;
|
|
1458
|
+
transformed = walkExpression2(join.on, aliases) || transformed;
|
|
1459
|
+
}
|
|
1460
|
+
if ("expr" in f && f.expr && "ast" in f.expr) {
|
|
1461
|
+
transformed = walkSelect2(f.expr.ast) || transformed;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
return transformed;
|
|
1465
|
+
}
|
|
1466
|
+
function walkSelect2(select) {
|
|
1467
|
+
let transformed = false;
|
|
1468
|
+
const aliases = getGenerateSeriesAliases(select.from);
|
|
1469
|
+
if (select.with) {
|
|
1470
|
+
for (const cte of select.with) {
|
|
1471
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1472
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1473
|
+
transformed = walkSelect2(cteSelect) || transformed;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
transformed = walkFrom2(select.from, aliases) || transformed;
|
|
1478
|
+
transformed = walkExpression2(select.where, aliases) || transformed;
|
|
1479
|
+
if (select.having) {
|
|
1480
|
+
if (Array.isArray(select.having)) {
|
|
1481
|
+
for (const h of select.having) {
|
|
1482
|
+
transformed = walkExpression2(h, aliases) || transformed;
|
|
1483
|
+
}
|
|
1484
|
+
} else {
|
|
1485
|
+
transformed = walkExpression2(select.having, aliases) || transformed;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
if (Array.isArray(select.columns)) {
|
|
1489
|
+
for (const col of select.columns) {
|
|
1490
|
+
if ("expr" in col) {
|
|
1491
|
+
transformed = walkExpression2(col.expr, aliases) || transformed;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
if (Array.isArray(select.groupby)) {
|
|
1496
|
+
for (const g of select.groupby) {
|
|
1497
|
+
transformed = walkExpression2(g, aliases) || transformed;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
if (Array.isArray(select.orderby)) {
|
|
1501
|
+
for (const order of select.orderby) {
|
|
1502
|
+
if (order.expr) {
|
|
1503
|
+
transformed = walkExpression2(order.expr, aliases) || transformed;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
if (select._orderby) {
|
|
1508
|
+
for (const order of select._orderby) {
|
|
1509
|
+
if (order.expr) {
|
|
1510
|
+
transformed = walkExpression2(order.expr, aliases) || transformed;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
if (select._next) {
|
|
1515
|
+
transformed = walkSelect2(select._next) || transformed;
|
|
1516
|
+
}
|
|
1517
|
+
return transformed;
|
|
1518
|
+
}
|
|
1519
|
+
function rewriteGenerateSeriesAliases(ast) {
|
|
1520
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1521
|
+
let transformed = false;
|
|
1522
|
+
for (const stmt of statements) {
|
|
1523
|
+
if (stmt.type === "select") {
|
|
1524
|
+
transformed = walkSelect2(stmt) || transformed;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
return transformed;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// src/sql/visitors/union-with-hoister.ts
|
|
1531
|
+
function getCteName(cte) {
|
|
1532
|
+
const nameObj = cte.name;
|
|
1533
|
+
if (!nameObj)
|
|
1534
|
+
return null;
|
|
1535
|
+
const value = nameObj.value;
|
|
1536
|
+
if (typeof value === "string")
|
|
1537
|
+
return value;
|
|
1538
|
+
return null;
|
|
1539
|
+
}
|
|
1540
|
+
function hoistWithInSelect(select) {
|
|
1541
|
+
if (!select.set_op || !select._next)
|
|
1542
|
+
return false;
|
|
1543
|
+
const arms = [];
|
|
1544
|
+
let current = select;
|
|
1545
|
+
while (current && current.type === "select") {
|
|
1546
|
+
arms.push(current);
|
|
1547
|
+
current = current._next;
|
|
1548
|
+
}
|
|
1549
|
+
const mergedWith = [];
|
|
1550
|
+
const seen = new Set;
|
|
1551
|
+
let hasWithBeyondFirst = false;
|
|
1552
|
+
for (const arm of arms) {
|
|
1553
|
+
if (arm.with && arm.with.length > 0) {
|
|
1554
|
+
if (arm !== arms[0]) {
|
|
1555
|
+
hasWithBeyondFirst = true;
|
|
1556
|
+
}
|
|
1557
|
+
for (const cte of arm.with) {
|
|
1558
|
+
const cteName = getCteName(cte);
|
|
1559
|
+
if (!cteName)
|
|
1560
|
+
return false;
|
|
1561
|
+
if (seen.has(cteName)) {
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
seen.add(cteName);
|
|
1565
|
+
mergedWith.push(cte);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
if (!hasWithBeyondFirst)
|
|
1570
|
+
return false;
|
|
1571
|
+
arms[0].with = mergedWith;
|
|
1572
|
+
if ("parentheses_symbol" in arms[0]) {
|
|
1573
|
+
arms[0].parentheses_symbol = false;
|
|
1574
|
+
}
|
|
1575
|
+
for (let i = 1;i < arms.length; i++) {
|
|
1576
|
+
arms[i].with = null;
|
|
1577
|
+
}
|
|
1578
|
+
return true;
|
|
1579
|
+
}
|
|
1580
|
+
function walkSelect3(select) {
|
|
1581
|
+
let transformed = false;
|
|
1582
|
+
if (select.with) {
|
|
1583
|
+
for (const cte of select.with) {
|
|
1584
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
1585
|
+
if (cteSelect && cteSelect.type === "select") {
|
|
1586
|
+
transformed = walkSelect3(cteSelect) || transformed;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
if (Array.isArray(select.from)) {
|
|
1591
|
+
for (const from of select.from) {
|
|
1592
|
+
if ("expr" in from && from.expr && "ast" in from.expr) {
|
|
1593
|
+
transformed = walkSelect3(from.expr.ast) || transformed;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
transformed = hoistWithInSelect(select) || transformed;
|
|
1598
|
+
if (select._next) {
|
|
1599
|
+
transformed = walkSelect3(select._next) || transformed;
|
|
1600
|
+
}
|
|
1601
|
+
return transformed;
|
|
1602
|
+
}
|
|
1603
|
+
function hoistUnionWith(ast) {
|
|
1604
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
1605
|
+
let transformed = false;
|
|
1606
|
+
for (const stmt of statements) {
|
|
1607
|
+
if (stmt.type === "select") {
|
|
1608
|
+
transformed = walkSelect3(stmt) || transformed;
|
|
1164
1609
|
}
|
|
1165
1610
|
}
|
|
1166
1611
|
return transformed;
|
|
@@ -1169,29 +1614,73 @@ function qualifyJoinColumns(ast) {
|
|
|
1169
1614
|
// src/sql/ast-transformer.ts
|
|
1170
1615
|
var { Parser } = nodeSqlParser;
|
|
1171
1616
|
var parser = new Parser;
|
|
1617
|
+
var CACHE_SIZE = 500;
|
|
1618
|
+
var transformCache = new Map;
|
|
1619
|
+
function getCachedOrTransform(query, transform) {
|
|
1620
|
+
const cached = transformCache.get(query);
|
|
1621
|
+
if (cached) {
|
|
1622
|
+
transformCache.delete(query);
|
|
1623
|
+
transformCache.set(query, cached);
|
|
1624
|
+
return cached;
|
|
1625
|
+
}
|
|
1626
|
+
const result = transform();
|
|
1627
|
+
if (transformCache.size >= CACHE_SIZE) {
|
|
1628
|
+
const oldestKey = transformCache.keys().next().value;
|
|
1629
|
+
if (oldestKey) {
|
|
1630
|
+
transformCache.delete(oldestKey);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
transformCache.set(query, result);
|
|
1634
|
+
return result;
|
|
1635
|
+
}
|
|
1636
|
+
var DEBUG_ENV = "DRIZZLE_DUCKDB_DEBUG_AST";
|
|
1637
|
+
function hasJoin(query) {
|
|
1638
|
+
return /\bjoin\b/i.test(query);
|
|
1639
|
+
}
|
|
1640
|
+
function debugLog(message, payload) {
|
|
1641
|
+
if (process?.env?.[DEBUG_ENV]) {
|
|
1642
|
+
console.debug("[duckdb-ast]", message, payload ?? "");
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1172
1645
|
function transformSQL(query) {
|
|
1173
1646
|
const needsArrayTransform = query.includes("@>") || query.includes("<@") || query.includes("&&");
|
|
1174
|
-
const needsJoinTransform = query.
|
|
1175
|
-
|
|
1647
|
+
const needsJoinTransform = hasJoin(query) || /\bupdate\b/i.test(query) || /\bdelete\b/i.test(query);
|
|
1648
|
+
const needsUnionTransform = /\bunion\b/i.test(query) || /\bintersect\b/i.test(query) || /\bexcept\b/i.test(query);
|
|
1649
|
+
const needsGenerateSeriesTransform = /\bgenerate_series\b/i.test(query);
|
|
1650
|
+
if (!needsArrayTransform && !needsJoinTransform && !needsUnionTransform && !needsGenerateSeriesTransform) {
|
|
1176
1651
|
return { sql: query, transformed: false };
|
|
1177
1652
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1653
|
+
return getCachedOrTransform(query, () => {
|
|
1654
|
+
try {
|
|
1655
|
+
const ast = parser.astify(query, { database: "PostgreSQL" });
|
|
1656
|
+
let transformed = false;
|
|
1657
|
+
if (needsArrayTransform) {
|
|
1658
|
+
transformed = transformArrayOperators(ast) || transformed;
|
|
1659
|
+
}
|
|
1660
|
+
if (needsJoinTransform) {
|
|
1661
|
+
transformed = qualifyJoinColumns(ast) || transformed;
|
|
1662
|
+
}
|
|
1663
|
+
if (needsGenerateSeriesTransform) {
|
|
1664
|
+
transformed = rewriteGenerateSeriesAliases(ast) || transformed;
|
|
1665
|
+
}
|
|
1666
|
+
if (needsUnionTransform) {
|
|
1667
|
+
transformed = hoistUnionWith(ast) || transformed;
|
|
1668
|
+
}
|
|
1669
|
+
if (!transformed) {
|
|
1670
|
+
debugLog("AST parsed but no transformation applied", {
|
|
1671
|
+
join: needsJoinTransform
|
|
1672
|
+
});
|
|
1673
|
+
return { sql: query, transformed: false };
|
|
1674
|
+
}
|
|
1675
|
+
const transformedSql = parser.sqlify(ast, { database: "PostgreSQL" });
|
|
1676
|
+
return { sql: transformedSql, transformed: true };
|
|
1677
|
+
} catch (err) {
|
|
1678
|
+
debugLog("AST transform failed; returning original SQL", {
|
|
1679
|
+
error: err.message
|
|
1680
|
+
});
|
|
1188
1681
|
return { sql: query, transformed: false };
|
|
1189
1682
|
}
|
|
1190
|
-
|
|
1191
|
-
return { sql: transformedSql, transformed: true };
|
|
1192
|
-
} catch {
|
|
1193
|
-
return { sql: query, transformed: false };
|
|
1194
|
-
}
|
|
1683
|
+
});
|
|
1195
1684
|
}
|
|
1196
1685
|
|
|
1197
1686
|
// src/dialect.ts
|
|
@@ -2269,7 +2758,7 @@ function renderImports(imports, importBasePath) {
|
|
|
2269
2758
|
// src/bin/duckdb-introspect.ts
|
|
2270
2759
|
function parseArgs(argv) {
|
|
2271
2760
|
const options = {
|
|
2272
|
-
outFile: path.resolve(
|
|
2761
|
+
outFile: path.resolve(process2.cwd(), "drizzle/schema.ts"),
|
|
2273
2762
|
outMeta: undefined,
|
|
2274
2763
|
allDatabases: false,
|
|
2275
2764
|
includeViews: false,
|
|
@@ -2294,12 +2783,12 @@ function parseArgs(argv) {
|
|
|
2294
2783
|
break;
|
|
2295
2784
|
case "--out":
|
|
2296
2785
|
case "--outFile":
|
|
2297
|
-
options.outFile = path.resolve(
|
|
2786
|
+
options.outFile = path.resolve(process2.cwd(), argv[++i] ?? "drizzle/schema.ts");
|
|
2298
2787
|
break;
|
|
2299
2788
|
case "--out-json":
|
|
2300
2789
|
case "--outJson":
|
|
2301
2790
|
case "--json":
|
|
2302
|
-
options.outMeta = path.resolve(
|
|
2791
|
+
options.outMeta = path.resolve(process2.cwd(), argv[++i] ?? "drizzle/schema.meta.json");
|
|
2303
2792
|
break;
|
|
2304
2793
|
case "--include-views":
|
|
2305
2794
|
case "--includeViews":
|
|
@@ -2314,7 +2803,7 @@ function parseArgs(argv) {
|
|
|
2314
2803
|
case "--help":
|
|
2315
2804
|
case "-h":
|
|
2316
2805
|
printHelp();
|
|
2317
|
-
|
|
2806
|
+
process2.exit(0);
|
|
2318
2807
|
default:
|
|
2319
2808
|
if (arg.startsWith("-")) {
|
|
2320
2809
|
console.warn(`Unknown option ${arg}`);
|
|
@@ -2356,12 +2845,12 @@ Examples:
|
|
|
2356
2845
|
`);
|
|
2357
2846
|
}
|
|
2358
2847
|
async function main() {
|
|
2359
|
-
const options = parseArgs(
|
|
2848
|
+
const options = parseArgs(process2.argv.slice(2));
|
|
2360
2849
|
if (!options.url) {
|
|
2361
2850
|
printHelp();
|
|
2362
2851
|
throw new Error("Missing required --url");
|
|
2363
2852
|
}
|
|
2364
|
-
const instanceOptions = options.url.startsWith("md:") &&
|
|
2853
|
+
const instanceOptions = options.url.startsWith("md:") && process2.env.MOTHERDUCK_TOKEN ? { motherduck_token: process2.env.MOTHERDUCK_TOKEN } : undefined;
|
|
2365
2854
|
const instance = await DuckDBInstance3.create(options.url, instanceOptions);
|
|
2366
2855
|
const connection = await instance.connect();
|
|
2367
2856
|
const db = drizzle(connection);
|
|
@@ -2397,5 +2886,5 @@ async function main() {
|
|
|
2397
2886
|
}
|
|
2398
2887
|
main().catch((err) => {
|
|
2399
2888
|
console.error(err instanceof Error ? err.message : err);
|
|
2400
|
-
|
|
2889
|
+
process2.exit(1);
|
|
2401
2890
|
});
|