sqlite-hub 0.12.0 → 0.17.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.
- package/README.md +118 -23
- package/bin/sqlite-hub.js +1041 -224
- package/frontend/index.html +1 -0
- package/frontend/js/api.js +32 -2
- package/frontend/js/app.js +989 -13
- package/frontend/js/components/modal.js +644 -15
- package/frontend/js/components/pageHeader.js +14 -16
- package/frontend/js/components/queryEditor.js +9 -1
- package/frontend/js/components/queryHistoryPanel.js +126 -131
- package/frontend/js/components/queryResults.js +79 -17
- package/frontend/js/components/rowEditorPanel.js +204 -10
- package/frontend/js/components/sidebar.js +102 -18
- package/frontend/js/components/tableDesignerEditor.js +69 -11
- package/frontend/js/router.js +8 -0
- package/frontend/js/store.js +868 -9
- package/frontend/js/utils/copyColumnExport.js +117 -0
- package/frontend/js/utils/exportFilenames.js +32 -0
- package/frontend/js/utils/filePathPreview.js +315 -0
- package/frontend/js/utils/format.js +1 -1
- package/frontend/js/utils/markdownDocuments.js +248 -0
- package/frontend/js/utils/rowEditorJson.js +65 -0
- package/frontend/js/utils/sqlFormatter.js +691 -0
- package/frontend/js/utils/tableDesigner.js +178 -6
- package/frontend/js/utils/textCellStats.js +20 -0
- package/frontend/js/utils/timestampPreview.js +264 -0
- package/frontend/js/views/charts.js +3 -1
- package/frontend/js/views/data.js +34 -2
- package/frontend/js/views/documents.js +300 -0
- package/frontend/js/views/editor.js +48 -1
- package/frontend/js/views/settings.js +39 -6
- package/frontend/js/views/structure.js +154 -212
- package/frontend/styles/base.css +6 -0
- package/frontend/styles/components.css +476 -2
- package/frontend/styles/structure-graph.css +0 -3
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/tokens.css +1 -1
- package/frontend/styles/views.css +422 -0
- package/package.json +3 -3
- package/server/routes/documents.js +163 -0
- package/server/routes/settings.js +22 -6
- package/server/server.js +6 -0
- package/server/services/sqlite/introspection.js +10 -0
- package/server/services/sqlite/sqlExecutor.js +29 -0
- package/server/services/sqlite/tableDesigner/changeAnalysis.js +25 -1
- package/server/services/sqlite/tableDesigner/schemaMapping.js +105 -10
- package/server/services/sqlite/tableDesigner/validation.js +60 -2
- package/server/services/sqlite/tableDesignerService.js +1 -1
- package/server/services/storage/appStateStore.js +313 -0
- package/tests/check-constraint-options.test.js +14 -0
- package/tests/cli-args.test.js +100 -0
- package/tests/copy-column-modal.test.js +83 -0
- package/tests/database-documents.test.js +85 -0
- package/tests/export-filenames.test.js +34 -0
- package/tests/file-path-preview.test.js +165 -0
- package/tests/markdown-documents.test.js +79 -0
- package/tests/row-editor-json.test.js +82 -0
- package/tests/row-editor-timestamp-preview.test.js +192 -0
- package/tests/settings-metadata.test.js +16 -0
- package/tests/settings-view.test.js +32 -0
- package/tests/sql-formatter.test.js +173 -0
- package/tests/sql-highlight.test.js +38 -0
- package/tests/table-designer-v2-unique-constraints.test.js +78 -0
- package/tests/text-cell-stats.test.js +38 -0
- package/fill.js +0 -526
|
@@ -345,12 +345,20 @@ function getTableDetail(db, tableName, options = {}) {
|
|
|
345
345
|
const foreignKeys = groupForeignKeys(
|
|
346
346
|
db.prepare(`PRAGMA foreign_key_list(${quoteIdentifier(tableName)})`).all()
|
|
347
347
|
);
|
|
348
|
+
const checkConstraints = extractCheckExpressions(entry.sql).map((expression, index) => ({
|
|
349
|
+
id: index,
|
|
350
|
+
expression: expression.trim(),
|
|
351
|
+
}));
|
|
348
352
|
|
|
349
353
|
const indexList = db
|
|
350
354
|
.prepare(`PRAGMA index_list(${quoteIdentifier(tableName)})`)
|
|
351
355
|
.all()
|
|
352
356
|
.map((indexEntry) => {
|
|
353
357
|
let indexColumns = [];
|
|
358
|
+
const indexSql =
|
|
359
|
+
db
|
|
360
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type = 'index' AND name = ?")
|
|
361
|
+
.get(indexEntry.name)?.sql ?? null;
|
|
354
362
|
|
|
355
363
|
try {
|
|
356
364
|
indexColumns = db
|
|
@@ -380,6 +388,7 @@ function getTableDetail(db, tableName, options = {}) {
|
|
|
380
388
|
unique: Boolean(indexEntry.unique),
|
|
381
389
|
origin: indexEntry.origin,
|
|
382
390
|
partial: Boolean(indexEntry.partial),
|
|
391
|
+
sql: indexSql,
|
|
383
392
|
columns: indexColumns,
|
|
384
393
|
};
|
|
385
394
|
});
|
|
@@ -402,6 +411,7 @@ function getTableDetail(db, tableName, options = {}) {
|
|
|
402
411
|
withoutRowId,
|
|
403
412
|
strict: Boolean(tableListEntry?.strict),
|
|
404
413
|
columns,
|
|
414
|
+
checkConstraints,
|
|
405
415
|
foreignKeys,
|
|
406
416
|
indexes: indexList,
|
|
407
417
|
indexCount: indexList.length,
|
|
@@ -204,6 +204,13 @@ function mapEditableColumns(tableDetail, columnDefinitions) {
|
|
|
204
204
|
const tableColumnsByName = new Map(
|
|
205
205
|
tableDetail.columns.map((column) => [column.name, column])
|
|
206
206
|
);
|
|
207
|
+
const foreignKeyColumnNames = new Set(
|
|
208
|
+
(tableDetail.foreignKeys ?? []).flatMap((foreignKey) =>
|
|
209
|
+
(foreignKey.mappings ?? [])
|
|
210
|
+
.map((mapping) => String(mapping.from ?? "").trim())
|
|
211
|
+
.filter(Boolean)
|
|
212
|
+
)
|
|
213
|
+
);
|
|
207
214
|
const identityColumns = new Set(
|
|
208
215
|
tableDetail.identityStrategy?.type === "primaryKey"
|
|
209
216
|
? tableDetail.identityStrategy.columns ?? []
|
|
@@ -223,12 +230,31 @@ function mapEditableColumns(tableDetail, columnDefinitions) {
|
|
|
223
230
|
visible: isRowId ? true : Boolean(columnMeta?.visible),
|
|
224
231
|
generated: Boolean(columnMeta?.generated),
|
|
225
232
|
identity: identityColumns.has(definition.column),
|
|
233
|
+
declaredType: columnMeta?.declaredType ?? "",
|
|
234
|
+
affinity: columnMeta?.affinity ?? "",
|
|
235
|
+
primaryKeyPosition: Number(columnMeta?.primaryKeyPosition ?? 0),
|
|
236
|
+
foreignKey: foreignKeyColumnNames.has(definition.column),
|
|
226
237
|
notNull: Boolean(columnMeta?.notNull),
|
|
227
238
|
allowedValues: columnMeta?.allowedValues ?? [],
|
|
228
239
|
};
|
|
229
240
|
});
|
|
230
241
|
}
|
|
231
242
|
|
|
243
|
+
function buildTableMeta(tableDetail) {
|
|
244
|
+
return {
|
|
245
|
+
columns: (tableDetail.columns ?? []).map((column) => ({
|
|
246
|
+
name: column.name,
|
|
247
|
+
declaredType: column.declaredType,
|
|
248
|
+
affinity: column.affinity,
|
|
249
|
+
primaryKeyPosition: Number(column.primaryKeyPosition ?? 0),
|
|
250
|
+
foreignKey: (tableDetail.foreignKeys ?? []).some((foreignKey) =>
|
|
251
|
+
(foreignKey.mappings ?? []).some((mapping) => mapping.from === column.name)
|
|
252
|
+
),
|
|
253
|
+
})),
|
|
254
|
+
foreignKeys: tableDetail.foreignKeys ?? [],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
232
258
|
function hasRequiredIdentityColumns(tableDetail, editableColumns) {
|
|
233
259
|
const availableSourceColumns = new Set(editableColumns.map((column) => column.sourceColumn));
|
|
234
260
|
|
|
@@ -301,6 +327,7 @@ function resolveEditableResult(db, columnDefinitions, serializedRows) {
|
|
|
301
327
|
reason: getEditableResultReason(tableDetail),
|
|
302
328
|
columns: editableColumns,
|
|
303
329
|
identityStrategy: tableDetail.identityStrategy,
|
|
330
|
+
tableMeta: buildTableMeta(tableDetail),
|
|
304
331
|
};
|
|
305
332
|
}
|
|
306
333
|
|
|
@@ -319,6 +346,7 @@ function resolveEditableResult(db, columnDefinitions, serializedRows) {
|
|
|
319
346
|
reason: "",
|
|
320
347
|
columns: editableColumns,
|
|
321
348
|
identityStrategy: tableDetail.identityStrategy,
|
|
349
|
+
tableMeta: buildTableMeta(tableDetail),
|
|
322
350
|
rows,
|
|
323
351
|
};
|
|
324
352
|
}
|
|
@@ -378,6 +406,7 @@ class SqlExecutor {
|
|
|
378
406
|
reason: editableResult.reason ?? "",
|
|
379
407
|
columns: editableResult.columns ?? [],
|
|
380
408
|
identityStrategy: editableResult.identityStrategy ?? null,
|
|
409
|
+
tableMeta: editableResult.tableMeta ?? null,
|
|
381
410
|
}
|
|
382
411
|
: null,
|
|
383
412
|
};
|
|
@@ -26,6 +26,17 @@ function hasColumnChanged(originalColumn, draftColumn) {
|
|
|
26
26
|
);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function hasConstraintChanged(constraint) {
|
|
30
|
+
return (
|
|
31
|
+
normalizeComparableValue(constraint.name) !==
|
|
32
|
+
normalizeComparableValue(constraint.originalName ?? constraint.name) ||
|
|
33
|
+
normalizeComparableValue(constraint.expression || constraint.sql) !==
|
|
34
|
+
normalizeComparableValue(
|
|
35
|
+
constraint.originalExpression ?? constraint.originalSql ?? constraint.expression ?? constraint.sql
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
function hasMeaningfulNewDraftContent(draft) {
|
|
30
41
|
return (
|
|
31
42
|
Boolean(String(draft.tableName ?? "").trim()) ||
|
|
@@ -149,7 +160,7 @@ function analyzeEditDraft(draft, originalDraft) {
|
|
|
149
160
|
warnings.push(
|
|
150
161
|
buildRiskyChangeWarning(
|
|
151
162
|
"Column Rename Requires Rebuild",
|
|
152
|
-
`Renaming column ${originalColumn.name} to ${column.name} is intentionally blocked in Table Designer
|
|
163
|
+
`Renaming column ${originalColumn.name} to ${column.name} is intentionally blocked in Table Designer v2.`
|
|
153
164
|
)
|
|
154
165
|
);
|
|
155
166
|
}
|
|
@@ -265,6 +276,19 @@ function analyzeEditDraft(draft, originalDraft) {
|
|
|
265
276
|
);
|
|
266
277
|
});
|
|
267
278
|
|
|
279
|
+
[...(draft.uniqueConstraints ?? []), ...(draft.checkConstraints ?? [])].forEach((constraint) => {
|
|
280
|
+
if (!hasConstraintChanged(constraint)) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
warnings.push(
|
|
285
|
+
buildRiskyChangeWarning(
|
|
286
|
+
"Constraint Change Requires Rebuild",
|
|
287
|
+
`Changing ${constraint.originalName || constraint.name || "a table constraint"} requires a SQLite table rebuild.`
|
|
288
|
+
)
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
|
|
268
292
|
const dirty = Boolean(executableStatements.length || warnings.length);
|
|
269
293
|
|
|
270
294
|
return {
|
|
@@ -74,6 +74,95 @@ function buildSimpleForeignKeyMap(foreignKeys = []) {
|
|
|
74
74
|
return map;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function buildUniqueConstraintExpression(index = {}) {
|
|
78
|
+
const columns = (index.columns ?? [])
|
|
79
|
+
.map((column) => column?.name)
|
|
80
|
+
.filter(Boolean);
|
|
81
|
+
|
|
82
|
+
if (!columns.length) {
|
|
83
|
+
return "UNIQUE constraint";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return `UNIQUE (${columns.map((columnName) => quoteIdentifier(columnName)).join(", ")})`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function mapComplexUniqueConstraint(index = {}) {
|
|
90
|
+
const sql = String(index.sql ?? "").trim();
|
|
91
|
+
const columns = (index.columns ?? [])
|
|
92
|
+
.map((column) => ({
|
|
93
|
+
name: column.name ?? "",
|
|
94
|
+
descending: Boolean(column.descending),
|
|
95
|
+
collation: column.collation ?? "",
|
|
96
|
+
}))
|
|
97
|
+
.filter((column) => column.name);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
id: `unique:${index.name}`,
|
|
101
|
+
name: index.name ?? "",
|
|
102
|
+
originalName: index.name ?? "",
|
|
103
|
+
columns,
|
|
104
|
+
partial: Boolean(index.partial),
|
|
105
|
+
origin: index.origin ?? "",
|
|
106
|
+
sql,
|
|
107
|
+
originalSql: sql,
|
|
108
|
+
expression: sql || buildUniqueConstraintExpression(index),
|
|
109
|
+
originalExpression: sql || buildUniqueConstraintExpression(index),
|
|
110
|
+
editable: true,
|
|
111
|
+
preserved: true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildComplexUniqueConstraints(indexes = []) {
|
|
116
|
+
return indexes
|
|
117
|
+
.filter((index) => index?.unique && (index.partial || (index.columns?.length ?? 0) !== 1))
|
|
118
|
+
.map(mapComplexUniqueConstraint);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function expressionMentionsColumn(expression = "", columnName = "") {
|
|
122
|
+
const normalizedExpression = String(expression ?? "").toLowerCase();
|
|
123
|
+
const normalizedColumn = String(columnName ?? "").toLowerCase();
|
|
124
|
+
|
|
125
|
+
if (!normalizedColumn) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
new RegExp(`(^|[^a-z0-9_$])${normalizedColumn.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([^a-z0-9_$]|$)`).test(
|
|
131
|
+
normalizedExpression
|
|
132
|
+
) ||
|
|
133
|
+
normalizedExpression.includes(`"${normalizedColumn.replaceAll('"', '""')}"`) ||
|
|
134
|
+
normalizedExpression.includes(`\`${normalizedColumn.replaceAll("`", "``")}\``) ||
|
|
135
|
+
normalizedExpression.includes(`[${normalizedColumn}]`)
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildCheckConstraints(tableDetail) {
|
|
140
|
+
const visibleColumns = (tableDetail.columns ?? []).filter(
|
|
141
|
+
(column) => column.visible !== false && !column.generated
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return (tableDetail.checkConstraints ?? []).map((constraint, index) => {
|
|
145
|
+
const expression = String(constraint.expression ?? "").trim();
|
|
146
|
+
const columns = visibleColumns
|
|
147
|
+
.filter((column) => expressionMentionsColumn(expression, column.name))
|
|
148
|
+
.map((column) => ({
|
|
149
|
+
name: column.name,
|
|
150
|
+
allowedValues: Array.isArray(column.allowedValues) ? column.allowedValues : [],
|
|
151
|
+
}));
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
id: `check:${constraint.id ?? index}`,
|
|
155
|
+
name: `CHECK ${index + 1}`,
|
|
156
|
+
originalName: `CHECK ${index + 1}`,
|
|
157
|
+
columns,
|
|
158
|
+
expression: expression ? `CHECK (${expression})` : "CHECK constraint",
|
|
159
|
+
originalExpression: expression ? `CHECK (${expression})` : "CHECK constraint",
|
|
160
|
+
editable: true,
|
|
161
|
+
preserved: true,
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
77
166
|
function buildSchemaWarnings(tableDetail) {
|
|
78
167
|
const warnings = [];
|
|
79
168
|
const generatedColumns = (tableDetail.columns ?? []).filter((column) => column.generated);
|
|
@@ -83,9 +172,7 @@ function buildSchemaWarnings(tableDetail) {
|
|
|
83
172
|
const complexForeignKeys = (tableDetail.foreignKeys ?? []).filter(
|
|
84
173
|
(foreignKey) => (foreignKey.mappings?.length ?? 0) !== 1
|
|
85
174
|
);
|
|
86
|
-
const
|
|
87
|
-
(index) => index.unique && (index.partial || (index.columns?.length ?? 0) !== 1)
|
|
88
|
-
);
|
|
175
|
+
const complexUniqueConstraints = buildComplexUniqueConstraints(tableDetail.indexes);
|
|
89
176
|
|
|
90
177
|
if (generatedColumns.length) {
|
|
91
178
|
warnings.push(
|
|
@@ -93,7 +180,7 @@ function buildSchemaWarnings(tableDetail) {
|
|
|
93
180
|
code: "GENERATED_COLUMNS_PRESENT",
|
|
94
181
|
title: "Generated Columns Detected",
|
|
95
182
|
message:
|
|
96
|
-
"Generated or hidden columns are not editable in Table Designer
|
|
183
|
+
"Generated or hidden columns are inspectable but not editable in Table Designer v2. Safe operations like table rename or adding simple columns still work.",
|
|
97
184
|
})
|
|
98
185
|
);
|
|
99
186
|
}
|
|
@@ -104,7 +191,7 @@ function buildSchemaWarnings(tableDetail) {
|
|
|
104
191
|
code: "COMPOSITE_PRIMARY_KEY_PRESENT",
|
|
105
192
|
title: "Composite Primary Key Detected",
|
|
106
193
|
message:
|
|
107
|
-
"This table uses more than one primary key column. Table Designer
|
|
194
|
+
"This table uses more than one primary key column. Table Designer v2 preserves it, but changing primary key structure requires a manual table rebuild.",
|
|
108
195
|
})
|
|
109
196
|
);
|
|
110
197
|
}
|
|
@@ -115,18 +202,19 @@ function buildSchemaWarnings(tableDetail) {
|
|
|
115
202
|
code: "COMPLEX_FOREIGN_KEYS_PRESENT",
|
|
116
203
|
title: "Complex Foreign Keys Detected",
|
|
117
204
|
message:
|
|
118
|
-
"Composite or multi-mapping foreign keys cannot be edited directly in Table Designer
|
|
205
|
+
"Composite or multi-mapping foreign keys cannot be edited directly in Table Designer v2. They are preserved until a rebuild is done manually.",
|
|
119
206
|
})
|
|
120
207
|
);
|
|
121
208
|
}
|
|
122
209
|
|
|
123
|
-
if (
|
|
210
|
+
if (complexUniqueConstraints.length) {
|
|
124
211
|
warnings.push(
|
|
125
212
|
createDesignerWarning({
|
|
126
213
|
code: "COMPLEX_UNIQUE_CONSTRAINTS_PRESENT",
|
|
127
|
-
title: "
|
|
214
|
+
title: "Table Designer v2 Unique Constraints",
|
|
128
215
|
message:
|
|
129
|
-
"Multi-column
|
|
216
|
+
"Multi-column and partial UNIQUE constraints are detected, shown in the v2 constraints panel, and preserved by SQLite-safe saves. Editing them still requires manual SQL review.",
|
|
217
|
+
tone: "muted",
|
|
130
218
|
})
|
|
131
219
|
);
|
|
132
220
|
}
|
|
@@ -149,7 +237,7 @@ function buildSchemaWarnings(tableDetail) {
|
|
|
149
237
|
code: "WITHOUT_ROWID_PRESENT",
|
|
150
238
|
title: "WITHOUT ROWID Table",
|
|
151
239
|
message:
|
|
152
|
-
"WITHOUT ROWID tables can be inspected here, but rebuild-style changes are intentionally blocked in
|
|
240
|
+
"WITHOUT ROWID tables can be inspected here, but rebuild-style changes are intentionally blocked in v2.",
|
|
153
241
|
tone: "muted",
|
|
154
242
|
})
|
|
155
243
|
);
|
|
@@ -189,6 +277,8 @@ function mapTableColumnToDraft(column, { uniqueColumns, foreignKeyMap }) {
|
|
|
189
277
|
function buildTableDesignerDraft(tableDetail) {
|
|
190
278
|
const uniqueColumns = buildSingleColumnUniqueSet(tableDetail.indexes);
|
|
191
279
|
const foreignKeyMap = buildSimpleForeignKeyMap(tableDetail.foreignKeys);
|
|
280
|
+
const uniqueConstraints = buildComplexUniqueConstraints(tableDetail.indexes);
|
|
281
|
+
const checkConstraints = buildCheckConstraints(tableDetail);
|
|
192
282
|
const columns = (tableDetail.columns ?? [])
|
|
193
283
|
.filter((column) => column.visible !== false && !column.generated)
|
|
194
284
|
.map((column) => mapTableColumnToDraft(column, { uniqueColumns, foreignKeyMap }));
|
|
@@ -199,6 +289,9 @@ function buildTableDesignerDraft(tableDetail) {
|
|
|
199
289
|
originalTableName: tableDetail.name,
|
|
200
290
|
tableName: tableDetail.name,
|
|
201
291
|
columns,
|
|
292
|
+
uniqueConstraints,
|
|
293
|
+
checkConstraints,
|
|
294
|
+
designerVersion: 2,
|
|
202
295
|
dirty: false,
|
|
203
296
|
schemaWarnings,
|
|
204
297
|
warnings: [...schemaWarnings],
|
|
@@ -227,6 +320,8 @@ function listDesignerTables(db) {
|
|
|
227
320
|
module.exports = {
|
|
228
321
|
SUPPORTED_TABLE_DESIGNER_TYPES,
|
|
229
322
|
buildTableDesignerDraft,
|
|
323
|
+
buildComplexUniqueConstraints,
|
|
324
|
+
buildCheckConstraints,
|
|
230
325
|
createDesignerWarning,
|
|
231
326
|
listDesignerTables,
|
|
232
327
|
normalizeDesignerType,
|
|
@@ -66,6 +66,53 @@ function normalizeColumnPayload(column = {}, index = 0) {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function normalizeUniqueConstraintPayload(constraint = {}, index = 0) {
|
|
70
|
+
return {
|
|
71
|
+
id: String(constraint.id ?? `unique:${index}`),
|
|
72
|
+
name: String(constraint.name ?? "").trim(),
|
|
73
|
+
originalName: String(constraint.originalName ?? constraint.name ?? "").trim(),
|
|
74
|
+
columns: Array.isArray(constraint.columns)
|
|
75
|
+
? constraint.columns
|
|
76
|
+
.map((column) => ({
|
|
77
|
+
name: String(column?.name ?? "").trim(),
|
|
78
|
+
descending: normalizeBoolean(column?.descending),
|
|
79
|
+
collation: String(column?.collation ?? "").trim(),
|
|
80
|
+
}))
|
|
81
|
+
.filter((column) => column.name)
|
|
82
|
+
: [],
|
|
83
|
+
partial: normalizeBoolean(constraint.partial),
|
|
84
|
+
origin: String(constraint.origin ?? "").trim(),
|
|
85
|
+
sql: String(constraint.sql ?? "").trim(),
|
|
86
|
+
originalSql: String(constraint.originalSql ?? constraint.sql ?? "").trim(),
|
|
87
|
+
expression: String(constraint.expression ?? "").trim(),
|
|
88
|
+
originalExpression: String(constraint.originalExpression ?? constraint.expression ?? constraint.sql ?? "").trim(),
|
|
89
|
+
editable: normalizeBoolean(constraint.editable),
|
|
90
|
+
preserved: constraint.preserved === undefined ? true : normalizeBoolean(constraint.preserved),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeCheckConstraintPayload(constraint = {}, index = 0) {
|
|
95
|
+
return {
|
|
96
|
+
id: String(constraint.id ?? `check:${index}`),
|
|
97
|
+
name: String(constraint.name ?? `CHECK ${index + 1}`).trim(),
|
|
98
|
+
originalName: String(constraint.originalName ?? constraint.name ?? `CHECK ${index + 1}`).trim(),
|
|
99
|
+
columns: Array.isArray(constraint.columns)
|
|
100
|
+
? constraint.columns
|
|
101
|
+
.map((column) => ({
|
|
102
|
+
name: String(column?.name ?? "").trim(),
|
|
103
|
+
allowedValues: Array.isArray(column?.allowedValues)
|
|
104
|
+
? column.allowedValues.map((value) => String(value ?? ""))
|
|
105
|
+
: [],
|
|
106
|
+
}))
|
|
107
|
+
.filter((column) => column.name)
|
|
108
|
+
: [],
|
|
109
|
+
expression: String(constraint.expression ?? "").trim(),
|
|
110
|
+
originalExpression: String(constraint.originalExpression ?? constraint.expression ?? "").trim(),
|
|
111
|
+
editable: normalizeBoolean(constraint.editable),
|
|
112
|
+
preserved: constraint.preserved === undefined ? true : normalizeBoolean(constraint.preserved),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
69
116
|
function normalizeDraftPayload(payload = {}) {
|
|
70
117
|
const mode = String(payload.mode ?? "create").trim() === "edit" ? "edit" : "create";
|
|
71
118
|
|
|
@@ -76,6 +123,17 @@ function normalizeDraftPayload(payload = {}) {
|
|
|
76
123
|
columns: Array.isArray(payload.columns)
|
|
77
124
|
? payload.columns.map((column, index) => normalizeColumnPayload(column, index))
|
|
78
125
|
: [],
|
|
126
|
+
uniqueConstraints: Array.isArray(payload.uniqueConstraints)
|
|
127
|
+
? payload.uniqueConstraints.map((constraint, index) =>
|
|
128
|
+
normalizeUniqueConstraintPayload(constraint, index)
|
|
129
|
+
)
|
|
130
|
+
: [],
|
|
131
|
+
checkConstraints: Array.isArray(payload.checkConstraints)
|
|
132
|
+
? payload.checkConstraints.map((constraint, index) =>
|
|
133
|
+
normalizeCheckConstraintPayload(constraint, index)
|
|
134
|
+
)
|
|
135
|
+
: [],
|
|
136
|
+
designerVersion: Number(payload.designerVersion) || 1,
|
|
79
137
|
schemaWarnings: Array.isArray(payload.schemaWarnings) ? payload.schemaWarnings : [],
|
|
80
138
|
warnings: Array.isArray(payload.warnings) ? payload.warnings : [],
|
|
81
139
|
fillImportedRows: normalizeBoolean(payload.fillImportedRows),
|
|
@@ -112,7 +170,7 @@ function validatePrimaryKeys(draft, originalDraft) {
|
|
|
112
170
|
|
|
113
171
|
if (!originalDraft) {
|
|
114
172
|
throw new ValidationError(
|
|
115
|
-
"Table Designer
|
|
173
|
+
"Table Designer v2 supports a single primary key column when creating a table."
|
|
116
174
|
);
|
|
117
175
|
}
|
|
118
176
|
|
|
@@ -134,7 +192,7 @@ function validatePrimaryKeys(draft, originalDraft) {
|
|
|
134
192
|
|
|
135
193
|
if (!isUnchangedCompositePrimaryKey) {
|
|
136
194
|
throw new ValidationError(
|
|
137
|
-
"Table Designer
|
|
195
|
+
"Table Designer v2 does not support editing composite primary keys."
|
|
138
196
|
);
|
|
139
197
|
}
|
|
140
198
|
}
|
|
@@ -109,7 +109,7 @@ class TableDesignerService {
|
|
|
109
109
|
|
|
110
110
|
if (!analysis.executable) {
|
|
111
111
|
throw new ValidationError(
|
|
112
|
-
"This schema change set would require a SQLite table rebuild. Table Designer
|
|
112
|
+
"This schema change set would require a SQLite table rebuild. Table Designer v2 keeps the SQL preview available but will not execute those changes automatically.",
|
|
113
113
|
{
|
|
114
114
|
code: "TABLE_DESIGNER_REBUILD_REQUIRED",
|
|
115
115
|
warnings: analysis.warnings,
|