admindb 1.0.3 → 1.2.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 +151 -47
- package/dist/app.d.ts +12 -0
- package/dist/app.js +7 -0
- package/dist/app.js.map +1 -1
- package/dist/args.d.ts +28 -0
- package/dist/args.js +187 -0
- package/dist/args.js.map +1 -0
- package/dist/cli.js +85 -15
- package/dist/cli.js.map +1 -1
- package/dist/db/database.d.ts +58 -37
- package/dist/db/database.js +239 -42
- package/dist/db/database.js.map +1 -1
- package/dist/db/manager.d.ts +8 -2
- package/dist/db/manager.js +33 -1
- package/dist/db/manager.js.map +1 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/css/input.css +53 -0
- package/dist/public/js/browse.js +847 -86
- package/dist/public/js/databases.js +99 -1
- package/dist/routes/api.js +261 -97
- package/dist/routes/api.js.map +1 -1
- package/dist/routes/databases.d.ts +10 -0
- package/dist/routes/databases.js +97 -0
- package/dist/routes/databases.js.map +1 -1
- package/dist/routes/pages.js +50 -30
- package/dist/routes/pages.js.map +1 -1
- package/dist/sql/generator.d.ts +0 -22
- package/dist/sql/generator.js +0 -48
- package/dist/sql/generator.js.map +1 -1
- package/dist/test/database.test.js +241 -63
- package/dist/test/database.test.js.map +1 -1
- package/dist/test/generator.test.js +0 -32
- package/dist/test/generator.test.js.map +1 -1
- package/dist/util.d.ts +18 -1
- package/dist/util.js +37 -2
- package/dist/util.js.map +1 -1
- package/dist/views/layouts/main.hbs +1 -1
- package/dist/views/pages/databases.hbs +41 -0
- package/dist/views/pages/table.hbs +81 -28
- package/dist/views/partials/sidebar.hbs +0 -12
- package/package.json +1 -1
- package/dist/public/js/triggers.js +0 -155
- package/dist/public/js/views.js +0 -176
- package/dist/views/pages/triggers.hbs +0 -162
- package/dist/views/pages/views.hbs +0 -134
package/dist/cli.js
CHANGED
|
@@ -5,50 +5,120 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
9
|
const app_1 = require("./app");
|
|
9
10
|
const config_1 = require("./config");
|
|
10
11
|
const logger_1 = require("./logger");
|
|
11
12
|
const manager_1 = require("./db/manager");
|
|
13
|
+
const args_1 = require("./args");
|
|
12
14
|
/**
|
|
13
15
|
* Standalone CLI / server entry point. Run `npm start` (or `npx admindb`).
|
|
14
|
-
* Configure via PORT, HOST, DB_PATH, DB_DIR, DB_FILES, BASE_PATH and LOG_LEVEL
|
|
15
|
-
* env vars. Set DB_DIR and/or DB_FILES to enable multi-database mode.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Everything can be set with CLI flags (see `--help`) or environment variables
|
|
18
|
+
* (PORT, HOST, DB_PATH, DB_DIR, DB_FILES, BASE_PATH, READONLY, LOG_LEVEL); flags
|
|
19
|
+
* win over the environment.
|
|
20
|
+
*
|
|
21
|
+
* By default the server runs in "manager" mode: the landing page lets you
|
|
22
|
+
* browse the filesystem and open any SQLite database file, or create new ones.
|
|
23
|
+
* `--open <file>` (or a file path / DB_PATH) opens a single database directly.
|
|
24
|
+
*
|
|
25
|
+
* SECURITY: this server exposes full, unauthenticated database AND filesystem
|
|
26
|
+
* access. It must NOT be exposed to the public internet — protect it yourself
|
|
27
|
+
* (bind to localhost, put it behind an authenticated reverse proxy, a VPN,
|
|
28
|
+
* etc.).
|
|
20
29
|
*/
|
|
21
|
-
const
|
|
30
|
+
const env = (0, config_1.loadConfig)();
|
|
31
|
+
const parsed = (0, args_1.parseArgs)(process.argv.slice(2));
|
|
32
|
+
if (parsed.error) {
|
|
33
|
+
console.error(parsed.error);
|
|
34
|
+
console.error();
|
|
35
|
+
console.error((0, args_1.helpText)());
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const args = parsed.args;
|
|
39
|
+
if (args.help) {
|
|
40
|
+
console.log((0, args_1.helpText)());
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
if (args.version) {
|
|
44
|
+
console.log((0, args_1.versionText)());
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
// Merge CLI flags over environment variables.
|
|
48
|
+
const config = {
|
|
49
|
+
host: args.host ?? env.host,
|
|
50
|
+
port: args.port ?? env.port,
|
|
51
|
+
dbPath: args.dbPath ?? env.dbPath,
|
|
52
|
+
dbDir: args.dbDir ?? env.dbDir,
|
|
53
|
+
dbFiles: args.dbFiles ?? env.dbFiles,
|
|
54
|
+
basePath: (args.basePath ?? env.basePath).replace(/\/+$/, ''),
|
|
55
|
+
logLevel: args.logLevel ?? env.logLevel,
|
|
56
|
+
readonly: args.readonly || env.readonly,
|
|
57
|
+
};
|
|
22
58
|
const logger = (0, logger_1.createLogger)(config.logLevel, 'admindb');
|
|
23
59
|
const app = (0, express_1.default)();
|
|
24
60
|
app.disable('x-powered-by');
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
// Mode selection:
|
|
62
|
+
// - A specific single file was requested (`--open`, a file path argument, or
|
|
63
|
+
// DB_PATH) and no folder/files were configured → single-database mode.
|
|
64
|
+
// - Otherwise → manager mode with a landing page where you can browse the
|
|
65
|
+
// filesystem and open database files, or create new ones.
|
|
66
|
+
const hasDir = Boolean(config.dbDir);
|
|
67
|
+
const hasFiles = Boolean(config.dbFiles && config.dbFiles.length > 0);
|
|
68
|
+
const explicitFile = args.dbPath ?? (process.env.DB_PATH ? process.env.DB_PATH : undefined);
|
|
69
|
+
const singleFileOnly = Boolean(explicitFile) && !hasDir && !hasFiles;
|
|
70
|
+
const useManager = !singleFileOnly;
|
|
71
|
+
const managerFiles = [...(config.dbFiles ?? [])];
|
|
72
|
+
if (useManager && explicitFile)
|
|
73
|
+
managerFiles.push(explicitFile);
|
|
74
|
+
// File-browser policy for manager mode:
|
|
75
|
+
// - a folder was requested → browsing is allowed but limited to that folder;
|
|
76
|
+
// - specific files were requested (no folder) → browsing is disabled entirely;
|
|
77
|
+
// - nothing requested → browsing is allowed (free); the folder defaults to the
|
|
78
|
+
// current directory so new/created databases land there.
|
|
79
|
+
const managerDir = hasDir ? config.dbDir : hasFiles || explicitFile ? undefined : process.cwd();
|
|
80
|
+
const allowBrowse = useManager && (hasDir || (!hasFiles && !explicitFile));
|
|
81
|
+
const browseRoot = hasDir ? node_path_1.default.resolve(config.dbDir) : undefined;
|
|
82
|
+
const router = useManager
|
|
27
83
|
? (0, app_1.createRouter)({
|
|
28
|
-
manager: new manager_1.DbManager({ dir:
|
|
84
|
+
manager: new manager_1.DbManager({ dir: managerDir, files: managerFiles, readonly: config.readonly }, logger),
|
|
29
85
|
basePath: config.basePath,
|
|
30
86
|
logger,
|
|
31
87
|
readonly: config.readonly,
|
|
88
|
+
allowBrowse,
|
|
89
|
+
browseRoot,
|
|
32
90
|
})
|
|
33
|
-
: (0, app_1.createRouter)({ dbPath:
|
|
91
|
+
: (0, app_1.createRouter)({ dbPath: explicitFile, basePath: config.basePath, logger, readonly: config.readonly });
|
|
34
92
|
app.use(config.basePath || '/', router);
|
|
35
93
|
const server = app.listen(config.port, config.host, () => {
|
|
36
94
|
const url = `http://${config.host}:${config.port}${config.basePath}/`;
|
|
37
95
|
logger.info(`AdminDB listening on ${url}`);
|
|
38
|
-
if (
|
|
39
|
-
const sources = [
|
|
96
|
+
if (useManager) {
|
|
97
|
+
const sources = [
|
|
98
|
+
managerDir ? `dir:${managerDir}` : null,
|
|
99
|
+
managerFiles.length ? `files:[${managerFiles.join(', ')}]` : null,
|
|
100
|
+
]
|
|
40
101
|
.filter(Boolean)
|
|
41
102
|
.join(' · ');
|
|
42
|
-
logger.info(`
|
|
103
|
+
logger.info(`Manager mode — ${sources}`);
|
|
104
|
+
if (!allowBrowse) {
|
|
105
|
+
logger.info('File browsing disabled — only the configured database files are listed.');
|
|
106
|
+
}
|
|
107
|
+
else if (browseRoot) {
|
|
108
|
+
logger.info(`File browsing is limited to: ${browseRoot}`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
logger.info('Open the URL above in your browser to browse and open database files.');
|
|
112
|
+
}
|
|
43
113
|
}
|
|
44
114
|
else {
|
|
45
|
-
logger.info(`Database file: ${
|
|
115
|
+
logger.info(`Database file: ${explicitFile}`);
|
|
46
116
|
}
|
|
47
117
|
if (config.readonly) {
|
|
48
118
|
logger.info('Read-only mode enabled — all writes are disabled.');
|
|
49
119
|
}
|
|
50
120
|
if (config.host === '0.0.0.0') {
|
|
51
|
-
logger.warn('Listening on 0.0.0.0 — this admin tool has NO authentication. Do not expose it to untrusted networks.');
|
|
121
|
+
logger.warn('Listening on 0.0.0.0 — this admin tool has NO authentication and exposes filesystem access. Do not expose it to untrusted networks.');
|
|
52
122
|
}
|
|
53
123
|
});
|
|
54
124
|
const shutdown = (signal) => {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;AACA,sDAA8B;AAC9B,+BAAqC;AACrC,qCAAsC;AACtC,qCAAwC;AACxC,0CAAyC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/db/database.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Logger } from '../logger';
|
|
2
|
-
import { type ColumnDef, type IndexDef
|
|
2
|
+
import { type ColumnDef, type IndexDef } from '../sql/generator';
|
|
3
|
+
import { type FilterValue } from '../util';
|
|
4
|
+
/** Structured filter types are shared with the API layer. */
|
|
5
|
+
export type { FilterCondition, FilterValue } from '../util';
|
|
3
6
|
/** SQLite-compatible bind value accepted by better-sqlite3 statements. */
|
|
4
7
|
export type SQLInputValue = null | number | bigint | string | Uint8Array;
|
|
5
8
|
export interface Result<T = unknown> {
|
|
@@ -53,20 +56,8 @@ export interface IndexInfo {
|
|
|
53
56
|
/** The `CREATE INDEX` statement from sqlite_master, when available. */
|
|
54
57
|
sql?: string | null;
|
|
55
58
|
}
|
|
56
|
-
export interface ViewInfo {
|
|
57
|
-
name: string;
|
|
58
|
-
/** The `CREATE VIEW …` statement from sqlite_master. */
|
|
59
|
-
sql: string | null;
|
|
60
|
-
}
|
|
61
|
-
export interface TriggerInfo {
|
|
62
|
-
name: string;
|
|
63
|
-
/** The table (or view) the trigger fires on. */
|
|
64
|
-
table: string;
|
|
65
|
-
/** The `CREATE TRIGGER …` statement from sqlite_master. */
|
|
66
|
-
sql: string | null;
|
|
67
|
-
}
|
|
68
59
|
export interface RowFilters {
|
|
69
|
-
[column: string]:
|
|
60
|
+
[column: string]: FilterValue;
|
|
70
61
|
}
|
|
71
62
|
export interface ColumnDetail extends ColumnInfo {
|
|
72
63
|
unique: boolean;
|
|
@@ -93,16 +84,19 @@ export interface SchemaInfo {
|
|
|
93
84
|
to: string;
|
|
94
85
|
}[];
|
|
95
86
|
}
|
|
87
|
+
export interface ReferencingTableInfo {
|
|
88
|
+
table: string;
|
|
89
|
+
/** FK columns in `table` that point at this table (`from` → `to`). */
|
|
90
|
+
refs: {
|
|
91
|
+
from: string;
|
|
92
|
+
to: string;
|
|
93
|
+
}[];
|
|
94
|
+
/** Rows in `table` whose FK column(s) actually reference this table. */
|
|
95
|
+
refCount: number;
|
|
96
|
+
}
|
|
96
97
|
export declare const INTERNAL_TABLES: {
|
|
97
98
|
readonly savedQueries: "_saved_queries";
|
|
98
99
|
};
|
|
99
|
-
/**
|
|
100
|
-
* Build a parameterized WHERE clause from per-column filters.
|
|
101
|
-
* Supported syntax in a filter value:
|
|
102
|
-
* - `=value` exact match, `!=value` not-equal, `>value`, `>=value`, `<value`, `<=value`
|
|
103
|
-
* - `value*` prefix match
|
|
104
|
-
* - anything else → case-insensitive substring (LIKE '%value%')
|
|
105
|
-
*/
|
|
106
100
|
export declare function buildFilterClause(filters: RowFilters | undefined, availableCols: string[]): {
|
|
107
101
|
where: string;
|
|
108
102
|
params: SQLInputValue[];
|
|
@@ -137,6 +131,12 @@ export declare class SqliteDatabase {
|
|
|
137
131
|
execResult(sql: string): Promise<Result<{
|
|
138
132
|
changes?: number;
|
|
139
133
|
}>>;
|
|
134
|
+
/**
|
|
135
|
+
* True when `sql` contains more than one statement. Detection is delegated to
|
|
136
|
+
* SQLite itself via `prepare()` (which refuses multi-statement strings), so
|
|
137
|
+
* comments, string literals and quoted identifiers are handled correctly.
|
|
138
|
+
*/
|
|
139
|
+
hasMultipleStatements(sql: string): boolean;
|
|
140
140
|
/** Run a write statement, reporting row changes when it is a single statement. */
|
|
141
141
|
runWrite(sql: string): Promise<Result<{
|
|
142
142
|
changes?: number;
|
|
@@ -166,10 +166,33 @@ export declare class SqliteDatabase {
|
|
|
166
166
|
changes: number;
|
|
167
167
|
lastInsertRowid: number | null;
|
|
168
168
|
}>>;
|
|
169
|
+
/**
|
|
170
|
+
* Apply many row updates (each with its own SET fields + primary-key WHERE
|
|
171
|
+
* clause) inside a single transaction. Used by the bulk "apply changes"
|
|
172
|
+
* flow of inline grid editing. Rolls everything back if any row fails.
|
|
173
|
+
*/
|
|
174
|
+
updateRows(table: string, rows: {
|
|
175
|
+
fields: WhereClause[];
|
|
176
|
+
where: WhereClause[];
|
|
177
|
+
}[]): Promise<Result<{
|
|
178
|
+
updated: number;
|
|
179
|
+
}>>;
|
|
169
180
|
deleteRow(table: string, where: WhereClause[]): Promise<Result<{
|
|
170
181
|
changes: number;
|
|
171
182
|
lastInsertRowid: number | null;
|
|
172
183
|
}>>;
|
|
184
|
+
/**
|
|
185
|
+
* Delete many rows (each identified by its primary-key WHERE clause) inside a
|
|
186
|
+
* single transaction. Used by bulk delete.
|
|
187
|
+
*/
|
|
188
|
+
deleteRows(table: string, rows: WhereClause[][]): Promise<Result<{
|
|
189
|
+
deleted: number;
|
|
190
|
+
}>>;
|
|
191
|
+
/**
|
|
192
|
+
* Fetch the rows matching the given primary-key WHERE clauses in one query
|
|
193
|
+
* (`(a = ? AND b = ?) OR …`). Used by bulk export and FK-impact checks.
|
|
194
|
+
*/
|
|
195
|
+
getRowsByPks(table: string, pks: WhereClause[][]): Promise<Result<Record<string, unknown>[]>>;
|
|
173
196
|
getCreateStatement(table: string): Promise<Result<string | null>>;
|
|
174
197
|
listSavedQueries(): Promise<Result<SavedQuery[]>>;
|
|
175
198
|
saveQuery(name: string, sql: string): Promise<Result<{
|
|
@@ -185,6 +208,20 @@ export declare class SqliteDatabase {
|
|
|
185
208
|
* foreign keys, and which other tables reference it.
|
|
186
209
|
*/
|
|
187
210
|
getSchema(table: string): Promise<Result<SchemaInfo>>;
|
|
211
|
+
/**
|
|
212
|
+
* Other tables with foreign keys pointing at `table`, grouped by table, each
|
|
213
|
+
* with the FK column mapping and a count of rows that actually reference it.
|
|
214
|
+
*/
|
|
215
|
+
getReferencingTables(table: string): Promise<Result<ReferencingTableInfo[]>>;
|
|
216
|
+
/**
|
|
217
|
+
* Rows in `table` whose `column` equals `value` — used to show which rows in
|
|
218
|
+
* a referencing table point at a specific row via a foreign key — with the
|
|
219
|
+
* total matching count.
|
|
220
|
+
*/
|
|
221
|
+
getRowsByFk(table: string, column: string, value: unknown, limit?: number): Promise<Result<{
|
|
222
|
+
rows: Record<string, unknown>[];
|
|
223
|
+
total: number;
|
|
224
|
+
}>>;
|
|
188
225
|
renameTable(oldName: string, newName: string): Promise<Result<{
|
|
189
226
|
changes?: number;
|
|
190
227
|
}>>;
|
|
@@ -206,21 +243,5 @@ export declare class SqliteDatabase {
|
|
|
206
243
|
dropIndex(indexName: string): Promise<Result<{
|
|
207
244
|
changes?: number;
|
|
208
245
|
}>>;
|
|
209
|
-
listViews(): Promise<Result<ViewInfo[]>>;
|
|
210
|
-
/** Preview the first `limit` rows of a view (browse). */
|
|
211
|
-
getViewRows(viewName: string, limit?: number): Promise<Result<Record<string, unknown>[]>>;
|
|
212
|
-
createView(name: string, selectSql: string): Promise<Result<{
|
|
213
|
-
changes?: number;
|
|
214
|
-
}>>;
|
|
215
|
-
dropView(name: string): Promise<Result<{
|
|
216
|
-
changes?: number;
|
|
217
|
-
}>>;
|
|
218
|
-
listTriggers(): Promise<Result<TriggerInfo[]>>;
|
|
219
|
-
createTrigger(def: TriggerDef): Promise<Result<{
|
|
220
|
-
changes?: number;
|
|
221
|
-
}>>;
|
|
222
|
-
dropTrigger(name: string): Promise<Result<{
|
|
223
|
-
changes?: number;
|
|
224
|
-
}>>;
|
|
225
246
|
private getTableInfoSync;
|
|
226
247
|
}
|
package/dist/db/database.js
CHANGED
|
@@ -17,20 +17,87 @@ function escapeLike(s) {
|
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Build a parameterized WHERE clause from per-column filters.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* - `value
|
|
23
|
-
*
|
|
20
|
+
*
|
|
21
|
+
* A filter value is either:
|
|
22
|
+
* - the legacy string syntax: `=value` exact, `!=value`, `>value`, `>=value`,
|
|
23
|
+
* `<value`, `<=value`, `value*` prefix, anything else → substring match; or
|
|
24
|
+
* - a structured `FilterCondition` (or array of them) produced by the
|
|
25
|
+
* type-aware filter form (FK dropdowns, boolean toggles, date/number ranges).
|
|
24
26
|
*/
|
|
27
|
+
function pushFilterCondition(conds, params, quotedCol, c) {
|
|
28
|
+
const value = String(c.value ?? '');
|
|
29
|
+
switch (c.op) {
|
|
30
|
+
case 'eq':
|
|
31
|
+
conds.push(`${quotedCol} = ?`);
|
|
32
|
+
params.push(value);
|
|
33
|
+
break;
|
|
34
|
+
case 'neq':
|
|
35
|
+
conds.push(`${quotedCol} != ?`);
|
|
36
|
+
params.push(value);
|
|
37
|
+
break;
|
|
38
|
+
case 'gt':
|
|
39
|
+
conds.push(`${quotedCol} > ?`);
|
|
40
|
+
params.push(value);
|
|
41
|
+
break;
|
|
42
|
+
case 'gte':
|
|
43
|
+
conds.push(`${quotedCol} >= ?`);
|
|
44
|
+
params.push(value);
|
|
45
|
+
break;
|
|
46
|
+
case 'lt':
|
|
47
|
+
conds.push(`${quotedCol} < ?`);
|
|
48
|
+
params.push(value);
|
|
49
|
+
break;
|
|
50
|
+
case 'lte':
|
|
51
|
+
conds.push(`${quotedCol} <= ?`);
|
|
52
|
+
params.push(value);
|
|
53
|
+
break;
|
|
54
|
+
case 'like':
|
|
55
|
+
conds.push(`${quotedCol} LIKE ? ESCAPE '\\'`);
|
|
56
|
+
params.push('%' + escapeLike(value) + '%');
|
|
57
|
+
break;
|
|
58
|
+
case 'prefix':
|
|
59
|
+
conds.push(`${quotedCol} LIKE ? ESCAPE '\\'`);
|
|
60
|
+
params.push(escapeLike(value) + '%');
|
|
61
|
+
break;
|
|
62
|
+
case 'between':
|
|
63
|
+
conds.push(`${quotedCol} >= ?`);
|
|
64
|
+
params.push(value);
|
|
65
|
+
conds.push(`${quotedCol} <= ?`);
|
|
66
|
+
params.push(String(c.max ?? ''));
|
|
67
|
+
break;
|
|
68
|
+
case 'null':
|
|
69
|
+
conds.push(`${quotedCol} IS NULL`);
|
|
70
|
+
break;
|
|
71
|
+
case 'notnull':
|
|
72
|
+
conds.push(`${quotedCol} IS NOT NULL`);
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
25
78
|
function buildFilterClause(filters, availableCols) {
|
|
26
79
|
const conds = [];
|
|
27
80
|
const params = [];
|
|
28
81
|
const colSet = new Set(availableCols);
|
|
29
82
|
for (const [col, raw] of Object.entries(filters ?? {})) {
|
|
30
|
-
|
|
31
|
-
if (value === '' || !colSet.has(col))
|
|
83
|
+
if (!colSet.has(col))
|
|
32
84
|
continue;
|
|
33
85
|
const q = (0, generator_1.quoteIdentifier)(col);
|
|
86
|
+
if (Array.isArray(raw)) {
|
|
87
|
+
for (const c of raw) {
|
|
88
|
+
if (c && typeof c === 'object')
|
|
89
|
+
pushFilterCondition(conds, params, q, c);
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (raw && typeof raw === 'object') {
|
|
94
|
+
pushFilterCondition(conds, params, q, raw);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// Legacy string syntax.
|
|
98
|
+
const value = String(raw ?? '').trim();
|
|
99
|
+
if (value === '')
|
|
100
|
+
continue;
|
|
34
101
|
if (value.startsWith('>=')) {
|
|
35
102
|
conds.push(`${q} >= ?`);
|
|
36
103
|
params.push(value.slice(2).trim());
|
|
@@ -153,6 +220,23 @@ class SqliteDatabase {
|
|
|
153
220
|
return { changes: undefined };
|
|
154
221
|
});
|
|
155
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* True when `sql` contains more than one statement. Detection is delegated to
|
|
225
|
+
* SQLite itself via `prepare()` (which refuses multi-statement strings), so
|
|
226
|
+
* comments, string literals and quoted identifiers are handled correctly.
|
|
227
|
+
*/
|
|
228
|
+
hasMultipleStatements(sql) {
|
|
229
|
+
const s = String(sql ?? '').trim();
|
|
230
|
+
if (!s)
|
|
231
|
+
return false;
|
|
232
|
+
try {
|
|
233
|
+
this.db.prepare(s);
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
return /more than one statement/i.test((0, util_1.errorMessage)(err));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
156
240
|
/** Run a write statement, reporting row changes when it is a single statement. */
|
|
157
241
|
async runWrite(sql) {
|
|
158
242
|
if (this.isReadOnly)
|
|
@@ -294,11 +378,99 @@ class SqliteDatabase {
|
|
|
294
378
|
const sql = `UPDATE ${(0, generator_1.quoteIdentifier)(table)} SET ${sets.join(', ')} WHERE ${conds.join(' AND ')}`;
|
|
295
379
|
return this.run(sql, [...fields.map((f) => this.toBind(f.value)), ...where.map((w) => this.toBind(w.value))]);
|
|
296
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Apply many row updates (each with its own SET fields + primary-key WHERE
|
|
383
|
+
* clause) inside a single transaction. Used by the bulk "apply changes"
|
|
384
|
+
* flow of inline grid editing. Rolls everything back if any row fails.
|
|
385
|
+
*/
|
|
386
|
+
async updateRows(table, rows) {
|
|
387
|
+
if (this.isReadOnly)
|
|
388
|
+
return this.readonlyBlocked();
|
|
389
|
+
return this.tryRun(() => {
|
|
390
|
+
this.db.exec('BEGIN');
|
|
391
|
+
try {
|
|
392
|
+
let updated = 0;
|
|
393
|
+
for (const { fields, where } of rows) {
|
|
394
|
+
if (!fields.length || !where.length)
|
|
395
|
+
continue;
|
|
396
|
+
const sets = fields.map((f) => `${(0, generator_1.quoteIdentifier)(f.column)} = ?`);
|
|
397
|
+
const conds = where.map((w) => `${(0, generator_1.quoteIdentifier)(w.column)} = ?`);
|
|
398
|
+
const sql = `UPDATE ${(0, generator_1.quoteIdentifier)(table)} SET ${sets.join(', ')} WHERE ${conds.join(' AND ')}`;
|
|
399
|
+
const info = this.db.prepare(sql).run(...[...fields.map((f) => this.toBind(f.value)), ...where.map((w) => this.toBind(w.value))]);
|
|
400
|
+
updated += Number(info.changes);
|
|
401
|
+
}
|
|
402
|
+
this.db.exec('COMMIT');
|
|
403
|
+
return { updated };
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
try {
|
|
407
|
+
this.db.exec('ROLLBACK');
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
/* ignore */
|
|
411
|
+
}
|
|
412
|
+
throw err;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
297
416
|
async deleteRow(table, where) {
|
|
298
417
|
const conds = where.map((w) => `${(0, generator_1.quoteIdentifier)(w.column)} = ?`);
|
|
299
418
|
const sql = `DELETE FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')}`;
|
|
300
419
|
return this.run(sql, where.map((w) => this.toBind(w.value)));
|
|
301
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Delete many rows (each identified by its primary-key WHERE clause) inside a
|
|
423
|
+
* single transaction. Used by bulk delete.
|
|
424
|
+
*/
|
|
425
|
+
async deleteRows(table, rows) {
|
|
426
|
+
if (this.isReadOnly)
|
|
427
|
+
return this.readonlyBlocked();
|
|
428
|
+
return this.tryRun(() => {
|
|
429
|
+
this.db.exec('BEGIN');
|
|
430
|
+
try {
|
|
431
|
+
let deleted = 0;
|
|
432
|
+
for (const where of rows) {
|
|
433
|
+
if (!where.length)
|
|
434
|
+
continue;
|
|
435
|
+
const conds = where.map((w) => `${(0, generator_1.quoteIdentifier)(w.column)} = ?`);
|
|
436
|
+
const sql = `DELETE FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')}`;
|
|
437
|
+
const info = this.db.prepare(sql).run(...where.map((w) => this.toBind(w.value)));
|
|
438
|
+
deleted += Number(info.changes);
|
|
439
|
+
}
|
|
440
|
+
this.db.exec('COMMIT');
|
|
441
|
+
return { deleted };
|
|
442
|
+
}
|
|
443
|
+
catch (err) {
|
|
444
|
+
try {
|
|
445
|
+
this.db.exec('ROLLBACK');
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
/* ignore */
|
|
449
|
+
}
|
|
450
|
+
throw err;
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Fetch the rows matching the given primary-key WHERE clauses in one query
|
|
456
|
+
* (`(a = ? AND b = ?) OR …`). Used by bulk export and FK-impact checks.
|
|
457
|
+
*/
|
|
458
|
+
async getRowsByPks(table, pks) {
|
|
459
|
+
return this.tryRun(() => {
|
|
460
|
+
const groups = [];
|
|
461
|
+
const params = [];
|
|
462
|
+
for (const where of pks) {
|
|
463
|
+
if (!where.length)
|
|
464
|
+
continue;
|
|
465
|
+
groups.push(where.map((w) => `${(0, generator_1.quoteIdentifier)(w.column)} = ?`).join(' AND '));
|
|
466
|
+
params.push(...where.map((w) => this.toBind(w.value)));
|
|
467
|
+
}
|
|
468
|
+
if (!groups.length)
|
|
469
|
+
return [];
|
|
470
|
+
const sql = `SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${groups.map((g) => `(${g})`).join(' OR ')}`;
|
|
471
|
+
return this.db.prepare(sql).all(...params);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
302
474
|
async getCreateStatement(table) {
|
|
303
475
|
return this.tryRun(() => {
|
|
304
476
|
const row = this.db
|
|
@@ -426,6 +598,67 @@ class SqliteDatabase {
|
|
|
426
598
|
};
|
|
427
599
|
});
|
|
428
600
|
}
|
|
601
|
+
/**
|
|
602
|
+
* Other tables with foreign keys pointing at `table`, grouped by table, each
|
|
603
|
+
* with the FK column mapping and a count of rows that actually reference it.
|
|
604
|
+
*/
|
|
605
|
+
async getReferencingTables(table) {
|
|
606
|
+
return this.tryRun(() => {
|
|
607
|
+
const allTables = this.db
|
|
608
|
+
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")
|
|
609
|
+
.all().map((r) => r.name);
|
|
610
|
+
const byTable = new Map();
|
|
611
|
+
for (const t of allTables) {
|
|
612
|
+
if (t === table)
|
|
613
|
+
continue;
|
|
614
|
+
try {
|
|
615
|
+
const fks = this.db.prepare(`PRAGMA foreign_key_list(${(0, generator_1.quoteIdentifier)(t)})`).all();
|
|
616
|
+
for (const fk of fks) {
|
|
617
|
+
if (fk.table === table) {
|
|
618
|
+
const arr = byTable.get(t) ?? [];
|
|
619
|
+
arr.push({ from: fk.from, to: fk.to ?? '' });
|
|
620
|
+
byTable.set(t, arr);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
/* ignore unreadable tables */
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const result = [];
|
|
629
|
+
for (const [t, refs] of byTable) {
|
|
630
|
+
let refCount = 0;
|
|
631
|
+
try {
|
|
632
|
+
const conds = refs.map((r) => `${(0, generator_1.quoteIdentifier)(r.from)} IS NOT NULL`);
|
|
633
|
+
const row = this.db.prepare(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(t)} WHERE ${conds.join(' OR ')}`).get();
|
|
634
|
+
refCount = Number(row.c);
|
|
635
|
+
}
|
|
636
|
+
catch {
|
|
637
|
+
/* count is best-effort */
|
|
638
|
+
}
|
|
639
|
+
result.push({ table: t, refs, refCount });
|
|
640
|
+
}
|
|
641
|
+
result.sort((a, b) => a.table.localeCompare(b.table));
|
|
642
|
+
return result;
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Rows in `table` whose `column` equals `value` — used to show which rows in
|
|
647
|
+
* a referencing table point at a specific row via a foreign key — with the
|
|
648
|
+
* total matching count.
|
|
649
|
+
*/
|
|
650
|
+
async getRowsByFk(table, column, value, limit = 50) {
|
|
651
|
+
return this.tryRun(() => {
|
|
652
|
+
const n = Math.max(1, Math.min(500, Number(limit) || 50));
|
|
653
|
+
const countRow = this.db
|
|
654
|
+
.prepare(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${(0, generator_1.quoteIdentifier)(column)} = ?`)
|
|
655
|
+
.get(value);
|
|
656
|
+
const rows = this.db
|
|
657
|
+
.prepare(`SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${(0, generator_1.quoteIdentifier)(column)} = ? LIMIT ${n}`)
|
|
658
|
+
.all(value);
|
|
659
|
+
return { rows, total: Number(countRow.c) };
|
|
660
|
+
});
|
|
661
|
+
}
|
|
429
662
|
async renameTable(oldName, newName) {
|
|
430
663
|
return this.execResult((0, generator_1.generateRenameTable)(oldName, newName));
|
|
431
664
|
}
|
|
@@ -447,42 +680,6 @@ class SqliteDatabase {
|
|
|
447
680
|
async dropIndex(indexName) {
|
|
448
681
|
return this.execResult((0, generator_1.generateDropIndex)(indexName));
|
|
449
682
|
}
|
|
450
|
-
// ---- Views & triggers -------------------------------------------------
|
|
451
|
-
async listViews() {
|
|
452
|
-
return this.tryRun(() => {
|
|
453
|
-
const rows = this.db
|
|
454
|
-
.prepare("SELECT name, sql FROM sqlite_master WHERE type = 'view' AND name NOT LIKE 'sqlite_%' ORDER BY name")
|
|
455
|
-
.all();
|
|
456
|
-
return rows.map((r) => ({ name: r.name, sql: r.sql ?? null }));
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
/** Preview the first `limit` rows of a view (browse). */
|
|
460
|
-
async getViewRows(viewName, limit = 200) {
|
|
461
|
-
return this.tryRun(() => {
|
|
462
|
-
const n = Math.max(1, Math.min(1000, Number(limit) || 200));
|
|
463
|
-
return this.db.prepare(`SELECT * FROM ${(0, generator_1.quoteIdentifier)(viewName)} LIMIT ${n}`).all();
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
async createView(name, selectSql) {
|
|
467
|
-
return this.execResult((0, generator_1.generateCreateView)(name, selectSql));
|
|
468
|
-
}
|
|
469
|
-
async dropView(name) {
|
|
470
|
-
return this.execResult((0, generator_1.generateDropView)(name));
|
|
471
|
-
}
|
|
472
|
-
async listTriggers() {
|
|
473
|
-
return this.tryRun(() => {
|
|
474
|
-
const rows = this.db
|
|
475
|
-
.prepare('SELECT name, tbl_name AS "table", sql FROM sqlite_master WHERE type = \'trigger\' AND name NOT LIKE \'sqlite_%\' ORDER BY name')
|
|
476
|
-
.all();
|
|
477
|
-
return rows.map((r) => ({ name: r.name, table: r.table, sql: r.sql ?? null }));
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
async createTrigger(def) {
|
|
481
|
-
return this.execResult((0, generator_1.generateCreateTrigger)(def));
|
|
482
|
-
}
|
|
483
|
-
async dropTrigger(name) {
|
|
484
|
-
return this.execResult((0, generator_1.generateDropTrigger)(name));
|
|
485
|
-
}
|
|
486
683
|
getTableInfoSync(table) {
|
|
487
684
|
try {
|
|
488
685
|
const cols = this.db.prepare(`PRAGMA table_info(${(0, generator_1.quoteIdentifier)(table)})`).all();
|