@leonardovida-md/drizzle-neo-duckdb 1.2.0 → 1.2.1

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.
@@ -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 process from "node:process";
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 source.alias ?? source.name;
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" && !(("table" in expr) && expr.table);
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,71 @@ function getColumnName(col) {
1040
1048
  }
1041
1049
  return null;
1042
1050
  }
1043
- function walkOnClause(expr, leftSource, rightSource, ambiguousColumns) {
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.type === "binary_expr") {
1048
- if (expr.operator === "=") {
1049
- const left = expr.left;
1050
- const right = expr.right;
1051
- if (isUnqualifiedColumnRef(left) && isUnqualifiedColumnRef(right)) {
1052
- const leftColName = getColumnName(left);
1053
- const rightColName = getColumnName(right);
1054
- if (leftColName && rightColName && leftColName === rightColName) {
1055
- left.table = leftSource;
1056
- right.table = rightSource;
1057
- ambiguousColumns.add(leftColName);
1058
- transformed = true;
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 === "AND" || expr.operator === "OR") {
1063
- transformed = walkOnClause(expr.left, leftSource, rightSource, ambiguousColumns) || transformed;
1064
- transformed = walkOnClause(expr.right, leftSource, rightSource, ambiguousColumns) || transformed;
1065
- }
1114
+ transformed = walkOnClause(isBinaryExpr(expr.left) ? expr.left : expr.left, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
1115
+ transformed = walkOnClause(isBinaryExpr(expr.right) ? expr.right : expr.right, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
1066
1116
  }
1067
1117
  return transformed;
1068
1118
  }
@@ -1073,12 +1123,12 @@ function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns)
1073
1123
  if (isUnqualifiedColumnRef(expr)) {
1074
1124
  const colName = getColumnName(expr);
1075
1125
  if (colName && ambiguousColumns.has(colName)) {
1076
- expr.table = defaultQualifier;
1126
+ applyQualifier(expr, defaultQualifier);
1077
1127
  transformed = true;
1078
1128
  }
1079
1129
  return transformed;
1080
1130
  }
1081
- if ("type" in expr && expr.type === "binary_expr") {
1131
+ if (isBinaryExpr(expr)) {
1082
1132
  const binary = expr;
1083
1133
  transformed = qualifyAmbiguousInExpression(binary.left, defaultQualifier, ambiguousColumns) || transformed;
1084
1134
  transformed = qualifyAmbiguousInExpression(binary.right, defaultQualifier, ambiguousColumns) || transformed;
@@ -1095,48 +1145,117 @@ function qualifyAmbiguousInExpression(expr, defaultQualifier, ambiguousColumns)
1095
1145
  transformed = qualifyAmbiguousInExpression(args.expr, defaultQualifier, ambiguousColumns) || transformed;
1096
1146
  }
1097
1147
  }
1148
+ if ("over" in expr && expr.over && typeof expr.over === "object") {
1149
+ const over = expr.over;
1150
+ if (Array.isArray(over.partition)) {
1151
+ for (const part of over.partition) {
1152
+ transformed = qualifyAmbiguousInExpression(part, defaultQualifier, ambiguousColumns) || transformed;
1153
+ }
1154
+ }
1155
+ if (Array.isArray(over.orderby)) {
1156
+ for (const order of over.orderby) {
1157
+ transformed = qualifyAmbiguousInExpression(order, defaultQualifier, ambiguousColumns) || transformed;
1158
+ }
1159
+ }
1160
+ }
1098
1161
  return transformed;
1099
1162
  }
1163
+ function hasUnqualifiedColumns(expr) {
1164
+ if (!expr || typeof expr !== "object")
1165
+ return false;
1166
+ if ("type" in expr && expr.type === "binary_expr") {
1167
+ const left = expr.left;
1168
+ const right = expr.right;
1169
+ const leftCol = unwrapColumnRef(left);
1170
+ const rightCol = unwrapColumnRef(right);
1171
+ if (isUnqualifiedColumnRef(left) || isUnqualifiedColumnRef(right) || leftCol && isUnqualifiedColumnRef(leftCol) || rightCol && isUnqualifiedColumnRef(rightCol)) {
1172
+ return true;
1173
+ }
1174
+ if (isBinaryExpr(expr.left) && hasUnqualifiedColumns(expr.left))
1175
+ return true;
1176
+ if (isBinaryExpr(expr.right) && hasUnqualifiedColumns(expr.right))
1177
+ return true;
1178
+ }
1179
+ if ("args" in expr && expr.args) {
1180
+ const args = expr.args;
1181
+ if (args.expr && isUnqualifiedColumnRef(args.expr))
1182
+ return true;
1183
+ if (args.value) {
1184
+ for (const arg of args.value) {
1185
+ if (isUnqualifiedColumnRef(arg))
1186
+ return true;
1187
+ }
1188
+ }
1189
+ }
1190
+ return false;
1191
+ }
1100
1192
  function walkSelect(select) {
1101
1193
  let transformed = false;
1102
1194
  const ambiguousColumns = new Set;
1103
1195
  if (Array.isArray(select.from) && select.from.length >= 2) {
1104
1196
  const firstSource = getTableSource(select.from[0]);
1105
- const defaultQualifier = firstSource ? getQualifier(firstSource) : "";
1197
+ const defaultQualifier = firstSource ? getQualifier(firstSource) : null;
1106
1198
  let prevSource = firstSource;
1199
+ let hasAnyUnqualified = false;
1107
1200
  for (const from of select.from) {
1108
1201
  if ("join" in from) {
1109
1202
  const join = from;
1110
- const currentSource = getTableSource(join);
1111
- if (join.on && prevSource && currentSource) {
1112
- const leftQualifier = getQualifier(prevSource);
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;
1203
+ if (join.on && hasUnqualifiedColumns(join.on)) {
1204
+ hasAnyUnqualified = true;
1205
+ break;
1121
1206
  }
1122
1207
  }
1123
- if ("expr" in from && from.expr && "ast" in from.expr) {
1124
- transformed = walkSelect(from.expr.ast) || transformed;
1125
- }
1126
1208
  }
1127
- if (ambiguousColumns.size > 0 && defaultQualifier) {
1128
- if (Array.isArray(select.columns)) {
1129
- for (const col of select.columns) {
1130
- if ("expr" in col) {
1131
- transformed = qualifyAmbiguousInExpression(col.expr, defaultQualifier, ambiguousColumns) || transformed;
1209
+ if (!hasAnyUnqualified) {
1210
+ for (const from of select.from) {
1211
+ if ("expr" in from && from.expr && "ast" in from.expr) {
1212
+ transformed = walkSelect(from.expr.ast) || transformed;
1213
+ }
1214
+ }
1215
+ } else {
1216
+ for (const from of select.from) {
1217
+ if ("join" in from) {
1218
+ const join = from;
1219
+ const currentSource = getTableSource(join);
1220
+ if (join.on && prevSource && currentSource) {
1221
+ const leftQualifier = getQualifier(prevSource);
1222
+ const rightQualifier = getQualifier(currentSource);
1223
+ transformed = walkOnClause(join.on, leftQualifier, rightQualifier, ambiguousColumns) || transformed;
1224
+ }
1225
+ if (join.using && prevSource && currentSource) {
1226
+ for (const usingCol of join.using) {
1227
+ if (typeof usingCol === "string") {
1228
+ ambiguousColumns.add(usingCol);
1229
+ } else if ("value" in usingCol) {
1230
+ ambiguousColumns.add(String(usingCol.value));
1231
+ }
1232
+ }
1132
1233
  }
1234
+ prevSource = currentSource;
1235
+ } else {
1236
+ const source = getTableSource(from);
1237
+ if (source) {
1238
+ prevSource = source;
1239
+ }
1240
+ }
1241
+ if ("expr" in from && from.expr && "ast" in from.expr) {
1242
+ transformed = walkSelect(from.expr.ast) || transformed;
1133
1243
  }
1134
1244
  }
1135
- transformed = qualifyAmbiguousInExpression(select.where, defaultQualifier, ambiguousColumns) || transformed;
1136
- if (Array.isArray(select.orderby)) {
1137
- for (const order of select.orderby) {
1138
- if (order.expr) {
1139
- transformed = qualifyAmbiguousInExpression(order.expr, defaultQualifier, ambiguousColumns) || transformed;
1245
+ if (ambiguousColumns.size > 0 && defaultQualifier) {
1246
+ if (Array.isArray(select.columns)) {
1247
+ for (const col of select.columns) {
1248
+ if ("expr" in col) {
1249
+ transformed = qualifyAmbiguousInExpression(col.expr, defaultQualifier, ambiguousColumns) || transformed;
1250
+ }
1251
+ }
1252
+ }
1253
+ transformed = qualifyAmbiguousInExpression(select.where, defaultQualifier, ambiguousColumns) || transformed;
1254
+ if (Array.isArray(select.orderby)) {
1255
+ for (const order of select.orderby) {
1256
+ if (order.expr) {
1257
+ transformed = qualifyAmbiguousInExpression(order.expr, defaultQualifier, ambiguousColumns) || transformed;
1258
+ }
1140
1259
  }
1141
1260
  }
1142
1261
  }
@@ -1161,6 +1280,40 @@ function qualifyJoinColumns(ast) {
1161
1280
  for (const stmt of statements) {
1162
1281
  if (stmt.type === "select") {
1163
1282
  transformed = walkSelect(stmt) || transformed;
1283
+ } else if (stmt.type === "insert") {
1284
+ const insert = stmt;
1285
+ if (insert.values && typeof insert.values === "object" && "type" in insert.values && insert.values.type === "select") {
1286
+ transformed = walkSelect(insert.values) || transformed;
1287
+ }
1288
+ } else if (stmt.type === "update") {
1289
+ const update = stmt;
1290
+ const mainSource = update.table?.[0] ? getTableSource(update.table[0]) : null;
1291
+ const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
1292
+ const fromSources = update.from ?? [];
1293
+ const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
1294
+ if (update.where && defaultQualifier && firstFrom) {
1295
+ const ambiguous = new Set;
1296
+ transformed = walkOnClause(update.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
1297
+ transformed = qualifyAmbiguousInExpression(update.where, defaultQualifier, ambiguous) || transformed;
1298
+ }
1299
+ if (Array.isArray(update.returning) && defaultQualifier) {
1300
+ for (const ret of update.returning) {
1301
+ transformed = qualifyAmbiguousInExpression(ret, defaultQualifier, new Set) || transformed;
1302
+ }
1303
+ }
1304
+ } else if (stmt.type === "delete") {
1305
+ const del = stmt;
1306
+ const mainSource = del.table?.[0] ? getTableSource(del.table[0]) : null;
1307
+ const defaultQualifier = mainSource ? getQualifier(mainSource) : null;
1308
+ const fromSources = del.from ?? [];
1309
+ const firstFrom = fromSources[0] ? getTableSource(fromSources[0]) : null;
1310
+ if (del.where && defaultQualifier && firstFrom) {
1311
+ const ambiguous = new Set;
1312
+ transformed = walkOnClause(del.where, defaultQualifier, getQualifier(firstFrom), ambiguous) || transformed;
1313
+ transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, ambiguous) || transformed;
1314
+ } else if (del.where && defaultQualifier) {
1315
+ transformed = qualifyAmbiguousInExpression(del.where, defaultQualifier, new Set) || transformed;
1316
+ }
1164
1317
  }
1165
1318
  }
1166
1319
  return transformed;
@@ -1169,29 +1322,65 @@ function qualifyJoinColumns(ast) {
1169
1322
  // src/sql/ast-transformer.ts
1170
1323
  var { Parser } = nodeSqlParser;
1171
1324
  var parser = new Parser;
1325
+ var CACHE_SIZE = 500;
1326
+ var transformCache = new Map;
1327
+ function getCachedOrTransform(query, transform) {
1328
+ const cached = transformCache.get(query);
1329
+ if (cached) {
1330
+ transformCache.delete(query);
1331
+ transformCache.set(query, cached);
1332
+ return cached;
1333
+ }
1334
+ const result = transform();
1335
+ if (transformCache.size >= CACHE_SIZE) {
1336
+ const oldestKey = transformCache.keys().next().value;
1337
+ if (oldestKey) {
1338
+ transformCache.delete(oldestKey);
1339
+ }
1340
+ }
1341
+ transformCache.set(query, result);
1342
+ return result;
1343
+ }
1344
+ var DEBUG_ENV = "DRIZZLE_DUCKDB_DEBUG_AST";
1345
+ function hasJoin(query) {
1346
+ return /\bjoin\b/i.test(query);
1347
+ }
1348
+ function debugLog(message, payload) {
1349
+ if (process?.env?.[DEBUG_ENV]) {
1350
+ console.debug("[duckdb-ast]", message, payload ?? "");
1351
+ }
1352
+ }
1172
1353
  function transformSQL(query) {
1173
1354
  const needsArrayTransform = query.includes("@>") || query.includes("<@") || query.includes("&&");
1174
- const needsJoinTransform = query.toLowerCase().includes("join");
1355
+ const needsJoinTransform = hasJoin(query) || /\bupdate\b/i.test(query) || /\bdelete\b/i.test(query);
1175
1356
  if (!needsArrayTransform && !needsJoinTransform) {
1176
1357
  return { sql: query, transformed: false };
1177
1358
  }
1178
- try {
1179
- const ast = parser.astify(query, { database: "PostgreSQL" });
1180
- let transformed = false;
1181
- if (needsArrayTransform) {
1182
- transformed = transformArrayOperators(ast) || transformed;
1183
- }
1184
- if (needsJoinTransform) {
1185
- transformed = qualifyJoinColumns(ast) || transformed;
1186
- }
1187
- if (!transformed) {
1359
+ return getCachedOrTransform(query, () => {
1360
+ try {
1361
+ const ast = parser.astify(query, { database: "PostgreSQL" });
1362
+ let transformed = false;
1363
+ if (needsArrayTransform) {
1364
+ transformed = transformArrayOperators(ast) || transformed;
1365
+ }
1366
+ if (needsJoinTransform) {
1367
+ transformed = qualifyJoinColumns(ast) || transformed;
1368
+ }
1369
+ if (!transformed) {
1370
+ debugLog("AST parsed but no transformation applied", {
1371
+ join: needsJoinTransform
1372
+ });
1373
+ return { sql: query, transformed: false };
1374
+ }
1375
+ const transformedSql = parser.sqlify(ast, { database: "PostgreSQL" });
1376
+ return { sql: transformedSql, transformed: true };
1377
+ } catch (err) {
1378
+ debugLog("AST transform failed; returning original SQL", {
1379
+ error: err.message
1380
+ });
1188
1381
  return { sql: query, transformed: false };
1189
1382
  }
1190
- const transformedSql = parser.sqlify(ast, { database: "PostgreSQL" });
1191
- return { sql: transformedSql, transformed: true };
1192
- } catch {
1193
- return { sql: query, transformed: false };
1194
- }
1383
+ });
1195
1384
  }
1196
1385
 
1197
1386
  // src/dialect.ts
@@ -2269,7 +2458,7 @@ function renderImports(imports, importBasePath) {
2269
2458
  // src/bin/duckdb-introspect.ts
2270
2459
  function parseArgs(argv) {
2271
2460
  const options = {
2272
- outFile: path.resolve(process.cwd(), "drizzle/schema.ts"),
2461
+ outFile: path.resolve(process2.cwd(), "drizzle/schema.ts"),
2273
2462
  outMeta: undefined,
2274
2463
  allDatabases: false,
2275
2464
  includeViews: false,
@@ -2294,12 +2483,12 @@ function parseArgs(argv) {
2294
2483
  break;
2295
2484
  case "--out":
2296
2485
  case "--outFile":
2297
- options.outFile = path.resolve(process.cwd(), argv[++i] ?? "drizzle/schema.ts");
2486
+ options.outFile = path.resolve(process2.cwd(), argv[++i] ?? "drizzle/schema.ts");
2298
2487
  break;
2299
2488
  case "--out-json":
2300
2489
  case "--outJson":
2301
2490
  case "--json":
2302
- options.outMeta = path.resolve(process.cwd(), argv[++i] ?? "drizzle/schema.meta.json");
2491
+ options.outMeta = path.resolve(process2.cwd(), argv[++i] ?? "drizzle/schema.meta.json");
2303
2492
  break;
2304
2493
  case "--include-views":
2305
2494
  case "--includeViews":
@@ -2314,7 +2503,7 @@ function parseArgs(argv) {
2314
2503
  case "--help":
2315
2504
  case "-h":
2316
2505
  printHelp();
2317
- process.exit(0);
2506
+ process2.exit(0);
2318
2507
  default:
2319
2508
  if (arg.startsWith("-")) {
2320
2509
  console.warn(`Unknown option ${arg}`);
@@ -2356,12 +2545,12 @@ Examples:
2356
2545
  `);
2357
2546
  }
2358
2547
  async function main() {
2359
- const options = parseArgs(process.argv.slice(2));
2548
+ const options = parseArgs(process2.argv.slice(2));
2360
2549
  if (!options.url) {
2361
2550
  printHelp();
2362
2551
  throw new Error("Missing required --url");
2363
2552
  }
2364
- const instanceOptions = options.url.startsWith("md:") && process.env.MOTHERDUCK_TOKEN ? { motherduck_token: process.env.MOTHERDUCK_TOKEN } : undefined;
2553
+ const instanceOptions = options.url.startsWith("md:") && process2.env.MOTHERDUCK_TOKEN ? { motherduck_token: process2.env.MOTHERDUCK_TOKEN } : undefined;
2365
2554
  const instance = await DuckDBInstance3.create(options.url, instanceOptions);
2366
2555
  const connection = await instance.connect();
2367
2556
  const db = drizzle(connection);
@@ -2397,5 +2586,5 @@ async function main() {
2397
2586
  }
2398
2587
  main().catch((err) => {
2399
2588
  console.error(err instanceof Error ? err.message : err);
2400
- process.exit(1);
2589
+ process2.exit(1);
2401
2590
  });