sqlite-hub 0.5.0 → 0.6.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 +2 -2
- package/changelog.md +8 -0
- package/frontend/assets/mockups/connections.png +0 -0
- package/frontend/assets/mockups/data.png +0 -0
- package/frontend/assets/mockups/data_row_editor.png +0 -0
- package/frontend/assets/mockups/home.png +0 -0
- package/frontend/assets/mockups/sql_editor.png +0 -0
- package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
- package/frontend/assets/mockups/structure.png +0 -0
- package/frontend/assets/mockups/structure_inspector.png +0 -0
- package/frontend/js/api.js +15 -0
- package/frontend/js/app.js +279 -8
- package/frontend/js/components/queryEditor.js +2 -2
- package/frontend/js/components/sidebar.js +1 -0
- package/frontend/js/components/tableDesignerEditor.js +356 -0
- package/frontend/js/components/tableDesignerSidebar.js +126 -0
- package/frontend/js/components/tableDesignerSqlPreview.js +40 -0
- package/frontend/js/router.js +10 -0
- package/frontend/js/store.js +296 -1
- package/frontend/js/utils/tableDesigner.js +1192 -0
- package/frontend/js/views/data.js +253 -263
- package/frontend/js/views/tableDesigner.js +37 -0
- package/frontend/styles/base.css +87 -73
- package/frontend/styles/components.css +798 -251
- package/frontend/styles/views.css +40 -0
- package/package.json +1 -1
- package/server/routes/tableDesigner.js +60 -0
- package/server/server.js +4 -0
- package/server/services/sqlite/tableDesigner/changeAnalysis.js +295 -0
- package/server/services/sqlite/tableDesigner/schemaMapping.js +233 -0
- package/server/services/sqlite/tableDesigner/sql.js +63 -0
- package/server/services/sqlite/tableDesigner/validation.js +245 -0
- package/server/services/sqlite/tableDesignerService.js +181 -0
- package/frontend/assets/mockups/data_edit.png +0 -0
- package/frontend/assets/mockups/graph_visualize.png +0 -0
- package/frontend/assets/mockups/overview.png +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const { quoteIdentifier, quoteIdentifierList } = require("../../../utils/identifier");
|
|
2
|
+
const { normalizeSqlFragment } = require("./validation");
|
|
3
|
+
|
|
4
|
+
function buildColumnDefinition(column) {
|
|
5
|
+
const parts = [quoteIdentifier(column.name)];
|
|
6
|
+
const type = normalizeSqlFragment(column.type);
|
|
7
|
+
const defaultValue = normalizeSqlFragment(column.defaultValue);
|
|
8
|
+
|
|
9
|
+
if (type) {
|
|
10
|
+
parts.push(type);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (column.primaryKey) {
|
|
14
|
+
parts.push("PRIMARY KEY");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (column.notNull) {
|
|
18
|
+
parts.push("NOT NULL");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (column.unique) {
|
|
22
|
+
parts.push("UNIQUE");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (defaultValue) {
|
|
26
|
+
parts.push(`DEFAULT ${defaultValue}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (column.referencesTable && column.referencesColumn) {
|
|
30
|
+
parts.push(
|
|
31
|
+
`REFERENCES ${quoteIdentifier(column.referencesTable)}(${quoteIdentifier(column.referencesColumn)})`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return parts.join(" ");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildCreateTableSql(draft) {
|
|
39
|
+
const columnSql = draft.columns.map((column) => ` ${buildColumnDefinition(column)}`).join(",\n");
|
|
40
|
+
|
|
41
|
+
return `CREATE TABLE ${quoteIdentifier(draft.tableName)} (\n${columnSql}\n);`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildAlterTableRenameSql(fromName, toName) {
|
|
45
|
+
return `ALTER TABLE ${quoteIdentifier(fromName)} RENAME TO ${quoteIdentifier(toName)};`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function buildAlterTableAddColumnSql(tableName, column) {
|
|
49
|
+
return `ALTER TABLE ${quoteIdentifier(tableName)} ADD COLUMN ${buildColumnDefinition(column)};`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildInsertRowsSql(tableName, columns) {
|
|
53
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
54
|
+
return `INSERT INTO ${quoteIdentifier(tableName)} (${quoteIdentifierList(columns)}) VALUES (${placeholders});`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
buildAlterTableAddColumnSql,
|
|
59
|
+
buildAlterTableRenameSql,
|
|
60
|
+
buildColumnDefinition,
|
|
61
|
+
buildCreateTableSql,
|
|
62
|
+
buildInsertRowsSql,
|
|
63
|
+
};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
const { ValidationError } = require("../../../utils/errors");
|
|
2
|
+
const { assertValidIdentifier } = require("../../../utils/identifier");
|
|
3
|
+
const { normalizeDesignerType } = require("./schemaMapping");
|
|
4
|
+
|
|
5
|
+
function normalizeBoolean(value) {
|
|
6
|
+
return value === true || value === "true" || value === 1 || value === "1";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function normalizeSqlFragment(value) {
|
|
10
|
+
return String(value ?? "").trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function assertSafeSqlFragment(value, label, { allowEmpty = true } = {}) {
|
|
14
|
+
const normalized = normalizeSqlFragment(value);
|
|
15
|
+
|
|
16
|
+
if (!normalized) {
|
|
17
|
+
if (allowEmpty) {
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
throw new ValidationError(`${label} is required.`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (
|
|
25
|
+
normalized.includes("\0") ||
|
|
26
|
+
normalized.includes(";") ||
|
|
27
|
+
normalized.includes("--") ||
|
|
28
|
+
normalized.includes("/*") ||
|
|
29
|
+
normalized.includes("*/")
|
|
30
|
+
) {
|
|
31
|
+
throw new ValidationError(
|
|
32
|
+
`${label} must be a single SQL fragment without statement separators or comments.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return normalized;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeColumnPayload(column = {}, index = 0) {
|
|
40
|
+
const importedValueIndex =
|
|
41
|
+
column.importedValueIndex === null || column.importedValueIndex === undefined
|
|
42
|
+
? null
|
|
43
|
+
: Number(column.importedValueIndex);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
id: String(column.id ?? `column:${index}`),
|
|
47
|
+
isNew: normalizeBoolean(column.isNew),
|
|
48
|
+
deleted: normalizeBoolean(column.deleted),
|
|
49
|
+
name: String(column.name ?? "").trim(),
|
|
50
|
+
type: normalizeDesignerType(column.type),
|
|
51
|
+
notNull: normalizeBoolean(column.notNull),
|
|
52
|
+
unique: normalizeBoolean(column.unique),
|
|
53
|
+
primaryKey: normalizeBoolean(column.primaryKey),
|
|
54
|
+
defaultValue: assertSafeSqlFragment(column.defaultValue, "Default value"),
|
|
55
|
+
referencesTable: String(column.referencesTable ?? "").trim(),
|
|
56
|
+
referencesColumn: String(column.referencesColumn ?? "").trim(),
|
|
57
|
+
originalName: String(column.originalName ?? "").trim(),
|
|
58
|
+
originalType: normalizeDesignerType(column.originalType || column.type),
|
|
59
|
+
originalNotNull: normalizeBoolean(column.originalNotNull),
|
|
60
|
+
originalUnique: normalizeBoolean(column.originalUnique),
|
|
61
|
+
originalPrimaryKey: normalizeBoolean(column.originalPrimaryKey),
|
|
62
|
+
originalDefaultValue: assertSafeSqlFragment(column.originalDefaultValue, "Original default value"),
|
|
63
|
+
originalReferencesTable: String(column.originalReferencesTable ?? "").trim(),
|
|
64
|
+
originalReferencesColumn: String(column.originalReferencesColumn ?? "").trim(),
|
|
65
|
+
importedValueIndex: Number.isInteger(importedValueIndex) ? importedValueIndex : null,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeDraftPayload(payload = {}) {
|
|
70
|
+
const mode = String(payload.mode ?? "create").trim() === "edit" ? "edit" : "create";
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
mode,
|
|
74
|
+
originalTableName: String(payload.originalTableName ?? "").trim(),
|
|
75
|
+
tableName: String(payload.tableName ?? "").trim(),
|
|
76
|
+
columns: Array.isArray(payload.columns)
|
|
77
|
+
? payload.columns.map((column, index) => normalizeColumnPayload(column, index))
|
|
78
|
+
: [],
|
|
79
|
+
schemaWarnings: Array.isArray(payload.schemaWarnings) ? payload.schemaWarnings : [],
|
|
80
|
+
warnings: Array.isArray(payload.warnings) ? payload.warnings : [],
|
|
81
|
+
fillImportedRows: normalizeBoolean(payload.fillImportedRows),
|
|
82
|
+
importedCsvFileName: String(payload.importedCsvFileName ?? "").trim(),
|
|
83
|
+
importedCsvDelimiter: String(payload.importedCsvDelimiter ?? "").trim(),
|
|
84
|
+
importedCsvRows: Array.isArray(payload.importedCsvRows)
|
|
85
|
+
? payload.importedCsvRows.map((row) =>
|
|
86
|
+
Array.isArray(row) ? row.map((cell) => String(cell ?? "")) : []
|
|
87
|
+
)
|
|
88
|
+
: [],
|
|
89
|
+
dirty: normalizeBoolean(payload.dirty),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeIdentifierKey(value) {
|
|
94
|
+
return String(value ?? "").trim().toLowerCase();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildSelfReferenceColumns(draft) {
|
|
98
|
+
return draft.columns
|
|
99
|
+
.filter((column) => !column.deleted)
|
|
100
|
+
.map((column) => column.name)
|
|
101
|
+
.filter(Boolean);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function validatePrimaryKeys(draft, originalDraft) {
|
|
105
|
+
const currentPrimaryKeyColumns = draft.columns.filter(
|
|
106
|
+
(column) => !column.deleted && column.primaryKey
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (currentPrimaryKeyColumns.length <= 1) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!originalDraft) {
|
|
114
|
+
throw new ValidationError(
|
|
115
|
+
"Table Designer v1 supports a single primary key column when creating a table."
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const originalPrimaryKeyNames = new Set(
|
|
120
|
+
originalDraft.columns
|
|
121
|
+
.filter((column) => column.primaryKey)
|
|
122
|
+
.map((column) => normalizeIdentifierKey(column.originalName || column.name))
|
|
123
|
+
);
|
|
124
|
+
const currentPrimaryKeyNames = new Set(
|
|
125
|
+
currentPrimaryKeyColumns.map((column) =>
|
|
126
|
+
normalizeIdentifierKey(column.originalName || column.name)
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const isUnchangedCompositePrimaryKey =
|
|
131
|
+
originalPrimaryKeyNames.size > 1 &&
|
|
132
|
+
originalPrimaryKeyNames.size === currentPrimaryKeyNames.size &&
|
|
133
|
+
[...currentPrimaryKeyNames].every((name) => originalPrimaryKeyNames.has(name));
|
|
134
|
+
|
|
135
|
+
if (!isUnchangedCompositePrimaryKey) {
|
|
136
|
+
throw new ValidationError(
|
|
137
|
+
"Table Designer v1 does not support editing composite primary keys."
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function validateTableDesignerDraft(draft, { catalogTables = [], originalDraft = null } = {}) {
|
|
143
|
+
if (!["create", "edit"].includes(draft.mode)) {
|
|
144
|
+
throw new ValidationError("Draft mode must be create or edit.");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
assertValidIdentifier(draft.tableName, "Table name");
|
|
148
|
+
|
|
149
|
+
if (draft.mode === "edit") {
|
|
150
|
+
assertValidIdentifier(draft.originalTableName, "Original table name");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!draft.columns.length) {
|
|
154
|
+
throw new ValidationError("At least one column is required.");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const activeColumns = draft.columns.filter((column) => !column.deleted);
|
|
158
|
+
|
|
159
|
+
if (!activeColumns.length) {
|
|
160
|
+
throw new ValidationError("At least one column is required.");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const catalogByName = new Map(
|
|
164
|
+
catalogTables.map((table) => [normalizeIdentifierKey(table.name), table])
|
|
165
|
+
);
|
|
166
|
+
const normalizedTableName = normalizeIdentifierKey(draft.tableName);
|
|
167
|
+
const normalizedOriginalTableName = normalizeIdentifierKey(draft.originalTableName);
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
catalogByName.has(normalizedTableName) &&
|
|
171
|
+
(draft.mode === "create" || normalizedTableName !== normalizedOriginalTableName)
|
|
172
|
+
) {
|
|
173
|
+
throw new ValidationError(`A table named ${draft.tableName} already exists.`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const seenColumns = new Set();
|
|
177
|
+
|
|
178
|
+
activeColumns.forEach((column) => {
|
|
179
|
+
assertValidIdentifier(column.name, "Column name");
|
|
180
|
+
assertSafeSqlFragment(column.type, `Type for ${column.name}`, { allowEmpty: false });
|
|
181
|
+
|
|
182
|
+
const normalizedColumnName = normalizeIdentifierKey(column.name);
|
|
183
|
+
|
|
184
|
+
if (seenColumns.has(normalizedColumnName)) {
|
|
185
|
+
throw new ValidationError(`Duplicate column name: ${column.name}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
seenColumns.add(normalizedColumnName);
|
|
189
|
+
|
|
190
|
+
const hasReferenceTable = Boolean(column.referencesTable);
|
|
191
|
+
const hasReferenceColumn = Boolean(column.referencesColumn);
|
|
192
|
+
|
|
193
|
+
if (hasReferenceTable !== hasReferenceColumn) {
|
|
194
|
+
throw new ValidationError(
|
|
195
|
+
`Column ${column.name} must define both a referenced table and a referenced column.`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!hasReferenceTable || !hasReferenceColumn) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const normalizedReferenceTable = normalizeIdentifierKey(column.referencesTable);
|
|
204
|
+
const referencedTable =
|
|
205
|
+
normalizedReferenceTable === normalizedTableName
|
|
206
|
+
? {
|
|
207
|
+
name: draft.tableName,
|
|
208
|
+
columns: buildSelfReferenceColumns(draft),
|
|
209
|
+
}
|
|
210
|
+
: catalogByName.get(normalizedReferenceTable);
|
|
211
|
+
|
|
212
|
+
if (!referencedTable) {
|
|
213
|
+
throw new ValidationError(
|
|
214
|
+
`Referenced table ${column.referencesTable} does not exist in the current SQLite schema.`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const hasReferencedColumn = (referencedTable.columns ?? []).some(
|
|
219
|
+
(candidate) => normalizeIdentifierKey(candidate) === normalizeIdentifierKey(column.referencesColumn)
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (!hasReferencedColumn) {
|
|
223
|
+
throw new ValidationError(
|
|
224
|
+
`Referenced column ${column.referencesColumn} does not exist on ${referencedTable.name}.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
validatePrimaryKeys(draft, originalDraft);
|
|
230
|
+
|
|
231
|
+
if (draft.mode === "edit" && draft.fillImportedRows) {
|
|
232
|
+
throw new ValidationError("Imported row fill is only available when creating a table.");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (draft.fillImportedRows && !draft.importedCsvRows.length) {
|
|
236
|
+
throw new ValidationError("Fill requires imported CSV rows.");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
normalizeDraftPayload,
|
|
242
|
+
normalizeIdentifierKey,
|
|
243
|
+
normalizeSqlFragment,
|
|
244
|
+
validateTableDesignerDraft,
|
|
245
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const { mapSqliteError, ValidationError } = require("../../utils/errors");
|
|
2
|
+
const { getTableDetail } = require("./introspection");
|
|
3
|
+
const { analyzeTableDesignerChanges } = require("./tableDesigner/changeAnalysis");
|
|
4
|
+
const {
|
|
5
|
+
SUPPORTED_TABLE_DESIGNER_TYPES,
|
|
6
|
+
buildTableDesignerDraft,
|
|
7
|
+
listDesignerTables,
|
|
8
|
+
normalizeDesignerType,
|
|
9
|
+
} = require("./tableDesigner/schemaMapping");
|
|
10
|
+
const {
|
|
11
|
+
normalizeDraftPayload,
|
|
12
|
+
validateTableDesignerDraft,
|
|
13
|
+
} = require("./tableDesigner/validation");
|
|
14
|
+
const { buildCreateTableSql, buildInsertRowsSql } = require("./tableDesigner/sql");
|
|
15
|
+
|
|
16
|
+
function getImportedFillColumns(draft) {
|
|
17
|
+
return draft.columns.filter(
|
|
18
|
+
(column) => !column.deleted && Number.isInteger(column.importedValueIndex)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function coerceImportedCellValue(column, value) {
|
|
23
|
+
const textValue = String(value ?? "");
|
|
24
|
+
const trimmedValue = textValue.trim();
|
|
25
|
+
const type = normalizeDesignerType(column.type);
|
|
26
|
+
|
|
27
|
+
if (!trimmedValue) {
|
|
28
|
+
return ["TEXT", "DATE", "DATETIME"].includes(type) ? "" : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (type === "BOOLEAN") {
|
|
32
|
+
const normalized = trimmedValue.toLowerCase();
|
|
33
|
+
|
|
34
|
+
if (["true", "yes", "1"].includes(normalized)) {
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (["false", "no", "0"].includes(normalized)) {
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (type === "INTEGER" && /^-?\d+$/.test(trimmedValue)) {
|
|
44
|
+
return Number(trimmedValue);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
["REAL", "NUMERIC"].includes(type) &&
|
|
49
|
+
/^-?(?:\d+|\d*\.\d+)(?:e[+-]?\d+)?$/i.test(trimmedValue)
|
|
50
|
+
) {
|
|
51
|
+
return Number(trimmedValue);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return textValue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class TableDesignerService {
|
|
58
|
+
constructor({ connectionManager }) {
|
|
59
|
+
this.connectionManager = connectionManager;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getOverview() {
|
|
63
|
+
const db = this.connectionManager.getActiveDatabase();
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
tables: listDesignerTables(db),
|
|
67
|
+
supportedTypes: SUPPORTED_TABLE_DESIGNER_TYPES,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getTableDraft(tableName) {
|
|
72
|
+
const db = this.connectionManager.getActiveDatabase();
|
|
73
|
+
const tableDetail = getTableDetail(db, tableName, { includeRowCount: false });
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
draft: buildTableDesignerDraft(tableDetail),
|
|
77
|
+
supportedTypes: SUPPORTED_TABLE_DESIGNER_TYPES,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
saveDraft(payload = {}) {
|
|
82
|
+
this.connectionManager.assertWritable();
|
|
83
|
+
|
|
84
|
+
const db = this.connectionManager.getActiveDatabase();
|
|
85
|
+
const draft = normalizeDraftPayload(payload.draft ?? payload);
|
|
86
|
+
const catalogTables = listDesignerTables(db);
|
|
87
|
+
const originalDraft =
|
|
88
|
+
draft.mode === "edit"
|
|
89
|
+
? buildTableDesignerDraft(
|
|
90
|
+
getTableDetail(db, draft.originalTableName, { includeRowCount: false })
|
|
91
|
+
)
|
|
92
|
+
: null;
|
|
93
|
+
|
|
94
|
+
validateTableDesignerDraft(draft, {
|
|
95
|
+
catalogTables,
|
|
96
|
+
originalDraft,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const analysis = analyzeTableDesignerChanges({ draft, originalDraft });
|
|
100
|
+
|
|
101
|
+
if (!analysis.dirty) {
|
|
102
|
+
return {
|
|
103
|
+
savedTableName: draft.mode === "edit" ? draft.originalTableName : draft.tableName,
|
|
104
|
+
executedSql: [],
|
|
105
|
+
draft: originalDraft ?? draft,
|
|
106
|
+
tables: catalogTables,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!analysis.executable) {
|
|
111
|
+
throw new ValidationError(
|
|
112
|
+
"This schema change set would require a SQLite table rebuild. Table Designer v1 keeps the SQL preview available but will not execute those changes automatically.",
|
|
113
|
+
{
|
|
114
|
+
code: "TABLE_DESIGNER_REBUILD_REQUIRED",
|
|
115
|
+
warnings: analysis.warnings,
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const executeStatements = db.transaction((statements) => {
|
|
121
|
+
statements.forEach((statement) => {
|
|
122
|
+
db.exec(statement);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const executeCreateWithImport = db.transaction((nextDraft) => {
|
|
127
|
+
db.exec(buildCreateTableSql(nextDraft));
|
|
128
|
+
|
|
129
|
+
if (!nextDraft.fillImportedRows || !nextDraft.importedCsvRows.length) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const importedColumns = getImportedFillColumns(nextDraft);
|
|
134
|
+
|
|
135
|
+
if (!importedColumns.length) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const insertStatement = db.prepare(
|
|
140
|
+
buildInsertRowsSql(
|
|
141
|
+
nextDraft.tableName,
|
|
142
|
+
importedColumns.map((column) => column.name)
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
nextDraft.importedCsvRows.forEach((row) => {
|
|
147
|
+
insertStatement.run(
|
|
148
|
+
importedColumns.map((column) =>
|
|
149
|
+
coerceImportedCellValue(column, row[column.importedValueIndex] ?? "")
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
if (draft.mode === "create") {
|
|
157
|
+
executeCreateWithImport(draft);
|
|
158
|
+
} else {
|
|
159
|
+
executeStatements(analysis.statements);
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
throw mapSqliteError(error);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const savedTableName = draft.tableName;
|
|
166
|
+
const nextDraft = buildTableDesignerDraft(
|
|
167
|
+
getTableDetail(db, savedTableName, { includeRowCount: false })
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
savedTableName,
|
|
172
|
+
executedSql: analysis.statements,
|
|
173
|
+
draft: nextDraft,
|
|
174
|
+
tables: listDesignerTables(db),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = {
|
|
180
|
+
TableDesignerService,
|
|
181
|
+
};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|