@adecore/database 0.0.1 → 0.16.0-beta.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/LICENSE +21 -0
- package/README.md +152 -1
- package/dist/DatabaseProvider.d.ts +13 -0
- package/dist/DatabaseProvider.js +17 -0
- package/dist/actions.d.ts +40 -0
- package/dist/actions.js +1 -0
- package/dist/client/client.d.ts +5 -0
- package/dist/client/client.js +245 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +2 -0
- package/dist/client/types.d.ts +97 -0
- package/dist/client/types.js +13 -0
- package/dist/client-context.d.ts +16 -0
- package/dist/client-context.js +17 -0
- package/dist/connections/ConnectionForm.d.ts +10 -0
- package/dist/connections/ConnectionForm.js +145 -0
- package/dist/connections/ConnectionManager.d.ts +12 -0
- package/dist/connections/ConnectionManager.js +93 -0
- package/dist/connections/EngineIcon.d.ts +9 -0
- package/dist/connections/EngineIcon.js +7 -0
- package/dist/connections/connection-config.d.ts +16 -0
- package/dist/connections/connection-config.js +107 -0
- package/dist/connections/docker.d.ts +9 -0
- package/dist/connections/docker.js +38 -0
- package/dist/connections/engine-icons.d.ts +3 -0
- package/dist/connections/engine-icons.js +3 -0
- package/dist/connections/use-docker-containers.d.ts +17 -0
- package/dist/connections/use-docker-containers.js +26 -0
- package/dist/console/DestructiveDialog.d.ts +9 -0
- package/dist/console/DestructiveDialog.js +9 -0
- package/dist/console/HistoryPanel.d.ts +10 -0
- package/dist/console/HistoryPanel.js +15 -0
- package/dist/console/QueryConsole.d.ts +14 -0
- package/dist/console/QueryConsole.js +261 -0
- package/dist/console/SchemaPicker.d.ts +11 -0
- package/dist/console/SchemaPicker.js +8 -0
- package/dist/console/SqlEditor.d.ts +12 -0
- package/dist/console/SqlEditor.js +61 -0
- package/dist/console/StatementResultView.d.ts +18 -0
- package/dist/console/StatementResultView.js +32 -0
- package/dist/console/TransactionControls.d.ts +13 -0
- package/dist/console/TransactionControls.js +12 -0
- package/dist/console/destructive.d.ts +7 -0
- package/dist/console/destructive.js +43 -0
- package/dist/console/editing.d.ts +12 -0
- package/dist/console/editing.js +60 -0
- package/dist/console/history.d.ts +13 -0
- package/dist/console/history.js +43 -0
- package/dist/console/labels.d.ts +1 -0
- package/dist/console/labels.js +6 -0
- package/dist/console/outcome.d.ts +6 -0
- package/dist/console/outcome.js +11 -0
- package/dist/console/schemas.d.ts +2 -0
- package/dist/console/schemas.js +5 -0
- package/dist/console/shortcuts.d.ts +2 -0
- package/dist/console/shortcuts.js +3 -0
- package/dist/console/useConsoleHistory.d.ts +7 -0
- package/dist/console/useConsoleHistory.js +22 -0
- package/dist/ddl/alter-mysql.d.ts +4 -0
- package/dist/ddl/alter-mysql.js +79 -0
- package/dist/ddl/alter-sqlite.d.ts +4 -0
- package/dist/ddl/alter-sqlite.js +80 -0
- package/dist/ddl/alter.d.ts +4 -0
- package/dist/ddl/alter.js +24 -0
- package/dist/ddl/column.d.ts +9 -0
- package/dist/ddl/column.js +62 -0
- package/dist/ddl/create.d.ts +12 -0
- package/dist/ddl/create.js +74 -0
- package/dist/ddl/dialect.d.ts +12 -0
- package/dist/ddl/dialect.js +34 -0
- package/dist/ddl/diff.d.ts +34 -0
- package/dist/ddl/diff.js +111 -0
- package/dist/ddl/draft.d.ts +46 -0
- package/dist/ddl/draft.js +70 -0
- package/dist/ddl/index.d.ts +10 -0
- package/dist/ddl/index.js +10 -0
- package/dist/ddl/parse.d.ts +17 -0
- package/dist/ddl/parse.js +131 -0
- package/dist/ddl/statements.d.ts +6 -0
- package/dist/ddl/statements.js +12 -0
- package/dist/ddl/types.d.ts +6 -0
- package/dist/ddl/types.js +52 -0
- package/dist/ddl/validate.d.ts +7 -0
- package/dist/ddl/validate.js +38 -0
- package/dist/designer/ColumnsEditor.d.ts +2 -0
- package/dist/designer/ColumnsEditor.js +24 -0
- package/dist/designer/DesignerView.d.ts +25 -0
- package/dist/designer/DesignerView.js +33 -0
- package/dist/designer/ForeignKeysEditor.d.ts +7 -0
- package/dist/designer/ForeignKeysEditor.js +24 -0
- package/dist/designer/IndexesEditor.d.ts +2 -0
- package/dist/designer/IndexesEditor.js +13 -0
- package/dist/designer/NamePicker.d.ts +11 -0
- package/dist/designer/NamePicker.js +13 -0
- package/dist/designer/OptionsEditor.d.ts +2 -0
- package/dist/designer/OptionsEditor.js +17 -0
- package/dist/designer/SqlPreview.d.ts +12 -0
- package/dist/designer/SqlPreview.js +17 -0
- package/dist/designer/TableDesigner.d.ts +10 -0
- package/dist/designer/TableDesigner.js +103 -0
- package/dist/designer/edit.d.ts +17 -0
- package/dist/designer/edit.js +115 -0
- package/dist/designer/editor-props.d.ts +10 -0
- package/dist/designer/editor-props.js +1 -0
- package/dist/designer/plan.d.ts +9 -0
- package/dist/designer/plan.js +13 -0
- package/dist/designer/recover.d.ts +2 -0
- package/dist/designer/recover.js +18 -0
- package/dist/designer/useReferenceColumns.d.ts +3 -0
- package/dist/designer/useReferenceColumns.js +36 -0
- package/dist/explorer/DatabaseExplorer.d.ts +13 -0
- package/dist/explorer/DatabaseExplorer.js +264 -0
- package/dist/explorer/RowMenu.d.ts +11 -0
- package/dist/explorer/RowMenu.js +89 -0
- package/dist/explorer/TableDialog.d.ts +15 -0
- package/dist/explorer/TableDialog.js +50 -0
- package/dist/explorer/expansion.d.ts +7 -0
- package/dist/explorer/expansion.js +31 -0
- package/dist/explorer/table-statements.d.ts +5 -0
- package/dist/explorer/table-statements.js +12 -0
- package/dist/explorer/tree.d.ts +125 -0
- package/dist/explorer/tree.js +295 -0
- package/dist/explorer/use-explorer-loads.d.ts +12 -0
- package/dist/explorer/use-explorer-loads.js +109 -0
- package/dist/explorer/use-stored-expansion.d.ts +9 -0
- package/dist/explorer/use-stored-expansion.js +47 -0
- package/dist/grid/CellEditor.d.ts +9 -0
- package/dist/grid/CellEditor.js +29 -0
- package/dist/grid/CopyAsMenu.d.ts +7 -0
- package/dist/grid/CopyAsMenu.js +10 -0
- package/dist/grid/DataGrid.d.ts +36 -0
- package/dist/grid/DataGrid.js +512 -0
- package/dist/grid/EnumPicker.d.ts +17 -0
- package/dist/grid/EnumPicker.js +53 -0
- package/dist/grid/GridHeaderCell.d.ts +36 -0
- package/dist/grid/GridHeaderCell.js +69 -0
- package/dist/grid/RecordView.d.ts +20 -0
- package/dist/grid/RecordView.js +126 -0
- package/dist/grid/ValueDock.d.ts +11 -0
- package/dist/grid/ValueDock.js +28 -0
- package/dist/grid/aggregates.d.ts +22 -0
- package/dist/grid/aggregates.js +62 -0
- package/dist/grid/cell-range.d.ts +14 -0
- package/dist/grid/cell-range.js +11 -0
- package/dist/grid/column-layout.d.ts +18 -0
- package/dist/grid/column-layout.js +49 -0
- package/dist/grid/column-selection.d.ts +11 -0
- package/dist/grid/column-selection.js +28 -0
- package/dist/grid/column-view.d.ts +10 -0
- package/dist/grid/column-view.js +23 -0
- package/dist/grid/copy-formats.d.ts +15 -0
- package/dist/grid/copy-formats.js +80 -0
- package/dist/grid/display.d.ts +20 -0
- package/dist/grid/display.js +81 -0
- package/dist/grid/edit-value.d.ts +5 -0
- package/dist/grid/edit-value.js +55 -0
- package/dist/grid/enum-type.d.ts +7 -0
- package/dist/grid/enum-type.js +52 -0
- package/dist/grid/focused-value.d.ts +4 -0
- package/dist/grid/focused-value.js +19 -0
- package/dist/grid/layout.d.ts +22 -0
- package/dist/grid/layout.js +74 -0
- package/dist/grid/navigation.d.ts +15 -0
- package/dist/grid/navigation.js +52 -0
- package/dist/grid/row-selection.d.ts +9 -0
- package/dist/grid/row-selection.js +24 -0
- package/dist/grid/sort.d.ts +7 -0
- package/dist/grid/sort.js +7 -0
- package/dist/grid/types.d.ts +30 -0
- package/dist/grid/types.js +9 -0
- package/dist/host/helper-path.d.ts +7 -0
- package/dist/host/helper-path.js +15 -0
- package/dist/host/host.d.ts +2 -0
- package/dist/host/host.js +343 -0
- package/dist/host/index.d.ts +5 -0
- package/dist/host/index.js +4 -0
- package/dist/host/spawn.d.ts +7 -0
- package/dist/host/spawn.js +98 -0
- package/dist/host/types.d.ts +19 -0
- package/dist/host/types.js +1 -0
- package/dist/host/validate.d.ts +10 -0
- package/dist/host/validate.js +339 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -0
- package/dist/locales/en.json +577 -0
- package/dist/locales/nl.json +577 -0
- package/dist/locales.d.ts +4 -0
- package/dist/locales.js +14 -0
- package/dist/protocol/connection.d.ts +56 -0
- package/dist/protocol/connection.js +1 -0
- package/dist/protocol/errors.d.ts +7 -0
- package/dist/protocol/errors.js +1 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/protocol/index.js +3 -0
- package/dist/protocol/messages.d.ts +250 -0
- package/dist/protocol/messages.js +2 -0
- package/dist/protocol/schema.d.ts +53 -0
- package/dist/protocol/schema.js +1 -0
- package/dist/protocol/values.d.ts +21 -0
- package/dist/protocol/values.js +15 -0
- package/dist/sql-split.d.ts +15 -0
- package/dist/sql-split.js +201 -0
- package/dist/sql.d.ts +29 -0
- package/dist/sql.js +104 -0
- package/dist/structure/StructureView.d.ts +14 -0
- package/dist/structure/StructureView.js +99 -0
- package/dist/structure/structure-text.d.ts +2 -0
- package/dist/structure/structure-text.js +2 -0
- package/dist/table/CommandField.d.ts +14 -0
- package/dist/table/CommandField.js +157 -0
- package/dist/table/ImportDialog.d.ts +27 -0
- package/dist/table/ImportDialog.js +28 -0
- package/dist/table/TableStatusBar.d.ts +19 -0
- package/dist/table/TableStatusBar.js +22 -0
- package/dist/table/TableToolbar.d.ts +43 -0
- package/dist/table/TableToolbar.js +16 -0
- package/dist/table/TableView.d.ts +13 -0
- package/dist/table/TableView.js +508 -0
- package/dist/table/command-field.d.ts +57 -0
- package/dist/table/command-field.js +203 -0
- package/dist/table/grid-rows.d.ts +23 -0
- package/dist/table/grid-rows.js +55 -0
- package/dist/table/import-mapping.d.ts +7 -0
- package/dist/table/import-mapping.js +28 -0
- package/dist/table/layout-store.d.ts +13 -0
- package/dist/table/layout-store.js +43 -0
- package/dist/table/paging.d.ts +12 -0
- package/dist/table/paging.js +20 -0
- package/dist/table/pending.d.ts +32 -0
- package/dist/table/pending.js +94 -0
- package/dist/table/references.d.ts +12 -0
- package/dist/table/references.js +21 -0
- package/dist/table/useLoaded.d.ts +7 -0
- package/dist/table/useLoaded.js +36 -0
- package/dist/table/useStoredLayout.d.ts +8 -0
- package/dist/table/useStoredLayout.js +39 -0
- package/dist/table/useTableTransfer.d.ts +51 -0
- package/dist/table/useTableTransfer.js +125 -0
- package/dist/testing/fake.d.ts +21 -0
- package/dist/testing/fake.js +372 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/use-popup-press.d.ts +1 -0
- package/dist/use-popup-press.js +24 -0
- package/dist/use-schema-change.d.ts +1 -0
- package/dist/use-schema-change.js +13 -0
- package/dist/use-stable-callback.d.ts +1 -0
- package/dist/use-stable-callback.js +9 -0
- package/dist/value/ValuePanel.d.ts +14 -0
- package/dist/value/ValuePanel.js +103 -0
- package/dist/value/draft.d.ts +33 -0
- package/dist/value/draft.js +44 -0
- package/dist/value/hex.d.ts +14 -0
- package/dist/value/hex.js +46 -0
- package/dist/value/json.d.ts +9 -0
- package/dist/value/json.js +71 -0
- package/dist/value/views.d.ts +22 -0
- package/dist/value/views.js +54 -0
- package/dist/workbench/DatabaseWorkbench.d.ts +9 -0
- package/dist/workbench/DatabaseWorkbench.js +179 -0
- package/dist/workbench/DesignerTab.d.ts +10 -0
- package/dist/workbench/DesignerTab.js +22 -0
- package/dist/workbench/TableTab.d.ts +10 -0
- package/dist/workbench/TableTab.js +8 -0
- package/dist/workbench/tabs.d.ts +42 -0
- package/dist/workbench/tabs.js +139 -0
- package/package.json +79 -3
- package/src/DatabaseProvider.tsx +37 -0
- package/src/actions.ts +34 -0
- package/src/client/client.ts +324 -0
- package/src/client/index.ts +18 -0
- package/src/client/types.ts +161 -0
- package/src/client-context.ts +35 -0
- package/src/connections/ConnectionForm.tsx +474 -0
- package/src/connections/ConnectionManager.tsx +224 -0
- package/src/connections/EngineIcon.tsx +18 -0
- package/src/connections/connection-config.ts +143 -0
- package/src/connections/docker.ts +50 -0
- package/src/connections/engine-icons.ts +5 -0
- package/src/connections/use-docker-containers.ts +41 -0
- package/src/console/DestructiveDialog.tsx +40 -0
- package/src/console/HistoryPanel.tsx +67 -0
- package/src/console/QueryConsole.tsx +439 -0
- package/src/console/SchemaPicker.tsx +32 -0
- package/src/console/SqlEditor.tsx +95 -0
- package/src/console/StatementResultView.tsx +176 -0
- package/src/console/TransactionControls.tsx +53 -0
- package/src/console/destructive.ts +60 -0
- package/src/console/editing.ts +77 -0
- package/src/console/history.ts +62 -0
- package/src/console/labels.ts +7 -0
- package/src/console/outcome.ts +14 -0
- package/src/console/schemas.ts +7 -0
- package/src/console/shortcuts.ts +4 -0
- package/src/console/useConsoleHistory.ts +39 -0
- package/src/ddl/alter-mysql.ts +90 -0
- package/src/ddl/alter-sqlite.ts +93 -0
- package/src/ddl/alter.ts +29 -0
- package/src/ddl/column.ts +74 -0
- package/src/ddl/create.ts +92 -0
- package/src/ddl/dialect.ts +53 -0
- package/src/ddl/diff.ts +164 -0
- package/src/ddl/draft.ts +136 -0
- package/src/ddl/index.ts +10 -0
- package/src/ddl/parse.ts +161 -0
- package/src/ddl/statements.ts +20 -0
- package/src/ddl/types.ts +60 -0
- package/src/ddl/validate.ts +47 -0
- package/src/designer/ColumnsEditor.tsx +204 -0
- package/src/designer/DesignerView.tsx +184 -0
- package/src/designer/ForeignKeysEditor.tsx +132 -0
- package/src/designer/IndexesEditor.tsx +63 -0
- package/src/designer/NamePicker.tsx +44 -0
- package/src/designer/OptionsEditor.tsx +101 -0
- package/src/designer/SqlPreview.tsx +56 -0
- package/src/designer/TableDesigner.tsx +186 -0
- package/src/designer/edit.ts +137 -0
- package/src/designer/editor-props.ts +13 -0
- package/src/designer/plan.ts +25 -0
- package/src/designer/recover.ts +20 -0
- package/src/designer/useReferenceColumns.ts +54 -0
- package/src/explorer/DatabaseExplorer.tsx +450 -0
- package/src/explorer/RowMenu.tsx +134 -0
- package/src/explorer/TableDialog.tsx +80 -0
- package/src/explorer/expansion.ts +47 -0
- package/src/explorer/table-statements.ts +18 -0
- package/src/explorer/tree.ts +453 -0
- package/src/explorer/use-explorer-loads.ts +157 -0
- package/src/explorer/use-stored-expansion.ts +66 -0
- package/src/grid/CellEditor.tsx +53 -0
- package/src/grid/CopyAsMenu.tsx +33 -0
- package/src/grid/DataGrid.tsx +871 -0
- package/src/grid/EnumPicker.tsx +108 -0
- package/src/grid/GridHeaderCell.tsx +243 -0
- package/src/grid/RecordView.tsx +293 -0
- package/src/grid/ValueDock.tsx +52 -0
- package/src/grid/aggregates.ts +94 -0
- package/src/grid/cell-range.ts +26 -0
- package/src/grid/column-layout.ts +71 -0
- package/src/grid/column-selection.ts +42 -0
- package/src/grid/column-view.ts +35 -0
- package/src/grid/copy-formats.ts +108 -0
- package/src/grid/display.ts +109 -0
- package/src/grid/edit-value.ts +60 -0
- package/src/grid/enum-type.ts +62 -0
- package/src/grid/focused-value.ts +23 -0
- package/src/grid/layout.ts +91 -0
- package/src/grid/navigation.ts +74 -0
- package/src/grid/row-selection.ts +35 -0
- package/src/grid/sort.ts +12 -0
- package/src/grid/types.ts +59 -0
- package/src/host/helper-path.ts +41 -0
- package/src/host/host.ts +456 -0
- package/src/host/index.ts +5 -0
- package/src/host/spawn.ts +124 -0
- package/src/host/types.ts +46 -0
- package/src/host/validate.ts +402 -0
- package/src/index.ts +37 -0
- package/src/locales/en.json +577 -0
- package/src/locales/nl.json +577 -0
- package/src/locales.ts +18 -0
- package/src/protocol/connection.ts +86 -0
- package/src/protocol/errors.ts +38 -0
- package/src/protocol/index.ts +33 -0
- package/src/protocol/messages.ts +168 -0
- package/src/protocol/schema.ts +76 -0
- package/src/protocol/values.ts +52 -0
- package/src/sql-split.ts +220 -0
- package/src/sql.ts +152 -0
- package/src/structure/StructureView.tsx +269 -0
- package/src/structure/structure-text.ts +4 -0
- package/src/table/CommandField.tsx +318 -0
- package/src/table/ImportDialog.tsx +168 -0
- package/src/table/TableStatusBar.tsx +91 -0
- package/src/table/TableToolbar.tsx +217 -0
- package/src/table/TableView.tsx +880 -0
- package/src/table/command-field.ts +285 -0
- package/src/table/grid-rows.ts +80 -0
- package/src/table/import-mapping.ts +37 -0
- package/src/table/layout-store.ts +62 -0
- package/src/table/paging.ts +33 -0
- package/src/table/pending.ts +143 -0
- package/src/table/references.ts +38 -0
- package/src/table/useLoaded.ts +58 -0
- package/src/table/useStoredLayout.ts +53 -0
- package/src/table/useTableTransfer.ts +187 -0
- package/src/testing/fake.ts +533 -0
- package/src/testing/index.ts +1 -0
- package/src/use-popup-press.ts +27 -0
- package/src/use-schema-change.ts +19 -0
- package/src/use-stable-callback.ts +12 -0
- package/src/value/ValuePanel.tsx +254 -0
- package/src/value/draft.ts +72 -0
- package/src/value/hex.ts +62 -0
- package/src/value/json.ts +74 -0
- package/src/value/views.ts +68 -0
- package/src/workbench/DatabaseWorkbench.tsx +348 -0
- package/src/workbench/DesignerTab.tsx +37 -0
- package/src/workbench/TableTab.tsx +27 -0
- package/src/workbench/tabs.ts +184 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Engine, StatementResult } from '../protocol/index.ts';
|
|
2
|
+
export declare const outcomeOf: (results: readonly StatementResult[]) => {
|
|
3
|
+
readonly ok: boolean;
|
|
4
|
+
readonly rows: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const isPageable: (sql: string, engine: Engine) => boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { firstKeyword } from '../sql-split.js';
|
|
2
|
+
/* How a run ended, for the history: whether every statement worked, and how many rows they read or changed. */
|
|
3
|
+
export const outcomeOf = (results) => ({
|
|
4
|
+
ok: results.every((result) => result.kind !== 'error'),
|
|
5
|
+
rows: results.reduce((sum, result) => sum + (result.kind === 'rows' ? result.rows.length : result.kind === 'done' ? result.affected : 0), 0)
|
|
6
|
+
});
|
|
7
|
+
/* Whether the helper can wrap the statement to fetch a page of it: a read that starts with `SELECT` or `WITH`. */
|
|
8
|
+
export const isPageable = (sql, engine) => {
|
|
9
|
+
const keyword = firstKeyword(sql, engine);
|
|
10
|
+
return keyword === 'SELECT' || keyword === 'WITH';
|
|
11
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type HistoryEntry } from './history.ts';
|
|
2
|
+
export interface ConsoleHistory {
|
|
3
|
+
readonly entries: readonly HistoryEntry[];
|
|
4
|
+
record(entry: HistoryEntry): void;
|
|
5
|
+
clear(): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function useConsoleHistory(connectionId: string): ConsoleHistory;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useDatabaseStorage } from '../client-context.js';
|
|
3
|
+
import { addEntry, historyKey, parseHistory, serializeHistory } from './history.js';
|
|
4
|
+
/* The runs of one connection, kept in the app's storage when it has one and for the life of the console when it has not. */
|
|
5
|
+
export function useConsoleHistory(connectionId) {
|
|
6
|
+
const storage = useDatabaseStorage();
|
|
7
|
+
const key = historyKey(connectionId);
|
|
8
|
+
const [state, setState] = useState(() => ({ key, entries: parseHistory(storage?.get(key) ?? null), changed: false }));
|
|
9
|
+
if (state.key !== key) {
|
|
10
|
+
setState({ key, entries: parseHistory(storage?.get(key) ?? null), changed: false });
|
|
11
|
+
}
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (state.changed) {
|
|
14
|
+
storage?.set(state.key, state.entries.length === 0 ? null : serializeHistory(state.entries));
|
|
15
|
+
}
|
|
16
|
+
}, [state, storage]);
|
|
17
|
+
return {
|
|
18
|
+
entries: state.entries,
|
|
19
|
+
record: (entry) => setState((current) => ({ ...current, entries: addEntry(current.entries, entry), changed: true })),
|
|
20
|
+
clear: () => setState((current) => ({ ...current, entries: [], changed: true }))
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Dialect } from './dialect.ts';
|
|
2
|
+
import type { TableDiff } from './diff.ts';
|
|
3
|
+
import type { TableDraft } from './draft.ts';
|
|
4
|
+
export declare const mysqlAlterSql: (dialect: Dialect, schema: string, original: TableDraft, draft: TableDraft, diff: TableDiff) => string[];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { columnSpecSql } from './column.js';
|
|
2
|
+
import { foreignKeySql, mysqlOptionsSql } from './create.js';
|
|
3
|
+
import { columnList, quote, tableName } from './dialect.js';
|
|
4
|
+
const positionSql = (dialect, after) => (after === null ? 'FIRST' : `AFTER ${quote(dialect, after)}`);
|
|
5
|
+
const indexSql = (dialect, index) => [
|
|
6
|
+
'ADD',
|
|
7
|
+
index.unique ? 'UNIQUE INDEX' : 'INDEX',
|
|
8
|
+
...(index.name === '' ? [] : [quote(dialect, index.name)]),
|
|
9
|
+
`(${columnList(dialect, index.columns)})`
|
|
10
|
+
].join(' ');
|
|
11
|
+
const withPosition = (clause, position) => (position === null ? clause : `${clause} ${position}`);
|
|
12
|
+
/*
|
|
13
|
+
* One `ALTER TABLE`, or two when it has to be. A position clause names the columns as they are
|
|
14
|
+
* called after a rename, and a foreign key cannot be dropped and added again under the same name
|
|
15
|
+
* in one statement, so both put the drops and renames in a statement of their own, first.
|
|
16
|
+
*/
|
|
17
|
+
export const mysqlAlterSql = (dialect, schema, original, draft, diff) => {
|
|
18
|
+
const early = [];
|
|
19
|
+
const late = [];
|
|
20
|
+
const column = (verb, target, previous, position) => {
|
|
21
|
+
const spec = columnSpecSql(dialect, target);
|
|
22
|
+
const named = verb === 'CHANGE' ? `${quote(dialect, previous)} ${quote(dialect, target.name)}` : quote(dialect, target.name);
|
|
23
|
+
return withPosition(`${verb} COLUMN ${named} ${spec}`, position);
|
|
24
|
+
};
|
|
25
|
+
const positioned = diff.keptColumns.some((kept) => kept.moved) || diff.addedColumns.some((added) => added.placed);
|
|
26
|
+
const reusesName = diff.droppedForeignKeys.some((dropped) => dropped.name !== '' && diff.addedForeignKeys.some((added) => added.name === dropped.name));
|
|
27
|
+
const split = (positioned && diff.keptColumns.some((kept) => kept.renamed)) || reusesName;
|
|
28
|
+
for (const foreignKey of diff.droppedForeignKeys) {
|
|
29
|
+
early.push(`DROP FOREIGN KEY ${quote(dialect, foreignKey.name)}`);
|
|
30
|
+
}
|
|
31
|
+
for (const index of diff.droppedIndexes) {
|
|
32
|
+
early.push(`DROP INDEX ${quote(dialect, index.name)}`);
|
|
33
|
+
}
|
|
34
|
+
for (const { from, to } of diff.renamedIndexes) {
|
|
35
|
+
early.push(`RENAME INDEX ${quote(dialect, from.name)} TO ${quote(dialect, to.name)}`);
|
|
36
|
+
}
|
|
37
|
+
if (diff.primaryKeyChanged && original.primaryKey.length > 0) {
|
|
38
|
+
early.push('DROP PRIMARY KEY');
|
|
39
|
+
}
|
|
40
|
+
for (const dropped of diff.droppedColumns) {
|
|
41
|
+
early.push(`DROP COLUMN ${quote(dialect, dropped.name)}`);
|
|
42
|
+
}
|
|
43
|
+
for (const kept of diff.keptColumns) {
|
|
44
|
+
const verb = kept.renamed ? 'CHANGE' : 'MODIFY';
|
|
45
|
+
if (split) {
|
|
46
|
+
if (kept.renamed || kept.redefined) {
|
|
47
|
+
early.push(column(verb, kept.column, kept.original.name, null));
|
|
48
|
+
}
|
|
49
|
+
if (kept.moved) {
|
|
50
|
+
late.push(column('MODIFY', kept.column, null, positionSql(dialect, kept.after)));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (kept.renamed || kept.redefined || kept.moved) {
|
|
54
|
+
early.push(column(verb, kept.column, kept.original.name, kept.moved ? positionSql(dialect, kept.after) : null));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const added of diff.addedColumns) {
|
|
58
|
+
late.push(column('ADD', added.column, null, added.placed ? positionSql(dialect, added.after) : null));
|
|
59
|
+
}
|
|
60
|
+
if (diff.primaryKeyChanged && draft.primaryKey.length > 0) {
|
|
61
|
+
late.push(`ADD PRIMARY KEY (${columnList(dialect, draft.primaryKey)})`);
|
|
62
|
+
}
|
|
63
|
+
for (const index of diff.addedIndexes) {
|
|
64
|
+
late.push(indexSql(dialect, index));
|
|
65
|
+
}
|
|
66
|
+
for (const foreignKey of diff.addedForeignKeys) {
|
|
67
|
+
late.push(`ADD ${foreignKeySql(dialect, schema, foreignKey)}`);
|
|
68
|
+
}
|
|
69
|
+
const options = diff.optionsChanged ? mysqlOptionsSql(draft.options, original.options).join(' ') : '';
|
|
70
|
+
if (options !== '') {
|
|
71
|
+
late.push(options);
|
|
72
|
+
}
|
|
73
|
+
if (diff.renamed) {
|
|
74
|
+
late.push(`RENAME TO ${quote(dialect, draft.name)}`);
|
|
75
|
+
}
|
|
76
|
+
const target = tableName(dialect, schema, original.name);
|
|
77
|
+
const statement = (clauses) => (clauses.length === 0 ? [] : [`ALTER TABLE ${target}\n ${clauses.join(',\n ')}`]);
|
|
78
|
+
return split ? [...statement(early), ...statement(late)] : statement([...early, ...late]);
|
|
79
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Dialect } from './dialect.ts';
|
|
2
|
+
import type { TableDiff } from './diff.ts';
|
|
3
|
+
import { type TableDraft } from './draft.ts';
|
|
4
|
+
export declare const sqliteAlterSql: (dialect: Dialect, schema: string, original: TableDraft, draft: TableDraft, diff: TableDiff) => string[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { columnSql, defaultClause } from './column.js';
|
|
2
|
+
import { createIndexSql, createTableParts } from './create.js';
|
|
3
|
+
import { atLeast, quote, tableName } from './dialect.js';
|
|
4
|
+
import { isAutoIndex } from './draft.js';
|
|
5
|
+
import { dropIndexSql } from './statements.js';
|
|
6
|
+
const RENAME_COLUMN = [3, 25];
|
|
7
|
+
const DROP_COLUMN = [3, 35];
|
|
8
|
+
/* The clauses `ALTER TABLE .. ADD COLUMN` refuses: a key, a computed value, or a NOT NULL column with nothing to fill the rows with. */
|
|
9
|
+
const addable = (column) => {
|
|
10
|
+
if (column.autoIncrement || (column.generated && column.generatedClause !== null && /\bSTORED\b/i.test(column.generatedClause))) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const expression = column.defaultValue?.trim() ?? '';
|
|
14
|
+
if (expression === '') {
|
|
15
|
+
return column.nullable;
|
|
16
|
+
}
|
|
17
|
+
// Only a constant may fill existing rows: not an expression, not the clock.
|
|
18
|
+
const clause = defaultClause(expression);
|
|
19
|
+
return !clause.startsWith('(') && !/^CURRENT_|^LOCALTIME|^NOW/i.test(clause) && (column.nullable || !/^NULL$/i.test(clause));
|
|
20
|
+
};
|
|
21
|
+
const needsRebuild = (dialect, original, diff) => {
|
|
22
|
+
if (diff.optionsChanged || diff.primaryKeyChanged || diff.droppedForeignKeys.length > 0 || diff.addedForeignKeys.length > 0) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (diff.keptColumns.some((kept) => kept.redefined || kept.moved || (kept.renamed && !atLeast(dialect, ...RENAME_COLUMN)))) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (diff.addedColumns.some((added) => added.placed || !addable(added.column))) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (diff.droppedColumns.length > 0 && !atLeast(dialect, ...DROP_COLUMN)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
const autoIndexed = new Set(original.indexes.filter((index) => isAutoIndex(index.name)).flatMap((index) => index.columns));
|
|
35
|
+
if (diff.droppedColumns.some((dropped) => autoIndexed.has(dropped.name))) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return [...diff.droppedIndexes, ...diff.renamedIndexes.map(({ from }) => from)].some((index) => isAutoIndex(index.name));
|
|
39
|
+
};
|
|
40
|
+
/* The steps of https://www.sqlite.org/lang_altertable.html under "Making Other Kinds Of Table Schema Changes", which keep the rows, the indexes and the foreign keys of other tables intact. */
|
|
41
|
+
const rebuildSql = (dialect, schema, original, draft, diff) => {
|
|
42
|
+
const temporary = `new_${draft.name}`;
|
|
43
|
+
const { table, indexes } = createTableParts(dialect, schema, draft, temporary);
|
|
44
|
+
const copied = diff.keptColumns.filter((kept) => !kept.column.generated);
|
|
45
|
+
const statements = ['PRAGMA foreign_keys=OFF', 'BEGIN', table];
|
|
46
|
+
if (copied.length > 0) {
|
|
47
|
+
const into = copied.map((kept) => quote(dialect, kept.column.name)).join(', ');
|
|
48
|
+
const from = copied.map((kept) => quote(dialect, kept.original.name)).join(', ');
|
|
49
|
+
statements.push(`INSERT INTO ${tableName(dialect, schema, temporary)} (${into}) SELECT ${from} FROM ${tableName(dialect, schema, original.name)}`);
|
|
50
|
+
}
|
|
51
|
+
statements.push(`DROP TABLE ${tableName(dialect, schema, original.name)}`, `ALTER TABLE ${tableName(dialect, schema, temporary)} RENAME TO ${quote(dialect, draft.name)}`, ...indexes.map((index) => createIndexSql(dialect, schema, draft.name, index)), 'PRAGMA foreign_key_check', 'COMMIT', 'PRAGMA foreign_keys=ON');
|
|
52
|
+
return statements;
|
|
53
|
+
};
|
|
54
|
+
/* `ALTER TABLE` where SQLite has one, a rebuild of the table for every other change. */
|
|
55
|
+
export const sqliteAlterSql = (dialect, schema, original, draft, diff) => {
|
|
56
|
+
if (needsRebuild(dialect, original, diff)) {
|
|
57
|
+
return rebuildSql(dialect, schema, original, draft, diff);
|
|
58
|
+
}
|
|
59
|
+
const target = tableName(dialect, schema, original.name);
|
|
60
|
+
const statements = [];
|
|
61
|
+
for (const index of [...diff.droppedIndexes, ...diff.renamedIndexes.map(({ from }) => from)]) {
|
|
62
|
+
statements.push(dropIndexSql(dialect, schema, original.name, index.name));
|
|
63
|
+
}
|
|
64
|
+
for (const dropped of diff.droppedColumns) {
|
|
65
|
+
statements.push(`ALTER TABLE ${target} DROP COLUMN ${quote(dialect, dropped.name)}`);
|
|
66
|
+
}
|
|
67
|
+
for (const kept of diff.keptColumns.filter((candidate) => candidate.renamed)) {
|
|
68
|
+
statements.push(`ALTER TABLE ${target} RENAME COLUMN ${quote(dialect, kept.original.name)} TO ${quote(dialect, kept.column.name)}`);
|
|
69
|
+
}
|
|
70
|
+
for (const added of diff.addedColumns) {
|
|
71
|
+
statements.push(`ALTER TABLE ${target} ADD COLUMN ${columnSql(dialect, added.column)}`);
|
|
72
|
+
}
|
|
73
|
+
for (const index of [...diff.addedIndexes, ...diff.renamedIndexes.map(({ to }) => to)]) {
|
|
74
|
+
statements.push(createIndexSql(dialect, schema, original.name, index));
|
|
75
|
+
}
|
|
76
|
+
if (diff.renamed) {
|
|
77
|
+
statements.push(`ALTER TABLE ${target} RENAME TO ${quote(dialect, draft.name)}`);
|
|
78
|
+
}
|
|
79
|
+
return statements;
|
|
80
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TableStructure } from '../protocol/index.ts';
|
|
2
|
+
import type { Dialect } from './dialect.ts';
|
|
3
|
+
import { type TableDraft } from './draft.ts';
|
|
4
|
+
export declare const alterTableSql: (dialect: Dialect, schema: string, structure: TableStructure, draft: TableDraft) => string[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mysqlAlterSql } from './alter-mysql.js';
|
|
2
|
+
import { sqliteAlterSql } from './alter-sqlite.js';
|
|
3
|
+
import { diffOf } from './diff.js';
|
|
4
|
+
import { draftOf } from './draft.js';
|
|
5
|
+
const unchanged = (diff) => !diff.renamed &&
|
|
6
|
+
!diff.optionsChanged &&
|
|
7
|
+
!diff.primaryKeyChanged &&
|
|
8
|
+
diff.droppedColumns.length === 0 &&
|
|
9
|
+
diff.addedColumns.length === 0 &&
|
|
10
|
+
diff.keptColumns.every((kept) => !kept.renamed && !kept.redefined && !kept.moved) &&
|
|
11
|
+
diff.droppedIndexes.length === 0 &&
|
|
12
|
+
diff.addedIndexes.length === 0 &&
|
|
13
|
+
diff.renamedIndexes.length === 0 &&
|
|
14
|
+
diff.droppedForeignKeys.length === 0 &&
|
|
15
|
+
diff.addedForeignKeys.length === 0;
|
|
16
|
+
/* The fewest statements that turn the table of `structure` into the draft; none when the draft changes nothing. */
|
|
17
|
+
export const alterTableSql = (dialect, schema, structure, draft) => {
|
|
18
|
+
const original = draftOf(structure);
|
|
19
|
+
const diff = diffOf(dialect, original, draft);
|
|
20
|
+
if (unchanged(diff)) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
return dialect.engine === 'sqlite' ? sqliteAlterSql(dialect, schema, original, draft, diff) : mysqlAlterSql(dialect, schema, original, draft, diff);
|
|
24
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Dialect } from './dialect.ts';
|
|
2
|
+
import type { ColumnDraft } from './draft.ts';
|
|
3
|
+
export declare const defaultClause: (expression: string) => string;
|
|
4
|
+
export interface ColumnSqlOptions {
|
|
5
|
+
readonly inlinePrimaryKey?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare const columnSpecSql: (dialect: Dialect, column: ColumnDraft, { inlinePrimaryKey }?: ColumnSqlOptions) => string;
|
|
8
|
+
export declare const columnSql: (dialect: Dialect, column: ColumnDraft, options?: ColumnSqlOptions) => string;
|
|
9
|
+
export declare const sameDefinition: (dialect: Dialect, left: ColumnDraft, right: ColumnDraft) => boolean;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { sqlLiteral } from '../sql.js';
|
|
2
|
+
import { quote } from './dialect.js';
|
|
3
|
+
const NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
4
|
+
const STRING = /^'(?:[^']|'')*'$/;
|
|
5
|
+
const KEYWORD = /^(?:NULL|TRUE|FALSE|CURRENT_DATE|CURRENT_TIME(?:\(\d*\))?|CURRENT_TIMESTAMP(?:\(\d*\))?|LOCALTIME(?:STAMP)?(?:\(\d*\))?|NOW\(\d*\))$/i;
|
|
6
|
+
/*
|
|
7
|
+
* The text after `DEFAULT`. A literal and the few keywords both engines take stand as written; any
|
|
8
|
+
* other expression goes in parentheses, which SQLite and MySQL 8 need for one that is not constant.
|
|
9
|
+
*/
|
|
10
|
+
export const defaultClause = (expression) => {
|
|
11
|
+
const text = expression.trim();
|
|
12
|
+
if (NUMBER.test(text) || STRING.test(text) || KEYWORD.test(text)) {
|
|
13
|
+
return text;
|
|
14
|
+
}
|
|
15
|
+
return text.startsWith('(') && text.endsWith(')') ? text : `(${text})`;
|
|
16
|
+
};
|
|
17
|
+
/* A column's definition without its name: `varchar(255) NOT NULL DEFAULT 'x'`. */
|
|
18
|
+
export const columnSpecSql = (dialect, column, { inlinePrimaryKey = false } = {}) => {
|
|
19
|
+
const mysql = dialect.engine === 'mysql';
|
|
20
|
+
const parts = [];
|
|
21
|
+
if (column.type !== '') {
|
|
22
|
+
parts.push(column.type);
|
|
23
|
+
}
|
|
24
|
+
if (column.generated && column.generatedClause !== null) {
|
|
25
|
+
parts.push(column.generatedClause);
|
|
26
|
+
}
|
|
27
|
+
if (inlinePrimaryKey) {
|
|
28
|
+
parts.push(column.autoIncrement ? 'PRIMARY KEY AUTOINCREMENT' : 'PRIMARY KEY');
|
|
29
|
+
}
|
|
30
|
+
// MariaDB takes neither NULL nor NOT NULL after the expression of a computed column.
|
|
31
|
+
if (column.generated && dialect.flavor === 'mariadb') {
|
|
32
|
+
// The expression says everything.
|
|
33
|
+
}
|
|
34
|
+
else if (!column.nullable) {
|
|
35
|
+
parts.push('NOT NULL');
|
|
36
|
+
}
|
|
37
|
+
else if (mysql) {
|
|
38
|
+
// MySQL gives a TIMESTAMP column NOT NULL unless it is told otherwise.
|
|
39
|
+
parts.push('NULL');
|
|
40
|
+
}
|
|
41
|
+
if (!column.generated) {
|
|
42
|
+
if (column.defaultValue !== null && column.defaultValue.trim() !== '') {
|
|
43
|
+
parts.push(`DEFAULT ${defaultClause(column.defaultValue)}`);
|
|
44
|
+
}
|
|
45
|
+
if (mysql && column.autoIncrement) {
|
|
46
|
+
parts.push('AUTO_INCREMENT');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (mysql && column.comment !== '') {
|
|
50
|
+
parts.push(`COMMENT ${sqlLiteral('mysql', column.comment)}`);
|
|
51
|
+
}
|
|
52
|
+
return parts.join(' ');
|
|
53
|
+
};
|
|
54
|
+
export const columnSql = (dialect, column, options) => `${quote(dialect, column.name)} ${columnSpecSql(dialect, column, options)}`.trimEnd();
|
|
55
|
+
const typeKey = (type) => type.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
56
|
+
/* Whether two columns are defined alike, whatever their names. SQLite keeps no comments, so they never differ there. */
|
|
57
|
+
export const sameDefinition = (dialect, left, right) => typeKey(left.type) === typeKey(right.type) &&
|
|
58
|
+
left.nullable === right.nullable &&
|
|
59
|
+
(left.defaultValue ?? '').trim() === (right.defaultValue ?? '').trim() &&
|
|
60
|
+
left.autoIncrement === right.autoIncrement &&
|
|
61
|
+
(dialect.engine === 'sqlite' || left.comment === right.comment) &&
|
|
62
|
+
left.generatedClause === right.generatedClause;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Dialect } from './dialect.ts';
|
|
2
|
+
import { type ForeignKeyDraft, type IndexDraft, type TableDraft } from './draft.ts';
|
|
3
|
+
import type { TableOptions } from './parse.ts';
|
|
4
|
+
export declare const foreignKeySql: (dialect: Dialect, schema: string, foreignKey: ForeignKeyDraft) => string;
|
|
5
|
+
export declare const mysqlOptionsSql: (options: TableOptions, against?: TableOptions) => string[];
|
|
6
|
+
export declare const createIndexSql: (dialect: Dialect, schema: string, table: string, index: Pick<IndexDraft, 'name' | 'columns' | 'unique'>) => string;
|
|
7
|
+
export interface CreateParts {
|
|
8
|
+
readonly table: string;
|
|
9
|
+
readonly indexes: readonly IndexDraft[];
|
|
10
|
+
}
|
|
11
|
+
export declare const createTableParts: (dialect: Dialect, schema: string, draft: TableDraft, name: string) => CreateParts;
|
|
12
|
+
export declare const createTableSql: (dialect: Dialect, schema: string, draft: TableDraft) => string[];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { sqlLiteral } from '../sql.js';
|
|
2
|
+
import { columnSql } from './column.js';
|
|
3
|
+
import { columnList, quote, sqliteSchemaPrefix, tableName } from './dialect.js';
|
|
4
|
+
import { isAutoIndex } from './draft.js';
|
|
5
|
+
const INDENT = ' ';
|
|
6
|
+
/* `CONSTRAINT name FOREIGN KEY (...) REFERENCES t (...) ON UPDATE .. ON DELETE ..`, shared by a create and an `ADD`. */
|
|
7
|
+
export const foreignKeySql = (dialect, schema, foreignKey) => {
|
|
8
|
+
const sqlite = dialect.engine === 'sqlite';
|
|
9
|
+
// SQLite cannot name another schema in a reference, and MySQL needs one only for another database.
|
|
10
|
+
const target = sqlite || foreignKey.referencedSchema === '' || foreignKey.referencedSchema === schema
|
|
11
|
+
? quote(dialect, foreignKey.referencedTable)
|
|
12
|
+
: `${quote(dialect, foreignKey.referencedSchema)}.${quote(dialect, foreignKey.referencedTable)}`;
|
|
13
|
+
const parts = [
|
|
14
|
+
...(foreignKey.name === '' ? [] : [`CONSTRAINT ${quote(dialect, foreignKey.name)}`]),
|
|
15
|
+
`FOREIGN KEY (${columnList(dialect, foreignKey.columns)})`,
|
|
16
|
+
`REFERENCES ${target} (${columnList(dialect, foreignKey.referencedColumns)})`
|
|
17
|
+
];
|
|
18
|
+
if (foreignKey.onUpdate !== null) {
|
|
19
|
+
parts.push(`ON UPDATE ${foreignKey.onUpdate}`);
|
|
20
|
+
}
|
|
21
|
+
if (foreignKey.onDelete !== null) {
|
|
22
|
+
parts.push(`ON DELETE ${foreignKey.onDelete}`);
|
|
23
|
+
}
|
|
24
|
+
return parts.join(' ');
|
|
25
|
+
};
|
|
26
|
+
/* `ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ...`: the options that say something, and with `against` only those that differ from it. */
|
|
27
|
+
export const mysqlOptionsSql = (options, against) => {
|
|
28
|
+
// Only a comment can be emptied; an engine, a character set or a collation has no empty value to ask for.
|
|
29
|
+
const changed = (field) => options[field] !== against?.[field] && (options[field] !== '' || (field === 'comment' && against !== undefined));
|
|
30
|
+
return [
|
|
31
|
+
...(changed('engine') ? [`ENGINE=${options.engine}`] : []),
|
|
32
|
+
...(changed('charset') ? [`DEFAULT CHARSET=${options.charset}`] : []),
|
|
33
|
+
...(changed('collation') ? [`COLLATE=${options.collation}`] : []),
|
|
34
|
+
...(changed('comment') ? [`COMMENT=${sqlLiteral('mysql', options.comment)}`] : [])
|
|
35
|
+
];
|
|
36
|
+
};
|
|
37
|
+
const sqliteOptionsSql = (options) => [...(options.strict ? ['STRICT'] : []), ...(options.withoutRowid ? ['WITHOUT ROWID'] : [])];
|
|
38
|
+
const inlineIndexSql = (dialect, index) => [index.unique ? 'UNIQUE INDEX' : 'INDEX', ...(index.name === '' ? [] : [quote(dialect, index.name)]), `(${columnList(dialect, index.columns)})`].join(' ');
|
|
39
|
+
/* `CREATE [UNIQUE] INDEX name ON table (columns)`. */
|
|
40
|
+
export const createIndexSql = (dialect, schema, table, index) => `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${dialect.engine === 'sqlite' ? sqliteSchemaPrefix(dialect, schema) : ''}${quote(dialect, index.name)} ON ${tableName(dialect, dialect.engine === 'sqlite' ? '' : schema, table)} (${columnList(dialect, index.columns)})`;
|
|
41
|
+
/* The `CREATE TABLE` of a draft under a name of the caller's choosing, which a rebuild uses for the table it fills before the swap. */
|
|
42
|
+
export const createTableParts = (dialect, schema, draft, name) => {
|
|
43
|
+
const sqlite = dialect.engine === 'sqlite';
|
|
44
|
+
// SQLite declares AUTOINCREMENT on the column itself, which then is the whole primary key; a WITHOUT ROWID table has no rowid to count.
|
|
45
|
+
const autoColumn = sqlite && draft.primaryKey.length === 1 && !draft.options.withoutRowid
|
|
46
|
+
? draft.columns.find((column) => column.name === draft.primaryKey[0] && column.autoIncrement)?.name
|
|
47
|
+
: undefined;
|
|
48
|
+
const lines = draft.columns.map((column) => columnSql(dialect, column, { inlinePrimaryKey: column.name === autoColumn }));
|
|
49
|
+
if (draft.primaryKey.length > 0 && autoColumn === undefined) {
|
|
50
|
+
lines.push(`PRIMARY KEY (${columnList(dialect, draft.primaryKey)})`);
|
|
51
|
+
}
|
|
52
|
+
const separate = [];
|
|
53
|
+
for (const index of draft.indexes) {
|
|
54
|
+
if (!sqlite) {
|
|
55
|
+
lines.push(inlineIndexSql(dialect, index));
|
|
56
|
+
}
|
|
57
|
+
else if (isAutoIndex(index.name) && index.unique) {
|
|
58
|
+
lines.push(`UNIQUE (${columnList(dialect, index.columns)})`);
|
|
59
|
+
}
|
|
60
|
+
else if (!isAutoIndex(index.name)) {
|
|
61
|
+
separate.push(index);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const foreignKey of draft.foreignKeys) {
|
|
65
|
+
lines.push(foreignKeySql(dialect, schema, foreignKey));
|
|
66
|
+
}
|
|
67
|
+
const options = sqlite ? sqliteOptionsSql(draft.options).join(', ') : mysqlOptionsSql(draft.options).join(' ');
|
|
68
|
+
const head = `CREATE TABLE ${tableName(dialect, schema, name)} (\n${lines.map((line) => INDENT + line).join(',\n')}\n)`;
|
|
69
|
+
return { table: options === '' ? head : `${head} ${options}`, indexes: separate };
|
|
70
|
+
};
|
|
71
|
+
export const createTableSql = (dialect, schema, draft) => {
|
|
72
|
+
const { table, indexes } = createTableParts(dialect, schema, draft, draft.name);
|
|
73
|
+
return [table, ...indexes.map((index) => createIndexSql(dialect, schema, draft.name, index))];
|
|
74
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Engine, ServerInfo } from '../protocol/index.ts';
|
|
2
|
+
export interface Dialect {
|
|
3
|
+
readonly flavor: ServerInfo['flavor'];
|
|
4
|
+
readonly engine: Engine;
|
|
5
|
+
readonly version: readonly number[];
|
|
6
|
+
}
|
|
7
|
+
export declare const dialectOf: (server: ServerInfo) => Dialect;
|
|
8
|
+
export declare const atLeast: (dialect: Dialect, ...wanted: number[]) => boolean;
|
|
9
|
+
export declare const quote: (dialect: Dialect, name: string) => string;
|
|
10
|
+
export declare const tableName: (dialect: Dialect, schema: string, table: string) => string;
|
|
11
|
+
export declare const sqliteSchemaPrefix: (dialect: Dialect, schema: string) => string;
|
|
12
|
+
export declare const columnList: (dialect: Dialect, columns: readonly string[]) => string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { qualifiedName, quoteIdentifier } from '../sql.js';
|
|
2
|
+
const VERSION = /(\d+(?:\.\d+)*)/;
|
|
3
|
+
export const dialectOf = (server) => ({
|
|
4
|
+
flavor: server.flavor,
|
|
5
|
+
engine: server.flavor === 'sqlite' ? 'sqlite' : 'mysql',
|
|
6
|
+
version: (VERSION.exec(server.version)?.[1] ?? '')
|
|
7
|
+
.split('.')
|
|
8
|
+
.filter((part) => part !== '')
|
|
9
|
+
.map(Number)
|
|
10
|
+
});
|
|
11
|
+
/* Whether the server is at least this version. A version nobody could read counts as new enough. */
|
|
12
|
+
export const atLeast = (dialect, ...wanted) => {
|
|
13
|
+
if (dialect.version.length === 0) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
for (let i = 0; i < wanted.length; i++) {
|
|
17
|
+
const have = dialect.version[i] ?? 0;
|
|
18
|
+
if (have !== wanted[i]) {
|
|
19
|
+
return have > wanted[i];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
};
|
|
24
|
+
export const quote = (dialect, name) => quoteIdentifier(dialect.engine, name);
|
|
25
|
+
/* SQLite reads an unqualified name as `main`, so only an attached schema is spelled out. */
|
|
26
|
+
const schemaOf = (dialect, schema) => schema === '' || (dialect.engine === 'sqlite' && schema === 'main') ? undefined : schema;
|
|
27
|
+
/* A table with its schema in front, when the statement needs one. */
|
|
28
|
+
export const tableName = (dialect, schema, table) => qualifiedName({ engine: dialect.engine, table, schema: schemaOf(dialect, schema) });
|
|
29
|
+
/* SQLite puts the schema in front of the name of an index, where MySQL names the table it is on. */
|
|
30
|
+
export const sqliteSchemaPrefix = (dialect, schema) => {
|
|
31
|
+
const named = schemaOf(dialect, schema);
|
|
32
|
+
return named === undefined ? '' : `${quote(dialect, named)}.`;
|
|
33
|
+
};
|
|
34
|
+
export const columnList = (dialect, columns) => columns.map((column) => quote(dialect, column)).join(', ');
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Dialect } from './dialect.ts';
|
|
2
|
+
import type { ColumnDraft, ForeignKeyDraft, IndexDraft, TableDraft } from './draft.ts';
|
|
3
|
+
export interface KeptColumn {
|
|
4
|
+
readonly original: ColumnDraft;
|
|
5
|
+
readonly column: ColumnDraft;
|
|
6
|
+
readonly renamed: boolean;
|
|
7
|
+
readonly redefined: boolean;
|
|
8
|
+
readonly moved: boolean;
|
|
9
|
+
readonly after: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface AddedColumn {
|
|
12
|
+
readonly column: ColumnDraft;
|
|
13
|
+
readonly placed: boolean;
|
|
14
|
+
readonly after: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface RenamedIndex {
|
|
17
|
+
readonly from: IndexDraft;
|
|
18
|
+
readonly to: IndexDraft;
|
|
19
|
+
}
|
|
20
|
+
export interface TableDiff {
|
|
21
|
+
readonly renamed: boolean;
|
|
22
|
+
readonly droppedColumns: readonly ColumnDraft[];
|
|
23
|
+
readonly keptColumns: readonly KeptColumn[];
|
|
24
|
+
readonly addedColumns: readonly AddedColumn[];
|
|
25
|
+
readonly primaryKeyChanged: boolean;
|
|
26
|
+
readonly droppedIndexes: readonly IndexDraft[];
|
|
27
|
+
readonly addedIndexes: readonly IndexDraft[];
|
|
28
|
+
readonly renamedIndexes: readonly RenamedIndex[];
|
|
29
|
+
readonly droppedForeignKeys: readonly ForeignKeyDraft[];
|
|
30
|
+
readonly addedForeignKeys: readonly ForeignKeyDraft[];
|
|
31
|
+
readonly optionsChanged: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare const diffOf: (dialect: Dialect, original: TableDraft, draft: TableDraft) => TableDiff;
|
|
34
|
+
export declare const dropsColumns: (original: TableDraft, draft: TableDraft) => boolean;
|
package/dist/ddl/diff.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { sameDefinition } from './column.js';
|
|
2
|
+
const sameList = (left, right) => left.length === right.length && left.every((name, at) => name === right[at]);
|
|
3
|
+
/* The positions of a longest run that already is in order: those columns can stay where they are. */
|
|
4
|
+
const longestRun = (positions) => {
|
|
5
|
+
const length = [];
|
|
6
|
+
const previous = [];
|
|
7
|
+
let best = -1;
|
|
8
|
+
for (let i = 0; i < positions.length; i++) {
|
|
9
|
+
length[i] = 1;
|
|
10
|
+
previous[i] = -1;
|
|
11
|
+
for (let before = 0; before < i; before++) {
|
|
12
|
+
if (positions[before] < positions[i] && length[before] + 1 > length[i]) {
|
|
13
|
+
length[i] = length[before] + 1;
|
|
14
|
+
previous[i] = before;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (best < 0 || length[i] > length[best]) {
|
|
18
|
+
best = i;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const run = new Set();
|
|
22
|
+
for (let at = best; at >= 0; at = previous[at]) {
|
|
23
|
+
run.add(at);
|
|
24
|
+
}
|
|
25
|
+
return run;
|
|
26
|
+
};
|
|
27
|
+
const OPTION_FIELDS = ['engine', 'charset', 'collation', 'comment', 'withoutRowid', 'strict'];
|
|
28
|
+
const matches = (original, draft) => draft.originalName !== null && (original.name === '' ? draft.key === original.key : draft.originalName === original.name);
|
|
29
|
+
/* What a draft changes in the table it came from, told apart by the names items kept. */
|
|
30
|
+
export const diffOf = (dialect, original, draft) => {
|
|
31
|
+
const survivors = draft.columns.filter((column) => column.originalName !== null);
|
|
32
|
+
const renames = new Map(survivors.map((column) => [column.originalName, column.name]));
|
|
33
|
+
const project = (names) => names.map((name) => renames.get(name) ?? name);
|
|
34
|
+
const originalOf = (name) => original.columns.find((column) => column.name === name);
|
|
35
|
+
const positions = survivors.map((column) => original.columns.findIndex((candidate) => candidate.name === column.originalName));
|
|
36
|
+
const staying = longestRun(positions);
|
|
37
|
+
let survivor = 0;
|
|
38
|
+
const keptColumns = [];
|
|
39
|
+
const addedColumns = [];
|
|
40
|
+
draft.columns.forEach((column, at) => {
|
|
41
|
+
const after = at === 0 ? null : draft.columns[at - 1].name;
|
|
42
|
+
if (column.originalName === null) {
|
|
43
|
+
addedColumns.push({ column, placed: draft.columns.slice(at + 1).some((later) => later.originalName !== null), after });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const rank = survivor++;
|
|
47
|
+
const before = originalOf(column.originalName);
|
|
48
|
+
if (before === undefined) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
keptColumns.push({
|
|
52
|
+
original: before,
|
|
53
|
+
column,
|
|
54
|
+
renamed: before.name !== column.name,
|
|
55
|
+
redefined: !sameDefinition(dialect, before, column),
|
|
56
|
+
moved: !staying.has(rank),
|
|
57
|
+
after
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
const droppedColumns = original.columns.filter((column) => !survivors.some((kept) => kept.originalName === column.name));
|
|
61
|
+
const droppedIndexes = [];
|
|
62
|
+
const addedIndexes = draft.indexes.filter((index) => index.originalName === null);
|
|
63
|
+
const renamedIndexes = [];
|
|
64
|
+
for (const before of original.indexes) {
|
|
65
|
+
const after = draft.indexes.find((index) => matches(before, index));
|
|
66
|
+
if (after === undefined) {
|
|
67
|
+
droppedIndexes.push(before);
|
|
68
|
+
}
|
|
69
|
+
else if (!sameList(project(before.columns), after.columns) || before.unique !== after.unique) {
|
|
70
|
+
droppedIndexes.push(before);
|
|
71
|
+
addedIndexes.push(after);
|
|
72
|
+
}
|
|
73
|
+
else if (before.name !== after.name) {
|
|
74
|
+
renamedIndexes.push({ from: before, to: after });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const droppedForeignKeys = [];
|
|
78
|
+
const addedForeignKeys = draft.foreignKeys.filter((key) => key.originalName === null);
|
|
79
|
+
for (const before of original.foreignKeys) {
|
|
80
|
+
const after = draft.foreignKeys.find((key) => matches(before, key));
|
|
81
|
+
const same = after !== undefined &&
|
|
82
|
+
before.name === after.name &&
|
|
83
|
+
sameList(project(before.columns), after.columns) &&
|
|
84
|
+
before.referencedSchema === after.referencedSchema &&
|
|
85
|
+
before.referencedTable === after.referencedTable &&
|
|
86
|
+
sameList(before.referencedColumns, after.referencedColumns) &&
|
|
87
|
+
before.onUpdate === after.onUpdate &&
|
|
88
|
+
before.onDelete === after.onDelete;
|
|
89
|
+
if (!same) {
|
|
90
|
+
droppedForeignKeys.push(before);
|
|
91
|
+
if (after !== undefined) {
|
|
92
|
+
addedForeignKeys.push(after);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
renamed: original.name !== draft.name,
|
|
98
|
+
droppedColumns,
|
|
99
|
+
keptColumns,
|
|
100
|
+
addedColumns,
|
|
101
|
+
primaryKeyChanged: !sameList(project(original.primaryKey), draft.primaryKey),
|
|
102
|
+
droppedIndexes,
|
|
103
|
+
addedIndexes,
|
|
104
|
+
renamedIndexes,
|
|
105
|
+
droppedForeignKeys,
|
|
106
|
+
addedForeignKeys,
|
|
107
|
+
optionsChanged: OPTION_FIELDS.some((field) => original.options[field] !== draft.options[field])
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
/* Whether applying the draft removes a column, and with it the data in it. */
|
|
111
|
+
export const dropsColumns = (original, draft) => original.columns.some((column) => !draft.columns.some((kept) => kept.originalName === column.name));
|