editdb-cli 1.0.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/.eslintrc.js +12 -0
- package/.prettierrc.json +6 -0
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/README_ja.md +50 -0
- package/assets/help_message_en.txt +11 -0
- package/assets/help_message_ja.txt +11 -0
- package/bin/editdb.js +7 -0
- package/counts.db +0 -0
- package/docs/CONTRIBUTING.md +38 -0
- package/docs/CONTRIBUTING_ja.md +38 -0
- package/docs/FEATURES.md +35 -0
- package/docs/FEATURES_ja.md +35 -0
- package/locales/en.json +147 -0
- package/locales/ja.json +147 -0
- package/package.json +34 -0
- package/src/__tests__/actions.test.js +399 -0
- package/src/__tests__/db.test.js +128 -0
- package/src/__tests__/utils.test.js +80 -0
- package/src/actions.js +691 -0
- package/src/config.js +3 -0
- package/src/constants.js +36 -0
- package/src/db.js +176 -0
- package/src/i18n.js +62 -0
- package/src/index.js +95 -0
- package/src/ui.js +418 -0
- package/src/utils.js +63 -0
package/src/actions.js
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { getTableInfo, getSingleRow, executeCustomQuery } from './db.js';
|
|
3
|
+
import {
|
|
4
|
+
parseValueBasedOnOriginal,
|
|
5
|
+
parseValueBasedOnSchema,
|
|
6
|
+
logger,
|
|
7
|
+
isValidIdentifier,
|
|
8
|
+
} from './utils.js';
|
|
9
|
+
import { t } from './i18n.js';
|
|
10
|
+
|
|
11
|
+
async function selectColumn(row) {
|
|
12
|
+
const keys = Object.keys(row);
|
|
13
|
+
|
|
14
|
+
const { column } = await inquirer.prompt([
|
|
15
|
+
{
|
|
16
|
+
type: 'list',
|
|
17
|
+
name: 'column',
|
|
18
|
+
message: t('actions.selectColumn'),
|
|
19
|
+
choices: keys,
|
|
20
|
+
},
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const originalValue = row[column];
|
|
24
|
+
|
|
25
|
+
const { value } = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'input',
|
|
28
|
+
name: 'value',
|
|
29
|
+
message: t('actions.newValuePrompt', {
|
|
30
|
+
value: originalValue,
|
|
31
|
+
type: typeof originalValue,
|
|
32
|
+
}),
|
|
33
|
+
validate: (input) => {
|
|
34
|
+
const parsed = parseValueBasedOnOriginal(input, originalValue);
|
|
35
|
+
if (parsed === undefined) {
|
|
36
|
+
return t('actions.invalidInput', { type: typeof originalValue });
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const newValue = parseValueBasedOnOriginal(value, originalValue);
|
|
44
|
+
return { column, newValue };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function confirmUpdate(db, table, idKey, rowId, column, newValue) {
|
|
48
|
+
if (!isValidIdentifier(table))
|
|
49
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
50
|
+
if (!isValidIdentifier(column))
|
|
51
|
+
throw new Error(`Invalid column name: ${column}`);
|
|
52
|
+
|
|
53
|
+
logger.log(t('actions.updateHeader'));
|
|
54
|
+
logger.log(t('actions.updateTable', { table }));
|
|
55
|
+
logger.log(t('actions.updateRow', { idKey, rowId }));
|
|
56
|
+
logger.log(t('actions.updateColumn', { column }));
|
|
57
|
+
logger.log(t('actions.updateValue', { value: newValue }));
|
|
58
|
+
logger.log(t('actions.updateFooter'));
|
|
59
|
+
|
|
60
|
+
const { ok } = await inquirer.prompt([
|
|
61
|
+
{ type: 'confirm', name: 'ok', message: t('common.confirm') },
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
if (!ok) {
|
|
65
|
+
logger.warn(t('common.cancel'));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const stmt = db.prepare(
|
|
71
|
+
`UPDATE "${table}" SET "${column}" = ? WHERE "${idKey}" = ?`
|
|
72
|
+
);
|
|
73
|
+
stmt.run(newValue, rowId);
|
|
74
|
+
|
|
75
|
+
const updated = getSingleRow(db, table, idKey, rowId);
|
|
76
|
+
logger.success(t('actions.updateSuccess'));
|
|
77
|
+
logger.log(updated);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
logger.error(t('actions.updateError', { message: err.message }));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function updateRow(db, table, idKey, row) {
|
|
84
|
+
const { column, newValue } = await selectColumn(row);
|
|
85
|
+
await confirmUpdate(db, table, idKey, row[idKey], column, newValue);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function insertRow(db, table) {
|
|
89
|
+
if (!isValidIdentifier(table))
|
|
90
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
91
|
+
logger.log(t('actions.insertHeader', { table }));
|
|
92
|
+
try {
|
|
93
|
+
const columns = getTableInfo(db, table);
|
|
94
|
+
const newRow = {};
|
|
95
|
+
|
|
96
|
+
for (const column of columns) {
|
|
97
|
+
if (column.pk === 1 && column.type.toUpperCase() === 'INTEGER') {
|
|
98
|
+
logger.info(t('actions.pkAuto', { name: column.name }));
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { value } = await inquirer.prompt([
|
|
103
|
+
{
|
|
104
|
+
type: 'input',
|
|
105
|
+
name: 'value',
|
|
106
|
+
message: t('actions.columnValuePrompt', {
|
|
107
|
+
name: column.name,
|
|
108
|
+
type: column.type,
|
|
109
|
+
default: column.dflt_val || 'NULL',
|
|
110
|
+
}),
|
|
111
|
+
validate: (input) => {
|
|
112
|
+
if (input === '' && column.dflt_val !== null) return true;
|
|
113
|
+
if (input.toLowerCase() === 'null' && column.notnull)
|
|
114
|
+
return t('actions.notNullError');
|
|
115
|
+
const parsed = parseValueBasedOnSchema(input, column.type);
|
|
116
|
+
if (parsed === undefined) {
|
|
117
|
+
return t('actions.invalidInput', { type: column.type });
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
if (value === '' && column.dflt_val !== null) {
|
|
125
|
+
newRow[column.name] = null;
|
|
126
|
+
} else {
|
|
127
|
+
newRow[column.name] = parseValueBasedOnSchema(value, column.type);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
logger.log(t('actions.insertContentHeader'));
|
|
132
|
+
logger.log(newRow);
|
|
133
|
+
logger.log(t('actions.insertContentFooter'));
|
|
134
|
+
|
|
135
|
+
const { ok } = await inquirer.prompt([
|
|
136
|
+
{ type: 'confirm', name: 'ok', message: t('common.confirm') },
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
if (!ok) {
|
|
140
|
+
logger.warn(t('actions.insertCancelled'));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const keys = Object.keys(newRow).filter((k) => newRow[k] !== null);
|
|
145
|
+
keys.forEach((k) => {
|
|
146
|
+
if (!isValidIdentifier(k)) throw new Error(`Invalid column name: ${k}`);
|
|
147
|
+
});
|
|
148
|
+
const placeholders = keys.map(() => '?').join(', ');
|
|
149
|
+
const sql = `INSERT INTO "${table}" (${keys.map((k) => `"${k}"`).join(', ')}) VALUES (${placeholders})`;
|
|
150
|
+
const values = keys.map((k) => newRow[k]);
|
|
151
|
+
|
|
152
|
+
const stmt = db.prepare(sql);
|
|
153
|
+
const result = stmt.run(values);
|
|
154
|
+
|
|
155
|
+
if (result.changes > 0) {
|
|
156
|
+
logger.success(
|
|
157
|
+
t('actions.insertSuccess', { rowid: result.lastInsertRowid })
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
logger.warn(t('actions.insertWarning'));
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
logger.error(t('actions.insertError', { message: err.message }));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function deleteRow(db, table, idKey, rowId) {
|
|
168
|
+
if (!isValidIdentifier(table))
|
|
169
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
170
|
+
if (!isValidIdentifier(idKey))
|
|
171
|
+
throw new Error(`Invalid column name: ${idKey}`);
|
|
172
|
+
|
|
173
|
+
logger.log(t('actions.deleteHeader'));
|
|
174
|
+
logger.log(t('actions.updateTable', { table }));
|
|
175
|
+
logger.log(t('actions.updateRow', { idKey, rowId }));
|
|
176
|
+
logger.log('--------------------');
|
|
177
|
+
|
|
178
|
+
const { ok } = await inquirer.prompt([
|
|
179
|
+
{ type: 'confirm', name: 'ok', message: t('actions.deleteConfirm') },
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
if (!ok) {
|
|
183
|
+
logger.warn(t('common.cancel'));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const stmt = db.prepare(`DELETE FROM "${table}" WHERE "${idKey}" = ?`);
|
|
189
|
+
const result = stmt.run(rowId);
|
|
190
|
+
|
|
191
|
+
if (result.changes > 0) {
|
|
192
|
+
logger.success(t('actions.deleteSuccess'));
|
|
193
|
+
} else {
|
|
194
|
+
logger.warn(t('actions.deleteWarning'));
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
logger.error(t('actions.deleteError', { message: err.message }));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function createTable(db) {
|
|
202
|
+
logger.log(t('actions.createTableHeader'));
|
|
203
|
+
|
|
204
|
+
const { tableName } = await inquirer.prompt([
|
|
205
|
+
{
|
|
206
|
+
type: 'input',
|
|
207
|
+
name: 'tableName',
|
|
208
|
+
message: t('actions.tableNamePrompt'),
|
|
209
|
+
validate: (input) => {
|
|
210
|
+
if (!input) return t('actions.tableNameValidateEmpty');
|
|
211
|
+
if (!isValidIdentifier(input))
|
|
212
|
+
return t('actions.tableNameValidateInvalid');
|
|
213
|
+
return true;
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
const columns = [];
|
|
219
|
+
let addMore = true;
|
|
220
|
+
|
|
221
|
+
while (addMore) {
|
|
222
|
+
const answers = await inquirer.prompt([
|
|
223
|
+
{
|
|
224
|
+
type: 'input',
|
|
225
|
+
name: 'name',
|
|
226
|
+
message: t('actions.columnNamePrompt'),
|
|
227
|
+
validate: (input) => {
|
|
228
|
+
if (!input) return t('actions.columnNameValidateEmpty');
|
|
229
|
+
if (!isValidIdentifier(input))
|
|
230
|
+
return t('actions.columnNameValidateInvalid');
|
|
231
|
+
return true;
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
type: 'list',
|
|
236
|
+
name: 'type',
|
|
237
|
+
message: t('actions.columnTypePrompt'),
|
|
238
|
+
choices: ['INTEGER', 'TEXT', 'REAL', 'BLOB', 'NUMERIC'],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
type: 'confirm',
|
|
242
|
+
name: 'pk',
|
|
243
|
+
message: t('actions.pkPrompt'),
|
|
244
|
+
default: false,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
type: 'confirm',
|
|
248
|
+
name: 'notnull',
|
|
249
|
+
message: t('actions.notNullPrompt'),
|
|
250
|
+
default: false,
|
|
251
|
+
when: (a) => !a.pk,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
type: 'input',
|
|
255
|
+
name: 'dflt_val',
|
|
256
|
+
message: t('actions.defaultValuePrompt'),
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
type: 'confirm',
|
|
260
|
+
name: 'addMore',
|
|
261
|
+
message: t('actions.addMoreColumnsPrompt'),
|
|
262
|
+
default: true,
|
|
263
|
+
},
|
|
264
|
+
]);
|
|
265
|
+
|
|
266
|
+
let columnDef = `"${answers.name}" ${answers.type}`;
|
|
267
|
+
if (answers.pk) columnDef += ' PRIMARY KEY';
|
|
268
|
+
if (answers.notnull && !answers.pk) columnDef += ' NOT NULL';
|
|
269
|
+
|
|
270
|
+
if (answers.dflt_val) {
|
|
271
|
+
const val = answers.dflt_val;
|
|
272
|
+
const upperVal = val.toUpperCase();
|
|
273
|
+
const isNumeric = !isNaN(parseFloat(val)) && isFinite(val);
|
|
274
|
+
const isKeyword = [
|
|
275
|
+
'CURRENT_TIME',
|
|
276
|
+
'CURRENT_DATE',
|
|
277
|
+
'CURRENT_TIMESTAMP',
|
|
278
|
+
'NULL',
|
|
279
|
+
].includes(upperVal);
|
|
280
|
+
|
|
281
|
+
if (isNumeric || isKeyword) {
|
|
282
|
+
columnDef += ` DEFAULT ${val}`;
|
|
283
|
+
} else {
|
|
284
|
+
columnDef += ` DEFAULT '${val.replace(/'/g, "''")}'`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
columns.push(columnDef);
|
|
289
|
+
addMore = answers.addMore;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (columns.length === 0) {
|
|
293
|
+
logger.warn(t('actions.createTableCancel'));
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const createSql = `CREATE TABLE "${tableName}" (${columns.join(',\n ')});`;
|
|
298
|
+
|
|
299
|
+
logger.log(t('actions.sqlHeader'));
|
|
300
|
+
logger.log(createSql);
|
|
301
|
+
logger.log(t('actions.sqlFooter'));
|
|
302
|
+
|
|
303
|
+
const { confirm } = await inquirer.prompt([
|
|
304
|
+
{
|
|
305
|
+
type: 'confirm',
|
|
306
|
+
name: 'confirm',
|
|
307
|
+
message: t('actions.sqlConfirm'),
|
|
308
|
+
default: true,
|
|
309
|
+
},
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
if (confirm) {
|
|
313
|
+
try {
|
|
314
|
+
db.prepare(createSql).run();
|
|
315
|
+
logger.success(t('actions.createSuccess', { tableName }));
|
|
316
|
+
} catch (err) {
|
|
317
|
+
logger.error(t('actions.createError', { message: err.message }));
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
logger.warn(t('actions.createCancelled'));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function executeCustomQueryAction(db) {
|
|
325
|
+
logger.log(t('actions.customQueryHeader'));
|
|
326
|
+
const { sql } = await inquirer.prompt([
|
|
327
|
+
{
|
|
328
|
+
type: 'input',
|
|
329
|
+
name: 'sql',
|
|
330
|
+
message: t('actions.customQueryPrompt'),
|
|
331
|
+
validate: (input) =>
|
|
332
|
+
!!input.trim() || t('actions.customQueryValidateEmpty'),
|
|
333
|
+
},
|
|
334
|
+
]);
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const results = executeCustomQuery(db, sql);
|
|
338
|
+
if (results.length > 0) {
|
|
339
|
+
logger.success(t('actions.customQueryResultHeader'));
|
|
340
|
+
console.table(results);
|
|
341
|
+
logger.info(t('actions.customQueryResultFooter'));
|
|
342
|
+
} else {
|
|
343
|
+
logger.warn(t('actions.customQueryNoResult'));
|
|
344
|
+
}
|
|
345
|
+
} catch (err) {
|
|
346
|
+
logger.error(t('actions.customQueryError', { message: err.message }));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export async function addColumn(db, table) {
|
|
351
|
+
if (!isValidIdentifier(table))
|
|
352
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
353
|
+
logger.log(t('actions.addColumnHeader', { table }));
|
|
354
|
+
|
|
355
|
+
const answers = await inquirer.prompt([
|
|
356
|
+
{
|
|
357
|
+
type: 'input',
|
|
358
|
+
name: 'name',
|
|
359
|
+
message: t('actions.columnNamePrompt'),
|
|
360
|
+
validate: (input) => {
|
|
361
|
+
if (!input) return t('actions.columnNameValidateEmpty');
|
|
362
|
+
if (!isValidIdentifier(input))
|
|
363
|
+
return t('actions.columnNameValidateInvalid');
|
|
364
|
+
return true;
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
type: 'list',
|
|
369
|
+
name: 'type',
|
|
370
|
+
message: t('actions.columnTypePrompt'),
|
|
371
|
+
choices: ['INTEGER', 'TEXT', 'REAL', 'BLOB', 'NUMERIC'],
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
type: 'confirm',
|
|
375
|
+
name: 'notnull',
|
|
376
|
+
message: t('actions.notNullPrompt'),
|
|
377
|
+
default: false,
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
type: 'input',
|
|
381
|
+
name: 'dflt_val',
|
|
382
|
+
message: t('actions.defaultValuePrompt'),
|
|
383
|
+
},
|
|
384
|
+
]);
|
|
385
|
+
|
|
386
|
+
let columnDef = `"${answers.name}" ${answers.type}`;
|
|
387
|
+
if (answers.notnull) columnDef += ' NOT NULL';
|
|
388
|
+
if (answers.dflt_val) {
|
|
389
|
+
const val = answers.dflt_val;
|
|
390
|
+
const upperVal = val.toUpperCase();
|
|
391
|
+
const isNumeric = !isNaN(parseFloat(val)) && isFinite(val);
|
|
392
|
+
const isKeyword = [
|
|
393
|
+
'CURRENT_TIME',
|
|
394
|
+
'CURRENT_DATE',
|
|
395
|
+
'CURRENT_TIMESTAMP',
|
|
396
|
+
'NULL',
|
|
397
|
+
].includes(upperVal);
|
|
398
|
+
|
|
399
|
+
if (isNumeric || isKeyword) {
|
|
400
|
+
columnDef += ` DEFAULT ${val}`;
|
|
401
|
+
} else {
|
|
402
|
+
columnDef += ` DEFAULT '${val.replace(/'/g, "''")}'`;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const sql = `ALTER TABLE "${table}" ADD COLUMN ${columnDef};`;
|
|
407
|
+
|
|
408
|
+
logger.log(t('actions.sqlHeader'));
|
|
409
|
+
logger.log(sql);
|
|
410
|
+
logger.log(t('actions.sqlFooter'));
|
|
411
|
+
|
|
412
|
+
const { confirm } = await inquirer.prompt([
|
|
413
|
+
{
|
|
414
|
+
type: 'confirm',
|
|
415
|
+
name: 'confirm',
|
|
416
|
+
message: t('actions.sqlConfirm'),
|
|
417
|
+
default: true,
|
|
418
|
+
},
|
|
419
|
+
]);
|
|
420
|
+
|
|
421
|
+
if (confirm) {
|
|
422
|
+
try {
|
|
423
|
+
db.prepare(sql).run();
|
|
424
|
+
logger.success(
|
|
425
|
+
t('actions.addColumnSuccess', { name: answers.name, table })
|
|
426
|
+
);
|
|
427
|
+
} catch (err) {
|
|
428
|
+
logger.error(t('actions.alterError', { message: err.message }));
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
logger.warn(t('actions.alterCancelled'));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export async function dropColumn(db, table) {
|
|
436
|
+
if (!isValidIdentifier(table))
|
|
437
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
438
|
+
|
|
439
|
+
const columns = getTableInfo(db, table);
|
|
440
|
+
if (columns.length <= 1) {
|
|
441
|
+
logger.error(t('actions.dropColumnLastError'));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const columnNames = columns.map((c) => c.name);
|
|
446
|
+
const { columnName } = await inquirer.prompt([
|
|
447
|
+
{
|
|
448
|
+
type: 'list',
|
|
449
|
+
name: 'columnName',
|
|
450
|
+
message: t('actions.dropColumnPrompt'),
|
|
451
|
+
choices: [...columnNames, new inquirer.Separator(), t('common.cancel')],
|
|
452
|
+
},
|
|
453
|
+
]);
|
|
454
|
+
|
|
455
|
+
if (columnName === t('common.cancel') || !columnName) {
|
|
456
|
+
logger.warn(t('actions.alterCancelled'));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const { confirm } = await inquirer.prompt([
|
|
461
|
+
{
|
|
462
|
+
type: 'confirm',
|
|
463
|
+
name: 'confirm',
|
|
464
|
+
message: t('actions.dropColumnConfirm', {
|
|
465
|
+
columnName: columnName,
|
|
466
|
+
table: table,
|
|
467
|
+
}),
|
|
468
|
+
default: true,
|
|
469
|
+
},
|
|
470
|
+
]);
|
|
471
|
+
|
|
472
|
+
if (!confirm) {
|
|
473
|
+
logger.warn(t('actions.alterCancelled'));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const tempTable = `_temp_${table}`;
|
|
478
|
+
const remainingColumns = columns.filter((c) => c.name !== columnName);
|
|
479
|
+
const remainingColumnNames = remainingColumns
|
|
480
|
+
.map((c) => `"${c.name}"`)
|
|
481
|
+
.join(', ');
|
|
482
|
+
|
|
483
|
+
const createTableSql = `CREATE TABLE "${tempTable}" (${remainingColumns
|
|
484
|
+
.map((c) => {
|
|
485
|
+
let def = `"${c.name}" ${c.type}`;
|
|
486
|
+
if (c.pk) def += ' PRIMARY KEY';
|
|
487
|
+
if (c.notnull) def += ' NOT NULL';
|
|
488
|
+
if (c.dflt_value) def += ` DEFAULT ${c.dflt_value}`;
|
|
489
|
+
return def;
|
|
490
|
+
})
|
|
491
|
+
.join(', ')});`;
|
|
492
|
+
const insertDataSql = `INSERT INTO "${tempTable}" (${remainingColumnNames}) SELECT ${remainingColumnNames} FROM "${table}";`;
|
|
493
|
+
const dropOldTableSql = `DROP TABLE "${table}";`;
|
|
494
|
+
const renameTableSql = `ALTER TABLE "${tempTable}" RENAME TO "${table}";`;
|
|
495
|
+
|
|
496
|
+
const transaction = db.transaction(() => {
|
|
497
|
+
db.exec(createTableSql);
|
|
498
|
+
db.exec(insertDataSql);
|
|
499
|
+
db.exec(dropOldTableSql);
|
|
500
|
+
db.exec(renameTableSql);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
transaction();
|
|
505
|
+
logger.success(t('actions.dropColumnSuccess', { name: columnName, table }));
|
|
506
|
+
} catch (err) {
|
|
507
|
+
logger.error(t('actions.alterError', { message: err.message }));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export async function renameTable(db, table) {
|
|
512
|
+
if (!isValidIdentifier(table))
|
|
513
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
514
|
+
logger.log(t('actions.renameTableHeader', { table }));
|
|
515
|
+
|
|
516
|
+
const { newTableName } = await inquirer.prompt([
|
|
517
|
+
{
|
|
518
|
+
type: 'input',
|
|
519
|
+
name: 'newTableName',
|
|
520
|
+
message: t('actions.renameTablePrompt'),
|
|
521
|
+
validate: (input) => {
|
|
522
|
+
if (!input) return t('actions.tableNameValidateEmpty');
|
|
523
|
+
if (!isValidIdentifier(input))
|
|
524
|
+
return t('actions.tableNameValidateInvalid');
|
|
525
|
+
return true;
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
]);
|
|
529
|
+
|
|
530
|
+
const sql = `ALTER TABLE "${table}" RENAME TO "${newTableName}";`;
|
|
531
|
+
logger.log(t('actions.sqlHeader'));
|
|
532
|
+
logger.log(sql);
|
|
533
|
+
logger.log(t('actions.sqlFooter'));
|
|
534
|
+
const { confirm } = await inquirer.prompt([
|
|
535
|
+
{
|
|
536
|
+
type: 'confirm',
|
|
537
|
+
name: 'confirm',
|
|
538
|
+
message: t('actions.sqlConfirm'),
|
|
539
|
+
default: true,
|
|
540
|
+
},
|
|
541
|
+
]);
|
|
542
|
+
|
|
543
|
+
if (confirm) {
|
|
544
|
+
try {
|
|
545
|
+
db.prepare(sql).run();
|
|
546
|
+
logger.success(
|
|
547
|
+
t('actions.renameTableSuccess', { old: table, new: newTableName })
|
|
548
|
+
);
|
|
549
|
+
return newTableName;
|
|
550
|
+
} catch (err) {
|
|
551
|
+
logger.error(t('actions.alterError', { message: err.message }));
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
} else {
|
|
555
|
+
logger.warn(t('actions.alterCancelled'));
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export function searchRows(db, table, whereClause, params) {
|
|
561
|
+
if (!isValidIdentifier(table))
|
|
562
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
563
|
+
const sql = `SELECT * FROM "${table}" WHERE ${whereClause}`;
|
|
564
|
+
return db.prepare(sql).all(params);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export async function updateMultipleRows(
|
|
568
|
+
db,
|
|
569
|
+
table,
|
|
570
|
+
whereClause,
|
|
571
|
+
params,
|
|
572
|
+
rowCount
|
|
573
|
+
) {
|
|
574
|
+
if (!isValidIdentifier(table))
|
|
575
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
576
|
+
|
|
577
|
+
logger.log(
|
|
578
|
+
t('actions.multiUpdate.header', {
|
|
579
|
+
count: rowCount,
|
|
580
|
+
table,
|
|
581
|
+
where: whereClause.replace(
|
|
582
|
+
/\?/g,
|
|
583
|
+
(
|
|
584
|
+
(i) => () =>
|
|
585
|
+
params[i++]
|
|
586
|
+
)(0)
|
|
587
|
+
),
|
|
588
|
+
})
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
const columns = getTableInfo(db, table).map((c) => c.name);
|
|
592
|
+
const { column } = await inquirer.prompt([
|
|
593
|
+
{
|
|
594
|
+
type: 'list',
|
|
595
|
+
name: 'column',
|
|
596
|
+
message: t('actions.multiUpdate.selectColumn'),
|
|
597
|
+
choices: columns,
|
|
598
|
+
},
|
|
599
|
+
]);
|
|
600
|
+
|
|
601
|
+
const { value } = await inquirer.prompt([
|
|
602
|
+
{
|
|
603
|
+
type: 'input',
|
|
604
|
+
name: 'value',
|
|
605
|
+
message: t('actions.multiUpdate.newValuePrompt', { column }),
|
|
606
|
+
},
|
|
607
|
+
]);
|
|
608
|
+
|
|
609
|
+
if (!isValidIdentifier(column))
|
|
610
|
+
throw new Error(`Invalid column name: ${column}`);
|
|
611
|
+
const sql = `UPDATE "${table}" SET "${column}" = ? WHERE ${whereClause}`;
|
|
612
|
+
|
|
613
|
+
logger.log(t('actions.sqlHeader'));
|
|
614
|
+
logger.log(sql);
|
|
615
|
+
logger.log(t('actions.sqlFooter'));
|
|
616
|
+
|
|
617
|
+
const { confirm } = await inquirer.prompt([
|
|
618
|
+
{
|
|
619
|
+
type: 'confirm',
|
|
620
|
+
name: 'confirm',
|
|
621
|
+
message: t('actions.multiUpdate.confirm', { count: rowCount }),
|
|
622
|
+
default: false,
|
|
623
|
+
},
|
|
624
|
+
]);
|
|
625
|
+
|
|
626
|
+
if (confirm) {
|
|
627
|
+
try {
|
|
628
|
+
const result = db.prepare(sql).run(value, ...params);
|
|
629
|
+
logger.success(
|
|
630
|
+
t('actions.multiUpdate.success', { count: result.changes })
|
|
631
|
+
);
|
|
632
|
+
} catch (err) {
|
|
633
|
+
logger.error(t('actions.updateError', { message: err.message }));
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
logger.warn(t('actions.multiUpdate.cancelled'));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export async function deleteMultipleRows(
|
|
641
|
+
db,
|
|
642
|
+
table,
|
|
643
|
+
whereClause,
|
|
644
|
+
params,
|
|
645
|
+
rowCount
|
|
646
|
+
) {
|
|
647
|
+
if (!isValidIdentifier(table))
|
|
648
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
649
|
+
|
|
650
|
+
logger.log(
|
|
651
|
+
t('actions.multiDelete.header', {
|
|
652
|
+
count: rowCount,
|
|
653
|
+
table,
|
|
654
|
+
where: whereClause.replace(
|
|
655
|
+
/\?/g,
|
|
656
|
+
(
|
|
657
|
+
(i) => () =>
|
|
658
|
+
params[i++]
|
|
659
|
+
)(0)
|
|
660
|
+
),
|
|
661
|
+
})
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
const sql = `DELETE FROM "${table}" WHERE ${whereClause}`;
|
|
665
|
+
|
|
666
|
+
logger.log(t('actions.sqlHeader'));
|
|
667
|
+
logger.log(sql);
|
|
668
|
+
logger.log(t('actions.sqlFooter'));
|
|
669
|
+
|
|
670
|
+
const { confirm } = await inquirer.prompt([
|
|
671
|
+
{
|
|
672
|
+
type: 'confirm',
|
|
673
|
+
name: 'confirm',
|
|
674
|
+
message: t('actions.multiDelete.confirm', { count: rowCount }),
|
|
675
|
+
default: false,
|
|
676
|
+
},
|
|
677
|
+
]);
|
|
678
|
+
|
|
679
|
+
if (confirm) {
|
|
680
|
+
try {
|
|
681
|
+
const result = db.prepare(sql).run(params);
|
|
682
|
+
logger.success(
|
|
683
|
+
t('actions.multiDelete.success', { count: result.changes })
|
|
684
|
+
);
|
|
685
|
+
} catch (err) {
|
|
686
|
+
logger.error(t('actions.deleteError', { message: err.message }));
|
|
687
|
+
}
|
|
688
|
+
} else {
|
|
689
|
+
logger.warn(t('actions.multiDelete.cancelled'));
|
|
690
|
+
}
|
|
691
|
+
}
|
package/src/config.js
ADDED