mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-06 15:03:22 +00:00
Compare commits
19 Commits
v1.7.0
...
jf/wrong_i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
595e3db0b3 | ||
|
|
ab89bad6d5 | ||
|
|
deb218423f | ||
|
|
48342471ac | ||
|
|
47bb87a88f | ||
|
|
a96c2e1078 | ||
|
|
26d95eed25 | ||
|
|
be65328f24 | ||
|
|
85fd14fa02 | ||
|
|
9c485b3b01 | ||
|
|
e993f1549c | ||
|
|
0db67ea42a | ||
|
|
b9e621bd68 | ||
|
|
93d59f8887 | ||
|
|
190e4f4ffa | ||
|
|
dc404c9d7e | ||
|
|
dd4324d64f | ||
|
|
1878083056 | ||
|
|
7b6271962a |
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,5 +1,29 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.8.0](https://github.com/chartdb/chartdb/compare/v1.7.0...v1.8.0) (2025-02-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **dbml-import:** add error highlighting for dbml imports ([#556](https://github.com/chartdb/chartdb/issues/556)) ([190e4f4](https://github.com/chartdb/chartdb/commit/190e4f4ffa834fa621f264dc608ca3f3b393a331))
|
||||||
|
* **docker image:** add support for custom inference servers ([#543](https://github.com/chartdb/chartdb/issues/543)) ([1878083](https://github.com/chartdb/chartdb/commit/1878083056ea4db7a05cdeeb38a4f7b9f5f95bd1))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **canvas:** add right-click option to create relationships ([#568](https://github.com/chartdb/chartdb/issues/568)) ([e993f15](https://github.com/chartdb/chartdb/commit/e993f1549c4c86bb9e7e36062db803ba6613b3b3))
|
||||||
|
* **canvas:** locate table from canvas ([#560](https://github.com/chartdb/chartdb/issues/560)) ([dc404c9](https://github.com/chartdb/chartdb/commit/dc404c9d7ee272c93aac69646bac859829a5234e))
|
||||||
|
* **docker:** add option to hide popups ([#580](https://github.com/chartdb/chartdb/issues/580)) ([a96c2e1](https://github.com/chartdb/chartdb/commit/a96c2e107838d2dc13b586923fd9dbe06598cdd8))
|
||||||
|
* **export-sql:** show create script for only filtered schemas ([#570](https://github.com/chartdb/chartdb/issues/570)) ([85fd14f](https://github.com/chartdb/chartdb/commit/85fd14fa02bb2879c36bba53369dbf2e7fa578d4))
|
||||||
|
* **i18n:** fix Ukrainian ([#554](https://github.com/chartdb/chartdb/issues/554)) ([7b62719](https://github.com/chartdb/chartdb/commit/7b6271962a99bfe5ffbd0176e714c76368ef5c41))
|
||||||
|
* **import dbml:** add import for indexes ([#566](https://github.com/chartdb/chartdb/issues/566)) ([0db67ea](https://github.com/chartdb/chartdb/commit/0db67ea42a5f9585ca1d246db7a7ff0239bec0ba))
|
||||||
|
* **import-query:** improve the cleanup for messy json input ([#562](https://github.com/chartdb/chartdb/issues/562)) ([93d59f8](https://github.com/chartdb/chartdb/commit/93d59f8887765098d040a3184aaee32112f67267))
|
||||||
|
* **index unique:** extract unique toggle for faster editing ([#559](https://github.com/chartdb/chartdb/issues/559)) ([dd4324d](https://github.com/chartdb/chartdb/commit/dd4324d64f7638ada5c022a2ab38bd8e6986af25))
|
||||||
|
* **mssql-import:** improve script readability by adding edition comment ([#572](https://github.com/chartdb/chartdb/issues/572)) ([be65328](https://github.com/chartdb/chartdb/commit/be65328f24b0361638b9e2edb39eaa9906e77f67))
|
||||||
|
* **realtionships section:** add the schema to source/target tables ([#561](https://github.com/chartdb/chartdb/issues/561)) ([b9e621b](https://github.com/chartdb/chartdb/commit/b9e621bd680730a0ffbf1054d735bfa418711cae))
|
||||||
|
* **sqlserver-import:** open ssms guide when max chars ([#565](https://github.com/chartdb/chartdb/issues/565)) ([9c485b3](https://github.com/chartdb/chartdb/commit/9c485b3b01a131bf551c7e95916b0c416f6aa0b5))
|
||||||
|
* **table actions:** fix size of table actions ([#578](https://github.com/chartdb/chartdb/issues/578)) ([26d95ee](https://github.com/chartdb/chartdb/commit/26d95eed25d86452d9168a9d93a301ba50d934e3))
|
||||||
|
|
||||||
## [1.7.0](https://github.com/chartdb/chartdb/compare/v1.6.1...v1.7.0) (2025-02-03)
|
## [1.7.0](https://github.com/chartdb/chartdb/compare/v1.6.1...v1.7.0) (2025-02-03)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@@ -1,6 +1,9 @@
|
|||||||
FROM node:22-alpine AS builder
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
ARG VITE_OPENAI_API_KEY
|
ARG VITE_OPENAI_API_KEY
|
||||||
|
ARG VITE_OPENAI_API_ENDPOINT
|
||||||
|
ARG VITE_LLM_MODEL_NAME
|
||||||
|
ARG VITE_HIDE_BUCKLE_DOT_DEV
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
@@ -10,9 +13,13 @@ RUN npm ci
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
RUN echo "VITE_OPENAI_API_KEY=${VITE_OPENAI_API_KEY}" > .env && \
|
||||||
|
echo "VITE_OPENAI_API_ENDPOINT=${VITE_OPENAI_API_ENDPOINT}" >> .env && \
|
||||||
|
echo "VITE_LLM_MODEL_NAME=${VITE_LLM_MODEL_NAME}" >> .env && \
|
||||||
|
echo "VITE_HIDE_BUCKLE_DOT_DEV=${VITE_HIDE_BUCKLE_DOT_DEV}" >> .env
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Use a lightweight web server to serve the production build
|
|
||||||
FROM nginx:stable-alpine AS production
|
FROM nginx:stable-alpine AS production
|
||||||
|
|
||||||
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
|
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
|
||||||
@@ -20,7 +27,6 @@ COPY ./default.conf.template /etc/nginx/conf.d/default.conf.template
|
|||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
# Expose the default port for the Nginx web server
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
25
README.md
25
README.md
@@ -107,8 +107,33 @@ docker build -t chartdb .
|
|||||||
docker run -e OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -p 8080:80 chartdb
|
docker run -e OPENAI_API_KEY=<YOUR_OPEN_AI_KEY> -p 8080:80 chartdb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Using Custom Inference Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build
|
||||||
|
docker build \
|
||||||
|
--build-arg VITE_OPENAI_API_ENDPOINT=<YOUR_ENDPOINT> \
|
||||||
|
--build-arg VITE_LLM_MODEL_NAME=<YOUR_MODEL_NAME> \
|
||||||
|
-t chartdb .
|
||||||
|
|
||||||
|
# Run
|
||||||
|
docker run \
|
||||||
|
-e OPENAI_API_ENDPOINT=<YOUR_ENDPOINT> \
|
||||||
|
-e LLM_MODEL_NAME=<YOUR_MODEL_NAME> \
|
||||||
|
-p 8080:80 chartdb
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** You must configure either Option 1 (OpenAI API key) OR Option 2 (Custom endpoint and model name) for AI capabilities to work. Do not mix the two options.
|
||||||
|
|
||||||
Open your browser and navigate to `http://localhost:8080`.
|
Open your browser and navigate to `http://localhost:8080`.
|
||||||
|
|
||||||
|
Example configuration for a local vLLM server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VITE_OPENAI_API_ENDPOINT=http://localhost:8000/v1
|
||||||
|
VITE_LLM_MODEL_NAME=Qwen/Qwen2.5-32B-Instruct-AWQ
|
||||||
|
```
|
||||||
|
|
||||||
## Try it on our website
|
## Try it on our website
|
||||||
|
|
||||||
1. Go to [ChartDB.io](https://chartdb.io?ref=github_readme_2)
|
1. Go to [ChartDB.io](https://chartdb.io?ref=github_readme_2)
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ server {
|
|||||||
|
|
||||||
location /config.js {
|
location /config.js {
|
||||||
default_type application/javascript;
|
default_type application/javascript;
|
||||||
return 200 "window.env = { OPENAI_API_KEY: \"$OPENAI_API_KEY\" };";
|
return 200 "window.env = {
|
||||||
|
OPENAI_API_KEY: \"$OPENAI_API_KEY\",
|
||||||
|
OPENAI_API_ENDPOINT: \"$OPENAI_API_ENDPOINT\",
|
||||||
|
LLM_MODEL_NAME: \"$LLM_MODEL_NAME\",
|
||||||
|
HIDE_BUCKLE_DOT_DEV: \"$HIDE_BUCKLE_DOT_DEV\"
|
||||||
|
};";
|
||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Replace placeholders in nginx.conf
|
# Replace placeholders in nginx.conf
|
||||||
envsubst '${OPENAI_API_KEY}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
|
envsubst '${OPENAI_API_KEY} ${OPENAI_API_ENDPOINT} ${LLM_MODEL_NAME} ${HIDE_BUCKLE_DOT_DEV}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
# Start Nginx
|
# Start Nginx
|
||||||
nginx -g "daemon off;"
|
nginx -g "daemon off;"
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "chartdb",
|
"name": "chartdb",
|
||||||
"version": "1.7.0",
|
"version": "1.8.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "chartdb",
|
"name": "chartdb",
|
||||||
"version": "1.7.0",
|
"version": "1.8.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^0.0.51",
|
"@ai-sdk/openai": "^0.0.51",
|
||||||
"@dbml/core": "^3.9.5",
|
"@dbml/core": "^3.9.5",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "chartdb",
|
"name": "chartdb",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.7.0",
|
"version": "1.8.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sq
|
|||||||
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
|
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
|
||||||
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
||||||
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
||||||
|
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
||||||
|
import type { ImportDBMLDialogProps } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
|
||||||
|
|
||||||
export interface DialogContext {
|
export interface DialogContext {
|
||||||
// Create diagram dialog
|
// Create diagram dialog
|
||||||
@@ -21,7 +23,9 @@ export interface DialogContext {
|
|||||||
closeExportSQLDialog: () => void;
|
closeExportSQLDialog: () => void;
|
||||||
|
|
||||||
// Create relationship dialog
|
// Create relationship dialog
|
||||||
openCreateRelationshipDialog: () => void;
|
openCreateRelationshipDialog: (
|
||||||
|
params?: Omit<CreateRelationshipDialogProps, 'dialog'>
|
||||||
|
) => void;
|
||||||
closeCreateRelationshipDialog: () => void;
|
closeCreateRelationshipDialog: () => void;
|
||||||
|
|
||||||
// Import database dialog
|
// Import database dialog
|
||||||
@@ -63,7 +67,9 @@ export interface DialogContext {
|
|||||||
closeImportDiagramDialog: () => void;
|
closeImportDiagramDialog: () => void;
|
||||||
|
|
||||||
// Import DBML dialog
|
// Import DBML dialog
|
||||||
openImportDBMLDialog: () => void;
|
openImportDBMLDialog: (
|
||||||
|
params?: Omit<ImportDBMLDialogProps, 'dialog'>
|
||||||
|
) => void;
|
||||||
closeImportDBMLDialog: () => void;
|
closeImportDBMLDialog: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-diagram-di
|
|||||||
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
||||||
import { ExportSQLDialog } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
import { ExportSQLDialog } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
|
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
||||||
import { CreateRelationshipDialog } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
import { CreateRelationshipDialog } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
||||||
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
|
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
|
||||||
import { ImportDatabaseDialog } from '@/dialogs/import-database-dialog/import-database-dialog';
|
import { ImportDatabaseDialog } from '@/dialogs/import-database-dialog/import-database-dialog';
|
||||||
@@ -18,6 +19,7 @@ import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-di
|
|||||||
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
|
||||||
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
|
||||||
import { BuckleDialog } from '@/dialogs/buckle-dialog/buckle-dialog';
|
import { BuckleDialog } from '@/dialogs/buckle-dialog/buckle-dialog';
|
||||||
|
import type { ImportDBMLDialogProps } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
|
||||||
import { ImportDBMLDialog } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
|
import { ImportDBMLDialog } from '@/dialogs/import-dbml-dialog/import-dbml-dialog';
|
||||||
|
|
||||||
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||||
@@ -28,6 +30,17 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
|
|
||||||
const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] =
|
const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const [createRelationshipDialogParams, setCreateRelationshipDialogParams] =
|
||||||
|
useState<Omit<CreateRelationshipDialogProps, 'dialog'>>();
|
||||||
|
const openCreateRelationshipDialogHandler: DialogContext['openCreateRelationshipDialog'] =
|
||||||
|
useCallback(
|
||||||
|
(params) => {
|
||||||
|
setCreateRelationshipDialogParams(params);
|
||||||
|
setOpenCreateRelationshipDialog(true);
|
||||||
|
},
|
||||||
|
[setOpenCreateRelationshipDialog]
|
||||||
|
);
|
||||||
|
|
||||||
const [openStarUsDialog, setOpenStarUsDialog] = useState(false);
|
const [openStarUsDialog, setOpenStarUsDialog] = useState(false);
|
||||||
const [openBuckleDialog, setOpenBuckleDialog] = useState(false);
|
const [openBuckleDialog, setOpenBuckleDialog] = useState(false);
|
||||||
|
|
||||||
@@ -99,6 +112,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
|
|
||||||
// Import DBML dialog
|
// Import DBML dialog
|
||||||
const [openImportDBMLDialog, setOpenImportDBMLDialog] = useState(false);
|
const [openImportDBMLDialog, setOpenImportDBMLDialog] = useState(false);
|
||||||
|
const [importDBMLDialogParams, setImportDBMLDialogParams] =
|
||||||
|
useState<Omit<ImportDBMLDialogProps, 'dialog'>>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialogContext.Provider
|
<dialogContext.Provider
|
||||||
@@ -109,8 +124,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
|
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
|
||||||
openExportSQLDialog: openExportSQLDialogHandler,
|
openExportSQLDialog: openExportSQLDialogHandler,
|
||||||
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
|
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
|
||||||
openCreateRelationshipDialog: () =>
|
openCreateRelationshipDialog:
|
||||||
setOpenCreateRelationshipDialog(true),
|
openCreateRelationshipDialogHandler,
|
||||||
closeCreateRelationshipDialog: () =>
|
closeCreateRelationshipDialog: () =>
|
||||||
setOpenCreateRelationshipDialog(false),
|
setOpenCreateRelationshipDialog(false),
|
||||||
openImportDatabaseDialog: openImportDatabaseDialogHandler,
|
openImportDatabaseDialog: openImportDatabaseDialogHandler,
|
||||||
@@ -130,7 +145,10 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
|
openImportDiagramDialog: () => setOpenImportDiagramDialog(true),
|
||||||
closeImportDiagramDialog: () =>
|
closeImportDiagramDialog: () =>
|
||||||
setOpenImportDiagramDialog(false),
|
setOpenImportDiagramDialog(false),
|
||||||
openImportDBMLDialog: () => setOpenImportDBMLDialog(true),
|
openImportDBMLDialog: (params) => {
|
||||||
|
setImportDBMLDialogParams(params);
|
||||||
|
setOpenImportDBMLDialog(true);
|
||||||
|
},
|
||||||
closeImportDBMLDialog: () => setOpenImportDBMLDialog(false),
|
closeImportDBMLDialog: () => setOpenImportDBMLDialog(false),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -143,6 +161,7 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
/>
|
/>
|
||||||
<CreateRelationshipDialog
|
<CreateRelationshipDialog
|
||||||
dialog={{ open: openCreateRelationshipDialog }}
|
dialog={{ open: openCreateRelationshipDialog }}
|
||||||
|
{...createRelationshipDialogParams}
|
||||||
/>
|
/>
|
||||||
<ImportDatabaseDialog
|
<ImportDatabaseDialog
|
||||||
dialog={{ open: openImportDatabaseDialog }}
|
dialog={{ open: openImportDatabaseDialog }}
|
||||||
@@ -160,7 +179,10 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
|
<ExportDiagramDialog dialog={{ open: openExportDiagramDialog }} />
|
||||||
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
|
<ImportDiagramDialog dialog={{ open: openImportDiagramDialog }} />
|
||||||
<BuckleDialog dialog={{ open: openBuckleDialog }} />
|
<BuckleDialog dialog={{ open: openBuckleDialog }} />
|
||||||
<ImportDBMLDialog dialog={{ open: openImportDBMLDialog }} />
|
<ImportDBMLDialog
|
||||||
|
dialog={{ open: openImportDBMLDialog }}
|
||||||
|
{...importDBMLDialogParams}
|
||||||
|
/>
|
||||||
</dialogContext.Provider>
|
</dialogContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
const [showCheckJsonButton, setShowCheckJsonButton] = useState(false);
|
const [showCheckJsonButton, setShowCheckJsonButton] = useState(false);
|
||||||
const [isCheckingJson, setIsCheckingJson] = useState(false);
|
const [isCheckingJson, setIsCheckingJson] = useState(false);
|
||||||
|
|
||||||
|
const [showSSMSInfoDialog, setShowSSMSInfoDialog] = useState(false);
|
||||||
|
|
||||||
|
const helpButtonRef = React.useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadScripts = async () => {
|
const loadScripts = async () => {
|
||||||
const { importMetadataScripts } = await import(
|
const { importMetadataScripts } = await import(
|
||||||
@@ -127,6 +131,16 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
const inputValue = e.target.value;
|
const inputValue = e.target.value;
|
||||||
setScriptResult(inputValue);
|
setScriptResult(inputValue);
|
||||||
|
|
||||||
|
// Automatically open SSMS info when input length is exactly 65535
|
||||||
|
if (inputValue.length === 65535) {
|
||||||
|
setShowSSMSInfoDialog(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show instructions when input contains "WITH fk_info as"
|
||||||
|
if (inputValue.toLowerCase().includes('with fk_info as')) {
|
||||||
|
helpButtonRef.current?.click();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[setScriptResult]
|
[setScriptResult]
|
||||||
);
|
);
|
||||||
@@ -245,7 +259,10 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
{t('new_diagram_dialog.import_database.step_1')}
|
{t('new_diagram_dialog.import_database.step_1')}
|
||||||
</div>
|
</div>
|
||||||
{databaseType === DatabaseType.SQL_SERVER && (
|
{databaseType === DatabaseType.SQL_SERVER && (
|
||||||
<SSMSInfo />
|
<SSMSInfo
|
||||||
|
open={showSSMSInfoDialog}
|
||||||
|
setOpen={setShowSSMSInfoDialog}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{databaseTypeToClientsMap[databaseType].length > 0 ? (
|
{databaseTypeToClientsMap[databaseType].length > 0 ? (
|
||||||
@@ -369,6 +386,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
showCheckJsonButton,
|
showCheckJsonButton,
|
||||||
isCheckingJson,
|
isCheckingJson,
|
||||||
handleCheckJson,
|
handleCheckJson,
|
||||||
|
showSSMSInfoDialog,
|
||||||
|
setShowSSMSInfoDialog,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const renderFooter = useCallback(() => {
|
const renderFooter = useCallback(() => {
|
||||||
@@ -386,7 +405,11 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
)}
|
)}
|
||||||
{isDesktop ? (
|
{isDesktop ? (
|
||||||
<ZoomableImage src="/load-new-db-instructions.gif">
|
<ZoomableImage src="/load-new-db-instructions.gif">
|
||||||
<Button type="button" variant="link">
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="link"
|
||||||
|
ref={helpButtonRef}
|
||||||
|
>
|
||||||
{t(
|
{t(
|
||||||
'new_diagram_dialog.import_database.instructions_link'
|
'new_diagram_dialog.import_database.instructions_link'
|
||||||
)}
|
)}
|
||||||
@@ -438,7 +461,11 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
|||||||
|
|
||||||
{!isDesktop ? (
|
{!isDesktop ? (
|
||||||
<ZoomableImage src="/load-new-db-instructions.gif">
|
<ZoomableImage src="/load-new-db-instructions.gif">
|
||||||
<Button type="button" variant="link">
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="link"
|
||||||
|
ref={helpButtonRef}
|
||||||
|
>
|
||||||
{t(
|
{t(
|
||||||
'new_diagram_dialog.import_database.instructions_link'
|
'new_diagram_dialog.import_database.instructions_link'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,32 +4,55 @@ import {
|
|||||||
HoverCardTrigger,
|
HoverCardTrigger,
|
||||||
} from '@/components/hover-card/hover-card';
|
} from '@/components/hover-card/hover-card';
|
||||||
import { Label } from '@/components/label/label';
|
import { Label } from '@/components/label/label';
|
||||||
import { Info } from 'lucide-react';
|
import { Info, X } from 'lucide-react';
|
||||||
import React from 'react';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
import SSMSInstructions from '@/assets/ssms-instructions.png';
|
import SSMSInstructions from '@/assets/ssms-instructions.png';
|
||||||
import { ZoomableImage } from '@/components/zoomable-image/zoomable-image';
|
import { ZoomableImage } from '@/components/zoomable-image/zoomable-image';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export interface SSMSInfoProps {}
|
export interface SSMSInfoProps {
|
||||||
|
open?: boolean;
|
||||||
|
setOpen?: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export const SSMSInfo = React.forwardRef<
|
export const SSMSInfo = React.forwardRef<
|
||||||
React.ElementRef<typeof HoverCardTrigger>,
|
React.ElementRef<typeof HoverCardTrigger>,
|
||||||
SSMSInfoProps
|
SSMSInfoProps
|
||||||
>((props, ref) => {
|
>(({ open: controlledOpen, setOpen: setControlledOpen }, ref) => {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (controlledOpen) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
}, [controlledOpen]);
|
||||||
|
|
||||||
|
const closeHandler = useCallback(() => {
|
||||||
|
setOpen(false);
|
||||||
|
setControlledOpen?.(false);
|
||||||
|
}, [setControlledOpen]);
|
||||||
|
|
||||||
|
const isOpen = useMemo(
|
||||||
|
() => open || controlledOpen,
|
||||||
|
[open, controlledOpen]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard
|
<HoverCard
|
||||||
open={open}
|
open={isOpen}
|
||||||
onOpenChange={(isOpen) => {
|
onOpenChange={(isOpen) => {
|
||||||
|
if (controlledOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setOpen(isOpen);
|
setOpen(isOpen);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HoverCardTrigger ref={ref} {...props} asChild>
|
<HoverCardTrigger ref={ref} asChild>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row items-center gap-1 text-pink-600"
|
className="flex flex-row items-center gap-1 text-pink-600"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(!open);
|
setOpen?.(!open);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Info size={14} />
|
<Info size={14} />
|
||||||
@@ -41,13 +64,21 @@ export const SSMSInfo = React.forwardRef<
|
|||||||
</div>
|
</div>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardContent className="w-80">
|
<HoverCardContent className="w-80">
|
||||||
<div className="flex">
|
<div className="flex flex-col">
|
||||||
<div className="space-y-1">
|
<div className="flex items-start justify-between">
|
||||||
<h4 className="text-sm font-semibold">
|
<h4 className="text-sm font-semibold">
|
||||||
{t(
|
{t(
|
||||||
'new_diagram_dialog.import_database.ssms_instructions.title'
|
'new_diagram_dialog.import_database.ssms_instructions.title'
|
||||||
)}
|
)}
|
||||||
</h4>
|
</h4>
|
||||||
|
<button
|
||||||
|
onClick={closeHandler}
|
||||||
|
className="text-muted-foreground hover:text-foreground"
|
||||||
|
>
|
||||||
|
<X size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
<span className="font-semibold">1. </span>
|
<span className="font-semibold">1. </span>
|
||||||
{t(
|
{t(
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
||||||
DatabaseType.GENERIC
|
DatabaseType.GENERIC
|
||||||
);
|
);
|
||||||
const { closeCreateDiagramDialog } = useDialog();
|
const { closeCreateDiagramDialog, openImportDBMLDialog } = useDialog();
|
||||||
const { updateConfig } = useConfig();
|
const { updateConfig } = useConfig();
|
||||||
const [scriptResult, setScriptResult] = useState('');
|
const [scriptResult, setScriptResult] = useState('');
|
||||||
const [databaseEdition, setDatabaseEdition] = useState<
|
const [databaseEdition, setDatabaseEdition] = useState<
|
||||||
@@ -104,6 +104,10 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
await updateConfig({ defaultDiagramId: diagram.id });
|
await updateConfig({ defaultDiagramId: diagram.id });
|
||||||
closeCreateDiagramDialog();
|
closeCreateDiagramDialog();
|
||||||
navigate(`/diagrams/${diagram.id}`);
|
navigate(`/diagrams/${diagram.id}`);
|
||||||
|
setTimeout(
|
||||||
|
() => openImportDBMLDialog({ withCreateEmptyDiagram: true }),
|
||||||
|
700
|
||||||
|
);
|
||||||
}, [
|
}, [
|
||||||
databaseType,
|
databaseType,
|
||||||
addDiagram,
|
addDiagram,
|
||||||
@@ -112,6 +116,7 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
|||||||
navigate,
|
navigate,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
diagramNumber,
|
diagramNumber,
|
||||||
|
openImportDBMLDialog,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -22,13 +22,17 @@ import { areFieldTypesCompatible } from '@/lib/data/data-types/data-types';
|
|||||||
const ErrorMessageRelationshipFieldsNotSameType =
|
const ErrorMessageRelationshipFieldsNotSameType =
|
||||||
'Relationships can only be created between fields of the same type';
|
'Relationships can only be created between fields of the same type';
|
||||||
|
|
||||||
export interface CreateRelationshipDialogProps extends BaseDialogProps {}
|
export interface CreateRelationshipDialogProps extends BaseDialogProps {
|
||||||
|
sourceTableId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const CreateRelationshipDialog: React.FC<
|
export const CreateRelationshipDialog: React.FC<
|
||||||
CreateRelationshipDialogProps
|
CreateRelationshipDialogProps
|
||||||
> = ({ dialog }) => {
|
> = ({ dialog, sourceTableId: preSelectedSourceTableId }) => {
|
||||||
const { closeCreateRelationshipDialog } = useDialog();
|
const { closeCreateRelationshipDialog } = useDialog();
|
||||||
const [primaryTableId, setPrimaryTableId] = useState<string | undefined>();
|
const [primaryTableId, setPrimaryTableId] = useState<string | undefined>(
|
||||||
|
preSelectedSourceTableId
|
||||||
|
);
|
||||||
const [primaryFieldId, setPrimaryFieldId] = useState<string | undefined>();
|
const [primaryFieldId, setPrimaryFieldId] = useState<string | undefined>();
|
||||||
const [referencedTableId, setReferencedTableId] = useState<
|
const [referencedTableId, setReferencedTableId] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
@@ -43,6 +47,9 @@ export const CreateRelationshipDialog: React.FC<
|
|||||||
const [canCreateRelationship, setCanCreateRelationship] = useState(false);
|
const [canCreateRelationship, setCanCreateRelationship] = useState(false);
|
||||||
const { fitView, setEdges } = useReactFlow();
|
const { fitView, setEdges } = useReactFlow();
|
||||||
const { databaseType } = useChartDB();
|
const { databaseType } = useChartDB();
|
||||||
|
const [primaryFieldSelectOpen, setPrimaryFieldSelectOpen] = useState(false);
|
||||||
|
const [referencedTableSelectOpen, setReferencedTableSelectOpen] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const tableOptions = useMemo(() => {
|
const tableOptions = useMemo(() => {
|
||||||
return tables.map(
|
return tables.map(
|
||||||
@@ -89,8 +96,23 @@ export const CreateRelationshipDialog: React.FC<
|
|||||||
setReferencedTableId(undefined);
|
setReferencedTableId(undefined);
|
||||||
setReferencedFieldId(undefined);
|
setReferencedFieldId(undefined);
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
|
setPrimaryFieldSelectOpen(false);
|
||||||
|
setReferencedTableSelectOpen(false);
|
||||||
}, [dialog.open]);
|
}, [dialog.open]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (preSelectedSourceTableId) {
|
||||||
|
const table = getTable(preSelectedSourceTableId);
|
||||||
|
if (table) {
|
||||||
|
setPrimaryTableId(preSelectedSourceTableId);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setPrimaryFieldSelectOpen(true);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}, [preSelectedSourceTableId, getTable]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCanCreateRelationship(false);
|
setCanCreateRelationship(false);
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
@@ -223,8 +245,14 @@ export const CreateRelationshipDialog: React.FC<
|
|||||||
)}
|
)}
|
||||||
value={primaryTableId}
|
value={primaryTableId}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setPrimaryTableId(value as string);
|
const newTableId = value as string;
|
||||||
|
setPrimaryTableId(newTableId);
|
||||||
|
if (
|
||||||
|
newTableId !==
|
||||||
|
preSelectedSourceTableId
|
||||||
|
) {
|
||||||
setPrimaryFieldId(undefined);
|
setPrimaryFieldId(undefined);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
emptyPlaceholder={t(
|
emptyPlaceholder={t(
|
||||||
'create_relationship_dialog.no_tables_found'
|
'create_relationship_dialog.no_tables_found'
|
||||||
@@ -253,6 +281,8 @@ export const CreateRelationshipDialog: React.FC<
|
|||||||
'create_relationship_dialog.primary_field_placeholder'
|
'create_relationship_dialog.primary_field_placeholder'
|
||||||
)}
|
)}
|
||||||
value={primaryFieldId}
|
value={primaryFieldId}
|
||||||
|
open={primaryFieldSelectOpen}
|
||||||
|
onOpenChange={setPrimaryFieldSelectOpen}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
setPrimaryFieldId(value as string)
|
setPrimaryFieldId(value as string)
|
||||||
}
|
}
|
||||||
@@ -283,6 +313,8 @@ export const CreateRelationshipDialog: React.FC<
|
|||||||
'create_relationship_dialog.referenced_table_placeholder'
|
'create_relationship_dialog.referenced_table_placeholder'
|
||||||
)}
|
)}
|
||||||
value={referencedTableId}
|
value={referencedTableId}
|
||||||
|
open={referencedTableSelectOpen}
|
||||||
|
onOpenChange={setReferencedTableSelectOpen}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setReferencedTableId(value as string);
|
setReferencedTableId(value as string);
|
||||||
setReferencedFieldId(undefined);
|
setReferencedFieldId(undefined);
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ import {
|
|||||||
} from '@/lib/data/export-metadata/export-sql-script';
|
} from '@/lib/data/export-metadata/export-sql-script';
|
||||||
import { databaseTypeToLabelMap } from '@/lib/databases';
|
import { databaseTypeToLabelMap } from '@/lib/databases';
|
||||||
import { DatabaseType } from '@/lib/domain/database-type';
|
import { DatabaseType } from '@/lib/domain/database-type';
|
||||||
|
import { shouldShowTablesBySchemaFilter } from '@/lib/domain/db-table';
|
||||||
import { Annoyed, Sparkles } from 'lucide-react';
|
import { Annoyed, Sparkles } from 'lucide-react';
|
||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||||
|
import type { Diagram } from '@/lib/domain/diagram';
|
||||||
|
|
||||||
export interface ExportSQLDialogProps extends BaseDialogProps {
|
export interface ExportSQLDialogProps extends BaseDialogProps {
|
||||||
targetDatabaseType: DatabaseType;
|
targetDatabaseType: DatabaseType;
|
||||||
@@ -34,7 +36,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
targetDatabaseType,
|
targetDatabaseType,
|
||||||
}) => {
|
}) => {
|
||||||
const { closeExportSQLDialog } = useDialog();
|
const { closeExportSQLDialog } = useDialog();
|
||||||
const { currentDiagram } = useChartDB();
|
const { currentDiagram, filteredSchemas } = useChartDB();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [script, setScript] = React.useState<string>();
|
const [script, setScript] = React.useState<string>();
|
||||||
const [error, setError] = React.useState<boolean>(false);
|
const [error, setError] = React.useState<boolean>(false);
|
||||||
@@ -43,17 +45,58 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
|||||||
const abortControllerRef = useRef<AbortController | null>(null);
|
const abortControllerRef = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
const exportSQLScript = useCallback(async () => {
|
const exportSQLScript = useCallback(async () => {
|
||||||
|
const filteredDiagram: Diagram = {
|
||||||
|
...currentDiagram,
|
||||||
|
tables: currentDiagram.tables?.filter((table) =>
|
||||||
|
shouldShowTablesBySchemaFilter(table, filteredSchemas)
|
||||||
|
),
|
||||||
|
relationships: currentDiagram.relationships?.filter((rel) => {
|
||||||
|
const sourceTable = currentDiagram.tables?.find(
|
||||||
|
(t) => t.id === rel.sourceTableId
|
||||||
|
);
|
||||||
|
const targetTable = currentDiagram.tables?.find(
|
||||||
|
(t) => t.id === rel.targetTableId
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
sourceTable &&
|
||||||
|
targetTable &&
|
||||||
|
shouldShowTablesBySchemaFilter(
|
||||||
|
sourceTable,
|
||||||
|
filteredSchemas
|
||||||
|
) &&
|
||||||
|
shouldShowTablesBySchemaFilter(targetTable, filteredSchemas)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
dependencies: currentDiagram.dependencies?.filter((dep) => {
|
||||||
|
const table = currentDiagram.tables?.find(
|
||||||
|
(t) => t.id === dep.tableId
|
||||||
|
);
|
||||||
|
const dependentTable = currentDiagram.tables?.find(
|
||||||
|
(t) => t.id === dep.dependentTableId
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
table &&
|
||||||
|
dependentTable &&
|
||||||
|
shouldShowTablesBySchemaFilter(table, filteredSchemas) &&
|
||||||
|
shouldShowTablesBySchemaFilter(
|
||||||
|
dependentTable,
|
||||||
|
filteredSchemas
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
if (targetDatabaseType === DatabaseType.GENERIC) {
|
if (targetDatabaseType === DatabaseType.GENERIC) {
|
||||||
return Promise.resolve(exportBaseSQL(currentDiagram));
|
return Promise.resolve(exportBaseSQL(filteredDiagram));
|
||||||
} else {
|
} else {
|
||||||
return exportSQL(currentDiagram, targetDatabaseType, {
|
return exportSQL(filteredDiagram, targetDatabaseType, {
|
||||||
stream: true,
|
stream: true,
|
||||||
onResultStream: (text) =>
|
onResultStream: (text) =>
|
||||||
setScript((prev) => (prev ? prev + text : text)),
|
setScript((prev) => (prev ? prev + text : text)),
|
||||||
signal: abortControllerRef.current?.signal,
|
signal: abortControllerRef.current?.signal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [targetDatabaseType, currentDiagram]);
|
}, [targetDatabaseType, currentDiagram, filteredSchemas]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dialog.open) {
|
if (!dialog.open) {
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import React, { useCallback, useEffect, useState, Suspense } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
Suspense,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
import { useDialog } from '@/hooks/use-dialog';
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -23,11 +30,54 @@ import { useCanvas } from '@/hooks/use-canvas';
|
|||||||
import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language';
|
import { setupDBMLLanguage } from '@/components/code-snippet/languages/dbml-language';
|
||||||
import { useToast } from '@/components/toast/use-toast';
|
import { useToast } from '@/components/toast/use-toast';
|
||||||
import { Spinner } from '@/components/spinner/spinner';
|
import { Spinner } from '@/components/spinner/spinner';
|
||||||
|
import { debounce } from '@/lib/utils';
|
||||||
|
|
||||||
export interface ImportDBMLDialogProps extends BaseDialogProps {}
|
interface DBMLError {
|
||||||
|
message: string;
|
||||||
|
line: number;
|
||||||
|
column: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDBMLError(error: unknown): DBMLError | null {
|
||||||
|
try {
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
const parsed = JSON.parse(error);
|
||||||
|
if (parsed.diags?.[0]) {
|
||||||
|
const diag = parsed.diags[0];
|
||||||
|
return {
|
||||||
|
message: diag.message,
|
||||||
|
line: diag.location.start.line,
|
||||||
|
column: diag.location.start.column,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (error && typeof error === 'object' && 'diags' in error) {
|
||||||
|
const parsed = error as {
|
||||||
|
diags: Array<{
|
||||||
|
message: string;
|
||||||
|
location: { start: { line: number; column: number } };
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
if (parsed.diags?.[0]) {
|
||||||
|
return {
|
||||||
|
message: parsed.diags[0].message,
|
||||||
|
line: parsed.diags[0].location.start.line,
|
||||||
|
column: parsed.diags[0].location.start.column,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing DBML error:', e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImportDBMLDialogProps extends BaseDialogProps {
|
||||||
|
withCreateEmptyDiagram?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const ImportDBMLDialog: React.FC<ImportDBMLDialogProps> = ({
|
export const ImportDBMLDialog: React.FC<ImportDBMLDialogProps> = ({
|
||||||
dialog,
|
dialog,
|
||||||
|
withCreateEmptyDiagram,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const initialDBML = `// Use DBML to define your database structure
|
const initialDBML = `// Use DBML to define your database structure
|
||||||
@@ -75,6 +125,16 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
} = useChartDB();
|
} = useChartDB();
|
||||||
const { reorderTables } = useCanvas();
|
const { reorderTables } = useCanvas();
|
||||||
const [reorder, setReorder] = useState(false);
|
const [reorder, setReorder] = useState(false);
|
||||||
|
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
|
||||||
|
const decorationsCollection =
|
||||||
|
useRef<monaco.editor.IEditorDecorationsCollection>();
|
||||||
|
|
||||||
|
const handleEditorDidMount = (
|
||||||
|
editor: monaco.editor.IStandaloneCodeEditor
|
||||||
|
) => {
|
||||||
|
editorRef.current = editor;
|
||||||
|
decorationsCollection.current = editor.createDecorationsCollection();
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reorder) {
|
if (reorder) {
|
||||||
@@ -85,34 +145,97 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
}
|
}
|
||||||
}, [reorder, reorderTables]);
|
}, [reorder, reorderTables]);
|
||||||
|
|
||||||
useEffect(() => {
|
const highlightErrorLine = useCallback((error: DBMLError) => {
|
||||||
if (!dialog.open) return;
|
if (!editorRef.current) return;
|
||||||
setErrorMessage(undefined);
|
|
||||||
setDBMLContent(initialDBML);
|
|
||||||
}, [dialog.open, initialDBML]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const model = editorRef.current.getModel();
|
||||||
const validateDBML = async () => {
|
if (!model) return;
|
||||||
if (!dbmlContent.trim()) {
|
|
||||||
|
const decorations = [
|
||||||
|
{
|
||||||
|
range: new monaco.Range(
|
||||||
|
error.line,
|
||||||
|
1,
|
||||||
|
error.line,
|
||||||
|
model.getLineMaxColumn(error.line)
|
||||||
|
),
|
||||||
|
options: {
|
||||||
|
isWholeLine: true,
|
||||||
|
className: 'dbml-error-line',
|
||||||
|
glyphMarginClassName: 'dbml-error-glyph',
|
||||||
|
hoverMessage: { value: error.message },
|
||||||
|
overviewRuler: {
|
||||||
|
color: '#ff0000',
|
||||||
|
position: monaco.editor.OverviewRulerLane.Right,
|
||||||
|
darkColor: '#ff0000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
decorationsCollection.current?.set(decorations);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const clearDecorations = useCallback(() => {
|
||||||
|
decorationsCollection.current?.clear();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validateDBML = useCallback(
|
||||||
|
async (content: string) => {
|
||||||
|
// Clear previous errors
|
||||||
setErrorMessage(undefined);
|
setErrorMessage(undefined);
|
||||||
return;
|
clearDecorations();
|
||||||
}
|
|
||||||
|
if (!content.trim()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
parser.parse(dbmlContent, 'dbml');
|
parser.parse(content, 'dbml');
|
||||||
setErrorMessage(undefined);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
const parsedError = parseDBMLError(e);
|
||||||
|
if (parsedError) {
|
||||||
setErrorMessage(
|
setErrorMessage(
|
||||||
e instanceof Error
|
t('import_dbml_dialog.error.description') +
|
||||||
? e.message
|
` (1 error found - in line ${parsedError.line})`
|
||||||
: t('import_dbml_dialog.error.description')
|
);
|
||||||
|
highlightErrorLine(parsedError);
|
||||||
|
} else {
|
||||||
|
setErrorMessage(
|
||||||
|
e instanceof Error ? e.message : JSON.stringify(e)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
[clearDecorations, highlightErrorLine, t]
|
||||||
|
);
|
||||||
|
|
||||||
validateDBML();
|
const debouncedValidateRef = useRef<((value: string) => void) | null>(null);
|
||||||
}, [dbmlContent, t]);
|
|
||||||
|
// Set up debounced validation
|
||||||
|
useEffect(() => {
|
||||||
|
debouncedValidateRef.current = debounce((value: string) => {
|
||||||
|
validateDBML(value);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
debouncedValidateRef.current = null;
|
||||||
|
};
|
||||||
|
}, [validateDBML]);
|
||||||
|
|
||||||
|
// Trigger validation when content changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (debouncedValidateRef.current) {
|
||||||
|
debouncedValidateRef.current(dbmlContent);
|
||||||
|
}
|
||||||
|
}, [dbmlContent]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!dialog.open) {
|
||||||
|
setErrorMessage(undefined);
|
||||||
|
clearDecorations();
|
||||||
|
setDBMLContent(initialDBML);
|
||||||
|
}
|
||||||
|
}, [dialog.open, initialDBML, clearDecorations]);
|
||||||
|
|
||||||
const handleImport = useCallback(async () => {
|
const handleImport = useCallback(async () => {
|
||||||
if (!dbmlContent.trim() || errorMessage) return;
|
if (!dbmlContent.trim() || errorMessage) return;
|
||||||
@@ -177,7 +300,7 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
description: (
|
description: (
|
||||||
<>
|
<>
|
||||||
<div>{t('import_dbml_dialog.error.description')}</div>
|
<div>{t('import_dbml_dialog.error.description')}</div>
|
||||||
{e instanceof Error ? <div>{e.message}</div> : null}
|
{e instanceof Error ? e.message : JSON.stringify(e)}
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -211,7 +334,11 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
showClose
|
showClose
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t('import_dbml_dialog.title')}</DialogTitle>
|
<DialogTitle>
|
||||||
|
{withCreateEmptyDiagram
|
||||||
|
? t('import_dbml_dialog.example_title')
|
||||||
|
: t('import_dbml_dialog.title')}
|
||||||
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
{t('import_dbml_dialog.description')}
|
{t('import_dbml_dialog.description')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@@ -222,6 +349,7 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
value={dbmlContent}
|
value={dbmlContent}
|
||||||
onChange={(value) => setDBMLContent(value || '')}
|
onChange={(value) => setDBMLContent(value || '')}
|
||||||
language="dbml"
|
language="dbml"
|
||||||
|
onMount={handleEditorDidMount}
|
||||||
theme={
|
theme={
|
||||||
effectiveTheme === 'dark'
|
effectiveTheme === 'dark'
|
||||||
? 'dbml-dark'
|
? 'dbml-dark'
|
||||||
@@ -232,6 +360,8 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
minimap: { enabled: false },
|
minimap: { enabled: false },
|
||||||
scrollBeyondLastLine: false,
|
scrollBeyondLastLine: false,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
|
glyphMargin: true,
|
||||||
|
lineNumbers: 'on',
|
||||||
scrollbar: {
|
scrollbar: {
|
||||||
vertical: 'visible',
|
vertical: 'visible',
|
||||||
horizontal: 'visible',
|
horizontal: 'visible',
|
||||||
@@ -246,7 +376,9 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button variant="secondary">
|
<Button variant="secondary">
|
||||||
{t('import_dbml_dialog.cancel')}
|
{withCreateEmptyDiagram
|
||||||
|
? t('import_dbml_dialog.skip_and_empty')
|
||||||
|
: t('import_dbml_dialog.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
{errorMessage ? (
|
{errorMessage ? (
|
||||||
@@ -266,7 +398,9 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
|
|||||||
onClick={handleImport}
|
onClick={handleImport}
|
||||||
disabled={!dbmlContent.trim() || !!errorMessage}
|
disabled={!dbmlContent.trim() || !!errorMessage}
|
||||||
>
|
>
|
||||||
{t('import_dbml_dialog.import')}
|
{withCreateEmptyDiagram
|
||||||
|
? t('import_dbml_dialog.show_example')
|
||||||
|
: t('import_dbml_dialog.import')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|||||||
@@ -109,6 +109,10 @@
|
|||||||
animation: rainbow-text-simple-animation 0.5s ease-in forwards;
|
animation: rainbow-text-simple-animation 0.5s ease-in forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dbml-error-line {
|
||||||
|
background-color: rgba(255, 0, 0, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes rainbow-text-simple-animation-rev {
|
@keyframes rainbow-text-simple-animation-rev {
|
||||||
0% {
|
0% {
|
||||||
background-size: 650%;
|
background-size: 650%;
|
||||||
|
|||||||
@@ -378,12 +378,15 @@ export const ar: LanguageTranslation = {
|
|||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -402,6 +405,7 @@ export const ar: LanguageTranslation = {
|
|||||||
edit_table: 'تعديل الجدول',
|
edit_table: 'تعديل الجدول',
|
||||||
duplicate_table: 'نسخ الجدول',
|
duplicate_table: 'نسخ الجدول',
|
||||||
delete_table: 'حذف الجدول',
|
delete_table: 'حذف الجدول',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: '({{key}} مغنظة الشبكة (اضغط مع الاستمرار على',
|
snap_to_grid_tooltip: '({{key}} مغنظة الشبكة (اضغط مع الاستمرار على',
|
||||||
|
|||||||
@@ -381,13 +381,16 @@ export const bn: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -406,6 +409,7 @@ export const bn: LanguageTranslation = {
|
|||||||
edit_table: 'টেবিল সম্পাদনা করুন',
|
edit_table: 'টেবিল সম্পাদনা করুন',
|
||||||
duplicate_table: 'টেবিল নকল করুন',
|
duplicate_table: 'টেবিল নকল করুন',
|
||||||
delete_table: 'টেবিল মুছে ফেলুন',
|
delete_table: 'টেবিল মুছে ফেলুন',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: 'গ্রিডে স্ন্যাপ করুন (অবস্থান {{key}})',
|
snap_to_grid_tooltip: 'গ্রিডে স্ন্যাপ করুন (অবস্থান {{key}})',
|
||||||
|
|||||||
@@ -384,13 +384,16 @@ export const de: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -409,6 +412,7 @@ export const de: LanguageTranslation = {
|
|||||||
edit_table: 'Tabelle bearbeiten',
|
edit_table: 'Tabelle bearbeiten',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'Tabelle löschen',
|
delete_table: 'Tabelle löschen',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Add translations
|
// TODO: Add translations
|
||||||
|
|||||||
@@ -376,13 +376,16 @@ export const en = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error importing DBML',
|
title: 'Error importing DBML',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -401,6 +404,7 @@ export const en = {
|
|||||||
edit_table: 'Edit Table',
|
edit_table: 'Edit Table',
|
||||||
duplicate_table: 'Duplicate Table',
|
duplicate_table: 'Duplicate Table',
|
||||||
delete_table: 'Delete Table',
|
delete_table: 'Delete Table',
|
||||||
|
add_relationship: 'Add Relationship',
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
|
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
|
||||||
|
|||||||
@@ -383,13 +383,16 @@ export const es: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -408,6 +411,7 @@ export const es: LanguageTranslation = {
|
|||||||
edit_table: 'Editar Tabla',
|
edit_table: 'Editar Tabla',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'Eliminar Tabla',
|
delete_table: 'Eliminar Tabla',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Add translations
|
// TODO: Add translations
|
||||||
|
|||||||
@@ -30,9 +30,8 @@ export const fr: LanguageTranslation = {
|
|||||||
theme: 'Thème',
|
theme: 'Thème',
|
||||||
show_dependencies: 'Afficher les Dépendances',
|
show_dependencies: 'Afficher les Dépendances',
|
||||||
hide_dependencies: 'Masquer les Dépendances',
|
hide_dependencies: 'Masquer les Dépendances',
|
||||||
// TODO: Translate
|
show_minimap: 'Afficher la Mini Carte',
|
||||||
show_minimap: 'Show Mini Map',
|
hide_minimap: 'Masquer la Mini Carte',
|
||||||
hide_minimap: 'Hide Mini Map',
|
|
||||||
},
|
},
|
||||||
share: {
|
share: {
|
||||||
share: 'Partage',
|
share: 'Partage',
|
||||||
@@ -101,9 +100,8 @@ export const fr: LanguageTranslation = {
|
|||||||
clear: 'Effacer',
|
clear: 'Effacer',
|
||||||
show_more: 'Afficher Plus',
|
show_more: 'Afficher Plus',
|
||||||
show_less: 'Afficher Moins',
|
show_less: 'Afficher Moins',
|
||||||
// TODO: Translate
|
copy_to_clipboard: 'Copier dans le presse-papiers',
|
||||||
copy_to_clipboard: 'Copy to Clipboard',
|
copied: 'Copié !',
|
||||||
copied: 'Copied!',
|
|
||||||
|
|
||||||
side_panel: {
|
side_panel: {
|
||||||
schema: 'Schéma:',
|
schema: 'Schéma:',
|
||||||
@@ -116,12 +114,11 @@ export const fr: LanguageTranslation = {
|
|||||||
add_table: 'Ajouter une Table',
|
add_table: 'Ajouter une Table',
|
||||||
filter: 'Filtrer',
|
filter: 'Filtrer',
|
||||||
collapse: 'Réduire Tout',
|
collapse: 'Réduire Tout',
|
||||||
// TODO: Translate
|
clear: 'Effacer le Filtre',
|
||||||
clear: 'Clear Filter',
|
no_results:
|
||||||
no_results: 'No tables found matching your filter.',
|
'Aucune table trouvée correspondant à votre filtre.',
|
||||||
// TODO: Translate
|
show_list: 'Afficher la Liste des Tableaux',
|
||||||
show_list: 'Show Table List',
|
show_dbml: "Afficher l'éditeur DBML",
|
||||||
show_dbml: 'Show DBML Editor',
|
|
||||||
|
|
||||||
table: {
|
table: {
|
||||||
fields: 'Champs',
|
fields: 'Champs',
|
||||||
@@ -153,7 +150,7 @@ export const fr: LanguageTranslation = {
|
|||||||
title: 'Actions de la Table',
|
title: 'Actions de la Table',
|
||||||
add_field: 'Ajouter un Champ',
|
add_field: 'Ajouter un Champ',
|
||||||
add_index: 'Ajouter un Index',
|
add_index: 'Ajouter un Index',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Tableau dupliqué',
|
||||||
delete_table: 'Supprimer la Table',
|
delete_table: 'Supprimer la Table',
|
||||||
change_schema: 'Changer le Schéma',
|
change_schema: 'Changer le Schéma',
|
||||||
},
|
},
|
||||||
@@ -236,14 +233,12 @@ export const fr: LanguageTranslation = {
|
|||||||
step_2: 'Si vous utilisez "Résultats en Grille", changez le nombre maximum de caractères récupérés pour les données non-XML (définir à 9999999).',
|
step_2: 'Si vous utilisez "Résultats en Grille", changez le nombre maximum de caractères récupérés pour les données non-XML (définir à 9999999).',
|
||||||
},
|
},
|
||||||
instructions_link: "Besoin d'aide ? Regardez comment",
|
instructions_link: "Besoin d'aide ? Regardez comment",
|
||||||
// TODO: Translate
|
check_script_result: 'Vérifier le résultat du Script',
|
||||||
check_script_result: 'Check Script Result',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel: 'Annuler',
|
cancel: 'Annuler',
|
||||||
back: 'Retour',
|
back: 'Retour',
|
||||||
// TODO: Translate
|
import_from_file: "Importer à partir d'un fichier",
|
||||||
import_from_file: 'Import from File',
|
|
||||||
empty_diagram: 'Diagramme vide',
|
empty_diagram: 'Diagramme vide',
|
||||||
continue: 'Continuer',
|
continue: 'Continuer',
|
||||||
import: 'Importer',
|
import: 'Importer',
|
||||||
@@ -358,40 +353,42 @@ export const fr: LanguageTranslation = {
|
|||||||
cancel: 'Annuler',
|
cancel: 'Annuler',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
|
||||||
export_diagram_dialog: {
|
export_diagram_dialog: {
|
||||||
title: 'Export Diagram',
|
title: 'Exporter le Diagramme',
|
||||||
description: 'Choose the format for export:',
|
description: "Sélectionner le format d'exportation :",
|
||||||
format_json: 'JSON',
|
format_json: 'JSON',
|
||||||
cancel: 'Cancel',
|
cancel: 'Annuler',
|
||||||
export: 'Export',
|
export: 'Exporter',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error exporting diagram',
|
title: "Erreur lors de l'exportation du diagramme",
|
||||||
description:
|
description:
|
||||||
'Something went wrong. Need help? chartdb.io@gmail.com',
|
"Une erreur s'est produite. Besoin d'aide ? chartdb.io@gmail.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
|
||||||
import_diagram_dialog: {
|
import_diagram_dialog: {
|
||||||
title: 'Import Diagram',
|
title: 'Importer un diagramme',
|
||||||
description: 'Paste the diagram JSON below:',
|
description: 'Coller le diagramme au format JSON ci-dessous :',
|
||||||
cancel: 'Cancel',
|
cancel: 'Annuler',
|
||||||
import: 'Import',
|
import: 'Exporter',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error importing diagram',
|
title: "Erreur lors de l'exportation du diagramme",
|
||||||
description:
|
description:
|
||||||
'The diagram JSON is invalid. Please check the JSON and try again. Need help? chartdb.io@gmail.com',
|
"Le diagramme JSON n'est pas valide. Veuillez vérifier le JSON et réessayer. Besoin d'aide ? chartdb.io@gmail.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Translate
|
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: "Exemple d'importation DBML",
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description:
|
||||||
import: 'Import',
|
'Importer un schéma de base de données à partir du format DBML.',
|
||||||
cancel: 'Cancel',
|
import: 'Importer',
|
||||||
|
cancel: 'Annuler',
|
||||||
|
skip_and_empty: 'Passer et vider',
|
||||||
|
show_example: 'Afficher un exemple',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Erreur',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description:
|
||||||
|
"Erreur d'analyse du DBML. Veuillez vérifier la syntaxe.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -408,12 +405,13 @@ export const fr: LanguageTranslation = {
|
|||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'Éditer la Table',
|
edit_table: 'Éditer la Table',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Tableau Dupliqué',
|
||||||
delete_table: 'Supprimer la Table',
|
delete_table: 'Supprimer la Table',
|
||||||
|
add_relationship: 'Ajouter une Relation',
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Add translations
|
snap_to_grid_tooltip:
|
||||||
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
|
'Aligner sur la grille (maintenir la touche {{key}})',
|
||||||
|
|
||||||
tool_tips: {
|
tool_tips: {
|
||||||
double_click_to_edit: 'Double-cliquez pour modifier',
|
double_click_to_edit: 'Double-cliquez pour modifier',
|
||||||
|
|||||||
@@ -381,13 +381,16 @@ export const gu: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -406,6 +409,7 @@ export const gu: LanguageTranslation = {
|
|||||||
edit_table: 'ટેબલ સંપાદિત કરો',
|
edit_table: 'ટેબલ સંપાદિત કરો',
|
||||||
duplicate_table: 'ટેબલ નકલ કરો',
|
duplicate_table: 'ટેબલ નકલ કરો',
|
||||||
delete_table: 'ટેબલ કાઢી નાખો',
|
delete_table: 'ટેબલ કાઢી નાખો',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: 'ગ્રિડ પર સ્નેપ કરો (જમાવટ {{key}})',
|
snap_to_grid_tooltip: 'ગ્રિડ પર સ્નેપ કરો (જમાવટ {{key}})',
|
||||||
|
|||||||
@@ -385,13 +385,16 @@ export const hi: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -410,6 +413,7 @@ export const hi: LanguageTranslation = {
|
|||||||
edit_table: 'तालिका संपादित करें',
|
edit_table: 'तालिका संपादित करें',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'तालिका हटाएँ',
|
delete_table: 'तालिका हटाएँ',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Add translations
|
// TODO: Add translations
|
||||||
|
|||||||
@@ -379,13 +379,16 @@ export const id_ID: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -405,6 +408,7 @@ export const id_ID: LanguageTranslation = {
|
|||||||
edit_table: 'Ubah Tabel',
|
edit_table: 'Ubah Tabel',
|
||||||
delete_table: 'Hapus Tabel',
|
delete_table: 'Hapus Tabel',
|
||||||
duplicate_table: 'Duplikat Tabel',
|
duplicate_table: 'Duplikat Tabel',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: 'Snap ke Kisi (Tahan {{key}})',
|
snap_to_grid_tooltip: 'Snap ke Kisi (Tahan {{key}})',
|
||||||
|
|||||||
@@ -388,13 +388,16 @@ export const ja: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -413,6 +416,7 @@ export const ja: LanguageTranslation = {
|
|||||||
edit_table: 'テーブルを編集',
|
edit_table: 'テーブルを編集',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'テーブルを削除',
|
delete_table: 'テーブルを削除',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Add translations
|
// TODO: Add translations
|
||||||
|
|||||||
@@ -377,13 +377,16 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -402,6 +405,7 @@ export const ko_KR: LanguageTranslation = {
|
|||||||
edit_table: '테이블 수정',
|
edit_table: '테이블 수정',
|
||||||
duplicate_table: '테이블 복제',
|
duplicate_table: '테이블 복제',
|
||||||
delete_table: '테이블 삭제',
|
delete_table: '테이블 삭제',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: '그리드에 맞추기 ({{key}}를 누른채 유지)',
|
snap_to_grid_tooltip: '그리드에 맞추기 ({{key}}를 누른채 유지)',
|
||||||
|
|||||||
@@ -389,13 +389,16 @@ export const mr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -414,8 +417,8 @@ export const mr: LanguageTranslation = {
|
|||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'टेबल संपादित करा',
|
edit_table: 'टेबल संपादित करा',
|
||||||
delete_table: 'टेबल हटवा',
|
delete_table: 'टेबल हटवा',
|
||||||
// TODO: Add translations
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
duplicate_table: 'Duplicate Table',
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Add translations
|
// TODO: Add translations
|
||||||
|
|||||||
@@ -382,13 +382,16 @@ export const ne: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -408,6 +411,7 @@ export const ne: LanguageTranslation = {
|
|||||||
edit_table: 'तालिका सम्पादन गर्नुहोस्',
|
edit_table: 'तालिका सम्पादन गर्नुहोस्',
|
||||||
duplicate_table: 'तालिका नक्कली गर्नुहोस्',
|
duplicate_table: 'तालिका नक्कली गर्नुहोस्',
|
||||||
delete_table: 'तालिका हटाउनुहोस्',
|
delete_table: 'तालिका हटाउनुहोस्',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: 'ग्रिडमा स्न्याप गर्नुहोस् ({{key}} थिच्नुहोस)',
|
snap_to_grid_tooltip: 'ग्रिडमा स्न्याप गर्नुहोस् ({{key}} थिच्नुहोस)',
|
||||||
|
|||||||
@@ -382,13 +382,16 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -407,6 +410,7 @@ export const pt_BR: LanguageTranslation = {
|
|||||||
edit_table: 'Editar Tabela',
|
edit_table: 'Editar Tabela',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'Excluir Tabela',
|
delete_table: 'Excluir Tabela',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Add translations
|
// TODO: Add translations
|
||||||
|
|||||||
@@ -378,13 +378,16 @@ export const ru: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -403,6 +406,7 @@ export const ru: LanguageTranslation = {
|
|||||||
edit_table: 'Изменить таблицу',
|
edit_table: 'Изменить таблицу',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: 'Удалить таблицу',
|
delete_table: 'Удалить таблицу',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
copy_to_clipboard: 'Скопировать в буфер обмена',
|
copy_to_clipboard: 'Скопировать в буфер обмена',
|
||||||
|
|||||||
@@ -385,13 +385,16 @@ export const te: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -409,9 +412,9 @@ export const te: LanguageTranslation = {
|
|||||||
|
|
||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'పట్టికను సవరించు',
|
edit_table: 'పట్టికను సవరించు',
|
||||||
// TODO: Translate
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
duplicate_table: 'Duplicate Table',
|
|
||||||
delete_table: 'పట్టికను తొలగించు',
|
delete_table: 'పట్టికను తొలగించు',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
|||||||
@@ -372,13 +372,16 @@ export const tr: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -394,8 +397,8 @@ export const tr: LanguageTranslation = {
|
|||||||
table_node_context_menu: {
|
table_node_context_menu: {
|
||||||
edit_table: 'Tabloyu Düzenle',
|
edit_table: 'Tabloyu Düzenle',
|
||||||
delete_table: 'Tabloyu Sil',
|
delete_table: 'Tabloyu Sil',
|
||||||
// TODO: Translate
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
duplicate_table: 'Duplicate Table',
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const uk: LanguageTranslation = {
|
|||||||
hide_sidebar: 'Приховати бічну панель',
|
hide_sidebar: 'Приховати бічну панель',
|
||||||
hide_cardinality: 'Приховати потужність',
|
hide_cardinality: 'Приховати потужність',
|
||||||
show_cardinality: 'Показати кардинальність',
|
show_cardinality: 'Показати кардинальність',
|
||||||
zoom_on_scroll: 'Збільшити прокручування',
|
zoom_on_scroll: 'Масштабувати прокручуванням',
|
||||||
theme: 'Тема',
|
theme: 'Тема',
|
||||||
show_dependencies: 'Показати залежності',
|
show_dependencies: 'Показати залежності',
|
||||||
hide_dependencies: 'Приховати залежності',
|
hide_dependencies: 'Приховати залежності',
|
||||||
@@ -377,13 +377,16 @@ export const uk: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -402,6 +405,7 @@ export const uk: LanguageTranslation = {
|
|||||||
edit_table: 'Редагувати таблицю',
|
edit_table: 'Редагувати таблицю',
|
||||||
duplicate_table: 'Дублювати таблицю',
|
duplicate_table: 'Дублювати таблицю',
|
||||||
delete_table: 'Видалити таблицю',
|
delete_table: 'Видалити таблицю',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: 'Вирівнювати за сіткою (Отримуйте {{key}})',
|
snap_to_grid_tooltip: 'Вирівнювати за сіткою (Отримуйте {{key}})',
|
||||||
|
|||||||
@@ -378,13 +378,16 @@ export const vi: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -403,6 +406,7 @@ export const vi: LanguageTranslation = {
|
|||||||
edit_table: 'Sửa bảng',
|
edit_table: 'Sửa bảng',
|
||||||
duplicate_table: 'Nhân đôi bảng',
|
duplicate_table: 'Nhân đôi bảng',
|
||||||
delete_table: 'Xóa bảng',
|
delete_table: 'Xóa bảng',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: 'Căn lưới (Giữ phím {{key}})',
|
snap_to_grid_tooltip: 'Căn lưới (Giữ phím {{key}})',
|
||||||
|
|||||||
@@ -374,13 +374,16 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -399,6 +402,7 @@ export const zh_CN: LanguageTranslation = {
|
|||||||
edit_table: '编辑表',
|
edit_table: '编辑表',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: '删除表',
|
delete_table: '删除表',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: '对齐到网格(按住 {{key}})',
|
snap_to_grid_tooltip: '对齐到网格(按住 {{key}})',
|
||||||
|
|||||||
@@ -373,13 +373,16 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
},
|
},
|
||||||
// TODO: Translate
|
// TODO: Translate
|
||||||
import_dbml_dialog: {
|
import_dbml_dialog: {
|
||||||
|
example_title: 'Import Example DBML',
|
||||||
title: 'Import DBML',
|
title: 'Import DBML',
|
||||||
description: 'Import a database schema from DBML format.',
|
description: 'Import a database schema from DBML format.',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
skip_and_empty: 'Skip & Empty',
|
||||||
|
show_example: 'Show Example',
|
||||||
error: {
|
error: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Failed to import DBML. Please check the syntax.',
|
description: 'Failed to parse DBML. Please check the syntax.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relationship_type: {
|
relationship_type: {
|
||||||
@@ -398,6 +401,7 @@ export const zh_TW: LanguageTranslation = {
|
|||||||
edit_table: '編輯表格',
|
edit_table: '編輯表格',
|
||||||
duplicate_table: 'Duplicate Table', // TODO: Translate
|
duplicate_table: 'Duplicate Table', // TODO: Translate
|
||||||
delete_table: '刪除表格',
|
delete_table: '刪除表格',
|
||||||
|
add_relationship: 'Add Relationship', // TODO: Translate
|
||||||
},
|
},
|
||||||
|
|
||||||
snap_to_grid_tooltip: '對齊網格(按住 {{key}})',
|
snap_to_grid_tooltip: '對齊網格(按住 {{key}})',
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ export const sqliteDataTypes: readonly DataType[] = [
|
|||||||
// Blob Type
|
// Blob Type
|
||||||
{ name: 'blob', id: 'blob' },
|
{ name: 'blob', id: 'blob' },
|
||||||
|
|
||||||
|
// Blob Type
|
||||||
|
{ name: 'json', id: 'json' },
|
||||||
|
|
||||||
// Date/Time Types (SQLite uses TEXT, REAL, or INTEGER types for dates and times)
|
// Date/Time Types (SQLite uses TEXT, REAL, or INTEGER types for dates and times)
|
||||||
{ name: 'date', id: 'date' },
|
{ name: 'date', id: 'date' },
|
||||||
{ name: 'datetime', id: 'datetime' },
|
{ name: 'datetime', id: 'datetime' },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Diagram } from '../../domain/diagram';
|
import type { Diagram } from '../../domain/diagram';
|
||||||
import { OPENAI_API_KEY } from '@/lib/env';
|
import { OPENAI_API_KEY, OPENAI_API_ENDPOINT, LLM_MODEL_NAME } from '@/lib/env';
|
||||||
import type { DatabaseType } from '@/lib/domain/database-type';
|
import type { DatabaseType } from '@/lib/domain/database-type';
|
||||||
import type { DBTable } from '@/lib/domain/db-table';
|
import type { DBTable } from '@/lib/domain/db-table';
|
||||||
import type { DataType } from '../data-types/data-types';
|
import type { DataType } from '../data-types/data-types';
|
||||||
@@ -196,6 +196,26 @@ export const exportBaseSQL = (diagram: Diagram): string => {
|
|||||||
return sqlScript;
|
return sqlScript;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateConfiguration = () => {
|
||||||
|
const apiKey = window?.env?.OPENAI_API_KEY ?? OPENAI_API_KEY;
|
||||||
|
const baseUrl = window?.env?.OPENAI_API_ENDPOINT ?? OPENAI_API_ENDPOINT;
|
||||||
|
const modelName = window?.env?.LLM_MODEL_NAME ?? LLM_MODEL_NAME;
|
||||||
|
|
||||||
|
// If using custom endpoint and model, don't require OpenAI API key
|
||||||
|
if (baseUrl && modelName) {
|
||||||
|
return { useCustomEndpoint: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If using OpenAI's service, require API key
|
||||||
|
if (apiKey) {
|
||||||
|
return { useCustomEndpoint: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
'Configuration Error: Either provide an OpenAI API key or both a custom endpoint and model name'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const exportSQL = async (
|
export const exportSQL = async (
|
||||||
diagram: Diagram,
|
diagram: Diagram,
|
||||||
databaseType: DatabaseType,
|
databaseType: DatabaseType,
|
||||||
@@ -213,20 +233,39 @@ export const exportSQL = async (
|
|||||||
return cachedResult;
|
return cachedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate configuration before proceeding
|
||||||
|
const { useCustomEndpoint } = validateConfiguration();
|
||||||
|
|
||||||
const [{ streamText, generateText }, { createOpenAI }] = await Promise.all([
|
const [{ streamText, generateText }, { createOpenAI }] = await Promise.all([
|
||||||
import('ai'),
|
import('ai'),
|
||||||
import('@ai-sdk/openai'),
|
import('@ai-sdk/openai'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const openai = createOpenAI({
|
const apiKey = window?.env?.OPENAI_API_KEY ?? OPENAI_API_KEY;
|
||||||
apiKey: window?.env?.OPENAI_API_KEY ?? OPENAI_API_KEY,
|
const baseUrl = window?.env?.OPENAI_API_ENDPOINT ?? OPENAI_API_ENDPOINT;
|
||||||
});
|
const modelName = window?.env?.LLM_MODEL_NAME || 'gpt-4o-mini-2024-07-18';
|
||||||
|
|
||||||
|
let config: { apiKey: string; baseUrl?: string };
|
||||||
|
|
||||||
|
if (useCustomEndpoint) {
|
||||||
|
config = {
|
||||||
|
apiKey: 'sk-xxx', // minimal valid API key format
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
config = {
|
||||||
|
apiKey: apiKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const openai = createOpenAI(config);
|
||||||
|
|
||||||
const prompt = generateSQLPrompt(databaseType, sqlScript);
|
const prompt = generateSQLPrompt(databaseType, sqlScript);
|
||||||
|
|
||||||
|
try {
|
||||||
if (options?.stream) {
|
if (options?.stream) {
|
||||||
const { textStream, text: textPromise } = await streamText({
|
const { textStream, text: textPromise } = await streamText({
|
||||||
model: openai('gpt-4o-mini-2024-07-18'),
|
model: openai(modelName),
|
||||||
prompt: prompt,
|
prompt: prompt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -244,12 +283,23 @@ export const exportSQL = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { text } = await generateText({
|
const { text } = await generateText({
|
||||||
model: openai('gpt-4o-mini-2024-07-18'),
|
model: openai(modelName),
|
||||||
prompt: prompt,
|
prompt: prompt,
|
||||||
});
|
});
|
||||||
|
|
||||||
setInCache(cacheKey, text);
|
setInCache(cacheKey, text);
|
||||||
return text;
|
return text;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error('Error generating SQL:', error);
|
||||||
|
if (error instanceof Error && error.message.includes('API key')) {
|
||||||
|
throw new Error(
|
||||||
|
'Error: Please check your API configuration. If using a custom endpoint, make sure the endpoint URL is correct.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
'Error generating SQL script. Please check your configuration and try again.'
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function getMySQLDataTypeSize(type: DataType) {
|
function getMySQLDataTypeSize(type: DataType) {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const sqliteQuery = `WITH fk_info AS (
|
|||||||
ELSE LOWER(p.type)
|
ELSE LOWER(p.type)
|
||||||
END,
|
END,
|
||||||
'ordinal_position', p.cid,
|
'ordinal_position', p.cid,
|
||||||
'nullable', (CASE WHEN p."notnull" = 0 THEN 'true' ELSE 'false' END),
|
'nullable', (CASE WHEN p."notnull" = 0 THEN true ELSE false END),
|
||||||
'collation', '',
|
'collation', '',
|
||||||
'character_maximum_length',
|
'character_maximum_length',
|
||||||
CASE
|
CASE
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
import { DatabaseEdition } from '@/lib/domain/database-edition';
|
import { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||||
|
|
||||||
const sqlServerQuery = `WITH fk_info AS (
|
const sqlServerQuery = `${`/* SQL Server 2017 and above edition (14.0, 15.0, 16.0, 17.0)*/`}
|
||||||
|
WITH fk_info AS (
|
||||||
SELECT
|
SELECT
|
||||||
JSON_QUERY(
|
JSON_QUERY(
|
||||||
'[' + STRING_AGG(
|
N'[' + STRING_AGG(
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY(N'{"schema": "' + COALESCE(REPLACE(tp_schema.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
JSON_QUERY(N'{
|
||||||
'", "table": "' + COALESCE(REPLACE(tp.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(tp_schema.name, '"', ''), ''), 'json') +
|
||||||
'", "column": "' + COALESCE(REPLACE(cp.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "table": "' + STRING_ESCAPE(COALESCE(REPLACE(tp.name, '"', ''), ''), 'json') +
|
||||||
'", "foreign_key_name": "' + COALESCE(REPLACE(fk.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "column": "' + STRING_ESCAPE(COALESCE(REPLACE(cp.name, '"', ''), ''), 'json') +
|
||||||
'", "reference_schema": "' + COALESCE(REPLACE(tr_schema.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "foreign_key_name": "' + STRING_ESCAPE(COALESCE(REPLACE(fk.name, '"', ''), ''), 'json') +
|
||||||
'", "reference_table": "' + COALESCE(REPLACE(tr.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "reference_schema": "' + STRING_ESCAPE(COALESCE(REPLACE(tr_schema.name, '"', ''), ''), 'json') +
|
||||||
'", "reference_column": "' + COALESCE(REPLACE(cr.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "reference_table": "' + STRING_ESCAPE(COALESCE(REPLACE(tr.name, '"', ''), ''), 'json') +
|
||||||
'", "fk_def": "FOREIGN KEY (' + COALESCE(REPLACE(cp.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "reference_column": "' + STRING_ESCAPE(COALESCE(REPLACE(cr.name, '"', ''), ''), 'json') +
|
||||||
') REFERENCES ' + COALESCE(REPLACE(tr.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "fk_def": "FOREIGN KEY (' + STRING_ESCAPE(COALESCE(REPLACE(cp.name, '"', ''), ''), 'json') +
|
||||||
'(' + COALESCE(REPLACE(cr.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
') REFERENCES ' + STRING_ESCAPE(COALESCE(REPLACE(tr.name, '"', ''), ''), 'json') +
|
||||||
') ON DELETE ' + fk.delete_referential_action_desc COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'(' + STRING_ESCAPE(COALESCE(REPLACE(cr.name, '"', ''), ''), 'json') +
|
||||||
' ON UPDATE ' + fk.update_referential_action_desc COLLATE SQL_Latin1_General_CP1_CI_AS + '"}')
|
') ON DELETE ' + STRING_ESCAPE(fk.delete_referential_action_desc, 'json') +
|
||||||
), ','
|
' ON UPDATE ' + STRING_ESCAPE(fk.update_referential_action_desc, 'json') +
|
||||||
|
'"}') COLLATE DATABASE_DEFAULT
|
||||||
|
), N','
|
||||||
) + N']'
|
) + N']'
|
||||||
) AS all_fks_json
|
) AS all_fks_json
|
||||||
FROM sys.foreign_keys AS fk
|
FROM sys.foreign_keys AS fk
|
||||||
@@ -31,153 +34,138 @@ const sqlServerQuery = `WITH fk_info AS (
|
|||||||
), pk_info AS (
|
), pk_info AS (
|
||||||
SELECT
|
SELECT
|
||||||
JSON_QUERY(
|
JSON_QUERY(
|
||||||
'[' + STRING_AGG(
|
N'[' +
|
||||||
|
STRING_AGG(
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY(N'{"schema": "' + COALESCE(REPLACE(pk.TABLE_SCHEMA, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
JSON_QUERY(N'{
|
||||||
'", "table": "' + COALESCE(REPLACE(pk.TABLE_NAME, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(pk.TABLE_SCHEMA, '"', ''), ''), 'json') +
|
||||||
'", "column": "' + COALESCE(REPLACE(pk.COLUMN_NAME, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "table": "' + STRING_ESCAPE(COALESCE(REPLACE(pk.TABLE_NAME, '"', ''), ''), 'json') +
|
||||||
'", "pk_def": "PRIMARY KEY (' + pk.COLUMN_NAME COLLATE SQL_Latin1_General_CP1_CI_AS + ')"}')
|
'", "column": "' + STRING_ESCAPE(COALESCE(REPLACE(pk.COLUMN_NAME, '"', ''), ''), 'json') +
|
||||||
), ','
|
'", "pk_def": "PRIMARY KEY (' + STRING_ESCAPE(pk.COLUMN_NAME, 'json') + N')"}') COLLATE DATABASE_DEFAULT
|
||||||
|
), N','
|
||||||
) + N']'
|
) + N']'
|
||||||
) AS all_pks_json
|
) AS all_pks_json
|
||||||
FROM
|
FROM (
|
||||||
(
|
|
||||||
SELECT
|
SELECT
|
||||||
kcu.TABLE_SCHEMA,
|
kcu.TABLE_SCHEMA,
|
||||||
kcu.TABLE_NAME,
|
kcu.TABLE_NAME,
|
||||||
kcu.COLUMN_NAME
|
kcu.COLUMN_NAME
|
||||||
FROM
|
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
|
||||||
INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
|
JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
||||||
JOIN
|
|
||||||
INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
|
||||||
ON kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
|
ON kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
|
||||||
AND kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
AND kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
||||||
WHERE
|
WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||||
tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
|
||||||
) pk
|
) pk
|
||||||
),
|
),
|
||||||
cols AS (
|
cols AS (
|
||||||
SELECT
|
SELECT
|
||||||
JSON_QUERY(
|
JSON_QUERY(N'[' +
|
||||||
'[' + STRING_AGG(
|
STRING_AGG(
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY('{"schema": "' + COALESCE(REPLACE(cols.TABLE_SCHEMA, '"', ''), '') +
|
JSON_QUERY(N'{
|
||||||
'", "table": "' + COALESCE(REPLACE(cols.TABLE_NAME, '"', ''), '') +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(cols.TABLE_SCHEMA, '"', ''), ''), 'json') +
|
||||||
'", "name": "' + COALESCE(REPLACE(cols.COLUMN_NAME, '"', ''), '') +
|
'", "table": "' + STRING_ESCAPE(COALESCE(REPLACE(cols.TABLE_NAME, '"', ''), ''), 'json') +
|
||||||
'", "ordinal_position": "' + CAST(cols.ORDINAL_POSITION AS NVARCHAR(MAX)) +
|
'", "name": "' + STRING_ESCAPE(COALESCE(REPLACE(cols.COLUMN_NAME, '"', ''), ''), 'json') +
|
||||||
'", "type": "' + LOWER(cols.DATA_TYPE) +
|
'", "ordinal_position": ' + CAST(cols.ORDINAL_POSITION AS NVARCHAR(MAX)) +
|
||||||
'", "character_maximum_length": "' +
|
', "type": "' + STRING_ESCAPE(LOWER(cols.DATA_TYPE), 'json') +
|
||||||
COALESCE(CAST(cols.CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(MAX)), 'null') +
|
'", "character_maximum_length": ' +
|
||||||
'", "precision": ' +
|
|
||||||
CASE
|
CASE
|
||||||
WHEN cols.DATA_TYPE IN ('numeric', 'decimal') THEN
|
WHEN cols.CHARACTER_MAXIMUM_LENGTH IS NULL THEN 'null'
|
||||||
CONCAT('{"precision":', COALESCE(CAST(cols.NUMERIC_PRECISION AS NVARCHAR(MAX)), 'null'),
|
ELSE CAST(cols.CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(MAX))
|
||||||
',"scale":', COALESCE(CAST(cols.NUMERIC_SCALE AS NVARCHAR(MAX)), 'null'), '}')
|
|
||||||
ELSE
|
|
||||||
'null'
|
|
||||||
END +
|
END +
|
||||||
', "nullable": ' +
|
', "precision": ' +
|
||||||
CASE WHEN cols.IS_NULLABLE = 'YES' THEN 'true' ELSE 'false' END +
|
CASE
|
||||||
', "default": "' +
|
WHEN cols.DATA_TYPE IN ('numeric', 'decimal')
|
||||||
COALESCE(REPLACE(CAST(cols.COLUMN_DEFAULT AS NVARCHAR(MAX)), '"', '\\"'), '') +
|
THEN '{"precision":' + COALESCE(CAST(cols.NUMERIC_PRECISION AS NVARCHAR(MAX)), 'null') +
|
||||||
'", "collation": "' +
|
',"scale":' + COALESCE(CAST(cols.NUMERIC_SCALE AS NVARCHAR(MAX)), 'null') + '}'
|
||||||
COALESCE(cols.COLLATION_NAME, '') +
|
ELSE 'null'
|
||||||
'"}')
|
END +
|
||||||
), ','
|
', "nullable": ' + CASE WHEN cols.IS_NULLABLE = 'YES' THEN 'true' ELSE 'false' END +
|
||||||
) + ']'
|
', "default": ' +
|
||||||
) AS all_columns_json
|
'"' + STRING_ESCAPE(COALESCE(REPLACE(CAST(cols.COLUMN_DEFAULT AS NVARCHAR(MAX)), '"', '\\"'), ''), 'json') + '"' +
|
||||||
FROM
|
', "collation": ' + CASE
|
||||||
INFORMATION_SCHEMA.COLUMNS cols
|
WHEN cols.COLLATION_NAME IS NULL THEN 'null'
|
||||||
WHERE
|
ELSE '"' + STRING_ESCAPE(cols.COLLATION_NAME, 'json') + '"'
|
||||||
cols.TABLE_CATALOG = DB_NAME()
|
END +
|
||||||
|
N'}') COLLATE DATABASE_DEFAULT
|
||||||
|
), N','
|
||||||
|
) +
|
||||||
|
N']') AS all_columns_json
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS cols
|
||||||
|
WHERE cols.TABLE_CATALOG = DB_NAME()
|
||||||
),
|
),
|
||||||
indexes AS (
|
indexes AS (
|
||||||
SELECT
|
SELECT
|
||||||
'[' + STRING_AGG(
|
N'[' +
|
||||||
|
STRING_AGG(
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY(
|
JSON_QUERY(N'{
|
||||||
N'{"schema": "' + COALESCE(REPLACE(s.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(s.name, '"', ''), ''), 'json') +
|
||||||
'", "table": "' + COALESCE(REPLACE(t.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "table": "' + STRING_ESCAPE(COALESCE(REPLACE(t.name, '"', ''), ''), 'json') +
|
||||||
'", "name": "' + COALESCE(REPLACE(i.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "name": "' + STRING_ESCAPE(COALESCE(REPLACE(i.name, '"', ''), ''), 'json') +
|
||||||
'", "column": "' + COALESCE(REPLACE(c.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "column": "' + STRING_ESCAPE(COALESCE(REPLACE(c.name, '"', ''), ''), 'json') +
|
||||||
'", "index_type": "' + LOWER(i.type_desc) COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "index_type": "' + STRING_ESCAPE(LOWER(i.type_desc), 'json') +
|
||||||
'", "unique": ' + CASE WHEN i.is_unique = 1 THEN 'true' ELSE 'false' END +
|
'", "unique": ' + CASE WHEN i.is_unique = 1 THEN 'true' ELSE 'false' END +
|
||||||
', "direction": "' + CASE WHEN ic.is_descending_key = 1 THEN 'desc' ELSE 'asc' END COLLATE SQL_Latin1_General_CP1_CI_AS +
|
', "direction": "' + CASE WHEN ic.is_descending_key = 1 THEN 'desc' ELSE 'asc' END +
|
||||||
'", "column_position": ' + CAST(ic.key_ordinal AS nvarchar(max)) + N'}'
|
'", "column_position": ' + CAST(ic.key_ordinal AS nvarchar(max)) + N'}'
|
||||||
)
|
) COLLATE DATABASE_DEFAULT
|
||||||
), ','
|
), N','
|
||||||
) + N']' AS all_indexes_json
|
) +
|
||||||
FROM
|
N']' AS all_indexes_json
|
||||||
sys.indexes i
|
FROM sys.indexes i
|
||||||
JOIN
|
JOIN sys.tables t ON i.object_id = t.object_id
|
||||||
sys.tables t ON i.object_id = t.object_id
|
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
||||||
JOIN
|
JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
||||||
sys.schemas s ON t.schema_id = s.schema_id
|
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
||||||
JOIN
|
WHERE s.name LIKE '%' AND i.name IS NOT NULL
|
||||||
sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
||||||
JOIN
|
|
||||||
sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
||||||
WHERE
|
|
||||||
s.name LIKE '%'
|
|
||||||
AND i.name IS NOT NULL
|
|
||||||
),
|
),
|
||||||
tbls AS (
|
tbls AS (
|
||||||
SELECT
|
SELECT
|
||||||
'[' + STRING_AGG(
|
N'[' + STRING_AGG(
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY(
|
JSON_QUERY(N'{
|
||||||
N'{"schema": "' + COALESCE(REPLACE(aggregated.schema_name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(aggregated.schema_name, '"', ''), ''), 'json') +
|
||||||
'", "table": "' + COALESCE(REPLACE(aggregated.table_name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "table": "' + STRING_ESCAPE(COALESCE(REPLACE(aggregated.table_name, '"', ''), ''), 'json') +
|
||||||
'", "row_count": "' + CAST(aggregated.row_count AS NVARCHAR(MAX)) +
|
'", "row_count": ' + CAST(aggregated.row_count AS NVARCHAR(MAX)) +
|
||||||
'", "table_type": "' + aggregated.table_type COLLATE SQL_Latin1_General_CP1_CI_AS +
|
', "table_type": "' + STRING_ESCAPE(aggregated.table_type, 'json') +
|
||||||
'", "creation_date": "' + CONVERT(NVARCHAR(MAX), aggregated.creation_date, 120) + '"}'
|
'", "creation_date": "' + CONVERT(NVARCHAR(MAX), aggregated.creation_date, 120) + N'"}'
|
||||||
)
|
) COLLATE DATABASE_DEFAULT
|
||||||
), ','
|
), N','
|
||||||
) + N']' AS all_tables_json
|
) +
|
||||||
FROM
|
N']' AS all_tables_json
|
||||||
(
|
FROM (
|
||||||
-- Select from tables
|
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(REPLACE(s.name, '"', ''), '') AS schema_name,
|
COALESCE(REPLACE(s.name, '"', ''), '') AS schema_name,
|
||||||
COALESCE(REPLACE(t.name, '"', ''), '') AS table_name,
|
COALESCE(REPLACE(t.name, '"', ''), '') AS table_name,
|
||||||
SUM(p.rows) AS row_count,
|
SUM(p.rows) AS row_count,
|
||||||
t.type_desc AS table_type,
|
t.type_desc AS table_type,
|
||||||
t.create_date AS creation_date
|
t.create_date AS creation_date
|
||||||
FROM
|
FROM sys.tables t
|
||||||
sys.tables t
|
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
||||||
JOIN
|
JOIN sys.partitions p ON t.object_id = p.object_id AND p.index_id IN (0, 1)
|
||||||
sys.schemas s ON t.schema_id = s.schema_id
|
WHERE s.name LIKE '%'
|
||||||
JOIN
|
GROUP BY s.name, t.name, t.type_desc, t.create_date
|
||||||
sys.partitions p ON t.object_id = p.object_id AND p.index_id IN (0, 1)
|
|
||||||
WHERE
|
|
||||||
s.name LIKE '%'
|
|
||||||
GROUP BY
|
|
||||||
s.name, t.name, t.type_desc, t.create_date
|
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
|
|
||||||
-- Select from views
|
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(REPLACE(s.name, '"', ''), '') AS table_name,
|
COALESCE(REPLACE(s.name, '"', ''), '') AS table_name,
|
||||||
COALESCE(REPLACE(v.name, '"', ''), '') AS object_name,
|
COALESCE(REPLACE(v.name, '"', ''), '') AS object_name,
|
||||||
0 AS row_count, -- Views don't have row counts
|
0 AS row_count,
|
||||||
'VIEW' AS table_type,
|
'VIEW' AS table_type,
|
||||||
v.create_date AS creation_date
|
v.create_date AS creation_date
|
||||||
FROM
|
FROM sys.views v
|
||||||
sys.views v
|
JOIN sys.schemas s ON v.schema_id = s.schema_id
|
||||||
JOIN
|
WHERE s.name LIKE '%'
|
||||||
sys.schemas s ON v.schema_id = s.schema_id
|
|
||||||
WHERE
|
|
||||||
s.name LIKE '%'
|
|
||||||
) AS aggregated
|
) AS aggregated
|
||||||
),
|
),
|
||||||
views AS (
|
views AS (
|
||||||
SELECT
|
SELECT
|
||||||
'[' + STRING_AGG(
|
'[' + STRING_AGG(
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY(
|
JSON_QUERY(N'{
|
||||||
N'{"schema": "' + STRING_ESCAPE(COALESCE(s.name, ''), 'json') +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(s.name, '"', ''), ''), 'json') +
|
||||||
'", "view_name": "' + STRING_ESCAPE(COALESCE(v.name, ''), 'json') +
|
'", "view_name": "' + STRING_ESCAPE(COALESCE(REPLACE(v.name, '"', ''), ''), 'json') +
|
||||||
'", "view_definition": "' +
|
'", "view_definition": "' +
|
||||||
STRING_ESCAPE(
|
STRING_ESCAPE(
|
||||||
CAST(
|
CAST(
|
||||||
@@ -186,135 +174,123 @@ views AS (
|
|||||||
'xs:base64Binary(sql:column("DefinitionBinary"))',
|
'xs:base64Binary(sql:column("DefinitionBinary"))',
|
||||||
'VARCHAR(MAX)'
|
'VARCHAR(MAX)'
|
||||||
), 'json') +
|
), 'json') +
|
||||||
'"}'
|
N'"}') COLLATE DATABASE_DEFAULT
|
||||||
)
|
), N','
|
||||||
), ','
|
|
||||||
) + N']' AS all_views_json
|
) + N']' AS all_views_json
|
||||||
FROM
|
FROM sys.views v
|
||||||
sys.views v
|
JOIN sys.schemas s ON v.schema_id = s.schema_id
|
||||||
JOIN
|
JOIN sys.sql_modules m ON v.object_id = m.object_id
|
||||||
sys.schemas s ON v.schema_id = s.schema_id
|
|
||||||
JOIN
|
|
||||||
sys.sql_modules m ON v.object_id = m.object_id
|
|
||||||
CROSS APPLY
|
CROSS APPLY
|
||||||
(SELECT CONVERT(VARBINARY(MAX), m.definition) AS DefinitionBinary) AS bin
|
(SELECT CONVERT(VARBINARY(MAX), m.definition) AS DefinitionBinary) AS bin
|
||||||
WHERE
|
WHERE s.name LIKE '%'
|
||||||
s.name LIKE '%'
|
|
||||||
)
|
)
|
||||||
SELECT JSON_QUERY(
|
SELECT JSON_QUERY(
|
||||||
N'{"fk_info": ' + ISNULL((SELECT cast(all_fks_json as nvarchar(max)) FROM fk_info), N'[]') +
|
N'{
|
||||||
|
"fk_info": ' + ISNULL((SELECT cast(all_fks_json as nvarchar(max)) FROM fk_info), N'[]') +
|
||||||
', "pk_info": ' + ISNULL((SELECT cast(all_pks_json as nvarchar(max)) FROM pk_info), N'[]') +
|
', "pk_info": ' + ISNULL((SELECT cast(all_pks_json as nvarchar(max)) FROM pk_info), N'[]') +
|
||||||
', "columns": ' + ISNULL((SELECT cast(all_columns_json as nvarchar(max)) FROM cols), N'[]') +
|
', "columns": ' + ISNULL((SELECT cast(all_columns_json as nvarchar(max)) FROM cols), N'[]') +
|
||||||
', "indexes": ' + ISNULL((SELECT cast(all_indexes_json as nvarchar(max)) FROM indexes), N'[]') +
|
', "indexes": ' + ISNULL((SELECT cast(all_indexes_json as nvarchar(max)) FROM indexes), N'[]') +
|
||||||
', "tables": ' + ISNULL((SELECT cast(all_tables_json as nvarchar(max)) FROM tbls), N'[]') +
|
', "tables": ' + ISNULL((SELECT cast(all_tables_json as nvarchar(max)) FROM tbls), N'[]') +
|
||||||
', "views": ' + ISNULL((SELECT cast(all_views_json as nvarchar(max)) FROM views), N'[]') +
|
', "views": ' + ISNULL((SELECT cast(all_views_json as nvarchar(max)) FROM views), N'[]') +
|
||||||
', "database_name": "' + DB_NAME() + '"' +
|
', "database_name": "' + STRING_ESCAPE(DB_NAME(), 'json') +
|
||||||
', "version": ""}'
|
'", "version": ""
|
||||||
|
}'
|
||||||
) AS metadata_json_to_import;
|
) AS metadata_json_to_import;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const sqlServer2016AndBelowQuery = `WITH fk_info AS (
|
const sqlServer2016AndBelowQuery = `${`/* SQL Server 2016 and below edition (13.0, 12.0, 11.0..) */`}
|
||||||
SELECT
|
WITH fk_info AS (
|
||||||
JSON_QUERY(
|
SELECT JSON_QUERY('[' +
|
||||||
'[' + ISNULL(
|
ISNULL(
|
||||||
STUFF((
|
STUFF((
|
||||||
SELECT ',' +
|
SELECT ',' +
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY(N'{"schema": "' + COALESCE(REPLACE(tp_schema.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
JSON_QUERY(N'{
|
||||||
'", "table": "' + COALESCE(REPLACE(tp.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(tp_schema.name, '"', ''), ''), 'json') +
|
||||||
'", "column": "' + COALESCE(REPLACE(cp.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "table": "' + STRING_ESCAPE(COALESCE(REPLACE(tp.name, '"', ''), ''), 'json') +
|
||||||
'", "foreign_key_name": "' + COALESCE(REPLACE(fk.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "column": "' + STRING_ESCAPE(COALESCE(REPLACE(cp.name, '"', ''), ''), 'json') +
|
||||||
'", "reference_schema": "' + COALESCE(REPLACE(tr_schema.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "foreign_key_name": "' + STRING_ESCAPE(COALESCE(REPLACE(fk.name, '"', ''), ''), 'json') +
|
||||||
'", "reference_table": "' + COALESCE(REPLACE(tr.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "reference_schema": "' + STRING_ESCAPE(COALESCE(REPLACE(tr_schema.name, '"', ''), ''), 'json') +
|
||||||
'", "reference_column": "' + COALESCE(REPLACE(cr.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "reference_table": "' + STRING_ESCAPE(COALESCE(REPLACE(tr.name, '"', ''), ''), 'json') +
|
||||||
'", "fk_def": "FOREIGN KEY (' + COALESCE(REPLACE(cp.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "reference_column": "' + STRING_ESCAPE(COALESCE(REPLACE(cr.name, '"', ''), ''), 'json') +
|
||||||
') REFERENCES ' + COALESCE(REPLACE(tr.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "fk_def": "FOREIGN KEY (' + STRING_ESCAPE(COALESCE(REPLACE(cp.name, '"', ''), ''), 'json') +
|
||||||
'(' + COALESCE(REPLACE(cr.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
') REFERENCES ' + STRING_ESCAPE(COALESCE(REPLACE(tr.name, '"', ''), ''), 'json') +
|
||||||
') ON DELETE ' + fk.delete_referential_action_desc COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'(' + STRING_ESCAPE(COALESCE(REPLACE(cr.name, '"', ''), ''), 'json') +
|
||||||
' ON UPDATE ' + fk.update_referential_action_desc COLLATE SQL_Latin1_General_CP1_CI_AS + '"}')
|
') ON DELETE ' + STRING_ESCAPE(fk.delete_referential_action_desc, 'json') +
|
||||||
|
' ON UPDATE ' + STRING_ESCAPE(fk.update_referential_action_desc, 'json') +
|
||||||
|
'"}') COLLATE DATABASE_DEFAULT
|
||||||
)
|
)
|
||||||
FROM
|
FROM sys.foreign_keys AS fk
|
||||||
sys.foreign_keys AS fk
|
JOIN sys.foreign_key_columns AS fkc ON fk.object_id = fkc.constraint_object_id
|
||||||
JOIN
|
JOIN sys.tables AS tp ON fkc.parent_object_id = tp.object_id
|
||||||
sys.foreign_key_columns AS fkc ON fk.object_id = fkc.constraint_object_id
|
JOIN sys.schemas AS tp_schema ON tp.schema_id = tp_schema.schema_id
|
||||||
JOIN
|
JOIN sys.columns AS cp ON fkc.parent_object_id = cp.object_id AND fkc.parent_column_id = cp.column_id
|
||||||
sys.tables AS tp ON fkc.parent_object_id = tp.object_id
|
JOIN sys.tables AS tr ON fkc.referenced_object_id = tr.object_id
|
||||||
JOIN
|
JOIN sys.schemas AS tr_schema ON tr.schema_id = tr_schema.schema_id
|
||||||
sys.schemas AS tp_schema ON tp.schema_id = tp_schema.schema_id
|
JOIN sys.columns AS cr ON fkc.referenced_object_id = cr.object_id AND fkc.referenced_column_id = cr.column_id
|
||||||
JOIN
|
|
||||||
sys.columns AS cp ON fkc.parent_object_id = cp.object_id AND fkc.parent_column_id = cp.column_id
|
|
||||||
JOIN
|
|
||||||
sys.tables AS tr ON fkc.referenced_object_id = tr.object_id
|
|
||||||
JOIN
|
|
||||||
sys.schemas AS tr_schema ON tr.schema_id = tr_schema.schema_id
|
|
||||||
JOIN
|
|
||||||
sys.columns AS cr ON fkc.referenced_object_id = cr.object_id AND fkc.referenced_column_id = cr.column_id
|
|
||||||
FOR XML PATH('')
|
FOR XML PATH('')
|
||||||
), 1, 1, ''), '')
|
), 1, 1, ''), '')
|
||||||
+ N']'
|
+ N']') AS all_fks_json
|
||||||
) AS all_fks_json
|
|
||||||
),
|
),
|
||||||
pk_info AS (
|
pk_info AS (
|
||||||
SELECT
|
SELECT JSON_QUERY('[' +
|
||||||
JSON_QUERY(
|
ISNULL(STUFF((
|
||||||
'[' + ISNULL(
|
|
||||||
STUFF((
|
|
||||||
SELECT ',' +
|
SELECT ',' +
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY(N'{"schema": "' + COALESCE(REPLACE(pk.TABLE_SCHEMA, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
JSON_QUERY(N'{
|
||||||
'", "table": "' + COALESCE(REPLACE(pk.TABLE_NAME, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(pk.TABLE_SCHEMA, '"', ''), ''), 'json') +
|
||||||
'", "column": "' + COALESCE(REPLACE(pk.COLUMN_NAME, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "table": "' + STRING_ESCAPE(COALESCE(REPLACE(pk.TABLE_NAME, '"', ''), ''), 'json') +
|
||||||
'", "pk_def": "PRIMARY KEY (' + pk.COLUMN_NAME COLLATE SQL_Latin1_General_CP1_CI_AS + ')"}')
|
'", "column": "' + STRING_ESCAPE(COALESCE(REPLACE(pk.COLUMN_NAME, '"', ''), ''), 'json') +
|
||||||
|
'", "pk_def": "PRIMARY KEY (' + STRING_ESCAPE(pk.COLUMN_NAME, 'json') + N')"}') COLLATE DATABASE_DEFAULT
|
||||||
)
|
)
|
||||||
FROM
|
FROM
|
||||||
(
|
(
|
||||||
SELECT
|
SELECT kcu.TABLE_SCHEMA,
|
||||||
kcu.TABLE_SCHEMA,
|
|
||||||
kcu.TABLE_NAME,
|
kcu.TABLE_NAME,
|
||||||
kcu.COLUMN_NAME
|
kcu.COLUMN_NAME
|
||||||
FROM
|
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
|
||||||
INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
|
JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
||||||
JOIN
|
|
||||||
INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
|
||||||
ON kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
|
ON kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
|
||||||
AND kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
AND kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
||||||
WHERE
|
WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||||
tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
|
||||||
) pk
|
) pk
|
||||||
FOR XML PATH('')
|
FOR XML PATH('')
|
||||||
), 1, 1, ''), '')
|
), 1, 1, ''), '')
|
||||||
+ N']'
|
+ N']') AS all_pks_json
|
||||||
) AS all_pks_json
|
|
||||||
),
|
),
|
||||||
cols AS (
|
cols AS (
|
||||||
SELECT
|
SELECT JSON_QUERY('[' +
|
||||||
JSON_QUERY(
|
ISNULL(
|
||||||
'[' + ISNULL(
|
|
||||||
STUFF((
|
STUFF((
|
||||||
SELECT ',' +
|
SELECT ',' +
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY('{"schema": "' + COALESCE(REPLACE(cols.TABLE_SCHEMA, '"', ''), '') +
|
JSON_QUERY('{
|
||||||
'", "table": "' + COALESCE(REPLACE(cols.TABLE_NAME, '"', ''), '') +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(cols.TABLE_SCHEMA, '"', ''), ''), 'json') +
|
||||||
'", "name": "' + COALESCE(REPLACE(cols.COLUMN_NAME, '"', ''), '') +
|
'", "table": "' + STRING_ESCAPE(COALESCE(REPLACE(cols.TABLE_NAME, '"', ''), ''), 'json') +
|
||||||
'", "ordinal_position": "' + CAST(cols.ORDINAL_POSITION AS NVARCHAR(MAX)) +
|
'", "name": "' + STRING_ESCAPE(COALESCE(REPLACE(cols.COLUMN_NAME, '"', ''), ''), 'json') +
|
||||||
'", "type": "' + LOWER(cols.DATA_TYPE) +
|
'", "ordinal_position": ' + CAST(cols.ORDINAL_POSITION AS NVARCHAR(MAX)) +
|
||||||
'", "character_maximum_length": "' +
|
', "type": "' + STRING_ESCAPE(LOWER(cols.DATA_TYPE), 'json') +
|
||||||
COALESCE(CAST(cols.CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(MAX)), 'null') +
|
'", "character_maximum_length": ' +
|
||||||
'", "precision": ' +
|
|
||||||
CASE
|
CASE
|
||||||
WHEN cols.DATA_TYPE IN ('numeric', 'decimal') THEN
|
WHEN cols.CHARACTER_MAXIMUM_LENGTH IS NULL THEN 'null'
|
||||||
CONCAT('{"precision":', COALESCE(CAST(cols.NUMERIC_PRECISION AS NVARCHAR(MAX)), 'null'),
|
ELSE CAST(cols.CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(MAX))
|
||||||
',"scale":', COALESCE(CAST(cols.NUMERIC_SCALE AS NVARCHAR(MAX)), 'null'), '}')
|
|
||||||
ELSE
|
|
||||||
'null'
|
|
||||||
END +
|
END +
|
||||||
', "nullable": ' +
|
', "precision": ' +
|
||||||
CASE WHEN cols.IS_NULLABLE = 'YES' THEN 'true' ELSE 'false' END +
|
CASE
|
||||||
', "default": "' +
|
WHEN cols.DATA_TYPE IN ('numeric', 'decimal')
|
||||||
COALESCE(REPLACE(CAST(cols.COLUMN_DEFAULT AS NVARCHAR(MAX)), '"', '"'), '') +
|
THEN '{"precision":' + COALESCE(CAST(cols.NUMERIC_PRECISION AS NVARCHAR(MAX)), 'null') +
|
||||||
'", "collation": "' +
|
',"scale":' + COALESCE(CAST(cols.NUMERIC_SCALE AS NVARCHAR(MAX)), 'null') + '}'
|
||||||
COALESCE(cols.COLLATION_NAME, '') +
|
ELSE 'null'
|
||||||
'"}')
|
END +
|
||||||
|
', "nullable": ' + CASE WHEN cols.IS_NULLABLE = 'YES' THEN 'true' ELSE 'false' END +
|
||||||
|
', "default": ' +
|
||||||
|
'"' + STRING_ESCAPE(COALESCE(REPLACE(CAST(cols.COLUMN_DEFAULT AS NVARCHAR(MAX)), '"', '\\"'), ''), 'json') + '"' +
|
||||||
|
', "collation": ' +
|
||||||
|
CASE
|
||||||
|
WHEN cols.COLLATION_NAME IS NULL THEN 'null'
|
||||||
|
ELSE '"' + STRING_ESCAPE(cols.COLLATION_NAME, 'json') + '"'
|
||||||
|
END +
|
||||||
|
N'}')
|
||||||
)
|
)
|
||||||
FROM
|
FROM
|
||||||
INFORMATION_SCHEMA.COLUMNS cols
|
INFORMATION_SCHEMA.COLUMNS cols
|
||||||
@@ -322,8 +298,7 @@ cols AS (
|
|||||||
cols.TABLE_CATALOG = DB_NAME()
|
cols.TABLE_CATALOG = DB_NAME()
|
||||||
FOR XML PATH('')
|
FOR XML PATH('')
|
||||||
), 1, 1, ''), '')
|
), 1, 1, ''), '')
|
||||||
+ ']'
|
+ ']') AS all_columns_json
|
||||||
) AS all_columns_json
|
|
||||||
),
|
),
|
||||||
indexes AS (
|
indexes AS (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -331,29 +306,23 @@ indexes AS (
|
|||||||
STUFF((
|
STUFF((
|
||||||
SELECT ',' +
|
SELECT ',' +
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY(
|
JSON_QUERY(N'{
|
||||||
N'{"schema": "' + COALESCE(REPLACE(s.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(s.name, '"', ''), ''), 'json') +
|
||||||
'", "table": "' + COALESCE(REPLACE(t.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "table": "' + STRING_ESCAPE(COALESCE(REPLACE(t.name, '"', ''), ''), 'json') +
|
||||||
'", "name": "' + COALESCE(REPLACE(i.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "name": "' + STRING_ESCAPE(COALESCE(REPLACE(i.name, '"', ''), ''), 'json') +
|
||||||
'", "column": "' + COALESCE(REPLACE(c.name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "column": "' + STRING_ESCAPE(COALESCE(REPLACE(c.name, '"', ''), ''), 'json') +
|
||||||
'", "index_type": "' + LOWER(i.type_desc) COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "index_type": "' + STRING_ESCAPE(LOWER(i.type_desc), 'json') +
|
||||||
'", "unique": ' + CASE WHEN i.is_unique = 1 THEN 'true' ELSE 'false' END +
|
'", "unique": ' + CASE WHEN i.is_unique = 1 THEN 'true' ELSE 'false' END +
|
||||||
', "direction": "' + CASE WHEN ic.is_descending_key = 1 THEN 'desc' ELSE 'asc' END COLLATE SQL_Latin1_General_CP1_CI_AS +
|
', "direction": "' + CASE WHEN ic.is_descending_key = 1 THEN 'desc' ELSE 'asc' END +
|
||||||
'", "column_position": ' + CAST(ic.key_ordinal AS nvarchar(max)) + N'}'
|
'", "column_position": ' + CAST(ic.key_ordinal AS nvarchar(max)) + N'}'
|
||||||
|
) COLLATE DATABASE_DEFAULT
|
||||||
)
|
)
|
||||||
)
|
FROM sys.indexes i
|
||||||
FROM
|
JOIN sys.tables t ON i.object_id = t.object_id
|
||||||
sys.indexes i
|
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
||||||
JOIN
|
JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
||||||
sys.tables t ON i.object_id = t.object_id
|
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
||||||
JOIN
|
WHERE s.name LIKE '%'
|
||||||
sys.schemas s ON t.schema_id = s.schema_id
|
|
||||||
JOIN
|
|
||||||
sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
||||||
JOIN
|
|
||||||
sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
||||||
WHERE
|
|
||||||
s.name LIKE '%'
|
|
||||||
AND i.name IS NOT NULL
|
AND i.name IS NOT NULL
|
||||||
FOR XML PATH('')
|
FOR XML PATH('')
|
||||||
), 1, 1, ''), '')
|
), 1, 1, ''), '')
|
||||||
@@ -365,12 +334,12 @@ tbls AS (
|
|||||||
STUFF((
|
STUFF((
|
||||||
SELECT ',' +
|
SELECT ',' +
|
||||||
CONVERT(nvarchar(max),
|
CONVERT(nvarchar(max),
|
||||||
JSON_QUERY(
|
JSON_QUERY(N'{
|
||||||
N'{"schema": "' + COALESCE(REPLACE(aggregated.schema_name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(aggregated.schema_name, '"', ''), ''), 'json') +
|
||||||
'", "table": "' + COALESCE(REPLACE(aggregated.object_name, '"', ''), '') COLLATE SQL_Latin1_General_CP1_CI_AS +
|
'", "table": "' + STRING_ESCAPE(COALESCE(REPLACE(aggregated.table_name, '"', ''), ''), 'json') +
|
||||||
'", "row_count": "' + CAST(aggregated.row_count AS NVARCHAR(MAX)) +
|
'", "row_count": ' + CAST(aggregated.row_count AS NVARCHAR(MAX)) +
|
||||||
'", "object_type": "' + aggregated.object_type COLLATE SQL_Latin1_General_CP1_CI_AS +
|
', "table_type": "' + STRING_ESCAPE(aggregated.table_type, 'json') +
|
||||||
'", "creation_date": "' + CONVERT(NVARCHAR(MAX), aggregated.creation_date, 120) + '"}'
|
'", "creation_date": "' + CONVERT(NVARCHAR(MAX), aggregated.creation_date, 120) + N'"}'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
FROM
|
FROM
|
||||||
@@ -378,20 +347,15 @@ tbls AS (
|
|||||||
-- Select from tables
|
-- Select from tables
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(REPLACE(s.name, '"', ''), '') AS schema_name,
|
COALESCE(REPLACE(s.name, '"', ''), '') AS schema_name,
|
||||||
COALESCE(REPLACE(t.name, '"', ''), '') AS object_name,
|
COALESCE(REPLACE(t.name, '"', ''), '') AS table_name,
|
||||||
SUM(p.rows) AS row_count,
|
SUM(p.rows) AS row_count,
|
||||||
t.type_desc AS object_type,
|
t.type_desc AS table_type,
|
||||||
t.create_date AS creation_date
|
t.create_date AS creation_date
|
||||||
FROM
|
FROM sys.tables t
|
||||||
sys.tables t
|
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
||||||
JOIN
|
JOIN sys.partitions p ON t.object_id = p.object_id AND p.index_id IN (0, 1)
|
||||||
sys.schemas s ON t.schema_id = s.schema_id
|
WHERE s.name LIKE '%'
|
||||||
JOIN
|
GROUP BY s.name, t.name, t.type_desc, t.create_date
|
||||||
sys.partitions p ON t.object_id = p.object_id AND p.index_id IN (0, 1)
|
|
||||||
WHERE
|
|
||||||
s.name LIKE '%'
|
|
||||||
GROUP BY
|
|
||||||
s.name, t.name, t.type_desc, t.create_date
|
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
|
|
||||||
@@ -402,12 +366,9 @@ tbls AS (
|
|||||||
0 AS row_count, -- Views don't have row counts
|
0 AS row_count, -- Views don't have row counts
|
||||||
'VIEW' AS object_type,
|
'VIEW' AS object_type,
|
||||||
v.create_date AS creation_date
|
v.create_date AS creation_date
|
||||||
FROM
|
FROM sys.views v
|
||||||
sys.views v
|
JOIN sys.schemas s ON v.schema_id = s.schema_id
|
||||||
JOIN
|
WHERE s.name LIKE '%'
|
||||||
sys.schemas s ON v.schema_id = s.schema_id
|
|
||||||
WHERE
|
|
||||||
s.name LIKE '%'
|
|
||||||
) AS aggregated
|
) AS aggregated
|
||||||
FOR XML PATH('')
|
FOR XML PATH('')
|
||||||
), 1, 1, ''), '')
|
), 1, 1, ''), '')
|
||||||
@@ -417,18 +378,18 @@ views AS (
|
|||||||
SELECT
|
SELECT
|
||||||
'[' +
|
'[' +
|
||||||
(
|
(
|
||||||
SELECT
|
SELECT STUFF((
|
||||||
STUFF((
|
|
||||||
SELECT ',' + CONVERT(nvarchar(max),
|
SELECT ',' + CONVERT(nvarchar(max),
|
||||||
JSON_QUERY(
|
JSON_QUERY(
|
||||||
N'{"schema": "' + COALESCE(REPLACE(s.name, '"', ''), '') +
|
N'{
|
||||||
'", "view_name": "' + COALESCE(REPLACE(v.name, '"', ''), '') +
|
"schema": "' + STRING_ESCAPE(COALESCE(REPLACE(s.name, '"', ''), ''), 'json') +
|
||||||
|
'", "view_name": "' + STRING_ESCAPE(COALESCE(REPLACE(v.name, '"', ''), ''), 'json') +
|
||||||
'", "view_definition": "' +
|
'", "view_definition": "' +
|
||||||
CAST(
|
CAST(
|
||||||
(
|
(
|
||||||
SELECT CAST(OBJECT_DEFINITION(v.object_id) AS VARBINARY(MAX)) FOR XML PATH('')
|
SELECT CAST(OBJECT_DEFINITION(v.object_id) AS VARBINARY(MAX)) FOR XML PATH('')
|
||||||
) AS NVARCHAR(MAX)
|
) AS NVARCHAR(MAX)
|
||||||
) + '"}'
|
) + N'"}'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
FROM
|
FROM
|
||||||
@@ -441,14 +402,16 @@ views AS (
|
|||||||
) + ']' AS all_views_json
|
) + ']' AS all_views_json
|
||||||
)
|
)
|
||||||
SELECT JSON_QUERY(
|
SELECT JSON_QUERY(
|
||||||
N'{"fk_info": ' + ISNULL((SELECT cast(all_fks_json as nvarchar(max)) FROM fk_info), N'[]') +
|
N'{
|
||||||
|
"fk_info": ' + ISNULL((SELECT cast(all_fks_json as nvarchar(max)) FROM fk_info), N'[]') +
|
||||||
', "pk_info": ' + ISNULL((SELECT cast(all_pks_json as nvarchar(max)) FROM pk_info), N'[]') +
|
', "pk_info": ' + ISNULL((SELECT cast(all_pks_json as nvarchar(max)) FROM pk_info), N'[]') +
|
||||||
', "columns": ' + ISNULL((SELECT cast(all_columns_json as nvarchar(max)) FROM cols), N'[]') +
|
', "columns": ' + ISNULL((SELECT cast(all_columns_json as nvarchar(max)) FROM cols), N'[]') +
|
||||||
', "indexes": ' + ISNULL((SELECT cast(all_indexes_json as nvarchar(max)) FROM indexes), N'[]') +
|
', "indexes": ' + ISNULL((SELECT cast(all_indexes_json as nvarchar(max)) FROM indexes), N'[]') +
|
||||||
', "tables": ' + ISNULL((SELECT cast(all_objects_json as nvarchar(max)) FROM tbls), N'[]') +
|
', "tables": ' + ISNULL((SELECT cast(all_objects_json as nvarchar(max)) FROM tbls), N'[]') +
|
||||||
', "views": ' + ISNULL((SELECT cast(all_views_json as nvarchar(max)) FROM views), N'[]') +
|
', "views": ' + ISNULL((SELECT cast(all_views_json as nvarchar(max)) FROM views), N'[]') +
|
||||||
', "database_name": "' + DB_NAME() + '"' +
|
', "database_name": "' + DB_NAME() + '"' +
|
||||||
', "version": ""}'
|
', "version": ""
|
||||||
|
}'
|
||||||
) AS metadata_json_to_import;`;
|
) AS metadata_json_to_import;`;
|
||||||
|
|
||||||
export const getSqlServerQuery = (
|
export const getSqlServerQuery = (
|
||||||
|
|||||||
@@ -10,14 +10,20 @@ export const fixMetadataJson = async (
|
|||||||
return (
|
return (
|
||||||
metadataJson
|
metadataJson
|
||||||
.trim()
|
.trim()
|
||||||
|
// First unescape the JSON string
|
||||||
|
.replace(/\\"/g, '"')
|
||||||
|
.replace(/\\\\/g, '\\')
|
||||||
.replace(/^[^{]*/, '') // Remove everything before the first '{'
|
.replace(/^[^{]*/, '') // Remove everything before the first '{'
|
||||||
.replace(/}[^}]*$/, '}') // Remove everything after the last '}'
|
.replace(/}[^}]*$/, '}') // Remove everything after the last '}'
|
||||||
|
.replace(/:""([^"]+)""/g, ':"$1"') // Convert :""value"" to :"value"
|
||||||
|
.replace(/""(\w+)""/g, '"$1"') // Convert ""key"" to "key"
|
||||||
.replace(/^\s+|\s+$/g, '')
|
.replace(/^\s+|\s+$/g, '')
|
||||||
.replace(/^"|"$/g, '')
|
.replace(/^"|"$/g, '')
|
||||||
.replace(/^'|'$/g, '')
|
.replace(/^'|'$/g, '')
|
||||||
.replace(/""""/g, '""') // Remove Quadruple quotes from keys
|
.replace(/""""/g, '""') // Remove Quadruple quotes from keys
|
||||||
.replace(/"""([^",}]+)"""/g, '"$1"') // Remove tripple quotes from keys
|
.replace(/"""([^",}]+)"""/g, '"$1"') // Remove tripple quotes from keys
|
||||||
.replace(/""([^",}]+)""/g, '"$1"') // Remove double quotes from keys
|
.replace(/""([^",}]+)""/g, '"$1"') // Remove double quotes from keys
|
||||||
|
|
||||||
/* eslint-disable-next-line no-useless-escape */
|
/* eslint-disable-next-line no-useless-escape */
|
||||||
.replace(/\"/g, '___ESCAPED_QUOTE___') // Temporarily replace empty strings
|
.replace(/\"/g, '___ESCAPED_QUOTE___') // Temporarily replace empty strings
|
||||||
.replace(/(?<=:\s*)""(?=\s*[,}])/g, '___EMPTY___') // Temporarily replace empty strings
|
.replace(/(?<=:\s*)""(?=\s*[,}])/g, '___EMPTY___') // Temporarily replace empty strings
|
||||||
|
|||||||
@@ -28,10 +28,24 @@ interface DBMLField {
|
|||||||
increment?: boolean;
|
increment?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DBMLIndexColumn {
|
||||||
|
value: string;
|
||||||
|
type?: string;
|
||||||
|
length?: number;
|
||||||
|
order?: 'asc' | 'desc';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DBMLIndex {
|
||||||
|
columns: string | (string | DBMLIndexColumn)[];
|
||||||
|
unique?: boolean;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface DBMLTable {
|
interface DBMLTable {
|
||||||
name: string;
|
name: string;
|
||||||
schema?: string | { name: string };
|
schema?: string | { name: string };
|
||||||
fields: DBMLField[];
|
fields: DBMLField[];
|
||||||
|
indexes?: DBMLIndex[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DBMLEndpoint {
|
interface DBMLEndpoint {
|
||||||
@@ -99,7 +113,8 @@ export const importDBMLToDiagram = async (
|
|||||||
|
|
||||||
// Extract only the necessary data from the parsed DBML
|
// Extract only the necessary data from the parsed DBML
|
||||||
const extractedData = {
|
const extractedData = {
|
||||||
tables: dbmlData.tables.map((table: DBMLTable) => ({
|
tables: (dbmlData.tables as unknown as DBMLTable[]).map(
|
||||||
|
(table) => ({
|
||||||
name: table.name,
|
name: table.name,
|
||||||
schema: table.schema,
|
schema: table.schema,
|
||||||
fields: table.fields.map((field: DBMLField) => ({
|
fields: table.fields.map((field: DBMLField) => ({
|
||||||
@@ -110,7 +125,48 @@ export const importDBMLToDiagram = async (
|
|||||||
not_null: field.not_null,
|
not_null: field.not_null,
|
||||||
increment: field.increment,
|
increment: field.increment,
|
||||||
})),
|
})),
|
||||||
})),
|
indexes:
|
||||||
|
table.indexes?.map((dbmlIndex) => {
|
||||||
|
let indexColumns: string[];
|
||||||
|
|
||||||
|
// Handle composite index case "(col1, col2)"
|
||||||
|
if (typeof dbmlIndex.columns === 'string') {
|
||||||
|
if (dbmlIndex.columns.includes('(')) {
|
||||||
|
// Composite index
|
||||||
|
const columnsStr =
|
||||||
|
dbmlIndex.columns.replace(/[()]/g, '');
|
||||||
|
indexColumns = columnsStr
|
||||||
|
.split(',')
|
||||||
|
.map((c) => c.trim());
|
||||||
|
} else {
|
||||||
|
// Single column
|
||||||
|
indexColumns = [dbmlIndex.columns.trim()];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle array of columns
|
||||||
|
indexColumns = Array.isArray(dbmlIndex.columns)
|
||||||
|
? dbmlIndex.columns.map((col) =>
|
||||||
|
typeof col === 'object' &&
|
||||||
|
'value' in col
|
||||||
|
? (col.value as string).trim()
|
||||||
|
: (col as string).trim()
|
||||||
|
)
|
||||||
|
: [String(dbmlIndex.columns).trim()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a consistent index name
|
||||||
|
const indexName =
|
||||||
|
dbmlIndex.name ||
|
||||||
|
`idx_${table.name}_${indexColumns.join('_')}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
columns: indexColumns,
|
||||||
|
unique: dbmlIndex.unique || false,
|
||||||
|
name: indexName,
|
||||||
|
};
|
||||||
|
}) || [],
|
||||||
|
})
|
||||||
|
),
|
||||||
refs: (dbmlData.refs as unknown as DBMLRef[]).map((ref) => ({
|
refs: (dbmlData.refs as unknown as DBMLRef[]).map((ref) => ({
|
||||||
endpoints: (ref.endpoints as [DBMLEndpoint, DBMLEndpoint]).map(
|
endpoints: (ref.endpoints as [DBMLEndpoint, DBMLEndpoint]).map(
|
||||||
(endpoint) => ({
|
(endpoint) => ({
|
||||||
@@ -126,7 +182,42 @@ export const importDBMLToDiagram = async (
|
|||||||
const tables: DBTable[] = extractedData.tables.map((table, index) => {
|
const tables: DBTable[] = extractedData.tables.map((table, index) => {
|
||||||
const row = Math.floor(index / 4);
|
const row = Math.floor(index / 4);
|
||||||
const col = index % 4;
|
const col = index % 4;
|
||||||
const tableSpacing = 300; // Increased spacing between tables
|
const tableSpacing = 300;
|
||||||
|
|
||||||
|
// Create fields first so we have their IDs
|
||||||
|
const fields = table.fields.map((field) => ({
|
||||||
|
id: generateId(),
|
||||||
|
name: field.name.replace(/['"]/g, ''),
|
||||||
|
type: mapDBMLTypeToGenericType(field.type.type_name),
|
||||||
|
nullable: !field.not_null,
|
||||||
|
primaryKey: field.pk || false,
|
||||||
|
unique: field.unique || false,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Convert DBML indexes to ChartDB indexes
|
||||||
|
const indexes =
|
||||||
|
table.indexes?.map((dbmlIndex) => {
|
||||||
|
const fieldIds = dbmlIndex.columns.map((columnName) => {
|
||||||
|
const field = fields.find((f) => f.name === columnName);
|
||||||
|
if (!field) {
|
||||||
|
throw new Error(
|
||||||
|
`Index references non-existent column: ${columnName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return field.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: generateId(),
|
||||||
|
name:
|
||||||
|
dbmlIndex.name ||
|
||||||
|
`idx_${table.name}_${dbmlIndex.columns.join('_')}`,
|
||||||
|
fieldIds,
|
||||||
|
unique: dbmlIndex.unique || false,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
};
|
||||||
|
}) || [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: generateId(),
|
id: generateId(),
|
||||||
@@ -136,18 +227,10 @@ export const importDBMLToDiagram = async (
|
|||||||
? table.schema
|
? table.schema
|
||||||
: table.schema?.name || '',
|
: table.schema?.name || '',
|
||||||
order: index,
|
order: index,
|
||||||
fields: table.fields.map((field) => ({
|
fields,
|
||||||
id: generateId(),
|
indexes,
|
||||||
name: field.name.replace(/['"]/g, ''),
|
|
||||||
type: mapDBMLTypeToGenericType(field.type.type_name),
|
|
||||||
nullable: !field.not_null,
|
|
||||||
primaryKey: field.pk || false,
|
|
||||||
unique: field.unique || false,
|
|
||||||
createdAt: Date.now(),
|
|
||||||
})),
|
|
||||||
x: col * tableSpacing,
|
x: col * tableSpacing,
|
||||||
y: row * tableSpacing,
|
y: row * tableSpacing,
|
||||||
indexes: [],
|
|
||||||
color: randomColor(),
|
color: randomColor(),
|
||||||
isView: false,
|
isView: false,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
export const OPENAI_API_KEY: string = import.meta.env.VITE_OPENAI_API_KEY;
|
export const OPENAI_API_KEY: string = import.meta.env.VITE_OPENAI_API_KEY;
|
||||||
|
export const OPENAI_API_ENDPOINT: string = import.meta.env
|
||||||
|
.VITE_OPENAI_API_ENDPOINT;
|
||||||
|
export const LLM_MODEL_NAME: string = import.meta.env.VITE_LLM_MODEL_NAME;
|
||||||
export const IS_CHARTDB_IO: boolean =
|
export const IS_CHARTDB_IO: boolean =
|
||||||
import.meta.env.VITE_IS_CHARTDB_IO === 'true';
|
import.meta.env.VITE_IS_CHARTDB_IO === 'true';
|
||||||
export const APP_URL: string = import.meta.env.VITE_APP_URL;
|
export const APP_URL: string = import.meta.env.VITE_APP_URL;
|
||||||
export const HOST_URL: string = import.meta.env.VITE_HOST_URL ?? '';
|
export const HOST_URL: string = import.meta.env.VITE_HOST_URL ?? '';
|
||||||
|
export const HIDE_BUCKLE_DOT_DEV: boolean =
|
||||||
|
(window?.env?.HIDE_BUCKLE_DOT_DEV ??
|
||||||
|
import.meta.env.VITE_HIDE_BUCKLE_DOT_DEV) === 'true';
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import { useChartDB } from '@/hooks/use-chartdb';
|
|||||||
import { useLayout } from '@/hooks/use-layout';
|
import { useLayout } from '@/hooks/use-layout';
|
||||||
import { cloneTable } from '@/lib/clone';
|
import { cloneTable } from '@/lib/clone';
|
||||||
import type { DBTable } from '@/lib/domain/db-table';
|
import type { DBTable } from '@/lib/domain/db-table';
|
||||||
import { Copy, Pencil, Trash2 } from 'lucide-react';
|
import { Copy, Pencil, Trash2, Workflow } from 'lucide-react';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
|
|
||||||
export interface TableNodeContextMenuProps {
|
export interface TableNodeContextMenuProps {
|
||||||
table: DBTable;
|
table: DBTable;
|
||||||
@@ -24,6 +25,7 @@ export const TableNodeContextMenu: React.FC<
|
|||||||
const { openTableFromSidebar } = useLayout();
|
const { openTableFromSidebar } = useLayout();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isMd: isDesktop } = useBreakpoint('md');
|
const { isMd: isDesktop } = useBreakpoint('md');
|
||||||
|
const { openCreateRelationshipDialog } = useDialog();
|
||||||
|
|
||||||
const duplicateTableHandler = useCallback(() => {
|
const duplicateTableHandler = useCallback(() => {
|
||||||
const clonedTable = cloneTable(table);
|
const clonedTable = cloneTable(table);
|
||||||
@@ -43,6 +45,12 @@ export const TableNodeContextMenu: React.FC<
|
|||||||
removeTable(table.id);
|
removeTable(table.id);
|
||||||
}, [removeTable, table.id]);
|
}, [removeTable, table.id]);
|
||||||
|
|
||||||
|
const addRelationshipHandler = useCallback(() => {
|
||||||
|
openCreateRelationshipDialog({
|
||||||
|
sourceTableId: table.id,
|
||||||
|
});
|
||||||
|
}, [openCreateRelationshipDialog, table.id]);
|
||||||
|
|
||||||
if (!isDesktop || readonly) {
|
if (!isDesktop || readonly) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
@@ -64,6 +72,13 @@ export const TableNodeContextMenu: React.FC<
|
|||||||
<span>{t('table_node_context_menu.duplicate_table')}</span>
|
<span>{t('table_node_context_menu.duplicate_table')}</span>
|
||||||
<Copy className="size-3.5" />
|
<Copy className="size-3.5" />
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={addRelationshipHandler}
|
||||||
|
className="flex justify-between gap-3"
|
||||||
|
>
|
||||||
|
<span>{t('table_node_context_menu.add_relationship')}</span>
|
||||||
|
<Workflow className="size-3.5" />
|
||||||
|
</ContextMenuItem>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={removeTableHandler}
|
onClick={removeTableHandler}
|
||||||
className="flex justify-between gap-3"
|
className="flex justify-between gap-3"
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import { Button } from '@/components/button/button';
|
|||||||
import {
|
import {
|
||||||
ChevronsLeftRight,
|
ChevronsLeftRight,
|
||||||
ChevronsRightLeft,
|
ChevronsRightLeft,
|
||||||
Pencil,
|
|
||||||
Table2,
|
Table2,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
Check,
|
Check,
|
||||||
|
CircleDotDashed,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Label } from '@/components/label/label';
|
import { Label } from '@/components/label/label';
|
||||||
import type { DBTable } from '@/lib/domain/db-table';
|
import type { DBTable } from '@/lib/domain/db-table';
|
||||||
@@ -247,7 +247,7 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
|
|||||||
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
|
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
|
||||||
onClick={openTableInEditor}
|
onClick={openTableInEditor}
|
||||||
>
|
>
|
||||||
<Pencil className="size-4" />
|
<CircleDotDashed className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{editMode ? null : (
|
{editMode ? null : (
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import { Helmet } from 'react-helmet-async';
|
|||||||
import { useStorage } from '@/hooks/use-storage';
|
import { useStorage } from '@/hooks/use-storage';
|
||||||
import { AlertProvider } from '@/context/alert-context/alert-provider';
|
import { AlertProvider } from '@/context/alert-context/alert-provider';
|
||||||
import { CanvasProvider } from '@/context/canvas-context/canvas-provider';
|
import { CanvasProvider } from '@/context/canvas-context/canvas-provider';
|
||||||
|
import { HIDE_BUCKLE_DOT_DEV } from '@/lib/env';
|
||||||
|
|
||||||
const OPEN_STAR_US_AFTER_SECONDS = 30;
|
const OPEN_STAR_US_AFTER_SECONDS = 30;
|
||||||
const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
|
const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
|
||||||
@@ -153,6 +154,10 @@ const EditorPageComponent: React.FC = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (HIDE_BUCKLE_DOT_DEV) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!currentDiagram?.id || githubRepoOpened) {
|
if (!currentDiagram?.id || githubRepoOpened) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -174,6 +179,10 @@ const EditorPageComponent: React.FC = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (HIDE_BUCKLE_DOT_DEV) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!currentDiagram?.id) {
|
if (!currentDiagram?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,10 +98,16 @@ export const RelationshipListItemContent: React.FC<
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<div className="truncate text-left text-sm">
|
<div className="truncate text-left text-sm">
|
||||||
|
{sourceTable?.schema
|
||||||
|
? `${sourceTable.schema}.`
|
||||||
|
: ''}
|
||||||
{sourceTable?.name}({sourceField?.name})
|
{sourceTable?.name}({sourceField?.name})
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
|
{sourceTable?.schema
|
||||||
|
? `${sourceTable.schema}.`
|
||||||
|
: ''}
|
||||||
{sourceTable?.name}({sourceField?.name})
|
{sourceTable?.name}({sourceField?.name})
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -117,11 +123,17 @@ export const RelationshipListItemContent: React.FC<
|
|||||||
</div>
|
</div>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<div className="truncate text-left text-sm ">
|
<div className="truncate text-left text-sm">
|
||||||
|
{targetTable?.schema
|
||||||
|
? `${targetTable.schema}.`
|
||||||
|
: ''}
|
||||||
{targetTable?.name}({targetField?.name})
|
{targetTable?.name}({targetField?.name})
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
|
{targetTable?.schema
|
||||||
|
? `${targetTable.schema}.`
|
||||||
|
: ''}
|
||||||
{targetTable?.name}({targetField?.name})
|
{targetTable?.name}({targetField?.name})
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Toggle } from '@/components/toggle/toggle';
|
||||||
|
|
||||||
|
export const TableIndexToggle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof Toggle>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof Toggle>
|
||||||
|
>((props, ref) => {
|
||||||
|
return (
|
||||||
|
<Toggle
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
variant="default"
|
||||||
|
className="h-8 w-[32px] p-2 text-xs text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TableIndexToggle.displayName = Toggle.displayName;
|
||||||
@@ -14,6 +14,12 @@ import { Label } from '@/components/label/label';
|
|||||||
import { Input } from '@/components/input/input';
|
import { Input } from '@/components/input/input';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SelectBox } from '@/components/select-box/select-box';
|
import { SelectBox } from '@/components/select-box/select-box';
|
||||||
|
import { TableIndexToggle } from './table-index-toggle';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/tooltip/tooltip';
|
||||||
|
|
||||||
export interface TableIndexProps {
|
export interface TableIndexProps {
|
||||||
index: DBIndex;
|
index: DBIndex;
|
||||||
@@ -54,7 +60,28 @@ export const TableIndex: React.FC<TableIndexProps> = ({
|
|||||||
)}
|
)}
|
||||||
keepOrder
|
keepOrder
|
||||||
/>
|
/>
|
||||||
<div className="flex shrink-0">
|
<div className="flex shrink-0 gap-1">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span>
|
||||||
|
<TableIndexToggle
|
||||||
|
pressed={index.unique}
|
||||||
|
onPressedChange={(value) =>
|
||||||
|
updateIndex({
|
||||||
|
unique: !!value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
U
|
||||||
|
</TableIndexToggle>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{t(
|
||||||
|
'side_panel.tables_section.table.index_actions.unique'
|
||||||
|
)}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
|
|||||||
<EllipsisVertical />
|
<EllipsisVertical />
|
||||||
</ListItemHeaderButton>
|
</ListItemHeaderButton>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="w-fit">
|
<DropdownMenuContent className="w-fit min-w-40">
|
||||||
<DropdownMenuLabel>
|
<DropdownMenuLabel>
|
||||||
{t(
|
{t(
|
||||||
'side_panel.tables_section.table.table_actions.title'
|
'side_panel.tables_section.table.table_actions.title'
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { DiagramName } from './diagram-name';
|
|||||||
import { LastSaved } from './last-saved';
|
import { LastSaved } from './last-saved';
|
||||||
import { LanguageNav } from './language-nav/language-nav';
|
import { LanguageNav } from './language-nav/language-nav';
|
||||||
import { Menu } from './menu/menu';
|
import { Menu } from './menu/menu';
|
||||||
|
import { HIDE_BUCKLE_DOT_DEV } from '@/lib/env';
|
||||||
|
|
||||||
export interface TopNavbarProps {}
|
export interface TopNavbarProps {}
|
||||||
|
|
||||||
@@ -30,6 +31,10 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const renderGetBuckleButton = useCallback(() => {
|
const renderGetBuckleButton = useCallback(() => {
|
||||||
|
if (HIDE_BUCKLE_DOT_DEV) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="gradient-background relative inline-flex items-center justify-center overflow-hidden rounded-lg p-0.5 text-base text-gray-700 focus:outline-none focus:ring-0"
|
className="gradient-background relative inline-flex items-center justify-center overflow-hidden rounded-lg p-0.5 text-base text-gray-700 focus:outline-none focus:ring-0"
|
||||||
|
|||||||
Reference in New Issue
Block a user