@rozenite/sqlite-plugin 1.13.0 → 2.1.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/CHANGELOG.md +23 -0
- package/dist/devtools/assets/panel-BarPLQ4-.css +1 -0
- package/dist/devtools/assets/{panel-DjRs5NTl.js → panel-BsdCagVv.js} +45 -44
- package/dist/devtools/panel.html +2 -2
- package/dist/react-native/chunks/index.require.js +4 -16
- package/dist/react-native/chunks/sql.require.js +1 -3
- package/dist/react-native/chunks/useRozeniteSqlitePlugin.require.js +92 -110
- package/dist/react-native/index.d.ts +2 -2
- package/dist/rozenite.json +1 -1
- package/package.json +27 -31
- package/react-native.ts +7 -14
- package/src/react-native/adapters/__tests__/expo-sqlite.test.ts +4 -14
- package/src/react-native/adapters/expo-sqlite.ts +11 -40
- package/src/react-native/adapters/generic.ts +5 -13
- package/src/react-native/adapters/index.ts +1 -4
- package/src/react-native/sqlite-view.ts +2 -8
- package/src/react-native/useRozeniteSqlitePlugin.ts +110 -130
- package/src/react-native/useSqliteAgentTools.ts +5 -14
- package/src/shared/__tests__/bridge-values.test.ts +6 -11
- package/src/shared/__tests__/sql.test.ts +4 -8
- package/src/shared/bridge-values.ts +2 -5
- package/src/shared/sql.ts +3 -7
- package/src/ui/__tests__/sql-editor-utils.test.ts +1 -5
- package/src/ui/__tests__/sqlite-drop-mutations.test.ts +138 -0
- package/src/ui/__tests__/sqlite-row-mutations.test.ts +1 -3
- package/src/ui/__tests__/sqlite-table-column-order.test.ts +4 -19
- package/src/ui/cell-detail-drawer.tsx +1 -6
- package/src/ui/globals.css +26 -65
- package/src/ui/panel.tsx +442 -568
- package/src/ui/query-result-table.tsx +8 -26
- package/src/ui/sql-editor-utils.ts +24 -55
- package/src/ui/sql-editor.tsx +13 -46
- package/src/ui/sqlite-data-table.tsx +11 -28
- package/src/ui/sqlite-drop-modal.tsx +193 -0
- package/src/ui/sqlite-drop-mutations.ts +53 -0
- package/src/ui/sqlite-introspection.ts +9 -25
- package/src/ui/sqlite-row-delete-modal.tsx +5 -12
- package/src/ui/sqlite-row-edit-modal.tsx +22 -64
- package/src/ui/sqlite-row-edit-value.ts +2 -11
- package/src/ui/sqlite-row-mutations.ts +10 -32
- package/src/ui/sqlite-table-column-order.ts +8 -23
- package/src/ui/use-sqlite-requests.ts +46 -86
- package/src/ui/utils.ts +1 -5
- package/src/ui/value-utils.tsx +3 -11
- package/dist/devtools/assets/panel-CIU0JBOs.css +0 -1
- package/postcss.config.js +0 -6
package/src/ui/panel.tsx
CHANGED
|
@@ -65,6 +65,14 @@ import {
|
|
|
65
65
|
} from './sqlite-introspection';
|
|
66
66
|
import { QueryResultTable } from './query-result-table';
|
|
67
67
|
import { SqliteRowDeleteModal } from './sqlite-row-delete-modal';
|
|
68
|
+
import { SqliteDropModal, type SqliteDropModalTarget } from './sqlite-drop-modal';
|
|
69
|
+
import {
|
|
70
|
+
buildDropAllEntitiesSql,
|
|
71
|
+
buildDropEntitySql,
|
|
72
|
+
buildSetForeignKeysSql,
|
|
73
|
+
isForeignKeysEnabled,
|
|
74
|
+
SQLITE_READ_FOREIGN_KEYS_SQL,
|
|
75
|
+
} from './sqlite-drop-mutations';
|
|
68
76
|
import { SqliteDataTable } from './sqlite-data-table';
|
|
69
77
|
import { SqliteRowEditModal } from './sqlite-row-edit-modal';
|
|
70
78
|
import { SqlEditor, type SqlEditorHandle } from './sql-editor';
|
|
@@ -100,12 +108,7 @@ import {
|
|
|
100
108
|
getDefaultTableColumnOrder,
|
|
101
109
|
resolveTableColumnOrderUpdate,
|
|
102
110
|
} from './sqlite-table-column-order';
|
|
103
|
-
import {
|
|
104
|
-
copyToClipboard,
|
|
105
|
-
downloadTextFile,
|
|
106
|
-
formatNumber,
|
|
107
|
-
slugifyFileName,
|
|
108
|
-
} from './utils';
|
|
111
|
+
import { copyToClipboard, downloadTextFile, formatNumber, slugifyFileName } from './utils';
|
|
109
112
|
import { getResultSummary, getScriptResultSummary } from './value-utils';
|
|
110
113
|
import './globals.css';
|
|
111
114
|
|
|
@@ -148,8 +151,16 @@ type ActiveRowMutationState = {
|
|
|
148
151
|
rowIndex: number;
|
|
149
152
|
} | null;
|
|
150
153
|
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
/**
|
|
155
|
+
* The drop dialog operates on the target captured when it opened, so that a
|
|
156
|
+
* mid-dialog explorer reload cannot repoint it at a different object.
|
|
157
|
+
*/
|
|
158
|
+
type ActiveDropState = {
|
|
159
|
+
databaseId: string;
|
|
160
|
+
target: SqliteDropModalTarget;
|
|
161
|
+
} | null;
|
|
162
|
+
|
|
163
|
+
const DEFAULT_QUERY = 'SELECT name, type FROM sqlite_schema ORDER BY type, name';
|
|
153
164
|
const DEFAULT_QUERY_LIMIT = 100;
|
|
154
165
|
const DEFAULT_PAGE_SIZE = 50;
|
|
155
166
|
const MIN_EDITOR_HEIGHT = 180;
|
|
@@ -164,18 +175,21 @@ const DEFAULT_EXPLORER_STATE: ExplorerState = {
|
|
|
164
175
|
loaded: false,
|
|
165
176
|
};
|
|
166
177
|
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
178
|
+
const DEFAULT_DROP_TARGET: SqliteDropModalTarget = {
|
|
179
|
+
kind: 'entity',
|
|
180
|
+
entityType: 'table',
|
|
181
|
+
qualifiedName: '',
|
|
182
|
+
confirmationValue: '',
|
|
183
|
+
sql: '',
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const joinClassNames = (...classNames: Array<string | false | null | undefined>) =>
|
|
187
|
+
classNames.filter(Boolean).join(' ');
|
|
170
188
|
|
|
171
|
-
const safeError = (error: unknown) =>
|
|
172
|
-
error instanceof Error ? error.message : String(error);
|
|
189
|
+
const safeError = (error: unknown) => (error instanceof Error ? error.message : String(error));
|
|
173
190
|
|
|
174
|
-
const getEntityKey = (
|
|
175
|
-
databaseId
|
|
176
|
-
schemaName: string,
|
|
177
|
-
entityName: string,
|
|
178
|
-
) => JSON.stringify([databaseId, schemaName, entityName]);
|
|
191
|
+
const getEntityKey = (databaseId: string, schemaName: string, entityName: string) =>
|
|
192
|
+
JSON.stringify([databaseId, schemaName, entityName]);
|
|
179
193
|
|
|
180
194
|
const getSchemaKey = (databaseId: string, schemaName: string) =>
|
|
181
195
|
JSON.stringify([databaseId, schemaName]);
|
|
@@ -183,20 +197,12 @@ const getSchemaKey = (databaseId: string, schemaName: string) =>
|
|
|
183
197
|
const getLineNumberAtPosition = (value: string, position: number) =>
|
|
184
198
|
value.slice(0, Math.max(0, position)).split('\n').length;
|
|
185
199
|
|
|
186
|
-
const buildGeneratedSelect = (
|
|
187
|
-
entity: SqliteEntity | null,
|
|
188
|
-
rowLimit: number,
|
|
189
|
-
) => {
|
|
200
|
+
const buildGeneratedSelect = (entity: SqliteEntity | null, rowLimit: number) => {
|
|
190
201
|
if (!entity) {
|
|
191
202
|
return DEFAULT_QUERY;
|
|
192
203
|
}
|
|
193
204
|
|
|
194
|
-
return buildBrowseEntitySql(
|
|
195
|
-
entity.schemaName,
|
|
196
|
-
entity.name,
|
|
197
|
-
Math.max(1, Math.floor(rowLimit)),
|
|
198
|
-
0,
|
|
199
|
-
);
|
|
205
|
+
return buildBrowseEntitySql(entity.schemaName, entity.name, Math.max(1, Math.floor(rowLimit)), 0);
|
|
200
206
|
};
|
|
201
207
|
|
|
202
208
|
const buildCsv = (result: SqliteQueryResult | null) => {
|
|
@@ -212,15 +218,11 @@ const buildCsv = (result: SqliteQueryResult | null) => {
|
|
|
212
218
|
|
|
213
219
|
return [
|
|
214
220
|
result.columns.join(','),
|
|
215
|
-
...result.rows.map((row) =>
|
|
216
|
-
result.columns.map((column) => escapeCell(row[column])).join(','),
|
|
217
|
-
),
|
|
221
|
+
...result.rows.map((row) => result.columns.map((column) => escapeCell(row[column])).join(',')),
|
|
218
222
|
].join('\n');
|
|
219
223
|
};
|
|
220
224
|
|
|
221
|
-
const getDefaultSelectedQueryStatementIndex = (
|
|
222
|
-
execution: SqliteScriptResult | null,
|
|
223
|
-
) => {
|
|
225
|
+
const getDefaultSelectedQueryStatementIndex = (execution: SqliteScriptResult | null) => {
|
|
224
226
|
if (!execution || execution.statements.length === 0) {
|
|
225
227
|
return null;
|
|
226
228
|
}
|
|
@@ -232,18 +234,11 @@ const getDefaultSelectedQueryStatementIndex = (
|
|
|
232
234
|
);
|
|
233
235
|
};
|
|
234
236
|
|
|
235
|
-
const getStatementQueryResult = (
|
|
236
|
-
statement
|
|
237
|
-
) => statement?.execution?.result ?? null;
|
|
237
|
+
const getStatementQueryResult = (statement: SqliteScriptStatementResult | null) =>
|
|
238
|
+
statement?.execution?.result ?? null;
|
|
238
239
|
|
|
239
|
-
const getStatementSelectorLabel = (
|
|
240
|
-
statement
|
|
241
|
-
maxLength = 72,
|
|
242
|
-
) => {
|
|
243
|
-
const normalizedSql = statement.input.sql
|
|
244
|
-
.replace(/\s+/g, ' ')
|
|
245
|
-
.replace(/;\s*$/, '')
|
|
246
|
-
.trim();
|
|
240
|
+
const getStatementSelectorLabel = (statement: SqliteScriptStatementResult, maxLength = 72) => {
|
|
241
|
+
const normalizedSql = statement.input.sql.replace(/\s+/g, ' ').replace(/;\s*$/, '').trim();
|
|
247
242
|
|
|
248
243
|
if (normalizedSql.length <= maxLength) {
|
|
249
244
|
return `${formatNumber(statement.index + 1)}. ${normalizedSql}`;
|
|
@@ -277,19 +272,13 @@ const buildExplorerGroups = (
|
|
|
277
272
|
|
|
278
273
|
return schemas
|
|
279
274
|
.map((schema) => {
|
|
280
|
-
const schemaEntities = entities.filter(
|
|
281
|
-
(entity) => entity.schemaName === schema.name,
|
|
282
|
-
);
|
|
275
|
+
const schemaEntities = entities.filter((entity) => entity.schemaName === schema.name);
|
|
283
276
|
const filteredEntities = term
|
|
284
277
|
? schemaEntities.filter((entity) =>
|
|
285
|
-
`${entity.name} ${entity.type} ${schema.name}
|
|
286
|
-
.toLowerCase()
|
|
287
|
-
.includes(term),
|
|
278
|
+
`${entity.name} ${entity.type} ${schema.name}`.toLowerCase().includes(term),
|
|
288
279
|
)
|
|
289
280
|
: schemaEntities;
|
|
290
|
-
const tables = filteredEntities.filter(
|
|
291
|
-
(entity) => entity.type === 'table',
|
|
292
|
-
);
|
|
281
|
+
const tables = filteredEntities.filter((entity) => entity.type === 'table');
|
|
293
282
|
const views = filteredEntities.filter((entity) => entity.type === 'view');
|
|
294
283
|
const visible =
|
|
295
284
|
term.length === 0 ||
|
|
@@ -315,6 +304,7 @@ const iconButtonClassName =
|
|
|
315
304
|
'sqlite-icon-button inline-flex h-10 w-10 items-center justify-center rounded-xl';
|
|
316
305
|
const primaryIconButtonClassName = `${iconButtonClassName} sqlite-button-primary`;
|
|
317
306
|
const secondaryIconButtonClassName = `${iconButtonClassName} sqlite-button-secondary`;
|
|
307
|
+
const dangerIconButtonClassName = `${iconButtonClassName} sqlite-button-danger`;
|
|
318
308
|
const ghostIconButtonClassName = `${iconButtonClassName} sqlite-button-ghost`;
|
|
319
309
|
|
|
320
310
|
const renderEmptyState = (
|
|
@@ -348,8 +338,7 @@ export default function SqlitePanel() {
|
|
|
348
338
|
const client = useRozeniteDevToolsClient<SqliteEventMap>({
|
|
349
339
|
pluginId: PLUGIN_ID,
|
|
350
340
|
});
|
|
351
|
-
const { requestDatabases, requestQuery, requestScriptExecution } =
|
|
352
|
-
useSqliteRequests(client);
|
|
341
|
+
const { requestDatabases, requestQuery, requestScriptExecution } = useSqliteRequests(client);
|
|
353
342
|
|
|
354
343
|
const querySplitRef = useRef<HTMLDivElement | null>(null);
|
|
355
344
|
const sidebarRef = useRef<HTMLDivElement | null>(null);
|
|
@@ -365,19 +354,14 @@ export default function SqlitePanel() {
|
|
|
365
354
|
const [editorSplit, setEditorSplit] = useState(50);
|
|
366
355
|
const [expandedDatabaseIds, setExpandedDatabaseIds] = useState<string[]>([]);
|
|
367
356
|
const [expandedSchemaKeys, setExpandedSchemaKeys] = useState<string[]>([]);
|
|
368
|
-
const [structureSection, setStructureSection] =
|
|
369
|
-
useState<StructureSection>('columns');
|
|
357
|
+
const [structureSection, setStructureSection] = useState<StructureSection>('columns');
|
|
370
358
|
|
|
371
359
|
const [databases, setDatabases] = useState<SqliteDatabaseInfo[]>([]);
|
|
372
|
-
const [selectedDatabaseId, setSelectedDatabaseId] = useState<string | null>(
|
|
373
|
-
null,
|
|
374
|
-
);
|
|
360
|
+
const [selectedDatabaseId, setSelectedDatabaseId] = useState<string | null>(null);
|
|
375
361
|
const [explorerStateByDatabase, setExplorerStateByDatabase] = useState<
|
|
376
362
|
Record<string, ExplorerState>
|
|
377
363
|
>({});
|
|
378
|
-
const [selectedEntityKey, setSelectedEntityKey] = useState<string | null>(
|
|
379
|
-
null,
|
|
380
|
-
);
|
|
364
|
+
const [selectedEntityKey, setSelectedEntityKey] = useState<string | null>(null);
|
|
381
365
|
|
|
382
366
|
const [databaseLoading, setDatabaseLoading] = useState(false);
|
|
383
367
|
const [browseLoading, setBrowseLoading] = useState(false);
|
|
@@ -391,9 +375,7 @@ export default function SqlitePanel() {
|
|
|
391
375
|
|
|
392
376
|
const [browseOffset, setBrowseOffset] = useState(0);
|
|
393
377
|
const [browsePageSize, setBrowsePageSize] = useState(DEFAULT_PAGE_SIZE);
|
|
394
|
-
const [browseResult, setBrowseResult] = useState<SqliteQueryResult | null>(
|
|
395
|
-
null,
|
|
396
|
-
);
|
|
378
|
+
const [browseResult, setBrowseResult] = useState<SqliteQueryResult | null>(null);
|
|
397
379
|
const [entityRowCount, setEntityRowCount] = useState<number | null>(null);
|
|
398
380
|
const [structureState, setStructureState] = useState<StructureState>({
|
|
399
381
|
columns: [],
|
|
@@ -402,37 +384,32 @@ export default function SqlitePanel() {
|
|
|
402
384
|
});
|
|
403
385
|
|
|
404
386
|
const [queryInput, setQueryInput] = useState(DEFAULT_QUERY);
|
|
405
|
-
const [queryExecution, setQueryExecution] =
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
387
|
+
const [queryExecution, setQueryExecution] = useState<SqliteScriptResult | null>(null);
|
|
388
|
+
const [selectedQueryStatementIndex, setSelectedQueryStatementIndex] = useState<number | null>(
|
|
389
|
+
null,
|
|
390
|
+
);
|
|
409
391
|
const [queryRowLimit, setQueryRowLimit] = useState(DEFAULT_QUERY_LIMIT);
|
|
410
392
|
const [querySelection, setQuerySelection] = useState({ start: 0, end: 0 });
|
|
411
393
|
const [, setQueryMessage] = useState('Ready.');
|
|
412
394
|
const [queryErrorLine, setQueryErrorLine] = useState<number | null>(null);
|
|
413
|
-
const [queryColumnCache, setQueryColumnCache] = useState(() =>
|
|
414
|
-
|
|
415
|
-
);
|
|
416
|
-
const [tableColumnOrderById, setTableColumnOrderById] = useState<
|
|
417
|
-
Record<string, string[]>
|
|
418
|
-
>({});
|
|
395
|
+
const [queryColumnCache, setQueryColumnCache] = useState(() => createSqlEditorColumnCache());
|
|
396
|
+
const [tableColumnOrderById, setTableColumnOrderById] = useState<Record<string, string[]>>({});
|
|
419
397
|
const [editingRow, setEditingRow] = useState<ActiveRowMutationState>(null);
|
|
420
398
|
const [deletingRow, setDeletingRow] = useState<ActiveRowMutationState>(null);
|
|
399
|
+
const [activeDrop, setActiveDrop] = useState<ActiveDropState>(null);
|
|
421
400
|
|
|
422
401
|
const [objectSearch, setObjectSearch] = useState('');
|
|
423
402
|
const [dataSearch, setDataSearch] = useState('');
|
|
424
403
|
|
|
425
404
|
const selectedDatabase = useMemo(
|
|
426
|
-
() =>
|
|
427
|
-
databases.find((database) => database.id === selectedDatabaseId) ?? null,
|
|
405
|
+
() => databases.find((database) => database.id === selectedDatabaseId) ?? null,
|
|
428
406
|
[databases, selectedDatabaseId],
|
|
429
407
|
);
|
|
430
408
|
|
|
431
409
|
const selectedExplorerState = useMemo(
|
|
432
410
|
() =>
|
|
433
|
-
(selectedDatabaseId
|
|
434
|
-
|
|
435
|
-
: null) ?? DEFAULT_EXPLORER_STATE,
|
|
411
|
+
(selectedDatabaseId ? explorerStateByDatabase[selectedDatabaseId] : null) ??
|
|
412
|
+
DEFAULT_EXPLORER_STATE,
|
|
436
413
|
[explorerStateByDatabase, selectedDatabaseId],
|
|
437
414
|
);
|
|
438
415
|
|
|
@@ -445,8 +422,7 @@ export default function SqlitePanel() {
|
|
|
445
422
|
entities.find(
|
|
446
423
|
(entity) =>
|
|
447
424
|
selectedDatabaseId != null &&
|
|
448
|
-
getEntityKey(selectedDatabaseId, entity.schemaName, entity.name) ===
|
|
449
|
-
selectedEntityKey,
|
|
425
|
+
getEntityKey(selectedDatabaseId, entity.schemaName, entity.name) === selectedEntityKey,
|
|
450
426
|
) ?? null,
|
|
451
427
|
[entities, selectedDatabaseId, selectedEntityKey],
|
|
452
428
|
);
|
|
@@ -546,20 +522,12 @@ export default function SqlitePanel() {
|
|
|
546
522
|
}, [browseResult, filteredBrowseRows]);
|
|
547
523
|
|
|
548
524
|
const dataPageStart = filteredBrowseRows.length > 0 ? browseOffset + 1 : 0;
|
|
549
|
-
const dataPageEnd =
|
|
550
|
-
filteredBrowseRows.length > 0
|
|
551
|
-
? browseOffset + filteredBrowseRows.length
|
|
552
|
-
: 0;
|
|
525
|
+
const dataPageEnd = filteredBrowseRows.length > 0 ? browseOffset + filteredBrowseRows.length : 0;
|
|
553
526
|
const canBrowseBackward = browseOffset > 0;
|
|
554
|
-
const canBrowseForward =
|
|
555
|
-
|
|
556
|
-
const currentDataPage = selectedEntity
|
|
557
|
-
? Math.floor(browseOffset / browsePageSize) + 1
|
|
558
|
-
: 0;
|
|
527
|
+
const canBrowseForward = entityRowCount != null && browseOffset + browsePageSize < entityRowCount;
|
|
528
|
+
const currentDataPage = selectedEntity ? Math.floor(browseOffset / browsePageSize) + 1 : 0;
|
|
559
529
|
const totalDataPages =
|
|
560
|
-
entityRowCount == null || entityRowCount === 0
|
|
561
|
-
? 0
|
|
562
|
-
: Math.ceil(entityRowCount / browsePageSize);
|
|
530
|
+
entityRowCount == null || entityRowCount === 0 ? 0 : Math.ceil(entityRowCount / browsePageSize);
|
|
563
531
|
const primaryKeyColumns = useMemo(
|
|
564
532
|
() => getPrimaryKeyColumns(structureState.columns),
|
|
565
533
|
[structureState.columns],
|
|
@@ -588,11 +556,8 @@ export default function SqlitePanel() {
|
|
|
588
556
|
type: column.type || '—',
|
|
589
557
|
nullable: column.notNull ? 'No' : 'Yes',
|
|
590
558
|
defaultValue: column.defaultValue ?? '—',
|
|
591
|
-
primaryKey:
|
|
592
|
-
|
|
593
|
-
foreignKey: structureState.foreignKeys.some(
|
|
594
|
-
(foreignKey) => foreignKey.from === column.name,
|
|
595
|
-
)
|
|
559
|
+
primaryKey: column.primaryKeyOrder > 0 ? `PK ${column.primaryKeyOrder}` : '—',
|
|
560
|
+
foreignKey: structureState.foreignKeys.some((foreignKey) => foreignKey.from === column.name)
|
|
596
561
|
? 'Yes'
|
|
597
562
|
: '—',
|
|
598
563
|
extra: column.hidden > 0 ? `Hidden ${column.hidden}` : '—',
|
|
@@ -611,9 +576,7 @@ export default function SqlitePanel() {
|
|
|
611
576
|
[structureState.indexes],
|
|
612
577
|
);
|
|
613
578
|
|
|
614
|
-
const structureColumnsTableColumns = useMemo<
|
|
615
|
-
ColumnDef<StructureColumnRow, unknown>[]
|
|
616
|
-
>(
|
|
579
|
+
const structureColumnsTableColumns = useMemo<ColumnDef<StructureColumnRow, unknown>[]>(
|
|
617
580
|
() => [
|
|
618
581
|
{ id: 'name', header: 'Name', accessorKey: 'name' },
|
|
619
582
|
{ id: 'type', header: 'Type', accessorKey: 'type' },
|
|
@@ -626,9 +589,7 @@ export default function SqlitePanel() {
|
|
|
626
589
|
[],
|
|
627
590
|
);
|
|
628
591
|
|
|
629
|
-
const structureIndexesTableColumns = useMemo<
|
|
630
|
-
ColumnDef<StructureIndexRow, unknown>[]
|
|
631
|
-
>(
|
|
592
|
+
const structureIndexesTableColumns = useMemo<ColumnDef<StructureIndexRow, unknown>[]>(
|
|
632
593
|
() => [
|
|
633
594
|
{ id: 'indexName', header: 'Index Name', accessorKey: 'indexName' },
|
|
634
595
|
{ id: 'columns', header: 'Columns', accessorKey: 'columns' },
|
|
@@ -638,10 +599,7 @@ export default function SqlitePanel() {
|
|
|
638
599
|
[],
|
|
639
600
|
);
|
|
640
601
|
|
|
641
|
-
const queryStatements = useMemo(
|
|
642
|
-
() => splitSqlStatements(queryInput),
|
|
643
|
-
[queryInput],
|
|
644
|
-
);
|
|
602
|
+
const queryStatements = useMemo(() => splitSqlStatements(queryInput), [queryInput]);
|
|
645
603
|
|
|
646
604
|
const selectedQueryStatement = useMemo(() => {
|
|
647
605
|
if (!queryExecution) {
|
|
@@ -649,18 +607,13 @@ export default function SqlitePanel() {
|
|
|
649
607
|
}
|
|
650
608
|
|
|
651
609
|
const nextIndex =
|
|
652
|
-
selectedQueryStatementIndex ??
|
|
653
|
-
getDefaultSelectedQueryStatementIndex(queryExecution);
|
|
610
|
+
selectedQueryStatementIndex ?? getDefaultSelectedQueryStatementIndex(queryExecution);
|
|
654
611
|
|
|
655
612
|
if (nextIndex == null) {
|
|
656
613
|
return null;
|
|
657
614
|
}
|
|
658
615
|
|
|
659
|
-
return (
|
|
660
|
-
queryExecution.statements.find(
|
|
661
|
-
(statement) => statement.index === nextIndex,
|
|
662
|
-
) ?? null
|
|
663
|
-
);
|
|
616
|
+
return queryExecution.statements.find((statement) => statement.index === nextIndex) ?? null;
|
|
664
617
|
}, [queryExecution, selectedQueryStatementIndex]);
|
|
665
618
|
|
|
666
619
|
const activeQueryResult = useMemo(
|
|
@@ -671,8 +624,7 @@ export default function SqlitePanel() {
|
|
|
671
624
|
const selectedQueryStatementValue = selectedQueryStatement?.index ?? '';
|
|
672
625
|
|
|
673
626
|
const queryTableId = useMemo(
|
|
674
|
-
() =>
|
|
675
|
-
buildQueryTableId(selectedDatabaseId, activeQueryResult?.columns ?? []),
|
|
627
|
+
() => buildQueryTableId(selectedDatabaseId, activeQueryResult?.columns ?? []),
|
|
676
628
|
[activeQueryResult?.columns, selectedDatabaseId],
|
|
677
629
|
);
|
|
678
630
|
|
|
@@ -710,19 +662,12 @@ export default function SqlitePanel() {
|
|
|
710
662
|
);
|
|
711
663
|
|
|
712
664
|
const getTableColumnOrder = useCallback(
|
|
713
|
-
(
|
|
714
|
-
tableId: string,
|
|
715
|
-
columnIds: string[],
|
|
716
|
-
fixedLeadingColumnIds: string[] = [],
|
|
717
|
-
) =>
|
|
665
|
+
(tableId: string, columnIds: string[], fixedLeadingColumnIds: string[] = []) =>
|
|
718
666
|
resolveTableColumnOrderUpdate({
|
|
719
667
|
columnIds,
|
|
720
668
|
fixedLeadingColumnIds,
|
|
721
669
|
storedColumnOrder: tableColumnOrderById[tableId],
|
|
722
|
-
nextColumnOrder: getDefaultTableColumnOrder(
|
|
723
|
-
columnIds,
|
|
724
|
-
fixedLeadingColumnIds,
|
|
725
|
-
),
|
|
670
|
+
nextColumnOrder: getDefaultTableColumnOrder(columnIds, fixedLeadingColumnIds),
|
|
726
671
|
}),
|
|
727
672
|
[tableColumnOrderById],
|
|
728
673
|
);
|
|
@@ -777,39 +722,26 @@ export default function SqlitePanel() {
|
|
|
777
722
|
);
|
|
778
723
|
|
|
779
724
|
const queryColumnOrder = useMemo(
|
|
780
|
-
() =>
|
|
781
|
-
getTableColumnOrder(queryTableId, queryColumnIds, [
|
|
782
|
-
SQLITE_ROW_NUMBER_COLUMN_ID,
|
|
783
|
-
]),
|
|
725
|
+
() => getTableColumnOrder(queryTableId, queryColumnIds, [SQLITE_ROW_NUMBER_COLUMN_ID]),
|
|
784
726
|
[getTableColumnOrder, queryColumnIds, queryTableId],
|
|
785
727
|
);
|
|
786
728
|
const dataColumnOrder = useMemo(
|
|
787
|
-
() =>
|
|
788
|
-
getTableColumnOrder(dataTableId, dataColumnIds, [
|
|
789
|
-
SQLITE_ROW_NUMBER_COLUMN_ID,
|
|
790
|
-
]),
|
|
729
|
+
() => getTableColumnOrder(dataTableId, dataColumnIds, [SQLITE_ROW_NUMBER_COLUMN_ID]),
|
|
791
730
|
[dataColumnIds, dataTableId, getTableColumnOrder],
|
|
792
731
|
);
|
|
793
732
|
const structureColumnsColumnOrder = useMemo(
|
|
794
|
-
() =>
|
|
795
|
-
getTableColumnOrder(structureColumnsTableId, structureColumnsColumnIds),
|
|
733
|
+
() => getTableColumnOrder(structureColumnsTableId, structureColumnsColumnIds),
|
|
796
734
|
[getTableColumnOrder, structureColumnsColumnIds, structureColumnsTableId],
|
|
797
735
|
);
|
|
798
736
|
const structureIndexesColumnOrder = useMemo(
|
|
799
|
-
() =>
|
|
800
|
-
getTableColumnOrder(structureIndexesTableId, structureIndexesColumnIds),
|
|
737
|
+
() => getTableColumnOrder(structureIndexesTableId, structureIndexesColumnIds),
|
|
801
738
|
[getTableColumnOrder, structureIndexesColumnIds, structureIndexesTableId],
|
|
802
739
|
);
|
|
803
740
|
|
|
804
|
-
const setEntitySelection = useCallback(
|
|
805
|
-
(databaseId
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
getEntityKey(databaseId, entity.schemaName, entity.name),
|
|
809
|
-
);
|
|
810
|
-
},
|
|
811
|
-
[],
|
|
812
|
-
);
|
|
741
|
+
const setEntitySelection = useCallback((databaseId: string, entity: SqliteEntity) => {
|
|
742
|
+
setSelectedDatabaseId(databaseId);
|
|
743
|
+
setSelectedEntityKey(getEntityKey(databaseId, entity.schemaName, entity.name));
|
|
744
|
+
}, []);
|
|
813
745
|
|
|
814
746
|
const loadDatabases = useCallback(async () => {
|
|
815
747
|
setDatabaseLoading(true);
|
|
@@ -837,10 +769,7 @@ export default function SqlitePanel() {
|
|
|
837
769
|
return [...currentIds, ...missingIds];
|
|
838
770
|
});
|
|
839
771
|
setSelectedDatabaseId((current) => {
|
|
840
|
-
if (
|
|
841
|
-
current &&
|
|
842
|
-
nextDatabases.some((database) => database.id === current)
|
|
843
|
-
) {
|
|
772
|
+
if (current && nextDatabases.some((database) => database.id === current)) {
|
|
844
773
|
return current;
|
|
845
774
|
}
|
|
846
775
|
|
|
@@ -899,9 +828,7 @@ export default function SqlitePanel() {
|
|
|
899
828
|
},
|
|
900
829
|
}));
|
|
901
830
|
setExpandedSchemaKeys((current) => {
|
|
902
|
-
const nextKeys = nextSchemas.map((schema) =>
|
|
903
|
-
getSchemaKey(databaseId, schema.name),
|
|
904
|
-
);
|
|
831
|
+
const nextKeys = nextSchemas.map((schema) => getSchemaKey(databaseId, schema.name));
|
|
905
832
|
return Array.from(new Set([...current, ...nextKeys]));
|
|
906
833
|
});
|
|
907
834
|
} catch (error) {
|
|
@@ -948,17 +875,12 @@ export default function SqlitePanel() {
|
|
|
948
875
|
selectedEntity.name,
|
|
949
876
|
browsePageSize,
|
|
950
877
|
browseOffset,
|
|
951
|
-
rowMutationDescriptor?.mode === 'rowid'
|
|
952
|
-
? rowMutationDescriptor.rowIdIdentifier
|
|
953
|
-
: null,
|
|
878
|
+
rowMutationDescriptor?.mode === 'rowid' ? rowMutationDescriptor.rowIdIdentifier : null,
|
|
954
879
|
),
|
|
955
880
|
}),
|
|
956
881
|
requestQuery({
|
|
957
882
|
databaseId: selectedDatabaseId,
|
|
958
|
-
sql: buildEntityCountSql(
|
|
959
|
-
selectedEntity.schemaName,
|
|
960
|
-
selectedEntity.name,
|
|
961
|
-
),
|
|
883
|
+
sql: buildEntityCountSql(selectedEntity.schemaName, selectedEntity.name),
|
|
962
884
|
}),
|
|
963
885
|
]);
|
|
964
886
|
|
|
@@ -1021,43 +943,27 @@ export default function SqlitePanel() {
|
|
|
1021
943
|
setStructureError(null);
|
|
1022
944
|
|
|
1023
945
|
try {
|
|
1024
|
-
const [columnsOutcome, foreignKeysOutcome, indexesOutcome] =
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
),
|
|
1039
|
-
}),
|
|
1040
|
-
requestQuery({
|
|
1041
|
-
databaseId: selectedDatabaseId,
|
|
1042
|
-
sql: buildIndexListSql(
|
|
1043
|
-
selectedEntity.schemaName,
|
|
1044
|
-
selectedEntity.name,
|
|
1045
|
-
),
|
|
1046
|
-
}),
|
|
1047
|
-
]);
|
|
946
|
+
const [columnsOutcome, foreignKeysOutcome, indexesOutcome] = await Promise.allSettled([
|
|
947
|
+
requestQuery({
|
|
948
|
+
databaseId: selectedDatabaseId,
|
|
949
|
+
sql: buildTableXInfoSql(selectedEntity.schemaName, selectedEntity.name),
|
|
950
|
+
}),
|
|
951
|
+
requestQuery({
|
|
952
|
+
databaseId: selectedDatabaseId,
|
|
953
|
+
sql: buildForeignKeySql(selectedEntity.schemaName, selectedEntity.name),
|
|
954
|
+
}),
|
|
955
|
+
requestQuery({
|
|
956
|
+
databaseId: selectedDatabaseId,
|
|
957
|
+
sql: buildIndexListSql(selectedEntity.schemaName, selectedEntity.name),
|
|
958
|
+
}),
|
|
959
|
+
]);
|
|
1048
960
|
|
|
1049
961
|
const columns =
|
|
1050
|
-
columnsOutcome.status === 'fulfilled'
|
|
1051
|
-
? parseColumns(columnsOutcome.value)
|
|
1052
|
-
: [];
|
|
962
|
+
columnsOutcome.status === 'fulfilled' ? parseColumns(columnsOutcome.value) : [];
|
|
1053
963
|
const foreignKeys =
|
|
1054
|
-
foreignKeysOutcome.status === 'fulfilled'
|
|
1055
|
-
? parseForeignKeys(foreignKeysOutcome.value)
|
|
1056
|
-
: [];
|
|
964
|
+
foreignKeysOutcome.status === 'fulfilled' ? parseForeignKeys(foreignKeysOutcome.value) : [];
|
|
1057
965
|
const indexes =
|
|
1058
|
-
indexesOutcome.status === 'fulfilled'
|
|
1059
|
-
? parseIndexes(indexesOutcome.value)
|
|
1060
|
-
: [];
|
|
966
|
+
indexesOutcome.status === 'fulfilled' ? parseIndexes(indexesOutcome.value) : [];
|
|
1061
967
|
|
|
1062
968
|
const enrichedIndexes = await Promise.all(
|
|
1063
969
|
indexes.map(async (index) => {
|
|
@@ -1119,9 +1025,7 @@ export default function SqlitePanel() {
|
|
|
1119
1025
|
|
|
1120
1026
|
const refreshExplorerData = useCallback(async () => {
|
|
1121
1027
|
const nextDatabases = await loadDatabases();
|
|
1122
|
-
await Promise.all(
|
|
1123
|
-
nextDatabases.map((database) => loadExplorer(database.id)),
|
|
1124
|
-
);
|
|
1028
|
+
await Promise.all(nextDatabases.map((database) => loadExplorer(database.id)));
|
|
1125
1029
|
}, [loadDatabases, loadExplorer]);
|
|
1126
1030
|
|
|
1127
1031
|
const refreshWorkspace = useCallback(async () => {
|
|
@@ -1134,12 +1038,7 @@ export default function SqlitePanel() {
|
|
|
1134
1038
|
|
|
1135
1039
|
const handleSaveRow = useCallback(
|
|
1136
1040
|
async (nextValues: Record<string, unknown>) => {
|
|
1137
|
-
if (
|
|
1138
|
-
!selectedDatabaseId ||
|
|
1139
|
-
!selectedEntity ||
|
|
1140
|
-
!editingRow ||
|
|
1141
|
-
!rowMutationDescriptor
|
|
1142
|
-
) {
|
|
1041
|
+
if (!selectedDatabaseId || !selectedEntity || !editingRow || !rowMutationDescriptor) {
|
|
1143
1042
|
throw new Error('The selected row is no longer available.');
|
|
1144
1043
|
}
|
|
1145
1044
|
|
|
@@ -1171,12 +1070,7 @@ export default function SqlitePanel() {
|
|
|
1171
1070
|
);
|
|
1172
1071
|
|
|
1173
1072
|
const handleDeleteRow = useCallback(async () => {
|
|
1174
|
-
if (
|
|
1175
|
-
!selectedDatabaseId ||
|
|
1176
|
-
!selectedEntity ||
|
|
1177
|
-
!deletingRow ||
|
|
1178
|
-
!rowMutationDescriptor
|
|
1179
|
-
) {
|
|
1073
|
+
if (!selectedDatabaseId || !selectedEntity || !deletingRow || !rowMutationDescriptor) {
|
|
1180
1074
|
throw new Error('The selected row is no longer available.');
|
|
1181
1075
|
}
|
|
1182
1076
|
|
|
@@ -1202,9 +1096,140 @@ export default function SqlitePanel() {
|
|
|
1202
1096
|
selectedEntity,
|
|
1203
1097
|
]);
|
|
1204
1098
|
|
|
1099
|
+
const handleOpenDropEntity = useCallback(() => {
|
|
1100
|
+
if (!selectedDatabaseId || !selectedEntity) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
setActiveDrop({
|
|
1105
|
+
databaseId: selectedDatabaseId,
|
|
1106
|
+
target: {
|
|
1107
|
+
kind: 'entity',
|
|
1108
|
+
entityType: selectedEntity.type,
|
|
1109
|
+
qualifiedName: `${selectedEntity.schemaName}.${selectedEntity.name}`,
|
|
1110
|
+
confirmationValue: selectedEntity.name,
|
|
1111
|
+
sql: `${buildDropEntitySql(selectedEntity)};`,
|
|
1112
|
+
},
|
|
1113
|
+
});
|
|
1114
|
+
}, [selectedDatabaseId, selectedEntity]);
|
|
1115
|
+
|
|
1116
|
+
const handleOpenDropAllEntities = useCallback(() => {
|
|
1117
|
+
if (!selectedDatabaseId || !selectedDatabase) {
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const tableCount = entities.filter((entity) => entity.type === 'table').length;
|
|
1122
|
+
const viewCount = entities.filter((entity) => entity.type === 'view').length;
|
|
1123
|
+
|
|
1124
|
+
setActiveDrop({
|
|
1125
|
+
databaseId: selectedDatabaseId,
|
|
1126
|
+
target: {
|
|
1127
|
+
kind: 'database',
|
|
1128
|
+
databaseName: selectedDatabase.name,
|
|
1129
|
+
confirmationValue: selectedDatabase.name,
|
|
1130
|
+
tableCount,
|
|
1131
|
+
viewCount,
|
|
1132
|
+
sql: buildDropAllEntitiesSql(entities),
|
|
1133
|
+
},
|
|
1134
|
+
});
|
|
1135
|
+
}, [entities, selectedDatabase, selectedDatabaseId]);
|
|
1136
|
+
|
|
1137
|
+
const dropSelectedEntity = useCallback(
|
|
1138
|
+
async (databaseId: string, sql: string) => {
|
|
1139
|
+
await requestQuery({ databaseId, sql });
|
|
1140
|
+
},
|
|
1141
|
+
[requestQuery],
|
|
1142
|
+
);
|
|
1143
|
+
|
|
1144
|
+
const dropAllEntities = useCallback(
|
|
1145
|
+
async (databaseId: string, sql: string) => {
|
|
1146
|
+
// Dropping a parent table before its children fails while foreign key
|
|
1147
|
+
// enforcement is on, so turn it off for the sweep. The pragma is
|
|
1148
|
+
// per-connection and that connection belongs to the running app, so the
|
|
1149
|
+
// original value has to be restored on every path out of here.
|
|
1150
|
+
const foreignKeysResult = await requestQuery({
|
|
1151
|
+
databaseId,
|
|
1152
|
+
sql: SQLITE_READ_FOREIGN_KEYS_SQL,
|
|
1153
|
+
});
|
|
1154
|
+
const foreignKeysWereEnabled = isForeignKeysEnabled(foreignKeysResult.rows);
|
|
1155
|
+
|
|
1156
|
+
if (foreignKeysWereEnabled) {
|
|
1157
|
+
await requestQuery({
|
|
1158
|
+
databaseId,
|
|
1159
|
+
sql: buildSetForeignKeysSql(false),
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
let dropError: unknown = null;
|
|
1164
|
+
|
|
1165
|
+
try {
|
|
1166
|
+
const scriptResult = await requestScriptExecution({
|
|
1167
|
+
databaseId,
|
|
1168
|
+
sql,
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
if (scriptResult.failedStatementIndex != null) {
|
|
1172
|
+
const failedStatement = scriptResult.statements.find(
|
|
1173
|
+
(statement) => statement.index === scriptResult.failedStatementIndex,
|
|
1174
|
+
);
|
|
1175
|
+
throw new Error(failedStatement?.error ?? 'Script execution failed.');
|
|
1176
|
+
}
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
dropError = error;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
if (foreignKeysWereEnabled) {
|
|
1182
|
+
try {
|
|
1183
|
+
await requestQuery({
|
|
1184
|
+
databaseId,
|
|
1185
|
+
sql: buildSetForeignKeysSql(true),
|
|
1186
|
+
});
|
|
1187
|
+
} catch (restoreError) {
|
|
1188
|
+
// The drop error, if any, takes priority: it's the reason the user
|
|
1189
|
+
// is looking at this modal. Only surface the restore failure when
|
|
1190
|
+
// the drop itself succeeded, since that's a new, silent problem.
|
|
1191
|
+
if (!dropError) {
|
|
1192
|
+
throw restoreError instanceof Error ? restoreError : new Error(String(restoreError));
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
if (dropError) {
|
|
1198
|
+
throw dropError instanceof Error ? dropError : new Error(String(dropError));
|
|
1199
|
+
}
|
|
1200
|
+
},
|
|
1201
|
+
[requestQuery, requestScriptExecution],
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
const handleConfirmDrop = useCallback(async () => {
|
|
1205
|
+
if (!activeDrop) {
|
|
1206
|
+
throw new Error('The drop target is no longer available.');
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// Execute the target captured when the dialog opened rather than the
|
|
1210
|
+
// current selection: an app reload can fire `sqlite:ready` mid-dialog,
|
|
1211
|
+
// which reloads the explorer and may move the selection elsewhere. The
|
|
1212
|
+
// user confirmed by typing this object's name, so this is the object that
|
|
1213
|
+
// has to be dropped.
|
|
1214
|
+
const { databaseId, target } = activeDrop;
|
|
1215
|
+
|
|
1216
|
+
if (!target.sql) {
|
|
1217
|
+
throw new Error('There is nothing to drop.');
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (target.kind === 'entity') {
|
|
1221
|
+
await dropSelectedEntity(databaseId, target.sql);
|
|
1222
|
+
} else {
|
|
1223
|
+
await dropAllEntities(databaseId, target.sql);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
setActiveDrop(null);
|
|
1227
|
+
setSelectedEntityKey(null);
|
|
1228
|
+
await refreshExplorerData();
|
|
1229
|
+
}, [activeDrop, dropAllEntities, dropSelectedEntity, refreshExplorerData]);
|
|
1230
|
+
|
|
1205
1231
|
const getActiveStatement = useCallback(() => {
|
|
1206
|
-
const cursorPosition =
|
|
1207
|
-
editorRef.current?.getSelection().start ?? querySelection.start;
|
|
1232
|
+
const cursorPosition = editorRef.current?.getSelection().start ?? querySelection.start;
|
|
1208
1233
|
const currentStatement = getStatementAtCursor(queryInput, cursorPosition);
|
|
1209
1234
|
const start = currentStatement?.start ?? 0;
|
|
1210
1235
|
const end = currentStatement?.end ?? queryInput.length;
|
|
@@ -1313,25 +1338,18 @@ export default function SqlitePanel() {
|
|
|
1313
1338
|
execution.failedStatementIndex == null
|
|
1314
1339
|
? null
|
|
1315
1340
|
: (execution.statements.find(
|
|
1316
|
-
(statement) =>
|
|
1317
|
-
statement.index === execution.failedStatementIndex,
|
|
1341
|
+
(statement) => statement.index === execution.failedStatementIndex,
|
|
1318
1342
|
) ?? null);
|
|
1319
1343
|
|
|
1320
1344
|
setQueryExecution(execution);
|
|
1321
|
-
setSelectedQueryStatementIndex(
|
|
1322
|
-
getDefaultSelectedQueryStatementIndex(execution),
|
|
1323
|
-
);
|
|
1345
|
+
setSelectedQueryStatementIndex(getDefaultSelectedQueryStatementIndex(execution));
|
|
1324
1346
|
|
|
1325
1347
|
if (failedStatement?.error) {
|
|
1326
1348
|
setQueryError(failedStatement.error);
|
|
1327
|
-
setQueryErrorLine(
|
|
1328
|
-
getLineNumberAtPosition(queryInput, failedStatement.start),
|
|
1329
|
-
);
|
|
1349
|
+
setQueryErrorLine(getLineNumberAtPosition(queryInput, failedStatement.start));
|
|
1330
1350
|
}
|
|
1331
1351
|
|
|
1332
|
-
setQueryMessage(
|
|
1333
|
-
getScriptResultSummary(execution) ?? 'Script execution completed.',
|
|
1334
|
-
);
|
|
1352
|
+
setQueryMessage(getScriptResultSummary(execution) ?? 'Script execution completed.');
|
|
1335
1353
|
|
|
1336
1354
|
if (hasMutatingStatements(execution)) {
|
|
1337
1355
|
await refreshWorkspace();
|
|
@@ -1340,9 +1358,7 @@ export default function SqlitePanel() {
|
|
|
1340
1358
|
setQueryExecution(null);
|
|
1341
1359
|
setSelectedQueryStatementIndex(null);
|
|
1342
1360
|
setQueryError(safeError(error));
|
|
1343
|
-
setQueryErrorLine(
|
|
1344
|
-
getLineNumberAtPosition(queryInput, querySelection.start),
|
|
1345
|
-
);
|
|
1361
|
+
setQueryErrorLine(getLineNumberAtPosition(queryInput, querySelection.start));
|
|
1346
1362
|
setQueryMessage('Execution failed.');
|
|
1347
1363
|
} finally {
|
|
1348
1364
|
setQueryLoading(false);
|
|
@@ -1364,22 +1380,12 @@ export default function SqlitePanel() {
|
|
|
1364
1380
|
|
|
1365
1381
|
const handleRunCurrentStatement = useCallback(async () => {
|
|
1366
1382
|
try {
|
|
1367
|
-
await runSingleStatement(
|
|
1368
|
-
getActiveStatement(),
|
|
1369
|
-
'Running current statement',
|
|
1370
|
-
);
|
|
1383
|
+
await runSingleStatement(getActiveStatement(), 'Running current statement');
|
|
1371
1384
|
} catch (error) {
|
|
1372
1385
|
setQueryError(safeError(error));
|
|
1373
|
-
setQueryErrorLine(
|
|
1374
|
-
getLineNumberAtPosition(queryInput, querySelection.start),
|
|
1375
|
-
);
|
|
1386
|
+
setQueryErrorLine(getLineNumberAtPosition(queryInput, querySelection.start));
|
|
1376
1387
|
}
|
|
1377
|
-
}, [
|
|
1378
|
-
getActiveStatement,
|
|
1379
|
-
queryInput,
|
|
1380
|
-
querySelection.start,
|
|
1381
|
-
runSingleStatement,
|
|
1382
|
-
]);
|
|
1388
|
+
}, [getActiveStatement, queryInput, querySelection.start, runSingleStatement]);
|
|
1383
1389
|
|
|
1384
1390
|
const handleSaveQuery = useCallback(() => {
|
|
1385
1391
|
const fileName = `${slugifyFileName(selectedEntity?.name ?? 'query')}.sql`;
|
|
@@ -1422,9 +1428,7 @@ export default function SqlitePanel() {
|
|
|
1422
1428
|
setQueryInput(formatted);
|
|
1423
1429
|
setQueryError(null);
|
|
1424
1430
|
setQueryErrorLine(null);
|
|
1425
|
-
setQueryMessage(
|
|
1426
|
-
formatted ? 'Formatted query.' : 'Cleared query formatting.',
|
|
1427
|
-
);
|
|
1431
|
+
setQueryMessage(formatted ? 'Formatted query.' : 'Cleared query formatting.');
|
|
1428
1432
|
} catch (error) {
|
|
1429
1433
|
setQueryError(safeError(error));
|
|
1430
1434
|
setQueryErrorLine(null);
|
|
@@ -1455,13 +1459,7 @@ export default function SqlitePanel() {
|
|
|
1455
1459
|
const columns = parseColumns(result);
|
|
1456
1460
|
|
|
1457
1461
|
setQueryColumnCache((current) =>
|
|
1458
|
-
setSqlEditorCachedColumns(
|
|
1459
|
-
current,
|
|
1460
|
-
selectedDatabaseId,
|
|
1461
|
-
schemaName,
|
|
1462
|
-
entityName,
|
|
1463
|
-
columns,
|
|
1464
|
-
),
|
|
1462
|
+
setSqlEditorCachedColumns(current, selectedDatabaseId, schemaName, entityName, columns),
|
|
1465
1463
|
);
|
|
1466
1464
|
|
|
1467
1465
|
return columns;
|
|
@@ -1479,24 +1477,18 @@ export default function SqlitePanel() {
|
|
|
1479
1477
|
return null;
|
|
1480
1478
|
}
|
|
1481
1479
|
|
|
1482
|
-
const aliases = extractSqlEditorAliases(
|
|
1483
|
-
context.state.doc.sliceString(0, context.pos),
|
|
1484
|
-
);
|
|
1480
|
+
const aliases = extractSqlEditorAliases(context.state.doc.sliceString(0, context.pos));
|
|
1485
1481
|
const entity = resolveSqlEditorEntityReference({
|
|
1486
1482
|
aliases,
|
|
1487
1483
|
entities,
|
|
1488
1484
|
request,
|
|
1489
|
-
selectedSchemaName:
|
|
1490
|
-
selectedEntity?.schemaName ?? defaultCompletionSchemaName ?? null,
|
|
1485
|
+
selectedSchemaName: selectedEntity?.schemaName ?? defaultCompletionSchemaName ?? null,
|
|
1491
1486
|
});
|
|
1492
1487
|
if (!entity) {
|
|
1493
1488
|
return null;
|
|
1494
1489
|
}
|
|
1495
1490
|
|
|
1496
|
-
const columns = await ensureQueryEntityColumns(
|
|
1497
|
-
entity.schemaName,
|
|
1498
|
-
entity.name,
|
|
1499
|
-
);
|
|
1491
|
+
const columns = await ensureQueryEntityColumns(entity.schemaName, entity.name);
|
|
1500
1492
|
if (context.aborted || columns.length === 0) {
|
|
1501
1493
|
return null;
|
|
1502
1494
|
}
|
|
@@ -1508,12 +1500,7 @@ export default function SqlitePanel() {
|
|
|
1508
1500
|
validFor: /^[A-Za-z_][\w$]*$/,
|
|
1509
1501
|
};
|
|
1510
1502
|
},
|
|
1511
|
-
[
|
|
1512
|
-
defaultCompletionSchemaName,
|
|
1513
|
-
ensureQueryEntityColumns,
|
|
1514
|
-
entities,
|
|
1515
|
-
selectedEntity?.schemaName,
|
|
1516
|
-
],
|
|
1503
|
+
[defaultCompletionSchemaName, ensureQueryEntityColumns, entities, selectedEntity?.schemaName],
|
|
1517
1504
|
);
|
|
1518
1505
|
|
|
1519
1506
|
const handleSidebarResizeStart = useCallback(
|
|
@@ -1604,11 +1591,7 @@ export default function SqlitePanel() {
|
|
|
1604
1591
|
}, [refreshExplorerData]);
|
|
1605
1592
|
|
|
1606
1593
|
useEffect(() => {
|
|
1607
|
-
if (
|
|
1608
|
-
!selectedDatabaseId ||
|
|
1609
|
-
selectedExplorerState.loading ||
|
|
1610
|
-
!selectedExplorerState.loaded
|
|
1611
|
-
) {
|
|
1594
|
+
if (!selectedDatabaseId || selectedExplorerState.loading || !selectedExplorerState.loaded) {
|
|
1612
1595
|
return;
|
|
1613
1596
|
}
|
|
1614
1597
|
|
|
@@ -1616,9 +1599,7 @@ export default function SqlitePanel() {
|
|
|
1616
1599
|
if (
|
|
1617
1600
|
current &&
|
|
1618
1601
|
selectedExplorerState.entities.some(
|
|
1619
|
-
(entity) =>
|
|
1620
|
-
getEntityKey(selectedDatabaseId, entity.schemaName, entity.name) ===
|
|
1621
|
-
current,
|
|
1602
|
+
(entity) => getEntityKey(selectedDatabaseId, entity.schemaName, entity.name) === current,
|
|
1622
1603
|
)
|
|
1623
1604
|
) {
|
|
1624
1605
|
return current;
|
|
@@ -1626,11 +1607,7 @@ export default function SqlitePanel() {
|
|
|
1626
1607
|
|
|
1627
1608
|
const fallbackEntity = selectedExplorerState.entities[0];
|
|
1628
1609
|
return fallbackEntity
|
|
1629
|
-
? getEntityKey(
|
|
1630
|
-
selectedDatabaseId,
|
|
1631
|
-
fallbackEntity.schemaName,
|
|
1632
|
-
fallbackEntity.name,
|
|
1633
|
-
)
|
|
1610
|
+
? getEntityKey(selectedDatabaseId, fallbackEntity.schemaName, fallbackEntity.name)
|
|
1634
1611
|
: null;
|
|
1635
1612
|
});
|
|
1636
1613
|
}, [
|
|
@@ -1670,18 +1647,11 @@ export default function SqlitePanel() {
|
|
|
1670
1647
|
}, [loadBrowse, selectedEntityKey]);
|
|
1671
1648
|
|
|
1672
1649
|
useEffect(() => {
|
|
1673
|
-
setQueryColumnCache((current) =>
|
|
1674
|
-
syncSqlEditorColumnCacheDatabase(current, selectedDatabaseId),
|
|
1675
|
-
);
|
|
1650
|
+
setQueryColumnCache((current) => syncSqlEditorColumnCacheDatabase(current, selectedDatabaseId));
|
|
1676
1651
|
}, [selectedDatabaseId]);
|
|
1677
1652
|
|
|
1678
1653
|
useEffect(() => {
|
|
1679
|
-
if (
|
|
1680
|
-
!selectedDatabaseId ||
|
|
1681
|
-
!selectedEntity ||
|
|
1682
|
-
structureLoading ||
|
|
1683
|
-
structureError
|
|
1684
|
-
) {
|
|
1654
|
+
if (!selectedDatabaseId || !selectedEntity || structureLoading || structureError) {
|
|
1685
1655
|
return;
|
|
1686
1656
|
}
|
|
1687
1657
|
|
|
@@ -1826,10 +1796,7 @@ export default function SqlitePanel() {
|
|
|
1826
1796
|
)
|
|
1827
1797
|
) : (
|
|
1828
1798
|
<div ref={querySplitRef} className="sqlite-query-layout">
|
|
1829
|
-
<section
|
|
1830
|
-
className="sqlite-query-editor-pane"
|
|
1831
|
-
style={{ flex: `0 0 ${editorSplit}%` }}
|
|
1832
|
-
>
|
|
1799
|
+
<section className="sqlite-query-editor-pane" style={{ flex: `0 0 ${editorSplit}%` }}>
|
|
1833
1800
|
{queryTabHeader}
|
|
1834
1801
|
<div className="sqlite-editor-frame">
|
|
1835
1802
|
<SqlEditor
|
|
@@ -1837,14 +1804,8 @@ export default function SqlitePanel() {
|
|
|
1837
1804
|
ariaLabel="SQL query editor"
|
|
1838
1805
|
completionSchema={editorCompletionSchema}
|
|
1839
1806
|
completionSource={editorCompletionSource}
|
|
1840
|
-
defaultSchema={
|
|
1841
|
-
|
|
1842
|
-
}
|
|
1843
|
-
defaultTable={
|
|
1844
|
-
cachedSelectedEntityColumns.length > 0
|
|
1845
|
-
? selectedEntity?.name
|
|
1846
|
-
: undefined
|
|
1847
|
-
}
|
|
1807
|
+
defaultSchema={selectedEntity?.schemaName ?? defaultCompletionSchemaName}
|
|
1808
|
+
defaultTable={cachedSelectedEntityColumns.length > 0 ? selectedEntity?.name : undefined}
|
|
1848
1809
|
errorLine={queryErrorLine}
|
|
1849
1810
|
onFormat={handleFormatQuery}
|
|
1850
1811
|
onRun={() => void handleRun()}
|
|
@@ -1872,9 +1833,7 @@ export default function SqlitePanel() {
|
|
|
1872
1833
|
<div className="sqlite-results-header sqlite-query-results-header">
|
|
1873
1834
|
<div className="sqlite-toolbar-actions sqlite-query-results-header-main">
|
|
1874
1835
|
{!queryExecution ? (
|
|
1875
|
-
<span className="sqlite-helper-text">
|
|
1876
|
-
Run SQL to inspect per-statement results.
|
|
1877
|
-
</span>
|
|
1836
|
+
<span className="sqlite-helper-text">Run SQL to inspect per-statement results.</span>
|
|
1878
1837
|
) : null}
|
|
1879
1838
|
|
|
1880
1839
|
{queryExecution && queryExecution.statements.length > 1 ? (
|
|
@@ -1950,8 +1909,7 @@ export default function SqlitePanel() {
|
|
|
1950
1909
|
<div className="sqlite-inline-error" aria-live="polite">
|
|
1951
1910
|
<div>
|
|
1952
1911
|
<p className="font-medium text-rose-100">
|
|
1953
|
-
{(queryExecution?.totalStatementCount ??
|
|
1954
|
-
queryStatements.length) > 1
|
|
1912
|
+
{(queryExecution?.totalStatementCount ?? queryStatements.length) > 1
|
|
1955
1913
|
? 'Script Error'
|
|
1956
1914
|
: 'SQL Error'}
|
|
1957
1915
|
</p>
|
|
@@ -1962,11 +1920,7 @@ export default function SqlitePanel() {
|
|
|
1962
1920
|
</p>
|
|
1963
1921
|
) : null}
|
|
1964
1922
|
</div>
|
|
1965
|
-
<button
|
|
1966
|
-
type="button"
|
|
1967
|
-
className={ghostButtonClassName}
|
|
1968
|
-
onClick={handleCopyError}
|
|
1969
|
-
>
|
|
1923
|
+
<button type="button" className={ghostButtonClassName} onClick={handleCopyError}>
|
|
1970
1924
|
<Copy aria-hidden="true" className="h-4 w-4" />
|
|
1971
1925
|
Copy Error
|
|
1972
1926
|
</button>
|
|
@@ -1979,20 +1933,15 @@ export default function SqlitePanel() {
|
|
|
1979
1933
|
result={activeQueryResult}
|
|
1980
1934
|
columnOrder={queryColumnOrder}
|
|
1981
1935
|
onColumnOrderChange={(nextColumnOrder) =>
|
|
1982
|
-
setTableColumnOrder(
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
nextColumnOrder,
|
|
1986
|
-
[SQLITE_ROW_NUMBER_COLUMN_ID],
|
|
1987
|
-
)
|
|
1936
|
+
setTableColumnOrder(queryTableId, queryColumnIds, nextColumnOrder, [
|
|
1937
|
+
SQLITE_ROW_NUMBER_COLUMN_ID,
|
|
1938
|
+
])
|
|
1988
1939
|
}
|
|
1989
1940
|
loading={queryLoading}
|
|
1990
1941
|
showMetadata={false}
|
|
1991
1942
|
shellClassName="h-full min-h-0"
|
|
1992
1943
|
scrollContainerClassName="min-h-0 sqlite-results-scroll-flush"
|
|
1993
|
-
emptyTitle={
|
|
1994
|
-
selectedQueryStatement?.error ? 'Statement Failed' : 'No Results'
|
|
1995
|
-
}
|
|
1944
|
+
emptyTitle={selectedQueryStatement?.error ? 'Statement Failed' : 'No Results'}
|
|
1996
1945
|
emptyDescription={
|
|
1997
1946
|
selectedQueryStatement?.error
|
|
1998
1947
|
? 'Select another statement to inspect its rows, or fix the error and run again.'
|
|
@@ -2019,9 +1968,7 @@ export default function SqlitePanel() {
|
|
|
2019
1968
|
disabled={editableColumns.length === 0}
|
|
2020
1969
|
aria-label={`Edit row ${rowNumber}`}
|
|
2021
1970
|
title={
|
|
2022
|
-
editableColumns.length === 0
|
|
2023
|
-
? 'No editable columns'
|
|
2024
|
-
: `Edit row ${rowNumber}`
|
|
1971
|
+
editableColumns.length === 0 ? 'No editable columns' : `Edit row ${rowNumber}`
|
|
2025
1972
|
}
|
|
2026
1973
|
onClick={(event) => {
|
|
2027
1974
|
event.stopPropagation();
|
|
@@ -2063,11 +2010,7 @@ export default function SqlitePanel() {
|
|
|
2063
2010
|
'database',
|
|
2064
2011
|
)
|
|
2065
2012
|
) : !selectedEntity ? (
|
|
2066
|
-
renderEmptyState(
|
|
2067
|
-
'Select A Table',
|
|
2068
|
-
'Choose a table in the sidebar to view its rows.',
|
|
2069
|
-
'table',
|
|
2070
|
-
)
|
|
2013
|
+
renderEmptyState('Select A Table', 'Choose a table in the sidebar to view its rows.', 'table')
|
|
2071
2014
|
) : (
|
|
2072
2015
|
<div className="sqlite-content-stack">
|
|
2073
2016
|
<header className="sqlite-object-header">
|
|
@@ -2092,11 +2035,7 @@ export default function SqlitePanel() {
|
|
|
2092
2035
|
</div>
|
|
2093
2036
|
</div>
|
|
2094
2037
|
{dataSearch.trim() ? (
|
|
2095
|
-
<button
|
|
2096
|
-
type="button"
|
|
2097
|
-
className="sqlite-chip"
|
|
2098
|
-
onClick={() => setDataSearch('')}
|
|
2099
|
-
>
|
|
2038
|
+
<button type="button" className="sqlite-chip" onClick={() => setDataSearch('')}>
|
|
2100
2039
|
contains {dataSearch}
|
|
2101
2040
|
<X aria-hidden="true" className="h-3.5 w-3.5" />
|
|
2102
2041
|
</button>
|
|
@@ -2114,10 +2053,7 @@ export default function SqlitePanel() {
|
|
|
2114
2053
|
>
|
|
2115
2054
|
<RefreshCw
|
|
2116
2055
|
aria-hidden="true"
|
|
2117
|
-
className={joinClassNames(
|
|
2118
|
-
'h-4 w-4',
|
|
2119
|
-
browseLoading && 'animate-spin',
|
|
2120
|
-
)}
|
|
2056
|
+
className={joinClassNames('h-4 w-4', browseLoading && 'animate-spin')}
|
|
2121
2057
|
/>
|
|
2122
2058
|
</button>
|
|
2123
2059
|
</div>
|
|
@@ -2146,9 +2082,7 @@ export default function SqlitePanel() {
|
|
|
2146
2082
|
showMetadata={false}
|
|
2147
2083
|
shellClassName="h-full min-h-0"
|
|
2148
2084
|
scrollContainerClassName="min-h-0 sqlite-results-scroll-flush"
|
|
2149
|
-
emptyTitle={
|
|
2150
|
-
selectedEntity ? 'No Rows On This Page' : 'No Table Selected'
|
|
2151
|
-
}
|
|
2085
|
+
emptyTitle={selectedEntity ? 'No Rows On This Page' : 'No Table Selected'}
|
|
2152
2086
|
emptyDescription={
|
|
2153
2087
|
selectedEntity
|
|
2154
2088
|
? 'This page does not contain rows.'
|
|
@@ -2198,11 +2132,7 @@ export default function SqlitePanel() {
|
|
|
2198
2132
|
<button
|
|
2199
2133
|
type="button"
|
|
2200
2134
|
className={secondaryButtonClassName}
|
|
2201
|
-
onClick={() =>
|
|
2202
|
-
setBrowseOffset((current) =>
|
|
2203
|
-
Math.max(0, current - browsePageSize),
|
|
2204
|
-
)
|
|
2205
|
-
}
|
|
2135
|
+
onClick={() => setBrowseOffset((current) => Math.max(0, current - browsePageSize))}
|
|
2206
2136
|
disabled={browseLoading || !canBrowseBackward}
|
|
2207
2137
|
>
|
|
2208
2138
|
Previous
|
|
@@ -2210,17 +2140,13 @@ export default function SqlitePanel() {
|
|
|
2210
2140
|
<button
|
|
2211
2141
|
type="button"
|
|
2212
2142
|
className={secondaryButtonClassName}
|
|
2213
|
-
onClick={() =>
|
|
2214
|
-
setBrowseOffset((current) => current + browsePageSize)
|
|
2215
|
-
}
|
|
2143
|
+
onClick={() => setBrowseOffset((current) => current + browsePageSize)}
|
|
2216
2144
|
disabled={browseLoading || !canBrowseForward}
|
|
2217
2145
|
>
|
|
2218
2146
|
Next
|
|
2219
2147
|
</button>
|
|
2220
2148
|
<span className="sqlite-badge sqlite-badge-neutral sqlite-tabular">
|
|
2221
|
-
{totalDataPages > 0
|
|
2222
|
-
? `${currentDataPage}/${totalDataPages}`
|
|
2223
|
-
: '0/0'}
|
|
2149
|
+
{totalDataPages > 0 ? `${currentDataPage}/${totalDataPages}` : '0/0'}
|
|
2224
2150
|
</span>
|
|
2225
2151
|
</div>
|
|
2226
2152
|
</footer>
|
|
@@ -2242,11 +2168,7 @@ export default function SqlitePanel() {
|
|
|
2242
2168
|
) : (
|
|
2243
2169
|
<div className="sqlite-content-stack">
|
|
2244
2170
|
<header className="sqlite-object-header">
|
|
2245
|
-
<div
|
|
2246
|
-
className="sqlite-section-tabs"
|
|
2247
|
-
role="tablist"
|
|
2248
|
-
aria-label="Structure sections"
|
|
2249
|
-
>
|
|
2171
|
+
<div className="sqlite-section-tabs" role="tablist" aria-label="Structure sections">
|
|
2250
2172
|
{(
|
|
2251
2173
|
[
|
|
2252
2174
|
['columns', 'Columns'],
|
|
@@ -2281,12 +2203,18 @@ export default function SqlitePanel() {
|
|
|
2281
2203
|
>
|
|
2282
2204
|
<RefreshCw
|
|
2283
2205
|
aria-hidden="true"
|
|
2284
|
-
className={joinClassNames(
|
|
2285
|
-
'h-4 w-4',
|
|
2286
|
-
structureLoading && 'animate-spin',
|
|
2287
|
-
)}
|
|
2206
|
+
className={joinClassNames('h-4 w-4', structureLoading && 'animate-spin')}
|
|
2288
2207
|
/>
|
|
2289
2208
|
</button>
|
|
2209
|
+
<button
|
|
2210
|
+
type="button"
|
|
2211
|
+
className={dangerIconButtonClassName}
|
|
2212
|
+
onClick={handleOpenDropEntity}
|
|
2213
|
+
aria-label={selectedEntity.type === 'view' ? 'Drop view' : 'Drop table'}
|
|
2214
|
+
title={selectedEntity.type === 'view' ? 'Drop view' : 'Drop table'}
|
|
2215
|
+
>
|
|
2216
|
+
<Trash2 aria-hidden="true" className="h-4 w-4" />
|
|
2217
|
+
</button>
|
|
2290
2218
|
</div>
|
|
2291
2219
|
</header>
|
|
2292
2220
|
|
|
@@ -2344,19 +2272,10 @@ export default function SqlitePanel() {
|
|
|
2344
2272
|
) : (
|
|
2345
2273
|
<div className="sqlite-chip-row">
|
|
2346
2274
|
{primaryKeyColumns
|
|
2347
|
-
.sort(
|
|
2348
|
-
(left, right) =>
|
|
2349
|
-
left.primaryKeyOrder - right.primaryKeyOrder,
|
|
2350
|
-
)
|
|
2275
|
+
.sort((left, right) => left.primaryKeyOrder - right.primaryKeyOrder)
|
|
2351
2276
|
.map((column) => (
|
|
2352
|
-
<span
|
|
2353
|
-
|
|
2354
|
-
className="sqlite-chip sqlite-chip-static"
|
|
2355
|
-
>
|
|
2356
|
-
<KeyRound
|
|
2357
|
-
aria-hidden="true"
|
|
2358
|
-
className="h-3.5 w-3.5"
|
|
2359
|
-
/>
|
|
2277
|
+
<span key={column.name} className="sqlite-chip sqlite-chip-static">
|
|
2278
|
+
<KeyRound aria-hidden="true" className="h-3.5 w-3.5" />
|
|
2360
2279
|
{column.name}
|
|
2361
2280
|
</span>
|
|
2362
2281
|
))}
|
|
@@ -2373,18 +2292,14 @@ export default function SqlitePanel() {
|
|
|
2373
2292
|
) : (
|
|
2374
2293
|
<div className="space-y-3">
|
|
2375
2294
|
{structureState.foreignKeys.map((foreignKey) => (
|
|
2376
|
-
<div
|
|
2377
|
-
key={`${foreignKey.id}-${foreignKey.seq}`}
|
|
2378
|
-
className="sqlite-key-row"
|
|
2379
|
-
>
|
|
2295
|
+
<div key={`${foreignKey.id}-${foreignKey.seq}`} className="sqlite-key-row">
|
|
2380
2296
|
<div>
|
|
2381
2297
|
<p className="font-medium text-white">
|
|
2382
2298
|
{foreignKey.from} → {foreignKey.table}
|
|
2383
2299
|
{foreignKey.to ? `.${foreignKey.to}` : ''}
|
|
2384
2300
|
</p>
|
|
2385
2301
|
<p className="sqlite-helper-text">
|
|
2386
|
-
Update {foreignKey.onUpdate} · Delete{
|
|
2387
|
-
{foreignKey.onDelete}
|
|
2302
|
+
Update {foreignKey.onUpdate} · Delete {foreignKey.onDelete}
|
|
2388
2303
|
</p>
|
|
2389
2304
|
</div>
|
|
2390
2305
|
<span className="sqlite-badge sqlite-badge-neutral">
|
|
@@ -2428,11 +2343,7 @@ export default function SqlitePanel() {
|
|
|
2428
2343
|
Skip To Workspace
|
|
2429
2344
|
</a>
|
|
2430
2345
|
<div className="sqlite-app-body">
|
|
2431
|
-
<aside
|
|
2432
|
-
ref={sidebarRef}
|
|
2433
|
-
className="sqlite-sidebar-wrap"
|
|
2434
|
-
style={{ width: sidebarWidth }}
|
|
2435
|
-
>
|
|
2346
|
+
<aside ref={sidebarRef} className="sqlite-sidebar-wrap" style={{ width: sidebarWidth }}>
|
|
2436
2347
|
<section className="sqlite-sidebar-panel">
|
|
2437
2348
|
<header className="sqlite-sidebar-header">
|
|
2438
2349
|
<div className="sqlite-toolbar-actions">
|
|
@@ -2455,6 +2366,26 @@ export default function SqlitePanel() {
|
|
|
2455
2366
|
)}
|
|
2456
2367
|
/>
|
|
2457
2368
|
</button>
|
|
2369
|
+
<button
|
|
2370
|
+
type="button"
|
|
2371
|
+
className={dangerIconButtonClassName}
|
|
2372
|
+
aria-label={
|
|
2373
|
+
selectedDatabase
|
|
2374
|
+
? `Drop all objects in ${selectedDatabase.name}`
|
|
2375
|
+
: 'Drop all objects'
|
|
2376
|
+
}
|
|
2377
|
+
title={
|
|
2378
|
+
selectedDatabase
|
|
2379
|
+
? `Drop all objects in ${selectedDatabase.name}`
|
|
2380
|
+
: 'Drop all objects'
|
|
2381
|
+
}
|
|
2382
|
+
onClick={handleOpenDropAllEntities}
|
|
2383
|
+
disabled={
|
|
2384
|
+
!selectedDatabaseId || entities.length === 0 || databaseLoading || entityLoading
|
|
2385
|
+
}
|
|
2386
|
+
>
|
|
2387
|
+
<Trash2 aria-hidden="true" className="h-4 w-4" />
|
|
2388
|
+
</button>
|
|
2458
2389
|
</div>
|
|
2459
2390
|
</header>
|
|
2460
2391
|
|
|
@@ -2487,9 +2418,7 @@ export default function SqlitePanel() {
|
|
|
2487
2418
|
</div>
|
|
2488
2419
|
) : databases.length === 0 ? (
|
|
2489
2420
|
renderEmptyState(
|
|
2490
|
-
databaseError
|
|
2491
|
-
? 'Could Not Load Databases'
|
|
2492
|
-
: 'No Databases Found',
|
|
2421
|
+
databaseError ? 'Could Not Load Databases' : 'No Databases Found',
|
|
2493
2422
|
databaseError ??
|
|
2494
2423
|
'Expose a SQLite adapter in your app, then refresh to inspect it here.',
|
|
2495
2424
|
'database',
|
|
@@ -2497,12 +2426,9 @@ export default function SqlitePanel() {
|
|
|
2497
2426
|
) : (
|
|
2498
2427
|
<div className="sqlite-connection-list">
|
|
2499
2428
|
{databases.map((database) => {
|
|
2500
|
-
const isExpanded = expandedDatabaseIds.includes(
|
|
2501
|
-
database.id,
|
|
2502
|
-
);
|
|
2429
|
+
const isExpanded = expandedDatabaseIds.includes(database.id);
|
|
2503
2430
|
const databaseExplorerState =
|
|
2504
|
-
explorerStateByDatabase[database.id] ??
|
|
2505
|
-
DEFAULT_EXPLORER_STATE;
|
|
2431
|
+
explorerStateByDatabase[database.id] ?? DEFAULT_EXPLORER_STATE;
|
|
2506
2432
|
const databaseExplorerGroups = buildExplorerGroups(
|
|
2507
2433
|
databaseExplorerState.schemas,
|
|
2508
2434
|
databaseExplorerState.entities,
|
|
@@ -2528,206 +2454,147 @@ export default function SqlitePanel() {
|
|
|
2528
2454
|
}}
|
|
2529
2455
|
>
|
|
2530
2456
|
{isExpanded ? (
|
|
2531
|
-
<ChevronDown
|
|
2532
|
-
aria-hidden="true"
|
|
2533
|
-
className="h-4 w-4 shrink-0"
|
|
2534
|
-
/>
|
|
2457
|
+
<ChevronDown aria-hidden="true" className="h-4 w-4 shrink-0" />
|
|
2535
2458
|
) : (
|
|
2536
|
-
<ChevronRight
|
|
2537
|
-
aria-hidden="true"
|
|
2538
|
-
className="h-4 w-4 shrink-0"
|
|
2539
|
-
/>
|
|
2459
|
+
<ChevronRight aria-hidden="true" className="h-4 w-4 shrink-0" />
|
|
2540
2460
|
)}
|
|
2541
|
-
<Database
|
|
2542
|
-
|
|
2543
|
-
className="h-4 w-4 shrink-0"
|
|
2544
|
-
/>
|
|
2545
|
-
<span className="min-w-0 truncate font-medium">
|
|
2546
|
-
{database.name}
|
|
2547
|
-
</span>
|
|
2461
|
+
<Database aria-hidden="true" className="h-4 w-4 shrink-0" />
|
|
2462
|
+
<span className="min-w-0 truncate font-medium">{database.name}</span>
|
|
2548
2463
|
</button>
|
|
2549
2464
|
|
|
2550
2465
|
{isExpanded ? (
|
|
2551
2466
|
<div className="sqlite-tree-shell">
|
|
2552
|
-
{databaseExplorerState.loading ||
|
|
2553
|
-
|
|
2554
|
-
<div
|
|
2555
|
-
className="sqlite-sidebar-skeleton"
|
|
2556
|
-
aria-live="polite"
|
|
2557
|
-
>
|
|
2467
|
+
{databaseExplorerState.loading || !databaseExplorerState.loaded ? (
|
|
2468
|
+
<div className="sqlite-sidebar-skeleton" aria-live="polite">
|
|
2558
2469
|
{Array.from({ length: 4 }, (_, index) => (
|
|
2559
|
-
<div
|
|
2560
|
-
key={index}
|
|
2561
|
-
className="sqlite-sidebar-skeleton-row"
|
|
2562
|
-
/>
|
|
2470
|
+
<div key={index} className="sqlite-sidebar-skeleton-row" />
|
|
2563
2471
|
))}
|
|
2564
2472
|
</div>
|
|
2565
2473
|
) : databaseExplorerState.error ? (
|
|
2566
|
-
<div
|
|
2567
|
-
className="sqlite-inline-error"
|
|
2568
|
-
aria-live="polite"
|
|
2569
|
-
>
|
|
2474
|
+
<div className="sqlite-inline-error" aria-live="polite">
|
|
2570
2475
|
<div>
|
|
2571
|
-
<p className="font-medium text-rose-100">
|
|
2572
|
-
Explorer Load Failed
|
|
2573
|
-
</p>
|
|
2476
|
+
<p className="font-medium text-rose-100">Explorer Load Failed</p>
|
|
2574
2477
|
<p className="mt-1 text-sm text-rose-100/90">
|
|
2575
2478
|
{databaseExplorerState.error}
|
|
2576
2479
|
</p>
|
|
2577
2480
|
</div>
|
|
2578
2481
|
</div>
|
|
2579
2482
|
) : databaseExplorerGroups.length === 0 ? (
|
|
2580
|
-
<div className="sqlite-tree-empty">
|
|
2581
|
-
No objects match this filter.
|
|
2582
|
-
</div>
|
|
2483
|
+
<div className="sqlite-tree-empty">No objects match this filter.</div>
|
|
2583
2484
|
) : (
|
|
2584
|
-
databaseExplorerGroups.map(
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
database.id,
|
|
2588
|
-
schema.name,
|
|
2589
|
-
);
|
|
2590
|
-
const isSchemaExpanded =
|
|
2591
|
-
expandedSchemaKeys.includes(schemaKey);
|
|
2592
|
-
|
|
2593
|
-
return (
|
|
2594
|
-
<div
|
|
2595
|
-
key={`${database.id}-${schema.name}`}
|
|
2596
|
-
className="sqlite-schema-group"
|
|
2597
|
-
>
|
|
2598
|
-
<button
|
|
2599
|
-
type="button"
|
|
2600
|
-
className="sqlite-schema-row"
|
|
2601
|
-
onClick={() => {
|
|
2602
|
-
setExpandedSchemaKeys((current) =>
|
|
2603
|
-
current.includes(schemaKey)
|
|
2604
|
-
? current.filter(
|
|
2605
|
-
(value) =>
|
|
2606
|
-
value !== schemaKey,
|
|
2607
|
-
)
|
|
2608
|
-
: [...current, schemaKey],
|
|
2609
|
-
);
|
|
2610
|
-
}}
|
|
2611
|
-
>
|
|
2612
|
-
{isSchemaExpanded ? (
|
|
2613
|
-
<ChevronDown
|
|
2614
|
-
aria-hidden="true"
|
|
2615
|
-
className="h-4 w-4"
|
|
2616
|
-
/>
|
|
2617
|
-
) : (
|
|
2618
|
-
<ChevronRight
|
|
2619
|
-
aria-hidden="true"
|
|
2620
|
-
className="h-4 w-4"
|
|
2621
|
-
/>
|
|
2622
|
-
)}
|
|
2623
|
-
<FolderTree
|
|
2624
|
-
aria-hidden="true"
|
|
2625
|
-
className="h-4 w-4"
|
|
2626
|
-
/>
|
|
2627
|
-
<span className="min-w-0 flex-1 truncate">
|
|
2628
|
-
{schema.name}
|
|
2629
|
-
</span>
|
|
2630
|
-
</button>
|
|
2485
|
+
databaseExplorerGroups.map(({ schema, tables, views }) => {
|
|
2486
|
+
const schemaKey = getSchemaKey(database.id, schema.name);
|
|
2487
|
+
const isSchemaExpanded = expandedSchemaKeys.includes(schemaKey);
|
|
2631
2488
|
|
|
2489
|
+
return (
|
|
2490
|
+
<div
|
|
2491
|
+
key={`${database.id}-${schema.name}`}
|
|
2492
|
+
className="sqlite-schema-group"
|
|
2493
|
+
>
|
|
2494
|
+
<button
|
|
2495
|
+
type="button"
|
|
2496
|
+
className="sqlite-schema-row"
|
|
2497
|
+
onClick={() => {
|
|
2498
|
+
setExpandedSchemaKeys((current) =>
|
|
2499
|
+
current.includes(schemaKey)
|
|
2500
|
+
? current.filter((value) => value !== schemaKey)
|
|
2501
|
+
: [...current, schemaKey],
|
|
2502
|
+
);
|
|
2503
|
+
}}
|
|
2504
|
+
>
|
|
2632
2505
|
{isSchemaExpanded ? (
|
|
2633
|
-
<
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2506
|
+
<ChevronDown aria-hidden="true" className="h-4 w-4" />
|
|
2507
|
+
) : (
|
|
2508
|
+
<ChevronRight aria-hidden="true" className="h-4 w-4" />
|
|
2509
|
+
)}
|
|
2510
|
+
<FolderTree aria-hidden="true" className="h-4 w-4" />
|
|
2511
|
+
<span className="min-w-0 flex-1 truncate">{schema.name}</span>
|
|
2512
|
+
</button>
|
|
2513
|
+
|
|
2514
|
+
{isSchemaExpanded ? (
|
|
2515
|
+
<div className="sqlite-schema-content">
|
|
2516
|
+
{tables.length > 0 ? (
|
|
2517
|
+
<div className="sqlite-object-section">
|
|
2518
|
+
<p className="sqlite-object-section-title">Tables</p>
|
|
2519
|
+
<div className="sqlite-object-list">
|
|
2520
|
+
{tables.map((entity) => {
|
|
2521
|
+
const isSelected =
|
|
2522
|
+
getEntityKey(
|
|
2523
|
+
database.id,
|
|
2524
|
+
entity.schemaName,
|
|
2525
|
+
entity.name,
|
|
2526
|
+
) === selectedEntityKey;
|
|
2527
|
+
|
|
2528
|
+
return (
|
|
2529
|
+
<button
|
|
2530
|
+
key={`${database.id}-${entity.schemaName}-${entity.name}`}
|
|
2531
|
+
type="button"
|
|
2532
|
+
className={joinClassNames(
|
|
2533
|
+
'sqlite-object-row',
|
|
2534
|
+
isSelected && 'is-active',
|
|
2535
|
+
)}
|
|
2536
|
+
onClick={() => {
|
|
2537
|
+
setEntitySelection(database.id, entity);
|
|
2538
|
+
setActiveTab('data');
|
|
2539
|
+
}}
|
|
2540
|
+
>
|
|
2541
|
+
<Table2
|
|
2542
|
+
aria-hidden="true"
|
|
2543
|
+
className="h-4 w-4 shrink-0"
|
|
2544
|
+
/>
|
|
2545
|
+
<span className="min-w-0 flex-1 truncate text-left">
|
|
2546
|
+
{entity.name}
|
|
2547
|
+
</span>
|
|
2548
|
+
</button>
|
|
2549
|
+
);
|
|
2550
|
+
})}
|
|
2676
2551
|
</div>
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
)
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
}
|
|
2711
|
-
>
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
/>
|
|
2716
|
-
<span className="min-w-0 flex-1 truncate text-left">
|
|
2717
|
-
{entity.name}
|
|
2718
|
-
</span>
|
|
2719
|
-
</button>
|
|
2720
|
-
);
|
|
2721
|
-
})}
|
|
2722
|
-
</div>
|
|
2552
|
+
</div>
|
|
2553
|
+
) : null}
|
|
2554
|
+
|
|
2555
|
+
{views.length > 0 ? (
|
|
2556
|
+
<div className="sqlite-object-section">
|
|
2557
|
+
<p className="sqlite-object-section-title">Views</p>
|
|
2558
|
+
<div className="sqlite-object-list">
|
|
2559
|
+
{views.map((entity) => {
|
|
2560
|
+
const isSelected =
|
|
2561
|
+
getEntityKey(
|
|
2562
|
+
database.id,
|
|
2563
|
+
entity.schemaName,
|
|
2564
|
+
entity.name,
|
|
2565
|
+
) === selectedEntityKey;
|
|
2566
|
+
|
|
2567
|
+
return (
|
|
2568
|
+
<button
|
|
2569
|
+
key={`${database.id}-${entity.schemaName}-${entity.name}`}
|
|
2570
|
+
type="button"
|
|
2571
|
+
className={joinClassNames(
|
|
2572
|
+
'sqlite-object-row',
|
|
2573
|
+
isSelected && 'is-active',
|
|
2574
|
+
)}
|
|
2575
|
+
onClick={() => {
|
|
2576
|
+
setEntitySelection(database.id, entity);
|
|
2577
|
+
setActiveTab('structure');
|
|
2578
|
+
}}
|
|
2579
|
+
>
|
|
2580
|
+
<FileCode2
|
|
2581
|
+
aria-hidden="true"
|
|
2582
|
+
className="h-4 w-4 shrink-0"
|
|
2583
|
+
/>
|
|
2584
|
+
<span className="min-w-0 flex-1 truncate text-left">
|
|
2585
|
+
{entity.name}
|
|
2586
|
+
</span>
|
|
2587
|
+
</button>
|
|
2588
|
+
);
|
|
2589
|
+
})}
|
|
2723
2590
|
</div>
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
)
|
|
2591
|
+
</div>
|
|
2592
|
+
) : null}
|
|
2593
|
+
</div>
|
|
2594
|
+
) : null}
|
|
2595
|
+
</div>
|
|
2596
|
+
);
|
|
2597
|
+
})
|
|
2731
2598
|
)}
|
|
2732
2599
|
</div>
|
|
2733
2600
|
) : null}
|
|
@@ -2809,6 +2676,13 @@ export default function SqlitePanel() {
|
|
|
2809
2676
|
onClose={() => setDeletingRow(null)}
|
|
2810
2677
|
onDelete={handleDeleteRow}
|
|
2811
2678
|
/>
|
|
2679
|
+
|
|
2680
|
+
<SqliteDropModal
|
|
2681
|
+
isOpen={!!activeDrop}
|
|
2682
|
+
target={activeDrop?.target ?? DEFAULT_DROP_TARGET}
|
|
2683
|
+
onClose={() => setActiveDrop(null)}
|
|
2684
|
+
onConfirm={handleConfirmDrop}
|
|
2685
|
+
/>
|
|
2812
2686
|
</div>
|
|
2813
2687
|
</div>
|
|
2814
2688
|
);
|