@sqg/sqg 0.22.0 → 0.23.0

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.
Files changed (3) hide show
  1. package/dist/index.mjs +100 -19
  2. package/dist/sqg.mjs +102 -21
  3. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -2744,6 +2744,21 @@ var EnumType = class {
2744
2744
  return `ENUM(${this.values.map((v) => `'${v}'`).join(", ")})`;
2745
2745
  }
2746
2746
  };
2747
+ /**
2748
+ * Append a placeholder (`?`, `$1`, `$name`, or an inlined source) to the parts
2749
+ * rendered so far, keeping it separated from the preceding SQL text.
2750
+ *
2751
+ * Without the separator, `LIMIT ${n}` would render as `LIMIT$1` — which
2752
+ * Postgres lexes as the identifier `limit$1` and rejects. The whitespace is
2753
+ * added to the parts themselves so every rendering path (the joined `sql`
2754
+ * string and `sqlParts`, used by the template-literal renderers) agrees.
2755
+ */
2756
+ function pushPlaceholder(parts, placeholder) {
2757
+ const lastIndex = parts.length - 1;
2758
+ const last = parts[lastIndex];
2759
+ if (typeof last === "string" && /[A-Za-z0-9_$]$/.test(last)) parts[lastIndex] = `${last} `;
2760
+ parts.push(placeholder);
2761
+ }
2747
2762
  var SQLQuery = class {
2748
2763
  columns;
2749
2764
  allColumns;
@@ -2926,23 +2941,13 @@ function parseSQLQueries(filePath, extraVariables) {
2926
2941
  return this.sqlParts.filter((part) => typeof part !== "string" && !part.name.startsWith("sources_"));
2927
2942
  }
2928
2943
  toSqlWithAnonymousPlaceholders() {
2929
- let sql = "";
2930
2944
  const sqlParts = [];
2931
- for (const part of this.sqlParts) if (typeof part === "string") {
2932
- sql += part;
2933
- sqlParts.push(part);
2934
- } else {
2935
- if (sql.length > 0) {
2936
- const last = sql[sql.length - 1];
2937
- if (last !== " " && last !== "=" && last !== ">" && last !== "<") sql += " ";
2938
- }
2939
- sql += "?";
2940
- if (part.name.startsWith("sources_")) sqlParts.push(part);
2941
- else sqlParts.push("?");
2942
- }
2945
+ for (const part of this.sqlParts) if (typeof part === "string") sqlParts.push(part);
2946
+ else if (part.name.startsWith("sources_")) pushPlaceholder(sqlParts, part);
2947
+ else pushPlaceholder(sqlParts, "?");
2943
2948
  return {
2944
2949
  parameters: this.parameters(),
2945
- sql,
2950
+ sql: sqlParts.map((part) => typeof part === "string" ? part : "?").join(""),
2946
2951
  sqlParts
2947
2952
  };
2948
2953
  }
@@ -2953,7 +2958,7 @@ function parseSQLQueries(filePath, extraVariables) {
2953
2958
  else {
2954
2959
  const varName = part.name;
2955
2960
  const value = part.value;
2956
- if (varName.startsWith("sources_")) sqlParts.push(part);
2961
+ if (varName.startsWith("sources_")) pushPlaceholder(sqlParts, part);
2957
2962
  else {
2958
2963
  let pos = parameters.findIndex((p) => p.name === varName);
2959
2964
  if (pos < 0) {
@@ -2963,7 +2968,7 @@ function parseSQLQueries(filePath, extraVariables) {
2963
2968
  });
2964
2969
  pos = parameters.length;
2965
2970
  } else pos = pos + 1;
2966
- sqlParts.push(`$${pos}`);
2971
+ pushPlaceholder(sqlParts, `$${pos}`);
2967
2972
  }
2968
2973
  }
2969
2974
  return {
@@ -2975,8 +2980,8 @@ function parseSQLQueries(filePath, extraVariables) {
2975
2980
  toSqlWithNamedPlaceholders() {
2976
2981
  const sqlParts = [];
2977
2982
  for (const part of this.sqlParts) if (typeof part === "string") sqlParts.push(part);
2978
- else if (part.name.startsWith("sources_")) sqlParts.push(part);
2979
- else sqlParts.push(`$${part.name}`);
2983
+ else if (part.name.startsWith("sources_")) pushPlaceholder(sqlParts, part);
2984
+ else pushPlaceholder(sqlParts, `$${part.name}`);
2980
2985
  return {
2981
2986
  parameters: this.parameters(),
2982
2987
  sqlParts,
@@ -2999,8 +3004,9 @@ function parseSQLQueries(filePath, extraVariables) {
2999
3004
  if (!varRef.startsWith("${") || !varRef.endsWith("}")) throw SqgError.inQuery(`Invalid variable reference: ${varRef}`, "SQL_PARSE_ERROR", name, filePath, { suggestion: "Variables should be in the format ${varName}" });
3000
3005
  const varName = varRef.replace("${", "").replace("}", "");
3001
3006
  const value = getVariable(varName);
3002
- if (to > from) sql.appendSql(content.slice(from, to));
3007
+ if (from >= 0 && child.from > from) sql.appendSql(content.slice(from, child.from));
3003
3008
  from = child.to;
3009
+ to = child.to;
3004
3010
  sql.appendVariable(varName, value);
3005
3011
  } else {
3006
3012
  if (from < 0) from = child.from;
@@ -3051,6 +3057,29 @@ function parseSQLQueries(filePath, extraVariables) {
3051
3057
  }
3052
3058
  //#endregion
3053
3059
  //#region src/db/types.ts
3060
+ /**
3061
+ * Whether an error is an integrity constraint violation (foreign key, unique,
3062
+ * check, not-null) rather than a problem with the statement itself.
3063
+ */
3064
+ function isConstraintViolation(error) {
3065
+ const message = error?.message ?? "";
3066
+ const code = String(error?.code ?? "");
3067
+ return code.startsWith("SQLITE_CONSTRAINT") || /^23/.test(code) || /^Constraint Error/i.test(message) || /violates .* constraint|constraint failed/i.test(message);
3068
+ }
3069
+ /**
3070
+ * Type introspection executes every EXEC statement against whatever data the
3071
+ * MIGRATE and TESTDATA blocks left behind, so a statement can fail purely
3072
+ * because a constraint has nothing to reference — a NOT NULL foreign key can
3073
+ * never be satisfied when no fixture created the parent row.
3074
+ *
3075
+ * Running an EXEC contributes nothing to the generated code (the prepared
3076
+ * statement already supplied the parameter types), so such a failure is
3077
+ * reported as a warning instead of aborting generation. Statements whose
3078
+ * result shape SQG needs (QUERY, including INSERT ... RETURNING) still fail.
3079
+ */
3080
+ function warnConstraintViolation(query, error) {
3081
+ consola.warn(`Skipped executing '${query.id}' in ${query.filename} during type introspection: ${error.message}\n Introspection runs each statement against the TESTDATA fixtures only, so constraints such as foreign keys may not be satisfiable. Code generation is unaffected — add a TESTDATA block with the referenced rows to silence this warning.`);
3082
+ }
3054
3083
  async function initializeDatabase(queries, execQueries, reporter) {
3055
3084
  const baselineQueries = queries.filter((q) => q.isBaseline && !q.sourceTarget);
3056
3085
  for (const query of baselineQueries) try {
@@ -3198,6 +3227,10 @@ const duckdb = new class {
3198
3227
  }
3199
3228
  return await stmt.run();
3200
3229
  } catch (error) {
3230
+ if (!query.isQuery && isConstraintViolation(error)) {
3231
+ warnConstraintViolation(query, error);
3232
+ return;
3233
+ }
3201
3234
  consola.error(`Failed to execute query '${query.id}':`, error);
3202
3235
  throw error;
3203
3236
  }
@@ -3238,6 +3271,38 @@ const tempDatabaseName = "sqg-db-temp";
3238
3271
  const typeIdToName = /* @__PURE__ */ new Map();
3239
3272
  for (const [name, id] of Object.entries(types.builtins)) typeIdToName.set(Number(id), name);
3240
3273
  /**
3274
+ * Turn off referential integrity enforcement for the introspection connection.
3275
+ *
3276
+ * QUERY and EXEC statements are introspected against whatever data MIGRATE and
3277
+ * TESTDATA left behind, each in its own transaction, so a NOT NULL foreign key
3278
+ * can never be satisfied by a row an earlier statement inserted.
3279
+ * `session_replication_role = replica` disables the triggers enforcing it, for
3280
+ * this connection only, and every statement introspected here is rolled back.
3281
+ *
3282
+ * This happens after the database is initialized, so a foreign key violation in
3283
+ * a MIGRATE or TESTDATA block is still reported — those describe real data.
3284
+ *
3285
+ * Setting it needs superuser (or the SET privilege), so this is best effort:
3286
+ * without it, constraint violations on EXEC statements are still tolerated,
3287
+ * only INSERT ... RETURNING queries would fail.
3288
+ */
3289
+ async function relaxConstraints(db, inTransaction) {
3290
+ try {
3291
+ if (inTransaction) {
3292
+ await db.query("SAVEPOINT sqg_relax_constraints");
3293
+ try {
3294
+ await db.query("SET LOCAL session_replication_role = 'replica'");
3295
+ } catch (e) {
3296
+ await db.query("ROLLBACK TO SAVEPOINT sqg_relax_constraints");
3297
+ throw e;
3298
+ }
3299
+ await db.query("RELEASE SAVEPOINT sqg_relax_constraints");
3300
+ } else await db.query("SET session_replication_role = 'replica'");
3301
+ } catch (e) {
3302
+ consola.debug(`Could not disable constraint enforcement for introspection: ${e.message}`);
3303
+ }
3304
+ }
3305
+ /**
3241
3306
  * External database mode: connects directly to the user's database.
3242
3307
  * Uses a single transaction for the entire session and rolls back on close,
3243
3308
  * so the database is never modified. Individual queries use savepoints.
@@ -3264,6 +3329,9 @@ var ExternalDbMode = class {
3264
3329
  await db.query("ROLLBACK TO SAVEPOINT sqg_query");
3265
3330
  }
3266
3331
  }
3332
+ relaxConstraints(db) {
3333
+ return relaxConstraints(db, true);
3334
+ }
3267
3335
  async close(db) {
3268
3336
  await db.query("ROLLBACK");
3269
3337
  await db.end();
@@ -3313,6 +3381,9 @@ var TempDbMode = class {
3313
3381
  await db.query("ROLLBACK");
3314
3382
  }
3315
3383
  }
3384
+ relaxConstraints(db) {
3385
+ return relaxConstraints(db, false);
3386
+ }
3316
3387
  async close(db) {
3317
3388
  await db.end();
3318
3389
  await this.dbInitial.query(`DROP DATABASE IF EXISTS "${tempDatabaseName}"`);
@@ -3408,6 +3479,7 @@ const postgres = new class {
3408
3479
  async executeQueries(queries, reporter) {
3409
3480
  const db = this.db;
3410
3481
  if (!db) throw new DatabaseError("PostgreSQL database not initialized", "postgres", "This is an internal error. Check that migrations completed successfully.");
3482
+ await this.mode.relaxConstraints(db);
3411
3483
  try {
3412
3484
  const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
3413
3485
  for (const query of executableQueries) {
@@ -3474,6 +3546,10 @@ const postgres = new class {
3474
3546
  }
3475
3547
  return result;
3476
3548
  } catch (error) {
3549
+ if (!query.isQuery && isConstraintViolation(error)) {
3550
+ warnConstraintViolation(query, error);
3551
+ return;
3552
+ }
3477
3553
  consola.error(`Failed to execute query '${query.id}':`, error);
3478
3554
  throw error;
3479
3555
  }
@@ -3526,6 +3602,7 @@ const sqlite = new class {
3526
3602
  executeQueries(queries, reporter) {
3527
3603
  const db = this.db;
3528
3604
  if (!db) throw new DatabaseError("SQLite database not initialized", "sqlite", "This is an internal error. Migrations may have failed silently.");
3605
+ db.pragma("foreign_keys = OFF");
3529
3606
  try {
3530
3607
  const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
3531
3608
  for (const query of executableQueries) {
@@ -3626,6 +3703,10 @@ const sqlite = new class {
3626
3703
  }
3627
3704
  return stmt.run(...params);
3628
3705
  } catch (error) {
3706
+ if (!query.isQuery && isConstraintViolation(error)) {
3707
+ warnConstraintViolation(query, error);
3708
+ return;
3709
+ }
3629
3710
  consola.error(`Failed to execute query '${query.id}' in ${query.filename}:\n ${statement.sql} \n ${statement.parameters.map((p) => p.value).join(", ")}`, error);
3630
3711
  throw error;
3631
3712
  }
package/dist/sqg.mjs CHANGED
@@ -787,6 +787,21 @@ var EnumType = class {
787
787
  return `ENUM(${this.values.map((v) => `'${v}'`).join(", ")})`;
788
788
  }
789
789
  };
790
+ /**
791
+ * Append a placeholder (`?`, `$1`, `$name`, or an inlined source) to the parts
792
+ * rendered so far, keeping it separated from the preceding SQL text.
793
+ *
794
+ * Without the separator, `LIMIT ${n}` would render as `LIMIT$1` — which
795
+ * Postgres lexes as the identifier `limit$1` and rejects. The whitespace is
796
+ * added to the parts themselves so every rendering path (the joined `sql`
797
+ * string and `sqlParts`, used by the template-literal renderers) agrees.
798
+ */
799
+ function pushPlaceholder(parts, placeholder) {
800
+ const lastIndex = parts.length - 1;
801
+ const last = parts[lastIndex];
802
+ if (typeof last === "string" && /[A-Za-z0-9_$]$/.test(last)) parts[lastIndex] = `${last} `;
803
+ parts.push(placeholder);
804
+ }
790
805
  var SQLQuery = class {
791
806
  columns;
792
807
  allColumns;
@@ -969,23 +984,13 @@ function parseSQLQueries(filePath, extraVariables) {
969
984
  return this.sqlParts.filter((part) => typeof part !== "string" && !part.name.startsWith("sources_"));
970
985
  }
971
986
  toSqlWithAnonymousPlaceholders() {
972
- let sql = "";
973
987
  const sqlParts = [];
974
- for (const part of this.sqlParts) if (typeof part === "string") {
975
- sql += part;
976
- sqlParts.push(part);
977
- } else {
978
- if (sql.length > 0) {
979
- const last = sql[sql.length - 1];
980
- if (last !== " " && last !== "=" && last !== ">" && last !== "<") sql += " ";
981
- }
982
- sql += "?";
983
- if (part.name.startsWith("sources_")) sqlParts.push(part);
984
- else sqlParts.push("?");
985
- }
988
+ for (const part of this.sqlParts) if (typeof part === "string") sqlParts.push(part);
989
+ else if (part.name.startsWith("sources_")) pushPlaceholder(sqlParts, part);
990
+ else pushPlaceholder(sqlParts, "?");
986
991
  return {
987
992
  parameters: this.parameters(),
988
- sql,
993
+ sql: sqlParts.map((part) => typeof part === "string" ? part : "?").join(""),
989
994
  sqlParts
990
995
  };
991
996
  }
@@ -996,7 +1001,7 @@ function parseSQLQueries(filePath, extraVariables) {
996
1001
  else {
997
1002
  const varName = part.name;
998
1003
  const value = part.value;
999
- if (varName.startsWith("sources_")) sqlParts.push(part);
1004
+ if (varName.startsWith("sources_")) pushPlaceholder(sqlParts, part);
1000
1005
  else {
1001
1006
  let pos = parameters.findIndex((p) => p.name === varName);
1002
1007
  if (pos < 0) {
@@ -1006,7 +1011,7 @@ function parseSQLQueries(filePath, extraVariables) {
1006
1011
  });
1007
1012
  pos = parameters.length;
1008
1013
  } else pos = pos + 1;
1009
- sqlParts.push(`$${pos}`);
1014
+ pushPlaceholder(sqlParts, `$${pos}`);
1010
1015
  }
1011
1016
  }
1012
1017
  return {
@@ -1018,8 +1023,8 @@ function parseSQLQueries(filePath, extraVariables) {
1018
1023
  toSqlWithNamedPlaceholders() {
1019
1024
  const sqlParts = [];
1020
1025
  for (const part of this.sqlParts) if (typeof part === "string") sqlParts.push(part);
1021
- else if (part.name.startsWith("sources_")) sqlParts.push(part);
1022
- else sqlParts.push(`$${part.name}`);
1026
+ else if (part.name.startsWith("sources_")) pushPlaceholder(sqlParts, part);
1027
+ else pushPlaceholder(sqlParts, `$${part.name}`);
1023
1028
  return {
1024
1029
  parameters: this.parameters(),
1025
1030
  sqlParts,
@@ -1042,8 +1047,9 @@ function parseSQLQueries(filePath, extraVariables) {
1042
1047
  if (!varRef.startsWith("${") || !varRef.endsWith("}")) throw SqgError.inQuery(`Invalid variable reference: ${varRef}`, "SQL_PARSE_ERROR", name, filePath, { suggestion: "Variables should be in the format ${varName}" });
1043
1048
  const varName = varRef.replace("${", "").replace("}", "");
1044
1049
  const value = getVariable(varName);
1045
- if (to > from) sql.appendSql(content.slice(from, to));
1050
+ if (from >= 0 && child.from > from) sql.appendSql(content.slice(from, child.from));
1046
1051
  from = child.to;
1052
+ to = child.to;
1047
1053
  sql.appendVariable(varName, value);
1048
1054
  } else {
1049
1055
  if (from < 0) from = child.from;
@@ -1094,6 +1100,29 @@ function parseSQLQueries(filePath, extraVariables) {
1094
1100
  }
1095
1101
  //#endregion
1096
1102
  //#region src/db/types.ts
1103
+ /**
1104
+ * Whether an error is an integrity constraint violation (foreign key, unique,
1105
+ * check, not-null) rather than a problem with the statement itself.
1106
+ */
1107
+ function isConstraintViolation(error) {
1108
+ const message = error?.message ?? "";
1109
+ const code = String(error?.code ?? "");
1110
+ return code.startsWith("SQLITE_CONSTRAINT") || /^23/.test(code) || /^Constraint Error/i.test(message) || /violates .* constraint|constraint failed/i.test(message);
1111
+ }
1112
+ /**
1113
+ * Type introspection executes every EXEC statement against whatever data the
1114
+ * MIGRATE and TESTDATA blocks left behind, so a statement can fail purely
1115
+ * because a constraint has nothing to reference — a NOT NULL foreign key can
1116
+ * never be satisfied when no fixture created the parent row.
1117
+ *
1118
+ * Running an EXEC contributes nothing to the generated code (the prepared
1119
+ * statement already supplied the parameter types), so such a failure is
1120
+ * reported as a warning instead of aborting generation. Statements whose
1121
+ * result shape SQG needs (QUERY, including INSERT ... RETURNING) still fail.
1122
+ */
1123
+ function warnConstraintViolation(query, error) {
1124
+ consola.warn(`Skipped executing '${query.id}' in ${query.filename} during type introspection: ${error.message}\n Introspection runs each statement against the TESTDATA fixtures only, so constraints such as foreign keys may not be satisfiable. Code generation is unaffected — add a TESTDATA block with the referenced rows to silence this warning.`);
1125
+ }
1097
1126
  async function initializeDatabase(queries, execQueries, reporter) {
1098
1127
  const baselineQueries = queries.filter((q) => q.isBaseline && !q.sourceTarget);
1099
1128
  for (const query of baselineQueries) try {
@@ -1241,6 +1270,10 @@ const duckdb = new class {
1241
1270
  }
1242
1271
  return await stmt.run();
1243
1272
  } catch (error) {
1273
+ if (!query.isQuery && isConstraintViolation(error)) {
1274
+ warnConstraintViolation(query, error);
1275
+ return;
1276
+ }
1244
1277
  consola.error(`Failed to execute query '${query.id}':`, error);
1245
1278
  throw error;
1246
1279
  }
@@ -1281,6 +1314,38 @@ const tempDatabaseName = "sqg-db-temp";
1281
1314
  const typeIdToName = /* @__PURE__ */ new Map();
1282
1315
  for (const [name, id] of Object.entries(types.builtins)) typeIdToName.set(Number(id), name);
1283
1316
  /**
1317
+ * Turn off referential integrity enforcement for the introspection connection.
1318
+ *
1319
+ * QUERY and EXEC statements are introspected against whatever data MIGRATE and
1320
+ * TESTDATA left behind, each in its own transaction, so a NOT NULL foreign key
1321
+ * can never be satisfied by a row an earlier statement inserted.
1322
+ * `session_replication_role = replica` disables the triggers enforcing it, for
1323
+ * this connection only, and every statement introspected here is rolled back.
1324
+ *
1325
+ * This happens after the database is initialized, so a foreign key violation in
1326
+ * a MIGRATE or TESTDATA block is still reported — those describe real data.
1327
+ *
1328
+ * Setting it needs superuser (or the SET privilege), so this is best effort:
1329
+ * without it, constraint violations on EXEC statements are still tolerated,
1330
+ * only INSERT ... RETURNING queries would fail.
1331
+ */
1332
+ async function relaxConstraints(db, inTransaction) {
1333
+ try {
1334
+ if (inTransaction) {
1335
+ await db.query("SAVEPOINT sqg_relax_constraints");
1336
+ try {
1337
+ await db.query("SET LOCAL session_replication_role = 'replica'");
1338
+ } catch (e) {
1339
+ await db.query("ROLLBACK TO SAVEPOINT sqg_relax_constraints");
1340
+ throw e;
1341
+ }
1342
+ await db.query("RELEASE SAVEPOINT sqg_relax_constraints");
1343
+ } else await db.query("SET session_replication_role = 'replica'");
1344
+ } catch (e) {
1345
+ consola.debug(`Could not disable constraint enforcement for introspection: ${e.message}`);
1346
+ }
1347
+ }
1348
+ /**
1284
1349
  * External database mode: connects directly to the user's database.
1285
1350
  * Uses a single transaction for the entire session and rolls back on close,
1286
1351
  * so the database is never modified. Individual queries use savepoints.
@@ -1307,6 +1372,9 @@ var ExternalDbMode = class {
1307
1372
  await db.query("ROLLBACK TO SAVEPOINT sqg_query");
1308
1373
  }
1309
1374
  }
1375
+ relaxConstraints(db) {
1376
+ return relaxConstraints(db, true);
1377
+ }
1310
1378
  async close(db) {
1311
1379
  await db.query("ROLLBACK");
1312
1380
  await db.end();
@@ -1356,6 +1424,9 @@ var TempDbMode = class {
1356
1424
  await db.query("ROLLBACK");
1357
1425
  }
1358
1426
  }
1427
+ relaxConstraints(db) {
1428
+ return relaxConstraints(db, false);
1429
+ }
1359
1430
  async close(db) {
1360
1431
  await db.end();
1361
1432
  await this.dbInitial.query(`DROP DATABASE IF EXISTS "${tempDatabaseName}"`);
@@ -1451,6 +1522,7 @@ const postgres = new class {
1451
1522
  async executeQueries(queries, reporter) {
1452
1523
  const db = this.db;
1453
1524
  if (!db) throw new DatabaseError("PostgreSQL database not initialized", "postgres", "This is an internal error. Check that migrations completed successfully.");
1525
+ await this.mode.relaxConstraints(db);
1454
1526
  try {
1455
1527
  const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
1456
1528
  for (const query of executableQueries) {
@@ -1517,6 +1589,10 @@ const postgres = new class {
1517
1589
  }
1518
1590
  return result;
1519
1591
  } catch (error) {
1592
+ if (!query.isQuery && isConstraintViolation(error)) {
1593
+ warnConstraintViolation(query, error);
1594
+ return;
1595
+ }
1520
1596
  consola.error(`Failed to execute query '${query.id}':`, error);
1521
1597
  throw error;
1522
1598
  }
@@ -1569,6 +1645,7 @@ const sqlite = new class {
1569
1645
  executeQueries(queries, reporter) {
1570
1646
  const db = this.db;
1571
1647
  if (!db) throw new DatabaseError("SQLite database not initialized", "sqlite", "This is an internal error. Migrations may have failed silently.");
1648
+ db.pragma("foreign_keys = OFF");
1572
1649
  try {
1573
1650
  const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
1574
1651
  for (const query of executableQueries) {
@@ -1669,6 +1746,10 @@ const sqlite = new class {
1669
1746
  }
1670
1747
  return stmt.run(...params);
1671
1748
  } catch (error) {
1749
+ if (!query.isQuery && isConstraintViolation(error)) {
1750
+ warnConstraintViolation(query, error);
1751
+ return;
1752
+ }
1672
1753
  consola.error(`Failed to execute query '${query.id}' in ${query.filename}:\n ${statement.sql} \n ${statement.parameters.map((p) => p.value).join(", ")}`, error);
1673
1754
  throw error;
1674
1755
  }
@@ -3964,7 +4045,7 @@ async function processProject(projectPath, ui) {
3964
4045
  //#region src/mcp-server.ts
3965
4046
  const server = new Server({
3966
4047
  name: "sqg-mcp",
3967
- version: process.env.npm_package_version ?? "0.22.0"
4048
+ version: process.env.npm_package_version ?? "0.23.0"
3968
4049
  }, { capabilities: {
3969
4050
  tools: {},
3970
4051
  resources: {}
@@ -4435,7 +4516,7 @@ function formatMs(ms) {
4435
4516
  }
4436
4517
  //#endregion
4437
4518
  //#region src/sqg.ts
4438
- const version = process.env.npm_package_version ?? "0.22.0";
4519
+ const version = process.env.npm_package_version ?? "0.23.0";
4439
4520
  updateNotifier({ pkg: {
4440
4521
  name: "@sqg/sqg",
4441
4522
  version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sqg/sqg",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",