qubu 0.7.1 → 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
|
-
|
|
973
|
-
const
|
|
974
|
-
for (const
|
|
975
|
-
|
|
976
|
-
|
|
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
|
|
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
|
|
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: {
|
|
@@ -9,8 +9,10 @@ libSQL application:
|
|
|
9
9
|
pnpm add @qubu/cli @qubu/migrate @qubu/adapter-libsql @libsql/client
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
The `qubu` command
|
|
13
|
-
|
|
12
|
+
The `qubu` command looks for `qubu.config.js`, then `qubu.config.ts`, in the
|
|
13
|
+
current working directory. If both exist, `.js` takes precedence. Use
|
|
14
|
+
`--config <path>` to select a configuration module explicitly. TypeScript
|
|
15
|
+
configs require a runtime that can import TypeScript; Qubu does not transpile them.
|
|
14
16
|
|
|
15
17
|
Every command accepts:
|
|
16
18
|
|