qubu 0.7.2 → 0.7.3

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.
@@ -159,7 +159,7 @@ async function readCatalog(connection, options) {
159
159
  else mappedConstraints.push(index);
160
160
  }
161
161
  const foreignRows = await query(connection, sqliteForeignKeyQuery, [tableName, options.namespace], options, diagnostics, `foreign-key:${tableName}`);
162
- for (const value of foreignKeys(currentTable, foreignRows, tableByName, options.namespace, diagnostics)) if (value.kind === "opaque-object") opaqueObjects.push(value);
162
+ for (const value of foreignKeys(currentTable, foreignRows, sqlText, tableByName, options.namespace, diagnostics)) if (value.kind === "opaque-object") opaqueObjects.push(value);
163
163
  else mappedConstraints.push(value);
164
164
  currentTable.indexes = mappedIndexes;
165
165
  currentTable.constraints = mappedConstraints;
@@ -968,15 +968,94 @@ function mapIndex(table, row, infoRows, sqlText, namespace, diagnostics, opaqueO
968
968
  reference: reference("index", physicalName, namespace, "sqlite_schema", "name")
969
969
  };
970
970
  }
971
+ function constraintDefinitions(sqlText) {
972
+ if (!sqlText) return [];
973
+ const tokens = sqlText.match(/--[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/|'(?:[^']|'')*'|"(?:[^"]|"")*"|`(?:[^`]|``)*`|\[[^\]]*\]|[A-Za-z_][A-Za-z0-9_$]*|[^\s]/g) ?? [];
974
+ const definitions = [];
975
+ let depth = 0;
976
+ let current = [];
977
+ for (const token of tokens) {
978
+ if (token.startsWith("--") || token.startsWith("/*")) continue;
979
+ if (token === "(") {
980
+ if (depth++ === 0) continue;
981
+ } else if (token === ")") {
982
+ if (--depth === 0) {
983
+ definitions.push(current);
984
+ break;
985
+ }
986
+ }
987
+ if (depth === 1 && token === ",") {
988
+ definitions.push(current);
989
+ current = [];
990
+ } else if (depth > 0) current.push(token);
991
+ }
992
+ return definitions;
993
+ }
994
+ function constraintColumns(tokens, start) {
995
+ if (tokens[start] !== "(") return;
996
+ const columns = [];
997
+ for (let index = start + 1; index < tokens.length; index += 2) {
998
+ const name = unquoteIdentifier(tokens[index]);
999
+ if (name === void 0) return;
1000
+ columns.push(name);
1001
+ if (tokens[index + 1] === ")") return columns;
1002
+ if (tokens[index + 1] !== ",") return;
1003
+ }
1004
+ }
1005
+ function sameColumns(left, right) {
1006
+ return left !== void 0 && left.length === right.length && left.every((name, index) => name === right[index]);
1007
+ }
971
1008
  function declaredConstraintName(sqlText, keyword, columns) {
972
- if (!sqlText) return void 0;
973
- const pattern = new RegExp(`CONSTRAINT\\s+("(?:[^"]|"")*"|\`[^\`]*\`|\\[[^\\]]*\\]|[A-Za-z_][A-Za-z0-9_\$]*)\\s+${keyword}\\s*\\(([^)]*)\\)`, "gi");
974
- for (const match of sqlText.matchAll(pattern)) {
975
- const found = (match[2] ?? "").split(",").map((value) => unquoteIdentifier(value.trim())).filter((value) => value !== void 0);
976
- if (found.length === columns.length && found.every((value, index) => value === columns[index])) return unquoteIdentifier(match[1]);
1009
+ const words = keyword === "UNIQUE" ? ["UNIQUE"] : ["PRIMARY", "KEY"];
1010
+ const names = [];
1011
+ for (const tokens of constraintDefinitions(sqlText)) for (let index = 0; index < tokens.length; index++) {
1012
+ if (tokens[index]?.toUpperCase() !== "CONSTRAINT" || !words.every((word, offset) => tokens[index + 2 + offset]?.toUpperCase() === word)) continue;
1013
+ const start = index + 2 + words.length;
1014
+ const found = tokens[start] === "(" ? constraintColumns(tokens, start) : index > 0 ? [unquoteIdentifier(tokens[0])].filter((name) => name !== void 0) : void 0;
1015
+ const name = unquoteIdentifier(tokens[index + 1]);
1016
+ if (name !== void 0 && sameColumns(found, columns)) names.push(name);
977
1017
  }
1018
+ return names.length === 1 ? names[0] : void 0;
978
1019
  }
979
- function foreignKeys(table, rows, tables, namespace, diagnostics) {
1020
+ function declaredForeignKeyName(sqlText, rows) {
1021
+ const names = [];
1022
+ const first = rows[0];
1023
+ for (const tokens of constraintDefinitions(sqlText)) for (let index = 0; index < tokens.length; index++) {
1024
+ const named = tokens[index]?.toUpperCase() === "CONSTRAINT";
1025
+ const unnamedTable = index === 0 && tokens[index]?.toUpperCase() === "FOREIGN";
1026
+ const unnamedColumn = index > 0 && tokens[index]?.toUpperCase() === "REFERENCES" && tokens[index - 2]?.toUpperCase() !== "CONSTRAINT";
1027
+ if (!named && !unnamedTable && !unnamedColumn) continue;
1028
+ const name = named ? unquoteIdentifier(tokens[index + 1]) : void 0;
1029
+ let cursor = named ? index + 2 : index;
1030
+ let source;
1031
+ if (tokens[cursor]?.toUpperCase() === "FOREIGN" && tokens[cursor + 1]?.toUpperCase() === "KEY") {
1032
+ source = constraintColumns(tokens, cursor + 2);
1033
+ if (!source) continue;
1034
+ cursor += 3 + source.length * 2;
1035
+ } else if (index > 0) {
1036
+ const column = unquoteIdentifier(tokens[0]);
1037
+ source = column === void 0 ? void 0 : [column];
1038
+ }
1039
+ if (tokens[cursor]?.toUpperCase() !== "REFERENCES" || !sameColumns(source, rows.map((row) => text(row.source_column))) || unquoteIdentifier(tokens[cursor + 1]) !== text(first.target_table)) continue;
1040
+ cursor += 2;
1041
+ const target = constraintColumns(tokens, cursor);
1042
+ if (target) cursor += 1 + target.length * 2;
1043
+ if (target ? !sameColumns(target, rows.map((row) => text(row.target_column))) : rows.some((row) => text(row.target_column) !== void 0)) continue;
1044
+ let onUpdate = "no-action";
1045
+ let onDelete = "no-action";
1046
+ for (; cursor < tokens.length; cursor++) {
1047
+ if (tokens[cursor]?.toUpperCase() !== "ON") continue;
1048
+ const clause = tokens[cursor + 1]?.toUpperCase();
1049
+ const verb = tokens[cursor + 2]?.toUpperCase();
1050
+ const value = verb === "NO" || verb === "SET" ? `${verb} ${tokens[cursor + 3]}` : verb;
1051
+ if (clause === "UPDATE") onUpdate = action(value) ?? "no-action";
1052
+ if (clause === "DELETE") onDelete = action(value) ?? "no-action";
1053
+ }
1054
+ if (onUpdate === (action(first.on_update) ?? "no-action") && onDelete === (action(first.on_delete) ?? "no-action")) names.push(name);
1055
+ }
1056
+ return names.length === 1 ? names[0] : void 0;
1057
+ }
1058
+ function foreignKeys(table, rows, sqlText, tables, namespace, diagnostics) {
980
1059
  const grouped = /* @__PURE__ */ new Map();
981
1060
  for (const row of rows) {
982
1061
  const id = text(row.id) ?? "0";
@@ -987,7 +1066,8 @@ function foreignKeys(table, rows, tables, namespace, diagnostics) {
987
1066
  return [...grouped.entries()].map(([key, group]) => {
988
1067
  const ordered = [...group].sort((left, right) => (number(left.seq) ?? 0) - (number(right.seq) ?? 0));
989
1068
  const first = ordered[0];
990
- const physicalName = `foreign_key_${table.physicalName}_${key}`;
1069
+ const declaredName = declaredForeignKeyName(sqlText, ordered);
1070
+ const physicalName = declaredName ?? `foreign_key_${table.physicalName}_${key}`;
991
1071
  const targetTableName = text(first.target_table);
992
1072
  const targetTable = targetTableName === void 0 ? void 0 : tables.get(targetTableName);
993
1073
  const targetColumns = ordered.map((row) => text(row.target_column));
@@ -1026,7 +1106,7 @@ function foreignKeys(table, rows, tables, namespace, diagnostics) {
1026
1106
  return {
1027
1107
  kind: "foreign-key",
1028
1108
  id: stableId(physicalName),
1029
- identitySource: "deterministic-fallback",
1109
+ identitySource: declaredName ? "physical-name" : "deterministic-fallback",
1030
1110
  physicalName,
1031
1111
  columns: sourceColumns,
1032
1112
  target: {
@@ -4,7 +4,7 @@ import { Ad as MetadataOf, Dr as UpdateFromClause, Gu as ExpressionWithOutput, K
4
4
  * PostgreSQL's only core rendering difference is positional parameters. PostgreSQL-specific
5
5
  * expressions and clauses should remain separate modules.
6
6
  */
7
- declare function postgresDialect(): Dialect<"json" | "ilike" | "on-conflict" | "row-locking" | "update-from">;
7
+ declare function postgresDialect(): Dialect<"on-conflict" | "json" | "ilike" | "row-locking" | "update-from">;
8
8
  /** PostgreSQL's case-insensitive pattern-match operator. */
9
9
  declare function ilike<TLeft extends ExpressionWithOutput<string>, R extends Operand<string>>(left: TLeft & ComparisonValidation<TLeft, R, "ILIKE">, pattern: R): SchemaExpression<ResultMeta<boolean, ((MetadataOf<TLeft & SqlCapabilityValidation<(MetadataOf<TLeft> extends (infer T) ? T extends MetadataOf<TLeft> ? T extends {
10
10
  readonly kind: "result";
package/dist/sqlite.d.mts CHANGED
@@ -27,6 +27,6 @@ type SqliteTimestampColumn<TOptions extends SqliteTimestampOptions = {}> = Colum
27
27
  }>;
28
28
  /** Declare a SQLite `INTEGER` timestamp with a live Date codec. */
29
29
  declare function sqliteTimestamp<const TOptions extends SqliteTimestampOptions = {}>(options?: TOptions): SqliteTimestampColumn<TOptions>;
30
- declare function sqliteDialect(): Dialect<"json" | "on-conflict">;
30
+ declare function sqliteDialect(): Dialect<"on-conflict" | "json">;
31
31
  //#endregion
32
32
  export { type ConflictAction, type ConflictTarget, type DoNothingAction, type DoUpdateAction, type ExcludedSource, type OnConflictClause, SqliteTimestampColumn, SqliteTimestampMode, SqliteTimestampOptions, doNothing, doUpdate, excluded, onConflict, sqliteDialect, sqliteTimestamp };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qubu",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/aleclarson/qubu"