@adecore/database 0.21.1 → 0.22.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/dist/assign-ref.d.ts +2 -0
- package/dist/assign-ref.js +9 -0
- package/dist/client/client.js +14 -15
- package/dist/client/types.d.ts +1 -0
- package/dist/client/types.js +2 -0
- package/dist/connections/ConnectionForm.js +7 -2
- package/dist/connections/ConnectionManager.js +5 -5
- package/dist/connections/EngineIcon.js +0 -1
- package/dist/console/QueryConsole.js +12 -4
- package/dist/console/editing.js +2 -4
- package/dist/console/useConsoleHistory.js +3 -2
- package/dist/ddl/alter-mysql.js +2 -8
- package/dist/ddl/alter-sqlite.js +1 -1
- package/dist/ddl/column.js +5 -8
- package/dist/ddl/create.d.ts +1 -0
- package/dist/ddl/create.js +1 -1
- package/dist/ddl/diff.js +25 -23
- package/dist/designer/ColumnsEditor.js +8 -6
- package/dist/designer/ForeignKeysEditor.js +5 -1
- package/dist/designer/OptionsEditor.js +12 -3
- package/dist/designer/SqlPreview.d.ts +1 -5
- package/dist/designer/SqlPreview.js +1 -1
- package/dist/designer/TableDesigner.js +7 -6
- package/dist/explorer/DatabaseExplorer.js +12 -13
- package/dist/explorer/TableDialog.js +1 -1
- package/dist/explorer/tree.js +6 -16
- package/dist/grid/DataGrid.js +11 -14
- package/dist/grid/EnumPicker.js +4 -3
- package/dist/grid/GridHeaderCell.d.ts +1 -1
- package/dist/grid/GridHeaderCell.js +2 -2
- package/dist/grid/RecordView.js +2 -1
- package/dist/grid/ValueDock.js +2 -6
- package/dist/grid/aggregates.js +2 -3
- package/dist/grid/column-layout.js +3 -2
- package/dist/grid/copy-formats.js +2 -3
- package/dist/grid/display.d.ts +0 -1
- package/dist/grid/display.js +1 -3
- package/dist/grid/layout.js +6 -11
- package/dist/host/helper-path.js +8 -0
- package/dist/host/host.js +11 -16
- package/dist/host/validate.js +44 -43
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +5 -2
- package/dist/structure/StructureView.js +5 -6
- package/dist/table/TableView.js +19 -12
- package/dist/table/command-field.js +8 -12
- package/dist/table/grid-rows.js +2 -1
- package/dist/table/layout-store.js +5 -2
- package/dist/table/pending.js +11 -3
- package/dist/table/useTableTransfer.js +6 -5
- package/dist/testing/fake.js +7 -9
- package/dist/value/ValueEditor.js +1 -1
- package/dist/value/draft.js +1 -6
- package/dist/value/json.js +4 -4
- package/dist/workbench/DatabaseWorkbench.js +12 -5
- package/dist/workbench/tabs.js +3 -4
- package/package.json +6 -7
- package/src/assign-ref.ts +10 -0
- package/src/client/client.ts +17 -18
- package/src/client/types.ts +3 -0
- package/src/connections/ConnectionForm.tsx +62 -47
- package/src/connections/ConnectionManager.tsx +6 -5
- package/src/connections/EngineIcon.tsx +0 -2
- package/src/console/QueryConsole.tsx +52 -31
- package/src/console/editing.ts +2 -4
- package/src/console/useConsoleHistory.ts +5 -2
- package/src/ddl/alter-mysql.ts +3 -11
- package/src/ddl/alter-sqlite.ts +1 -1
- package/src/ddl/column.ts +5 -7
- package/src/ddl/create.ts +1 -1
- package/src/ddl/diff.ts +30 -25
- package/src/designer/ColumnsEditor.tsx +145 -125
- package/src/designer/ForeignKeysEditor.tsx +44 -27
- package/src/designer/OptionsEditor.tsx +95 -75
- package/src/designer/SqlPreview.tsx +2 -3
- package/src/designer/TableDesigner.tsx +7 -6
- package/src/explorer/DatabaseExplorer.tsx +26 -31
- package/src/explorer/TableDialog.tsx +1 -1
- package/src/explorer/tree.ts +8 -19
- package/src/grid/DataGrid.tsx +11 -14
- package/src/grid/EnumPicker.tsx +4 -3
- package/src/grid/GridHeaderCell.tsx +2 -3
- package/src/grid/RecordView.tsx +2 -1
- package/src/grid/ValueDock.tsx +2 -5
- package/src/grid/aggregates.ts +2 -4
- package/src/grid/column-layout.ts +3 -2
- package/src/grid/copy-formats.ts +2 -3
- package/src/grid/display.ts +1 -5
- package/src/grid/layout.ts +10 -11
- package/src/host/helper-path.ts +8 -8
- package/src/host/host.ts +11 -16
- package/src/host/validate.ts +45 -43
- package/src/protocol/messages.ts +0 -1
- package/src/sql.ts +7 -2
- package/src/structure/StructureView.tsx +7 -8
- package/src/table/CommandField.tsx +0 -1
- package/src/table/TableView.tsx +48 -34
- package/src/table/command-field.ts +9 -12
- package/src/table/grid-rows.ts +2 -7
- package/src/table/layout-store.ts +6 -3
- package/src/table/pending.ts +12 -7
- package/src/table/useTableTransfer.ts +7 -5
- package/src/testing/fake.ts +8 -12
- package/src/value/ValueEditor.tsx +1 -1
- package/src/value/draft.ts +1 -6
- package/src/value/json.ts +4 -4
- package/src/workbench/DatabaseWorkbench.tsx +14 -6
- package/src/workbench/DesignerTab.tsx +1 -1
- package/src/workbench/tabs.ts +5 -8
package/dist/host/validate.js
CHANGED
|
@@ -13,6 +13,7 @@ const fail = (message) => {
|
|
|
13
13
|
throw new InvalidRequest(message);
|
|
14
14
|
};
|
|
15
15
|
const isDict = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
|
+
const isRequestId = (value) => typeof value === 'string' && value.length > 0 && value.length <= MAX_ID_LENGTH;
|
|
16
17
|
/* Rejects a key nobody asked for, so a typo cannot pass for an option the helper ignores. */
|
|
17
18
|
const shape = (value, label, required, optional = []) => {
|
|
18
19
|
if (!isDict(value)) {
|
|
@@ -30,12 +31,12 @@ const shape = (value, label, required, optional = []) => {
|
|
|
30
31
|
}
|
|
31
32
|
return value;
|
|
32
33
|
};
|
|
33
|
-
const
|
|
34
|
+
const filledText = (value, label) => {
|
|
34
35
|
if (typeof value !== 'string' || value.length === 0) {
|
|
35
36
|
fail(`"${label}" must be a non-empty string.`);
|
|
36
37
|
}
|
|
37
38
|
};
|
|
38
|
-
const
|
|
39
|
+
const anyText = (value, label) => {
|
|
39
40
|
if (typeof value !== 'string') {
|
|
40
41
|
fail(`"${label}" must be a string.`);
|
|
41
42
|
}
|
|
@@ -56,7 +57,7 @@ const oneOf = (allowed) => (value, label) => {
|
|
|
56
57
|
}
|
|
57
58
|
};
|
|
58
59
|
const absolutePath = (value, label) => {
|
|
59
|
-
|
|
60
|
+
filledText(value, label);
|
|
60
61
|
if (!ABSOLUTE_PATH.test(value)) {
|
|
61
62
|
fail(`"${label}" must be an absolute path.`);
|
|
62
63
|
}
|
|
@@ -112,17 +113,17 @@ const tunnel = (input, label) => {
|
|
|
112
113
|
}
|
|
113
114
|
if (input.kind === 'ssh') {
|
|
114
115
|
const config = shape(input, label, ['kind', 'host'], ['port', 'user', 'identityFile']);
|
|
115
|
-
|
|
116
|
+
filledText(config.host, `${label}.host`);
|
|
116
117
|
optional(config, 'port', label, port);
|
|
117
|
-
optional(config, 'user', label,
|
|
118
|
-
optional(config, 'identityFile', label,
|
|
118
|
+
optional(config, 'user', label, filledText);
|
|
119
|
+
optional(config, 'identityFile', label, filledText);
|
|
119
120
|
return;
|
|
120
121
|
}
|
|
121
122
|
if (input.kind === 'docker') {
|
|
122
123
|
const config = shape(input, label, ['kind', 'container'], ['port', 'context']);
|
|
123
|
-
|
|
124
|
+
filledText(config.container, `${label}.container`);
|
|
124
125
|
optional(config, 'port', label, port);
|
|
125
|
-
optional(config, 'context', label,
|
|
126
|
+
optional(config, 'context', label, filledText);
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
128
129
|
fail(`"${label}.kind" must be "ssh" or "docker".`);
|
|
@@ -144,12 +145,12 @@ const connection = (input, label) => {
|
|
|
144
145
|
optional(config, 'tunnel', label, tunnel);
|
|
145
146
|
// A Docker tunnel finds the server by its container, so the host is left empty.
|
|
146
147
|
const viaDocker = isDict(config.tunnel) && config.tunnel.kind === 'docker';
|
|
147
|
-
(viaDocker ?
|
|
148
|
-
|
|
148
|
+
(viaDocker ? anyText : filledText)(config.host, `${label}.host`);
|
|
149
|
+
filledText(config.user, `${label}.user`);
|
|
149
150
|
optional(config, 'port', label, port);
|
|
150
|
-
optional(config, 'socket', label,
|
|
151
|
-
optional(config, 'password', label,
|
|
152
|
-
optional(config, 'database', label,
|
|
151
|
+
optional(config, 'socket', label, filledText);
|
|
152
|
+
optional(config, 'password', label, anyText);
|
|
153
|
+
optional(config, 'database', label, anyText);
|
|
153
154
|
optional(config, 'tls', label, oneOf(TLS_MODES));
|
|
154
155
|
optional(config, 'readOnly', label, flag);
|
|
155
156
|
return;
|
|
@@ -185,43 +186,43 @@ const exportSource = (input, label) => {
|
|
|
185
186
|
}
|
|
186
187
|
if (input.kind === 'table') {
|
|
187
188
|
const source = shape(input, label, ['kind', 'schema', 'table'], ['where', 'orderBy']);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
optional(source, 'where', label,
|
|
191
|
-
optional(source, 'orderBy', label,
|
|
189
|
+
filledText(source.schema, `${label}.schema`);
|
|
190
|
+
filledText(source.table, `${label}.table`);
|
|
191
|
+
optional(source, 'where', label, anyText);
|
|
192
|
+
optional(source, 'orderBy', label, anyText);
|
|
192
193
|
return;
|
|
193
194
|
}
|
|
194
195
|
if (input.kind === 'query') {
|
|
195
196
|
const source = shape(input, label, ['kind', 'sql'], ['schema']);
|
|
196
|
-
|
|
197
|
-
optional(source, 'schema', label,
|
|
197
|
+
filledText(source.sql, `${label}.sql`);
|
|
198
|
+
optional(source, 'schema', label, filledText);
|
|
198
199
|
return;
|
|
199
200
|
}
|
|
200
201
|
fail(`"${label}.kind" must be "table" or "query".`);
|
|
201
202
|
};
|
|
202
203
|
const cellLimit = (params) => optional(params, 'cellLimit', 'params', (limit, name) => integer(limit, name, 1, MAX_CELL_LIMIT));
|
|
203
204
|
const table = (params) => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
filledText(params.session, 'params.session');
|
|
206
|
+
filledText(params.schema, 'params.schema');
|
|
207
|
+
filledText(params.table, 'params.table');
|
|
207
208
|
};
|
|
208
209
|
const TARGET = ['session', 'schema', 'table'];
|
|
209
210
|
const PARAMS = {
|
|
210
211
|
open: (input) => connection(shape(input, 'params', ['connection']).connection, 'params.connection'),
|
|
211
212
|
test: (input) => connection(shape(input, 'params', ['connection']).connection, 'params.connection'),
|
|
212
|
-
close: (input) =>
|
|
213
|
-
schemas: (input) =>
|
|
213
|
+
close: (input) => filledText(shape(input, 'params', ['session']).session, 'params.session'),
|
|
214
|
+
schemas: (input) => filledText(shape(input, 'params', ['session']).session, 'params.session'),
|
|
214
215
|
tables: (input) => {
|
|
215
216
|
const params = shape(input, 'params', ['session', 'schema']);
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
filledText(params.session, 'params.session');
|
|
218
|
+
filledText(params.schema, 'params.schema');
|
|
218
219
|
},
|
|
219
220
|
structure: (input) => table(shape(input, 'params', TARGET)),
|
|
220
221
|
rows: (input) => {
|
|
221
222
|
const params = shape(input, 'params', [...TARGET, 'offset', 'limit'], ['where', 'orderBy', 'cellLimit']);
|
|
222
223
|
table(params);
|
|
223
|
-
optional(params, 'where', 'params',
|
|
224
|
-
optional(params, 'orderBy', 'params',
|
|
224
|
+
optional(params, 'where', 'params', anyText);
|
|
225
|
+
optional(params, 'orderBy', 'params', anyText);
|
|
225
226
|
integer(params.offset, 'params.offset', 0, Number.MAX_SAFE_INTEGER);
|
|
226
227
|
integer(params.limit, 'params.limit', 1, MAX_LIMIT);
|
|
227
228
|
cellLimit(params);
|
|
@@ -229,13 +230,13 @@ const PARAMS = {
|
|
|
229
230
|
count: (input) => {
|
|
230
231
|
const params = shape(input, 'params', TARGET, ['where']);
|
|
231
232
|
table(params);
|
|
232
|
-
optional(params, 'where', 'params',
|
|
233
|
+
optional(params, 'where', 'params', anyText);
|
|
233
234
|
},
|
|
234
235
|
cell: (input) => {
|
|
235
236
|
const params = shape(input, 'params', [...TARGET, 'key', 'column']);
|
|
236
237
|
table(params);
|
|
237
238
|
values(params.key, 'params.key', value, true);
|
|
238
|
-
|
|
239
|
+
filledText(params.column, 'params.column');
|
|
239
240
|
},
|
|
240
241
|
apply: (input) => {
|
|
241
242
|
const params = shape(input, 'params', [...TARGET, 'changes']);
|
|
@@ -248,34 +249,34 @@ const PARAMS = {
|
|
|
248
249
|
},
|
|
249
250
|
execute: (input) => {
|
|
250
251
|
const params = shape(input, 'params', ['session', 'sql'], ['schema', 'limit', 'cellLimit']);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
optional(params, 'schema', 'params',
|
|
252
|
+
filledText(params.session, 'params.session');
|
|
253
|
+
filledText(params.sql, 'params.sql');
|
|
254
|
+
optional(params, 'schema', 'params', filledText);
|
|
254
255
|
optional(params, 'limit', 'params', (limit, name) => integer(limit, name, 1, MAX_LIMIT));
|
|
255
256
|
cellLimit(params);
|
|
256
257
|
},
|
|
257
258
|
page: (input) => {
|
|
258
259
|
const params = shape(input, 'params', ['session', 'sql', 'offset', 'limit'], ['schema', 'cellLimit']);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
optional(params, 'schema', 'params',
|
|
260
|
+
filledText(params.session, 'params.session');
|
|
261
|
+
filledText(params.sql, 'params.sql');
|
|
262
|
+
optional(params, 'schema', 'params', filledText);
|
|
262
263
|
integer(params.offset, 'params.offset', 0, Number.MAX_SAFE_INTEGER);
|
|
263
264
|
integer(params.limit, 'params.limit', 1, MAX_LIMIT);
|
|
264
265
|
cellLimit(params);
|
|
265
266
|
},
|
|
266
267
|
transaction: (input) => {
|
|
267
268
|
const params = shape(input, 'params', ['session', 'action']);
|
|
268
|
-
|
|
269
|
+
filledText(params.session, 'params.session');
|
|
269
270
|
oneOf(TRANSACTION_ACTIONS)(params.action, 'params.action');
|
|
270
271
|
},
|
|
271
272
|
export: (input) => {
|
|
272
273
|
const params = shape(input, 'params', ['session', 'source', 'format', 'path'], ['header', 'tableName']);
|
|
273
|
-
|
|
274
|
+
filledText(params.session, 'params.session');
|
|
274
275
|
exportSource(params.source, 'params.source');
|
|
275
276
|
oneOf(EXPORT_FORMATS)(params.format, 'params.format');
|
|
276
277
|
absolutePath(params.path, 'params.path');
|
|
277
278
|
optional(params, 'header', 'params', flag);
|
|
278
|
-
optional(params, 'tableName', 'params',
|
|
279
|
+
optional(params, 'tableName', 'params', filledText);
|
|
279
280
|
},
|
|
280
281
|
sample: (input) => {
|
|
281
282
|
const params = shape(input, 'params', ['path', 'format', 'header'], ['limit']);
|
|
@@ -296,25 +297,25 @@ const PARAMS = {
|
|
|
296
297
|
}
|
|
297
298
|
params.columns.forEach((column, i) => {
|
|
298
299
|
if (column !== null) {
|
|
299
|
-
|
|
300
|
+
filledText(column, `params.columns[${i}]`);
|
|
300
301
|
}
|
|
301
302
|
});
|
|
302
303
|
},
|
|
303
304
|
discover: (input) => {
|
|
304
305
|
const params = shape(input, 'params', ['kind'], ['context']);
|
|
305
306
|
oneOf(['docker'])(params.kind, 'params.kind');
|
|
306
|
-
optional(params, 'context', 'params',
|
|
307
|
+
optional(params, 'context', 'params', filledText);
|
|
307
308
|
},
|
|
308
309
|
cancel: (input) => {
|
|
309
310
|
const params = shape(input, 'params', ['request']);
|
|
310
|
-
if (
|
|
311
|
+
if (!isRequestId(params.request)) {
|
|
311
312
|
fail(`"params.request" must be a string of 1 to ${MAX_ID_LENGTH} characters.`);
|
|
312
313
|
}
|
|
313
314
|
}
|
|
314
315
|
};
|
|
315
316
|
/* Checks the whole request before anything reaches the helper, which then never has to defend itself against a page. */
|
|
316
317
|
export const parseRequest = (input) => {
|
|
317
|
-
const id = isDict(input) &&
|
|
318
|
+
const id = isDict(input) && isRequestId(input.id) ? input.id : '';
|
|
318
319
|
try {
|
|
319
320
|
const envelope = shape(input, 'request', ['id', 'method', 'params']);
|
|
320
321
|
if (id === '') {
|
package/dist/sql.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface SqlTarget {
|
|
|
12
12
|
export type LiteralValue = Cell | Value | {
|
|
13
13
|
readonly kind: 'default';
|
|
14
14
|
};
|
|
15
|
+
export declare const NUMERIC_TEXT: RegExp;
|
|
16
|
+
export declare const isNumericKind: (kind: ValueKind) => boolean;
|
|
15
17
|
export declare const quoteIdentifier: (engine: Engine, name: string) => string;
|
|
16
18
|
export declare const qualifiedName: (target: SqlTarget) => string;
|
|
17
19
|
export declare const sqlLiteral: (engine: Engine, value: LiteralValue, kind?: ValueKind) => string;
|
package/dist/sql.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const NUMERIC_KINDS = new Set(['integer', 'decimal', 'float']);
|
|
2
|
-
|
|
2
|
+
/* A number as SQL and most people write it: a sign, digits with an optional fraction, and an exponent. */
|
|
3
|
+
export const NUMERIC_TEXT = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
4
|
+
/* Whether a value of this kind is a number, which a grid aligns against the end of its cell. */
|
|
5
|
+
export const isNumericKind = (kind) => NUMERIC_KINDS.has(kind);
|
|
3
6
|
const ORDER_ITEM = /\s*(`(?:[^`]|``)+`|"(?:[^"]|"")+"|[A-Za-z_][\w$]*)(?:\s+(asc|desc))?\s*(,|$)/iy;
|
|
4
7
|
const markOf = (engine) => (engine === 'mysql' ? '`' : '"');
|
|
5
8
|
export const quoteIdentifier = (engine, name) => {
|
|
@@ -28,7 +31,7 @@ export const sqlLiteral = (engine, value, kind) => {
|
|
|
28
31
|
return Number.isFinite(value) ? String(value) : 'NULL';
|
|
29
32
|
case 'string': {
|
|
30
33
|
const trimmed = value.trim();
|
|
31
|
-
return kind !== undefined &&
|
|
34
|
+
return kind !== undefined && isNumericKind(kind) && NUMERIC_TEXT.test(trimmed) ? trimmed : stringLiteral(engine, value);
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
switch (value.kind) {
|
|
@@ -10,14 +10,13 @@ import { useSchemaChange } from '../use-schema-change.js';
|
|
|
10
10
|
import { referenceOf } from './structure-text.js';
|
|
11
11
|
const TH = 'sticky top-0 h-7 bg-surface px-3 text-left text-xs font-medium whitespace-nowrap text-text-faint';
|
|
12
12
|
const TD = 'h-7 border-t border-border-soft px-3 text-xs whitespace-nowrap text-text select-text';
|
|
13
|
-
const MONO = CODE_TEXT;
|
|
14
13
|
/* The columns, indexes, foreign keys and DDL of one table or view. */
|
|
15
14
|
export function StructureView({ connection, schema, table, toolbarStart, className, ref }) {
|
|
16
15
|
const { t } = useTranslation('database');
|
|
17
16
|
const client = useDatabaseClient();
|
|
18
17
|
const [answer, setAnswer] = useState(null);
|
|
19
18
|
const [attempt, setAttempt] = useState(0);
|
|
20
|
-
// A schema change reloads
|
|
19
|
+
// A schema change reloads in place: the old answer stays on screen until the new one arrives.
|
|
21
20
|
const [reloads, setReloads] = useState(0);
|
|
22
21
|
const answered = answer !== null && answer.connection === connection && answer.schema === schema && answer.table === table && answer.attempt === attempt;
|
|
23
22
|
const load = answered ? answer.load : { status: 'loading' };
|
|
@@ -69,14 +68,14 @@ function ColumnsTable({ structure }) {
|
|
|
69
68
|
}
|
|
70
69
|
function ColumnRow({ column, primary, foreign }) {
|
|
71
70
|
const { t } = useTranslation('database');
|
|
72
|
-
return (_jsxs("tr", { children: [_jsx("td", { className: TD, children: _jsxs("span", { className: "flex items-center gap-1.5", children: [primary ? (_jsx(Icon, { icon: KeyRound, size: 12, className: "shrink-0 text-status-needs-you" })) : foreign ? (_jsx(Icon, { icon: Link2, size: 12, className: "shrink-0 text-text-muted" })) : (_jsx("span", { className: "size-3 shrink-0" })), column.name] }) }), _jsx("td", { className: clsx(TD,
|
|
71
|
+
return (_jsxs("tr", { children: [_jsx("td", { className: TD, children: _jsxs("span", { className: "flex items-center gap-1.5", children: [primary ? (_jsx(Icon, { icon: KeyRound, size: 12, className: "shrink-0 text-status-needs-you" })) : foreign ? (_jsx(Icon, { icon: Link2, size: 12, className: "shrink-0 text-text-muted" })) : (_jsx("span", { className: "size-3 shrink-0" })), column.name] }) }), _jsx("td", { className: clsx(TD, CODE_TEXT), children: column.type }), _jsx("td", { className: clsx(TD, 'text-text-muted'), children: _jsx(YesNo, { value: column.nullable }) }), _jsx("td", { className: clsx(TD, CODE_TEXT), children: column.defaultValue }), _jsx("td", { className: TD, children: _jsxs("span", { className: "flex gap-1", children: [column.autoIncrement && _jsx(Pill, { children: t('structure.autoIncrement') }), column.generated && _jsx(Pill, { children: t('structure.generated') })] }) }), _jsx("td", { className: clsx(TD, 'text-text-muted'), children: column.comment })] }));
|
|
73
72
|
}
|
|
74
73
|
function IndexesTable({ indexes }) {
|
|
75
74
|
const { t } = useTranslation('database');
|
|
76
75
|
if (indexes.length === 0) {
|
|
77
76
|
return _jsx(EmptyState, { children: t('structure.noIndexes') });
|
|
78
77
|
}
|
|
79
|
-
return (_jsx(Grid, { heads: [t('structure.index.name'), t('structure.index.columns'), t('structure.index.kind')], children: indexes.map((index) => (_jsxs("tr", { children: [_jsx("td", { className: TD, children: index.name }), _jsx("td", { className: clsx(TD,
|
|
78
|
+
return (_jsx(Grid, { heads: [t('structure.index.name'), t('structure.index.columns'), t('structure.index.kind')], children: indexes.map((index) => (_jsxs("tr", { children: [_jsx("td", { className: TD, children: index.name }), _jsx("td", { className: clsx(TD, CODE_TEXT), children: index.columns.join(', ') }), _jsx("td", { className: TD, children: _jsxs("span", { className: "flex gap-1", children: [index.primary && _jsx(Pill, { tone: "accent", children: t('structure.primary') }), index.unique && !index.primary && _jsx(Pill, { children: t('structure.unique') })] }) })] }, index.name))) }));
|
|
80
79
|
}
|
|
81
80
|
function ForeignKeysTable({ foreignKeys }) {
|
|
82
81
|
const { t } = useTranslation('database');
|
|
@@ -90,12 +89,12 @@ function ForeignKeysTable({ foreignKeys }) {
|
|
|
90
89
|
t('structure.foreignKey.references'),
|
|
91
90
|
t('structure.foreignKey.onUpdate'),
|
|
92
91
|
t('structure.foreignKey.onDelete')
|
|
93
|
-
], children: foreignKeys.map((key, i) => (_jsxs("tr", { children: [_jsx("td", { className: TD, children: key.name }), _jsx("td", { className: clsx(TD,
|
|
92
|
+
], children: foreignKeys.map((key, i) => (_jsxs("tr", { children: [_jsx("td", { className: TD, children: key.name }), _jsx("td", { className: clsx(TD, CODE_TEXT), children: key.columns.join(', ') }), _jsx("td", { className: clsx(TD, CODE_TEXT), children: referenceOf(key) }), _jsx("td", { className: clsx(TD, 'text-text-muted'), children: action(key.onUpdate) }), _jsx("td", { className: clsx(TD, 'text-text-muted'), children: action(key.onDelete) })] }, key.name ?? i))) }));
|
|
94
93
|
}
|
|
95
94
|
function Ddl({ ddl }) {
|
|
96
95
|
const { t } = useTranslation('database');
|
|
97
96
|
if (ddl === null) {
|
|
98
97
|
return _jsx(EmptyState, { children: t('structure.noDdl') });
|
|
99
98
|
}
|
|
100
|
-
return (_jsxs("div", { className: "relative", children: [_jsxs(Button, { variant: "secondary", size: "xs", className: "absolute top-2 right-3", onClick: () => copyText(ddl), children: [_jsx(Icon, { icon: Copy, size: 12 }), t('structure.copy')] }), _jsx("pre", { className: clsx(
|
|
99
|
+
return (_jsxs("div", { className: "relative", children: [_jsxs(Button, { variant: "secondary", size: "xs", className: "absolute top-2 right-3", onClick: () => copyText(ddl), children: [_jsx(Icon, { icon: Copy, size: 12 }), t('structure.copy')] }), _jsx("pre", { className: clsx(CODE_TEXT, 'overflow-auto p-3 pr-24 whitespace-pre text-text select-text'), children: ddl })] }));
|
|
101
100
|
}
|
package/dist/table/TableView.js
CHANGED
|
@@ -40,6 +40,18 @@ const parseRecordWidth = (raw) => {
|
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
const NO_KEYS = new Set();
|
|
43
|
+
const readOnlyReasonOf = (connection, structure, t) => {
|
|
44
|
+
if (connection.config.readOnly === true) {
|
|
45
|
+
return t('table.readOnly.connection');
|
|
46
|
+
}
|
|
47
|
+
if (structure?.kind === 'view') {
|
|
48
|
+
return t('table.readOnly.view');
|
|
49
|
+
}
|
|
50
|
+
if (structure !== null && structure.rowKey === null) {
|
|
51
|
+
return t('table.readOnly.noKey');
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
43
55
|
/* What the strip over the table, or the app's notice, says of an export or an import. */
|
|
44
56
|
const transferText = (found, t) => {
|
|
45
57
|
switch (found.kind) {
|
|
@@ -53,7 +65,6 @@ const transferText = (found, t) => {
|
|
|
53
65
|
return found.message;
|
|
54
66
|
}
|
|
55
67
|
};
|
|
56
|
-
/* How much of a value a menu item spells out before it cuts it. */
|
|
57
68
|
const MENU_VALUE_LENGTH = 40;
|
|
58
69
|
const FOCUS_COMMAND = shortcut('Mod+F');
|
|
59
70
|
const DUPLICATE_ROWS = shortcut('Mod+D');
|
|
@@ -112,15 +123,7 @@ function TableBody({ connection, schema, table, toolbarStart, defaultWhere, defa
|
|
|
112
123
|
const structure = structureLoad.value;
|
|
113
124
|
const loaded = rowsLoad.value?.result ?? null;
|
|
114
125
|
const loadedOffset = rowsLoad.value?.offset ?? 0;
|
|
115
|
-
const readOnlyReason = connection
|
|
116
|
-
? t('table.readOnly.connection')
|
|
117
|
-
: structure === null
|
|
118
|
-
? null
|
|
119
|
-
: structure.kind === 'view'
|
|
120
|
-
? t('table.readOnly.view')
|
|
121
|
-
: structure.rowKey === null
|
|
122
|
-
? t('table.readOnly.noKey')
|
|
123
|
-
: null;
|
|
126
|
+
const readOnlyReason = readOnlyReasonOf(connection, structure, t);
|
|
124
127
|
const editable = readOnlyReason === null && structure !== null && loaded !== null && !rowsLoad.loading;
|
|
125
128
|
const columns = useMemo(() => (loaded === null ? [] : buildGridColumns(loaded, structure)), [loaded, structure]);
|
|
126
129
|
const rows = useMemo(() => (loaded === null ? [] : buildGridRows({ structure, loaded, pending, offset: loadedOffset })), [loaded, structure, pending, loadedOffset]);
|
|
@@ -220,7 +223,7 @@ function TableBody({ connection, schema, table, toolbarStart, defaultWhere, defa
|
|
|
220
223
|
});
|
|
221
224
|
}
|
|
222
225
|
};
|
|
223
|
-
/*
|
|
226
|
+
/* A change that leaves the SQL alone only remembers the filters and keeps the page. */
|
|
224
227
|
const applyChips = (next) => {
|
|
225
228
|
const where = chipsToWhere(next);
|
|
226
229
|
const orderBy = chipsToOrderBy(engine, next);
|
|
@@ -469,7 +472,7 @@ function TableBody({ connection, schema, table, toolbarStart, defaultWhere, defa
|
|
|
469
472
|
busy: transfer.notice?.kind === 'exporting',
|
|
470
473
|
onExport: transfer.exportAs,
|
|
471
474
|
onImport: readOnlyReason === null && structure !== null ? () => guard(transfer.chooseImportFile) : undefined
|
|
472
|
-
}, pendingCount: changeCount, submitting: submitting, engine: engine, chips: chips, columns: commandColumns, pageSize: pageSize, bounds: bounds, commandRef: command, onChipsChange: applyChips, onJumpToColumn: jumpToColumn, onRefresh: refresh, onAddRow: () => setPending((now) => addInsertWith(now, {})), onDeleteRows: deleteSelected, onCloneRows: () => cloneRows([...selected]), onRevertRows: () => revertRowKeys(selected), onToggleRecordView: toggleRecordView, onPageSizeChange: changePageSize, onFirstPage: () => goToPage(0), onLastPage: () => void goToLast(), onSubmit: () => void submit(), onRevert: discardPending }), notice !== null && (_jsx(Banner, { icon: CircleAlert, tone: "error", message: notice, className: "shrink-0 pt-2", children: failure === null ? (_jsx(Button, { size: "xs", onClick: refresh, children: t('table.retry') })) : (_jsx(Button, { size: "xs", onClick: () => setFailure(null), children: t('table.dismiss') })) })), shapeChanged && (_jsx(Banner, { icon: RefreshCw, tone: "neutral", message: t('table.changed'), className: "shrink-0 pt-2", children: _jsx(Button, { size: "xs", onClick: refresh, children: t('table.reload') }) })), transfer.notice !== null &&
|
|
475
|
+
}, pendingCount: changeCount, submitting: submitting, engine: engine, chips: chips, columns: commandColumns, pageSize: pageSize, bounds: bounds, commandRef: command, onChipsChange: applyChips, onJumpToColumn: jumpToColumn, onRefresh: refresh, onAddRow: () => setPending((now) => addInsertWith(now, {})), onDeleteRows: deleteSelected, onCloneRows: () => cloneRows([...selected]), onRevertRows: () => revertRowKeys(selected), onToggleRecordView: toggleRecordView, onPageSizeChange: changePageSize, onFirstPage: () => goToPage(0), onLastPage: () => void goToLast(), onSubmit: () => void submit(), onRevert: discardPending }), notice !== null && (_jsx(Banner, { icon: CircleAlert, tone: "error", message: notice, className: "shrink-0 pt-2", children: failure === null ? (_jsx(Button, { size: "xs", onClick: refresh, children: t('table.retry') })) : (_jsx(Button, { size: "xs", onClick: () => setFailure(null), children: t('table.dismiss') })) })), shapeChanged && (_jsx(Banner, { icon: RefreshCw, tone: "neutral", message: t('table.changed'), className: "shrink-0 pt-2", children: _jsx(Button, { size: "xs", onClick: refresh, children: t('table.reload') }) })), transfer.notice !== null && _jsx(TransferBanner, { notice: transfer.notice, onCancel: transfer.cancelExport, onDismiss: transfer.dismiss }), loaded === null ? (_jsx(PanelEmpty, { busy: notice === null, children: notice === null ? t('table.loading') : t('table.notLoaded') })) : (_jsxs(_Fragment, { children: [_jsx(ValueDock, { open: recordOpen, width: recordWidth ?? RECORD_WIDTH, onWidthChange: setRecordWidth, panel: _jsx(RecordView, { label: t('table.recordFields', { table }), columns: columns, rows: rows, index: recordIndex, onIndexChange: changeRecordIndex, focusedColumn: focus !== null && focusedIndex === recordIndex ? focus.column : null, editable: editable, onCommit: commit, loadValue: loadValue, onFocusedCellChange: followInGrid, onFollow: onAction === undefined ? undefined : follow, canFollow: (cell) => referenceAt(cell) !== null, onClose: () => setRecordOpen(false), empty: t('table.empty') }), children: _jsx("div", { "aria-busy": rowsLoad.loading, className: clsx('relative flex min-h-0 flex-1 flex-col', rowsLoad.loading && 'pointer-events-none opacity-60'), children: _jsx(DataGrid, { ref: grid, label: t('table.grid', { table }), columns: columns, rows: rows, editable: editable, empty: t('table.empty'), selectedKeys: selected, onSelectedKeysChange: setSelected, onCommit: commit, loadValue: loadValue, sorts: sorts, onSortsChange: changeSorts, onFocusedCellChange: setFocus, onDeleteSelected: editable ? deleteSelected : undefined, sqlTarget: sqlTarget, queryMenu: queryMenu, menu: gridMenu, onDuplicateRows: editable ? cloneRows : undefined, onFollow: onAction === undefined ? undefined : follow, canFollow: (cell) => referenceAt(cell) !== null, onRangeChange: setRange, initialLayout: stored ?? undefined, onLayoutChange: remember, focusRequest: focusRequest, columnRequest: columnRequest }) }) }), _jsx(TableStatusBar, { elapsedMs: loaded.elapsedMs, bounds: bounds, counting: counting, selection: selectionFigures, onCount: () => void countRows(), onPrevious: () => goToPage(offset - pageSize), onNext: () => goToPage(offset + pageSize) })] })), transfer.importDraft !== null && structure !== null && (_jsx(ImportDialog, { open: true, path: transfer.importDraft.path, tableColumns: importableColumns(structure.columns), sample: transfer.importDraft.sample, header: transfer.importDraft.header, mapping: transfer.importDraft.mapping, busy: transfer.importDraft.busy, error: transfer.importDraft.error, onHeaderChange: transfer.changeHeader, onMappingChange: transfer.changeMapping, onImport: transfer.runImport, onCancel: transfer.closeImport, onOpenChange: (open) => !open && transfer.closeImport() })), _jsx(PromptDialog, { open: discard !== null, danger: true, title: t('table.discard.title'), description: t('table.discard.description'), confirmLabel: t('table.discard.confirm'), onConfirm: () => {
|
|
473
476
|
discard?.run();
|
|
474
477
|
setDiscard(null);
|
|
475
478
|
}, onOpenChange: (open) => {
|
|
@@ -478,3 +481,7 @@ function TableBody({ connection, schema, table, toolbarStart, defaultWhere, defa
|
|
|
478
481
|
}
|
|
479
482
|
} })] }));
|
|
480
483
|
}
|
|
484
|
+
function TransferBanner({ notice, onCancel, onDismiss }) {
|
|
485
|
+
const { t } = useTranslation('database');
|
|
486
|
+
return (_jsx(Banner, { icon: notice.kind === 'failed' ? CircleAlert : notice.kind === 'exporting' ? Download : Check, tone: notice.kind === 'failed' ? 'error' : 'neutral', message: transferText(notice, t), className: "shrink-0 pt-2", children: notice.kind === 'exporting' ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { size: 12, label: t('table.export.running', { format: notice.format.toUpperCase() }) }), _jsx(Button, { size: "xs", onClick: onCancel, children: t('table.export.cancel') })] })) : (_jsx(Button, { size: "xs", onClick: onDismiss, children: t('table.dismiss') })) }));
|
|
487
|
+
}
|
|
@@ -14,6 +14,7 @@ const NAME_BEING_TYPED = /^\s*(?:[\w$]*|[`"][^`"]*[`"]?)$/;
|
|
|
14
14
|
export const chipKey = (chip) => (chip.kind === 'sort' ? `sort:${chip.column}` : `${chip.kind}:${chip.kind === 'filter' ? chip.sql : chip.text}`);
|
|
15
15
|
const isFilter = (chip) => chip.kind === 'filter';
|
|
16
16
|
const isSort = (chip) => chip.kind === 'sort';
|
|
17
|
+
const sortChip = (sort) => ({ kind: 'sort', column: sort.column, direction: sort.direction });
|
|
17
18
|
/* A name as typed in SQL: bare when it can be, else in double quotes. The quotes are only for the text of a chip; `sql` quotes per engine. */
|
|
18
19
|
export const identifierText = (name) => (BARE_NAME.test(name) ? name : `"${name.replaceAll('"', '""')}"`);
|
|
19
20
|
/* The conditions AND-ed together, each in parentheses when it holds an OR and there is more than one. */
|
|
@@ -39,10 +40,7 @@ export const chipsToOrderBy = (engine, chips) => chips
|
|
|
39
40
|
.join(', ');
|
|
40
41
|
export const sortsOf = (chips) => chips.filter(isSort).map(({ column, direction }) => ({ column, direction }));
|
|
41
42
|
/* The sorts of a header menu replace every sort and raw ORDER BY, keeping the filters where they are. */
|
|
42
|
-
export const withSorts = (chips, sorts) => [
|
|
43
|
-
...chips.filter(isFilter),
|
|
44
|
-
...sorts.map((sort) => ({ kind: 'sort', column: sort.column, direction: sort.direction }))
|
|
45
|
-
];
|
|
43
|
+
export const withSorts = (chips, sorts) => [...chips.filter(isFilter), ...sorts.map(sortChip)];
|
|
46
44
|
/* A column that is not sorted joins as ascending; one that is flips its direction where it stands. */
|
|
47
45
|
export const toggleSort = (chips, column) => {
|
|
48
46
|
if (!chips.some((chip) => isSort(chip) && chip.column === column)) {
|
|
@@ -75,7 +73,7 @@ export const sortChipsOf = (engine, orderBy) => {
|
|
|
75
73
|
return [];
|
|
76
74
|
}
|
|
77
75
|
const sorts = parseOrderByLoose(engine, text);
|
|
78
|
-
return sorts === null ? [{ kind: 'order', text }] : sorts.map(
|
|
76
|
+
return sorts === null ? [{ kind: 'order', text }] : sorts.map(sortChip);
|
|
79
77
|
};
|
|
80
78
|
export const chipsFromQuery = (engine, { where, orderBy, remembered }) => [
|
|
81
79
|
...filterChipsOf(where, remembered),
|
|
@@ -112,13 +110,11 @@ const readOperation = (rest) => {
|
|
|
112
110
|
const operator = compare[1] === '==' ? '=' : compare[1] === '!=' ? '<>' : compare[1];
|
|
113
111
|
return isClosed(compare[2]) ? `${operator} ${compare[2]}` : null;
|
|
114
112
|
}
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (member !== null) {
|
|
121
|
-
return isClosed(member[2]) ? `${upper(member[1])} ${member[2]}` : null;
|
|
113
|
+
for (const form of [PATTERN, MEMBER]) {
|
|
114
|
+
const found = form.exec(rest);
|
|
115
|
+
if (found !== null) {
|
|
116
|
+
return isClosed(found[2]) ? `${upper(found[1])} ${found[2]}` : null;
|
|
117
|
+
}
|
|
122
118
|
}
|
|
123
119
|
const between = BETWEEN.exec(rest);
|
|
124
120
|
if (between !== null) {
|
package/dist/table/grid-rows.js
CHANGED
|
@@ -7,7 +7,8 @@ export const insertedRowKey = (id) => `new:${id}`;
|
|
|
7
7
|
export const parseRowKey = (key) => key.startsWith('new:') ? { kind: 'inserted', id: Number(key.slice(4)) } : { kind: 'loaded', index: Number(key.slice(4)) };
|
|
8
8
|
export const buildGridColumns = (loaded, structure) => {
|
|
9
9
|
const keys = { primaryKey: structure?.primaryKey ?? [], foreignKey: structure?.foreignKeys.flatMap((key) => key.columns) ?? [] };
|
|
10
|
-
|
|
10
|
+
const infoOf = (name) => structure?.columns.find((info) => info.name === name);
|
|
11
|
+
return loaded.columns.map((column) => gridColumnOf(column, infoOf(column.name), keys));
|
|
11
12
|
};
|
|
12
13
|
/* The loaded rows with their pending edits laid over them, then the rows waiting to be inserted. */
|
|
13
14
|
export const buildGridRows = ({ structure, loaded, pending, offset }) => {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { PAGE_SIZES } from './paging.js';
|
|
2
|
-
/*
|
|
2
|
+
/*
|
|
3
|
+
* Bump when the shape changes; a stored value of another version is ignored rather than migrated. `filters`
|
|
4
|
+
* came later without a bump, since `where` still says the same thing for a value without it.
|
|
5
|
+
*/
|
|
3
6
|
export const LAYOUT_VERSION = 1;
|
|
4
7
|
export const EMPTY_LAYOUT = { widths: {}, hidden: [], pinned: [], pageSize: null, where: '', orderBy: '', filters: [] };
|
|
5
8
|
export const layoutStorageKey = (connectionId, schema, table) => `database:table:${connectionId}:${schema}.${table}`;
|
|
6
9
|
const isStringList = (value) => Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
7
|
-
const isStoredFilter = (value) => isRecord(value) && typeof value.text === 'string' && typeof value.sql === 'string' && value.sql.trim() !== '';
|
|
8
10
|
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
+
const isStoredFilter = (value) => isRecord(value) && typeof value.text === 'string' && typeof value.sql === 'string' && value.sql.trim() !== '';
|
|
9
12
|
export const serializeLayout = (layout) => JSON.stringify({ version: LAYOUT_VERSION, ...layout });
|
|
10
13
|
/* The layout a stored text holds, or `null` when it is missing, broken or from another version. A field that is off is dropped alone. */
|
|
11
14
|
export const parseStoredLayout = (text) => {
|
package/dist/table/pending.js
CHANGED
|
@@ -69,7 +69,6 @@ export const rowKeyOf = (structure, loaded, row) => {
|
|
|
69
69
|
}
|
|
70
70
|
return key;
|
|
71
71
|
};
|
|
72
|
-
const numeric = (text) => Number(text);
|
|
73
72
|
/* Updates, then deletes, then inserts, all computed against the page as loaded. Throws for a change on a row without a usable key, which the grid never lets a person make. */
|
|
74
73
|
export const toRowChanges = (structure, loaded, pending) => {
|
|
75
74
|
const keyOf = (row) => {
|
|
@@ -80,7 +79,7 @@ export const toRowChanges = (structure, loaded, pending) => {
|
|
|
80
79
|
return key;
|
|
81
80
|
};
|
|
82
81
|
const updates = Object.keys(pending.edits)
|
|
83
|
-
.map(
|
|
82
|
+
.map(Number)
|
|
84
83
|
.filter((row) => !pending.deletes.has(row))
|
|
85
84
|
.sort((left, right) => left - right)
|
|
86
85
|
.map((row) => ({ kind: 'update', key: keyOf(row), values: pending.edits[row] }));
|
|
@@ -88,7 +87,16 @@ export const toRowChanges = (structure, loaded, pending) => {
|
|
|
88
87
|
const inserts = pending.inserts.map((row) => ({ kind: 'insert', values: row.values }));
|
|
89
88
|
return [...updates, ...deletes, ...inserts];
|
|
90
89
|
};
|
|
90
|
+
const keyValueText = (value) => {
|
|
91
|
+
if (value === null) {
|
|
92
|
+
return 'NULL';
|
|
93
|
+
}
|
|
94
|
+
if (typeof value === 'object') {
|
|
95
|
+
return `0x${value.hex}`;
|
|
96
|
+
}
|
|
97
|
+
return typeof value === 'string' ? `'${value}'` : String(value);
|
|
98
|
+
};
|
|
91
99
|
/* A key as a person would write it in a WHERE: `id = 5, tenant = 'a'`. */
|
|
92
100
|
export const describeKey = (key) => Object.entries(key)
|
|
93
|
-
.map(([column, value]) => `${column} = ${
|
|
101
|
+
.map(([column, value]) => `${column} = ${keyValueText(value)}`)
|
|
94
102
|
.join(', ');
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { messageOf } from '@adecore/ui';
|
|
3
|
-
import {
|
|
3
|
+
import { isCancelled } from '../client/types.js';
|
|
4
4
|
import { formatOfPath, importableColumns, mapColumn, matchColumns } from './import-mapping.js';
|
|
5
|
+
/* How long a finished export or import stays on the strip. */
|
|
6
|
+
const NOTICE_MS = 8000;
|
|
5
7
|
/* Export of the table as filtered, and import of a file into it, with the state both need between the dialogs and the strip. */
|
|
6
8
|
export function useTableTransfer({ client, session, files, schema, table, structure, where, orderBy, onImported, report }) {
|
|
7
9
|
const [notice, setNotice] = useState(null);
|
|
@@ -13,7 +15,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
|
|
|
13
15
|
if (notice?.kind !== 'exported' && notice?.kind !== 'imported') {
|
|
14
16
|
return;
|
|
15
17
|
}
|
|
16
|
-
const timer = setTimeout(() => setNotice(null),
|
|
18
|
+
const timer = setTimeout(() => setNotice(null), NOTICE_MS);
|
|
17
19
|
return () => clearTimeout(timer);
|
|
18
20
|
}, [notice]);
|
|
19
21
|
useEffect(() => () => sampling.current?.abort(), []);
|
|
@@ -43,8 +45,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
|
|
|
43
45
|
show({ kind: 'exported', rows: result.rows });
|
|
44
46
|
}
|
|
45
47
|
catch (error) {
|
|
46
|
-
|
|
47
|
-
if (cancelled) {
|
|
48
|
+
if (controller.signal.aborted || isCancelled(error)) {
|
|
48
49
|
setNotice(null);
|
|
49
50
|
}
|
|
50
51
|
else {
|
|
@@ -93,7 +94,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
|
|
|
93
94
|
setImportDraft((now) => now === null || now.path !== path ? now : { ...now, header, sample: read, mapping: matchColumns(read.columns, tableColumns, header) });
|
|
94
95
|
}
|
|
95
96
|
catch (error) {
|
|
96
|
-
if (!(error
|
|
97
|
+
if (!isCancelled(error)) {
|
|
97
98
|
setImportDraft((now) => (now === null || now.path !== path ? now : { ...now, error: messageOf(error) }));
|
|
98
99
|
}
|
|
99
100
|
}
|
package/dist/testing/fake.js
CHANGED
|
@@ -32,12 +32,15 @@ const toTables = (database) => new Map(Object.entries(database.schemas).map(([sc
|
|
|
32
32
|
}
|
|
33
33
|
]))
|
|
34
34
|
]));
|
|
35
|
-
const
|
|
35
|
+
const findSchema = (session, schema) => {
|
|
36
36
|
const tables = session.schemas.get(schema);
|
|
37
37
|
if (tables === undefined) {
|
|
38
38
|
throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
return tables;
|
|
41
|
+
};
|
|
42
|
+
const findTable = (session, schema, name) => {
|
|
43
|
+
const table = findSchema(session, schema).get(name);
|
|
41
44
|
if (table === undefined) {
|
|
42
45
|
throw new FakeError('query-failed', `Table '${schema}.${name}' doesn't exist`, '42S02');
|
|
43
46
|
}
|
|
@@ -191,9 +194,7 @@ const selectSchema = (session, schema) => {
|
|
|
191
194
|
if (schema === undefined) {
|
|
192
195
|
return;
|
|
193
196
|
}
|
|
194
|
-
|
|
195
|
-
throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
|
|
196
|
-
}
|
|
197
|
+
findSchema(session, schema);
|
|
197
198
|
session.schema = schema;
|
|
198
199
|
};
|
|
199
200
|
/*
|
|
@@ -246,10 +247,7 @@ export const fakeDatabaseTransport = (options) => {
|
|
|
246
247
|
schemas: [...sessionOf(session).schemas.keys()].map((name) => ({ name, system: SYSTEM_SCHEMAS.includes(name) }))
|
|
247
248
|
}),
|
|
248
249
|
tables: ({ session, schema }) => {
|
|
249
|
-
const tables = sessionOf(session)
|
|
250
|
-
if (tables === undefined) {
|
|
251
|
-
throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
|
|
252
|
-
}
|
|
250
|
+
const tables = findSchema(sessionOf(session), schema);
|
|
253
251
|
return { tables: [...tables].map(([name, table]) => ({ name, kind: table.kind, rowEstimate: table.rows.length, comment: null })) };
|
|
254
252
|
},
|
|
255
253
|
structure: ({ session, schema, table: name }) => {
|
|
@@ -69,7 +69,7 @@ export function ValueEditor({ column, value, editable, onCommit }) {
|
|
|
69
69
|
copyText(rendered.text);
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
|
-
copyText(
|
|
72
|
+
copyText(placeholder ?? draft.text);
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
const editor = (extra) => (_jsx(TextArea, { "aria-label": t(source.binary ? 'value.hexEditor' : 'value.editor', { column: column.name }), wrap: wrap || source.binary ? 'soft' : 'off', spellCheck: false, readOnly: !editable, placeholder: placeholder, value: draft.mode === 'value' ? draft.text : '', className: clsx(CODE_TEXT, extra), onChange: (event) => change({ text: event.target.value, mode: 'value' }), onKeyDown: handleKeyDown }));
|
package/dist/value/draft.js
CHANGED
|
@@ -2,12 +2,7 @@ import { parseDraft } from '../grid/edit-value.js';
|
|
|
2
2
|
import { parseHex } from './hex.js';
|
|
3
3
|
import { checkJson, isJsonDocument } from './json.js';
|
|
4
4
|
/* The text a value is edited as: a binary value as its hex, everything else as it is written. */
|
|
5
|
-
const textOf = (value) =>
|
|
6
|
-
if (typeof value === 'object') {
|
|
7
|
-
return value.hex;
|
|
8
|
-
}
|
|
9
|
-
return String(value);
|
|
10
|
-
};
|
|
5
|
+
const textOf = (value) => (typeof value === 'object' ? value.hex : String(value));
|
|
11
6
|
export const sourceOf = (column, value) => {
|
|
12
7
|
const binary = column.kind === 'binary' || (value !== null && typeof value === 'object');
|
|
13
8
|
const original = value === null ? { text: '', mode: 'null' } : { text: textOf(value), mode: 'value' };
|
package/dist/value/json.js
CHANGED
|
@@ -42,11 +42,11 @@ export const prettyJson = (text) => {
|
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
44
|
if (char === '{' || char === '[') {
|
|
45
|
-
const
|
|
45
|
+
const close = char === '{' ? '}' : ']';
|
|
46
46
|
output += char;
|
|
47
|
-
if (
|
|
48
|
-
output +=
|
|
49
|
-
i = text.indexOf(
|
|
47
|
+
if (nextToken(i + 1) === close) {
|
|
48
|
+
output += close;
|
|
49
|
+
i = text.indexOf(close, i + 1) + 1;
|
|
50
50
|
continue;
|
|
51
51
|
}
|
|
52
52
|
depth++;
|