mirror of
				https://github.com/chartdb/chartdb.git
				synced 2025-11-04 05:53:15 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			jf/edit-cl
			...
			jf/add_dup
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					76f9662b80 | 
							
								
								
									
										2
									
								
								.github/workflows/cla.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/cla.yaml
									
									
									
									
										vendored
									
									
								
							@@ -7,7 +7,7 @@ on:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  actions: write
 | 
			
		||||
  contents: read
 | 
			
		||||
  contents: write # this can be 'read' if the signatures are in remote repository
 | 
			
		||||
  pull-requests: write
 | 
			
		||||
  statuses: write
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										50
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,55 +1,5 @@
 | 
			
		||||
# Changelog
 | 
			
		||||
 | 
			
		||||
## [1.15.1](https://github.com/chartdb/chartdb/compare/v1.15.0...v1.15.1) (2025-08-27)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* add actions menu to diagram list + add duplicate diagram ([#876](https://github.com/chartdb/chartdb/issues/876)) ([abd2a6c](https://github.com/chartdb/chartdb/commit/abd2a6ccbe1aa63db44ec28b3eff525cc5d3f8b0))
 | 
			
		||||
* **custom-types:** Make schema optional ([#866](https://github.com/chartdb/chartdb/issues/866)) ([60c5675](https://github.com/chartdb/chartdb/commit/60c5675cbfe205859d2d0c9848d8345a0a854671))
 | 
			
		||||
* handle quoted identifiers with special characters in SQL import/export and DBML generation ([#877](https://github.com/chartdb/chartdb/issues/877)) ([66b0863](https://github.com/chartdb/chartdb/commit/66b086378cd63347acab5fc7f13db7db4feaa872))
 | 
			
		||||
 | 
			
		||||
## [1.15.0](https://github.com/chartdb/chartdb/compare/v1.14.0...v1.15.0) (2025-08-26)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* add auto increment support for fields with database-specific export ([#851](https://github.com/chartdb/chartdb/issues/851)) ([c77c983](https://github.com/chartdb/chartdb/commit/c77c983989ae38a6b1139dd9015f4f3178d4e103))
 | 
			
		||||
* **filter:** filter tables by areas ([#836](https://github.com/chartdb/chartdb/issues/836)) ([e9c5442](https://github.com/chartdb/chartdb/commit/e9c5442d9df2beadad78187da3363bb6406636c4))
 | 
			
		||||
* include foreign keys inline in SQLite CREATE TABLE statements ([#833](https://github.com/chartdb/chartdb/issues/833)) ([43fc1d7](https://github.com/chartdb/chartdb/commit/43fc1d7fc26876b22c61405f6c3df89fc66b7992))
 | 
			
		||||
* **postgres:** add support hash index types ([#812](https://github.com/chartdb/chartdb/issues/812)) ([0d623a8](https://github.com/chartdb/chartdb/commit/0d623a86b1cb7cbd223e10ad23d09fc0e106c006))
 | 
			
		||||
* support create views ([#868](https://github.com/chartdb/chartdb/issues/868)) ([0a5874a](https://github.com/chartdb/chartdb/commit/0a5874a69b6323145430c1fb4e3482ac7da4916c))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* area filter logic ([#861](https://github.com/chartdb/chartdb/issues/861)) ([73daf0d](https://github.com/chartdb/chartdb/commit/73daf0df2142a29c2eeebe60b43198bcca869026))
 | 
			
		||||
* **area filter:** fix dragging tables over filtered areas ([#842](https://github.com/chartdb/chartdb/issues/842)) ([19fd94c](https://github.com/chartdb/chartdb/commit/19fd94c6bde3a9ec749cd1ccacbedb6abc96d037))
 | 
			
		||||
* **canvas:** delete table + area together bug ([#859](https://github.com/chartdb/chartdb/issues/859)) ([b697e26](https://github.com/chartdb/chartdb/commit/b697e26170da95dcb427ff6907b6f663c98ba59f))
 | 
			
		||||
* **cla:** Harden action ([#867](https://github.com/chartdb/chartdb/issues/867)) ([ad8e344](https://github.com/chartdb/chartdb/commit/ad8e34483fdf4226de76c9e7768bc2ba9bf154de))
 | 
			
		||||
* DBML export error with multi-line table comments for SQL Server ([#852](https://github.com/chartdb/chartdb/issues/852)) ([0545b41](https://github.com/chartdb/chartdb/commit/0545b411407b2449220d10981a04c3e368a90ca3))
 | 
			
		||||
* filter to default schema on load new diagram ([#849](https://github.com/chartdb/chartdb/issues/849)) ([712bdf5](https://github.com/chartdb/chartdb/commit/712bdf5b958919d940c4f2a1c3b7c7e969990f02))
 | 
			
		||||
* **filter:** filter toggle issues with no schemas dbs ([#856](https://github.com/chartdb/chartdb/issues/856)) ([d0dee84](https://github.com/chartdb/chartdb/commit/d0dee849702161d979b4f589a7e6579fbaade22d))
 | 
			
		||||
* **filters:** refactor diagram filters - remove schema filter ([#832](https://github.com/chartdb/chartdb/issues/832)) ([4f1d329](https://github.com/chartdb/chartdb/commit/4f1d3295c09782ab46d82ce21b662032aa094f22))
 | 
			
		||||
* for sqlite import - add more types & include type parameters ([#834](https://github.com/chartdb/chartdb/issues/834)) ([5936500](https://github.com/chartdb/chartdb/commit/5936500ca00a57b3f161616264c26152a13c36d2))
 | 
			
		||||
* improve creating view to table dependency ([#874](https://github.com/chartdb/chartdb/issues/874)) ([44be48f](https://github.com/chartdb/chartdb/commit/44be48ff3ad1361279331c17364090b13af471a1))
 | 
			
		||||
* initially show filter when filter active ([#853](https://github.com/chartdb/chartdb/issues/853)) ([ab4845c](https://github.com/chartdb/chartdb/commit/ab4845c7728e6e0b2d852f8005921fd90630eef9))
 | 
			
		||||
* **menu:** clear file menu ([#843](https://github.com/chartdb/chartdb/issues/843)) ([eaebe34](https://github.com/chartdb/chartdb/commit/eaebe3476824af779214a354b3e991923a22f195))
 | 
			
		||||
* merge relationship & dependency sections to ref section ([#870](https://github.com/chartdb/chartdb/issues/870)) ([ec3719e](https://github.com/chartdb/chartdb/commit/ec3719ebce4664b2aa6e3322fb3337e72bc21015))
 | 
			
		||||
* move dbml into sections menu ([#862](https://github.com/chartdb/chartdb/issues/862)) ([2531a70](https://github.com/chartdb/chartdb/commit/2531a7023f36ef29e67c0da6bca4fd0346b18a51))
 | 
			
		||||
* open filter by default ([#863](https://github.com/chartdb/chartdb/issues/863)) ([7e0fdd1](https://github.com/chartdb/chartdb/commit/7e0fdd1595bffe29e769d29602d04f42edfe417e))
 | 
			
		||||
* preserve composite primary key constraint names across import/export workflows ([#869](https://github.com/chartdb/chartdb/issues/869)) ([215d579](https://github.com/chartdb/chartdb/commit/215d57979df2e91fa61988acff590daad2f4e771))
 | 
			
		||||
* prevent false change detection in DBML editor by stripping public schema on import ([#858](https://github.com/chartdb/chartdb/issues/858)) ([0aaa451](https://github.com/chartdb/chartdb/commit/0aaa451479911d047e4cc83f063afa68a122ba9b))
 | 
			
		||||
* remove unnecessary space ([#845](https://github.com/chartdb/chartdb/issues/845)) ([f1a4298](https://github.com/chartdb/chartdb/commit/f1a429836221aacdda73b91665bf33ffb011164c))
 | 
			
		||||
* reorder with areas ([#846](https://github.com/chartdb/chartdb/issues/846)) ([d7c9536](https://github.com/chartdb/chartdb/commit/d7c9536272cf1d42104b7064ea448d128d091a20))
 | 
			
		||||
* **select-box:** fix select box issue in dialog ([#840](https://github.com/chartdb/chartdb/issues/840)) ([cb2ba66](https://github.com/chartdb/chartdb/commit/cb2ba66233c8c04e2d963cf2d210499d8512a268))
 | 
			
		||||
* set default filter only if has more than 1 schemas ([#855](https://github.com/chartdb/chartdb/issues/855)) ([b4ccfcd](https://github.com/chartdb/chartdb/commit/b4ccfcdcde2f3565b0d3bbc46fa1715feb6cd925))
 | 
			
		||||
* show default schema first ([#854](https://github.com/chartdb/chartdb/issues/854)) ([1759b0b](https://github.com/chartdb/chartdb/commit/1759b0b9f271ed25f7c71f26c344e3f1d97bc5fb))
 | 
			
		||||
* **sidebar:** add titles to sidebar ([#844](https://github.com/chartdb/chartdb/issues/844)) ([b8f2141](https://github.com/chartdb/chartdb/commit/b8f2141bd2e67272030896fb4009a7925f9f09e4))
 | 
			
		||||
* **sql-import:** fix SQL Server foreign key parsing for tables without schema prefix ([#857](https://github.com/chartdb/chartdb/issues/857)) ([04d91c6](https://github.com/chartdb/chartdb/commit/04d91c67b1075e94948f75186878e633df7abbca))
 | 
			
		||||
* **table colors:** switch to default table color ([#841](https://github.com/chartdb/chartdb/issues/841)) ([0da3cae](https://github.com/chartdb/chartdb/commit/0da3caeeac37926dd22f38d98423611f39c0412a))
 | 
			
		||||
* update filter on adding table ([#838](https://github.com/chartdb/chartdb/issues/838)) ([41ba251](https://github.com/chartdb/chartdb/commit/41ba25137789dda25266178cd7c96ecbb37e62a4))
 | 
			
		||||
 | 
			
		||||
## [1.14.0](https://github.com/chartdb/chartdb/compare/v1.13.2...v1.14.0) (2025-08-04)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										281
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										281
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,15 +1,15 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "chartdb",
 | 
			
		||||
    "version": "1.15.1",
 | 
			
		||||
    "version": "1.14.0",
 | 
			
		||||
    "lockfileVersion": 3,
 | 
			
		||||
    "requires": true,
 | 
			
		||||
    "packages": {
 | 
			
		||||
        "": {
 | 
			
		||||
            "name": "chartdb",
 | 
			
		||||
            "version": "1.15.1",
 | 
			
		||||
            "version": "1.14.0",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@ai-sdk/openai": "^0.0.51",
 | 
			
		||||
                "@dbml/core": "^3.13.9",
 | 
			
		||||
                "@dbml/core": "^3.9.5",
 | 
			
		||||
                "@dnd-kit/sortable": "^8.0.0",
 | 
			
		||||
                "@monaco-editor/react": "^4.6.0",
 | 
			
		||||
                "@radix-ui/react-accordion": "^1.2.0",
 | 
			
		||||
@@ -586,15 +586,15 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@babel/code-frame": {
 | 
			
		||||
            "version": "7.27.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
 | 
			
		||||
            "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
 | 
			
		||||
            "version": "7.26.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
 | 
			
		||||
            "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@babel/helper-validator-identifier": "^7.27.1",
 | 
			
		||||
                "@babel/helper-validator-identifier": "^7.25.9",
 | 
			
		||||
                "js-tokens": "^4.0.0",
 | 
			
		||||
                "picocolors": "^1.1.1"
 | 
			
		||||
                "picocolors": "^1.0.0"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=6.9.0"
 | 
			
		||||
@@ -738,18 +738,18 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@babel/helper-string-parser": {
 | 
			
		||||
            "version": "7.27.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
 | 
			
		||||
            "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
 | 
			
		||||
            "version": "7.25.9",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
 | 
			
		||||
            "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=6.9.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@babel/helper-validator-identifier": {
 | 
			
		||||
            "version": "7.27.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
 | 
			
		||||
            "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
 | 
			
		||||
            "version": "7.25.9",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
 | 
			
		||||
            "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=6.9.0"
 | 
			
		||||
@@ -766,26 +766,26 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@babel/helpers": {
 | 
			
		||||
            "version": "7.28.4",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
 | 
			
		||||
            "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
 | 
			
		||||
            "version": "7.26.7",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz",
 | 
			
		||||
            "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@babel/template": "^7.27.2",
 | 
			
		||||
                "@babel/types": "^7.28.4"
 | 
			
		||||
                "@babel/template": "^7.25.9",
 | 
			
		||||
                "@babel/types": "^7.26.7"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=6.9.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@babel/parser": {
 | 
			
		||||
            "version": "7.28.4",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
 | 
			
		||||
            "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
 | 
			
		||||
            "version": "7.26.7",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
 | 
			
		||||
            "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@babel/types": "^7.28.4"
 | 
			
		||||
                "@babel/types": "^7.26.7"
 | 
			
		||||
            },
 | 
			
		||||
            "bin": {
 | 
			
		||||
                "parser": "bin/babel-parser.js"
 | 
			
		||||
@@ -827,24 +827,27 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@babel/runtime": {
 | 
			
		||||
            "version": "7.28.4",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
 | 
			
		||||
            "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
 | 
			
		||||
            "version": "7.26.7",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
 | 
			
		||||
            "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "regenerator-runtime": "^0.14.0"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=6.9.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@babel/template": {
 | 
			
		||||
            "version": "7.27.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
 | 
			
		||||
            "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
 | 
			
		||||
            "version": "7.25.9",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
 | 
			
		||||
            "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@babel/code-frame": "^7.27.1",
 | 
			
		||||
                "@babel/parser": "^7.27.2",
 | 
			
		||||
                "@babel/types": "^7.27.1"
 | 
			
		||||
                "@babel/code-frame": "^7.25.9",
 | 
			
		||||
                "@babel/parser": "^7.25.9",
 | 
			
		||||
                "@babel/types": "^7.25.9"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=6.9.0"
 | 
			
		||||
@@ -880,25 +883,25 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@babel/types": {
 | 
			
		||||
            "version": "7.28.4",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
 | 
			
		||||
            "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
 | 
			
		||||
            "version": "7.26.7",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
 | 
			
		||||
            "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@babel/helper-string-parser": "^7.27.1",
 | 
			
		||||
                "@babel/helper-validator-identifier": "^7.27.1"
 | 
			
		||||
                "@babel/helper-string-parser": "^7.25.9",
 | 
			
		||||
                "@babel/helper-validator-identifier": "^7.25.9"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=6.9.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@dbml/core": {
 | 
			
		||||
            "version": "3.13.9",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@dbml/core/-/core-3.13.9.tgz",
 | 
			
		||||
            "integrity": "sha512-JgJ470yuTZU7tP64ZL5FpEh7zSXjSoKzkARmin8iVVhdsNM8Nq4e+FFhG6J6acPtGHtoLahOs9LqrC17B9MqYg==",
 | 
			
		||||
            "version": "3.9.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@dbml/core/-/core-3.9.5.tgz",
 | 
			
		||||
            "integrity": "sha512-lX/G5qer42irufv5rvx6Y3ISV2ZLDRlxj8R+OZMdhC6wAw0VYPYIts23MdMFPY39Iay0TDtfmwsbOsVy/yjSIg==",
 | 
			
		||||
            "license": "Apache-2.0",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@dbml/parse": "^3.13.9",
 | 
			
		||||
                "@dbml/parse": "^3.9.5",
 | 
			
		||||
                "antlr4": "^4.13.1",
 | 
			
		||||
                "lodash": "^4.17.15",
 | 
			
		||||
                "parsimmon": "^1.13.0",
 | 
			
		||||
@@ -909,15 +912,15 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@dbml/parse": {
 | 
			
		||||
            "version": "3.13.9",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@dbml/parse/-/parse-3.13.9.tgz",
 | 
			
		||||
            "integrity": "sha512-JMfOxWquXMZpF/MTLy2xWLImx3z9D0t67T7x/BT892WvmhM+9cnJHFA2URT1NXu9jdajbTTFuoWSyzdsfNpaRw==",
 | 
			
		||||
            "version": "3.9.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@dbml/parse/-/parse-3.9.5.tgz",
 | 
			
		||||
            "integrity": "sha512-z8MjBYDFiYf7WtsagwGATEye81xQcO9VXFzttSjdJ+wgdSFzFSex9letJPIMIcYXBkm4Fg5qLDk+G9uq/413Dg==",
 | 
			
		||||
            "license": "Apache-2.0",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "lodash-es": "^4.17.21"
 | 
			
		||||
                "lodash": "^4.17.21"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=18"
 | 
			
		||||
            "peerDependencies": {
 | 
			
		||||
                "lodash": "^4.17.21"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@dnd-kit/accessibility": {
 | 
			
		||||
@@ -1367,9 +1370,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@eslint-community/eslint-utils": {
 | 
			
		||||
            "version": "4.9.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
 | 
			
		||||
            "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
 | 
			
		||||
            "version": "4.4.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
 | 
			
		||||
            "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@@ -1414,9 +1417,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@eslint/config-array": {
 | 
			
		||||
            "version": "0.21.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
 | 
			
		||||
            "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
 | 
			
		||||
            "version": "0.19.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz",
 | 
			
		||||
            "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "Apache-2.0",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@@ -1428,20 +1431,10 @@
 | 
			
		||||
                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@eslint/config-helpers": {
 | 
			
		||||
            "version": "0.3.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
 | 
			
		||||
            "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "Apache-2.0",
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@eslint/core": {
 | 
			
		||||
            "version": "0.15.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
 | 
			
		||||
            "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
 | 
			
		||||
            "version": "0.10.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
 | 
			
		||||
            "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "Apache-2.0",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@@ -1452,9 +1445,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@eslint/eslintrc": {
 | 
			
		||||
            "version": "3.3.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
 | 
			
		||||
            "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
 | 
			
		||||
            "version": "3.2.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
 | 
			
		||||
            "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@@ -1489,16 +1482,13 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@eslint/js": {
 | 
			
		||||
            "version": "9.35.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz",
 | 
			
		||||
            "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==",
 | 
			
		||||
            "version": "9.19.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
 | 
			
		||||
            "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": {
 | 
			
		||||
                "url": "https://eslint.org/donate"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@eslint/object-schema": {
 | 
			
		||||
@@ -1512,13 +1502,13 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@eslint/plugin-kit": {
 | 
			
		||||
            "version": "0.3.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
 | 
			
		||||
            "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
 | 
			
		||||
            "version": "0.2.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
 | 
			
		||||
            "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "Apache-2.0",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@eslint/core": "^0.15.2",
 | 
			
		||||
                "@eslint/core": "^0.10.0",
 | 
			
		||||
                "levn": "^0.4.1"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@@ -1616,9 +1606,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@humanwhocodes/retry": {
 | 
			
		||||
            "version": "0.4.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
 | 
			
		||||
            "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
 | 
			
		||||
            "version": "0.4.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
 | 
			
		||||
            "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "Apache-2.0",
 | 
			
		||||
            "engines": {
 | 
			
		||||
@@ -4286,6 +4276,12 @@
 | 
			
		||||
                "@types/deep-eql": "*"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@types/cookie": {
 | 
			
		||||
            "version": "0.6.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
 | 
			
		||||
            "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
 | 
			
		||||
            "license": "MIT"
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@types/d3-color": {
 | 
			
		||||
            "version": "3.1.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
 | 
			
		||||
@@ -4557,9 +4553,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
 | 
			
		||||
            "version": "2.0.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
 | 
			
		||||
            "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
 | 
			
		||||
            "version": "2.0.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
 | 
			
		||||
            "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@@ -4965,9 +4961,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/acorn": {
 | 
			
		||||
            "version": "8.15.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
 | 
			
		||||
            "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
 | 
			
		||||
            "version": "8.14.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
 | 
			
		||||
            "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "bin": {
 | 
			
		||||
                "acorn": "bin/acorn"
 | 
			
		||||
@@ -5490,9 +5486,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/brace-expansion": {
 | 
			
		||||
            "version": "1.1.12",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
 | 
			
		||||
            "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
 | 
			
		||||
            "version": "1.1.11",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
 | 
			
		||||
            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@@ -6582,23 +6578,22 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/eslint": {
 | 
			
		||||
            "version": "9.35.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz",
 | 
			
		||||
            "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==",
 | 
			
		||||
            "version": "9.19.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
 | 
			
		||||
            "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@eslint-community/eslint-utils": "^4.8.0",
 | 
			
		||||
                "@eslint-community/eslint-utils": "^4.2.0",
 | 
			
		||||
                "@eslint-community/regexpp": "^4.12.1",
 | 
			
		||||
                "@eslint/config-array": "^0.21.0",
 | 
			
		||||
                "@eslint/config-helpers": "^0.3.1",
 | 
			
		||||
                "@eslint/core": "^0.15.2",
 | 
			
		||||
                "@eslint/eslintrc": "^3.3.1",
 | 
			
		||||
                "@eslint/js": "9.35.0",
 | 
			
		||||
                "@eslint/plugin-kit": "^0.3.5",
 | 
			
		||||
                "@eslint/config-array": "^0.19.0",
 | 
			
		||||
                "@eslint/core": "^0.10.0",
 | 
			
		||||
                "@eslint/eslintrc": "^3.2.0",
 | 
			
		||||
                "@eslint/js": "9.19.0",
 | 
			
		||||
                "@eslint/plugin-kit": "^0.2.5",
 | 
			
		||||
                "@humanfs/node": "^0.16.6",
 | 
			
		||||
                "@humanwhocodes/module-importer": "^1.0.1",
 | 
			
		||||
                "@humanwhocodes/retry": "^0.4.2",
 | 
			
		||||
                "@humanwhocodes/retry": "^0.4.1",
 | 
			
		||||
                "@types/estree": "^1.0.6",
 | 
			
		||||
                "@types/json-schema": "^7.0.15",
 | 
			
		||||
                "ajv": "^6.12.4",
 | 
			
		||||
@@ -6606,9 +6601,9 @@
 | 
			
		||||
                "cross-spawn": "^7.0.6",
 | 
			
		||||
                "debug": "^4.3.2",
 | 
			
		||||
                "escape-string-regexp": "^4.0.0",
 | 
			
		||||
                "eslint-scope": "^8.4.0",
 | 
			
		||||
                "eslint-visitor-keys": "^4.2.1",
 | 
			
		||||
                "espree": "^10.4.0",
 | 
			
		||||
                "eslint-scope": "^8.2.0",
 | 
			
		||||
                "eslint-visitor-keys": "^4.2.0",
 | 
			
		||||
                "espree": "^10.3.0",
 | 
			
		||||
                "esquery": "^1.5.0",
 | 
			
		||||
                "esutils": "^2.0.2",
 | 
			
		||||
                "fast-deep-equal": "^3.1.3",
 | 
			
		||||
@@ -6817,9 +6812,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/eslint-scope": {
 | 
			
		||||
            "version": "8.4.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
 | 
			
		||||
            "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
 | 
			
		||||
            "version": "8.2.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
 | 
			
		||||
            "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "BSD-2-Clause",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@@ -6847,9 +6842,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/eslint/node_modules/eslint-visitor-keys": {
 | 
			
		||||
            "version": "4.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
 | 
			
		||||
            "version": "4.2.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
 | 
			
		||||
            "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "Apache-2.0",
 | 
			
		||||
            "engines": {
 | 
			
		||||
@@ -6867,15 +6862,15 @@
 | 
			
		||||
            "peer": true
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/espree": {
 | 
			
		||||
            "version": "10.4.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
 | 
			
		||||
            "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
 | 
			
		||||
            "version": "10.3.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
 | 
			
		||||
            "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "BSD-2-Clause",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "acorn": "^8.15.0",
 | 
			
		||||
                "acorn": "^8.14.0",
 | 
			
		||||
                "acorn-jsx": "^5.3.2",
 | 
			
		||||
                "eslint-visitor-keys": "^4.2.1"
 | 
			
		||||
                "eslint-visitor-keys": "^4.2.0"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
			
		||||
@@ -6885,9 +6880,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/espree/node_modules/eslint-visitor-keys": {
 | 
			
		||||
            "version": "4.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
 | 
			
		||||
            "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
 | 
			
		||||
            "version": "4.2.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
 | 
			
		||||
            "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "Apache-2.0",
 | 
			
		||||
            "engines": {
 | 
			
		||||
@@ -7370,9 +7365,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/glob/node_modules/brace-expansion": {
 | 
			
		||||
            "version": "2.0.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
 | 
			
		||||
            "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
 | 
			
		||||
            "version": "2.0.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
 | 
			
		||||
            "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "balanced-match": "^1.0.0"
 | 
			
		||||
@@ -8445,12 +8440,6 @@
 | 
			
		||||
            "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
 | 
			
		||||
            "license": "MIT"
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/lodash-es": {
 | 
			
		||||
            "version": "4.17.21",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
 | 
			
		||||
            "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
 | 
			
		||||
            "license": "MIT"
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/lodash.merge": {
 | 
			
		||||
            "version": "4.6.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
 | 
			
		||||
@@ -9611,13 +9600,15 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/react-router": {
 | 
			
		||||
            "version": "7.8.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.2.tgz",
 | 
			
		||||
            "integrity": "sha512-7M2fR1JbIZ/jFWqelpvSZx+7vd7UlBTfdZqf6OSdF9g6+sfdqJDAWcak6ervbHph200ePlu+7G8LdoiC3ReyAQ==",
 | 
			
		||||
            "version": "7.1.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.5.tgz",
 | 
			
		||||
            "integrity": "sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@types/cookie": "^0.6.0",
 | 
			
		||||
                "cookie": "^1.0.1",
 | 
			
		||||
                "set-cookie-parser": "^2.6.0"
 | 
			
		||||
                "set-cookie-parser": "^2.6.0",
 | 
			
		||||
                "turbo-stream": "2.4.0"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=20.0.0"
 | 
			
		||||
@@ -9633,12 +9624,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/react-router-dom": {
 | 
			
		||||
            "version": "7.8.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.2.tgz",
 | 
			
		||||
            "integrity": "sha512-Z4VM5mKDipal2jQ385H6UBhiiEDlnJPx6jyWsTYoZQdl5TrjxEV2a9yl3Fi60NBJxYzOTGTTHXPi0pdizvTwow==",
 | 
			
		||||
            "version": "7.1.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.5.tgz",
 | 
			
		||||
            "integrity": "sha512-/4f9+up0Qv92D3bB8iN5P1s3oHAepSGa9h5k6tpTFlixTTskJZwKGhJ6vRJ277tLD1zuaZTt95hyGWV1Z37csQ==",
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "react-router": "7.8.2"
 | 
			
		||||
                "react-router": "7.1.5"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=20.0.0"
 | 
			
		||||
@@ -9769,6 +9760,12 @@
 | 
			
		||||
                "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/regenerator-runtime": {
 | 
			
		||||
            "version": "0.14.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
 | 
			
		||||
            "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
 | 
			
		||||
            "license": "MIT"
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/regexp.prototype.flags": {
 | 
			
		||||
            "version": "1.5.4",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
 | 
			
		||||
@@ -11029,6 +11026,12 @@
 | 
			
		||||
            "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
 | 
			
		||||
            "license": "0BSD"
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/turbo-stream": {
 | 
			
		||||
            "version": "2.4.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
 | 
			
		||||
            "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
 | 
			
		||||
            "license": "ISC"
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/type-check": {
 | 
			
		||||
            "version": "0.4.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
 | 
			
		||||
@@ -11310,9 +11313,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/vite": {
 | 
			
		||||
            "version": "5.4.20",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
 | 
			
		||||
            "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
 | 
			
		||||
            "version": "5.4.14",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
 | 
			
		||||
            "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "chartdb",
 | 
			
		||||
    "private": true,
 | 
			
		||||
    "version": "1.15.1",
 | 
			
		||||
    "version": "1.14.0",
 | 
			
		||||
    "type": "module",
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "dev": "vite",
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
    },
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@ai-sdk/openai": "^0.0.51",
 | 
			
		||||
        "@dbml/core": "^3.13.9",
 | 
			
		||||
        "@dbml/core": "^3.9.5",
 | 
			
		||||
        "@dnd-kit/sortable": "^8.0.0",
 | 
			
		||||
        "@monaco-editor/react": "^4.6.0",
 | 
			
		||||
        "@radix-ui/react-accordion": "^1.2.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -11,26 +11,18 @@ import {
 | 
			
		||||
    DropdownMenuItem,
 | 
			
		||||
    DropdownMenuTrigger,
 | 
			
		||||
} from '@/components/dropdown-menu/dropdown-menu';
 | 
			
		||||
import {
 | 
			
		||||
    Tooltip,
 | 
			
		||||
    TooltipContent,
 | 
			
		||||
    TooltipTrigger,
 | 
			
		||||
} from '@/components/tooltip/tooltip';
 | 
			
		||||
 | 
			
		||||
export interface ButtonAlternative {
 | 
			
		||||
    label: string;
 | 
			
		||||
    onClick: () => void;
 | 
			
		||||
    disabled?: boolean;
 | 
			
		||||
    icon?: React.ReactNode;
 | 
			
		||||
    className?: string;
 | 
			
		||||
    tooltip?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ButtonWithAlternativesProps
 | 
			
		||||
    extends React.ButtonHTMLAttributes<HTMLButtonElement>,
 | 
			
		||||
        VariantProps<typeof buttonVariants> {
 | 
			
		||||
    asChild?: boolean;
 | 
			
		||||
    alternatives: Array<ButtonAlternative>;
 | 
			
		||||
    alternatives: Array<{
 | 
			
		||||
        label: string;
 | 
			
		||||
        onClick: () => void;
 | 
			
		||||
        disabled?: boolean;
 | 
			
		||||
        icon?: React.ReactNode;
 | 
			
		||||
        className?: string;
 | 
			
		||||
    }>;
 | 
			
		||||
    dropdownTriggerClassName?: string;
 | 
			
		||||
    chevronDownIconClassName?: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -95,36 +87,19 @@ const ButtonWithAlternatives = React.forwardRef<
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </DropdownMenuTrigger>
 | 
			
		||||
                        <DropdownMenuContent align="end">
 | 
			
		||||
                            {alternatives.map((alternative, index) => {
 | 
			
		||||
                                const menuItem = (
 | 
			
		||||
                                    <DropdownMenuItem
 | 
			
		||||
                                        key={index}
 | 
			
		||||
                                        onClick={alternative.onClick}
 | 
			
		||||
                                        disabled={alternative.disabled}
 | 
			
		||||
                                        className={cn(alternative.className)}
 | 
			
		||||
                                    >
 | 
			
		||||
                                        <span className="flex w-full items-center justify-between gap-2">
 | 
			
		||||
                                            {alternative.label}
 | 
			
		||||
                                            {alternative.icon}
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </DropdownMenuItem>
 | 
			
		||||
                                );
 | 
			
		||||
 | 
			
		||||
                                if (alternative.tooltip) {
 | 
			
		||||
                                    return (
 | 
			
		||||
                                        <Tooltip key={index}>
 | 
			
		||||
                                            <TooltipTrigger asChild>
 | 
			
		||||
                                                {menuItem}
 | 
			
		||||
                                            </TooltipTrigger>
 | 
			
		||||
                                            <TooltipContent side="left">
 | 
			
		||||
                                                {alternative.tooltip}
 | 
			
		||||
                                            </TooltipContent>
 | 
			
		||||
                                        </Tooltip>
 | 
			
		||||
                                    );
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                return menuItem;
 | 
			
		||||
                            })}
 | 
			
		||||
                            {alternatives.map((alternative, index) => (
 | 
			
		||||
                                <DropdownMenuItem
 | 
			
		||||
                                    key={index}
 | 
			
		||||
                                    onClick={alternative.onClick}
 | 
			
		||||
                                    disabled={alternative.disabled}
 | 
			
		||||
                                    className={cn(alternative.className)}
 | 
			
		||||
                                >
 | 
			
		||||
                                    <span className="flex w-full items-center justify-between gap-2">
 | 
			
		||||
                                        {alternative.label}
 | 
			
		||||
                                        {alternative.icon}
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                </DropdownMenuItem>
 | 
			
		||||
                            ))}
 | 
			
		||||
                        </DropdownMenuContent>
 | 
			
		||||
                    </DropdownMenu>
 | 
			
		||||
                ) : null}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,45 +5,27 @@ import {
 | 
			
		||||
    PopoverTrigger,
 | 
			
		||||
} from '@/components/popover/popover';
 | 
			
		||||
import { colorOptions } from '@/lib/colors';
 | 
			
		||||
import { cn } from '@/lib/utils';
 | 
			
		||||
 | 
			
		||||
export interface ColorPickerProps {
 | 
			
		||||
    color: string;
 | 
			
		||||
    onChange: (color: string) => void;
 | 
			
		||||
    disabled?: boolean;
 | 
			
		||||
    popoverOnMouseDown?: (e: React.MouseEvent) => void;
 | 
			
		||||
    popoverOnClick?: (e: React.MouseEvent) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ColorPicker = React.forwardRef<
 | 
			
		||||
    React.ElementRef<typeof PopoverTrigger>,
 | 
			
		||||
    ColorPickerProps
 | 
			
		||||
>(({ color, onChange, disabled, popoverOnMouseDown, popoverOnClick }, ref) => {
 | 
			
		||||
>(({ color, onChange }, ref) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <Popover>
 | 
			
		||||
            <PopoverTrigger
 | 
			
		||||
                asChild
 | 
			
		||||
                ref={ref}
 | 
			
		||||
                disabled={disabled}
 | 
			
		||||
                {...(disabled ? { onClick: (e) => e.preventDefault() } : {})}
 | 
			
		||||
            >
 | 
			
		||||
            <PopoverTrigger asChild ref={ref}>
 | 
			
		||||
                <div
 | 
			
		||||
                    className={cn(
 | 
			
		||||
                        'h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md',
 | 
			
		||||
                        {
 | 
			
		||||
                            'hover:shadow-none cursor-default': disabled,
 | 
			
		||||
                        }
 | 
			
		||||
                    )}
 | 
			
		||||
                    className="h-6 w-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md"
 | 
			
		||||
                    style={{
 | 
			
		||||
                        backgroundColor: color,
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
            </PopoverTrigger>
 | 
			
		||||
            <PopoverContent
 | 
			
		||||
                className="w-fit"
 | 
			
		||||
                onMouseDown={popoverOnMouseDown}
 | 
			
		||||
                onClick={popoverOnClick}
 | 
			
		||||
            >
 | 
			
		||||
            <PopoverContent className="w-fit">
 | 
			
		||||
                <div className="grid grid-cols-4 gap-2">
 | 
			
		||||
                    {colorOptions.map((option) => (
 | 
			
		||||
                        <div
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ export interface SelectBoxOption {
 | 
			
		||||
    regex?: string;
 | 
			
		||||
    extractRegex?: RegExp;
 | 
			
		||||
    group?: string;
 | 
			
		||||
    icon?: React.ReactNode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface SelectBoxProps {
 | 
			
		||||
@@ -54,10 +53,6 @@ export interface SelectBoxProps {
 | 
			
		||||
    open?: boolean;
 | 
			
		||||
    onOpenChange?: (open: boolean) => void;
 | 
			
		||||
    popoverClassName?: string;
 | 
			
		||||
    readonly?: boolean;
 | 
			
		||||
    footerButtons?: React.ReactNode;
 | 
			
		||||
    commandOnMouseDown?: (e: React.MouseEvent) => void;
 | 
			
		||||
    commandOnClick?: (e: React.MouseEvent) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
 | 
			
		||||
@@ -83,10 +78,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
 | 
			
		||||
            open,
 | 
			
		||||
            onOpenChange: setOpen,
 | 
			
		||||
            popoverClassName,
 | 
			
		||||
            readonly,
 | 
			
		||||
            footerButtons,
 | 
			
		||||
            commandOnMouseDown,
 | 
			
		||||
            commandOnClick,
 | 
			
		||||
        },
 | 
			
		||||
        ref
 | 
			
		||||
    ) => {
 | 
			
		||||
@@ -161,20 +152,18 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
 | 
			
		||||
                            className={`inline-flex min-w-0 shrink-0 items-center gap-1 rounded-md border py-0.5 pl-2 pr-1 text-xs font-medium text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${oneLine ? 'mx-0.5' : ''}`}
 | 
			
		||||
                        >
 | 
			
		||||
                            <span>{option.label}</span>
 | 
			
		||||
                            {!readonly ? (
 | 
			
		||||
                                <span
 | 
			
		||||
                                    onClick={(e) => {
 | 
			
		||||
                                        e.preventDefault();
 | 
			
		||||
                                        handleSelect(option.value);
 | 
			
		||||
                                    }}
 | 
			
		||||
                                    className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
 | 
			
		||||
                                >
 | 
			
		||||
                                    <Cross2Icon />
 | 
			
		||||
                                </span>
 | 
			
		||||
                            ) : null}
 | 
			
		||||
                            <span
 | 
			
		||||
                                onClick={(e) => {
 | 
			
		||||
                                    e.preventDefault();
 | 
			
		||||
                                    handleSelect(option.value);
 | 
			
		||||
                                }}
 | 
			
		||||
                                className="flex items-center rounded-sm px-px text-muted-foreground/60 hover:bg-accent hover:text-muted-foreground"
 | 
			
		||||
                            >
 | 
			
		||||
                                <Cross2Icon />
 | 
			
		||||
                            </span>
 | 
			
		||||
                        </span>
 | 
			
		||||
                    )),
 | 
			
		||||
            [options, value, handleSelect, oneLine, keepOrder, readonly]
 | 
			
		||||
            [options, value, handleSelect, oneLine, keepOrder]
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const isAllSelected = React.useMemo(
 | 
			
		||||
@@ -247,8 +236,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
 | 
			
		||||
                                matches?.map((match) => match?.toString())
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                        onMouseDown={commandOnMouseDown}
 | 
			
		||||
                        onClick={commandOnClick}
 | 
			
		||||
                    >
 | 
			
		||||
                        {multiple && (
 | 
			
		||||
                            <div
 | 
			
		||||
@@ -263,11 +250,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
 | 
			
		||||
                            </div>
 | 
			
		||||
                        )}
 | 
			
		||||
                        <div className="flex flex-1 items-center truncate">
 | 
			
		||||
                            {option.icon ? (
 | 
			
		||||
                                <span className="mr-2 shrink-0">
 | 
			
		||||
                                    {option.icon}
 | 
			
		||||
                                </span>
 | 
			
		||||
                            ) : null}
 | 
			
		||||
                            <span>
 | 
			
		||||
                                {isRegexMatch ? searchTerm : option.label}
 | 
			
		||||
                                {!isRegexMatch && optionSuffix
 | 
			
		||||
@@ -294,15 +276,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
 | 
			
		||||
                    </CommandItem>
 | 
			
		||||
                );
 | 
			
		||||
            },
 | 
			
		||||
            [
 | 
			
		||||
                value,
 | 
			
		||||
                multiple,
 | 
			
		||||
                searchTerm,
 | 
			
		||||
                handleSelect,
 | 
			
		||||
                optionSuffix,
 | 
			
		||||
                commandOnClick,
 | 
			
		||||
                commandOnMouseDown,
 | 
			
		||||
            ]
 | 
			
		||||
            [value, multiple, searchTerm, handleSelect, optionSuffix]
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
@@ -310,7 +284,7 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
 | 
			
		||||
                <PopoverTrigger asChild tabIndex={0} onKeyDown={handleKeyDown}>
 | 
			
		||||
                    <div
 | 
			
		||||
                        className={cn(
 | 
			
		||||
                            `flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''} ${readonly ? 'pointer-events-none' : ''}`,
 | 
			
		||||
                            `flex min-h-[36px] cursor-pointer items-center justify-between rounded-md border px-3 py-1 data-[state=open]:border-ring ${disabled ? 'bg-muted pointer-events-none' : ''}`,
 | 
			
		||||
                            className
 | 
			
		||||
                        )}
 | 
			
		||||
                    >
 | 
			
		||||
@@ -380,8 +354,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
 | 
			
		||||
                        popoverClassName
 | 
			
		||||
                    )}
 | 
			
		||||
                    align="center"
 | 
			
		||||
                    onMouseDown={(e) => e.stopPropagation()}
 | 
			
		||||
                    onClick={(e) => e.stopPropagation()}
 | 
			
		||||
                >
 | 
			
		||||
                    <Command
 | 
			
		||||
                        filter={(value, search, keywords) => {
 | 
			
		||||
@@ -471,9 +443,6 @@ export const SelectBox = React.forwardRef<HTMLInputElement, SelectBoxProps>(
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </ScrollArea>
 | 
			
		||||
                    </Command>
 | 
			
		||||
                    {footerButtons ? (
 | 
			
		||||
                        <div className="border-t">{footerButtons}</div>
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                </PopoverContent>
 | 
			
		||||
            </Popover>
 | 
			
		||||
        );
 | 
			
		||||
 
 | 
			
		||||
@@ -14,16 +14,6 @@ export interface CanvasContext {
 | 
			
		||||
    overlapGraph: Graph<string>;
 | 
			
		||||
    setShowFilter: React.Dispatch<React.SetStateAction<boolean>>;
 | 
			
		||||
    showFilter: boolean;
 | 
			
		||||
    editTableModeTable: {
 | 
			
		||||
        tableId: string;
 | 
			
		||||
        fieldId?: string;
 | 
			
		||||
    } | null;
 | 
			
		||||
    setEditTableModeTable: React.Dispatch<
 | 
			
		||||
        React.SetStateAction<{
 | 
			
		||||
            tableId: string;
 | 
			
		||||
            fieldId?: string;
 | 
			
		||||
        } | null>
 | 
			
		||||
    >;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const canvasContext = createContext<CanvasContext>({
 | 
			
		||||
@@ -33,6 +23,4 @@ export const canvasContext = createContext<CanvasContext>({
 | 
			
		||||
    overlapGraph: createGraph(),
 | 
			
		||||
    setShowFilter: emptyFn,
 | 
			
		||||
    showFilter: false,
 | 
			
		||||
    editTableModeTable: null,
 | 
			
		||||
    setEditTableModeTable: emptyFn,
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -33,10 +33,6 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
 | 
			
		||||
    const { fitView } = useReactFlow();
 | 
			
		||||
    const [overlapGraph, setOverlapGraph] =
 | 
			
		||||
        useState<Graph<string>>(createGraph());
 | 
			
		||||
    const [editTableModeTable, setEditTableModeTable] = useState<{
 | 
			
		||||
        tableId: string;
 | 
			
		||||
        fieldId?: string;
 | 
			
		||||
    } | null>(null);
 | 
			
		||||
 | 
			
		||||
    const [showFilter, setShowFilter] = useState(false);
 | 
			
		||||
    const diagramIdActiveFilterRef = useRef<string>();
 | 
			
		||||
@@ -131,8 +127,6 @@ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
 | 
			
		||||
                overlapGraph,
 | 
			
		||||
                setShowFilter,
 | 
			
		||||
                showFilter,
 | 
			
		||||
                editTableModeTable,
 | 
			
		||||
                setEditTableModeTable,
 | 
			
		||||
            }}
 | 
			
		||||
        >
 | 
			
		||||
            {children}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,7 @@ import type { ChartDBContext, ChartDBEvent } from './chartdb-context';
 | 
			
		||||
import { chartDBContext } from './chartdb-context';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import type { DBField } from '@/lib/domain/db-field';
 | 
			
		||||
import {
 | 
			
		||||
    getTableIndexesWithPrimaryKey,
 | 
			
		||||
    type DBIndex,
 | 
			
		||||
} from '@/lib/domain/db-index';
 | 
			
		||||
import type { DBIndex } from '@/lib/domain/db-index';
 | 
			
		||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
 | 
			
		||||
import { useStorage } from '@/hooks/use-storage';
 | 
			
		||||
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
 | 
			
		||||
@@ -41,7 +38,8 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
    React.PropsWithChildren<ChartDBProviderProps>
 | 
			
		||||
> = ({ children, diagram, readonly: readonlyProp }) => {
 | 
			
		||||
    const { hasDiff } = useDiff();
 | 
			
		||||
    const storageDB = useStorage();
 | 
			
		||||
    const dbStorage = useStorage();
 | 
			
		||||
    let db = dbStorage;
 | 
			
		||||
    const events = useEventEmitter<ChartDBEvent>();
 | 
			
		||||
    const { addUndoAction, resetRedoStack, resetUndoStack } =
 | 
			
		||||
        useRedoUndoStack();
 | 
			
		||||
@@ -101,6 +99,10 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
        [readonlyProp, hasDiff]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (readonly) {
 | 
			
		||||
        db = storageInitialValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const schemas = useMemo(
 | 
			
		||||
        () =>
 | 
			
		||||
            databasesWithSchemas.includes(databaseType)
 | 
			
		||||
@@ -129,11 +131,6 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
        [tables, defaultSchemaName, databaseType]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const db = useMemo(
 | 
			
		||||
        () => (readonly ? storageInitialValue : storageDB),
 | 
			
		||||
        [storageDB, readonly]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const currentDiagram: Diagram = useMemo(
 | 
			
		||||
        () => ({
 | 
			
		||||
            id: diagramId,
 | 
			
		||||
@@ -351,11 +348,6 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
                order: tables.length,
 | 
			
		||||
                ...attributes,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            table.indexes = getTableIndexesWithPrimaryKey({
 | 
			
		||||
                table,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            await addTable(table);
 | 
			
		||||
 | 
			
		||||
            return table;
 | 
			
		||||
@@ -647,30 +639,17 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
            options = { updateHistory: true }
 | 
			
		||||
        ) => {
 | 
			
		||||
            const prevField = getField(tableId, fieldId);
 | 
			
		||||
 | 
			
		||||
            const updateTableFn = (table: DBTable) => {
 | 
			
		||||
                const updatedTable: DBTable = {
 | 
			
		||||
                    ...table,
 | 
			
		||||
                    fields: table.fields.map((f) =>
 | 
			
		||||
                        f.id === fieldId ? { ...f, ...field } : f
 | 
			
		||||
                    ),
 | 
			
		||||
                } satisfies DBTable;
 | 
			
		||||
 | 
			
		||||
                updatedTable.indexes = getTableIndexesWithPrimaryKey({
 | 
			
		||||
                    table: updatedTable,
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                return updatedTable;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            setTables((tables) =>
 | 
			
		||||
                tables.map((table) => {
 | 
			
		||||
                    if (table.id === tableId) {
 | 
			
		||||
                        return updateTableFn(table);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return table;
 | 
			
		||||
                })
 | 
			
		||||
                tables.map((table) =>
 | 
			
		||||
                    table.id === tableId
 | 
			
		||||
                        ? {
 | 
			
		||||
                              ...table,
 | 
			
		||||
                              fields: table.fields.map((f) =>
 | 
			
		||||
                                  f.id === fieldId ? { ...f, ...field } : f
 | 
			
		||||
                              ),
 | 
			
		||||
                          }
 | 
			
		||||
                        : table
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            const table = await db.getTable({ diagramId, id: tableId });
 | 
			
		||||
@@ -685,7 +664,10 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
                db.updateTable({
 | 
			
		||||
                    id: tableId,
 | 
			
		||||
                    attributes: {
 | 
			
		||||
                        ...updateTableFn(table),
 | 
			
		||||
                        ...table,
 | 
			
		||||
                        fields: table.fields.map((f) =>
 | 
			
		||||
                            f.id === fieldId ? { ...f, ...field } : f
 | 
			
		||||
                        ),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            ]);
 | 
			
		||||
@@ -712,29 +694,19 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
            fieldId: string,
 | 
			
		||||
            options = { updateHistory: true }
 | 
			
		||||
        ) => {
 | 
			
		||||
            const updateTableFn = (table: DBTable) => {
 | 
			
		||||
                const updatedTable: DBTable = {
 | 
			
		||||
                    ...table,
 | 
			
		||||
                    fields: table.fields.filter((f) => f.id !== fieldId),
 | 
			
		||||
                } satisfies DBTable;
 | 
			
		||||
 | 
			
		||||
                updatedTable.indexes = getTableIndexesWithPrimaryKey({
 | 
			
		||||
                    table: updatedTable,
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                return updatedTable;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            const fields = getTable(tableId)?.fields ?? [];
 | 
			
		||||
            const prevField = getField(tableId, fieldId);
 | 
			
		||||
            setTables((tables) =>
 | 
			
		||||
                tables.map((table) => {
 | 
			
		||||
                    if (table.id === tableId) {
 | 
			
		||||
                        return updateTableFn(table);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return table;
 | 
			
		||||
                })
 | 
			
		||||
                tables.map((table) =>
 | 
			
		||||
                    table.id === tableId
 | 
			
		||||
                        ? {
 | 
			
		||||
                              ...table,
 | 
			
		||||
                              fields: table.fields.filter(
 | 
			
		||||
                                  (f) => f.id !== fieldId
 | 
			
		||||
                              ),
 | 
			
		||||
                          }
 | 
			
		||||
                        : table
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            events.emit({
 | 
			
		||||
@@ -758,7 +730,8 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
                db.updateTable({
 | 
			
		||||
                    id: tableId,
 | 
			
		||||
                    attributes: {
 | 
			
		||||
                        ...updateTableFn(table),
 | 
			
		||||
                        ...table,
 | 
			
		||||
                        fields: table.fields.filter((f) => f.id !== fieldId),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            ]);
 | 
			
		||||
@@ -1580,17 +1553,17 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
 | 
			
		||||
    const updateDiagramData: ChartDBContext['updateDiagramData'] = useCallback(
 | 
			
		||||
        async (diagram, options) => {
 | 
			
		||||
            const st = options?.forceUpdateStorage ? storageDB : db;
 | 
			
		||||
            const st = options?.forceUpdateStorage ? dbStorage : db;
 | 
			
		||||
            await st.deleteDiagram(diagram.id);
 | 
			
		||||
            await st.addDiagram({ diagram });
 | 
			
		||||
            loadDiagramFromData(diagram);
 | 
			
		||||
        },
 | 
			
		||||
        [db, storageDB, loadDiagramFromData]
 | 
			
		||||
        [db, dbStorage, loadDiagramFromData]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
 | 
			
		||||
        async (diagramId: string) => {
 | 
			
		||||
            const diagram = await storageDB.getDiagram(diagramId, {
 | 
			
		||||
            const diagram = await db.getDiagram(diagramId, {
 | 
			
		||||
                includeRelationships: true,
 | 
			
		||||
                includeTables: true,
 | 
			
		||||
                includeDependencies: true,
 | 
			
		||||
@@ -1604,7 +1577,7 @@ export const ChartDBProvider: React.FC<
 | 
			
		||||
 | 
			
		||||
            return diagram;
 | 
			
		||||
        },
 | 
			
		||||
        [storageDB, loadDiagramFromData]
 | 
			
		||||
        [db, loadDiagramFromData]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Custom type operations
 | 
			
		||||
 
 | 
			
		||||
@@ -764,6 +764,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
 | 
			
		||||
                db.db_dependencies.where('diagramId').equals(id).delete(),
 | 
			
		||||
                db.areas.where('diagramId').equals(id).delete(),
 | 
			
		||||
                db.db_custom_types.where('diagramId').equals(id).delete(),
 | 
			
		||||
                db.diagram_filters.where('diagramId').equals(id).delete(),
 | 
			
		||||
            ]);
 | 
			
		||||
        },
 | 
			
		||||
        [db]
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import { Dialog, DialogContent } from '@/components/dialog/dialog';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import { useStorage } from '@/hooks/use-storage';
 | 
			
		||||
import type { Diagram } from '@/lib/domain/diagram';
 | 
			
		||||
import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
 | 
			
		||||
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
 | 
			
		||||
import { useNavigate } from 'react-router-dom';
 | 
			
		||||
import { useConfig } from '@/hooks/use-config';
 | 
			
		||||
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,6 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
 | 
			
		||||
                        type="button"
 | 
			
		||||
                        variant="outline"
 | 
			
		||||
                        onClick={createNewDiagram}
 | 
			
		||||
                        disabled={databaseType === DatabaseType.GENERIC}
 | 
			
		||||
                    >
 | 
			
		||||
                        {t('new_diagram_dialog.empty_diagram')}
 | 
			
		||||
                    </Button>
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ import { useDialog } from '@/hooks/use-dialog';
 | 
			
		||||
import {
 | 
			
		||||
    exportBaseSQL,
 | 
			
		||||
    exportSQL,
 | 
			
		||||
} from '@/lib/data/sql-export/export-sql-script';
 | 
			
		||||
} from '@/lib/data/export-metadata/export-sql-script';
 | 
			
		||||
import { databaseTypeToLabelMap } from '@/lib/databases';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import { Annoyed, Sparkles } from 'lucide-react';
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import type { DatabaseEdition } from '@/lib/domain/database-edition';
 | 
			
		||||
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
 | 
			
		||||
import { loadDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
 | 
			
		||||
import type { Diagram } from '@/lib/domain/diagram';
 | 
			
		||||
import { loadFromDatabaseMetadata } from '@/lib/data/import-metadata/import';
 | 
			
		||||
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
 | 
			
		||||
import { useChartDB } from '@/hooks/use-chartdb';
 | 
			
		||||
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
 | 
			
		||||
import { Trans, useTranslation } from 'react-i18next';
 | 
			
		||||
 
 | 
			
		||||
@@ -132,7 +132,7 @@ Ref: comments.user_id > users.id // Each comment is written by one user`;
 | 
			
		||||
                const preprocessedContent = preprocessDBML(content);
 | 
			
		||||
                const sanitizedContent = sanitizeDBML(preprocessedContent);
 | 
			
		||||
                const parser = new Parser();
 | 
			
		||||
                parser.parse(sanitizedContent, 'dbmlv2');
 | 
			
		||||
                parser.parse(sanitizedContent, 'dbml');
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                const parsedError = parseDBMLError(e);
 | 
			
		||||
                if (parsedError) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import React, { useCallback } from 'react';
 | 
			
		||||
import React, { useCallback, useState } from 'react';
 | 
			
		||||
import {
 | 
			
		||||
    DropdownMenu,
 | 
			
		||||
    DropdownMenuContent,
 | 
			
		||||
@@ -7,90 +7,208 @@ import {
 | 
			
		||||
    DropdownMenuTrigger,
 | 
			
		||||
} from '@/components/dropdown-menu/dropdown-menu';
 | 
			
		||||
import { Button } from '@/components/button/button';
 | 
			
		||||
import { Ellipsis, Layers2, SquareArrowOutUpRight, Trash2 } from 'lucide-react';
 | 
			
		||||
import { useChartDB } from '@/hooks/use-chartdb';
 | 
			
		||||
import type { Diagram } from '@/lib/domain';
 | 
			
		||||
import type { Diagram } from '@/lib/domain/diagram';
 | 
			
		||||
import {
 | 
			
		||||
    Copy,
 | 
			
		||||
    MoreHorizontal,
 | 
			
		||||
    SquareArrowOutUpRight,
 | 
			
		||||
    Trash2,
 | 
			
		||||
    Loader2,
 | 
			
		||||
} from 'lucide-react';
 | 
			
		||||
import { useStorage } from '@/hooks/use-storage';
 | 
			
		||||
import { cloneDiagram } from '@/lib/clone';
 | 
			
		||||
import { useAlert } from '@/context/alert-context/alert-context';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { cloneDiagram } from '@/lib/clone';
 | 
			
		||||
import { useParams, useNavigate } from 'react-router-dom';
 | 
			
		||||
import { useConfig } from '@/hooks/use-config';
 | 
			
		||||
 | 
			
		||||
interface DiagramRowActionsMenuProps {
 | 
			
		||||
    diagram: Diagram;
 | 
			
		||||
    onOpen: () => void;
 | 
			
		||||
    refetch: () => void;
 | 
			
		||||
    numberOfDiagrams: number;
 | 
			
		||||
    onSelectDiagram?: (diagramId: string | undefined) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const DiagramRowActionsMenu: React.FC<DiagramRowActionsMenuProps> = ({
 | 
			
		||||
    diagram,
 | 
			
		||||
    onOpen,
 | 
			
		||||
    refetch,
 | 
			
		||||
    numberOfDiagrams,
 | 
			
		||||
    onSelectDiagram,
 | 
			
		||||
}) => {
 | 
			
		||||
    const { diagramId } = useChartDB();
 | 
			
		||||
    const { deleteDiagram, addDiagram } = useStorage();
 | 
			
		||||
    const { addDiagram, deleteDiagram, listDiagrams, getDiagram } =
 | 
			
		||||
        useStorage();
 | 
			
		||||
    const { showAlert } = useAlert();
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const { diagramId: currentDiagramId } = useParams<{ diagramId: string }>();
 | 
			
		||||
    const navigate = useNavigate();
 | 
			
		||||
    const { updateConfig } = useConfig();
 | 
			
		||||
    const [isDuplicating, setIsDuplicating] = useState(false);
 | 
			
		||||
 | 
			
		||||
    const onDelete = useCallback(async () => {
 | 
			
		||||
        deleteDiagram(diagram.id);
 | 
			
		||||
        refetch();
 | 
			
		||||
    const handleDuplicateDiagram = useCallback(async () => {
 | 
			
		||||
        setIsDuplicating(true);
 | 
			
		||||
 | 
			
		||||
        if (diagram.id === diagramId || numberOfDiagrams <= 1) {
 | 
			
		||||
            window.location.href = '/';
 | 
			
		||||
        try {
 | 
			
		||||
            // Load the full diagram with all components
 | 
			
		||||
            const fullDiagram = await getDiagram(diagram.id, {
 | 
			
		||||
                includeTables: true,
 | 
			
		||||
                includeRelationships: true,
 | 
			
		||||
                includeAreas: true,
 | 
			
		||||
                includeDependencies: true,
 | 
			
		||||
                includeCustomTypes: true,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (!fullDiagram) {
 | 
			
		||||
                console.error('Failed to load diagram for duplication');
 | 
			
		||||
                setIsDuplicating(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const { diagram: clonedDiagram } = cloneDiagram(fullDiagram);
 | 
			
		||||
 | 
			
		||||
            // Generate a unique name for the duplicated diagram
 | 
			
		||||
            const diagrams = await listDiagrams();
 | 
			
		||||
            const existingNames = diagrams.map((d) => d.name);
 | 
			
		||||
            let duplicatedName = `${diagram.name} - Copy`;
 | 
			
		||||
            let counter = 1;
 | 
			
		||||
 | 
			
		||||
            while (existingNames.includes(duplicatedName)) {
 | 
			
		||||
                duplicatedName = `${diagram.name} - Copy ${counter}`;
 | 
			
		||||
                counter++;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const diagramToAdd = {
 | 
			
		||||
                ...clonedDiagram,
 | 
			
		||||
                name: duplicatedName,
 | 
			
		||||
                createdAt: new Date(),
 | 
			
		||||
                updatedAt: new Date(),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // Add 2 second delay for better UX
 | 
			
		||||
            await new Promise((resolve) => setTimeout(resolve, 2000));
 | 
			
		||||
 | 
			
		||||
            await addDiagram({ diagram: diagramToAdd });
 | 
			
		||||
 | 
			
		||||
            // Clear current selection first, then select the new diagram
 | 
			
		||||
            if (onSelectDiagram) {
 | 
			
		||||
                onSelectDiagram(undefined); // Clear selection
 | 
			
		||||
                await refetch(); // Refresh the list
 | 
			
		||||
                // Use setTimeout to ensure the DOM has updated with the new row
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                    onSelectDiagram(diagramToAdd.id);
 | 
			
		||||
                }, 100);
 | 
			
		||||
            } else {
 | 
			
		||||
                await refetch(); // Refresh the list
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.error('Error duplicating diagram:', error);
 | 
			
		||||
        } finally {
 | 
			
		||||
            setIsDuplicating(false);
 | 
			
		||||
        }
 | 
			
		||||
    }, [deleteDiagram, diagram.id, diagramId, refetch, numberOfDiagrams]);
 | 
			
		||||
    }, [
 | 
			
		||||
        diagram,
 | 
			
		||||
        addDiagram,
 | 
			
		||||
        listDiagrams,
 | 
			
		||||
        getDiagram,
 | 
			
		||||
        refetch,
 | 
			
		||||
        onSelectDiagram,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    const onDuplicate = useCallback(async () => {
 | 
			
		||||
        const duplicatedDiagram = cloneDiagram(diagram);
 | 
			
		||||
    const handleDeleteDiagram = useCallback(() => {
 | 
			
		||||
        showAlert({
 | 
			
		||||
            title: t('delete_diagram_alert.title'),
 | 
			
		||||
            description: t('delete_diagram_alert.description'),
 | 
			
		||||
            actionLabel: t('delete_diagram_alert.delete'),
 | 
			
		||||
            closeLabel: t('delete_diagram_alert.cancel'),
 | 
			
		||||
            onAction: async () => {
 | 
			
		||||
                await deleteDiagram(diagram.id);
 | 
			
		||||
 | 
			
		||||
        const diagramToAdd = duplicatedDiagram.diagram;
 | 
			
		||||
                // If we deleted the currently open diagram, navigate to another one
 | 
			
		||||
                if (currentDiagramId === diagram.id) {
 | 
			
		||||
                    // Get updated list of diagrams after deletion
 | 
			
		||||
                    const remainingDiagrams = await listDiagrams();
 | 
			
		||||
 | 
			
		||||
        if (!diagramToAdd) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
                    if (remainingDiagrams.length > 0) {
 | 
			
		||||
                        // Sort by last modified date (most recent first)
 | 
			
		||||
                        const sortedDiagrams = remainingDiagrams.sort(
 | 
			
		||||
                            (a, b) =>
 | 
			
		||||
                                b.updatedAt.getTime() - a.updatedAt.getTime()
 | 
			
		||||
                        );
 | 
			
		||||
 | 
			
		||||
        diagramToAdd.name = `${diagram.name} (Copy)`;
 | 
			
		||||
                        // Navigate to the most recently modified diagram
 | 
			
		||||
                        const firstDiagram = sortedDiagrams[0];
 | 
			
		||||
                        updateConfig({
 | 
			
		||||
                            config: { defaultDiagramId: firstDiagram.id },
 | 
			
		||||
                        });
 | 
			
		||||
                        navigate(`/diagrams/${firstDiagram.id}`);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // No diagrams left, navigate to home
 | 
			
		||||
                        navigate('/');
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        addDiagram({ diagram: diagramToAdd });
 | 
			
		||||
        refetch();
 | 
			
		||||
    }, [addDiagram, refetch, diagram]);
 | 
			
		||||
                refetch(); // Refresh the list
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }, [
 | 
			
		||||
        diagram.id,
 | 
			
		||||
        currentDiagramId,
 | 
			
		||||
        deleteDiagram,
 | 
			
		||||
        refetch,
 | 
			
		||||
        showAlert,
 | 
			
		||||
        t,
 | 
			
		||||
        listDiagrams,
 | 
			
		||||
        updateConfig,
 | 
			
		||||
        navigate,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <DropdownMenu>
 | 
			
		||||
            <DropdownMenuTrigger asChild>
 | 
			
		||||
                <Button
 | 
			
		||||
                    variant="ghost"
 | 
			
		||||
                    size="icon"
 | 
			
		||||
                    size="sm"
 | 
			
		||||
                    className="size-8 p-0"
 | 
			
		||||
                    onClick={(e) => e.stopPropagation()}
 | 
			
		||||
                    disabled={isDuplicating}
 | 
			
		||||
                >
 | 
			
		||||
                    <Ellipsis className="size-4" />
 | 
			
		||||
                    {isDuplicating ? (
 | 
			
		||||
                        <Loader2 className="size-3.5 animate-spin" />
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <MoreHorizontal className="size-3.5" />
 | 
			
		||||
                    )}
 | 
			
		||||
                </Button>
 | 
			
		||||
            </DropdownMenuTrigger>
 | 
			
		||||
            <DropdownMenuContent align="end">
 | 
			
		||||
                <DropdownMenuItem
 | 
			
		||||
                    onClick={onOpen}
 | 
			
		||||
                    onClick={(e) => {
 | 
			
		||||
                        e.stopPropagation();
 | 
			
		||||
                        onOpen();
 | 
			
		||||
                    }}
 | 
			
		||||
                    className="flex justify-between gap-4"
 | 
			
		||||
                >
 | 
			
		||||
                    {t('open_diagram_dialog.diagram_actions.open')}
 | 
			
		||||
                    Open
 | 
			
		||||
                    <SquareArrowOutUpRight className="size-3.5" />
 | 
			
		||||
                </DropdownMenuItem>
 | 
			
		||||
 | 
			
		||||
                <DropdownMenuItem
 | 
			
		||||
                    onClick={onDuplicate}
 | 
			
		||||
                    onClick={(e) => {
 | 
			
		||||
                        e.stopPropagation();
 | 
			
		||||
                        handleDuplicateDiagram();
 | 
			
		||||
                    }}
 | 
			
		||||
                    className="flex justify-between gap-4"
 | 
			
		||||
                >
 | 
			
		||||
                    {t('open_diagram_dialog.diagram_actions.duplicate')}
 | 
			
		||||
                    <Layers2 className="size-3.5" />
 | 
			
		||||
                    {t('menu.databases.duplicate')}
 | 
			
		||||
                    <Copy className="size-3.5" />
 | 
			
		||||
                </DropdownMenuItem>
 | 
			
		||||
 | 
			
		||||
                <DropdownMenuSeparator />
 | 
			
		||||
                <DropdownMenuItem
 | 
			
		||||
                    onClick={onDelete}
 | 
			
		||||
                    className="flex justify-between gap-4 text-red-700"
 | 
			
		||||
                    onClick={(e) => {
 | 
			
		||||
                        e.stopPropagation();
 | 
			
		||||
                        handleDeleteDiagram();
 | 
			
		||||
                    }}
 | 
			
		||||
                    className="flex items-center justify-between text-red-600 focus:text-red-600"
 | 
			
		||||
                >
 | 
			
		||||
                    {t('open_diagram_dialog.diagram_actions.delete')}
 | 
			
		||||
                    <Trash2 className="size-3.5 text-red-700" />
 | 
			
		||||
                    {t('menu.databases.delete_diagram')}
 | 
			
		||||
                    <Trash2 className="size-3.5" />
 | 
			
		||||
                </DropdownMenuItem>
 | 
			
		||||
            </DropdownMenuContent>
 | 
			
		||||
        </DropdownMenu>
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,10 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
 | 
			
		||||
        string | undefined
 | 
			
		||||
    >();
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setSelectedDiagramId(undefined);
 | 
			
		||||
    }, [dialog.open]);
 | 
			
		||||
 | 
			
		||||
    const fetchDiagrams = useCallback(async () => {
 | 
			
		||||
        const diagrams = await listDiagrams({ includeTables: true });
 | 
			
		||||
        setDiagrams(
 | 
			
		||||
@@ -57,12 +61,8 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
 | 
			
		||||
    }, [listDiagrams]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!dialog.open) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        setSelectedDiagramId(undefined);
 | 
			
		||||
        fetchDiagrams();
 | 
			
		||||
    }, [dialog.open, fetchDiagrams]);
 | 
			
		||||
    }, [fetchDiagrams, dialog.open]);
 | 
			
		||||
 | 
			
		||||
    const openDiagram = useCallback(
 | 
			
		||||
        (diagramId: string) => {
 | 
			
		||||
@@ -168,7 +168,6 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
 | 
			
		||||
                                            'open_diagram_dialog.table_columns.tables_count'
 | 
			
		||||
                                        )}
 | 
			
		||||
                                    </TableHead>
 | 
			
		||||
                                    <TableHead />
 | 
			
		||||
                                </TableRow>
 | 
			
		||||
                            </TableHeader>
 | 
			
		||||
                            <TableBody>
 | 
			
		||||
@@ -231,10 +230,10 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
 | 
			
		||||
                                                    openDiagram(diagram.id);
 | 
			
		||||
                                                    closeOpenDiagramDialog();
 | 
			
		||||
                                                }}
 | 
			
		||||
                                                numberOfDiagrams={
 | 
			
		||||
                                                    diagrams.length
 | 
			
		||||
                                                }
 | 
			
		||||
                                                refetch={fetchDiagrams}
 | 
			
		||||
                                                onSelectDiagram={
 | 
			
		||||
                                                    setSelectedDiagramId
 | 
			
		||||
                                                }
 | 
			
		||||
                                            />
 | 
			
		||||
                                        </TableCell>
 | 
			
		||||
                                    </TableRow>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,50 +0,0 @@
 | 
			
		||||
import { useEffect, useCallback, type RefObject } from 'react';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Custom hook that handles click outside detection with capture phase
 | 
			
		||||
 * to work properly with React Flow canvas and other event-stopping elements
 | 
			
		||||
 */
 | 
			
		||||
export function useClickOutside(
 | 
			
		||||
    ref: RefObject<HTMLElement>,
 | 
			
		||||
    handler: () => void,
 | 
			
		||||
    isActive = true
 | 
			
		||||
) {
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!isActive) return;
 | 
			
		||||
 | 
			
		||||
        const handleClickOutside = (event: MouseEvent) => {
 | 
			
		||||
            if (ref.current && !ref.current.contains(event.target as Node)) {
 | 
			
		||||
                handler();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Use capture phase to catch events before React Flow or other libraries can stop them
 | 
			
		||||
        document.addEventListener('mousedown', handleClickOutside, true);
 | 
			
		||||
 | 
			
		||||
        return () => {
 | 
			
		||||
            document.removeEventListener('mousedown', handleClickOutside, true);
 | 
			
		||||
        };
 | 
			
		||||
    }, [ref, handler, isActive]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Specialized version of useClickOutside for edit mode inputs
 | 
			
		||||
 * Adds a small delay to prevent race conditions with blur events
 | 
			
		||||
 */
 | 
			
		||||
export function useEditClickOutside(
 | 
			
		||||
    inputRef: RefObject<HTMLElement>,
 | 
			
		||||
    editMode: boolean,
 | 
			
		||||
    onSave: () => void,
 | 
			
		||||
    delay = 100
 | 
			
		||||
) {
 | 
			
		||||
    const handleClickOutside = useCallback(() => {
 | 
			
		||||
        if (editMode) {
 | 
			
		||||
            // Small delay to ensure any pending state updates are processed
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                onSave();
 | 
			
		||||
            }, delay);
 | 
			
		||||
        }
 | 
			
		||||
    }, [editMode, onSave, delay]);
 | 
			
		||||
 | 
			
		||||
    useClickOutside(inputRef, handleClickOutside, editMode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,142 +0,0 @@
 | 
			
		||||
import { useCallback } from 'react';
 | 
			
		||||
import { useReactFlow } from '@xyflow/react';
 | 
			
		||||
import { useLayout } from '@/hooks/use-layout';
 | 
			
		||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
 | 
			
		||||
 | 
			
		||||
interface FocusOptions {
 | 
			
		||||
    select?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useFocusOn = () => {
 | 
			
		||||
    const { fitView, setNodes, setEdges } = useReactFlow();
 | 
			
		||||
    const { hideSidePanel } = useLayout();
 | 
			
		||||
    const { isMd: isDesktop } = useBreakpoint('md');
 | 
			
		||||
 | 
			
		||||
    const focusOnArea = useCallback(
 | 
			
		||||
        (areaId: string, options: FocusOptions = {}) => {
 | 
			
		||||
            const { select = true } = options;
 | 
			
		||||
 | 
			
		||||
            if (select) {
 | 
			
		||||
                setNodes((nodes) =>
 | 
			
		||||
                    nodes.map((node) =>
 | 
			
		||||
                        node.id === areaId
 | 
			
		||||
                            ? {
 | 
			
		||||
                                  ...node,
 | 
			
		||||
                                  selected: true,
 | 
			
		||||
                              }
 | 
			
		||||
                            : {
 | 
			
		||||
                                  ...node,
 | 
			
		||||
                                  selected: false,
 | 
			
		||||
                              }
 | 
			
		||||
                    )
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fitView({
 | 
			
		||||
                duration: 500,
 | 
			
		||||
                maxZoom: 1,
 | 
			
		||||
                minZoom: 1,
 | 
			
		||||
                nodes: [
 | 
			
		||||
                    {
 | 
			
		||||
                        id: areaId,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (!isDesktop) {
 | 
			
		||||
                hideSidePanel();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        [fitView, setNodes, hideSidePanel, isDesktop]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const focusOnTable = useCallback(
 | 
			
		||||
        (tableId: string, options: FocusOptions = {}) => {
 | 
			
		||||
            const { select = true } = options;
 | 
			
		||||
 | 
			
		||||
            if (select) {
 | 
			
		||||
                setNodes((nodes) =>
 | 
			
		||||
                    nodes.map((node) =>
 | 
			
		||||
                        node.id === tableId
 | 
			
		||||
                            ? {
 | 
			
		||||
                                  ...node,
 | 
			
		||||
                                  selected: true,
 | 
			
		||||
                              }
 | 
			
		||||
                            : {
 | 
			
		||||
                                  ...node,
 | 
			
		||||
                                  selected: false,
 | 
			
		||||
                              }
 | 
			
		||||
                    )
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fitView({
 | 
			
		||||
                duration: 500,
 | 
			
		||||
                maxZoom: 1,
 | 
			
		||||
                minZoom: 1,
 | 
			
		||||
                nodes: [
 | 
			
		||||
                    {
 | 
			
		||||
                        id: tableId,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (!isDesktop) {
 | 
			
		||||
                hideSidePanel();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        [fitView, setNodes, hideSidePanel, isDesktop]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const focusOnRelationship = useCallback(
 | 
			
		||||
        (
 | 
			
		||||
            relationshipId: string,
 | 
			
		||||
            sourceTableId: string,
 | 
			
		||||
            targetTableId: string,
 | 
			
		||||
            options: FocusOptions = {}
 | 
			
		||||
        ) => {
 | 
			
		||||
            const { select = true } = options;
 | 
			
		||||
 | 
			
		||||
            if (select) {
 | 
			
		||||
                setEdges((edges) =>
 | 
			
		||||
                    edges.map((edge) =>
 | 
			
		||||
                        edge.id === relationshipId
 | 
			
		||||
                            ? {
 | 
			
		||||
                                  ...edge,
 | 
			
		||||
                                  selected: true,
 | 
			
		||||
                              }
 | 
			
		||||
                            : {
 | 
			
		||||
                                  ...edge,
 | 
			
		||||
                                  selected: false,
 | 
			
		||||
                              }
 | 
			
		||||
                    )
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fitView({
 | 
			
		||||
                duration: 500,
 | 
			
		||||
                maxZoom: 1,
 | 
			
		||||
                minZoom: 1,
 | 
			
		||||
                nodes: [
 | 
			
		||||
                    {
 | 
			
		||||
                        id: sourceTableId,
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id: targetTableId,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (!isDesktop) {
 | 
			
		||||
                hideSidePanel();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        [fitView, setEdges, hideSidePanel, isDesktop]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        focusOnArea,
 | 
			
		||||
        focusOnTable,
 | 
			
		||||
        focusOnRelationship,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
@@ -1,320 +0,0 @@
 | 
			
		||||
import { useCallback, useMemo, useState, useEffect } from 'react';
 | 
			
		||||
import { useChartDB } from './use-chartdb';
 | 
			
		||||
import { useDebounce } from './use-debounce-v2';
 | 
			
		||||
import type { DBField, DBTable } from '@/lib/domain';
 | 
			
		||||
import type {
 | 
			
		||||
    SelectBoxOption,
 | 
			
		||||
    SelectBoxProps,
 | 
			
		||||
} from '@/components/select-box/select-box';
 | 
			
		||||
import {
 | 
			
		||||
    dataTypeDataToDataType,
 | 
			
		||||
    sortedDataTypeMap,
 | 
			
		||||
} from '@/lib/data/data-types/data-types';
 | 
			
		||||
import { generateDBFieldSuffix } from '@/lib/domain/db-field';
 | 
			
		||||
import type { DataTypeData } from '@/lib/data/data-types/data-types';
 | 
			
		||||
 | 
			
		||||
const generateFieldRegexPatterns = (
 | 
			
		||||
    dataType: DataTypeData
 | 
			
		||||
): {
 | 
			
		||||
    regex?: string;
 | 
			
		||||
    extractRegex?: RegExp;
 | 
			
		||||
} => {
 | 
			
		||||
    if (!dataType.fieldAttributes) {
 | 
			
		||||
        return { regex: undefined, extractRegex: undefined };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const typeName = dataType.name;
 | 
			
		||||
    const fieldAttributes = dataType.fieldAttributes;
 | 
			
		||||
 | 
			
		||||
    if (fieldAttributes.hasCharMaxLength) {
 | 
			
		||||
        if (fieldAttributes.hasCharMaxLengthOption) {
 | 
			
		||||
            return {
 | 
			
		||||
                regex: `^${typeName}\\((\\d+|[mM][aA][xX])\\)$`,
 | 
			
		||||
                extractRegex: /\((\d+|max)\)/i,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        return {
 | 
			
		||||
            regex: `^${typeName}\\(\\d+\\)$`,
 | 
			
		||||
            extractRegex: /\((\d+)\)/,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (fieldAttributes.precision && fieldAttributes.scale) {
 | 
			
		||||
        return {
 | 
			
		||||
            regex: `^${typeName}\\s*\\(\\s*\\d+\\s*(?:,\\s*\\d+\\s*)?\\)$`,
 | 
			
		||||
            extractRegex: new RegExp(
 | 
			
		||||
                `${typeName}\\s*\\(\\s*(\\d+)\\s*(?:,\\s*(\\d+)\\s*)?\\)`
 | 
			
		||||
            ),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (fieldAttributes.precision) {
 | 
			
		||||
        return {
 | 
			
		||||
            regex: `^${typeName}\\s*\\(\\s*\\d+\\s*\\)$`,
 | 
			
		||||
            extractRegex: /\((\d+)\)/,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return { regex: undefined, extractRegex: undefined };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useUpdateTableField = (
 | 
			
		||||
    table: DBTable,
 | 
			
		||||
    field: DBField,
 | 
			
		||||
    customUpdateField?: (attrs: Partial<DBField>) => void
 | 
			
		||||
) => {
 | 
			
		||||
    const {
 | 
			
		||||
        databaseType,
 | 
			
		||||
        customTypes,
 | 
			
		||||
        updateField: chartDBUpdateField,
 | 
			
		||||
        removeField: chartDBRemoveField,
 | 
			
		||||
    } = useChartDB();
 | 
			
		||||
 | 
			
		||||
    // Local state for responsive UI
 | 
			
		||||
    const [localFieldName, setLocalFieldName] = useState(field.name);
 | 
			
		||||
    const [localNullable, setLocalNullable] = useState(field.nullable);
 | 
			
		||||
    const [localPrimaryKey, setLocalPrimaryKey] = useState(field.primaryKey);
 | 
			
		||||
 | 
			
		||||
    // Update local state when field properties change externally
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setLocalFieldName(field.name);
 | 
			
		||||
        setLocalNullable(field.nullable);
 | 
			
		||||
        setLocalPrimaryKey(field.primaryKey);
 | 
			
		||||
    }, [field.name, field.nullable, field.primaryKey]);
 | 
			
		||||
 | 
			
		||||
    // Use custom updateField if provided, otherwise use the chartDB one
 | 
			
		||||
    const updateField = useMemo(
 | 
			
		||||
        () =>
 | 
			
		||||
            customUpdateField
 | 
			
		||||
                ? (
 | 
			
		||||
                      _tableId: string,
 | 
			
		||||
                      _fieldId: string,
 | 
			
		||||
                      attrs: Partial<DBField>
 | 
			
		||||
                  ) => customUpdateField(attrs)
 | 
			
		||||
                : chartDBUpdateField,
 | 
			
		||||
        [customUpdateField, chartDBUpdateField]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Calculate primary key fields for validation
 | 
			
		||||
    const primaryKeyFields = useMemo(() => {
 | 
			
		||||
        return table.fields.filter((f) => f.primaryKey);
 | 
			
		||||
    }, [table.fields]);
 | 
			
		||||
 | 
			
		||||
    const primaryKeyCount = useMemo(
 | 
			
		||||
        () => primaryKeyFields.length,
 | 
			
		||||
        [primaryKeyFields.length]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Generate data type options for select box
 | 
			
		||||
    const dataFieldOptions = useMemo(() => {
 | 
			
		||||
        const standardTypes: SelectBoxOption[] = sortedDataTypeMap[
 | 
			
		||||
            databaseType
 | 
			
		||||
        ].map((type) => {
 | 
			
		||||
            const regexPatterns = generateFieldRegexPatterns(type);
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                label: type.name,
 | 
			
		||||
                value: type.id,
 | 
			
		||||
                regex: regexPatterns.regex,
 | 
			
		||||
                extractRegex: regexPatterns.extractRegex,
 | 
			
		||||
                group: customTypes?.length ? 'Standard Types' : undefined,
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (!customTypes?.length) {
 | 
			
		||||
            return standardTypes;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add custom types as options
 | 
			
		||||
        const customTypeOptions: SelectBoxOption[] = customTypes.map(
 | 
			
		||||
            (type) => ({
 | 
			
		||||
                label: type.name,
 | 
			
		||||
                value: type.name,
 | 
			
		||||
                description:
 | 
			
		||||
                    type.kind === 'enum' ? `${type.values?.join(' | ')}` : '',
 | 
			
		||||
                group: 'Custom Types',
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return [...standardTypes, ...customTypeOptions];
 | 
			
		||||
    }, [databaseType, customTypes]);
 | 
			
		||||
 | 
			
		||||
    // Handle data type change
 | 
			
		||||
    const handleDataTypeChange = useCallback<
 | 
			
		||||
        NonNullable<SelectBoxProps['onChange']>
 | 
			
		||||
    >(
 | 
			
		||||
        (value, regexMatches) => {
 | 
			
		||||
            const dataType = sortedDataTypeMap[databaseType].find(
 | 
			
		||||
                (v) => v.id === value
 | 
			
		||||
            ) ?? {
 | 
			
		||||
                id: value as string,
 | 
			
		||||
                name: value as string,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let characterMaximumLength: string | undefined = undefined;
 | 
			
		||||
            let precision: number | undefined = undefined;
 | 
			
		||||
            let scale: number | undefined = undefined;
 | 
			
		||||
 | 
			
		||||
            if (regexMatches?.length) {
 | 
			
		||||
                if (dataType?.fieldAttributes?.hasCharMaxLength) {
 | 
			
		||||
                    characterMaximumLength = regexMatches[1]?.toLowerCase();
 | 
			
		||||
                } else if (
 | 
			
		||||
                    dataType?.fieldAttributes?.precision &&
 | 
			
		||||
                    dataType?.fieldAttributes?.scale
 | 
			
		||||
                ) {
 | 
			
		||||
                    precision = parseInt(regexMatches[1]);
 | 
			
		||||
                    scale = regexMatches[2]
 | 
			
		||||
                        ? parseInt(regexMatches[2])
 | 
			
		||||
                        : undefined;
 | 
			
		||||
                } else if (dataType?.fieldAttributes?.precision) {
 | 
			
		||||
                    precision = parseInt(regexMatches[1]);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if (
 | 
			
		||||
                    dataType?.fieldAttributes?.hasCharMaxLength &&
 | 
			
		||||
                    field.characterMaximumLength
 | 
			
		||||
                ) {
 | 
			
		||||
                    characterMaximumLength = field.characterMaximumLength;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (dataType?.fieldAttributes?.precision && field.precision) {
 | 
			
		||||
                    precision = field.precision;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (dataType?.fieldAttributes?.scale && field.scale) {
 | 
			
		||||
                    scale = field.scale;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            updateField(table.id, field.id, {
 | 
			
		||||
                characterMaximumLength,
 | 
			
		||||
                precision,
 | 
			
		||||
                scale,
 | 
			
		||||
                increment: undefined,
 | 
			
		||||
                default: undefined,
 | 
			
		||||
                type: dataTypeDataToDataType(
 | 
			
		||||
                    dataType ?? {
 | 
			
		||||
                        id: value as string,
 | 
			
		||||
                        name: value as string,
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        [
 | 
			
		||||
            updateField,
 | 
			
		||||
            databaseType,
 | 
			
		||||
            field.characterMaximumLength,
 | 
			
		||||
            field.precision,
 | 
			
		||||
            field.scale,
 | 
			
		||||
            field.id,
 | 
			
		||||
            table.id,
 | 
			
		||||
        ]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Debounced update for field name
 | 
			
		||||
    const debouncedNameUpdate = useDebounce(
 | 
			
		||||
        useCallback(
 | 
			
		||||
            (value: string) => {
 | 
			
		||||
                if (value.trim() !== field.name) {
 | 
			
		||||
                    updateField(table.id, field.id, { name: value });
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            [updateField, table.id, field.id, field.name]
 | 
			
		||||
        ),
 | 
			
		||||
        300 // 300ms debounce for text input
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Debounced update for nullable toggle
 | 
			
		||||
    const debouncedNullableUpdate = useDebounce(
 | 
			
		||||
        useCallback(
 | 
			
		||||
            (value: boolean) => {
 | 
			
		||||
                updateField(table.id, field.id, { nullable: value });
 | 
			
		||||
            },
 | 
			
		||||
            [updateField, table.id, field.id]
 | 
			
		||||
        ),
 | 
			
		||||
        100 // 100ms debounce for toggle
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Debounced update for primary key toggle
 | 
			
		||||
    const debouncedPrimaryKeyUpdate = useDebounce(
 | 
			
		||||
        useCallback(
 | 
			
		||||
            (value: boolean, primaryKeyCount: number) => {
 | 
			
		||||
                if (value) {
 | 
			
		||||
                    // When setting as primary key
 | 
			
		||||
                    const updates: Partial<DBField> = {
 | 
			
		||||
                        primaryKey: true,
 | 
			
		||||
                    };
 | 
			
		||||
                    // Only auto-set unique if this will be the only primary key
 | 
			
		||||
                    if (primaryKeyCount === 0) {
 | 
			
		||||
                        updates.unique = true;
 | 
			
		||||
                    }
 | 
			
		||||
                    updateField(table.id, field.id, updates);
 | 
			
		||||
                } else {
 | 
			
		||||
                    // When removing primary key
 | 
			
		||||
                    updateField(table.id, field.id, {
 | 
			
		||||
                        primaryKey: false,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            [updateField, table.id, field.id]
 | 
			
		||||
        ),
 | 
			
		||||
        100 // 100ms debounce for toggle
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Handle primary key toggle with optimistic update
 | 
			
		||||
    const handlePrimaryKeyToggle = useCallback(
 | 
			
		||||
        (value: boolean) => {
 | 
			
		||||
            setLocalPrimaryKey(value);
 | 
			
		||||
            debouncedPrimaryKeyUpdate(value, primaryKeyCount);
 | 
			
		||||
        },
 | 
			
		||||
        [primaryKeyCount, debouncedPrimaryKeyUpdate]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Handle nullable toggle with optimistic update
 | 
			
		||||
    const handleNullableToggle = useCallback(
 | 
			
		||||
        (value: boolean) => {
 | 
			
		||||
            setLocalNullable(value);
 | 
			
		||||
            debouncedNullableUpdate(value);
 | 
			
		||||
        },
 | 
			
		||||
        [debouncedNullableUpdate]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Handle name change with optimistic update
 | 
			
		||||
    const handleNameChange = useCallback(
 | 
			
		||||
        (value: string) => {
 | 
			
		||||
            setLocalFieldName(value);
 | 
			
		||||
            debouncedNameUpdate(value);
 | 
			
		||||
        },
 | 
			
		||||
        [debouncedNameUpdate]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Utility function to generate field suffix for display
 | 
			
		||||
    const generateFieldSuffix = useCallback(
 | 
			
		||||
        (typeId?: string) => {
 | 
			
		||||
            return generateDBFieldSuffix(field, {
 | 
			
		||||
                databaseType,
 | 
			
		||||
                forceExtended: true,
 | 
			
		||||
                typeId,
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        [field, databaseType]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const removeField = useCallback(() => {
 | 
			
		||||
        chartDBRemoveField(table.id, field.id);
 | 
			
		||||
    }, [chartDBRemoveField, table.id, field.id]);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        dataFieldOptions,
 | 
			
		||||
        handleDataTypeChange,
 | 
			
		||||
        handlePrimaryKeyToggle,
 | 
			
		||||
        handleNullableToggle,
 | 
			
		||||
        handleNameChange,
 | 
			
		||||
        generateFieldSuffix,
 | 
			
		||||
        primaryKeyCount,
 | 
			
		||||
        fieldName: localFieldName,
 | 
			
		||||
        nullable: localNullable,
 | 
			
		||||
        primaryKey: localPrimaryKey,
 | 
			
		||||
        removeField,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
import { useCallback, useState, useEffect } from 'react';
 | 
			
		||||
import { useChartDB } from './use-chartdb';
 | 
			
		||||
import { useDebounce } from './use-debounce-v2';
 | 
			
		||||
import type { DBTable } from '@/lib/domain';
 | 
			
		||||
 | 
			
		||||
// Hook for updating table properties with debouncing for performance
 | 
			
		||||
export const useUpdateTable = (table: DBTable) => {
 | 
			
		||||
    const { updateTable: chartDBUpdateTable } = useChartDB();
 | 
			
		||||
    const [localTableName, setLocalTableName] = useState(table.name);
 | 
			
		||||
 | 
			
		||||
    // Debounced update function
 | 
			
		||||
    const debouncedUpdate = useDebounce(
 | 
			
		||||
        useCallback(
 | 
			
		||||
            (value: string) => {
 | 
			
		||||
                if (value.trim() && value.trim() !== table.name) {
 | 
			
		||||
                    chartDBUpdateTable(table.id, { name: value.trim() });
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            [chartDBUpdateTable, table.id, table.name]
 | 
			
		||||
        ),
 | 
			
		||||
        1000 // 1000ms debounce
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Update local state immediately for responsive UI
 | 
			
		||||
    const handleTableNameChange = useCallback(
 | 
			
		||||
        (value: string) => {
 | 
			
		||||
            setLocalTableName(value);
 | 
			
		||||
            debouncedUpdate(value);
 | 
			
		||||
        },
 | 
			
		||||
        [debouncedUpdate]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Update local state when table name changes externally
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setLocalTableName(table.name);
 | 
			
		||||
    }, [table.name]);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        tableName: localTableName,
 | 
			
		||||
        handleTableNameChange,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
@@ -12,15 +12,16 @@ export const ar: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'الأنواع المخصصة',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'الإجراءات',
 | 
			
		||||
                new: 'جديد...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'قواعد البيانات',
 | 
			
		||||
                new: 'مخطط جديد',
 | 
			
		||||
                browse: 'تصفح...',
 | 
			
		||||
                save: 'حفظ',
 | 
			
		||||
                duplicate: 'تكرار',
 | 
			
		||||
                import: 'استيراد قاعدة بيانات',
 | 
			
		||||
                export_sql: 'SQL تصدير',
 | 
			
		||||
                export_as: 'تصدير كـ',
 | 
			
		||||
                delete_diagram: 'حذف',
 | 
			
		||||
                delete_diagram: 'حذف الرسم البياني',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'تحرير',
 | 
			
		||||
@@ -74,10 +75,10 @@ export const ar: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'ترتيب تلقائي للرسم البياني',
 | 
			
		||||
            title: 'إعادة ترتيب الرسم البياني',
 | 
			
		||||
            description:
 | 
			
		||||
                'هذا الإجراء سيقوم بإعادة ترتيب الجداول في المخطط بشكل تلقائي. هل تريد المتابعة؟',
 | 
			
		||||
            reorder: 'ترتيب تلقائي',
 | 
			
		||||
            reorder: 'إعادة ترتيب',
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -248,7 +249,6 @@ export const ar: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'لم يتم تحديد قيم التعداد',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -271,7 +271,7 @@ export const ar: LanguageTranslation = {
 | 
			
		||||
            show_all: 'عرض الكل',
 | 
			
		||||
            undo: 'تراجع',
 | 
			
		||||
            redo: 'إعادة',
 | 
			
		||||
            reorder_diagram: 'ترتيب تلقائي للرسم البياني',
 | 
			
		||||
            reorder_diagram: 'إعادة ترتيب الرسم البياني',
 | 
			
		||||
            highlight_overlapping_tables: 'تمييز الجداول المتداخلة',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            filter: 'Filter Tables',
 | 
			
		||||
@@ -314,7 +314,7 @@ export const ar: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'فتح قاعدة بيانات',
 | 
			
		||||
            title: 'فتح مخطط',
 | 
			
		||||
            description: 'اختر مخططًا لفتحه من القائمة ادناه',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'الإسم',
 | 
			
		||||
@@ -324,12 +324,6 @@ export const ar: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'إلغاء',
 | 
			
		||||
            open: 'فتح',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'فتح',
 | 
			
		||||
                duplicate: 'تكرار',
 | 
			
		||||
                delete: 'حذف',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const bn: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'কাস্টম টাইপ',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'কার্য',
 | 
			
		||||
                new: 'নতুন...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'ডাটাবেস',
 | 
			
		||||
                new: 'নতুন ডায়াগ্রাম',
 | 
			
		||||
                browse: 'ব্রাউজ করুন...',
 | 
			
		||||
                save: 'সংরক্ষণ করুন',
 | 
			
		||||
                duplicate: 'ডুপ্লিকেট করুন',
 | 
			
		||||
                import: 'ডাটাবেস আমদানি করুন',
 | 
			
		||||
                export_sql: 'SQL রপ্তানি করুন',
 | 
			
		||||
                export_as: 'রূপে রপ্তানি করুন',
 | 
			
		||||
                delete_diagram: 'মুছুন',
 | 
			
		||||
                delete_diagram: 'ডায়াগ্রাম মুছুন',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'সম্পাদনা',
 | 
			
		||||
@@ -75,10 +76,10 @@ export const bn: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'স্বয়ংক্রিয় ডায়াগ্রাম সাজান',
 | 
			
		||||
            title: 'ডায়াগ্রাম পুনর্বিন্যাস করুন',
 | 
			
		||||
            description:
 | 
			
		||||
                'এই কাজটি ডায়াগ্রামের সমস্ত টেবিল পুনর্বিন্যাস করবে। আপনি কি চালিয়ে যেতে চান?',
 | 
			
		||||
            reorder: 'স্বয়ংক্রিয় সাজান',
 | 
			
		||||
            reorder: 'পুনর্বিন্যাস করুন',
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -249,7 +250,6 @@ export const bn: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'কোন enum মান সংজ্ঞায়িত নেই',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -272,7 +272,7 @@ export const bn: LanguageTranslation = {
 | 
			
		||||
            show_all: 'সব দেখান',
 | 
			
		||||
            undo: 'পূর্বাবস্থায় ফিরুন',
 | 
			
		||||
            redo: 'পুনরায় করুন',
 | 
			
		||||
            reorder_diagram: 'স্বয়ংক্রিয় ডায়াগ্রাম সাজান',
 | 
			
		||||
            reorder_diagram: 'ডায়াগ্রাম পুনর্বিন্যাস করুন',
 | 
			
		||||
            highlight_overlapping_tables: 'ওভারল্যাপিং টেবিল হাইলাইট করুন',
 | 
			
		||||
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
@@ -316,7 +316,7 @@ export const bn: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'ডেটাবেস খুলুন',
 | 
			
		||||
            title: 'চিত্র খুলুন',
 | 
			
		||||
            description: 'নিচের তালিকা থেকে একটি চিত্র নির্বাচন করুন।',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'নাম',
 | 
			
		||||
@@ -326,12 +326,6 @@ export const bn: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'বাতিল করুন',
 | 
			
		||||
            open: 'খুলুন',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'খুলুন',
 | 
			
		||||
                duplicate: 'ডুপ্লিকেট',
 | 
			
		||||
                delete: 'মুছুন',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const de: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'Benutzerdefinierte Typen',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Aktionen',
 | 
			
		||||
                new: 'Neu...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Datenbanken',
 | 
			
		||||
                new: 'Neues Diagramm',
 | 
			
		||||
                browse: 'Durchsuchen...',
 | 
			
		||||
                save: 'Speichern',
 | 
			
		||||
                duplicate: 'Diagramm duplizieren',
 | 
			
		||||
                import: 'Datenbank importieren',
 | 
			
		||||
                export_sql: 'SQL exportieren',
 | 
			
		||||
                export_as: 'Exportieren als',
 | 
			
		||||
                delete_diagram: 'Löschen',
 | 
			
		||||
                delete_diagram: 'Diagramm löschen',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Bearbeiten',
 | 
			
		||||
@@ -75,10 +76,10 @@ export const de: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Diagramm automatisch anordnen',
 | 
			
		||||
            title: 'Diagramm neu anordnen',
 | 
			
		||||
            description:
 | 
			
		||||
                'Diese Aktion wird alle Tabellen im Diagramm neu anordnen. Möchten Sie fortfahren?',
 | 
			
		||||
            reorder: 'Automatisch anordnen',
 | 
			
		||||
            reorder: 'Neu anordnen',
 | 
			
		||||
            cancel: 'Abbrechen',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -250,7 +251,6 @@ export const de: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'Keine Enum-Werte definiert',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -273,7 +273,7 @@ export const de: LanguageTranslation = {
 | 
			
		||||
            show_all: 'Alle anzeigen',
 | 
			
		||||
            undo: 'Rückgängig',
 | 
			
		||||
            redo: 'Wiederholen',
 | 
			
		||||
            reorder_diagram: 'Diagramm automatisch anordnen',
 | 
			
		||||
            reorder_diagram: 'Diagramm neu anordnen',
 | 
			
		||||
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
@@ -305,7 +305,7 @@ export const de: LanguageTranslation = {
 | 
			
		||||
                    step_1: 'Gehen Sie zu Tools > Optionen > Abfrageergebnisse > SQL Server.',
 | 
			
		||||
                    step_2: 'Wenn Sie "Ergebnisse in Raster" verwenden, ändern Sie die maximale Zeichenanzahl für Nicht-XML-Daten (auf 9999999 setzen).',
 | 
			
		||||
                },
 | 
			
		||||
                instructions_link: 'Brauchen Sie Hilfe? So geht’s',
 | 
			
		||||
                instructions_link: "Brauchen Sie Hilfe? So geht's",
 | 
			
		||||
                check_script_result: 'Skriptergebnis überprüfen',
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
@@ -319,7 +319,7 @@ export const de: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Datenbank öffnen',
 | 
			
		||||
            title: 'Diagramm öffnen',
 | 
			
		||||
            description: 'Wählen Sie ein Diagramm aus der Liste unten aus.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'Name',
 | 
			
		||||
@@ -329,12 +329,6 @@ export const de: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'Abbrechen',
 | 
			
		||||
            open: 'Öffnen',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Öffnen',
 | 
			
		||||
                duplicate: 'Duplizieren',
 | 
			
		||||
                delete: 'Löschen',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const en = {
 | 
			
		||||
            custom_types: 'Custom Types',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Actions',
 | 
			
		||||
                new: 'New...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Databases',
 | 
			
		||||
                new: 'New Diagram',
 | 
			
		||||
                browse: 'Browse...',
 | 
			
		||||
                save: 'Save',
 | 
			
		||||
                duplicate: 'Duplicate Diagram',
 | 
			
		||||
                import: 'Import',
 | 
			
		||||
                export_sql: 'Export SQL',
 | 
			
		||||
                export_as: 'Export as',
 | 
			
		||||
                delete_diagram: 'Delete',
 | 
			
		||||
                delete_diagram: 'Delete Diagram',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Edit',
 | 
			
		||||
@@ -73,10 +74,10 @@ export const en = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Auto Arrange Diagram',
 | 
			
		||||
            title: 'Reorder Diagram',
 | 
			
		||||
            description:
 | 
			
		||||
                'This action will rearrange all tables in the diagram. Do you want to continue?',
 | 
			
		||||
            reorder: 'Auto Arrange',
 | 
			
		||||
            reorder: 'Reorder',
 | 
			
		||||
            cancel: 'Cancel',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -242,7 +243,6 @@ export const en = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'No enum values defined',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -265,7 +265,7 @@ export const en = {
 | 
			
		||||
            show_all: 'Show All',
 | 
			
		||||
            undo: 'Undo',
 | 
			
		||||
            redo: 'Redo',
 | 
			
		||||
            reorder_diagram: 'Auto Arrange Diagram',
 | 
			
		||||
            reorder_diagram: 'Reorder Diagram',
 | 
			
		||||
            highlight_overlapping_tables: 'Highlight Overlapping Tables',
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -307,7 +307,7 @@ export const en = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Open Database',
 | 
			
		||||
            title: 'Open Diagram',
 | 
			
		||||
            description: 'Select a diagram to open from the list below.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'Name',
 | 
			
		||||
@@ -317,12 +317,6 @@ export const en = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'Cancel',
 | 
			
		||||
            open: 'Open',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Open',
 | 
			
		||||
                duplicate: 'Duplicate',
 | 
			
		||||
                delete: 'Delete',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const es: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'Tipos Personalizados',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Acciones',
 | 
			
		||||
                new: 'Nuevo...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Bases de Datos',
 | 
			
		||||
                new: 'Nuevo Diagrama',
 | 
			
		||||
                browse: 'Examinar...',
 | 
			
		||||
                save: 'Guardar',
 | 
			
		||||
                duplicate: 'Duplicar',
 | 
			
		||||
                import: 'Importar Base de Datos',
 | 
			
		||||
                export_sql: 'Exportar SQL',
 | 
			
		||||
                export_as: 'Exportar como',
 | 
			
		||||
                delete_diagram: 'Eliminar',
 | 
			
		||||
                delete_diagram: 'Eliminar Diagrama',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Editar',
 | 
			
		||||
@@ -74,10 +75,10 @@ export const es: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Organizar Diagrama Automáticamente',
 | 
			
		||||
            title: 'Reordenar Diagrama',
 | 
			
		||||
            description:
 | 
			
		||||
                'Esta acción reorganizará todas las tablas en el diagrama. ¿Deseas continuar?',
 | 
			
		||||
            reorder: 'Organizar Automáticamente',
 | 
			
		||||
            reorder: 'Reordenar',
 | 
			
		||||
            cancel: 'Cancelar',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -248,7 +249,6 @@ export const es: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'No hay valores de enum definidos',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -271,7 +271,7 @@ export const es: LanguageTranslation = {
 | 
			
		||||
            show_all: 'Mostrar Todo',
 | 
			
		||||
            undo: 'Deshacer',
 | 
			
		||||
            redo: 'Rehacer',
 | 
			
		||||
            reorder_diagram: 'Organizar Diagrama Automáticamente',
 | 
			
		||||
            reorder_diagram: 'Reordenar Diagrama',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -316,7 +316,7 @@ export const es: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Abrir Base de Datos',
 | 
			
		||||
            title: 'Abrir Diagrama',
 | 
			
		||||
            description:
 | 
			
		||||
                'Selecciona un diagrama para abrir de la lista a continuación.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
@@ -327,12 +327,6 @@ export const es: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'Cancelar',
 | 
			
		||||
            open: 'Abrir',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Abrir',
 | 
			
		||||
                duplicate: 'Duplicar',
 | 
			
		||||
                delete: 'Eliminar',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'Types Personnalisés',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Actions',
 | 
			
		||||
                new: 'Nouveau...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Bases de Données',
 | 
			
		||||
                new: 'Nouveau Diagramme',
 | 
			
		||||
                browse: 'Parcourir...',
 | 
			
		||||
                save: 'Enregistrer',
 | 
			
		||||
                duplicate: 'Dupliquer',
 | 
			
		||||
                import: 'Importer Base de Données',
 | 
			
		||||
                export_sql: 'Exporter SQL',
 | 
			
		||||
                export_as: 'Exporter en tant que',
 | 
			
		||||
                delete_diagram: 'Supprimer',
 | 
			
		||||
                delete_diagram: 'Supprimer le Diagramme',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Édition',
 | 
			
		||||
@@ -73,10 +74,10 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Organiser Automatiquement le Diagramme',
 | 
			
		||||
            title: 'Réorganiser le Diagramme',
 | 
			
		||||
            description:
 | 
			
		||||
                'Cette action réorganisera toutes les tables dans le diagramme. Voulez-vous continuer ?',
 | 
			
		||||
            reorder: 'Organiser Automatiquement',
 | 
			
		||||
            reorder: 'Réorganiser',
 | 
			
		||||
            cancel: 'Annuler',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -246,7 +247,6 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: "Aucune valeur d'énumération définie",
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -269,7 +269,7 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
            show_all: 'Afficher Tout',
 | 
			
		||||
            undo: 'Annuler',
 | 
			
		||||
            redo: 'Rétablir',
 | 
			
		||||
            reorder_diagram: 'Organiser Automatiquement le Diagramme',
 | 
			
		||||
            reorder_diagram: 'Réorganiser le Diagramme',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -313,7 +313,7 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Ouvrir Base de Données',
 | 
			
		||||
            title: 'Ouvrir Diagramme',
 | 
			
		||||
            description:
 | 
			
		||||
                'Sélectionnez un diagramme à ouvrir dans la liste ci-dessous.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
@@ -324,12 +324,6 @@ export const fr: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'Annuler',
 | 
			
		||||
            open: 'Ouvrir',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Ouvrir',
 | 
			
		||||
                duplicate: 'Dupliquer',
 | 
			
		||||
                delete: 'Supprimer',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const gu: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'કસ્ટમ ટાઇપ',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'ક્રિયાઓ',
 | 
			
		||||
                new: 'નવું...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'ડેટાબેસેસ',
 | 
			
		||||
                new: 'નવું ડાયાગ્રામ',
 | 
			
		||||
                browse: 'બ્રાઉજ કરો...',
 | 
			
		||||
                save: 'સાચવો',
 | 
			
		||||
                duplicate: 'ડુપ્લિકેટ',
 | 
			
		||||
                import: 'ડેટાબેસ આયાત કરો',
 | 
			
		||||
                export_sql: 'SQL નિકાસ કરો',
 | 
			
		||||
                export_as: 'રૂપે નિકાસ કરો',
 | 
			
		||||
                delete_diagram: 'કાઢી નાખો',
 | 
			
		||||
                delete_diagram: 'ડાયાગ્રામ કાઢી નાખો',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'ફેરફાર',
 | 
			
		||||
@@ -75,10 +76,10 @@ export const gu: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'ડાયાગ્રામ ઑટોમેટિક ગોઠવો',
 | 
			
		||||
            title: 'ડાયાગ્રામ ફરી વ્યવસ્થિત કરો',
 | 
			
		||||
            description:
 | 
			
		||||
                'આ ક્રિયા ડાયાગ્રામમાં બધી ટેબલ્સને ફરીથી વ્યવસ્થિત કરશે. શું તમે ચાલુ રાખવા માંગો છો?',
 | 
			
		||||
            reorder: 'ઑટોમેટિક ગોઠવો',
 | 
			
		||||
            reorder: 'ફરી વ્યવસ્થિત કરો',
 | 
			
		||||
            cancel: 'રદ કરો',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -250,7 +251,6 @@ export const gu: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'કોઈ enum મૂલ્યો વ્યાખ્યાયિત નથી',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -273,7 +273,7 @@ export const gu: LanguageTranslation = {
 | 
			
		||||
            show_all: 'બધું બતાવો',
 | 
			
		||||
            undo: 'અનડુ',
 | 
			
		||||
            redo: 'રીડુ',
 | 
			
		||||
            reorder_diagram: 'ડાયાગ્રામ ઑટોમેટિક ગોઠવો',
 | 
			
		||||
            reorder_diagram: 'ડાયાગ્રામ ફરીથી વ્યવસ્થિત કરો',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -316,7 +316,7 @@ export const gu: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'ડેટાબેસ ખોલો',
 | 
			
		||||
            title: 'ડાયાગ્રામ ખોલો',
 | 
			
		||||
            description: 'નીચેની યાદીમાંથી એક ડાયાગ્રામ પસંદ કરો.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'નામ',
 | 
			
		||||
@@ -326,12 +326,6 @@ export const gu: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'રદ કરો',
 | 
			
		||||
            open: 'ખોલો',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'ખોલો',
 | 
			
		||||
                duplicate: 'ડુપ્લિકેટ',
 | 
			
		||||
                delete: 'કાઢી નાખો',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const hi: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'कस्टम टाइप',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'कार्य',
 | 
			
		||||
                new: 'नया...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'डेटाबेस',
 | 
			
		||||
                new: 'नया आरेख',
 | 
			
		||||
                browse: 'ब्राउज़ करें...',
 | 
			
		||||
                save: 'सहेजें',
 | 
			
		||||
                duplicate: 'डुप्लिकेट',
 | 
			
		||||
                import: 'डेटाबेस आयात करें',
 | 
			
		||||
                export_sql: 'SQL निर्यात करें',
 | 
			
		||||
                export_as: 'के रूप में निर्यात करें',
 | 
			
		||||
                delete_diagram: 'हटाएँ',
 | 
			
		||||
                delete_diagram: 'आरेख हटाएँ',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'संपादित करें',
 | 
			
		||||
@@ -74,10 +75,10 @@ export const hi: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'आरेख स्वचालित व्यवस्थित करें',
 | 
			
		||||
            title: 'आरेख पुनः व्यवस्थित करें',
 | 
			
		||||
            description:
 | 
			
		||||
                'यह क्रिया आरेख में सभी तालिकाओं को पुनः व्यवस्थित कर देगी। क्या आप जारी रखना चाहते हैं?',
 | 
			
		||||
            reorder: 'स्वचालित व्यवस्थित करें',
 | 
			
		||||
            reorder: 'पुनः व्यवस्थित करें',
 | 
			
		||||
            cancel: 'रद्द करें',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -249,7 +250,6 @@ export const hi: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'कोई enum मान परिभाषित नहीं',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -272,7 +272,7 @@ export const hi: LanguageTranslation = {
 | 
			
		||||
            show_all: 'सभी दिखाएँ',
 | 
			
		||||
            undo: 'पूर्ववत करें',
 | 
			
		||||
            redo: 'पुनः करें',
 | 
			
		||||
            reorder_diagram: 'आरेख स्वचालित व्यवस्थित करें',
 | 
			
		||||
            reorder_diagram: 'आरेख पुनः व्यवस्थित करें',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -318,7 +318,7 @@ export const hi: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'डेटाबेस खोलें',
 | 
			
		||||
            title: 'आरेख खोलें',
 | 
			
		||||
            description: 'नीचे दी गई सूची से एक आरेख चुनें।',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'नाम',
 | 
			
		||||
@@ -328,12 +328,6 @@ export const hi: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'रद्द करें',
 | 
			
		||||
            open: 'खोलें',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'खोलें',
 | 
			
		||||
                duplicate: 'डुप्लिकेट',
 | 
			
		||||
                delete: 'हटाएं',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const hr: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'Prilagođeni Tipovi',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Akcije',
 | 
			
		||||
                new: 'Novi...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Baze Podataka',
 | 
			
		||||
                new: 'Novi Dijagram',
 | 
			
		||||
                browse: 'Pregledaj...',
 | 
			
		||||
                save: 'Spremi',
 | 
			
		||||
                duplicate: 'Dupliciraj dijagram',
 | 
			
		||||
                import: 'Uvezi',
 | 
			
		||||
                export_sql: 'Izvezi SQL',
 | 
			
		||||
                export_as: 'Izvezi kao',
 | 
			
		||||
                delete_diagram: 'Izbriši',
 | 
			
		||||
                delete_diagram: 'Izbriši dijagram',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Uredi',
 | 
			
		||||
@@ -73,10 +74,10 @@ export const hr: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Automatski preuredi dijagram',
 | 
			
		||||
            title: 'Preuredi dijagram',
 | 
			
		||||
            description:
 | 
			
		||||
                'Ova radnja će preurediti sve tablice u dijagramu. Želite li nastaviti?',
 | 
			
		||||
            reorder: 'Automatski preuredi',
 | 
			
		||||
            reorder: 'Preuredi',
 | 
			
		||||
            cancel: 'Odustani',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -245,7 +246,6 @@ export const hr: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum vrijednosti',
 | 
			
		||||
                    composite_fields: 'Polja',
 | 
			
		||||
                    no_fields: 'Nema definiranih polja',
 | 
			
		||||
                    no_values: 'Nema definiranih enum vrijednosti',
 | 
			
		||||
                    field_name_placeholder: 'Naziv polja',
 | 
			
		||||
                    field_type_placeholder: 'Odaberi tip',
 | 
			
		||||
                    add_field: 'Dodaj polje',
 | 
			
		||||
@@ -269,7 +269,7 @@ export const hr: LanguageTranslation = {
 | 
			
		||||
            show_all: 'Prikaži sve',
 | 
			
		||||
            undo: 'Poništi',
 | 
			
		||||
            redo: 'Ponovi',
 | 
			
		||||
            reorder_diagram: 'Automatski preuredi dijagram',
 | 
			
		||||
            reorder_diagram: 'Preuredi dijagram',
 | 
			
		||||
            highlight_overlapping_tables: 'Istakni preklapajuće tablice',
 | 
			
		||||
            clear_custom_type_highlight: 'Ukloni isticanje za "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -311,7 +311,7 @@ export const hr: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Otvori bazu podataka',
 | 
			
		||||
            title: 'Otvori dijagram',
 | 
			
		||||
            description: 'Odaberite dijagram za otvaranje iz popisa ispod.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'Naziv',
 | 
			
		||||
@@ -321,12 +321,6 @@ export const hr: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'Odustani',
 | 
			
		||||
            open: 'Otvori',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Otvori',
 | 
			
		||||
                duplicate: 'Dupliciraj',
 | 
			
		||||
                delete: 'Obriši',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const id_ID: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'Tipe Kustom',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Aksi',
 | 
			
		||||
                new: 'Baru...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Basis Data',
 | 
			
		||||
                new: 'Diagram Baru',
 | 
			
		||||
                browse: 'Jelajahi...',
 | 
			
		||||
                save: 'Simpan',
 | 
			
		||||
                duplicate: 'Duplikat',
 | 
			
		||||
                import: 'Impor Database',
 | 
			
		||||
                export_sql: 'Ekspor SQL',
 | 
			
		||||
                export_as: 'Ekspor Sebagai',
 | 
			
		||||
                delete_diagram: 'Hapus',
 | 
			
		||||
                delete_diagram: 'Hapus Diagram',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Ubah',
 | 
			
		||||
@@ -74,10 +75,10 @@ export const id_ID: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Atur Otomatis Diagram',
 | 
			
		||||
            title: 'Atur Ulang Diagram',
 | 
			
		||||
            description:
 | 
			
		||||
                'Tindakan ini akan mengatur ulang semua tabel di diagram. Apakah Anda ingin melanjutkan?',
 | 
			
		||||
            reorder: 'Atur Otomatis',
 | 
			
		||||
            reorder: 'Atur Ulang',
 | 
			
		||||
            cancel: 'Batal',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -248,7 +249,6 @@ export const id_ID: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'Tidak ada nilai enum yang ditentukan',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -271,7 +271,7 @@ export const id_ID: LanguageTranslation = {
 | 
			
		||||
            show_all: 'Tampilkan Semua',
 | 
			
		||||
            undo: 'Undo',
 | 
			
		||||
            redo: 'Redo',
 | 
			
		||||
            reorder_diagram: 'Atur Otomatis Diagram',
 | 
			
		||||
            reorder_diagram: 'Atur Ulang Diagram',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -315,7 +315,7 @@ export const id_ID: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Buka Database',
 | 
			
		||||
            title: 'Buka Diagram',
 | 
			
		||||
            description: 'Pilih diagram untuk dibuka dari daftar di bawah.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'Name',
 | 
			
		||||
@@ -325,12 +325,6 @@ export const id_ID: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'Batal',
 | 
			
		||||
            open: 'Buka',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Buka',
 | 
			
		||||
                duplicate: 'Duplikat',
 | 
			
		||||
                delete: 'Hapus',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const ja: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'カスタムタイプ',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'アクション',
 | 
			
		||||
                new: '新規...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'データベース',
 | 
			
		||||
                new: '新しいダイアグラム',
 | 
			
		||||
                browse: '参照...',
 | 
			
		||||
                save: '保存',
 | 
			
		||||
                duplicate: '複製',
 | 
			
		||||
                import: 'データベースをインポート',
 | 
			
		||||
                export_sql: 'SQLをエクスポート',
 | 
			
		||||
                export_as: '形式を指定してエクスポート',
 | 
			
		||||
                delete_diagram: '削除',
 | 
			
		||||
                delete_diagram: 'ダイアグラムを削除',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: '編集',
 | 
			
		||||
@@ -76,10 +77,10 @@ export const ja: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'ダイアグラムを自動配置',
 | 
			
		||||
            title: 'ダイアグラムを並べ替え',
 | 
			
		||||
            description:
 | 
			
		||||
                'この操作によりダイアグラム内のすべてのテーブルが再配置されます。続行しますか?',
 | 
			
		||||
            reorder: '自動配置',
 | 
			
		||||
            reorder: '並べ替え',
 | 
			
		||||
            cancel: 'キャンセル',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -253,7 +254,6 @@ export const ja: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: '列挙値が定義されていません',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -276,7 +276,7 @@ export const ja: LanguageTranslation = {
 | 
			
		||||
            show_all: 'すべて表示',
 | 
			
		||||
            undo: '元に戻す',
 | 
			
		||||
            redo: 'やり直し',
 | 
			
		||||
            reorder_diagram: 'ダイアグラムを自動配置',
 | 
			
		||||
            reorder_diagram: 'ダイアグラムを並べ替え',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            highlight_overlapping_tables: 'Highlight Overlapping Tables',
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
@@ -320,7 +320,7 @@ export const ja: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'データベースを開く',
 | 
			
		||||
            title: 'ダイアグラムを開く',
 | 
			
		||||
            description: '以下のリストからダイアグラムを選択してください。',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: '名前',
 | 
			
		||||
@@ -330,12 +330,6 @@ export const ja: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'キャンセル',
 | 
			
		||||
            open: '開く',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: '開く',
 | 
			
		||||
                duplicate: '複製',
 | 
			
		||||
                delete: '削除',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const ko_KR: LanguageTranslation = {
 | 
			
		||||
            custom_types: '사용자 지정 타입',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: '작업',
 | 
			
		||||
                new: '새로 만들기...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: '데이터베이스',
 | 
			
		||||
                new: '새 다이어그램',
 | 
			
		||||
                browse: '찾아보기...',
 | 
			
		||||
                save: '저장',
 | 
			
		||||
                duplicate: '복사',
 | 
			
		||||
                import: '데이터베이스 가져오기',
 | 
			
		||||
                export_sql: 'SQL로 저장',
 | 
			
		||||
                export_as: '다른 형식으로 저장',
 | 
			
		||||
                delete_diagram: '삭제',
 | 
			
		||||
                delete_diagram: '다이어그램 삭제',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: '편집',
 | 
			
		||||
@@ -74,10 +75,10 @@ export const ko_KR: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: '다이어그램 자동 정렬',
 | 
			
		||||
            title: '다이어그램 재정렬',
 | 
			
		||||
            description:
 | 
			
		||||
                '이 작업은 모든 다이어그램이 재정렬됩니다. 계속하시겠습니까?',
 | 
			
		||||
            reorder: '자동 정렬',
 | 
			
		||||
            reorder: '재정렬',
 | 
			
		||||
            cancel: '취소',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -248,7 +249,6 @@ export const ko_KR: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: '정의된 열거형 값이 없습니다',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -271,7 +271,7 @@ export const ko_KR: LanguageTranslation = {
 | 
			
		||||
            show_all: '전체 저장',
 | 
			
		||||
            undo: '실행 취소',
 | 
			
		||||
            redo: '다시 실행',
 | 
			
		||||
            reorder_diagram: '다이어그램 자동 정렬',
 | 
			
		||||
            reorder_diagram: '다이어그램 재정렬',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -315,7 +315,7 @@ export const ko_KR: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: '데이터베이스 열기',
 | 
			
		||||
            title: '다이어그램 열기',
 | 
			
		||||
            description: '아래의 목록에서 다이어그램을 선택하세요.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: '이름',
 | 
			
		||||
@@ -325,12 +325,6 @@ export const ko_KR: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: '취소',
 | 
			
		||||
            open: '열기',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: '열기',
 | 
			
		||||
                duplicate: '복제',
 | 
			
		||||
                delete: '삭제',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const mr: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'कस्टम प्रकार',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'क्रिया',
 | 
			
		||||
                new: 'नवीन...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'डेटाबेस',
 | 
			
		||||
                new: 'नवीन आरेख',
 | 
			
		||||
                browse: 'ब्राउज करा...',
 | 
			
		||||
                save: 'जतन करा',
 | 
			
		||||
                duplicate: 'डुप्लिकेट',
 | 
			
		||||
                import: 'डेटाबेस इम्पोर्ट करा',
 | 
			
		||||
                export_sql: 'SQL एक्स्पोर्ट करा',
 | 
			
		||||
                export_as: 'म्हणून एक्स्पोर्ट करा',
 | 
			
		||||
                delete_diagram: 'हटवा',
 | 
			
		||||
                delete_diagram: 'आरेख हटवा',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'संपादन करा',
 | 
			
		||||
@@ -75,10 +76,10 @@ export const mr: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'आरेख स्वयंचलित व्यवस्थित करा',
 | 
			
		||||
            title: 'आरेख पुनःक्रमित करा',
 | 
			
		||||
            description:
 | 
			
		||||
                'ही क्रिया आरेखातील सर्व टेबल्सची पुनर्रचना करेल. तुम्हाला पुढे जायचे आहे का?',
 | 
			
		||||
            reorder: 'स्वयंचलित व्यवस्थित करा',
 | 
			
		||||
            reorder: 'पुनःक्रमित करा',
 | 
			
		||||
            cancel: 'रद्द करा',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -252,7 +253,6 @@ export const mr: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'कोणतीही enum मूल्ये परिभाषित नाहीत',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -275,7 +275,7 @@ export const mr: LanguageTranslation = {
 | 
			
		||||
            show_all: 'सर्व दाखवा',
 | 
			
		||||
            undo: 'पूर्ववत करा',
 | 
			
		||||
            redo: 'पुन्हा करा',
 | 
			
		||||
            reorder_diagram: 'आरेख स्वयंचलित व्यवस्थित करा',
 | 
			
		||||
            reorder_diagram: 'आरेख पुनःक्रमित करा',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -321,7 +321,7 @@ export const mr: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'डेटाबेस उघडा',
 | 
			
		||||
            title: 'आरेख उघडा',
 | 
			
		||||
            description: 'खालील यादीतून उघडण्यासाठी एक आरेख निवडा.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'नाव',
 | 
			
		||||
@@ -331,12 +331,6 @@ export const mr: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'रद्द करा',
 | 
			
		||||
            open: 'उघडा',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'उघडा',
 | 
			
		||||
                duplicate: 'डुप्लिकेट',
 | 
			
		||||
                delete: 'हटवा',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const ne: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'कस्टम प्रकारहरू',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'कार्यहरू',
 | 
			
		||||
                new: 'नयाँ...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'डाटाबेसहरू',
 | 
			
		||||
                new: 'नयाँ डायाग्राम',
 | 
			
		||||
                browse: 'ब्राउज गर्नुहोस्...',
 | 
			
		||||
                save: 'सुरक्षित गर्नुहोस्',
 | 
			
		||||
                duplicate: 'डुप्लिकेट',
 | 
			
		||||
                import: 'डाटाबेस आयात गर्नुहोस्',
 | 
			
		||||
                export_sql: 'SQL निर्यात गर्नुहोस्',
 | 
			
		||||
                export_as: 'निर्यात गर्नुहोस्',
 | 
			
		||||
                delete_diagram: 'हटाउनुहोस्',
 | 
			
		||||
                delete_diagram: 'डायाग्राम हटाउनुहोस्',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'सम्पादन',
 | 
			
		||||
@@ -75,10 +76,10 @@ export const ne: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'डायाग्राम स्वचालित मिलाउनुहोस्',
 | 
			
		||||
            title: 'डायाग्राम पुनः क्रमबद्ध गर्नुहोस्',
 | 
			
		||||
            description:
 | 
			
		||||
                'यो कार्य पूर्ववत गर्न सकिँदैन। यो डायाग्राम स्थायी रूपमा हटाउनेछ।',
 | 
			
		||||
            reorder: 'स्वचालित मिलाउनुहोस्',
 | 
			
		||||
            reorder: 'पुनः क्रमबद्ध गर्नुहोस्',
 | 
			
		||||
            cancel: 'रद्द गर्नुहोस्',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -249,7 +250,6 @@ export const ne: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'कुनै enum मानहरू परिभाषित छैनन्',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -272,7 +272,7 @@ export const ne: LanguageTranslation = {
 | 
			
		||||
            show_all: 'सबै देखाउनुहोस्',
 | 
			
		||||
            undo: 'पूर्ववत',
 | 
			
		||||
            redo: 'पुनः गर्नुहोस्',
 | 
			
		||||
            reorder_diagram: 'डायाग्राम स्वचालित मिलाउनुहोस्',
 | 
			
		||||
            reorder_diagram: 'पुनः क्रमबद्ध गर्नुहोस्',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -317,7 +317,7 @@ export const ne: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'डाटाबेस खोल्नुहोस्',
 | 
			
		||||
            title: 'डायाग्राम खोल्नुहोस्',
 | 
			
		||||
            description:
 | 
			
		||||
                'तलको सूचीबाट खोल्नका लागि एक डायाग्राम चयन गर्नुहोस्।',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
@@ -328,12 +328,6 @@ export const ne: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'रद्द गर्नुहोस्',
 | 
			
		||||
            open: 'खोल्नुहोस्',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'खोल्नुहोस्',
 | 
			
		||||
                duplicate: 'डुप्लिकेट',
 | 
			
		||||
                delete: 'मेटाउनुहोस्',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const pt_BR: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'Tipos Personalizados',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Ações',
 | 
			
		||||
                new: 'Novo...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Bancos de Dados',
 | 
			
		||||
                new: 'Novo Diagrama',
 | 
			
		||||
                browse: 'Navegar...',
 | 
			
		||||
                save: 'Salvar',
 | 
			
		||||
                duplicate: 'Duplicar',
 | 
			
		||||
                import: 'Importar Banco de Dados',
 | 
			
		||||
                export_sql: 'Exportar SQL',
 | 
			
		||||
                export_as: 'Exportar como',
 | 
			
		||||
                delete_diagram: 'Excluir',
 | 
			
		||||
                delete_diagram: 'Excluir Diagrama',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Editar',
 | 
			
		||||
@@ -75,10 +76,10 @@ export const pt_BR: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Organizar Diagrama Automaticamente',
 | 
			
		||||
            title: 'Reordenar Diagrama',
 | 
			
		||||
            description:
 | 
			
		||||
                'Esta ação reorganizará todas as tabelas no diagrama. Deseja continuar?',
 | 
			
		||||
            reorder: 'Organizar Automaticamente',
 | 
			
		||||
            reorder: 'Reordenar',
 | 
			
		||||
            cancel: 'Cancelar',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -249,7 +250,6 @@ export const pt_BR: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'Nenhum valor de enum definido',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -272,7 +272,7 @@ export const pt_BR: LanguageTranslation = {
 | 
			
		||||
            show_all: 'Mostrar Tudo',
 | 
			
		||||
            undo: 'Desfazer',
 | 
			
		||||
            redo: 'Refazer',
 | 
			
		||||
            reorder_diagram: 'Organizar Diagrama Automaticamente',
 | 
			
		||||
            reorder_diagram: 'Reordenar Diagrama',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -317,7 +317,7 @@ export const pt_BR: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Abrir Banco de Dados',
 | 
			
		||||
            title: 'Abrir Diagrama',
 | 
			
		||||
            description: 'Selecione um diagrama para abrir da lista abaixo.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'Nome',
 | 
			
		||||
@@ -327,12 +327,6 @@ export const pt_BR: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'Cancelar',
 | 
			
		||||
            open: 'Abrir',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Abrir',
 | 
			
		||||
                duplicate: 'Duplicar',
 | 
			
		||||
                delete: 'Excluir',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const ru: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'Пользовательские типы',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Действия',
 | 
			
		||||
                new: 'Новая...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Базы данных',
 | 
			
		||||
                new: 'Новая диаграмма',
 | 
			
		||||
                browse: 'Обзор...',
 | 
			
		||||
                save: 'Сохранить',
 | 
			
		||||
                duplicate: 'Дублировать',
 | 
			
		||||
                import: 'Импортировать базу данных',
 | 
			
		||||
                export_sql: 'Экспорт SQL',
 | 
			
		||||
                export_as: 'Экспортировать как',
 | 
			
		||||
                delete_diagram: 'Удалить',
 | 
			
		||||
                delete_diagram: 'Удалить диаграмму',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Изменение',
 | 
			
		||||
@@ -73,10 +74,10 @@ export const ru: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Автоматическая расстановка диаграммы',
 | 
			
		||||
            title: 'Переупорядочить диаграмму',
 | 
			
		||||
            description:
 | 
			
		||||
                'Это действие переставит все таблицы на диаграмме. Хотите продолжить?',
 | 
			
		||||
            reorder: 'Автоматическая расстановка',
 | 
			
		||||
            reorder: 'Изменить порядок',
 | 
			
		||||
            cancel: 'Отменить',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -246,7 +247,6 @@ export const ru: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'Значения перечисления не определены',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -269,7 +269,7 @@ export const ru: LanguageTranslation = {
 | 
			
		||||
            show_all: 'Показать все',
 | 
			
		||||
            undo: 'Отменить',
 | 
			
		||||
            redo: 'Вернуть',
 | 
			
		||||
            reorder_diagram: 'Автоматическая расстановка диаграммы',
 | 
			
		||||
            reorder_diagram: 'Переупорядочить диаграмму',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -313,7 +313,7 @@ export const ru: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Открыть базу данных',
 | 
			
		||||
            title: 'Открыть диаграмму',
 | 
			
		||||
            description:
 | 
			
		||||
                'Выберите диаграмму, которую нужно открыть, из списка ниже.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
@@ -324,12 +324,6 @@ export const ru: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'Отмена',
 | 
			
		||||
            open: 'Открыть',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Открыть',
 | 
			
		||||
                duplicate: 'Дублировать',
 | 
			
		||||
                delete: 'Удалить',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const te: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'కస్టమ్ టైప్స్',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'చర్యలు',
 | 
			
		||||
                new: 'కొత్తది...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'డేటాబేస్లు',
 | 
			
		||||
                new: 'కొత్త డైగ్రాం',
 | 
			
		||||
                browse: 'బ్రాఉజ్ చేయండి...',
 | 
			
		||||
                save: 'సేవ్',
 | 
			
		||||
                duplicate: 'డుప్లికేట్',
 | 
			
		||||
                import: 'డేటాబేస్ను దిగుమతి చేసుకోండి',
 | 
			
		||||
                export_sql: 'SQL ఎగుమతి',
 | 
			
		||||
                export_as: 'వగా ఎగుమతి చేయండి',
 | 
			
		||||
                delete_diagram: 'తొలగించండి',
 | 
			
		||||
                delete_diagram: 'చిత్రాన్ని తొలగించండి',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'సవరించు',
 | 
			
		||||
@@ -75,10 +76,10 @@ export const te: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'చిత్రాన్ని స్వయంచాలకంగా అమర్చండి',
 | 
			
		||||
            title: 'చిత్రాన్ని పునఃసరిచేయండి',
 | 
			
		||||
            description:
 | 
			
		||||
                'ఈ చర్య చిత్రంలోని అన్ని పట్టికలను పునఃస్థాపిస్తుంది. మీరు కొనసాగించాలనుకుంటున్నారా?',
 | 
			
		||||
            reorder: 'స్వయంచాలకంగా అమర్చండి',
 | 
			
		||||
            reorder: 'పునఃసరిచేయండి',
 | 
			
		||||
            cancel: 'రద్దు',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -250,7 +251,6 @@ export const te: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'ఏ enum విలువలు నిర్వచించబడలేదు',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -273,7 +273,7 @@ export const te: LanguageTranslation = {
 | 
			
		||||
            show_all: 'అన్ని చూపించు',
 | 
			
		||||
            undo: 'తిరిగి చేయు',
 | 
			
		||||
            redo: 'మరలా చేయు',
 | 
			
		||||
            reorder_diagram: 'చిత్రాన్ని స్వయంచాలకంగా అమర్చండి',
 | 
			
		||||
            reorder_diagram: 'చిత్రాన్ని పునఃసరిచేయండి',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -318,7 +318,7 @@ export const te: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'డేటాబేస్ తెరవండి',
 | 
			
		||||
            title: 'చిత్రం తెరవండి',
 | 
			
		||||
            description: 'కింద ఉన్న జాబితా నుండి చిత్రాన్ని ఎంచుకోండి.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'పేరు',
 | 
			
		||||
@@ -328,12 +328,6 @@ export const te: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'రద్దు',
 | 
			
		||||
            open: 'తెరవు',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'తెరవు',
 | 
			
		||||
                duplicate: 'నకలు',
 | 
			
		||||
                delete: 'తొలగించు',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const tr: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'Özel Tipler',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Eylemler',
 | 
			
		||||
                new: 'Yeni...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Veritabanları',
 | 
			
		||||
                new: 'Yeni Diyagram',
 | 
			
		||||
                browse: 'Gözat...',
 | 
			
		||||
                save: 'Kaydet',
 | 
			
		||||
                duplicate: 'Kopyala',
 | 
			
		||||
                import: 'Veritabanı İçe Aktar',
 | 
			
		||||
                export_sql: 'SQL Olarak Dışa Aktar',
 | 
			
		||||
                export_as: 'Olarak Dışa Aktar',
 | 
			
		||||
                delete_diagram: 'Sil',
 | 
			
		||||
                delete_diagram: 'Diyagramı Sil',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Düzenle',
 | 
			
		||||
@@ -75,10 +76,10 @@ export const tr: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Diyagramı Otomatik Düzenle',
 | 
			
		||||
            title: 'Diyagramı Yeniden Sırala',
 | 
			
		||||
            description:
 | 
			
		||||
                'Bu işlem tüm tabloları yeniden düzenleyecektir. Devam etmek istiyor musunuz?',
 | 
			
		||||
            reorder: 'Otomatik Düzenle',
 | 
			
		||||
            reorder: 'Yeniden Sırala',
 | 
			
		||||
            cancel: 'İptal',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -249,7 +250,6 @@ export const tr: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'Tanımlanmış enum değeri yok',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -271,7 +271,7 @@ export const tr: LanguageTranslation = {
 | 
			
		||||
            show_all: 'Hepsini Gör',
 | 
			
		||||
            undo: 'Geri Al',
 | 
			
		||||
            redo: 'Yinele',
 | 
			
		||||
            reorder_diagram: 'Diyagramı Otomatik Düzenle',
 | 
			
		||||
            reorder_diagram: 'Diyagramı Yeniden Sırala',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -313,7 +313,7 @@ export const tr: LanguageTranslation = {
 | 
			
		||||
            import: 'İçe Aktar',
 | 
			
		||||
        },
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Veritabanı Aç',
 | 
			
		||||
            title: 'Diyagramı Aç',
 | 
			
		||||
            description: 'Aşağıdaki listeden açmak için bir diyagram seçin.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'Ad',
 | 
			
		||||
@@ -323,12 +323,6 @@ export const tr: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'İptal',
 | 
			
		||||
            open: 'Aç',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Aç',
 | 
			
		||||
                duplicate: 'Kopyala',
 | 
			
		||||
                delete: 'Sil',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const uk: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'Користувацькі типи',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Дії',
 | 
			
		||||
                new: 'Нова...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Бази даних',
 | 
			
		||||
                new: 'Нова діаграма',
 | 
			
		||||
                browse: 'Огляд...',
 | 
			
		||||
                save: 'Зберегти',
 | 
			
		||||
                duplicate: 'Дублювати',
 | 
			
		||||
                import: 'Імпорт бази даних',
 | 
			
		||||
                export_sql: 'Експорт SQL',
 | 
			
		||||
                export_as: 'Експортувати як',
 | 
			
		||||
                delete_diagram: 'Видалити',
 | 
			
		||||
                delete_diagram: 'Видалити діаграму',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Редагувати',
 | 
			
		||||
@@ -73,10 +74,10 @@ export const uk: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Автоматичне розміщення діаграми',
 | 
			
		||||
            title: 'Перевпорядкувати діаграму',
 | 
			
		||||
            description:
 | 
			
		||||
                'Ця дія перевпорядкує всі таблиці на діаграмі. Хочете продовжити?',
 | 
			
		||||
            reorder: 'Автоматичне розміщення',
 | 
			
		||||
            reorder: 'Перевпорядкувати',
 | 
			
		||||
            cancel: 'Скасувати',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -247,7 +248,6 @@ export const uk: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'Значення переліку не визначені',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -270,7 +270,7 @@ export const uk: LanguageTranslation = {
 | 
			
		||||
            show_all: 'Показати все',
 | 
			
		||||
            undo: 'Скасувати',
 | 
			
		||||
            redo: 'Повторити',
 | 
			
		||||
            reorder_diagram: 'Автоматичне розміщення діаграми',
 | 
			
		||||
            reorder_diagram: 'Перевпорядкувати діаграму',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -314,7 +314,7 @@ export const uk: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Відкрити базу даних',
 | 
			
		||||
            title: 'Відкрити діаграму',
 | 
			
		||||
            description:
 | 
			
		||||
                'Виберіть діаграму, яку потрібно відкрити, зі списку нижче.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
@@ -325,12 +325,6 @@ export const uk: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'Скасувати',
 | 
			
		||||
            open: 'Відкрити',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Відкрити',
 | 
			
		||||
                duplicate: 'Дублювати',
 | 
			
		||||
                delete: 'Видалити',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const vi: LanguageTranslation = {
 | 
			
		||||
            custom_types: 'Kiểu tùy chỉnh',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: 'Hành động',
 | 
			
		||||
                new: 'Mới...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: 'Cơ sở dữ liệu',
 | 
			
		||||
                new: 'Sơ đồ mới',
 | 
			
		||||
                browse: 'Duyệt...',
 | 
			
		||||
                save: 'Lưu',
 | 
			
		||||
                duplicate: 'Nhân đôi',
 | 
			
		||||
                import: 'Nhập cơ sở dữ liệu',
 | 
			
		||||
                export_sql: 'Xuất SQL',
 | 
			
		||||
                export_as: 'Xuất thành',
 | 
			
		||||
                delete_diagram: 'Xóa',
 | 
			
		||||
                delete_diagram: 'Xóa sơ đồ',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: 'Sửa',
 | 
			
		||||
@@ -74,10 +75,10 @@ export const vi: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: 'Tự động sắp xếp sơ đồ',
 | 
			
		||||
            title: 'Sắp xếp lại sơ đồ',
 | 
			
		||||
            description:
 | 
			
		||||
                'Hành động này sẽ sắp xếp lại tất cả các bảng trong sơ đồ. Bạn có muốn tiếp tục không?',
 | 
			
		||||
            reorder: 'Tự động sắp xếp',
 | 
			
		||||
            reorder: 'Sắp xếp',
 | 
			
		||||
            cancel: 'Hủy',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -248,7 +249,6 @@ export const vi: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: 'Không có giá trị enum được định nghĩa',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -271,7 +271,7 @@ export const vi: LanguageTranslation = {
 | 
			
		||||
            show_all: 'Hiển thị tất cả',
 | 
			
		||||
            undo: 'Hoàn tác',
 | 
			
		||||
            redo: 'Làm lại',
 | 
			
		||||
            reorder_diagram: 'Tự động sắp xếp sơ đồ',
 | 
			
		||||
            reorder_diagram: 'Sắp xếp lại sơ đồ',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -315,7 +315,7 @@ export const vi: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: 'Mở cơ sở dữ liệu',
 | 
			
		||||
            title: 'Mở sơ đồ',
 | 
			
		||||
            description: 'Chọn sơ đồ để mở từ danh sách bên dưới.',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: 'Tên',
 | 
			
		||||
@@ -325,12 +325,6 @@ export const vi: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: 'Hủy',
 | 
			
		||||
            open: 'Mở',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: 'Mở',
 | 
			
		||||
                duplicate: 'Nhân bản',
 | 
			
		||||
                delete: 'Xóa',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const zh_CN: LanguageTranslation = {
 | 
			
		||||
            custom_types: '自定义类型',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: '操作',
 | 
			
		||||
                new: '新建...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: '数据库',
 | 
			
		||||
                new: '新建关系图',
 | 
			
		||||
                browse: '浏览...',
 | 
			
		||||
                save: '保存',
 | 
			
		||||
                duplicate: '复制',
 | 
			
		||||
                import: '导入数据库',
 | 
			
		||||
                export_sql: '导出 SQL 语句',
 | 
			
		||||
                export_as: '导出为',
 | 
			
		||||
                delete_diagram: '删除',
 | 
			
		||||
                delete_diagram: '删除关系图',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: '编辑',
 | 
			
		||||
@@ -72,9 +73,9 @@ export const zh_CN: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: '自动排列关系图',
 | 
			
		||||
            title: '重新排列关系图',
 | 
			
		||||
            description: '此操作将重新排列关系图中的所有表。是否要继续?',
 | 
			
		||||
            reorder: '自动排列',
 | 
			
		||||
            reorder: '重新排列',
 | 
			
		||||
            cancel: '取消',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -245,7 +246,6 @@ export const zh_CN: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: '没有定义枚举值',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -268,7 +268,7 @@ export const zh_CN: LanguageTranslation = {
 | 
			
		||||
            show_all: '展示全部',
 | 
			
		||||
            undo: '撤销',
 | 
			
		||||
            redo: '重做',
 | 
			
		||||
            reorder_diagram: '自动排列关系图',
 | 
			
		||||
            reorder_diagram: '重新排列关系图',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -312,7 +312,7 @@ export const zh_CN: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: '打开数据库',
 | 
			
		||||
            title: '打开关系图',
 | 
			
		||||
            description: '从下面的列表中选择一个图表打开。',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: '名称',
 | 
			
		||||
@@ -322,12 +322,6 @@ export const zh_CN: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: '取消',
 | 
			
		||||
            open: '打开',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: '打开',
 | 
			
		||||
                duplicate: '复制',
 | 
			
		||||
                delete: '删除',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,16 @@ export const zh_TW: LanguageTranslation = {
 | 
			
		||||
            custom_types: '自定義類型',
 | 
			
		||||
        },
 | 
			
		||||
        menu: {
 | 
			
		||||
            actions: {
 | 
			
		||||
                actions: '操作',
 | 
			
		||||
                new: '新增...',
 | 
			
		||||
            databases: {
 | 
			
		||||
                databases: '資料庫',
 | 
			
		||||
                new: '新增圖表',
 | 
			
		||||
                browse: '瀏覽...',
 | 
			
		||||
                save: '儲存',
 | 
			
		||||
                duplicate: '複製',
 | 
			
		||||
                import: '匯入資料庫',
 | 
			
		||||
                export_sql: '匯出 SQL',
 | 
			
		||||
                export_as: '匯出為特定格式',
 | 
			
		||||
                delete_diagram: '刪除',
 | 
			
		||||
                delete_diagram: '刪除圖表',
 | 
			
		||||
            },
 | 
			
		||||
            edit: {
 | 
			
		||||
                edit: '編輯',
 | 
			
		||||
@@ -72,9 +73,9 @@ export const zh_TW: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        reorder_diagram_alert: {
 | 
			
		||||
            title: '自動排列圖表',
 | 
			
		||||
            title: '重新排列圖表',
 | 
			
		||||
            description: '此操作將重新排列圖表中的所有表格。是否繼續?',
 | 
			
		||||
            reorder: '自動排列',
 | 
			
		||||
            reorder: '重新排列',
 | 
			
		||||
            cancel: '取消',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@@ -245,7 +246,6 @@ export const zh_TW: LanguageTranslation = {
 | 
			
		||||
                    enum_values: 'Enum Values',
 | 
			
		||||
                    composite_fields: 'Fields',
 | 
			
		||||
                    no_fields: 'No fields defined',
 | 
			
		||||
                    no_values: '沒有定義列舉值',
 | 
			
		||||
                    field_name_placeholder: 'Field name',
 | 
			
		||||
                    field_type_placeholder: 'Select type',
 | 
			
		||||
                    add_field: 'Add Field',
 | 
			
		||||
@@ -268,7 +268,7 @@ export const zh_TW: LanguageTranslation = {
 | 
			
		||||
            show_all: '顯示全部',
 | 
			
		||||
            undo: '復原',
 | 
			
		||||
            redo: '重做',
 | 
			
		||||
            reorder_diagram: '自動排列圖表',
 | 
			
		||||
            reorder_diagram: '重新排列圖表',
 | 
			
		||||
            // TODO: Translate
 | 
			
		||||
            clear_custom_type_highlight: 'Clear highlight for "{{typeName}}"',
 | 
			
		||||
            custom_type_highlight_tooltip:
 | 
			
		||||
@@ -311,7 +311,7 @@ export const zh_TW: LanguageTranslation = {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        open_diagram_dialog: {
 | 
			
		||||
            title: '開啟資料庫',
 | 
			
		||||
            title: '開啟圖表',
 | 
			
		||||
            description: '請從以下列表中選擇一個圖表。',
 | 
			
		||||
            table_columns: {
 | 
			
		||||
                name: '名稱',
 | 
			
		||||
@@ -321,12 +321,6 @@ export const zh_TW: LanguageTranslation = {
 | 
			
		||||
            },
 | 
			
		||||
            cancel: '取消',
 | 
			
		||||
            open: '開啟',
 | 
			
		||||
 | 
			
		||||
            diagram_actions: {
 | 
			
		||||
                open: '開啟',
 | 
			
		||||
                duplicate: '複製',
 | 
			
		||||
                delete: '刪除',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        export_sql_dialog: {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,4 @@
 | 
			
		||||
 | 
			
		||||
    .marker-definitions {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .nodrag {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,20 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { describe, it, expect, vi } from 'vitest';
 | 
			
		||||
import { exportBaseSQL } from '../export-sql-script';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import type { Diagram } from '@/lib/domain/diagram';
 | 
			
		||||
import type { DBTable } from '@/lib/domain/db-table';
 | 
			
		||||
import type { DBField } from '@/lib/domain/db-field';
 | 
			
		||||
 | 
			
		||||
// Mock the dbml/core importer
 | 
			
		||||
vi.mock('@dbml/core', () => ({
 | 
			
		||||
    importer: {
 | 
			
		||||
        import: vi.fn((sql: string) => {
 | 
			
		||||
            // Return a simplified DBML for testing
 | 
			
		||||
            return sql;
 | 
			
		||||
        }),
 | 
			
		||||
    },
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
    // Helper to generate test IDs and timestamps
 | 
			
		||||
    let idCounter = 0;
 | 
			
		||||
@@ -106,7 +116,7 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Should contain composite primary key syntax
 | 
			
		||||
            expect(sql).toContain('PRIMARY KEY ("spell_id", "component_id")');
 | 
			
		||||
            expect(sql).toContain('PRIMARY KEY (spell_id, component_id)');
 | 
			
		||||
            // Should NOT contain individual PRIMARY KEY constraints
 | 
			
		||||
            expect(sql).not.toMatch(/spell_id\s+uuid\s+NOT NULL\s+PRIMARY KEY/);
 | 
			
		||||
            expect(sql).not.toMatch(
 | 
			
		||||
@@ -114,96 +124,6 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should not create duplicate index for composite primary key', () => {
 | 
			
		||||
            const tableId = testId();
 | 
			
		||||
            const field1Id = testId();
 | 
			
		||||
            const field2Id = testId();
 | 
			
		||||
            const field3Id = testId();
 | 
			
		||||
 | 
			
		||||
            const diagram: Diagram = createDiagram({
 | 
			
		||||
                id: testId(),
 | 
			
		||||
                name: 'Landlord System',
 | 
			
		||||
                databaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        id: tableId,
 | 
			
		||||
                        name: 'users_master_table',
 | 
			
		||||
                        schema: 'landlord',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                id: field1Id,
 | 
			
		||||
                                name: 'master_user_id',
 | 
			
		||||
                                type: { id: 'bigint', name: 'bigint' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                                unique: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                            createField({
 | 
			
		||||
                                id: field2Id,
 | 
			
		||||
                                name: 'tenant_id',
 | 
			
		||||
                                type: { id: 'bigint', name: 'bigint' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                                unique: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                            createField({
 | 
			
		||||
                                id: field3Id,
 | 
			
		||||
                                name: 'tenant_user_id',
 | 
			
		||||
                                type: { id: 'bigint', name: 'bigint' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                                unique: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                            createField({
 | 
			
		||||
                                id: testId(),
 | 
			
		||||
                                name: 'enabled',
 | 
			
		||||
                                type: { id: 'boolean', name: 'boolean' },
 | 
			
		||||
                                primaryKey: false,
 | 
			
		||||
                                nullable: true,
 | 
			
		||||
                                unique: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                        indexes: [
 | 
			
		||||
                            {
 | 
			
		||||
                                id: testId(),
 | 
			
		||||
                                name: 'idx_users_master_table_master_user_id_tenant_id_tenant_user_id',
 | 
			
		||||
                                unique: false,
 | 
			
		||||
                                fieldIds: [field1Id, field2Id, field3Id],
 | 
			
		||||
                                createdAt: testTime,
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                id: testId(),
 | 
			
		||||
                                name: 'index_1',
 | 
			
		||||
                                unique: true,
 | 
			
		||||
                                fieldIds: [field2Id, field3Id],
 | 
			
		||||
                                createdAt: testTime,
 | 
			
		||||
                            },
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
                relationships: [],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const sql = exportBaseSQL({
 | 
			
		||||
                diagram,
 | 
			
		||||
                targetDatabaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
                isDBMLFlow: true,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Should contain composite primary key constraint
 | 
			
		||||
            expect(sql).toContain(
 | 
			
		||||
                'PRIMARY KEY ("master_user_id", "tenant_id", "tenant_user_id")'
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Should NOT contain the duplicate index for the primary key fields
 | 
			
		||||
            expect(sql).not.toContain(
 | 
			
		||||
                'CREATE INDEX idx_users_master_table_master_user_id_tenant_id_tenant_user_id'
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Should still contain the unique index on subset of fields
 | 
			
		||||
            expect(sql).toContain('CREATE UNIQUE INDEX index_1');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle single primary keys inline', () => {
 | 
			
		||||
            const diagram: Diagram = createDiagram({
 | 
			
		||||
                id: testId(),
 | 
			
		||||
@@ -245,7 +165,7 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Should contain inline PRIMARY KEY
 | 
			
		||||
            expect(sql).toMatch(/"id"\s+uuid\s+NOT NULL\s+PRIMARY KEY/);
 | 
			
		||||
            expect(sql).toMatch(/id\s+uuid\s+NOT NULL\s+PRIMARY KEY/);
 | 
			
		||||
            // Should NOT contain separate PRIMARY KEY constraint
 | 
			
		||||
            expect(sql).not.toContain('PRIMARY KEY (id)');
 | 
			
		||||
        });
 | 
			
		||||
@@ -306,8 +226,8 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
            expect(sql).not.toContain('DEFAULT has default');
 | 
			
		||||
            expect(sql).not.toContain('DEFAULT DEFAULT has default');
 | 
			
		||||
            // The fields should still be in the table
 | 
			
		||||
            expect(sql).toContain('"is_active" boolean');
 | 
			
		||||
            expect(sql).toContain('"stock_count" integer NOT NULL'); // integer gets simplified to int
 | 
			
		||||
            expect(sql).toContain('is_active boolean');
 | 
			
		||||
            expect(sql).toContain('stock_count integer NOT NULL'); // integer gets simplified to int
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle valid default values correctly', () => {
 | 
			
		||||
@@ -429,8 +349,8 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Should convert NOW to NOW() and ('now') to now()
 | 
			
		||||
            expect(sql).toContain('"created_at" timestamp DEFAULT NOW');
 | 
			
		||||
            expect(sql).toContain('"updated_at" timestamp DEFAULT now()');
 | 
			
		||||
            expect(sql).toContain('created_at timestamp DEFAULT NOW');
 | 
			
		||||
            expect(sql).toContain('updated_at timestamp DEFAULT now()');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -485,9 +405,9 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Should handle char with explicit length
 | 
			
		||||
            expect(sql).toContain('"element_code" char(2)');
 | 
			
		||||
            expect(sql).toContain('element_code char(2)');
 | 
			
		||||
            // Should add default length for char without length
 | 
			
		||||
            expect(sql).toContain('"status" char(1)');
 | 
			
		||||
            expect(sql).toContain('status char(1)');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should not have spaces between char and parentheses', () => {
 | 
			
		||||
@@ -596,7 +516,7 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Should create a valid table without primary key
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE "experiment_logs"');
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE experiment_logs');
 | 
			
		||||
            expect(sql).not.toContain('PRIMARY KEY');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -711,11 +631,11 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Should create both tables
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE "guilds"');
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE "guild_members"');
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE guilds');
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE guild_members');
 | 
			
		||||
            // Should create foreign key
 | 
			
		||||
            expect(sql).toContain(
 | 
			
		||||
                'ALTER TABLE "guild_members" ADD CONSTRAINT fk_guild_members_guild FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id");'
 | 
			
		||||
                'ALTER TABLE guild_members ADD CONSTRAINT fk_guild_members_guild FOREIGN KEY (guild_id) REFERENCES guilds (id)'
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
@@ -789,9 +709,12 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
                isDBMLFlow: true,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Should create schemas
 | 
			
		||||
            expect(sql).toContain('CREATE SCHEMA IF NOT EXISTS transportation');
 | 
			
		||||
            expect(sql).toContain('CREATE SCHEMA IF NOT EXISTS magic');
 | 
			
		||||
            // Should use schema-qualified table names
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE "transportation"."portals"');
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE "magic"."spells"');
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE transportation.portals');
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE magic.spells');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -838,7 +761,7 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Should still create table structure
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE "empty_table"');
 | 
			
		||||
            expect(sql).toContain('CREATE TABLE empty_table');
 | 
			
		||||
            expect(sql).toContain('(\n\n)');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -939,9 +862,9 @@ describe('DBML Export - SQL Generation Tests', () => {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Should include precision and scale
 | 
			
		||||
            expect(sql).toContain('"amount" numeric(15, 2)');
 | 
			
		||||
            expect(sql).toContain('amount numeric(15, 2)');
 | 
			
		||||
            // Should include precision only when scale is not provided
 | 
			
		||||
            expect(sql).toContain('"interest_rate" numeric(5)');
 | 
			
		||||
            expect(sql).toContain('interest_rate numeric(5)');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -178,15 +178,7 @@ export function exportMSSQL({
 | 
			
		||||
                    })
 | 
			
		||||
                    .join(',\n')}${
 | 
			
		||||
                    table.fields.filter((f) => f.primaryKey).length > 0
 | 
			
		||||
                        ? `,\n    ${(() => {
 | 
			
		||||
                              // Find PK index to get the constraint name
 | 
			
		||||
                              const pkIndex = table.indexes.find(
 | 
			
		||||
                                  (idx) => idx.isPrimaryKey
 | 
			
		||||
                              );
 | 
			
		||||
                              return pkIndex?.name
 | 
			
		||||
                                  ? `CONSTRAINT [${pkIndex.name}] `
 | 
			
		||||
                                  : '';
 | 
			
		||||
                          })()}PRIMARY KEY (${table.fields
 | 
			
		||||
                        ? `,\n    PRIMARY KEY (${table.fields
 | 
			
		||||
                              .filter((f) => f.primaryKey)
 | 
			
		||||
                              .map((f) => `[${f.name}]`)
 | 
			
		||||
                              .join(', ')})`
 | 
			
		||||
@@ -313,15 +313,7 @@ export function exportMySQL({
 | 
			
		||||
                    .join(',\n')}${
 | 
			
		||||
                    // Add PRIMARY KEY as table constraint
 | 
			
		||||
                    primaryKeyFields.length > 0
 | 
			
		||||
                        ? `,\n    ${(() => {
 | 
			
		||||
                              // Find PK index to get the constraint name
 | 
			
		||||
                              const pkIndex = table.indexes.find(
 | 
			
		||||
                                  (idx) => idx.isPrimaryKey
 | 
			
		||||
                              );
 | 
			
		||||
                              return pkIndex?.name
 | 
			
		||||
                                  ? `CONSTRAINT \`${pkIndex.name}\` `
 | 
			
		||||
                                  : '';
 | 
			
		||||
                          })()}PRIMARY KEY (${primaryKeyFields
 | 
			
		||||
                        ? `,\n    PRIMARY KEY (${primaryKeyFields
 | 
			
		||||
                              .map((f) => `\`${f.name}\``)
 | 
			
		||||
                              .join(', ')})`
 | 
			
		||||
                        : ''
 | 
			
		||||
@@ -325,15 +325,7 @@ export function exportPostgreSQL({
 | 
			
		||||
                    })
 | 
			
		||||
                    .join(',\n')}${
 | 
			
		||||
                    primaryKeyFields.length > 0
 | 
			
		||||
                        ? `,\n    ${(() => {
 | 
			
		||||
                              // Find PK index to get the constraint name
 | 
			
		||||
                              const pkIndex = table.indexes.find(
 | 
			
		||||
                                  (idx) => idx.isPrimaryKey
 | 
			
		||||
                              );
 | 
			
		||||
                              return pkIndex?.name
 | 
			
		||||
                                  ? `CONSTRAINT "${pkIndex.name}" `
 | 
			
		||||
                                  : '';
 | 
			
		||||
                          })()}PRIMARY KEY (${primaryKeyFields
 | 
			
		||||
                        ? `,\n    PRIMARY KEY (${primaryKeyFields
 | 
			
		||||
                              .map((f) => `"${f.name}"`)
 | 
			
		||||
                              .join(', ')})`
 | 
			
		||||
                        : ''
 | 
			
		||||
@@ -20,61 +20,6 @@ const simplifyDataType = (typeName: string): string => {
 | 
			
		||||
    return typeMap[typeName.toLowerCase()] || typeName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Helper function to properly quote table/schema names with special characters
 | 
			
		||||
const getQuotedTableName = (
 | 
			
		||||
    table: DBTable,
 | 
			
		||||
    isDBMLFlow: boolean = false
 | 
			
		||||
): string => {
 | 
			
		||||
    // Check if a name is already quoted
 | 
			
		||||
    const isAlreadyQuoted = (name: string) => {
 | 
			
		||||
        return (
 | 
			
		||||
            (name.startsWith('"') && name.endsWith('"')) ||
 | 
			
		||||
            (name.startsWith('`') && name.endsWith('`')) ||
 | 
			
		||||
            (name.startsWith('[') && name.endsWith(']'))
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Only add quotes if needed and not already quoted
 | 
			
		||||
    const quoteIfNeeded = (name: string) => {
 | 
			
		||||
        if (isAlreadyQuoted(name)) {
 | 
			
		||||
            return name;
 | 
			
		||||
        }
 | 
			
		||||
        const needsQuoting = /[^a-zA-Z0-9_]/.test(name) || isDBMLFlow;
 | 
			
		||||
        return needsQuoting ? `"${name}"` : name;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (table.schema) {
 | 
			
		||||
        const quotedSchema = quoteIfNeeded(table.schema);
 | 
			
		||||
        const quotedTable = quoteIfNeeded(table.name);
 | 
			
		||||
        return `${quotedSchema}.${quotedTable}`;
 | 
			
		||||
    } else {
 | 
			
		||||
        return quoteIfNeeded(table.name);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getQuotedFieldName = (
 | 
			
		||||
    fieldName: string,
 | 
			
		||||
    isDBMLFlow: boolean = false
 | 
			
		||||
): string => {
 | 
			
		||||
    // Check if a name is already quoted
 | 
			
		||||
    const isAlreadyQuoted = (name: string) => {
 | 
			
		||||
        return (
 | 
			
		||||
            (name.startsWith('"') && name.endsWith('"')) ||
 | 
			
		||||
            (name.startsWith('`') && name.endsWith('`')) ||
 | 
			
		||||
            (name.startsWith('[') && name.endsWith(']'))
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (isAlreadyQuoted(fieldName)) {
 | 
			
		||||
        return fieldName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For DBML flow, always quote field names
 | 
			
		||||
    // Otherwise, only quote if it contains special characters
 | 
			
		||||
    const needsQuoting = /[^a-zA-Z0-9_]/.test(fieldName) || isDBMLFlow;
 | 
			
		||||
    return needsQuoting ? `"${fieldName}"` : fieldName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const exportBaseSQL = ({
 | 
			
		||||
    diagram,
 | 
			
		||||
    targetDatabaseType,
 | 
			
		||||
@@ -118,21 +63,18 @@ export const exportBaseSQL = ({
 | 
			
		||||
    let sqlScript = '';
 | 
			
		||||
 | 
			
		||||
    // First create the CREATE SCHEMA statements for all the found schemas based on tables
 | 
			
		||||
    // Skip schema creation for DBML flow as DBML doesn't support CREATE SCHEMA syntax
 | 
			
		||||
    if (!isDBMLFlow) {
 | 
			
		||||
        const schemas = new Set<string>();
 | 
			
		||||
        tables.forEach((table) => {
 | 
			
		||||
            if (table.schema) {
 | 
			
		||||
                schemas.add(table.schema);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    const schemas = new Set<string>();
 | 
			
		||||
    tables.forEach((table) => {
 | 
			
		||||
        if (table.schema) {
 | 
			
		||||
            schemas.add(table.schema);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
        // Add CREATE SCHEMA statements if any schemas exist
 | 
			
		||||
        schemas.forEach((schema) => {
 | 
			
		||||
            sqlScript += `CREATE SCHEMA IF NOT EXISTS "${schema}";\n`;
 | 
			
		||||
        });
 | 
			
		||||
        if (schemas.size > 0) sqlScript += '\n'; // Add newline only if schemas were added
 | 
			
		||||
    }
 | 
			
		||||
    // Add CREATE SCHEMA statements if any schemas exist
 | 
			
		||||
    schemas.forEach((schema) => {
 | 
			
		||||
        sqlScript += `CREATE SCHEMA IF NOT EXISTS ${schema};\n`;
 | 
			
		||||
    });
 | 
			
		||||
    if (schemas.size > 0) sqlScript += '\n'; // Add newline only if schemas were added
 | 
			
		||||
 | 
			
		||||
    // Add CREATE TYPE statements for ENUMs and COMPOSITE types from diagram.customTypes
 | 
			
		||||
    if (diagram.customTypes && diagram.customTypes.length > 0) {
 | 
			
		||||
@@ -224,7 +166,9 @@ export const exportBaseSQL = ({
 | 
			
		||||
 | 
			
		||||
    // Loop through each non-view table to generate the SQL statements
 | 
			
		||||
    nonViewTables.forEach((table) => {
 | 
			
		||||
        const tableName = getQuotedTableName(table, isDBMLFlow);
 | 
			
		||||
        const tableName = table.schema
 | 
			
		||||
            ? `${table.schema}.${table.name}`
 | 
			
		||||
            : table.name;
 | 
			
		||||
        sqlScript += `CREATE TABLE ${tableName} (\n`;
 | 
			
		||||
 | 
			
		||||
        // Check for composite primary keys
 | 
			
		||||
@@ -293,8 +237,7 @@ export const exportBaseSQL = ({
 | 
			
		||||
                typeName = 'char';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const quotedFieldName = getQuotedFieldName(field.name, isDBMLFlow);
 | 
			
		||||
            sqlScript += `  ${quotedFieldName} ${typeName}`;
 | 
			
		||||
            sqlScript += `  ${field.name} ${typeName}`;
 | 
			
		||||
 | 
			
		||||
            // Add size for character types
 | 
			
		||||
            if (
 | 
			
		||||
@@ -370,35 +313,21 @@ export const exportBaseSQL = ({
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Handle PRIMARY KEY constraint - only add inline if no PK index with custom name
 | 
			
		||||
            const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey);
 | 
			
		||||
            if (field.primaryKey && !hasCompositePrimaryKey && !pkIndex?.name) {
 | 
			
		||||
            // Handle PRIMARY KEY constraint - only add inline if not composite
 | 
			
		||||
            if (field.primaryKey && !hasCompositePrimaryKey) {
 | 
			
		||||
                sqlScript += ' PRIMARY KEY';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Add a comma after each field except the last one (or before PK constraint)
 | 
			
		||||
            const needsPKConstraint =
 | 
			
		||||
                hasCompositePrimaryKey ||
 | 
			
		||||
                (primaryKeyFields.length === 1 && pkIndex?.name);
 | 
			
		||||
            if (index < table.fields.length - 1 || needsPKConstraint) {
 | 
			
		||||
            // Add a comma after each field except the last one (or before composite primary key)
 | 
			
		||||
            if (index < table.fields.length - 1 || hasCompositePrimaryKey) {
 | 
			
		||||
                sqlScript += ',\n';
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Add primary key constraint if needed (for composite PKs or single PK with custom name)
 | 
			
		||||
        const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey);
 | 
			
		||||
        if (
 | 
			
		||||
            hasCompositePrimaryKey ||
 | 
			
		||||
            (primaryKeyFields.length === 1 && pkIndex?.name)
 | 
			
		||||
        ) {
 | 
			
		||||
            const pkFieldNames = primaryKeyFields
 | 
			
		||||
                .map((f) => getQuotedFieldName(f.name, isDBMLFlow))
 | 
			
		||||
                .join(', ');
 | 
			
		||||
            if (pkIndex?.name) {
 | 
			
		||||
                sqlScript += `\n  CONSTRAINT ${pkIndex.name} PRIMARY KEY (${pkFieldNames})`;
 | 
			
		||||
            } else {
 | 
			
		||||
                sqlScript += `\n  PRIMARY KEY (${pkFieldNames})`;
 | 
			
		||||
            }
 | 
			
		||||
        // Add composite primary key constraint if needed
 | 
			
		||||
        if (hasCompositePrimaryKey) {
 | 
			
		||||
            const pkFieldNames = primaryKeyFields.map((f) => f.name).join(', ');
 | 
			
		||||
            sqlScript += `\n  PRIMARY KEY (${pkFieldNames})`;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sqlScript += '\n);\n';
 | 
			
		||||
@@ -414,43 +343,18 @@ export const exportBaseSQL = ({
 | 
			
		||||
        table.fields.forEach((field) => {
 | 
			
		||||
            // Add column comment (only for databases that support COMMENT ON syntax)
 | 
			
		||||
            if (field.comments && supportsCommentOn) {
 | 
			
		||||
                const quotedFieldName = getQuotedFieldName(
 | 
			
		||||
                    field.name,
 | 
			
		||||
                    isDBMLFlow
 | 
			
		||||
                );
 | 
			
		||||
                sqlScript += `COMMENT ON COLUMN ${tableName}.${quotedFieldName} IS '${escapeSQLComment(field.comments)}';\n`;
 | 
			
		||||
                sqlScript += `COMMENT ON COLUMN ${tableName}.${field.name} IS '${escapeSQLComment(field.comments)}';\n`;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Generate SQL for indexes
 | 
			
		||||
        table.indexes.forEach((index) => {
 | 
			
		||||
            // Skip the primary key index (it's already handled as a constraint)
 | 
			
		||||
            if (index.isPrimaryKey) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get the fields for this index
 | 
			
		||||
            const indexFields = index.fieldIds
 | 
			
		||||
                .map((fieldId) => table.fields.find((f) => f.id === fieldId))
 | 
			
		||||
                .filter(
 | 
			
		||||
                    (field): field is NonNullable<typeof field> =>
 | 
			
		||||
                        field !== undefined
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
            // Skip if this index exactly matches the primary key fields
 | 
			
		||||
            // This prevents creating redundant indexes for composite primary keys
 | 
			
		||||
            if (
 | 
			
		||||
                primaryKeyFields.length > 0 &&
 | 
			
		||||
                primaryKeyFields.length === indexFields.length &&
 | 
			
		||||
                primaryKeyFields.every((pk) =>
 | 
			
		||||
                    indexFields.some((field) => field.id === pk.id)
 | 
			
		||||
            const fieldNames = index.fieldIds
 | 
			
		||||
                .map(
 | 
			
		||||
                    (fieldId) =>
 | 
			
		||||
                        table.fields.find((field) => field.id === fieldId)?.name
 | 
			
		||||
                )
 | 
			
		||||
            ) {
 | 
			
		||||
                return; // Skip this index as it's redundant with the primary key
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const fieldNames = indexFields
 | 
			
		||||
                .map((field) => getQuotedFieldName(field.name, isDBMLFlow))
 | 
			
		||||
                .filter(Boolean)
 | 
			
		||||
                .join(', ');
 | 
			
		||||
 | 
			
		||||
            if (fieldNames) {
 | 
			
		||||
@@ -528,18 +432,13 @@ export const exportBaseSQL = ({
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const fkTableName = getQuotedTableName(fkTable, isDBMLFlow);
 | 
			
		||||
            const refTableName = getQuotedTableName(refTable, isDBMLFlow);
 | 
			
		||||
            const quotedFkFieldName = getQuotedFieldName(
 | 
			
		||||
                fkField.name,
 | 
			
		||||
                isDBMLFlow
 | 
			
		||||
            );
 | 
			
		||||
            const quotedRefFieldName = getQuotedFieldName(
 | 
			
		||||
                refField.name,
 | 
			
		||||
                isDBMLFlow
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            sqlScript += `ALTER TABLE ${fkTableName} ADD CONSTRAINT ${relationship.name} FOREIGN KEY (${quotedFkFieldName}) REFERENCES ${refTableName} (${quotedRefFieldName});\n`;
 | 
			
		||||
            const fkTableName = fkTable.schema
 | 
			
		||||
                ? `${fkTable.schema}.${fkTable.name}`
 | 
			
		||||
                : fkTable.name;
 | 
			
		||||
            const refTableName = refTable.schema
 | 
			
		||||
                ? `${refTable.schema}.${refTable.name}`
 | 
			
		||||
                : refTable.name;
 | 
			
		||||
            sqlScript += `ALTER TABLE ${fkTableName} ADD CONSTRAINT ${relationship.name} FOREIGN KEY (${fkField.name}) REFERENCES ${refTableName} (${refField.name});\n`;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
import type { DBCustomType, DBCustomTypeKind } from '@/lib/domain';
 | 
			
		||||
import { schemaNameToDomainSchemaName } from '@/lib/domain';
 | 
			
		||||
import type { DBCustomTypeInfo } from '../metadata-types/custom-type-info';
 | 
			
		||||
import { generateId } from '@/lib/utils';
 | 
			
		||||
 | 
			
		||||
export const createCustomTypesFromMetadata = ({
 | 
			
		||||
    customTypes,
 | 
			
		||||
}: {
 | 
			
		||||
    customTypes: DBCustomTypeInfo[];
 | 
			
		||||
}): DBCustomType[] => {
 | 
			
		||||
    return customTypes.map((customType) => {
 | 
			
		||||
        return {
 | 
			
		||||
            id: generateId(),
 | 
			
		||||
            schema: schemaNameToDomainSchemaName(customType.schema),
 | 
			
		||||
            name: customType.type,
 | 
			
		||||
            kind: customType.kind as DBCustomTypeKind,
 | 
			
		||||
            values: customType.values,
 | 
			
		||||
            fields: customType.fields,
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
@@ -1,351 +0,0 @@
 | 
			
		||||
import { generateId } from '@/lib/utils';
 | 
			
		||||
import type { AST } from 'node-sql-parser';
 | 
			
		||||
import type { DBDependency, DBTable } from '@/lib/domain';
 | 
			
		||||
import { DatabaseType, schemaNameToDomainSchemaName } from '@/lib/domain';
 | 
			
		||||
import type { ViewInfo } from '../metadata-types/view-info';
 | 
			
		||||
import { decodeViewDefinition } from './tables';
 | 
			
		||||
 | 
			
		||||
const astDatabaseTypes: Record<DatabaseType, string> = {
 | 
			
		||||
    [DatabaseType.POSTGRESQL]: 'postgresql',
 | 
			
		||||
    [DatabaseType.MYSQL]: 'postgresql',
 | 
			
		||||
    [DatabaseType.MARIADB]: 'postgresql',
 | 
			
		||||
    [DatabaseType.GENERIC]: 'postgresql',
 | 
			
		||||
    [DatabaseType.SQLITE]: 'postgresql',
 | 
			
		||||
    [DatabaseType.SQL_SERVER]: 'postgresql',
 | 
			
		||||
    [DatabaseType.CLICKHOUSE]: 'postgresql',
 | 
			
		||||
    [DatabaseType.COCKROACHDB]: 'postgresql',
 | 
			
		||||
    [DatabaseType.ORACLE]: 'postgresql',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createDependenciesFromMetadata = async ({
 | 
			
		||||
    views,
 | 
			
		||||
    tables,
 | 
			
		||||
    databaseType,
 | 
			
		||||
}: {
 | 
			
		||||
    views: ViewInfo[];
 | 
			
		||||
    tables: DBTable[];
 | 
			
		||||
    databaseType: DatabaseType;
 | 
			
		||||
}): Promise<DBDependency[]> => {
 | 
			
		||||
    if (!views || views.length === 0) {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { Parser } = await import('node-sql-parser');
 | 
			
		||||
    const parser = new Parser();
 | 
			
		||||
 | 
			
		||||
    const dependencies = views
 | 
			
		||||
        .flatMap((view) => {
 | 
			
		||||
            const viewSchema = schemaNameToDomainSchemaName(view.schema);
 | 
			
		||||
            const viewTable = tables.find(
 | 
			
		||||
                (table) =>
 | 
			
		||||
                    table.name === view.view_name && viewSchema === table.schema
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (!viewTable) {
 | 
			
		||||
                console.warn(
 | 
			
		||||
                    `Source table for view ${view.view_name} not found (schema: ${viewSchema})`
 | 
			
		||||
                );
 | 
			
		||||
                return []; // Skip this view and proceed to the next
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (view.view_definition) {
 | 
			
		||||
                try {
 | 
			
		||||
                    const decodedViewDefinition = decodeViewDefinition(
 | 
			
		||||
                        databaseType,
 | 
			
		||||
                        view.view_definition
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    let modifiedViewDefinition = '';
 | 
			
		||||
                    if (
 | 
			
		||||
                        databaseType === DatabaseType.MYSQL ||
 | 
			
		||||
                        databaseType === DatabaseType.MARIADB
 | 
			
		||||
                    ) {
 | 
			
		||||
                        modifiedViewDefinition = preprocessViewDefinitionMySQL(
 | 
			
		||||
                            decodedViewDefinition
 | 
			
		||||
                        );
 | 
			
		||||
                    } else if (databaseType === DatabaseType.SQL_SERVER) {
 | 
			
		||||
                        modifiedViewDefinition =
 | 
			
		||||
                            preprocessViewDefinitionSQLServer(
 | 
			
		||||
                                decodedViewDefinition
 | 
			
		||||
                            );
 | 
			
		||||
                    } else {
 | 
			
		||||
                        modifiedViewDefinition = preprocessViewDefinition(
 | 
			
		||||
                            decodedViewDefinition
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Parse using the appropriate dialect
 | 
			
		||||
                    const ast = parser.astify(modifiedViewDefinition, {
 | 
			
		||||
                        database: astDatabaseTypes[databaseType],
 | 
			
		||||
                        type: 'select', // Parsing a SELECT statement
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    let relatedTables = extractTablesFromAST(ast);
 | 
			
		||||
 | 
			
		||||
                    // Filter out duplicate tables without schema
 | 
			
		||||
                    relatedTables = filterDuplicateTables(relatedTables);
 | 
			
		||||
 | 
			
		||||
                    return relatedTables.map((relTable) => {
 | 
			
		||||
                        const relSchema = relTable.schema || view.schema; // Use view's schema if relSchema is undefined
 | 
			
		||||
                        const relTableName = relTable.tableName;
 | 
			
		||||
 | 
			
		||||
                        const table = tables.find(
 | 
			
		||||
                            (table) =>
 | 
			
		||||
                                table.name === relTableName &&
 | 
			
		||||
                                (table.schema || '') === relSchema
 | 
			
		||||
                        );
 | 
			
		||||
 | 
			
		||||
                        if (table) {
 | 
			
		||||
                            const dependency: DBDependency = {
 | 
			
		||||
                                id: generateId(),
 | 
			
		||||
                                schema: view.schema,
 | 
			
		||||
                                tableId: table.id, // related table
 | 
			
		||||
                                dependentSchema: table.schema,
 | 
			
		||||
                                dependentTableId: viewTable.id, // dependent view
 | 
			
		||||
                                createdAt: Date.now(),
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
                            return dependency;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            console.warn(
 | 
			
		||||
                                `Dependent table ${relSchema}.${relTableName} not found for view ${view.schema}.${view.view_name}`
 | 
			
		||||
                            );
 | 
			
		||||
                            return null;
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                    console.error(
 | 
			
		||||
                        `Error parsing view ${view.schema}.${view.view_name}:`,
 | 
			
		||||
                        error
 | 
			
		||||
                    );
 | 
			
		||||
                    return [];
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                console.warn(
 | 
			
		||||
                    `View definition missing for ${view.schema}.${view.view_name}`
 | 
			
		||||
                );
 | 
			
		||||
                return [];
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .filter((dependency) => dependency !== null);
 | 
			
		||||
 | 
			
		||||
    return dependencies;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Add this new function to filter out duplicate tables
 | 
			
		||||
function filterDuplicateTables(
 | 
			
		||||
    tables: { schema?: string; tableName: string }[]
 | 
			
		||||
): { schema?: string; tableName: string }[] {
 | 
			
		||||
    const tableMap = new Map<string, { schema?: string; tableName: string }>();
 | 
			
		||||
 | 
			
		||||
    for (const table of tables) {
 | 
			
		||||
        const key = table.tableName;
 | 
			
		||||
        const existingTable = tableMap.get(key);
 | 
			
		||||
 | 
			
		||||
        if (!existingTable || (table.schema && !existingTable.schema)) {
 | 
			
		||||
            tableMap.set(key, table);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Array.from(tableMap.values());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Preprocess the view_definition to remove schema from CREATE VIEW
 | 
			
		||||
function preprocessViewDefinition(viewDefinition: string): string {
 | 
			
		||||
    if (!viewDefinition) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove leading and trailing whitespace
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\s+/g, ' ').trim();
 | 
			
		||||
 | 
			
		||||
    // Replace escaped double quotes with regular ones
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\\"/g, '"');
 | 
			
		||||
 | 
			
		||||
    // Replace 'CREATE MATERIALIZED VIEW' with 'CREATE VIEW'
 | 
			
		||||
    viewDefinition = viewDefinition.replace(
 | 
			
		||||
        /CREATE\s+MATERIALIZED\s+VIEW/i,
 | 
			
		||||
        'CREATE VIEW'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Regular expression to match 'CREATE VIEW [schema.]view_name [ (column definitions) ] AS'
 | 
			
		||||
    // This regex captures the view name and skips any content between the view name and 'AS'
 | 
			
		||||
    const regex =
 | 
			
		||||
        /CREATE\s+VIEW\s+(?:(?:`[^`]+`|"[^"]+"|\w+)\.)?(?:`([^`]+)`|"([^"]+)"|(\w+))[\s\S]*?\bAS\b\s+/i;
 | 
			
		||||
 | 
			
		||||
    const match = viewDefinition.match(regex);
 | 
			
		||||
    let modifiedDefinition: string;
 | 
			
		||||
 | 
			
		||||
    if (match) {
 | 
			
		||||
        const viewName = match[1] || match[2] || match[3];
 | 
			
		||||
        // Extract the SQL after the 'AS' keyword
 | 
			
		||||
        const restOfDefinition = viewDefinition.substring(
 | 
			
		||||
            match.index! + match[0].length
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Replace double-quoted identifiers with unquoted ones
 | 
			
		||||
        let modifiedSQL = restOfDefinition.replace(/"(\w+)"/g, '$1');
 | 
			
		||||
 | 
			
		||||
        // Replace '::' type casts with 'CAST' expressions
 | 
			
		||||
        modifiedSQL = modifiedSQL.replace(
 | 
			
		||||
            /\(([^()]+)\)::(\w+)/g,
 | 
			
		||||
            'CAST($1 AS $2)'
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Remove ClickHouse-specific syntax that may still be present
 | 
			
		||||
        // For example, remove SETTINGS clauses inside the SELECT statement
 | 
			
		||||
        modifiedSQL = modifiedSQL.replace(/\bSETTINGS\b[\s\S]*$/i, '');
 | 
			
		||||
 | 
			
		||||
        modifiedDefinition = `CREATE VIEW ${viewName} AS ${modifiedSQL}`;
 | 
			
		||||
    } else {
 | 
			
		||||
        console.warn('Could not preprocess view definition:', viewDefinition);
 | 
			
		||||
        modifiedDefinition = viewDefinition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return modifiedDefinition;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Preprocess the view_definition for SQL Server
 | 
			
		||||
function preprocessViewDefinitionSQLServer(viewDefinition: string): string {
 | 
			
		||||
    if (!viewDefinition) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove BOM if present
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/^\uFEFF/, '');
 | 
			
		||||
 | 
			
		||||
    // Normalize whitespace
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\s+/g, ' ').trim();
 | 
			
		||||
 | 
			
		||||
    // Remove square brackets and replace with double quotes
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\[([^\]]+)\]/g, '"$1"');
 | 
			
		||||
 | 
			
		||||
    // Remove database names from fully qualified identifiers
 | 
			
		||||
    viewDefinition = viewDefinition.replace(
 | 
			
		||||
        /"([a-zA-Z0-9_]+)"\."([a-zA-Z0-9_]+)"\."([a-zA-Z0-9_]+)"/g,
 | 
			
		||||
        '"$2"."$3"'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Replace SQL Server functions with PostgreSQL equivalents
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\bGETDATE\(\)/gi, 'NOW()');
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\bISNULL\(/gi, 'COALESCE(');
 | 
			
		||||
 | 
			
		||||
    // Replace 'TOP N' with 'LIMIT N' at the end of the query
 | 
			
		||||
    const topMatch = viewDefinition.match(/SELECT\s+TOP\s+(\d+)/i);
 | 
			
		||||
    if (topMatch) {
 | 
			
		||||
        const topN = topMatch[1];
 | 
			
		||||
        viewDefinition = viewDefinition.replace(
 | 
			
		||||
            /SELECT\s+TOP\s+\d+/i,
 | 
			
		||||
            'SELECT'
 | 
			
		||||
        );
 | 
			
		||||
        viewDefinition = viewDefinition.replace(/;+\s*$/, ''); // Remove semicolons at the end
 | 
			
		||||
        viewDefinition += ` LIMIT ${topN}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\n/g, ''); // Remove newlines
 | 
			
		||||
 | 
			
		||||
    // Adjust CREATE VIEW syntax
 | 
			
		||||
    const regex =
 | 
			
		||||
        /CREATE\s+VIEW\s+(?:"?([^".\s]+)"?\.)?"?([^".\s]+)"?\s+AS\s+/i;
 | 
			
		||||
    const match = viewDefinition.match(regex);
 | 
			
		||||
    let modifiedDefinition: string;
 | 
			
		||||
 | 
			
		||||
    if (match) {
 | 
			
		||||
        const viewName = match[2];
 | 
			
		||||
        const modifiedSQL = viewDefinition.substring(
 | 
			
		||||
            match.index! + match[0].length
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Remove semicolons at the end
 | 
			
		||||
        const finalSQL = modifiedSQL.replace(/;+\s*$/, '');
 | 
			
		||||
 | 
			
		||||
        modifiedDefinition = `CREATE VIEW "${viewName}" AS ${finalSQL}`;
 | 
			
		||||
    } else {
 | 
			
		||||
        console.warn('Could not preprocess view definition:', viewDefinition);
 | 
			
		||||
        modifiedDefinition = viewDefinition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return modifiedDefinition;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Preprocess the view_definition to remove schema from CREATE VIEW
 | 
			
		||||
function preprocessViewDefinitionMySQL(viewDefinition: string): string {
 | 
			
		||||
    if (!viewDefinition) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove any trailing semicolons
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/;\s*$/, '');
 | 
			
		||||
 | 
			
		||||
    // Remove backticks from identifiers
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/`/g, '');
 | 
			
		||||
 | 
			
		||||
    // Remove unnecessary parentheses around joins and ON clauses
 | 
			
		||||
    viewDefinition = removeRedundantParentheses(viewDefinition);
 | 
			
		||||
 | 
			
		||||
    return viewDefinition;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function removeRedundantParentheses(sql: string): string {
 | 
			
		||||
    // Regular expressions to match unnecessary parentheses
 | 
			
		||||
    const patterns = [
 | 
			
		||||
        /\(\s*(JOIN\s+[^()]+?)\s*\)/gi,
 | 
			
		||||
        /\(\s*(ON\s+[^()]+?)\s*\)/gi,
 | 
			
		||||
        // Additional patterns if necessary
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    let prevSql;
 | 
			
		||||
    do {
 | 
			
		||||
        prevSql = sql;
 | 
			
		||||
        patterns.forEach((pattern) => {
 | 
			
		||||
            sql = sql.replace(pattern, '$1');
 | 
			
		||||
        });
 | 
			
		||||
    } while (sql !== prevSql);
 | 
			
		||||
 | 
			
		||||
    return sql;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function extractTablesFromAST(
 | 
			
		||||
    ast: AST | AST[]
 | 
			
		||||
): { schema?: string; tableName: string }[] {
 | 
			
		||||
    const tablesMap = new Map<string, { schema: string; tableName: string }>();
 | 
			
		||||
    const visitedNodes = new Set();
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
    function traverse(node: any) {
 | 
			
		||||
        if (!node || visitedNodes.has(node)) return;
 | 
			
		||||
        visitedNodes.add(node);
 | 
			
		||||
 | 
			
		||||
        if (Array.isArray(node)) {
 | 
			
		||||
            node.forEach(traverse);
 | 
			
		||||
        } else if (typeof node === 'object') {
 | 
			
		||||
            // Check if node represents a table
 | 
			
		||||
            if (
 | 
			
		||||
                Object.hasOwnProperty.call(node, 'table') &&
 | 
			
		||||
                typeof node.table === 'string'
 | 
			
		||||
            ) {
 | 
			
		||||
                let schema = node.db || node.schema;
 | 
			
		||||
                const tableName = node.table;
 | 
			
		||||
                if (tableName) {
 | 
			
		||||
                    // Assign default schema if undefined
 | 
			
		||||
                    schema = schemaNameToDomainSchemaName(schema) || '';
 | 
			
		||||
                    const key = `${schema}.${tableName}`;
 | 
			
		||||
                    if (!tablesMap.has(key)) {
 | 
			
		||||
                        tablesMap.set(key, { schema, tableName });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Recursively traverse all properties
 | 
			
		||||
            for (const key in node) {
 | 
			
		||||
                if (Object.hasOwnProperty.call(node, key)) {
 | 
			
		||||
                    traverse(node[key]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    traverse(ast);
 | 
			
		||||
 | 
			
		||||
    return Array.from(tablesMap.values());
 | 
			
		||||
}
 | 
			
		||||
@@ -1,64 +0,0 @@
 | 
			
		||||
import type { DBField } from '@/lib/domain';
 | 
			
		||||
import type { ColumnInfo } from '../metadata-types/column-info';
 | 
			
		||||
import type { AggregatedIndexInfo } from '../metadata-types/index-info';
 | 
			
		||||
import type { PrimaryKeyInfo } from '../metadata-types/primary-key-info';
 | 
			
		||||
import type { TableInfo } from '../metadata-types/table-info';
 | 
			
		||||
import { generateId } from '@/lib/utils';
 | 
			
		||||
 | 
			
		||||
export const createFieldsFromMetadata = ({
 | 
			
		||||
    tableColumns,
 | 
			
		||||
    tablePrimaryKeys,
 | 
			
		||||
    aggregatedIndexes,
 | 
			
		||||
}: {
 | 
			
		||||
    tableColumns: ColumnInfo[];
 | 
			
		||||
    tableSchema?: string;
 | 
			
		||||
    tableInfo: TableInfo;
 | 
			
		||||
    tablePrimaryKeys: PrimaryKeyInfo[];
 | 
			
		||||
    aggregatedIndexes: AggregatedIndexInfo[];
 | 
			
		||||
}) => {
 | 
			
		||||
    const uniqueColumns = tableColumns.reduce((acc, col) => {
 | 
			
		||||
        if (!acc.has(col.name)) {
 | 
			
		||||
            acc.set(col.name, col);
 | 
			
		||||
        }
 | 
			
		||||
        return acc;
 | 
			
		||||
    }, new Map<string, ColumnInfo>());
 | 
			
		||||
 | 
			
		||||
    const sortedColumns = Array.from(uniqueColumns.values()).sort(
 | 
			
		||||
        (a, b) => a.ordinal_position - b.ordinal_position
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const tablePrimaryKeysColumns = tablePrimaryKeys.map((pk) =>
 | 
			
		||||
        pk.column.trim()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return sortedColumns.map(
 | 
			
		||||
        (col: ColumnInfo): DBField => ({
 | 
			
		||||
            id: generateId(),
 | 
			
		||||
            name: col.name,
 | 
			
		||||
            type: {
 | 
			
		||||
                id: col.type.split(' ').join('_').toLowerCase(),
 | 
			
		||||
                name: col.type.toLowerCase(),
 | 
			
		||||
            },
 | 
			
		||||
            primaryKey: tablePrimaryKeysColumns.includes(col.name),
 | 
			
		||||
            unique: Object.values(aggregatedIndexes).some(
 | 
			
		||||
                (idx) =>
 | 
			
		||||
                    idx.unique &&
 | 
			
		||||
                    idx.columns.length === 1 &&
 | 
			
		||||
                    idx.columns[0].name === col.name
 | 
			
		||||
            ),
 | 
			
		||||
            nullable: Boolean(col.nullable),
 | 
			
		||||
            ...(col.character_maximum_length &&
 | 
			
		||||
            col.character_maximum_length !== 'null'
 | 
			
		||||
                ? { characterMaximumLength: col.character_maximum_length }
 | 
			
		||||
                : {}),
 | 
			
		||||
            ...(col.precision?.precision
 | 
			
		||||
                ? { precision: col.precision.precision }
 | 
			
		||||
                : {}),
 | 
			
		||||
            ...(col.precision?.scale ? { scale: col.precision.scale } : {}),
 | 
			
		||||
            ...(col.default ? { default: col.default } : {}),
 | 
			
		||||
            ...(col.collation ? { collation: col.collation } : {}),
 | 
			
		||||
            createdAt: Date.now(),
 | 
			
		||||
            comments: col.comment ? col.comment : undefined,
 | 
			
		||||
        })
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@@ -1,82 +0,0 @@
 | 
			
		||||
import type { DatabaseEdition, Diagram } from '@/lib/domain';
 | 
			
		||||
import { adjustTablePositions, DatabaseType } from '@/lib/domain';
 | 
			
		||||
import { generateDiagramId } from '@/lib/utils';
 | 
			
		||||
import type { DatabaseMetadata } from '../metadata-types/database-metadata';
 | 
			
		||||
import { createCustomTypesFromMetadata } from './custom-types';
 | 
			
		||||
import { createRelationshipsFromMetadata } from './relationships';
 | 
			
		||||
import { createTablesFromMetadata } from './tables';
 | 
			
		||||
import { createDependenciesFromMetadata } from './dependencies';
 | 
			
		||||
 | 
			
		||||
export const loadFromDatabaseMetadata = async ({
 | 
			
		||||
    databaseType,
 | 
			
		||||
    databaseMetadata,
 | 
			
		||||
    diagramNumber,
 | 
			
		||||
    databaseEdition,
 | 
			
		||||
}: {
 | 
			
		||||
    databaseType: DatabaseType;
 | 
			
		||||
    databaseMetadata: DatabaseMetadata;
 | 
			
		||||
    diagramNumber?: number;
 | 
			
		||||
    databaseEdition?: DatabaseEdition;
 | 
			
		||||
}): Promise<Diagram> => {
 | 
			
		||||
    const {
 | 
			
		||||
        fk_info: foreignKeys,
 | 
			
		||||
        views: views,
 | 
			
		||||
        custom_types: customTypes,
 | 
			
		||||
    } = databaseMetadata;
 | 
			
		||||
 | 
			
		||||
    const tables = createTablesFromMetadata({
 | 
			
		||||
        databaseMetadata,
 | 
			
		||||
        databaseType,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const relationships = createRelationshipsFromMetadata({
 | 
			
		||||
        foreignKeys,
 | 
			
		||||
        tables,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const dependencies = await createDependenciesFromMetadata({
 | 
			
		||||
        views,
 | 
			
		||||
        tables,
 | 
			
		||||
        databaseType,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const dbCustomTypes = customTypes
 | 
			
		||||
        ? createCustomTypesFromMetadata({
 | 
			
		||||
              customTypes,
 | 
			
		||||
          })
 | 
			
		||||
        : [];
 | 
			
		||||
 | 
			
		||||
    const adjustedTables = adjustTablePositions({
 | 
			
		||||
        tables,
 | 
			
		||||
        relationships,
 | 
			
		||||
        mode: 'perSchema',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const sortedTables = adjustedTables.sort((a, b) => {
 | 
			
		||||
        if (a.isView === b.isView) {
 | 
			
		||||
            // Both are either tables or views, so sort alphabetically by name
 | 
			
		||||
            return a.name.localeCompare(b.name);
 | 
			
		||||
        }
 | 
			
		||||
        // If one is a view and the other is not, put tables first
 | 
			
		||||
        return a.isView ? 1 : -1;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const diagram: Diagram = {
 | 
			
		||||
        id: generateDiagramId(),
 | 
			
		||||
        name: databaseMetadata.database_name
 | 
			
		||||
            ? `${databaseMetadata.database_name}-db`
 | 
			
		||||
            : diagramNumber
 | 
			
		||||
              ? `Diagram ${diagramNumber}`
 | 
			
		||||
              : 'New Diagram',
 | 
			
		||||
        databaseType: databaseType ?? DatabaseType.GENERIC,
 | 
			
		||||
        databaseEdition,
 | 
			
		||||
        tables: sortedTables,
 | 
			
		||||
        relationships,
 | 
			
		||||
        dependencies,
 | 
			
		||||
        customTypes: dbCustomTypes,
 | 
			
		||||
        createdAt: new Date(),
 | 
			
		||||
        updatedAt: new Date(),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return diagram;
 | 
			
		||||
};
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
import type { DBField, DBIndex, IndexType } from '@/lib/domain';
 | 
			
		||||
import type { AggregatedIndexInfo } from '../metadata-types/index-info';
 | 
			
		||||
import { generateId } from '@/lib/utils';
 | 
			
		||||
 | 
			
		||||
export const createIndexesFromMetadata = ({
 | 
			
		||||
    aggregatedIndexes,
 | 
			
		||||
    fields,
 | 
			
		||||
}: {
 | 
			
		||||
    aggregatedIndexes: AggregatedIndexInfo[];
 | 
			
		||||
    fields: DBField[];
 | 
			
		||||
}): DBIndex[] =>
 | 
			
		||||
    aggregatedIndexes.map(
 | 
			
		||||
        (idx): DBIndex => ({
 | 
			
		||||
            id: generateId(),
 | 
			
		||||
            name: idx.name,
 | 
			
		||||
            unique: Boolean(idx.unique),
 | 
			
		||||
            fieldIds: idx.columns
 | 
			
		||||
                .sort((a, b) => a.position - b.position)
 | 
			
		||||
                .map((c) => fields.find((f) => f.name === c.name)?.id)
 | 
			
		||||
                .filter((id): id is string => id !== undefined),
 | 
			
		||||
            createdAt: Date.now(),
 | 
			
		||||
            type: idx.index_type?.toLowerCase() as IndexType,
 | 
			
		||||
        })
 | 
			
		||||
    );
 | 
			
		||||
@@ -1,85 +0,0 @@
 | 
			
		||||
import type {
 | 
			
		||||
    Cardinality,
 | 
			
		||||
    DBField,
 | 
			
		||||
    DBRelationship,
 | 
			
		||||
    DBTable,
 | 
			
		||||
} from '@/lib/domain';
 | 
			
		||||
import { schemaNameToDomainSchemaName } from '@/lib/domain';
 | 
			
		||||
import type { ForeignKeyInfo } from '../metadata-types/foreign-key-info';
 | 
			
		||||
import { generateId } from '@/lib/utils';
 | 
			
		||||
 | 
			
		||||
const determineCardinality = (
 | 
			
		||||
    field: DBField,
 | 
			
		||||
    isTablePKComplex: boolean
 | 
			
		||||
): Cardinality => {
 | 
			
		||||
    return field.unique || (field.primaryKey && !isTablePKComplex)
 | 
			
		||||
        ? 'one'
 | 
			
		||||
        : 'many';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createRelationshipsFromMetadata = ({
 | 
			
		||||
    foreignKeys,
 | 
			
		||||
    tables,
 | 
			
		||||
}: {
 | 
			
		||||
    foreignKeys: ForeignKeyInfo[];
 | 
			
		||||
    tables: DBTable[];
 | 
			
		||||
}): DBRelationship[] => {
 | 
			
		||||
    return foreignKeys
 | 
			
		||||
        .map((fk: ForeignKeyInfo): DBRelationship | null => {
 | 
			
		||||
            const schema = schemaNameToDomainSchemaName(fk.schema);
 | 
			
		||||
            const sourceTable = tables.find(
 | 
			
		||||
                (table) => table.name === fk.table && table.schema === schema
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            const targetSchema = schemaNameToDomainSchemaName(
 | 
			
		||||
                fk.reference_schema
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            const targetTable = tables.find(
 | 
			
		||||
                (table) =>
 | 
			
		||||
                    table.name === fk.reference_table &&
 | 
			
		||||
                    table.schema === targetSchema
 | 
			
		||||
            );
 | 
			
		||||
            const sourceField = sourceTable?.fields.find(
 | 
			
		||||
                (field) => field.name === fk.column
 | 
			
		||||
            );
 | 
			
		||||
            const targetField = targetTable?.fields.find(
 | 
			
		||||
                (field) => field.name === fk.reference_column
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            const isSourceTablePKComplex =
 | 
			
		||||
                (sourceTable?.fields.filter((field) => field.primaryKey) ?? [])
 | 
			
		||||
                    .length > 1;
 | 
			
		||||
            const isTargetTablePKComplex =
 | 
			
		||||
                (targetTable?.fields.filter((field) => field.primaryKey) ?? [])
 | 
			
		||||
                    .length > 1;
 | 
			
		||||
 | 
			
		||||
            if (sourceTable && targetTable && sourceField && targetField) {
 | 
			
		||||
                const sourceCardinality = determineCardinality(
 | 
			
		||||
                    sourceField,
 | 
			
		||||
                    isSourceTablePKComplex
 | 
			
		||||
                );
 | 
			
		||||
                const targetCardinality = determineCardinality(
 | 
			
		||||
                    targetField,
 | 
			
		||||
                    isTargetTablePKComplex
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                return {
 | 
			
		||||
                    id: generateId(),
 | 
			
		||||
                    name: fk.foreign_key_name,
 | 
			
		||||
                    sourceSchema: schema,
 | 
			
		||||
                    targetSchema: targetSchema,
 | 
			
		||||
                    sourceTableId: sourceTable.id,
 | 
			
		||||
                    targetTableId: targetTable.id,
 | 
			
		||||
                    sourceFieldId: sourceField.id,
 | 
			
		||||
                    targetFieldId: targetField.id,
 | 
			
		||||
                    sourceCardinality,
 | 
			
		||||
                    targetCardinality,
 | 
			
		||||
                    createdAt: Date.now(),
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        })
 | 
			
		||||
        .filter((rel) => rel !== null) as DBRelationship[];
 | 
			
		||||
};
 | 
			
		||||
@@ -1,228 +0,0 @@
 | 
			
		||||
import type { DBIndex, DBTable } from '@/lib/domain';
 | 
			
		||||
import {
 | 
			
		||||
    DatabaseType,
 | 
			
		||||
    generateTableKey,
 | 
			
		||||
    schemaNameToDomainSchemaName,
 | 
			
		||||
} from '@/lib/domain';
 | 
			
		||||
import type { DatabaseMetadata } from '../metadata-types/database-metadata';
 | 
			
		||||
import type { TableInfo } from '../metadata-types/table-info';
 | 
			
		||||
import { createAggregatedIndexes } from '../metadata-types/index-info';
 | 
			
		||||
import {
 | 
			
		||||
    decodeBase64ToUtf16LE,
 | 
			
		||||
    decodeBase64ToUtf8,
 | 
			
		||||
    generateId,
 | 
			
		||||
} from '@/lib/utils';
 | 
			
		||||
import {
 | 
			
		||||
    defaultTableColor,
 | 
			
		||||
    materializedViewColor,
 | 
			
		||||
    viewColor,
 | 
			
		||||
} from '@/lib/colors';
 | 
			
		||||
import { createFieldsFromMetadata } from './fields';
 | 
			
		||||
import { createIndexesFromMetadata } from './indexes';
 | 
			
		||||
 | 
			
		||||
export const decodeViewDefinition = (
 | 
			
		||||
    databaseType: DatabaseType,
 | 
			
		||||
    viewDefinition?: string
 | 
			
		||||
): string => {
 | 
			
		||||
    if (!viewDefinition) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let decodedViewDefinition: string;
 | 
			
		||||
    if (databaseType === DatabaseType.SQL_SERVER) {
 | 
			
		||||
        decodedViewDefinition = decodeBase64ToUtf16LE(viewDefinition);
 | 
			
		||||
    } else {
 | 
			
		||||
        decodedViewDefinition = decodeBase64ToUtf8(viewDefinition);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return decodedViewDefinition;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createTablesFromMetadata = ({
 | 
			
		||||
    databaseMetadata,
 | 
			
		||||
    databaseType,
 | 
			
		||||
}: {
 | 
			
		||||
    databaseMetadata: DatabaseMetadata;
 | 
			
		||||
    databaseType: DatabaseType;
 | 
			
		||||
}): DBTable[] => {
 | 
			
		||||
    const {
 | 
			
		||||
        tables: tableInfos,
 | 
			
		||||
        pk_info: primaryKeys,
 | 
			
		||||
        columns,
 | 
			
		||||
        indexes,
 | 
			
		||||
        views: views,
 | 
			
		||||
    } = databaseMetadata;
 | 
			
		||||
 | 
			
		||||
    // Pre-compute view names for faster lookup if there are views
 | 
			
		||||
    const viewNamesSet = new Set<string>();
 | 
			
		||||
    const materializedViewNamesSet = new Set<string>();
 | 
			
		||||
 | 
			
		||||
    if (views && views.length > 0) {
 | 
			
		||||
        views.forEach((view) => {
 | 
			
		||||
            const key = generateTableKey({
 | 
			
		||||
                schemaName: view.schema,
 | 
			
		||||
                tableName: view.view_name,
 | 
			
		||||
            });
 | 
			
		||||
            viewNamesSet.add(key);
 | 
			
		||||
 | 
			
		||||
            if (
 | 
			
		||||
                view.view_definition &&
 | 
			
		||||
                decodeViewDefinition(databaseType, view.view_definition)
 | 
			
		||||
                    .toLowerCase()
 | 
			
		||||
                    .includes('materialized')
 | 
			
		||||
            ) {
 | 
			
		||||
                materializedViewNamesSet.add(key);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pre-compute lookup maps for better performance
 | 
			
		||||
    const columnsByTable = new Map<string, (typeof columns)[0][]>();
 | 
			
		||||
    const indexesByTable = new Map<string, (typeof indexes)[0][]>();
 | 
			
		||||
    const primaryKeysByTable = new Map<string, (typeof primaryKeys)[0][]>();
 | 
			
		||||
 | 
			
		||||
    // Group columns by table
 | 
			
		||||
    columns.forEach((col) => {
 | 
			
		||||
        const key = generateTableKey({
 | 
			
		||||
            schemaName: col.schema,
 | 
			
		||||
            tableName: col.table,
 | 
			
		||||
        });
 | 
			
		||||
        if (!columnsByTable.has(key)) {
 | 
			
		||||
            columnsByTable.set(key, []);
 | 
			
		||||
        }
 | 
			
		||||
        columnsByTable.get(key)!.push(col);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Group indexes by table
 | 
			
		||||
    indexes.forEach((idx) => {
 | 
			
		||||
        const key = generateTableKey({
 | 
			
		||||
            schemaName: idx.schema,
 | 
			
		||||
            tableName: idx.table,
 | 
			
		||||
        });
 | 
			
		||||
        if (!indexesByTable.has(key)) {
 | 
			
		||||
            indexesByTable.set(key, []);
 | 
			
		||||
        }
 | 
			
		||||
        indexesByTable.get(key)!.push(idx);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Group primary keys by table
 | 
			
		||||
    primaryKeys.forEach((pk) => {
 | 
			
		||||
        const key = generateTableKey({
 | 
			
		||||
            schemaName: pk.schema,
 | 
			
		||||
            tableName: pk.table,
 | 
			
		||||
        });
 | 
			
		||||
        if (!primaryKeysByTable.has(key)) {
 | 
			
		||||
            primaryKeysByTable.set(key, []);
 | 
			
		||||
        }
 | 
			
		||||
        primaryKeysByTable.get(key)!.push(pk);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const result = tableInfos.map((tableInfo: TableInfo) => {
 | 
			
		||||
        const tableSchema = schemaNameToDomainSchemaName(tableInfo.schema);
 | 
			
		||||
        const tableKey = generateTableKey({
 | 
			
		||||
            schemaName: tableInfo.schema,
 | 
			
		||||
            tableName: tableInfo.table,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Use pre-computed lookups instead of filtering entire arrays
 | 
			
		||||
        const tableIndexes = indexesByTable.get(tableKey) || [];
 | 
			
		||||
        const tablePrimaryKeys = primaryKeysByTable.get(tableKey) || [];
 | 
			
		||||
        const tableColumns = columnsByTable.get(tableKey) || [];
 | 
			
		||||
 | 
			
		||||
        // Aggregate indexes with multiple columns
 | 
			
		||||
        const aggregatedIndexes = createAggregatedIndexes({
 | 
			
		||||
            tableInfo,
 | 
			
		||||
            tableSchema,
 | 
			
		||||
            tableIndexes,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const fields = createFieldsFromMetadata({
 | 
			
		||||
            aggregatedIndexes,
 | 
			
		||||
            tableColumns,
 | 
			
		||||
            tablePrimaryKeys,
 | 
			
		||||
            tableInfo,
 | 
			
		||||
            tableSchema,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Check for composite primary key and find matching index name
 | 
			
		||||
        const primaryKeyFields = fields.filter((f) => f.primaryKey);
 | 
			
		||||
        let pkMatchingIndexName: string | undefined;
 | 
			
		||||
        let pkIndex: DBIndex | undefined;
 | 
			
		||||
 | 
			
		||||
        if (primaryKeyFields.length >= 1) {
 | 
			
		||||
            // We have a composite primary key, look for an index that matches all PK columns
 | 
			
		||||
            const pkFieldNames = primaryKeyFields.map((f) => f.name).sort();
 | 
			
		||||
 | 
			
		||||
            // Find an index that matches the primary key columns exactly
 | 
			
		||||
            const matchingIndex = aggregatedIndexes.find((index) => {
 | 
			
		||||
                const indexColumnNames = index.columns
 | 
			
		||||
                    .map((c) => c.name)
 | 
			
		||||
                    .sort();
 | 
			
		||||
                return (
 | 
			
		||||
                    indexColumnNames.length === pkFieldNames.length &&
 | 
			
		||||
                    indexColumnNames.every((col, i) => col === pkFieldNames[i])
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (matchingIndex) {
 | 
			
		||||
                pkMatchingIndexName = matchingIndex.name;
 | 
			
		||||
                // Create a special PK index
 | 
			
		||||
                pkIndex = {
 | 
			
		||||
                    id: generateId(),
 | 
			
		||||
                    name: matchingIndex.name,
 | 
			
		||||
                    unique: true,
 | 
			
		||||
                    fieldIds: primaryKeyFields.map((f) => f.id),
 | 
			
		||||
                    createdAt: Date.now(),
 | 
			
		||||
                    isPrimaryKey: true,
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Filter out the index that matches the composite PK (to avoid duplication)
 | 
			
		||||
        const filteredAggregatedIndexes = pkMatchingIndexName
 | 
			
		||||
            ? aggregatedIndexes.filter(
 | 
			
		||||
                  (idx) => idx.name !== pkMatchingIndexName
 | 
			
		||||
              )
 | 
			
		||||
            : aggregatedIndexes;
 | 
			
		||||
 | 
			
		||||
        const dbIndexes = createIndexesFromMetadata({
 | 
			
		||||
            aggregatedIndexes: filteredAggregatedIndexes,
 | 
			
		||||
            fields,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Add the PK index if it exists
 | 
			
		||||
        if (pkIndex) {
 | 
			
		||||
            dbIndexes.push(pkIndex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Determine if the current table is a view by checking against pre-computed sets
 | 
			
		||||
        const viewKey = generateTableKey({
 | 
			
		||||
            schemaName: tableSchema,
 | 
			
		||||
            tableName: tableInfo.table,
 | 
			
		||||
        });
 | 
			
		||||
        const isView = viewNamesSet.has(viewKey);
 | 
			
		||||
        const isMaterializedView = materializedViewNamesSet.has(viewKey);
 | 
			
		||||
 | 
			
		||||
        // Initial random positions; these will be adjusted later
 | 
			
		||||
        return {
 | 
			
		||||
            id: generateId(),
 | 
			
		||||
            name: tableInfo.table,
 | 
			
		||||
            schema: tableSchema,
 | 
			
		||||
            x: Math.random() * 1000, // Placeholder X
 | 
			
		||||
            y: Math.random() * 800, // Placeholder Y
 | 
			
		||||
            fields,
 | 
			
		||||
            indexes: dbIndexes,
 | 
			
		||||
            color: isMaterializedView
 | 
			
		||||
                ? materializedViewColor
 | 
			
		||||
                : isView
 | 
			
		||||
                  ? viewColor
 | 
			
		||||
                  : defaultTableColor,
 | 
			
		||||
            isView: isView,
 | 
			
		||||
            isMaterializedView: isMaterializedView,
 | 
			
		||||
            createdAt: Date.now(),
 | 
			
		||||
            comments: tableInfo.comment ? tableInfo.comment : undefined,
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
};
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -86,7 +86,7 @@ export interface SQLBinaryExpr extends SQLASTNode {
 | 
			
		||||
 | 
			
		||||
export interface SQLFunctionNode extends SQLASTNode {
 | 
			
		||||
    type: 'function';
 | 
			
		||||
    name: string | { name: Array<{ value: string }> };
 | 
			
		||||
    name: string;
 | 
			
		||||
    args?: {
 | 
			
		||||
        value: SQLASTArg[];
 | 
			
		||||
    };
 | 
			
		||||
@@ -108,31 +108,6 @@ export interface SQLStringLiteral extends SQLASTNode {
 | 
			
		||||
    value: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface SQLDefaultNode extends SQLASTNode {
 | 
			
		||||
    type: 'default';
 | 
			
		||||
    value: SQLASTNode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface SQLCastNode extends SQLASTNode {
 | 
			
		||||
    type: 'cast';
 | 
			
		||||
    expr: SQLASTNode;
 | 
			
		||||
    target: Array<{ dataType: string }>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface SQLBooleanNode extends SQLASTNode {
 | 
			
		||||
    type: 'bool';
 | 
			
		||||
    value: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface SQLNullNode extends SQLASTNode {
 | 
			
		||||
    type: 'null';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface SQLNumberNode extends SQLASTNode {
 | 
			
		||||
    type: 'number';
 | 
			
		||||
    value: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type SQLASTArg =
 | 
			
		||||
    | SQLColumnRef
 | 
			
		||||
    | SQLStringLiteral
 | 
			
		||||
@@ -171,22 +146,6 @@ export function buildSQLFromAST(
 | 
			
		||||
): string {
 | 
			
		||||
    if (!ast) return '';
 | 
			
		||||
 | 
			
		||||
    // Handle default value wrapper
 | 
			
		||||
    if (ast.type === 'default' && 'value' in ast) {
 | 
			
		||||
        const defaultNode = ast as SQLDefaultNode;
 | 
			
		||||
        return buildSQLFromAST(defaultNode.value, dbType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Handle PostgreSQL cast expressions (e.g., 'value'::type)
 | 
			
		||||
    if (ast.type === 'cast' && 'expr' in ast && 'target' in ast) {
 | 
			
		||||
        const castNode = ast as SQLCastNode;
 | 
			
		||||
        const expr = buildSQLFromAST(castNode.expr, dbType);
 | 
			
		||||
        if (castNode.target.length > 0 && castNode.target[0].dataType) {
 | 
			
		||||
            return `${expr}::${castNode.target[0].dataType.toLowerCase()}`;
 | 
			
		||||
        }
 | 
			
		||||
        return expr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ast.type === 'binary_expr') {
 | 
			
		||||
        const expr = ast as SQLBinaryExpr;
 | 
			
		||||
        const leftSQL = buildSQLFromAST(expr.left, dbType);
 | 
			
		||||
@@ -196,59 +155,7 @@ export function buildSQLFromAST(
 | 
			
		||||
 | 
			
		||||
    if (ast.type === 'function') {
 | 
			
		||||
        const func = ast as SQLFunctionNode;
 | 
			
		||||
        let funcName = '';
 | 
			
		||||
 | 
			
		||||
        // Handle nested function name structure
 | 
			
		||||
        if (typeof func.name === 'object' && func.name && 'name' in func.name) {
 | 
			
		||||
            const nameObj = func.name as { name: Array<{ value: string }> };
 | 
			
		||||
            if (nameObj.name.length > 0) {
 | 
			
		||||
                funcName = nameObj.name[0].value || '';
 | 
			
		||||
            }
 | 
			
		||||
        } else if (typeof func.name === 'string') {
 | 
			
		||||
            funcName = func.name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!funcName) return '';
 | 
			
		||||
 | 
			
		||||
        // Normalize PostgreSQL function names to uppercase for consistency
 | 
			
		||||
        if (dbType === DatabaseType.POSTGRESQL) {
 | 
			
		||||
            const pgFunctions = [
 | 
			
		||||
                'now',
 | 
			
		||||
                'current_timestamp',
 | 
			
		||||
                'current_date',
 | 
			
		||||
                'current_time',
 | 
			
		||||
                'gen_random_uuid',
 | 
			
		||||
                'random',
 | 
			
		||||
                'nextval',
 | 
			
		||||
                'currval',
 | 
			
		||||
            ];
 | 
			
		||||
            if (pgFunctions.includes(funcName.toLowerCase())) {
 | 
			
		||||
                funcName = funcName.toUpperCase();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Some PostgreSQL functions don't have parentheses (like CURRENT_TIMESTAMP)
 | 
			
		||||
        if (funcName === 'CURRENT_TIMESTAMP' && !func.args) {
 | 
			
		||||
            return funcName;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Handle SQL Server function defaults that were preprocessed as strings
 | 
			
		||||
        // The preprocessor converts NEWID() to 'newid', GETDATE() to 'getdate', etc.
 | 
			
		||||
        if (dbType === DatabaseType.SQL_SERVER) {
 | 
			
		||||
            const sqlServerFunctions: Record<string, string> = {
 | 
			
		||||
                newid: 'NEWID()',
 | 
			
		||||
                newsequentialid: 'NEWSEQUENTIALID()',
 | 
			
		||||
                getdate: 'GETDATE()',
 | 
			
		||||
                sysdatetime: 'SYSDATETIME()',
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            const lowerFuncName = funcName.toLowerCase();
 | 
			
		||||
            if (sqlServerFunctions[lowerFuncName]) {
 | 
			
		||||
                return sqlServerFunctions[lowerFuncName];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let expr = funcName;
 | 
			
		||||
        let expr = func.name;
 | 
			
		||||
        if (func.args) {
 | 
			
		||||
            expr +=
 | 
			
		||||
                '(' +
 | 
			
		||||
@@ -268,31 +175,12 @@ export function buildSQLFromAST(
 | 
			
		||||
                    })
 | 
			
		||||
                    .join(', ') +
 | 
			
		||||
                ')';
 | 
			
		||||
        } else {
 | 
			
		||||
            expr += '()';
 | 
			
		||||
        }
 | 
			
		||||
        return expr;
 | 
			
		||||
    } else if (ast.type === 'column_ref') {
 | 
			
		||||
        return quoteIdentifier((ast as SQLColumnRef).column, dbType);
 | 
			
		||||
    } else if (ast.type === 'expr_list') {
 | 
			
		||||
        return (ast as SQLExprList).value.map((v) => v.value).join(' AND ');
 | 
			
		||||
    } else if (ast.type === 'single_quote_string') {
 | 
			
		||||
        // String literal with single quotes
 | 
			
		||||
        const strNode = ast as SQLStringLiteral;
 | 
			
		||||
        return `'${strNode.value}'`;
 | 
			
		||||
    } else if (ast.type === 'double_quote_string') {
 | 
			
		||||
        // String literal with double quotes
 | 
			
		||||
        const strNode = ast as SQLStringLiteral;
 | 
			
		||||
        return `"${strNode.value}"`;
 | 
			
		||||
    } else if (ast.type === 'bool') {
 | 
			
		||||
        // Boolean value
 | 
			
		||||
        const boolNode = ast as SQLBooleanNode;
 | 
			
		||||
        return boolNode.value ? 'TRUE' : 'FALSE';
 | 
			
		||||
    } else if (ast.type === 'null') {
 | 
			
		||||
        return 'NULL';
 | 
			
		||||
    } else if (ast.type === 'number') {
 | 
			
		||||
        const numNode = ast as SQLNumberNode;
 | 
			
		||||
        return String(numNode.value);
 | 
			
		||||
    } else {
 | 
			
		||||
        const valueNode = ast as { type: string; value: string | number };
 | 
			
		||||
        return typeof valueNode.value === 'string'
 | 
			
		||||
@@ -891,10 +779,10 @@ export function convertToChartDBDiagram(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const sourceField = sourceTable.fields.find(
 | 
			
		||||
            (f) => f.name.toLowerCase() === rel.sourceColumn.toLowerCase()
 | 
			
		||||
            (f) => f.name === rel.sourceColumn
 | 
			
		||||
        );
 | 
			
		||||
        const targetField = targetTable.fields.find(
 | 
			
		||||
            (f) => f.name.toLowerCase() === rel.targetColumn.toLowerCase()
 | 
			
		||||
            (f) => f.name === rel.targetColumn
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (!sourceField || !targetField) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,228 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { fromMySQL } from '../mysql';
 | 
			
		||||
 | 
			
		||||
describe('MySQL Default Value Import', () => {
 | 
			
		||||
    describe('String Default Values', () => {
 | 
			
		||||
        it('should parse simple string defaults with single quotes', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE tavern_patrons (
 | 
			
		||||
                    patron_id INT NOT NULL,
 | 
			
		||||
                    membership_status VARCHAR(50) DEFAULT 'regular',
 | 
			
		||||
                    PRIMARY KEY (patron_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromMySQL(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const statusColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'membership_status'
 | 
			
		||||
            );
 | 
			
		||||
            expect(statusColumn?.default).toBe("'regular'");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should parse string defaults with escaped quotes', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE wizard_spellbooks (
 | 
			
		||||
                    spellbook_id INT NOT NULL,
 | 
			
		||||
                    incantation VARCHAR(255) DEFAULT 'Dragon\\'s flame',
 | 
			
		||||
                    spell_metadata TEXT DEFAULT '{"type": "fire"}',
 | 
			
		||||
                    PRIMARY KEY (spellbook_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromMySQL(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const incantationColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'incantation'
 | 
			
		||||
            );
 | 
			
		||||
            expect(incantationColumn?.default).toBeTruthy();
 | 
			
		||||
            const metadataColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'spell_metadata'
 | 
			
		||||
            );
 | 
			
		||||
            expect(metadataColumn?.default).toBeTruthy();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Numeric Default Values', () => {
 | 
			
		||||
        it('should parse integer defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE dungeon_levels (
 | 
			
		||||
                    level_id INT NOT NULL,
 | 
			
		||||
                    monster_count INT DEFAULT 0,
 | 
			
		||||
                    max_treasure INT DEFAULT 1000,
 | 
			
		||||
                    PRIMARY KEY (level_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromMySQL(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const monsterColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'monster_count'
 | 
			
		||||
            );
 | 
			
		||||
            expect(monsterColumn?.default).toBe('0');
 | 
			
		||||
            const treasureColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'max_treasure'
 | 
			
		||||
            );
 | 
			
		||||
            expect(treasureColumn?.default).toBe('1000');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should parse decimal defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE merchant_inventory (
 | 
			
		||||
                    item_id INT NOT NULL,
 | 
			
		||||
                    base_price DECIMAL(10, 2) DEFAULT 99.99,
 | 
			
		||||
                    loyalty_discount FLOAT DEFAULT 0.15,
 | 
			
		||||
                    PRIMARY KEY (item_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromMySQL(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const priceColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'base_price'
 | 
			
		||||
            );
 | 
			
		||||
            expect(priceColumn?.default).toBe('99.99');
 | 
			
		||||
            const discountColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'loyalty_discount'
 | 
			
		||||
            );
 | 
			
		||||
            expect(discountColumn?.default).toBe('0.15');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Boolean Default Values', () => {
 | 
			
		||||
        it('should parse boolean defaults in MySQL (using TINYINT)', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE character_status (
 | 
			
		||||
                    character_id INT NOT NULL,
 | 
			
		||||
                    is_alive TINYINT(1) DEFAULT 1,
 | 
			
		||||
                    is_cursed TINYINT(1) DEFAULT 0,
 | 
			
		||||
                    has_magic BOOLEAN DEFAULT TRUE,
 | 
			
		||||
                    PRIMARY KEY (character_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromMySQL(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const aliveColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'is_alive'
 | 
			
		||||
            );
 | 
			
		||||
            expect(aliveColumn?.default).toBe('1');
 | 
			
		||||
            const cursedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'is_cursed'
 | 
			
		||||
            );
 | 
			
		||||
            expect(cursedColumn?.default).toBe('0');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('NULL Default Values', () => {
 | 
			
		||||
        it('should parse NULL defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE companion_animals (
 | 
			
		||||
                    companion_id INT NOT NULL,
 | 
			
		||||
                    special_trait VARCHAR(255) DEFAULT NULL,
 | 
			
		||||
                    PRIMARY KEY (companion_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromMySQL(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const traitColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'special_trait'
 | 
			
		||||
            );
 | 
			
		||||
            expect(traitColumn?.default).toBe('NULL');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Function Default Values', () => {
 | 
			
		||||
        it('should parse function defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE quest_entries (
 | 
			
		||||
                    entry_id INT NOT NULL AUTO_INCREMENT,
 | 
			
		||||
                    quest_accepted TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
                    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 | 
			
		||||
                    quest_uuid VARCHAR(36) DEFAULT (UUID()),
 | 
			
		||||
                    PRIMARY KEY (entry_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromMySQL(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const acceptedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'quest_accepted'
 | 
			
		||||
            );
 | 
			
		||||
            expect(acceptedColumn?.default).toBe('CURRENT_TIMESTAMP');
 | 
			
		||||
            const updatedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'last_updated'
 | 
			
		||||
            );
 | 
			
		||||
            expect(updatedColumn?.default).toBe('CURRENT_TIMESTAMP');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('AUTO_INCREMENT', () => {
 | 
			
		||||
        it('should handle AUTO_INCREMENT columns correctly', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE hero_registry (
 | 
			
		||||
                    hero_id INT NOT NULL AUTO_INCREMENT,
 | 
			
		||||
                    hero_name VARCHAR(100),
 | 
			
		||||
                    PRIMARY KEY (hero_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromMySQL(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const idColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'hero_id'
 | 
			
		||||
            );
 | 
			
		||||
            expect(idColumn?.increment).toBe(true);
 | 
			
		||||
            // AUTO_INCREMENT columns typically don't have a default value
 | 
			
		||||
            expect(idColumn?.default).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Complex Real-World Example', () => {
 | 
			
		||||
        it('should handle complex table with multiple default types', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE adventurer_profiles (
 | 
			
		||||
                    adventurer_id BIGINT NOT NULL AUTO_INCREMENT,
 | 
			
		||||
                    character_name VARCHAR(50) NOT NULL,
 | 
			
		||||
                    guild_email VARCHAR(255) NOT NULL,
 | 
			
		||||
                    rank VARCHAR(20) DEFAULT 'novice',
 | 
			
		||||
                    is_guild_verified TINYINT(1) DEFAULT 0,
 | 
			
		||||
                    gold_coins INT DEFAULT 100,
 | 
			
		||||
                    account_balance DECIMAL(10, 2) DEFAULT 0.00,
 | 
			
		||||
                    joined_realm TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
                    last_quest TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 | 
			
		||||
                    inventory_data JSON DEFAULT NULL,
 | 
			
		||||
                    PRIMARY KEY (adventurer_id),
 | 
			
		||||
                    UNIQUE KEY uk_guild_email (guild_email),
 | 
			
		||||
                    INDEX idx_rank (rank)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromMySQL(sql);
 | 
			
		||||
            const table = result.tables[0];
 | 
			
		||||
            expect(table).toBeDefined();
 | 
			
		||||
 | 
			
		||||
            // Check various default values
 | 
			
		||||
            const rankColumn = table.columns.find((c) => c.name === 'rank');
 | 
			
		||||
            expect(rankColumn?.default).toBe("'novice'");
 | 
			
		||||
 | 
			
		||||
            const verifiedColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'is_guild_verified'
 | 
			
		||||
            );
 | 
			
		||||
            expect(verifiedColumn?.default).toBe('0');
 | 
			
		||||
 | 
			
		||||
            const goldColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'gold_coins'
 | 
			
		||||
            );
 | 
			
		||||
            expect(goldColumn?.default).toBe('100');
 | 
			
		||||
 | 
			
		||||
            const balanceColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'account_balance'
 | 
			
		||||
            );
 | 
			
		||||
            expect(balanceColumn?.default).toBe('0.00');
 | 
			
		||||
 | 
			
		||||
            const joinedColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'joined_realm'
 | 
			
		||||
            );
 | 
			
		||||
            expect(joinedColumn?.default).toBe('CURRENT_TIMESTAMP');
 | 
			
		||||
 | 
			
		||||
            const inventoryColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'inventory_data'
 | 
			
		||||
            );
 | 
			
		||||
            expect(inventoryColumn?.default).toBe('NULL');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -101,28 +101,12 @@ function extractColumnsFromCreateTable(statement: string): SQLColumn[] {
 | 
			
		||||
            const typeMatch = definition.match(/^([^\s(]+)(?:\(([^)]+)\))?/);
 | 
			
		||||
            const dataType = typeMatch ? typeMatch[1] : '';
 | 
			
		||||
 | 
			
		||||
            // Extract default value
 | 
			
		||||
            let defaultValue: string | undefined;
 | 
			
		||||
            const defaultMatch = definition.match(
 | 
			
		||||
                /DEFAULT\s+('[^']*'|"[^"]*"|NULL|CURRENT_TIMESTAMP|\S+)/i
 | 
			
		||||
            );
 | 
			
		||||
            if (defaultMatch) {
 | 
			
		||||
                defaultValue = defaultMatch[1];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check for AUTO_INCREMENT
 | 
			
		||||
            const increment = definition
 | 
			
		||||
                .toUpperCase()
 | 
			
		||||
                .includes('AUTO_INCREMENT');
 | 
			
		||||
 | 
			
		||||
            columns.push({
 | 
			
		||||
                name: columnName,
 | 
			
		||||
                type: dataType,
 | 
			
		||||
                nullable,
 | 
			
		||||
                primaryKey,
 | 
			
		||||
                unique: definition.toUpperCase().includes('UNIQUE'),
 | 
			
		||||
                default: defaultValue,
 | 
			
		||||
                increment,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -737,28 +721,7 @@ export async function fromMySQL(sqlContent: string): Promise<SQLParserResult> {
 | 
			
		||||
                        parseError
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    // Try fallback parser when main parser fails
 | 
			
		||||
                    const tableMatch = trimmedStmt.match(
 | 
			
		||||
                        /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?`?([^`\s(]+)`?\s*\(/i
 | 
			
		||||
                    );
 | 
			
		||||
                    if (tableMatch) {
 | 
			
		||||
                        const tableName = tableMatch[1];
 | 
			
		||||
                        const tableId = generateId();
 | 
			
		||||
                        tableMap[tableName] = tableId;
 | 
			
		||||
 | 
			
		||||
                        const extractedColumns =
 | 
			
		||||
                            extractColumnsFromCreateTable(trimmedStmt);
 | 
			
		||||
                        if (extractedColumns.length > 0) {
 | 
			
		||||
                            tables.push({
 | 
			
		||||
                                id: tableId,
 | 
			
		||||
                                name: tableName,
 | 
			
		||||
                                schema: undefined,
 | 
			
		||||
                                columns: extractedColumns,
 | 
			
		||||
                                indexes: [],
 | 
			
		||||
                                order: tables.length,
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    // Error handling without logging
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,215 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { fromPostgres } from '../postgresql';
 | 
			
		||||
 | 
			
		||||
describe('PostgreSQL ALTER TABLE ADD COLUMN Tests', () => {
 | 
			
		||||
    it('should handle ALTER TABLE ADD COLUMN statements', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
            CREATE SCHEMA IF NOT EXISTS "public";
 | 
			
		||||
 | 
			
		||||
            CREATE TABLE "public"."location" (
 | 
			
		||||
                "id" bigint NOT NULL,
 | 
			
		||||
                CONSTRAINT "pk_table_7_id" PRIMARY KEY ("id")
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            -- Add new fields to existing location table
 | 
			
		||||
            ALTER TABLE location ADD COLUMN country_id INT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN state_id INT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN location_type_id INT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN city_id INT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN street TEXT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN block TEXT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN building TEXT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN floor TEXT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN apartment TEXT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN lat INT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN long INT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN elevation INT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN erp_site_id INT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN is_active TEXT;
 | 
			
		||||
            ALTER TABLE location ADD COLUMN remarks TEXT;
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(1);
 | 
			
		||||
        const locationTable = result.tables[0];
 | 
			
		||||
 | 
			
		||||
        expect(locationTable.name).toBe('location');
 | 
			
		||||
        expect(locationTable.schema).toBe('public');
 | 
			
		||||
 | 
			
		||||
        // Should have the original id column plus all the added columns
 | 
			
		||||
        expect(locationTable.columns).toHaveLength(16);
 | 
			
		||||
 | 
			
		||||
        // Check that the id column is present
 | 
			
		||||
        const idColumn = locationTable.columns.find((col) => col.name === 'id');
 | 
			
		||||
        expect(idColumn).toBeDefined();
 | 
			
		||||
        expect(idColumn?.type).toBe('BIGINT');
 | 
			
		||||
        expect(idColumn?.primaryKey).toBe(true);
 | 
			
		||||
 | 
			
		||||
        // Check some of the added columns
 | 
			
		||||
        const countryIdColumn = locationTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'country_id'
 | 
			
		||||
        );
 | 
			
		||||
        expect(countryIdColumn).toBeDefined();
 | 
			
		||||
        expect(countryIdColumn?.type).toBe('INTEGER');
 | 
			
		||||
 | 
			
		||||
        const streetColumn = locationTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'street'
 | 
			
		||||
        );
 | 
			
		||||
        expect(streetColumn).toBeDefined();
 | 
			
		||||
        expect(streetColumn?.type).toBe('TEXT');
 | 
			
		||||
 | 
			
		||||
        const remarksColumn = locationTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'remarks'
 | 
			
		||||
        );
 | 
			
		||||
        expect(remarksColumn).toBeDefined();
 | 
			
		||||
        expect(remarksColumn?.type).toBe('TEXT');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle ALTER TABLE ADD COLUMN with schema qualification', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
            CREATE TABLE public.users (
 | 
			
		||||
                id INTEGER PRIMARY KEY
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            ALTER TABLE public.users ADD COLUMN email VARCHAR(255);
 | 
			
		||||
            ALTER TABLE public.users ADD COLUMN created_at TIMESTAMP;
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(1);
 | 
			
		||||
        const usersTable = result.tables[0];
 | 
			
		||||
 | 
			
		||||
        expect(usersTable.columns).toHaveLength(3);
 | 
			
		||||
 | 
			
		||||
        const emailColumn = usersTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'email'
 | 
			
		||||
        );
 | 
			
		||||
        expect(emailColumn).toBeDefined();
 | 
			
		||||
        expect(emailColumn?.type).toBe('VARCHAR(255)');
 | 
			
		||||
 | 
			
		||||
        const createdAtColumn = usersTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'created_at'
 | 
			
		||||
        );
 | 
			
		||||
        expect(createdAtColumn).toBeDefined();
 | 
			
		||||
        expect(createdAtColumn?.type).toBe('TIMESTAMP');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle ALTER TABLE ADD COLUMN with constraints', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
            CREATE TABLE products (
 | 
			
		||||
                id SERIAL PRIMARY KEY
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            ALTER TABLE products ADD COLUMN name VARCHAR(100) NOT NULL;
 | 
			
		||||
            ALTER TABLE products ADD COLUMN sku VARCHAR(50) UNIQUE;
 | 
			
		||||
            ALTER TABLE products ADD COLUMN price DECIMAL(10,2) DEFAULT 0.00;
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(1);
 | 
			
		||||
        const productsTable = result.tables[0];
 | 
			
		||||
 | 
			
		||||
        expect(productsTable.columns).toHaveLength(4);
 | 
			
		||||
 | 
			
		||||
        const nameColumn = productsTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'name'
 | 
			
		||||
        );
 | 
			
		||||
        expect(nameColumn).toBeDefined();
 | 
			
		||||
        expect(nameColumn?.nullable).toBe(false);
 | 
			
		||||
 | 
			
		||||
        const skuColumn = productsTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'sku'
 | 
			
		||||
        );
 | 
			
		||||
        expect(skuColumn).toBeDefined();
 | 
			
		||||
        expect(skuColumn?.unique).toBe(true);
 | 
			
		||||
 | 
			
		||||
        const priceColumn = productsTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'price'
 | 
			
		||||
        );
 | 
			
		||||
        expect(priceColumn).toBeDefined();
 | 
			
		||||
        expect(priceColumn?.default).toBe('0');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not add duplicate columns', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
            CREATE TABLE items (
 | 
			
		||||
                id INTEGER PRIMARY KEY,
 | 
			
		||||
                name VARCHAR(100)
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            ALTER TABLE items ADD COLUMN description TEXT;
 | 
			
		||||
            ALTER TABLE items ADD COLUMN name VARCHAR(200); -- Should not be added as duplicate
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(1);
 | 
			
		||||
        const itemsTable = result.tables[0];
 | 
			
		||||
 | 
			
		||||
        // Should only have 3 columns: id, name (original), and description
 | 
			
		||||
        expect(itemsTable.columns).toHaveLength(3);
 | 
			
		||||
 | 
			
		||||
        const nameColumns = itemsTable.columns.filter(
 | 
			
		||||
            (col) => col.name === 'name'
 | 
			
		||||
        );
 | 
			
		||||
        expect(nameColumns).toHaveLength(1);
 | 
			
		||||
        expect(nameColumns[0].type).toBe('VARCHAR(100)'); // Should keep original type
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should use default schema when not specified', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
            CREATE TABLE test_table (
 | 
			
		||||
                id INTEGER PRIMARY KEY
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            ALTER TABLE test_table ADD COLUMN value TEXT;
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(1);
 | 
			
		||||
        const testTable = result.tables[0];
 | 
			
		||||
 | 
			
		||||
        expect(testTable.schema).toBe('public');
 | 
			
		||||
        expect(testTable.columns).toHaveLength(2);
 | 
			
		||||
 | 
			
		||||
        const valueColumn = testTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'value'
 | 
			
		||||
        );
 | 
			
		||||
        expect(valueColumn).toBeDefined();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle quoted identifiers in ALTER TABLE ADD COLUMN', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
            CREATE TABLE "my-table" (
 | 
			
		||||
                "id" INTEGER PRIMARY KEY
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            ALTER TABLE "my-table" ADD COLUMN "my-column" VARCHAR(50);
 | 
			
		||||
            ALTER TABLE "my-table" ADD COLUMN "another-column" INTEGER;
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(1);
 | 
			
		||||
        const myTable = result.tables[0];
 | 
			
		||||
 | 
			
		||||
        expect(myTable.name).toBe('my-table');
 | 
			
		||||
        expect(myTable.columns).toHaveLength(3);
 | 
			
		||||
 | 
			
		||||
        const myColumn = myTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'my-column'
 | 
			
		||||
        );
 | 
			
		||||
        expect(myColumn).toBeDefined();
 | 
			
		||||
        expect(myColumn?.type).toBe('VARCHAR(50)');
 | 
			
		||||
 | 
			
		||||
        const anotherColumn = myTable.columns.find(
 | 
			
		||||
            (col) => col.name === 'another-column'
 | 
			
		||||
        );
 | 
			
		||||
        expect(anotherColumn).toBeDefined();
 | 
			
		||||
        expect(anotherColumn?.type).toBe('INTEGER');
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,118 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { fromPostgres } from '../postgresql';
 | 
			
		||||
 | 
			
		||||
describe('PostgreSQL ALTER TABLE ALTER COLUMN TYPE', () => {
 | 
			
		||||
    it('should handle ALTER TABLE ALTER COLUMN TYPE statements', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
CREATE SCHEMA IF NOT EXISTS "public";
 | 
			
		||||
 | 
			
		||||
CREATE TABLE "public"."table_12" (
 | 
			
		||||
    "id" SERIAL,
 | 
			
		||||
    "field1" varchar(200),
 | 
			
		||||
    "field2" varchar(200),
 | 
			
		||||
    "field3" varchar(200),
 | 
			
		||||
    PRIMARY KEY ("id")
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
ALTER TABLE table_12 ALTER COLUMN field1 TYPE VARCHAR(254);
 | 
			
		||||
ALTER TABLE table_12 ALTER COLUMN field2 TYPE VARCHAR(254);
 | 
			
		||||
ALTER TABLE table_12 ALTER COLUMN field3 TYPE VARCHAR(254);
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(1);
 | 
			
		||||
        const table = result.tables[0];
 | 
			
		||||
 | 
			
		||||
        expect(table.name).toBe('table_12');
 | 
			
		||||
        expect(table.columns).toHaveLength(4); // id, field1, field2, field3
 | 
			
		||||
 | 
			
		||||
        // Check that the columns have the updated type
 | 
			
		||||
        const field1 = table.columns.find((col) => col.name === 'field1');
 | 
			
		||||
        expect(field1).toBeDefined();
 | 
			
		||||
        expect(field1?.type).toBe('VARCHAR(254)'); // Should be updated from 200 to 254
 | 
			
		||||
 | 
			
		||||
        const field2 = table.columns.find((col) => col.name === 'field2');
 | 
			
		||||
        expect(field2).toBeDefined();
 | 
			
		||||
        expect(field2?.type).toBe('VARCHAR(254)');
 | 
			
		||||
 | 
			
		||||
        const field3 = table.columns.find((col) => col.name === 'field3');
 | 
			
		||||
        expect(field3).toBeDefined();
 | 
			
		||||
        expect(field3?.type).toBe('VARCHAR(254)');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle various ALTER COLUMN TYPE scenarios', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
CREATE TABLE test_table (
 | 
			
		||||
    id INTEGER PRIMARY KEY,
 | 
			
		||||
    name VARCHAR(50),
 | 
			
		||||
    age SMALLINT,
 | 
			
		||||
    score NUMERIC(5,2)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
-- Change varchar length
 | 
			
		||||
ALTER TABLE test_table ALTER COLUMN name TYPE VARCHAR(100);
 | 
			
		||||
 | 
			
		||||
-- Change numeric type
 | 
			
		||||
ALTER TABLE test_table ALTER COLUMN age TYPE INTEGER;
 | 
			
		||||
 | 
			
		||||
-- Change precision
 | 
			
		||||
ALTER TABLE test_table ALTER COLUMN score TYPE NUMERIC(10,4);
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
        const table = result.tables[0];
 | 
			
		||||
 | 
			
		||||
        const nameCol = table.columns.find((col) => col.name === 'name');
 | 
			
		||||
        expect(nameCol?.type).toBe('VARCHAR(100)');
 | 
			
		||||
 | 
			
		||||
        const ageCol = table.columns.find((col) => col.name === 'age');
 | 
			
		||||
        expect(ageCol?.type).toBe('INTEGER');
 | 
			
		||||
 | 
			
		||||
        const scoreCol = table.columns.find((col) => col.name === 'score');
 | 
			
		||||
        expect(scoreCol?.type).toBe('NUMERIC(10,4)');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle multiple type changes on the same column', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
CREATE SCHEMA IF NOT EXISTS "public";
 | 
			
		||||
 | 
			
		||||
CREATE TABLE "public"."table_12" (
 | 
			
		||||
    "id" SERIAL,
 | 
			
		||||
    "field1" varchar(200),
 | 
			
		||||
    "field2" varchar(200),
 | 
			
		||||
    "field3" varchar(200),
 | 
			
		||||
    PRIMARY KEY ("id")
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
ALTER TABLE table_12 ALTER COLUMN field1 TYPE VARCHAR(254);
 | 
			
		||||
ALTER TABLE table_12 ALTER COLUMN field2 TYPE VARCHAR(254);
 | 
			
		||||
ALTER TABLE table_12 ALTER COLUMN field3 TYPE VARCHAR(254);
 | 
			
		||||
ALTER TABLE table_12 ALTER COLUMN field1 TYPE BIGINT;
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(1);
 | 
			
		||||
        const table = result.tables[0];
 | 
			
		||||
 | 
			
		||||
        expect(table.name).toBe('table_12');
 | 
			
		||||
        expect(table.schema).toBe('public');
 | 
			
		||||
        expect(table.columns).toHaveLength(4);
 | 
			
		||||
 | 
			
		||||
        // Check that field1 has the final type (BIGINT), not the intermediate VARCHAR(254)
 | 
			
		||||
        const field1 = table.columns.find((col) => col.name === 'field1');
 | 
			
		||||
        expect(field1).toBeDefined();
 | 
			
		||||
        expect(field1?.type).toBe('BIGINT'); // Should be BIGINT, not VARCHAR(254)
 | 
			
		||||
 | 
			
		||||
        // Check that field2 and field3 still have VARCHAR(254)
 | 
			
		||||
        const field2 = table.columns.find((col) => col.name === 'field2');
 | 
			
		||||
        expect(field2).toBeDefined();
 | 
			
		||||
        expect(field2?.type).toBe('VARCHAR(254)');
 | 
			
		||||
 | 
			
		||||
        const field3 = table.columns.find((col) => col.name === 'field3');
 | 
			
		||||
        expect(field3).toBeDefined();
 | 
			
		||||
        expect(field3?.type).toBe('VARCHAR(254)');
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,117 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { fromPostgres } from '../postgresql';
 | 
			
		||||
 | 
			
		||||
describe('PostgreSQL ALTER TABLE with Foreign Keys', () => {
 | 
			
		||||
    it('should handle ALTER TABLE ADD COLUMN followed by ALTER TABLE ADD FOREIGN KEY', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
CREATE SCHEMA IF NOT EXISTS "public";
 | 
			
		||||
 | 
			
		||||
CREATE TABLE "public"."location" (
 | 
			
		||||
    "id" bigint NOT NULL,
 | 
			
		||||
    CONSTRAINT "pk_table_7_id" PRIMARY KEY ("id")
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
-- Add new fields to existing location table
 | 
			
		||||
ALTER TABLE location ADD COLUMN country_id INT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN state_id INT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN location_type_id INT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN city_id INT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN street TEXT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN block TEXT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN building TEXT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN floor TEXT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN apartment TEXT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN lat INT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN long INT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN elevation INT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN erp_site_id INT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN is_active TEXT;
 | 
			
		||||
ALTER TABLE location ADD COLUMN remarks TEXT;
 | 
			
		||||
 | 
			
		||||
-- Create lookup tables
 | 
			
		||||
CREATE TABLE country (
 | 
			
		||||
    id SERIAL PRIMARY KEY,
 | 
			
		||||
    name VARCHAR(100) NOT NULL,
 | 
			
		||||
    code VARCHAR(3) UNIQUE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE state (
 | 
			
		||||
    id SERIAL PRIMARY KEY,
 | 
			
		||||
    name VARCHAR(100) NOT NULL,
 | 
			
		||||
    country_id INT NOT NULL,
 | 
			
		||||
    FOREIGN KEY (country_id) REFERENCES country(id)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE location_type (
 | 
			
		||||
    id SERIAL PRIMARY KEY,
 | 
			
		||||
    name VARCHAR(100) NOT NULL
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE city (
 | 
			
		||||
    id SERIAL PRIMARY KEY,
 | 
			
		||||
    name VARCHAR(100) NOT NULL,
 | 
			
		||||
    state_id INT NOT NULL,
 | 
			
		||||
    FOREIGN KEY (state_id) REFERENCES state(id)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
-- Add foreign key constraints from location to lookup tables
 | 
			
		||||
ALTER TABLE location ADD CONSTRAINT fk_location_country 
 | 
			
		||||
    FOREIGN KEY (country_id) REFERENCES country(id);
 | 
			
		||||
ALTER TABLE location ADD CONSTRAINT fk_location_state 
 | 
			
		||||
    FOREIGN KEY (state_id) REFERENCES state(id);
 | 
			
		||||
ALTER TABLE location ADD CONSTRAINT fk_location_location_type 
 | 
			
		||||
    FOREIGN KEY (location_type_id) REFERENCES location_type(id);
 | 
			
		||||
ALTER TABLE location ADD CONSTRAINT fk_location_city 
 | 
			
		||||
    FOREIGN KEY (city_id) REFERENCES city(id);
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
        const locationTable = result.tables.find((t) => t.name === 'location');
 | 
			
		||||
 | 
			
		||||
        // Check tables
 | 
			
		||||
        expect(result.tables).toHaveLength(5); // location, country, state, location_type, city
 | 
			
		||||
 | 
			
		||||
        // Check location table has all columns
 | 
			
		||||
        expect(locationTable).toBeDefined();
 | 
			
		||||
        expect(locationTable?.columns).toHaveLength(16); // id + 15 added columns
 | 
			
		||||
 | 
			
		||||
        // Check foreign key relationships
 | 
			
		||||
        const locationRelationships = result.relationships.filter(
 | 
			
		||||
            (r) => r.sourceTable === 'location'
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Should have 4 FKs from location to lookup tables + 2 from state/city
 | 
			
		||||
        expect(result.relationships.length).toBeGreaterThanOrEqual(6);
 | 
			
		||||
 | 
			
		||||
        // Check specific foreign keys from location
 | 
			
		||||
        expect(
 | 
			
		||||
            locationRelationships.some(
 | 
			
		||||
                (r) =>
 | 
			
		||||
                    r.sourceColumn === 'country_id' &&
 | 
			
		||||
                    r.targetTable === 'country'
 | 
			
		||||
            )
 | 
			
		||||
        ).toBe(true);
 | 
			
		||||
 | 
			
		||||
        expect(
 | 
			
		||||
            locationRelationships.some(
 | 
			
		||||
                (r) =>
 | 
			
		||||
                    r.sourceColumn === 'state_id' && r.targetTable === 'state'
 | 
			
		||||
            )
 | 
			
		||||
        ).toBe(true);
 | 
			
		||||
 | 
			
		||||
        expect(
 | 
			
		||||
            locationRelationships.some(
 | 
			
		||||
                (r) =>
 | 
			
		||||
                    r.sourceColumn === 'location_type_id' &&
 | 
			
		||||
                    r.targetTable === 'location_type'
 | 
			
		||||
            )
 | 
			
		||||
        ).toBe(true);
 | 
			
		||||
 | 
			
		||||
        expect(
 | 
			
		||||
            locationRelationships.some(
 | 
			
		||||
                (r) => r.sourceColumn === 'city_id' && r.targetTable === 'city'
 | 
			
		||||
            )
 | 
			
		||||
        ).toBe(true);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,395 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { fromPostgres } from '../postgresql';
 | 
			
		||||
 | 
			
		||||
describe('PostgreSQL Default Value Import', () => {
 | 
			
		||||
    describe('String Default Values', () => {
 | 
			
		||||
        it('should parse simple string defaults with single quotes', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE heroes (
 | 
			
		||||
                    hero_id INTEGER NOT NULL,
 | 
			
		||||
                    hero_status CHARACTER VARYING DEFAULT 'questing',
 | 
			
		||||
                    PRIMARY KEY (hero_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const statusColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'hero_status'
 | 
			
		||||
            );
 | 
			
		||||
            expect(statusColumn?.default).toBe("'questing'");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should parse string defaults with special characters that need escaping', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE spell_scrolls (
 | 
			
		||||
                    scroll_id INTEGER NOT NULL,
 | 
			
		||||
                    incantation CHARACTER VARYING DEFAULT 'Dragon''s breath',
 | 
			
		||||
                    rune_inscription TEXT DEFAULT 'Ancient rune
 | 
			
		||||
Sacred symbol',
 | 
			
		||||
                    PRIMARY KEY (scroll_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const incantationColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'incantation'
 | 
			
		||||
            );
 | 
			
		||||
            expect(incantationColumn?.default).toBe("'Dragon''s breath'");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should parse elvish text default values', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE elven_greetings (
 | 
			
		||||
                    greeting_id INTEGER NOT NULL,
 | 
			
		||||
                    elvish_welcome CHARACTER VARYING DEFAULT 'Mae govannen',
 | 
			
		||||
                    PRIMARY KEY (greeting_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const greetingColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'elvish_welcome'
 | 
			
		||||
            );
 | 
			
		||||
            expect(greetingColumn?.default).toBe("'Mae govannen'");
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Numeric Default Values', () => {
 | 
			
		||||
        it('should parse integer defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE dragon_hoards (
 | 
			
		||||
                    hoard_id INTEGER NOT NULL,
 | 
			
		||||
                    gold_pieces INTEGER DEFAULT 0,
 | 
			
		||||
                    max_treasure_value INTEGER DEFAULT 10000,
 | 
			
		||||
                    PRIMARY KEY (hoard_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const goldColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'gold_pieces'
 | 
			
		||||
            );
 | 
			
		||||
            expect(goldColumn?.default).toBe('0');
 | 
			
		||||
            const treasureColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'max_treasure_value'
 | 
			
		||||
            );
 | 
			
		||||
            expect(treasureColumn?.default).toBe('10000');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should parse decimal defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE enchanted_items (
 | 
			
		||||
                    item_id INTEGER NOT NULL,
 | 
			
		||||
                    market_price DECIMAL(10, 2) DEFAULT 99.99,
 | 
			
		||||
                    magic_power_rating NUMERIC DEFAULT 0.85,
 | 
			
		||||
                    PRIMARY KEY (item_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const priceColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'market_price'
 | 
			
		||||
            );
 | 
			
		||||
            expect(priceColumn?.default).toBe('99.99');
 | 
			
		||||
            const powerColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'magic_power_rating'
 | 
			
		||||
            );
 | 
			
		||||
            expect(powerColumn?.default).toBe('0.85');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Boolean Default Values', () => {
 | 
			
		||||
        it('should parse boolean defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE magical_artifacts (
 | 
			
		||||
                    artifact_id INTEGER NOT NULL,
 | 
			
		||||
                    is_cursed BOOLEAN DEFAULT TRUE,
 | 
			
		||||
                    is_destroyed BOOLEAN DEFAULT FALSE,
 | 
			
		||||
                    is_legendary BOOLEAN DEFAULT '1',
 | 
			
		||||
                    is_identified BOOLEAN DEFAULT '0',
 | 
			
		||||
                    PRIMARY KEY (artifact_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const cursedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'is_cursed'
 | 
			
		||||
            );
 | 
			
		||||
            expect(cursedColumn?.default).toBe('TRUE');
 | 
			
		||||
            const destroyedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'is_destroyed'
 | 
			
		||||
            );
 | 
			
		||||
            expect(destroyedColumn?.default).toBe('FALSE');
 | 
			
		||||
            const legendaryColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'is_legendary'
 | 
			
		||||
            );
 | 
			
		||||
            expect(legendaryColumn?.default).toBe("'1'");
 | 
			
		||||
            const identifiedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'is_identified'
 | 
			
		||||
            );
 | 
			
		||||
            expect(identifiedColumn?.default).toBe("'0'");
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('NULL Default Values', () => {
 | 
			
		||||
        it('should parse NULL defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE wizard_familiars (
 | 
			
		||||
                    familiar_id INTEGER NOT NULL,
 | 
			
		||||
                    special_ability CHARACTER VARYING DEFAULT NULL,
 | 
			
		||||
                    PRIMARY KEY (familiar_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const abilityColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'special_ability'
 | 
			
		||||
            );
 | 
			
		||||
            expect(abilityColumn?.default).toBe('NULL');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Function Default Values', () => {
 | 
			
		||||
        it('should parse function defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE quest_logs (
 | 
			
		||||
                    quest_id UUID DEFAULT gen_random_uuid(),
 | 
			
		||||
                    quest_started TIMESTAMP DEFAULT NOW(),
 | 
			
		||||
                    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
                    difficulty_roll INTEGER DEFAULT random()
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const questIdColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'quest_id'
 | 
			
		||||
            );
 | 
			
		||||
            expect(questIdColumn?.default).toBe('GEN_RANDOM_UUID()');
 | 
			
		||||
            const startedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'quest_started'
 | 
			
		||||
            );
 | 
			
		||||
            expect(startedColumn?.default).toBe('NOW()');
 | 
			
		||||
            const updatedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'last_updated'
 | 
			
		||||
            );
 | 
			
		||||
            expect(updatedColumn?.default).toBe('CURRENT_TIMESTAMP');
 | 
			
		||||
            const difficultyColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'difficulty_roll'
 | 
			
		||||
            );
 | 
			
		||||
            expect(difficultyColumn?.default).toBe('RANDOM()');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Complex Real-World Example', () => {
 | 
			
		||||
        it('should handle a complex guild management table correctly', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "realm"(
 | 
			
		||||
                    "realm_id" integer NOT NULL
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                CREATE TABLE "guild"(
 | 
			
		||||
                    "guild_id" CHARACTER VARYING NOT NULL UNIQUE,
 | 
			
		||||
                    PRIMARY KEY ("guild_id")
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                CREATE TABLE "guild_schedule"(
 | 
			
		||||
                    "schedule_id" CHARACTER VARYING NOT NULL UNIQUE,
 | 
			
		||||
                    PRIMARY KEY ("schedule_id")
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                CREATE TABLE "guild_quests"(
 | 
			
		||||
                    "is_active" CHARACTER VARYING NOT NULL DEFAULT 'active',
 | 
			
		||||
                    "quest_description" CHARACTER VARYING,
 | 
			
		||||
                    "quest_type" CHARACTER VARYING,
 | 
			
		||||
                    "quest_status" CHARACTER VARYING DEFAULT 'pending',
 | 
			
		||||
                    "quest_id" CHARACTER VARYING NOT NULL UNIQUE,
 | 
			
		||||
                    "reward_gold" CHARACTER VARYING,
 | 
			
		||||
                    "quest_giver" CHARACTER VARYING,
 | 
			
		||||
                    "party_size" CHARACTER VARYING,
 | 
			
		||||
                    "difficulty_level" CHARACTER VARYING,
 | 
			
		||||
                    "monster_type" CHARACTER VARYING,
 | 
			
		||||
                    "dungeon_location" CHARACTER VARYING,
 | 
			
		||||
                    "main_guild_ref" CHARACTER VARYING NOT NULL,
 | 
			
		||||
                    "schedule_ref" CHARACTER VARYING,
 | 
			
		||||
                    "last_attempt" CHARACTER VARYING,
 | 
			
		||||
                    "max_attempts" INTEGER,
 | 
			
		||||
                    "failed_attempts" INTEGER,
 | 
			
		||||
                    "party_members" INTEGER,
 | 
			
		||||
                    "loot_distributor" CHARACTER VARYING,
 | 
			
		||||
                    "quest_validator" CHARACTER VARYING,
 | 
			
		||||
                    "scout_report" CHARACTER VARYING,
 | 
			
		||||
                    "completion_xp" INTEGER,
 | 
			
		||||
                    "bonus_xp" INTEGER,
 | 
			
		||||
                    "map_coordinates" CHARACTER VARYING,
 | 
			
		||||
                    "quest_correlation" CHARACTER VARYING,
 | 
			
		||||
                    "is_completed" BOOLEAN NOT NULL DEFAULT '0',
 | 
			
		||||
                    "reward_items" CHARACTER VARYING,
 | 
			
		||||
                    "quest_priority" INTEGER,
 | 
			
		||||
                    "started_at" CHARACTER VARYING,
 | 
			
		||||
                    "status" CHARACTER VARYING,
 | 
			
		||||
                    "completed_at" CHARACTER VARYING,
 | 
			
		||||
                    "party_level" INTEGER,
 | 
			
		||||
                    "quest_master" CHARACTER VARYING,
 | 
			
		||||
                    PRIMARY KEY ("quest_id"),
 | 
			
		||||
                    FOREIGN KEY ("main_guild_ref") REFERENCES "guild"("guild_id"),
 | 
			
		||||
                    FOREIGN KEY ("schedule_ref") REFERENCES "guild_schedule"("schedule_id")
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            // Find the guild_quests table
 | 
			
		||||
            const questTable = result.tables.find(
 | 
			
		||||
                (t) => t.name === 'guild_quests'
 | 
			
		||||
            );
 | 
			
		||||
            expect(questTable).toBeDefined();
 | 
			
		||||
 | 
			
		||||
            // Check specific default values
 | 
			
		||||
            const activeColumn = questTable?.columns.find(
 | 
			
		||||
                (c) => c.name === 'is_active'
 | 
			
		||||
            );
 | 
			
		||||
            expect(activeColumn?.default).toBe("'active'");
 | 
			
		||||
 | 
			
		||||
            const statusColumn = questTable?.columns.find(
 | 
			
		||||
                (c) => c.name === 'quest_status'
 | 
			
		||||
            );
 | 
			
		||||
            expect(statusColumn?.default).toBe("'pending'");
 | 
			
		||||
 | 
			
		||||
            const completedColumn = questTable?.columns.find(
 | 
			
		||||
                (c) => c.name === 'is_completed'
 | 
			
		||||
            );
 | 
			
		||||
            expect(completedColumn?.default).toBe("'0'");
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('ALTER TABLE ADD COLUMN with defaults', () => {
 | 
			
		||||
        it('should handle ALTER TABLE ADD COLUMN with default values', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE adventurers (
 | 
			
		||||
                    adventurer_id INTEGER NOT NULL,
 | 
			
		||||
                    PRIMARY KEY (adventurer_id)
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                ALTER TABLE adventurers ADD COLUMN class_type VARCHAR(50) DEFAULT 'warrior';
 | 
			
		||||
                ALTER TABLE adventurers ADD COLUMN experience_points INTEGER DEFAULT 0;
 | 
			
		||||
                ALTER TABLE adventurers ADD COLUMN is_guild_member BOOLEAN DEFAULT TRUE;
 | 
			
		||||
                ALTER TABLE adventurers ADD COLUMN joined_at TIMESTAMP DEFAULT NOW();
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const classColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'class_type'
 | 
			
		||||
            );
 | 
			
		||||
            expect(classColumn?.default).toBe("'warrior'");
 | 
			
		||||
 | 
			
		||||
            const xpColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'experience_points'
 | 
			
		||||
            );
 | 
			
		||||
            expect(xpColumn?.default).toBe('0');
 | 
			
		||||
 | 
			
		||||
            const guildColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'is_guild_member'
 | 
			
		||||
            );
 | 
			
		||||
            expect(guildColumn?.default).toBe('TRUE');
 | 
			
		||||
 | 
			
		||||
            const joinedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'joined_at'
 | 
			
		||||
            );
 | 
			
		||||
            expect(joinedColumn?.default).toBe('NOW()');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Edge Cases and Special Characters', () => {
 | 
			
		||||
        it('should handle defaults with parentheses in strings', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE spell_formulas (
 | 
			
		||||
                    formula_id INTEGER NOT NULL,
 | 
			
		||||
                    damage_calculation VARCHAR DEFAULT '(strength + magic) * 2',
 | 
			
		||||
                    mana_cost TEXT DEFAULT 'cast(level * 10 - wisdom)',
 | 
			
		||||
                    PRIMARY KEY (formula_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const damageColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'damage_calculation'
 | 
			
		||||
            );
 | 
			
		||||
            expect(damageColumn?.default).toBe("'(strength + magic) * 2'");
 | 
			
		||||
            const manaColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'mana_cost'
 | 
			
		||||
            );
 | 
			
		||||
            expect(manaColumn?.default).toBe("'cast(level * 10 - wisdom)'");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle defaults with JSON strings', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE item_enchantments (
 | 
			
		||||
                    enchantment_id INTEGER NOT NULL,
 | 
			
		||||
                    properties JSON DEFAULT '{"element": "fire"}',
 | 
			
		||||
                    modifiers JSONB DEFAULT '[]',
 | 
			
		||||
                    PRIMARY KEY (enchantment_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const propertiesColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'properties'
 | 
			
		||||
            );
 | 
			
		||||
            expect(propertiesColumn?.default).toBe(`'{"element": "fire"}'`);
 | 
			
		||||
            const modifiersColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'modifiers'
 | 
			
		||||
            );
 | 
			
		||||
            expect(modifiersColumn?.default).toBe("'[]'");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle casting in defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE ancient_runes (
 | 
			
		||||
                    rune_id INTEGER NOT NULL,
 | 
			
		||||
                    rune_type VARCHAR DEFAULT 'healing'::text,
 | 
			
		||||
                    PRIMARY KEY (rune_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const runeColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'rune_type'
 | 
			
		||||
            );
 | 
			
		||||
            expect(runeColumn?.default).toBe("'healing'::text");
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Serial Types', () => {
 | 
			
		||||
        it('should not set default for SERIAL types as they auto-increment', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE monster_spawns (
 | 
			
		||||
                    spawn_id SERIAL PRIMARY KEY,
 | 
			
		||||
                    minion_id SMALLSERIAL,
 | 
			
		||||
                    boss_id BIGSERIAL
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const spawnColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'spawn_id'
 | 
			
		||||
            );
 | 
			
		||||
            expect(spawnColumn?.default).toBeUndefined();
 | 
			
		||||
            expect(spawnColumn?.increment).toBe(true);
 | 
			
		||||
 | 
			
		||||
            const minionColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'minion_id'
 | 
			
		||||
            );
 | 
			
		||||
            expect(minionColumn?.default).toBeUndefined();
 | 
			
		||||
            expect(minionColumn?.increment).toBe(true);
 | 
			
		||||
 | 
			
		||||
            const bossColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'boss_id'
 | 
			
		||||
            );
 | 
			
		||||
            expect(bossColumn?.default).toBeUndefined();
 | 
			
		||||
            expect(bossColumn?.increment).toBe(true);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,350 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { fromPostgres } from '../postgresql';
 | 
			
		||||
 | 
			
		||||
describe('PostgreSQL Import - Quoted Identifiers with Special Characters', () => {
 | 
			
		||||
    describe('CREATE TABLE with quoted identifiers', () => {
 | 
			
		||||
        it('should handle tables with quoted schema and table names', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "my-schema"."user-profiles" (
 | 
			
		||||
                    id serial PRIMARY KEY,
 | 
			
		||||
                    name text NOT NULL
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const table = result.tables[0];
 | 
			
		||||
            expect(table.schema).toBe('my-schema');
 | 
			
		||||
            expect(table.name).toBe('user-profiles');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle tables with spaces in schema and table names', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "user schema"."profile table" (
 | 
			
		||||
                    "user id" integer PRIMARY KEY,
 | 
			
		||||
                    "full name" varchar(255)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const table = result.tables[0];
 | 
			
		||||
            expect(table.schema).toBe('user schema');
 | 
			
		||||
            expect(table.name).toBe('profile table');
 | 
			
		||||
            expect(table.columns).toBeDefined();
 | 
			
		||||
            expect(table.columns.length).toBeGreaterThan(0);
 | 
			
		||||
            // Note: Column names with spaces might be parsed differently
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle mixed quoted and unquoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "special-schema".users (
 | 
			
		||||
                    id serial PRIMARY KEY
 | 
			
		||||
                );
 | 
			
		||||
                CREATE TABLE public."special-table" (
 | 
			
		||||
                    id serial PRIMARY KEY
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(2);
 | 
			
		||||
 | 
			
		||||
            expect(result.tables[0].schema).toBe('special-schema');
 | 
			
		||||
            expect(result.tables[0].name).toBe('users');
 | 
			
		||||
            expect(result.tables[1].schema).toBe('public');
 | 
			
		||||
            expect(result.tables[1].name).toBe('special-table');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle tables with dots in names', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "schema.with.dots"."table.with.dots" (
 | 
			
		||||
                    id serial PRIMARY KEY,
 | 
			
		||||
                    data text
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const table = result.tables[0];
 | 
			
		||||
            expect(table.schema).toBe('schema.with.dots');
 | 
			
		||||
            expect(table.name).toBe('table.with.dots');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('FOREIGN KEY with quoted identifiers', () => {
 | 
			
		||||
        it('should handle inline REFERENCES with quoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "auth-schema"."users" (
 | 
			
		||||
                    "user-id" serial PRIMARY KEY,
 | 
			
		||||
                    email text UNIQUE
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                CREATE TABLE "app-schema"."user-profiles" (
 | 
			
		||||
                    id serial PRIMARY KEY,
 | 
			
		||||
                    "user-id" integer REFERENCES "auth-schema"."users"("user-id"),
 | 
			
		||||
                    bio text
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(2);
 | 
			
		||||
            expect(result.relationships).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const relationship = result.relationships[0];
 | 
			
		||||
            expect(relationship.sourceTable).toBe('user-profiles');
 | 
			
		||||
            expect(relationship.targetTable).toBe('users');
 | 
			
		||||
            expect(relationship.sourceColumn).toBe('user-id');
 | 
			
		||||
            expect(relationship.targetColumn).toBe('user-id');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle FOREIGN KEY constraints with quoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "schema one"."table one" (
 | 
			
		||||
                    "id field" serial PRIMARY KEY,
 | 
			
		||||
                    "data field" text
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                CREATE TABLE "schema two"."table two" (
 | 
			
		||||
                    id serial PRIMARY KEY,
 | 
			
		||||
                    "ref id" integer,
 | 
			
		||||
                    FOREIGN KEY ("ref id") REFERENCES "schema one"."table one"("id field")
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(2);
 | 
			
		||||
            expect(result.relationships).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const relationship = result.relationships[0];
 | 
			
		||||
            expect(relationship.sourceTable).toBe('table two');
 | 
			
		||||
            expect(relationship.targetTable).toBe('table one');
 | 
			
		||||
            expect(relationship.sourceColumn).toBe('ref id');
 | 
			
		||||
            expect(relationship.targetColumn).toBe('id field');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle named constraints with quoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "auth"."users" (
 | 
			
		||||
                    id serial PRIMARY KEY
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                CREATE TABLE "app"."profiles" (
 | 
			
		||||
                    id serial PRIMARY KEY,
 | 
			
		||||
                    user_id integer,
 | 
			
		||||
                    CONSTRAINT "fk-user-profile" FOREIGN KEY (user_id) REFERENCES "auth"."users"(id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.relationships).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const relationship = result.relationships[0];
 | 
			
		||||
            // Note: Constraint names with special characters might be normalized
 | 
			
		||||
            expect(relationship.name).toBeDefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle ALTER TABLE ADD CONSTRAINT with quoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "user-schema"."user-accounts" (
 | 
			
		||||
                    "account-id" serial PRIMARY KEY,
 | 
			
		||||
                    username text
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                CREATE TABLE "order-schema"."user-orders" (
 | 
			
		||||
                    "order-id" serial PRIMARY KEY,
 | 
			
		||||
                    "account-id" integer
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                ALTER TABLE "order-schema"."user-orders" 
 | 
			
		||||
                ADD CONSTRAINT "fk_orders_accounts" 
 | 
			
		||||
                FOREIGN KEY ("account-id") 
 | 
			
		||||
                REFERENCES "user-schema"."user-accounts"("account-id");
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(2);
 | 
			
		||||
            expect(result.relationships).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const relationship = result.relationships[0];
 | 
			
		||||
            expect(relationship.name).toBe('fk_orders_accounts');
 | 
			
		||||
            expect(relationship.sourceTable).toBe('user-orders');
 | 
			
		||||
            expect(relationship.targetTable).toBe('user-accounts');
 | 
			
		||||
            expect(relationship.sourceColumn).toBe('account-id');
 | 
			
		||||
            expect(relationship.targetColumn).toBe('account-id');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle complex mixed quoting scenarios', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE auth.users (
 | 
			
		||||
                    id serial PRIMARY KEY
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                CREATE TABLE "app-data"."user_profiles" (
 | 
			
		||||
                    profile_id serial PRIMARY KEY,
 | 
			
		||||
                    "user-id" integer REFERENCES auth.users(id)
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                CREATE TABLE "app-data".posts (
 | 
			
		||||
                    id serial PRIMARY KEY,
 | 
			
		||||
                    profile_id integer
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                ALTER TABLE "app-data".posts 
 | 
			
		||||
                ADD CONSTRAINT fk_posts_profiles 
 | 
			
		||||
                FOREIGN KEY (profile_id) 
 | 
			
		||||
                REFERENCES "app-data"."user_profiles"(profile_id);
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(3);
 | 
			
		||||
            expect(result.relationships).toHaveLength(2);
 | 
			
		||||
 | 
			
		||||
            // Verify the relationships were correctly identified
 | 
			
		||||
            const profilesTable = result.tables.find(
 | 
			
		||||
                (t) => t.name === 'user_profiles'
 | 
			
		||||
            );
 | 
			
		||||
            expect(profilesTable?.schema).toBe('app-data');
 | 
			
		||||
 | 
			
		||||
            const postsTable = result.tables.find((t) => t.name === 'posts');
 | 
			
		||||
            expect(postsTable?.schema).toBe('app-data');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Edge cases and special scenarios', () => {
 | 
			
		||||
        it('should handle Unicode characters in quoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "схема"."таблица" (
 | 
			
		||||
                    "идентификатор" serial PRIMARY KEY,
 | 
			
		||||
                    "данные" text
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const table = result.tables[0];
 | 
			
		||||
            expect(table.schema).toBe('схема');
 | 
			
		||||
            expect(table.name).toBe('таблица');
 | 
			
		||||
            expect(table.columns).toBeDefined();
 | 
			
		||||
            expect(table.columns.length).toBeGreaterThan(0);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle parentheses in quoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "schema(prod)"."users(archived)" (
 | 
			
		||||
                    id serial PRIMARY KEY,
 | 
			
		||||
                    data text
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const table = result.tables[0];
 | 
			
		||||
            expect(table.schema).toBe('schema(prod)');
 | 
			
		||||
            expect(table.name).toBe('users(archived)');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle forward slashes in quoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "api/v1"."users/profiles" (
 | 
			
		||||
                    id serial PRIMARY KEY
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const table = result.tables[0];
 | 
			
		||||
            expect(table.schema).toBe('api/v1');
 | 
			
		||||
            expect(table.name).toBe('users/profiles');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle IF NOT EXISTS with quoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE IF NOT EXISTS "test-schema"."test-table" (
 | 
			
		||||
                    id serial PRIMARY KEY
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const table = result.tables[0];
 | 
			
		||||
            expect(table.schema).toBe('test-schema');
 | 
			
		||||
            expect(table.name).toBe('test-table');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle ONLY keyword with quoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE ONLY "parent-schema"."parent-table" (
 | 
			
		||||
                    id serial PRIMARY KEY
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                ALTER TABLE ONLY "parent-schema"."parent-table"
 | 
			
		||||
                ADD CONSTRAINT "unique-constraint" UNIQUE (id);
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            // ONLY keyword might trigger warnings
 | 
			
		||||
            expect(result.warnings).toBeDefined();
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const table = result.tables[0];
 | 
			
		||||
            expect(table.schema).toBe('parent-schema');
 | 
			
		||||
            expect(table.name).toBe('parent-table');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle self-referencing foreign keys with quoted identifiers', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE "org-schema"."departments" (
 | 
			
		||||
                    "dept-id" serial PRIMARY KEY,
 | 
			
		||||
                    "parent-dept-id" integer REFERENCES "org-schema"."departments"("dept-id"),
 | 
			
		||||
                    name text
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromPostgres(sql);
 | 
			
		||||
 | 
			
		||||
            expect(result.warnings || []).toHaveLength(0);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            expect(result.relationships).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            const relationship = result.relationships[0];
 | 
			
		||||
            expect(relationship.sourceTable).toBe('departments');
 | 
			
		||||
            expect(relationship.targetTable).toBe('departments'); // Self-reference
 | 
			
		||||
            expect(relationship.sourceColumn).toBe('parent-dept-id');
 | 
			
		||||
            expect(relationship.targetColumn).toBe('dept-id');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -91,38 +91,7 @@ export interface AlterTableExprItem {
 | 
			
		||||
    action: string;
 | 
			
		||||
    resource?: string;
 | 
			
		||||
    type?: string;
 | 
			
		||||
    keyword?: string;
 | 
			
		||||
    constraint?: { constraint_type?: string };
 | 
			
		||||
    // Properties for ADD COLUMN
 | 
			
		||||
    column?:
 | 
			
		||||
        | {
 | 
			
		||||
              column?:
 | 
			
		||||
                  | {
 | 
			
		||||
                        expr?: {
 | 
			
		||||
                            value?: string;
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                  | string;
 | 
			
		||||
          }
 | 
			
		||||
        | string
 | 
			
		||||
        | ColumnReference;
 | 
			
		||||
    definition?: {
 | 
			
		||||
        dataType?: string;
 | 
			
		||||
        length?: number;
 | 
			
		||||
        precision?: number;
 | 
			
		||||
        scale?: number;
 | 
			
		||||
        suffix?: unknown[];
 | 
			
		||||
        nullable?: { type: string };
 | 
			
		||||
        unique?: string;
 | 
			
		||||
        primary_key?: string;
 | 
			
		||||
        constraint?: string;
 | 
			
		||||
        default_val?: unknown;
 | 
			
		||||
        auto_increment?: string;
 | 
			
		||||
    };
 | 
			
		||||
    nullable?: { type: string; value?: string };
 | 
			
		||||
    unique?: string;
 | 
			
		||||
    default_val?: unknown;
 | 
			
		||||
    // Properties for constraints
 | 
			
		||||
    create_definitions?:
 | 
			
		||||
        | AlterTableConstraintDefinition
 | 
			
		||||
        | {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,6 @@ import type {
 | 
			
		||||
    SQLForeignKey,
 | 
			
		||||
    SQLEnumType,
 | 
			
		||||
} from '../../common';
 | 
			
		||||
import { buildSQLFromAST } from '../../common';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import type {
 | 
			
		||||
    TableReference,
 | 
			
		||||
    ColumnReference,
 | 
			
		||||
@@ -349,20 +347,13 @@ function extractColumnsFromSQL(sql: string): SQLColumn[] {
 | 
			
		||||
 | 
			
		||||
        // Try to extract column definition
 | 
			
		||||
        // Match: column_name TYPE[(params)][array]
 | 
			
		||||
        // First extract column name and everything after it
 | 
			
		||||
        const columnMatch = trimmedLine.match(/^\s*["']?(\w+)["']?\s+(.+)/i);
 | 
			
		||||
        // Updated regex to handle complex types like GEOGRAPHY(POINT, 4326) and custom types like subscription_status
 | 
			
		||||
        const columnMatch = trimmedLine.match(
 | 
			
		||||
            /^\s*["']?(\w+)["']?\s+([\w_]+(?:\([^)]+\))?(?:\[\])?)/i
 | 
			
		||||
        );
 | 
			
		||||
        if (columnMatch) {
 | 
			
		||||
            const columnName = columnMatch[1];
 | 
			
		||||
            const restOfLine = columnMatch[2];
 | 
			
		||||
 | 
			
		||||
            // Now extract the type from the rest of the line
 | 
			
		||||
            // Match type which could be multi-word (like CHARACTER VARYING) with optional params
 | 
			
		||||
            const typeMatch = restOfLine.match(
 | 
			
		||||
                /^((?:CHARACTER\s+VARYING|DOUBLE\s+PRECISION|[\w]+)(?:\([^)]+\))?(?:\[\])?)/i
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (!typeMatch) continue;
 | 
			
		||||
            let columnType = typeMatch[1].trim();
 | 
			
		||||
            let columnType = columnMatch[2];
 | 
			
		||||
 | 
			
		||||
            // Normalize PostGIS types
 | 
			
		||||
            if (columnType.toUpperCase().startsWith('GEOGRAPHY')) {
 | 
			
		||||
@@ -389,65 +380,7 @@ function extractColumnsFromSQL(sql: string): SQLColumn[] {
 | 
			
		||||
            const isPrimary = trimmedLine.match(/PRIMARY\s+KEY/i) !== null;
 | 
			
		||||
            const isNotNull = trimmedLine.match(/NOT\s+NULL/i) !== null;
 | 
			
		||||
            const isUnique = trimmedLine.match(/\bUNIQUE\b/i) !== null;
 | 
			
		||||
 | 
			
		||||
            // Extract default value
 | 
			
		||||
            let defaultValue: string | undefined;
 | 
			
		||||
            // Updated regex to handle casting with :: operator
 | 
			
		||||
            const defaultMatch = trimmedLine.match(
 | 
			
		||||
                /DEFAULT\s+((?:'[^']*'|"[^"]*"|\S+)(?:::\w+)?)/i
 | 
			
		||||
            );
 | 
			
		||||
            if (defaultMatch) {
 | 
			
		||||
                let defVal = defaultMatch[1].trim();
 | 
			
		||||
                // Remove trailing comma if present
 | 
			
		||||
                defVal = defVal.replace(/,$/, '').trim();
 | 
			
		||||
                // Handle string literals
 | 
			
		||||
                if (defVal.startsWith("'") && defVal.endsWith("'")) {
 | 
			
		||||
                    // Keep the quotes for string literals
 | 
			
		||||
                    defaultValue = defVal;
 | 
			
		||||
                } else if (defVal.match(/^\d+(\.\d+)?$/)) {
 | 
			
		||||
                    // Numeric value
 | 
			
		||||
                    defaultValue = defVal;
 | 
			
		||||
                } else if (
 | 
			
		||||
                    defVal.toUpperCase() === 'TRUE' ||
 | 
			
		||||
                    defVal.toUpperCase() === 'FALSE'
 | 
			
		||||
                ) {
 | 
			
		||||
                    // Boolean value
 | 
			
		||||
                    defaultValue = defVal.toUpperCase();
 | 
			
		||||
                } else if (defVal.toUpperCase() === 'NULL') {
 | 
			
		||||
                    // NULL value
 | 
			
		||||
                    defaultValue = 'NULL';
 | 
			
		||||
                } else if (defVal.includes('(') && defVal.includes(')')) {
 | 
			
		||||
                    // Function call (like gen_random_uuid())
 | 
			
		||||
                    // Normalize PostgreSQL function names to uppercase
 | 
			
		||||
                    const funcMatch = defVal.match(/^(\w+)\(/);
 | 
			
		||||
                    if (funcMatch) {
 | 
			
		||||
                        const funcName = funcMatch[1];
 | 
			
		||||
                        const pgFunctions = [
 | 
			
		||||
                            'now',
 | 
			
		||||
                            'current_timestamp',
 | 
			
		||||
                            'current_date',
 | 
			
		||||
                            'current_time',
 | 
			
		||||
                            'gen_random_uuid',
 | 
			
		||||
                            'random',
 | 
			
		||||
                            'nextval',
 | 
			
		||||
                            'currval',
 | 
			
		||||
                        ];
 | 
			
		||||
                        if (pgFunctions.includes(funcName.toLowerCase())) {
 | 
			
		||||
                            defaultValue = defVal.replace(
 | 
			
		||||
                                funcName,
 | 
			
		||||
                                funcName.toUpperCase()
 | 
			
		||||
                            );
 | 
			
		||||
                        } else {
 | 
			
		||||
                            defaultValue = defVal;
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        defaultValue = defVal;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Other expressions
 | 
			
		||||
                    defaultValue = defVal;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            const hasDefault = trimmedLine.match(/DEFAULT\s+/i) !== null;
 | 
			
		||||
 | 
			
		||||
            columns.push({
 | 
			
		||||
                name: columnName,
 | 
			
		||||
@@ -455,7 +388,7 @@ function extractColumnsFromSQL(sql: string): SQLColumn[] {
 | 
			
		||||
                nullable: !isNotNull && !isPrimary,
 | 
			
		||||
                primaryKey: isPrimary,
 | 
			
		||||
                unique: isUnique || isPrimary,
 | 
			
		||||
                default: defaultValue,
 | 
			
		||||
                default: hasDefault ? 'has default' : undefined,
 | 
			
		||||
                increment:
 | 
			
		||||
                    isSerialType ||
 | 
			
		||||
                    trimmedLine.includes('gen_random_uuid()') ||
 | 
			
		||||
@@ -557,21 +490,16 @@ function extractForeignKeysFromCreateTable(
 | 
			
		||||
 | 
			
		||||
    const tableBody = tableBodyMatch[1];
 | 
			
		||||
 | 
			
		||||
    // Pattern for inline REFERENCES - handles quoted and unquoted identifiers
 | 
			
		||||
    // Pattern for inline REFERENCES - more flexible to handle various formats
 | 
			
		||||
    const inlineRefPattern =
 | 
			
		||||
        /(?:"([^"]+)"|([^"\s,()]+))\s+(?:\w+(?:\([^)]*\))?(?:\[[^\]]*\])?(?:\s+\w+)*\s+)?REFERENCES\s+(?:(?:"([^"]+)"|([^"\s.]+))\.)?(?:"([^"]+)"|([^"\s.(]+))\s*\(\s*(?:"([^"]+)"|([^"\s,)]+))\s*\)/gi;
 | 
			
		||||
        /["']?(\w+)["']?\s+(?:\w+(?:\([^)]*\))?(?:\[[^\]]*\])?(?:\s+\w+)*\s+)?REFERENCES\s+(?:["']?(\w+)["']?\.)?["']?(\w+)["']?\s*\(\s*["']?(\w+)["']?\s*\)/gi;
 | 
			
		||||
 | 
			
		||||
    let match;
 | 
			
		||||
    while ((match = inlineRefPattern.exec(tableBody)) !== null) {
 | 
			
		||||
        // Extract values from appropriate match groups
 | 
			
		||||
        // Groups: 1=quoted source col, 2=unquoted source col,
 | 
			
		||||
        //         3=quoted schema, 4=unquoted schema,
 | 
			
		||||
        //         5=quoted target table, 6=unquoted target table,
 | 
			
		||||
        //         7=quoted target col, 8=unquoted target col
 | 
			
		||||
        const sourceColumn = match[1] || match[2];
 | 
			
		||||
        const targetSchema = match[3] || match[4] || 'public';
 | 
			
		||||
        const targetTable = match[5] || match[6];
 | 
			
		||||
        const targetColumn = match[7] || match[8];
 | 
			
		||||
        const sourceColumn = match[1];
 | 
			
		||||
        const targetSchema = match[2] || 'public';
 | 
			
		||||
        const targetTable = match[3];
 | 
			
		||||
        const targetColumn = match[4];
 | 
			
		||||
 | 
			
		||||
        const targetTableKey = `${targetSchema}.${targetTable}`;
 | 
			
		||||
        const targetTableId = tableMap[targetTableKey];
 | 
			
		||||
@@ -593,16 +521,15 @@ function extractForeignKeysFromCreateTable(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pattern for FOREIGN KEY constraints - handles quoted and unquoted identifiers
 | 
			
		||||
    // Pattern for FOREIGN KEY constraints
 | 
			
		||||
    const fkConstraintPattern =
 | 
			
		||||
        /FOREIGN\s+KEY\s*\(\s*(?:"([^"]+)"|([^"\s,)]+))\s*\)\s*REFERENCES\s+(?:(?:"([^"]+)"|([^"\s.]+))\.)?(?:"([^"]+)"|([^"\s.(]+))\s*\(\s*(?:"([^"]+)"|([^"\s,)]+))\s*\)/gi;
 | 
			
		||||
        /FOREIGN\s+KEY\s*\(\s*["']?(\w+)["']?\s*\)\s*REFERENCES\s+(?:["']?(\w+)["']?\.)?["']?(\w+)["']?\s*\(\s*["']?(\w+)["']?\s*\)/gi;
 | 
			
		||||
 | 
			
		||||
    while ((match = fkConstraintPattern.exec(tableBody)) !== null) {
 | 
			
		||||
        // Extract values from appropriate match groups
 | 
			
		||||
        const sourceColumn = match[1] || match[2];
 | 
			
		||||
        const targetSchema = match[3] || match[4] || 'public';
 | 
			
		||||
        const targetTable = match[5] || match[6];
 | 
			
		||||
        const targetColumn = match[7] || match[8];
 | 
			
		||||
        const sourceColumn = match[1];
 | 
			
		||||
        const targetSchema = match[2] || 'public';
 | 
			
		||||
        const targetTable = match[3];
 | 
			
		||||
        const targetColumn = match[4];
 | 
			
		||||
 | 
			
		||||
        const targetTableKey = `${targetSchema}.${targetTable}`;
 | 
			
		||||
        const targetTableId = tableMap[targetTableKey];
 | 
			
		||||
@@ -658,16 +585,12 @@ export async function fromPostgres(
 | 
			
		||||
                    ? stmt.sql.substring(createTableIndex)
 | 
			
		||||
                    : stmt.sql;
 | 
			
		||||
 | 
			
		||||
            // Updated regex to properly handle quoted identifiers with special characters
 | 
			
		||||
            // Matches: schema.table, "schema"."table", "schema".table, schema."table"
 | 
			
		||||
            const tableMatch = sqlFromCreate.match(
 | 
			
		||||
                /CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?(?:\s+ONLY)?\s+(?:(?:"([^"]+)"|([^"\s.]+))\.)?(?:"([^"]+)"|([^"\s.(]+))/i
 | 
			
		||||
                /CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?(?:\s+ONLY)?\s+(?:"?([^"\s.]+)"?\.)?["'`]?([^"'`\s.(]+)["'`]?/i
 | 
			
		||||
            );
 | 
			
		||||
            if (tableMatch) {
 | 
			
		||||
                // Extract schema and table names from the appropriate match groups
 | 
			
		||||
                // Groups: 1=quoted schema, 2=unquoted schema, 3=quoted table, 4=unquoted table
 | 
			
		||||
                const schemaName = tableMatch[1] || tableMatch[2] || 'public';
 | 
			
		||||
                const tableName = tableMatch[3] || tableMatch[4];
 | 
			
		||||
                const schemaName = tableMatch[1] || 'public';
 | 
			
		||||
                const tableName = tableMatch[2];
 | 
			
		||||
                const tableKey = `${schemaName}.${tableName}`;
 | 
			
		||||
                tableMap[tableKey] = generateId();
 | 
			
		||||
            }
 | 
			
		||||
@@ -1015,16 +938,12 @@ export async function fromPostgres(
 | 
			
		||||
                    ? stmt.sql.substring(createTableIndex)
 | 
			
		||||
                    : stmt.sql;
 | 
			
		||||
 | 
			
		||||
            // Updated regex to properly handle quoted identifiers with special characters
 | 
			
		||||
            // Matches: schema.table, "schema"."table", "schema".table, schema."table"
 | 
			
		||||
            const tableMatch = sqlFromCreate.match(
 | 
			
		||||
                /CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?(?:\s+ONLY)?\s+(?:(?:"([^"]+)"|([^"\s.]+))\.)?(?:"([^"]+)"|([^"\s.(]+))/i
 | 
			
		||||
                /CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?(?:\s+ONLY)?\s+(?:"?([^"\s.]+)"?\.)?["'`]?([^"'`\s.(]+)["'`]?/i
 | 
			
		||||
            );
 | 
			
		||||
            if (tableMatch) {
 | 
			
		||||
                // Extract schema and table names from the appropriate match groups
 | 
			
		||||
                // Groups: 1=quoted schema, 2=unquoted schema, 3=quoted table, 4=unquoted table
 | 
			
		||||
                const schemaName = tableMatch[1] || tableMatch[2] || 'public';
 | 
			
		||||
                const tableName = tableMatch[3] || tableMatch[4];
 | 
			
		||||
                const schemaName = tableMatch[1] || 'public';
 | 
			
		||||
                const tableName = tableMatch[2];
 | 
			
		||||
                const tableKey = `${schemaName}.${tableName}`;
 | 
			
		||||
                const tableId = tableMap[tableKey];
 | 
			
		||||
 | 
			
		||||
@@ -1063,7 +982,7 @@ export async function fromPostgres(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Fourth pass: process ALTER TABLE statements for foreign keys and ADD COLUMN
 | 
			
		||||
    // Fourth pass: process ALTER TABLE statements for foreign keys
 | 
			
		||||
    for (const stmt of statements) {
 | 
			
		||||
        if (stmt.type === 'alter' && stmt.parsed) {
 | 
			
		||||
            const alterTableStmt = stmt.parsed as AlterTableStatement;
 | 
			
		||||
@@ -1093,440 +1012,13 @@ export async function fromPostgres(
 | 
			
		||||
            );
 | 
			
		||||
            if (!table) continue;
 | 
			
		||||
 | 
			
		||||
            // Process ALTER TABLE expressions
 | 
			
		||||
            // Process foreign key constraints in ALTER TABLE
 | 
			
		||||
            if (alterTableStmt.expr && Array.isArray(alterTableStmt.expr)) {
 | 
			
		||||
                alterTableStmt.expr.forEach((expr: AlterTableExprItem) => {
 | 
			
		||||
                    // Handle ALTER COLUMN TYPE
 | 
			
		||||
                    if (expr.action === 'alter' && expr.resource === 'column') {
 | 
			
		||||
                        // Extract column name
 | 
			
		||||
                        let columnName: string | undefined;
 | 
			
		||||
                        if (
 | 
			
		||||
                            typeof expr.column === 'object' &&
 | 
			
		||||
                            'column' in expr.column
 | 
			
		||||
                        ) {
 | 
			
		||||
                            const innerColumn = expr.column.column;
 | 
			
		||||
                            if (
 | 
			
		||||
                                typeof innerColumn === 'object' &&
 | 
			
		||||
                                'expr' in innerColumn &&
 | 
			
		||||
                                innerColumn.expr?.value
 | 
			
		||||
                            ) {
 | 
			
		||||
                                columnName = innerColumn.expr.value;
 | 
			
		||||
                            } else if (typeof innerColumn === 'string') {
 | 
			
		||||
                                columnName = innerColumn;
 | 
			
		||||
                            }
 | 
			
		||||
                        } else if (typeof expr.column === 'string') {
 | 
			
		||||
                            columnName = expr.column;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // Check if it's a TYPE change
 | 
			
		||||
                        if (
 | 
			
		||||
                            columnName &&
 | 
			
		||||
                            expr.type === 'alter' &&
 | 
			
		||||
                            expr.definition?.dataType
 | 
			
		||||
                        ) {
 | 
			
		||||
                            // Find the column in the table and update its type
 | 
			
		||||
                            const column = table.columns.find(
 | 
			
		||||
                                (col) => (col as SQLColumn).name === columnName
 | 
			
		||||
                            );
 | 
			
		||||
                            if (column) {
 | 
			
		||||
                                const definition = expr.definition;
 | 
			
		||||
                                const rawDataType = String(definition.dataType);
 | 
			
		||||
 | 
			
		||||
                                // console.log('ALTER TYPE expr:', JSON.stringify(expr, null, 2));
 | 
			
		||||
 | 
			
		||||
                                // Normalize the type
 | 
			
		||||
                                let normalizedType =
 | 
			
		||||
                                    normalizePostgreSQLType(rawDataType);
 | 
			
		||||
 | 
			
		||||
                                // Handle type parameters
 | 
			
		||||
                                if (
 | 
			
		||||
                                    definition.scale !== undefined &&
 | 
			
		||||
                                    definition.scale !== null
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    // For NUMERIC/DECIMAL with scale, length is actually precision
 | 
			
		||||
                                    const precision =
 | 
			
		||||
                                        definition.length ||
 | 
			
		||||
                                        definition.precision;
 | 
			
		||||
                                    normalizedType = `${normalizedType}(${precision},${definition.scale})`;
 | 
			
		||||
                                } else if (
 | 
			
		||||
                                    definition.length !== undefined &&
 | 
			
		||||
                                    definition.length !== null
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    normalizedType = `${normalizedType}(${definition.length})`;
 | 
			
		||||
                                } else if (definition.precision !== undefined) {
 | 
			
		||||
                                    normalizedType = `${normalizedType}(${definition.precision})`;
 | 
			
		||||
                                } else if (
 | 
			
		||||
                                    definition.suffix &&
 | 
			
		||||
                                    Array.isArray(definition.suffix) &&
 | 
			
		||||
                                    definition.suffix.length > 0
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    const params = definition.suffix
 | 
			
		||||
                                        .map((s: unknown) => {
 | 
			
		||||
                                            if (
 | 
			
		||||
                                                typeof s === 'object' &&
 | 
			
		||||
                                                s !== null &&
 | 
			
		||||
                                                'value' in s
 | 
			
		||||
                                            ) {
 | 
			
		||||
                                                return String(s.value);
 | 
			
		||||
                                            }
 | 
			
		||||
                                            return String(s);
 | 
			
		||||
                                        })
 | 
			
		||||
                                        .join(',');
 | 
			
		||||
                                    normalizedType = `${normalizedType}(${params})`;
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                // Update the column type
 | 
			
		||||
                                (column as SQLColumn).type = normalizedType;
 | 
			
		||||
 | 
			
		||||
                                // Update typeArgs if applicable
 | 
			
		||||
                                if (
 | 
			
		||||
                                    definition.scale !== undefined &&
 | 
			
		||||
                                    definition.scale !== null
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    // For NUMERIC/DECIMAL with scale
 | 
			
		||||
                                    const precision =
 | 
			
		||||
                                        definition.length ||
 | 
			
		||||
                                        definition.precision;
 | 
			
		||||
                                    (column as SQLColumn).typeArgs = {
 | 
			
		||||
                                        precision: precision,
 | 
			
		||||
                                        scale: definition.scale,
 | 
			
		||||
                                    };
 | 
			
		||||
                                } else if (definition.length) {
 | 
			
		||||
                                    (column as SQLColumn).typeArgs = {
 | 
			
		||||
                                        length: definition.length,
 | 
			
		||||
                                    };
 | 
			
		||||
                                } else if (definition.precision) {
 | 
			
		||||
                                    (column as SQLColumn).typeArgs = {
 | 
			
		||||
                                        precision: definition.precision,
 | 
			
		||||
                                    };
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        // Handle ADD COLUMN
 | 
			
		||||
                    } else if (
 | 
			
		||||
                        expr.action === 'add' &&
 | 
			
		||||
                        expr.resource === 'column'
 | 
			
		||||
                    ) {
 | 
			
		||||
                        // Handle ADD COLUMN directly from expr structure
 | 
			
		||||
                        // Extract column name from the nested structure
 | 
			
		||||
                        let columnName: string | undefined;
 | 
			
		||||
                        if (
 | 
			
		||||
                            typeof expr.column === 'object' &&
 | 
			
		||||
                            'column' in expr.column
 | 
			
		||||
                        ) {
 | 
			
		||||
                            const innerColumn = expr.column.column;
 | 
			
		||||
                            if (
 | 
			
		||||
                                typeof innerColumn === 'object' &&
 | 
			
		||||
                                'expr' in innerColumn &&
 | 
			
		||||
                                innerColumn.expr?.value
 | 
			
		||||
                            ) {
 | 
			
		||||
                                columnName = innerColumn.expr.value;
 | 
			
		||||
                            } else if (typeof innerColumn === 'string') {
 | 
			
		||||
                                columnName = innerColumn;
 | 
			
		||||
                            }
 | 
			
		||||
                        } else if (typeof expr.column === 'string') {
 | 
			
		||||
                            columnName = expr.column;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (columnName && typeof columnName === 'string') {
 | 
			
		||||
                            const definition = expr.definition || {};
 | 
			
		||||
                            const rawDataType = String(
 | 
			
		||||
                                definition?.dataType || 'TEXT'
 | 
			
		||||
                            );
 | 
			
		||||
                            // console.log('expr:', JSON.stringify(expr, null, 2));
 | 
			
		||||
 | 
			
		||||
                            // Normalize the type
 | 
			
		||||
                            let normalizedBaseType =
 | 
			
		||||
                                normalizePostgreSQLType(rawDataType);
 | 
			
		||||
 | 
			
		||||
                            // Check if it's a serial type
 | 
			
		||||
                            const upperType = rawDataType.toUpperCase();
 | 
			
		||||
                            const isSerialType = [
 | 
			
		||||
                                'SERIAL',
 | 
			
		||||
                                'SERIAL2',
 | 
			
		||||
                                'SERIAL4',
 | 
			
		||||
                                'SERIAL8',
 | 
			
		||||
                                'BIGSERIAL',
 | 
			
		||||
                                'SMALLSERIAL',
 | 
			
		||||
                            ].includes(upperType.split('(')[0]);
 | 
			
		||||
 | 
			
		||||
                            if (isSerialType) {
 | 
			
		||||
                                const typeLength = definition?.length as
 | 
			
		||||
                                    | number
 | 
			
		||||
                                    | undefined;
 | 
			
		||||
                                if (upperType === 'SERIAL') {
 | 
			
		||||
                                    if (typeLength === 2) {
 | 
			
		||||
                                        normalizedBaseType = 'SMALLINT';
 | 
			
		||||
                                    } else if (typeLength === 8) {
 | 
			
		||||
                                        normalizedBaseType = 'BIGINT';
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        normalizedBaseType = 'INTEGER';
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // Handle type parameters
 | 
			
		||||
                            let finalDataType = normalizedBaseType;
 | 
			
		||||
                            const isNormalizedIntegerType =
 | 
			
		||||
                                ['INTEGER', 'BIGINT', 'SMALLINT'].includes(
 | 
			
		||||
                                    normalizedBaseType
 | 
			
		||||
                                ) &&
 | 
			
		||||
                                (upperType === 'INT' || upperType === 'SERIAL');
 | 
			
		||||
 | 
			
		||||
                            if (!isSerialType && !isNormalizedIntegerType) {
 | 
			
		||||
                                const precision = definition?.precision;
 | 
			
		||||
                                const scale = definition?.scale;
 | 
			
		||||
                                const length = definition?.length;
 | 
			
		||||
                                const suffix =
 | 
			
		||||
                                    (definition?.suffix as unknown[]) || [];
 | 
			
		||||
 | 
			
		||||
                                if (suffix.length > 0) {
 | 
			
		||||
                                    const params = suffix
 | 
			
		||||
                                        .map((s: unknown) => {
 | 
			
		||||
                                            if (
 | 
			
		||||
                                                typeof s === 'object' &&
 | 
			
		||||
                                                s !== null &&
 | 
			
		||||
                                                'value' in s
 | 
			
		||||
                                            ) {
 | 
			
		||||
                                                return String(
 | 
			
		||||
                                                    (s as { value: unknown })
 | 
			
		||||
                                                        .value
 | 
			
		||||
                                                );
 | 
			
		||||
                                            }
 | 
			
		||||
                                            return String(s);
 | 
			
		||||
                                        })
 | 
			
		||||
                                        .join(',');
 | 
			
		||||
                                    finalDataType = `${normalizedBaseType}(${params})`;
 | 
			
		||||
                                } else if (precision !== undefined) {
 | 
			
		||||
                                    if (scale !== undefined) {
 | 
			
		||||
                                        finalDataType = `${normalizedBaseType}(${precision},${scale})`;
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        finalDataType = `${normalizedBaseType}(${precision})`;
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else if (
 | 
			
		||||
                                    length !== undefined &&
 | 
			
		||||
                                    length !== null
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    finalDataType = `${normalizedBaseType}(${length})`;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // Check for nullable constraint
 | 
			
		||||
                            let nullable = true;
 | 
			
		||||
                            if (isSerialType) {
 | 
			
		||||
                                nullable = false;
 | 
			
		||||
                            } else if (
 | 
			
		||||
                                expr.nullable &&
 | 
			
		||||
                                expr.nullable.type === 'not null'
 | 
			
		||||
                            ) {
 | 
			
		||||
                                nullable = false;
 | 
			
		||||
                            } else if (
 | 
			
		||||
                                definition?.nullable &&
 | 
			
		||||
                                definition.nullable.type === 'not null'
 | 
			
		||||
                            ) {
 | 
			
		||||
                                nullable = false;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // Check for unique constraint
 | 
			
		||||
                            const isUnique =
 | 
			
		||||
                                expr.unique === 'unique' ||
 | 
			
		||||
                                definition?.unique === 'unique';
 | 
			
		||||
 | 
			
		||||
                            // Check for default value
 | 
			
		||||
                            let defaultValue: string | undefined;
 | 
			
		||||
                            const defaultVal =
 | 
			
		||||
                                expr.default_val || definition?.default_val;
 | 
			
		||||
                            if (defaultVal && !isSerialType) {
 | 
			
		||||
                                // Create a temporary columnDef to use the getDefaultValueString function
 | 
			
		||||
                                const tempColumnDef = {
 | 
			
		||||
                                    default_val: defaultVal,
 | 
			
		||||
                                } as ColumnDefinition;
 | 
			
		||||
                                defaultValue =
 | 
			
		||||
                                    getDefaultValueString(tempColumnDef);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // Create the new column object
 | 
			
		||||
                            const newColumn: SQLColumn = {
 | 
			
		||||
                                name: columnName,
 | 
			
		||||
                                type: finalDataType,
 | 
			
		||||
                                nullable: nullable,
 | 
			
		||||
                                primaryKey:
 | 
			
		||||
                                    definition?.primary_key === 'primary key' ||
 | 
			
		||||
                                    definition?.constraint === 'primary key' ||
 | 
			
		||||
                                    isSerialType,
 | 
			
		||||
                                unique: isUnique,
 | 
			
		||||
                                default: defaultValue,
 | 
			
		||||
                                increment:
 | 
			
		||||
                                    isSerialType ||
 | 
			
		||||
                                    definition?.auto_increment ===
 | 
			
		||||
                                        'auto_increment' ||
 | 
			
		||||
                                    (stmt.sql
 | 
			
		||||
                                        .toUpperCase()
 | 
			
		||||
                                        .includes('GENERATED') &&
 | 
			
		||||
                                        stmt.sql
 | 
			
		||||
                                            .toUpperCase()
 | 
			
		||||
                                            .includes('IDENTITY')),
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
                            // Add the column to the table if it doesn't already exist
 | 
			
		||||
                            const tableColumns = table.columns as SQLColumn[];
 | 
			
		||||
                            if (
 | 
			
		||||
                                !tableColumns.some(
 | 
			
		||||
                                    (col) => col.name === columnName
 | 
			
		||||
                                )
 | 
			
		||||
                            ) {
 | 
			
		||||
                                tableColumns.push(newColumn);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (
 | 
			
		||||
                        expr.action === 'add' &&
 | 
			
		||||
                        expr.create_definitions
 | 
			
		||||
                    ) {
 | 
			
		||||
                    if (expr.action === 'add' && expr.create_definitions) {
 | 
			
		||||
                        const createDefs = expr.create_definitions;
 | 
			
		||||
 | 
			
		||||
                        // Check if it's adding a column (legacy structure)
 | 
			
		||||
                        if (createDefs.resource === 'column') {
 | 
			
		||||
                            const columnDef =
 | 
			
		||||
                                createDefs as unknown as ColumnDefinition;
 | 
			
		||||
                            const columnName = extractColumnName(
 | 
			
		||||
                                columnDef.column
 | 
			
		||||
                            );
 | 
			
		||||
 | 
			
		||||
                            if (columnName) {
 | 
			
		||||
                                // Extract the column type and properties
 | 
			
		||||
                                const definition =
 | 
			
		||||
                                    columnDef.definition as Record<
 | 
			
		||||
                                        string,
 | 
			
		||||
                                        unknown
 | 
			
		||||
                                    >;
 | 
			
		||||
                                const rawDataType = String(
 | 
			
		||||
                                    definition?.dataType || 'TEXT'
 | 
			
		||||
                                );
 | 
			
		||||
 | 
			
		||||
                                // Normalize the type
 | 
			
		||||
                                let normalizedBaseType =
 | 
			
		||||
                                    normalizePostgreSQLType(rawDataType);
 | 
			
		||||
 | 
			
		||||
                                // Check if it's a serial type
 | 
			
		||||
                                const upperType = rawDataType.toUpperCase();
 | 
			
		||||
                                const isSerialType = [
 | 
			
		||||
                                    'SERIAL',
 | 
			
		||||
                                    'SERIAL2',
 | 
			
		||||
                                    'SERIAL4',
 | 
			
		||||
                                    'SERIAL8',
 | 
			
		||||
                                    'BIGSERIAL',
 | 
			
		||||
                                    'SMALLSERIAL',
 | 
			
		||||
                                ].includes(upperType.split('(')[0]);
 | 
			
		||||
 | 
			
		||||
                                if (isSerialType) {
 | 
			
		||||
                                    const typeLength = definition?.length as
 | 
			
		||||
                                        | number
 | 
			
		||||
                                        | undefined;
 | 
			
		||||
                                    if (upperType === 'SERIAL') {
 | 
			
		||||
                                        if (typeLength === 2) {
 | 
			
		||||
                                            normalizedBaseType = 'SMALLINT';
 | 
			
		||||
                                        } else if (typeLength === 8) {
 | 
			
		||||
                                            normalizedBaseType = 'BIGINT';
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            normalizedBaseType = 'INTEGER';
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                // Handle type parameters
 | 
			
		||||
                                let finalDataType = normalizedBaseType;
 | 
			
		||||
                                const isNormalizedIntegerType =
 | 
			
		||||
                                    ['INTEGER', 'BIGINT', 'SMALLINT'].includes(
 | 
			
		||||
                                        normalizedBaseType
 | 
			
		||||
                                    ) &&
 | 
			
		||||
                                    (upperType === 'INT' ||
 | 
			
		||||
                                        upperType === 'SERIAL');
 | 
			
		||||
 | 
			
		||||
                                if (!isSerialType && !isNormalizedIntegerType) {
 | 
			
		||||
                                    const precision =
 | 
			
		||||
                                        columnDef.definition?.precision;
 | 
			
		||||
                                    const scale = columnDef.definition?.scale;
 | 
			
		||||
                                    const length = columnDef.definition?.length;
 | 
			
		||||
                                    const suffix =
 | 
			
		||||
                                        (definition?.suffix as unknown[]) || [];
 | 
			
		||||
 | 
			
		||||
                                    if (suffix.length > 0) {
 | 
			
		||||
                                        const params = suffix
 | 
			
		||||
                                            .map((s: unknown) => {
 | 
			
		||||
                                                if (
 | 
			
		||||
                                                    typeof s === 'object' &&
 | 
			
		||||
                                                    s !== null &&
 | 
			
		||||
                                                    'value' in s
 | 
			
		||||
                                                ) {
 | 
			
		||||
                                                    return String(
 | 
			
		||||
                                                        (
 | 
			
		||||
                                                            s as {
 | 
			
		||||
                                                                value: unknown;
 | 
			
		||||
                                                            }
 | 
			
		||||
                                                        ).value
 | 
			
		||||
                                                    );
 | 
			
		||||
                                                }
 | 
			
		||||
                                                return String(s);
 | 
			
		||||
                                            })
 | 
			
		||||
                                            .join(',');
 | 
			
		||||
                                        finalDataType = `${normalizedBaseType}(${params})`;
 | 
			
		||||
                                    } else if (precision !== undefined) {
 | 
			
		||||
                                        if (scale !== undefined) {
 | 
			
		||||
                                            finalDataType = `${normalizedBaseType}(${precision},${scale})`;
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            finalDataType = `${normalizedBaseType}(${precision})`;
 | 
			
		||||
                                        }
 | 
			
		||||
                                    } else if (
 | 
			
		||||
                                        length !== undefined &&
 | 
			
		||||
                                        length !== null
 | 
			
		||||
                                    ) {
 | 
			
		||||
                                        finalDataType = `${normalizedBaseType}(${length})`;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                // Create the new column object
 | 
			
		||||
                                const newColumn: SQLColumn = {
 | 
			
		||||
                                    name: columnName,
 | 
			
		||||
                                    type: finalDataType,
 | 
			
		||||
                                    nullable: isSerialType
 | 
			
		||||
                                        ? false
 | 
			
		||||
                                        : columnDef.nullable?.type !==
 | 
			
		||||
                                          'not null',
 | 
			
		||||
                                    primaryKey:
 | 
			
		||||
                                        columnDef.primary_key ===
 | 
			
		||||
                                            'primary key' ||
 | 
			
		||||
                                        columnDef.definition?.constraint ===
 | 
			
		||||
                                            'primary key' ||
 | 
			
		||||
                                        isSerialType,
 | 
			
		||||
                                    unique: columnDef.unique === 'unique',
 | 
			
		||||
                                    typeArgs: getTypeArgs(columnDef.definition),
 | 
			
		||||
                                    default: isSerialType
 | 
			
		||||
                                        ? undefined
 | 
			
		||||
                                        : getDefaultValueString(columnDef),
 | 
			
		||||
                                    increment:
 | 
			
		||||
                                        isSerialType ||
 | 
			
		||||
                                        columnDef.auto_increment ===
 | 
			
		||||
                                            'auto_increment' ||
 | 
			
		||||
                                        (stmt.sql
 | 
			
		||||
                                            .toUpperCase()
 | 
			
		||||
                                            .includes('GENERATED') &&
 | 
			
		||||
                                            stmt.sql
 | 
			
		||||
                                                .toUpperCase()
 | 
			
		||||
                                                .includes('IDENTITY')),
 | 
			
		||||
                                };
 | 
			
		||||
 | 
			
		||||
                                // Add the column to the table if it doesn't already exist
 | 
			
		||||
                                const tableColumns2 =
 | 
			
		||||
                                    table.columns as SQLColumn[];
 | 
			
		||||
                                if (
 | 
			
		||||
                                    !tableColumns2.some(
 | 
			
		||||
                                        (col) => col.name === columnName
 | 
			
		||||
                                    )
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    tableColumns2.push(newColumn);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        } else if (
 | 
			
		||||
                        if (
 | 
			
		||||
                            createDefs.constraint_type === 'FOREIGN KEY' ||
 | 
			
		||||
                            createDefs.constraint_type === 'foreign key'
 | 
			
		||||
                        ) {
 | 
			
		||||
@@ -1637,188 +1129,19 @@ export async function fromPostgres(
 | 
			
		||||
            }
 | 
			
		||||
        } else if (stmt.type === 'alter' && !stmt.parsed) {
 | 
			
		||||
            // Handle ALTER TABLE statements that failed to parse
 | 
			
		||||
 | 
			
		||||
            // First try to extract ALTER COLUMN TYPE statements
 | 
			
		||||
            const alterTypeMatch = stmt.sql.match(
 | 
			
		||||
                /ALTER\s+TABLE\s+(?:ONLY\s+)?(?:(?:"([^"]+)"|([^"\s.]+))\.)?(?:"([^"]+)"|([^"\s.(]+))\s+ALTER\s+COLUMN\s+(?:"([^"]+)"|([^"\s]+))\s+TYPE\s+([\w_]+(?:\([^)]*\))?(?:\[\])?)/i
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (alterTypeMatch) {
 | 
			
		||||
                const schemaName =
 | 
			
		||||
                    alterTypeMatch[1] || alterTypeMatch[2] || 'public';
 | 
			
		||||
                const tableName = alterTypeMatch[3] || alterTypeMatch[4];
 | 
			
		||||
                const columnName = alterTypeMatch[5] || alterTypeMatch[6];
 | 
			
		||||
                let columnType = alterTypeMatch[7];
 | 
			
		||||
 | 
			
		||||
                const table = findTableWithSchemaSupport(
 | 
			
		||||
                    tables,
 | 
			
		||||
                    tableName,
 | 
			
		||||
                    schemaName
 | 
			
		||||
                );
 | 
			
		||||
                if (table && columnName) {
 | 
			
		||||
                    const column = (table.columns as SQLColumn[]).find(
 | 
			
		||||
                        (col) => col.name === columnName
 | 
			
		||||
                    );
 | 
			
		||||
                    if (column) {
 | 
			
		||||
                        // Normalize and update the type
 | 
			
		||||
                        columnType = normalizePostgreSQLType(columnType);
 | 
			
		||||
                        column.type = columnType;
 | 
			
		||||
 | 
			
		||||
                        // Extract and update typeArgs if present
 | 
			
		||||
                        const typeMatch = columnType.match(
 | 
			
		||||
                            /^(\w+)(?:\(([^)]+)\))?$/
 | 
			
		||||
                        );
 | 
			
		||||
                        if (typeMatch && typeMatch[2]) {
 | 
			
		||||
                            const params = typeMatch[2]
 | 
			
		||||
                                .split(',')
 | 
			
		||||
                                .map((p) => p.trim());
 | 
			
		||||
                            if (params.length === 1) {
 | 
			
		||||
                                column.typeArgs = {
 | 
			
		||||
                                    length: parseInt(params[0]),
 | 
			
		||||
                                };
 | 
			
		||||
                            } else if (params.length === 2) {
 | 
			
		||||
                                column.typeArgs = {
 | 
			
		||||
                                    precision: parseInt(params[0]),
 | 
			
		||||
                                    scale: parseInt(params[1]),
 | 
			
		||||
                                };
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Then try to extract ADD COLUMN statements
 | 
			
		||||
            const alterColumnMatch = stmt.sql.match(
 | 
			
		||||
                /ALTER\s+TABLE\s+(?:ONLY\s+)?(?:(?:"([^"]+)"|([^"\s.]+))\.)?(?:"([^"]+)"|([^"\s.(]+))\s+ADD\s+COLUMN\s+(?:"([^"]+)"|([^"\s]+))\s+([\w_]+(?:\([^)]*\))?(?:\[\])?)/i
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (alterColumnMatch) {
 | 
			
		||||
                const schemaName =
 | 
			
		||||
                    alterColumnMatch[1] || alterColumnMatch[2] || 'public';
 | 
			
		||||
                const tableName = alterColumnMatch[3] || alterColumnMatch[4];
 | 
			
		||||
                const columnName = alterColumnMatch[5] || alterColumnMatch[6];
 | 
			
		||||
                let columnType = alterColumnMatch[7];
 | 
			
		||||
 | 
			
		||||
                const table = findTableWithSchemaSupport(
 | 
			
		||||
                    tables,
 | 
			
		||||
                    tableName,
 | 
			
		||||
                    schemaName
 | 
			
		||||
                );
 | 
			
		||||
                if (table && columnName) {
 | 
			
		||||
                    const tableColumns = table.columns as SQLColumn[];
 | 
			
		||||
                    if (!tableColumns.some((col) => col.name === columnName)) {
 | 
			
		||||
                        // Normalize the type
 | 
			
		||||
                        columnType = normalizePostgreSQLType(columnType);
 | 
			
		||||
 | 
			
		||||
                        // Check for constraints in the statement
 | 
			
		||||
                        const columnDefPart = stmt.sql.substring(
 | 
			
		||||
                            stmt.sql.indexOf(columnName)
 | 
			
		||||
                        );
 | 
			
		||||
                        const isPrimary =
 | 
			
		||||
                            columnDefPart.match(/PRIMARY\s+KEY/i) !== null;
 | 
			
		||||
                        const isNotNull =
 | 
			
		||||
                            columnDefPart.match(/NOT\s+NULL/i) !== null;
 | 
			
		||||
                        const isUnique =
 | 
			
		||||
                            columnDefPart.match(/\bUNIQUE\b/i) !== null;
 | 
			
		||||
                        // Extract default value
 | 
			
		||||
                        let defaultValue: string | undefined;
 | 
			
		||||
                        // Updated regex to handle casting with :: operator
 | 
			
		||||
                        const defaultMatch = columnDefPart.match(
 | 
			
		||||
                            /DEFAULT\s+((?:'[^']*'|"[^"]*"|\S+)(?:::\w+)?)/i
 | 
			
		||||
                        );
 | 
			
		||||
                        if (defaultMatch) {
 | 
			
		||||
                            let defVal = defaultMatch[1].trim();
 | 
			
		||||
                            // Remove trailing comma or semicolon if present
 | 
			
		||||
                            defVal = defVal.replace(/[,;]$/, '').trim();
 | 
			
		||||
                            // Handle string literals
 | 
			
		||||
                            if (
 | 
			
		||||
                                defVal.startsWith("'") &&
 | 
			
		||||
                                defVal.endsWith("'")
 | 
			
		||||
                            ) {
 | 
			
		||||
                                // Keep the quotes for string literals
 | 
			
		||||
                                defaultValue = defVal;
 | 
			
		||||
                            } else if (defVal.match(/^\d+(\.\d+)?$/)) {
 | 
			
		||||
                                // Numeric value
 | 
			
		||||
                                defaultValue = defVal;
 | 
			
		||||
                            } else if (
 | 
			
		||||
                                defVal.toUpperCase() === 'TRUE' ||
 | 
			
		||||
                                defVal.toUpperCase() === 'FALSE'
 | 
			
		||||
                            ) {
 | 
			
		||||
                                // Boolean value
 | 
			
		||||
                                defaultValue = defVal.toUpperCase();
 | 
			
		||||
                            } else if (defVal.toUpperCase() === 'NULL') {
 | 
			
		||||
                                // NULL value
 | 
			
		||||
                                defaultValue = 'NULL';
 | 
			
		||||
                            } else if (
 | 
			
		||||
                                defVal.includes('(') &&
 | 
			
		||||
                                defVal.includes(')')
 | 
			
		||||
                            ) {
 | 
			
		||||
                                // Function call
 | 
			
		||||
                                // Normalize PostgreSQL function names to uppercase
 | 
			
		||||
                                const funcMatch = defVal.match(/^(\w+)\(/);
 | 
			
		||||
                                if (funcMatch) {
 | 
			
		||||
                                    const funcName = funcMatch[1];
 | 
			
		||||
                                    const pgFunctions = [
 | 
			
		||||
                                        'now',
 | 
			
		||||
                                        'current_timestamp',
 | 
			
		||||
                                        'current_date',
 | 
			
		||||
                                        'current_time',
 | 
			
		||||
                                        'gen_random_uuid',
 | 
			
		||||
                                        'random',
 | 
			
		||||
                                        'nextval',
 | 
			
		||||
                                        'currval',
 | 
			
		||||
                                    ];
 | 
			
		||||
                                    if (
 | 
			
		||||
                                        pgFunctions.includes(
 | 
			
		||||
                                            funcName.toLowerCase()
 | 
			
		||||
                                        )
 | 
			
		||||
                                    ) {
 | 
			
		||||
                                        defaultValue = defVal.replace(
 | 
			
		||||
                                            funcName,
 | 
			
		||||
                                            funcName.toUpperCase()
 | 
			
		||||
                                        );
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        defaultValue = defVal;
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    defaultValue = defVal;
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                // Other expressions
 | 
			
		||||
                                defaultValue = defVal;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        tableColumns.push({
 | 
			
		||||
                            name: columnName,
 | 
			
		||||
                            type: columnType,
 | 
			
		||||
                            nullable: !isNotNull && !isPrimary,
 | 
			
		||||
                            primaryKey: isPrimary,
 | 
			
		||||
                            unique: isUnique || isPrimary,
 | 
			
		||||
                            default: defaultValue,
 | 
			
		||||
                            increment: false,
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Extract foreign keys using regex as fallback
 | 
			
		||||
            // Updated regex to handle quoted identifiers properly
 | 
			
		||||
            const alterFKMatch = stmt.sql.match(
 | 
			
		||||
                /ALTER\s+TABLE\s+(?:ONLY\s+)?(?:(?:"([^"]+)"|([^"\s.]+))\.)?(?:"([^"]+)"|([^"\s.(]+))\s+ADD\s+CONSTRAINT\s+(?:"([^"]+)"|([^"\s]+))\s+FOREIGN\s+KEY\s*\((?:"([^"]+)"|([^"\s)]+))\)\s+REFERENCES\s+(?:(?:"([^"]+)"|([^"\s.]+))\.)?(?:"([^"]+)"|([^"\s.(]+))\s*\((?:"([^"]+)"|([^"\s)]+))\)/i
 | 
			
		||||
                /ALTER\s+TABLE\s+(?:ONLY\s+)?(?:"?([^"\s.]+)"?\.)?["']?([^"'\s.(]+)["']?\s+ADD\s+CONSTRAINT\s+["']?([^"'\s]+)["']?\s+FOREIGN\s+KEY\s*\(["']?([^"'\s)]+)["']?\)\s+REFERENCES\s+(?:"?([^"\s.]+)"?\.)?["']?([^"'\s.(]+)["']?\s*\(["']?([^"'\s)]+)["']?\)/i
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (alterFKMatch) {
 | 
			
		||||
                // Extract values from appropriate match groups
 | 
			
		||||
                const sourceSchema =
 | 
			
		||||
                    alterFKMatch[1] || alterFKMatch[2] || 'public';
 | 
			
		||||
                const sourceTable = alterFKMatch[3] || alterFKMatch[4];
 | 
			
		||||
                const constraintName = alterFKMatch[5] || alterFKMatch[6];
 | 
			
		||||
                const sourceColumn = alterFKMatch[7] || alterFKMatch[8];
 | 
			
		||||
                const targetSchema =
 | 
			
		||||
                    alterFKMatch[9] || alterFKMatch[10] || 'public';
 | 
			
		||||
                const targetTable = alterFKMatch[11] || alterFKMatch[12];
 | 
			
		||||
                const targetColumn = alterFKMatch[13] || alterFKMatch[14];
 | 
			
		||||
                const sourceSchema = alterFKMatch[1] || 'public';
 | 
			
		||||
                const sourceTable = alterFKMatch[2];
 | 
			
		||||
                const constraintName = alterFKMatch[3];
 | 
			
		||||
                const sourceColumn = alterFKMatch[4];
 | 
			
		||||
                const targetSchema = alterFKMatch[5] || 'public';
 | 
			
		||||
                const targetTable = alterFKMatch[6];
 | 
			
		||||
                const targetColumn = alterFKMatch[7];
 | 
			
		||||
 | 
			
		||||
                const sourceTableId = getTableIdWithSchemaSupport(
 | 
			
		||||
                    tableMap,
 | 
			
		||||
@@ -1952,10 +1275,58 @@ export async function fromPostgres(
 | 
			
		||||
function getDefaultValueString(
 | 
			
		||||
    columnDef: ColumnDefinition
 | 
			
		||||
): string | undefined {
 | 
			
		||||
    const defVal = columnDef.default_val;
 | 
			
		||||
    let defVal = columnDef.default_val;
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        defVal &&
 | 
			
		||||
        typeof defVal === 'object' &&
 | 
			
		||||
        defVal.type === 'default' &&
 | 
			
		||||
        'value' in defVal
 | 
			
		||||
    ) {
 | 
			
		||||
        defVal = defVal.value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (defVal === undefined || defVal === null) return undefined;
 | 
			
		||||
 | 
			
		||||
    // Use buildSQLFromAST to reconstruct the default value
 | 
			
		||||
    return buildSQLFromAST(defVal, DatabaseType.POSTGRESQL);
 | 
			
		||||
    let value: string | undefined;
 | 
			
		||||
 | 
			
		||||
    switch (typeof defVal) {
 | 
			
		||||
        case 'string':
 | 
			
		||||
            value = defVal;
 | 
			
		||||
            break;
 | 
			
		||||
        case 'number':
 | 
			
		||||
            value = String(defVal);
 | 
			
		||||
            break;
 | 
			
		||||
        case 'boolean':
 | 
			
		||||
            value = defVal ? 'TRUE' : 'FALSE';
 | 
			
		||||
            break;
 | 
			
		||||
        case 'object':
 | 
			
		||||
            if ('value' in defVal && typeof defVal.value === 'string') {
 | 
			
		||||
                value = defVal.value;
 | 
			
		||||
            } else if ('raw' in defVal && typeof defVal.raw === 'string') {
 | 
			
		||||
                value = defVal.raw;
 | 
			
		||||
            } else if (defVal.type === 'bool') {
 | 
			
		||||
                value = defVal.value ? 'TRUE' : 'FALSE';
 | 
			
		||||
            } else if (defVal.type === 'function' && defVal.name) {
 | 
			
		||||
                const fnName = defVal.name;
 | 
			
		||||
                if (
 | 
			
		||||
                    fnName &&
 | 
			
		||||
                    typeof fnName === 'object' &&
 | 
			
		||||
                    Array.isArray(fnName.name) &&
 | 
			
		||||
                    fnName.name.length > 0 &&
 | 
			
		||||
                    fnName.name[0].value
 | 
			
		||||
                ) {
 | 
			
		||||
                    value = fnName.name[0].value.toUpperCase();
 | 
			
		||||
                } else if (typeof fnName === 'string') {
 | 
			
		||||
                    value = fnName.toUpperCase();
 | 
			
		||||
                } else {
 | 
			
		||||
                    value = 'UNKNOWN_FUNCTION';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            value = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return value;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,252 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { fromSQLServer } from '../sqlserver';
 | 
			
		||||
 | 
			
		||||
describe('SQL Server Default Value Import', () => {
 | 
			
		||||
    describe('String Default Values', () => {
 | 
			
		||||
        it('should parse simple string defaults with single quotes', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE kingdom_citizens (
 | 
			
		||||
                    citizen_id INT NOT NULL,
 | 
			
		||||
                    allegiance NVARCHAR(50) DEFAULT 'neutral',
 | 
			
		||||
                    PRIMARY KEY (citizen_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromSQLServer(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const allegianceColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'allegiance'
 | 
			
		||||
            );
 | 
			
		||||
            expect(allegianceColumn?.default).toBe("'neutral'");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should parse string defaults with Unicode prefix', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE ancient_scrolls (
 | 
			
		||||
                    scroll_id INT NOT NULL,
 | 
			
		||||
                    runic_inscription NVARCHAR(255) DEFAULT N'Ancient wisdom',
 | 
			
		||||
                    prophecy NVARCHAR(MAX) DEFAULT N'The chosen one shall rise',
 | 
			
		||||
                    PRIMARY KEY (scroll_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromSQLServer(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const runicColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'runic_inscription'
 | 
			
		||||
            );
 | 
			
		||||
            expect(runicColumn?.default).toBe("N'Ancient wisdom'");
 | 
			
		||||
            const prophecyColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'prophecy'
 | 
			
		||||
            );
 | 
			
		||||
            expect(prophecyColumn?.default).toBe(
 | 
			
		||||
                "N'The chosen one shall rise'"
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Numeric Default Values', () => {
 | 
			
		||||
        it('should parse integer defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE castle_treasury (
 | 
			
		||||
                    treasury_id INT NOT NULL,
 | 
			
		||||
                    gold_count INT DEFAULT 0,
 | 
			
		||||
                    max_capacity BIGINT DEFAULT 100000,
 | 
			
		||||
                    guard_posts SMALLINT DEFAULT 5,
 | 
			
		||||
                    PRIMARY KEY (treasury_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromSQLServer(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const goldColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'gold_count'
 | 
			
		||||
            );
 | 
			
		||||
            expect(goldColumn?.default).toBe('0');
 | 
			
		||||
            const capacityColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'max_capacity'
 | 
			
		||||
            );
 | 
			
		||||
            expect(capacityColumn?.default).toBe('100000');
 | 
			
		||||
            const guardColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'guard_posts'
 | 
			
		||||
            );
 | 
			
		||||
            expect(guardColumn?.default).toBe('5');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should parse decimal defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE blacksmith_shop (
 | 
			
		||||
                    item_id INT NOT NULL,
 | 
			
		||||
                    weapon_price DECIMAL(10, 2) DEFAULT 99.99,
 | 
			
		||||
                    guild_discount FLOAT DEFAULT 0.15,
 | 
			
		||||
                    enchantment_tax NUMERIC(5, 4) DEFAULT 0.0825,
 | 
			
		||||
                    PRIMARY KEY (item_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromSQLServer(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const priceColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'weapon_price'
 | 
			
		||||
            );
 | 
			
		||||
            expect(priceColumn?.default).toBe('99.99');
 | 
			
		||||
            const discountColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'guild_discount'
 | 
			
		||||
            );
 | 
			
		||||
            expect(discountColumn?.default).toBe('0.15');
 | 
			
		||||
            const taxColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'enchantment_tax'
 | 
			
		||||
            );
 | 
			
		||||
            expect(taxColumn?.default).toBe('0.0825');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Boolean Default Values', () => {
 | 
			
		||||
        it('should parse BIT defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE magic_barriers (
 | 
			
		||||
                    barrier_id INT NOT NULL,
 | 
			
		||||
                    is_active BIT DEFAULT 1,
 | 
			
		||||
                    is_breached BIT DEFAULT 0,
 | 
			
		||||
                    PRIMARY KEY (barrier_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromSQLServer(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const activeColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'is_active'
 | 
			
		||||
            );
 | 
			
		||||
            expect(activeColumn?.default).toBe('1');
 | 
			
		||||
            const breachedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'is_breached'
 | 
			
		||||
            );
 | 
			
		||||
            expect(breachedColumn?.default).toBe('0');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Date and Time Default Values', () => {
 | 
			
		||||
        it('should parse date/time function defaults', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE battle_logs (
 | 
			
		||||
                    battle_id INT NOT NULL,
 | 
			
		||||
                    battle_started DATETIME DEFAULT GETDATE(),
 | 
			
		||||
                    last_action DATETIME2 DEFAULT SYSDATETIME(),
 | 
			
		||||
                    battle_date DATE DEFAULT GETDATE(),
 | 
			
		||||
                    PRIMARY KEY (battle_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromSQLServer(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const startedColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'battle_started'
 | 
			
		||||
            );
 | 
			
		||||
            expect(startedColumn?.default).toBe('GETDATE()');
 | 
			
		||||
            const actionColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'last_action'
 | 
			
		||||
            );
 | 
			
		||||
            expect(actionColumn?.default).toBe('SYSDATETIME()');
 | 
			
		||||
            const dateColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'battle_date'
 | 
			
		||||
            );
 | 
			
		||||
            expect(dateColumn?.default).toBe('GETDATE()');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('IDENTITY columns', () => {
 | 
			
		||||
        it('should handle IDENTITY columns correctly', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE legendary_weapons (
 | 
			
		||||
                    weapon_id INT IDENTITY(1,1) NOT NULL,
 | 
			
		||||
                    legacy_id BIGINT IDENTITY(100,10) NOT NULL,
 | 
			
		||||
                    weapon_name NVARCHAR(100),
 | 
			
		||||
                    PRIMARY KEY (weapon_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromSQLServer(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const weaponColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'weapon_id'
 | 
			
		||||
            );
 | 
			
		||||
            expect(weaponColumn?.increment).toBe(true);
 | 
			
		||||
            const legacyColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'legacy_id'
 | 
			
		||||
            );
 | 
			
		||||
            expect(legacyColumn?.increment).toBe(true);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Complex Real-World Example with Schema', () => {
 | 
			
		||||
        it('should handle complex table with schema and multiple default types', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE [dbo].[QuestContracts] (
 | 
			
		||||
                    [ContractID] INT IDENTITY(1,1) NOT NULL,
 | 
			
		||||
                    [AdventurerID] INT NOT NULL,
 | 
			
		||||
                    [QuestDate] DATETIME DEFAULT GETDATE(),
 | 
			
		||||
                    [QuestStatus] NVARCHAR(20) DEFAULT N'Available',
 | 
			
		||||
                    [RewardAmount] DECIMAL(10, 2) DEFAULT 0.00,
 | 
			
		||||
                    [IsCompleted] BIT DEFAULT 0,
 | 
			
		||||
                    [CompletedDate] DATETIME NULL,
 | 
			
		||||
                    [QuestNotes] NVARCHAR(MAX) DEFAULT NULL,
 | 
			
		||||
                    [DifficultyLevel] INT DEFAULT 5,
 | 
			
		||||
                    [QuestGuid] UNIQUEIDENTIFIER DEFAULT NEWID(),
 | 
			
		||||
                    PRIMARY KEY ([ContractID])
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            const result = await fromSQLServer(sql);
 | 
			
		||||
            const table = result.tables[0];
 | 
			
		||||
            expect(table).toBeDefined();
 | 
			
		||||
            expect(table.schema).toBe('dbo');
 | 
			
		||||
 | 
			
		||||
            // Check various default values
 | 
			
		||||
            const questDateColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'QuestDate'
 | 
			
		||||
            );
 | 
			
		||||
            expect(questDateColumn?.default).toBe('GETDATE()');
 | 
			
		||||
 | 
			
		||||
            const statusColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'QuestStatus'
 | 
			
		||||
            );
 | 
			
		||||
            expect(statusColumn?.default).toBe("N'Available'");
 | 
			
		||||
 | 
			
		||||
            const rewardColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'RewardAmount'
 | 
			
		||||
            );
 | 
			
		||||
            expect(rewardColumn?.default).toBe('0.00');
 | 
			
		||||
 | 
			
		||||
            const completedColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'IsCompleted'
 | 
			
		||||
            );
 | 
			
		||||
            expect(completedColumn?.default).toBe('0');
 | 
			
		||||
 | 
			
		||||
            const difficultyColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'DifficultyLevel'
 | 
			
		||||
            );
 | 
			
		||||
            expect(difficultyColumn?.default).toBe('5');
 | 
			
		||||
 | 
			
		||||
            const guidColumn = table.columns.find(
 | 
			
		||||
                (c) => c.name === 'QuestGuid'
 | 
			
		||||
            );
 | 
			
		||||
            expect(guidColumn?.default).toBe('NEWID()');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Expressions in defaults', () => {
 | 
			
		||||
        it('should handle parentheses in default expressions', async () => {
 | 
			
		||||
            const sql = `
 | 
			
		||||
                CREATE TABLE spell_calculations (
 | 
			
		||||
                    calculation_id INT NOT NULL,
 | 
			
		||||
                    base_damage INT DEFAULT (10 + 5),
 | 
			
		||||
                    total_power DECIMAL(10,2) DEFAULT ((100.0 * 0.15) + 10),
 | 
			
		||||
                    PRIMARY KEY (calculation_id)
 | 
			
		||||
                );
 | 
			
		||||
            `;
 | 
			
		||||
            const result = await fromSQLServer(sql);
 | 
			
		||||
            expect(result.tables).toHaveLength(1);
 | 
			
		||||
            const damageColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'base_damage'
 | 
			
		||||
            );
 | 
			
		||||
            expect(damageColumn?.default).toBe('(10 + 5)');
 | 
			
		||||
            const powerColumn = result.tables[0].columns.find(
 | 
			
		||||
                (c) => c.name === 'total_power'
 | 
			
		||||
            );
 | 
			
		||||
            expect(powerColumn?.default).toBe('((100.0 * 0.15) + 10)');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,91 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { fromSQLServer } from '../sqlserver';
 | 
			
		||||
 | 
			
		||||
describe('SQL Server Complex Fantasy Case', () => {
 | 
			
		||||
    it('should parse complex SQL with SpellDefinition and SpellComponent tables', async () => {
 | 
			
		||||
        // Complex SQL with same structure as user's case but fantasy-themed
 | 
			
		||||
        const sql = `CREATE TABLE [DBO].[SpellDefinition](
 | 
			
		||||
  [SPELLID]  (VARCHAR)(32),    
 | 
			
		||||
  [HASVERBALCOMP] BOOLEAN,  
 | 
			
		||||
  [INCANTATION] [VARCHAR](128),  
 | 
			
		||||
  [INCANTATIONFIX] BOOLEAN,  
 | 
			
		||||
  [ITSCOMPONENTREL]  [VARCHAR](32), FOREIGN KEY (itscomponentrel) REFERENCES SpellComponent(SPELLID), 
 | 
			
		||||
  [SHOWVISUALS] BOOLEAN,    ) ON [PRIMARY]
 | 
			
		||||
 | 
			
		||||
CREATE TABLE [DBO].[SpellComponent](
 | 
			
		||||
  [ALIAS] CHAR (50),    
 | 
			
		||||
  [SPELLID]  (VARCHAR)(32),    
 | 
			
		||||
  [ISOPTIONAL] BOOLEAN,  
 | 
			
		||||
  [ITSPARENTCOMP]  [VARCHAR](32), FOREIGN KEY (itsparentcomp) REFERENCES SpellComponent(SPELLID), 
 | 
			
		||||
  [ITSSCHOOLMETA]  [VARCHAR](32), FOREIGN KEY (itsschoolmeta) REFERENCES MagicSchool(SCHOOLID), 
 | 
			
		||||
  [KEYATTR] CHAR (100),  ) ON [PRIMARY]`;
 | 
			
		||||
 | 
			
		||||
        console.log('Testing complex fantasy SQL...');
 | 
			
		||||
        console.log(
 | 
			
		||||
            'Number of CREATE TABLE statements:',
 | 
			
		||||
            (sql.match(/CREATE\s+TABLE/gi) || []).length
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const result = await fromSQLServer(sql);
 | 
			
		||||
 | 
			
		||||
        console.log(
 | 
			
		||||
            'Result tables:',
 | 
			
		||||
            result.tables.map((t) => t.name)
 | 
			
		||||
        );
 | 
			
		||||
        console.log('Result relationships:', result.relationships.length);
 | 
			
		||||
 | 
			
		||||
        // Debug: Show actual relationships
 | 
			
		||||
        if (result.relationships.length === 0) {
 | 
			
		||||
            console.log('WARNING: No relationships found!');
 | 
			
		||||
        } else {
 | 
			
		||||
            console.log('Relationships found:');
 | 
			
		||||
            result.relationships.forEach((r) => {
 | 
			
		||||
                console.log(
 | 
			
		||||
                    `  ${r.sourceTable}.${r.sourceColumn} -> ${r.targetTable}.${r.targetColumn}`
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Should create TWO tables
 | 
			
		||||
        expect(result.tables).toHaveLength(2);
 | 
			
		||||
 | 
			
		||||
        // Check first table
 | 
			
		||||
        const spellDef = result.tables.find(
 | 
			
		||||
            (t) => t.name === 'SpellDefinition'
 | 
			
		||||
        );
 | 
			
		||||
        expect(spellDef).toBeDefined();
 | 
			
		||||
        expect(spellDef?.schema).toBe('DBO');
 | 
			
		||||
        expect(spellDef?.columns).toHaveLength(6);
 | 
			
		||||
 | 
			
		||||
        // Check second table
 | 
			
		||||
        const spellComp = result.tables.find(
 | 
			
		||||
            (t) => t.name === 'SpellComponent'
 | 
			
		||||
        );
 | 
			
		||||
        expect(spellComp).toBeDefined();
 | 
			
		||||
        expect(spellComp?.schema).toBe('DBO');
 | 
			
		||||
        expect(spellComp?.columns).toHaveLength(6);
 | 
			
		||||
 | 
			
		||||
        // Check foreign key relationships (should have at least 2)
 | 
			
		||||
        expect(result.relationships.length).toBeGreaterThanOrEqual(2);
 | 
			
		||||
 | 
			
		||||
        // Check FK from SpellDefinition to SpellComponent
 | 
			
		||||
        const fkDefToComp = result.relationships.find(
 | 
			
		||||
            (r) =>
 | 
			
		||||
                r.sourceTable === 'SpellDefinition' &&
 | 
			
		||||
                r.targetTable === 'SpellComponent' &&
 | 
			
		||||
                r.sourceColumn === 'itscomponentrel'
 | 
			
		||||
        );
 | 
			
		||||
        expect(fkDefToComp).toBeDefined();
 | 
			
		||||
        expect(fkDefToComp?.targetColumn).toBe('SPELLID');
 | 
			
		||||
 | 
			
		||||
        // Check self-referential FK in SpellComponent
 | 
			
		||||
        const selfRefFK = result.relationships.find(
 | 
			
		||||
            (r) =>
 | 
			
		||||
                r.sourceTable === 'SpellComponent' &&
 | 
			
		||||
                r.targetTable === 'SpellComponent' &&
 | 
			
		||||
                r.sourceColumn === 'itsparentcomp'
 | 
			
		||||
        );
 | 
			
		||||
        expect(selfRefFK).toBeDefined();
 | 
			
		||||
        expect(selfRefFK?.targetColumn).toBe('SPELLID');
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,102 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { sqlImportToDiagram } from '../../../index';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
 | 
			
		||||
describe('SQL Server Full Import Flow', () => {
 | 
			
		||||
    it('should create relationships when importing through the full flow', async () => {
 | 
			
		||||
        const sql = `CREATE TABLE [DBO].[SpellDefinition](
 | 
			
		||||
  [SPELLID]  (VARCHAR)(32),    
 | 
			
		||||
  [HASVERBALCOMP] BOOLEAN,  
 | 
			
		||||
  [INCANTATION] [VARCHAR](128),  
 | 
			
		||||
  [INCANTATIONFIX] BOOLEAN,  
 | 
			
		||||
  [ITSCOMPONENTREL]  [VARCHAR](32), FOREIGN KEY (itscomponentrel) REFERENCES SpellComponent(SPELLID), 
 | 
			
		||||
  [SHOWVISUALS] BOOLEAN,    ) ON [PRIMARY]
 | 
			
		||||
 | 
			
		||||
CREATE TABLE [DBO].[SpellComponent](
 | 
			
		||||
  [ALIAS] CHAR (50),    
 | 
			
		||||
  [SPELLID]  (VARCHAR)(32),    
 | 
			
		||||
  [ISOPTIONAL] BOOLEAN,  
 | 
			
		||||
  [ITSPARENTCOMP]  [VARCHAR](32), FOREIGN KEY (itsparentcomp) REFERENCES SpellComponent(SPELLID), 
 | 
			
		||||
  [ITSSCHOOLMETA]  [VARCHAR](32), FOREIGN KEY (itsschoolmeta) REFERENCES MagicSchool(SCHOOLID), 
 | 
			
		||||
  [KEYATTR] CHAR (100),  ) ON [PRIMARY]`;
 | 
			
		||||
 | 
			
		||||
        // Test the full import flow as the application uses it
 | 
			
		||||
        const diagram = await sqlImportToDiagram({
 | 
			
		||||
            sqlContent: sql,
 | 
			
		||||
            sourceDatabaseType: DatabaseType.SQL_SERVER,
 | 
			
		||||
            targetDatabaseType: DatabaseType.SQL_SERVER,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Verify tables
 | 
			
		||||
        expect(diagram.tables).toHaveLength(2);
 | 
			
		||||
        const tableNames = diagram.tables?.map((t) => t.name).sort();
 | 
			
		||||
        expect(tableNames).toEqual(['SpellComponent', 'SpellDefinition']);
 | 
			
		||||
 | 
			
		||||
        // Verify relationships are created in the diagram
 | 
			
		||||
        expect(diagram.relationships).toBeDefined();
 | 
			
		||||
        expect(diagram.relationships?.length).toBeGreaterThanOrEqual(2);
 | 
			
		||||
 | 
			
		||||
        // Check specific relationships
 | 
			
		||||
        const fk1 = diagram.relationships?.find(
 | 
			
		||||
            (r) =>
 | 
			
		||||
                r.sourceFieldId &&
 | 
			
		||||
                r.targetFieldId && // Must have field IDs
 | 
			
		||||
                diagram.tables?.some(
 | 
			
		||||
                    (t) =>
 | 
			
		||||
                        t.id === r.sourceTableId && t.name === 'SpellDefinition'
 | 
			
		||||
                )
 | 
			
		||||
        );
 | 
			
		||||
        expect(fk1).toBeDefined();
 | 
			
		||||
 | 
			
		||||
        const fk2 = diagram.relationships?.find(
 | 
			
		||||
            (r) =>
 | 
			
		||||
                r.sourceFieldId &&
 | 
			
		||||
                r.targetFieldId && // Must have field IDs
 | 
			
		||||
                diagram.tables?.some(
 | 
			
		||||
                    (t) =>
 | 
			
		||||
                        t.id === r.sourceTableId &&
 | 
			
		||||
                        t.name === 'SpellComponent' &&
 | 
			
		||||
                        t.id === r.targetTableId // self-reference
 | 
			
		||||
                )
 | 
			
		||||
        );
 | 
			
		||||
        expect(fk2).toBeDefined();
 | 
			
		||||
 | 
			
		||||
        console.log(
 | 
			
		||||
            'Full flow test - Relationships created:',
 | 
			
		||||
            diagram.relationships?.length
 | 
			
		||||
        );
 | 
			
		||||
        diagram.relationships?.forEach((r) => {
 | 
			
		||||
            const sourceTable = diagram.tables?.find(
 | 
			
		||||
                (t) => t.id === r.sourceTableId
 | 
			
		||||
            );
 | 
			
		||||
            const targetTable = diagram.tables?.find(
 | 
			
		||||
                (t) => t.id === r.targetTableId
 | 
			
		||||
            );
 | 
			
		||||
            const sourceField = sourceTable?.fields.find(
 | 
			
		||||
                (f) => f.id === r.sourceFieldId
 | 
			
		||||
            );
 | 
			
		||||
            const targetField = targetTable?.fields.find(
 | 
			
		||||
                (f) => f.id === r.targetFieldId
 | 
			
		||||
            );
 | 
			
		||||
            console.log(
 | 
			
		||||
                `  ${sourceTable?.name}.${sourceField?.name} -> ${targetTable?.name}.${targetField?.name}`
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle case-insensitive field matching', async () => {
 | 
			
		||||
        const sql = `CREATE TABLE DragonLair (
 | 
			
		||||
  [LAIRID] INT PRIMARY KEY,
 | 
			
		||||
  [parentLairId] INT, FOREIGN KEY (PARENTLAIRID) REFERENCES DragonLair(lairid)
 | 
			
		||||
)`;
 | 
			
		||||
 | 
			
		||||
        const diagram = await sqlImportToDiagram({
 | 
			
		||||
            sqlContent: sql,
 | 
			
		||||
            sourceDatabaseType: DatabaseType.SQL_SERVER,
 | 
			
		||||
            targetDatabaseType: DatabaseType.SQL_SERVER,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Should create the self-referential relationship despite case differences
 | 
			
		||||
        expect(diagram.relationships?.length).toBe(1);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,132 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { fromSQLServer } from '../sqlserver';
 | 
			
		||||
 | 
			
		||||
describe('SQL Server Multiple Tables with Foreign Keys', () => {
 | 
			
		||||
    it('should parse multiple tables with foreign keys in user format', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
            CREATE TABLE [DBO].[QuestReward](
 | 
			
		||||
                [BOID] (VARCHAR)(32),
 | 
			
		||||
                [HASEXTRACOL] BOOLEAN,
 | 
			
		||||
                [REWARDCODE] [VARCHAR](128),
 | 
			
		||||
                [REWARDFIX] BOOLEAN,
 | 
			
		||||
                [ITSQUESTREL] [VARCHAR](32), FOREIGN KEY (itsquestrel) REFERENCES QuestRelation(BOID),
 | 
			
		||||
                [SHOWDETAILS] BOOLEAN,
 | 
			
		||||
            ) ON [PRIMARY]
 | 
			
		||||
 | 
			
		||||
            CREATE TABLE [DBO].[QuestRelation](
 | 
			
		||||
                [ALIAS] CHAR (50),
 | 
			
		||||
                [BOID] (VARCHAR)(32),
 | 
			
		||||
                [ISOPTIONAL] BOOLEAN,
 | 
			
		||||
                [ITSPARENTREL] [VARCHAR](32), FOREIGN KEY (itsparentrel) REFERENCES QuestRelation(BOID),
 | 
			
		||||
                [ITSGUILDMETA] [VARCHAR](32), FOREIGN KEY (itsguildmeta) REFERENCES GuildMeta(BOID),
 | 
			
		||||
                [KEYATTR] CHAR (100),
 | 
			
		||||
            ) ON [PRIMARY]
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromSQLServer(sql);
 | 
			
		||||
 | 
			
		||||
        // Should create both tables
 | 
			
		||||
        expect(result.tables).toHaveLength(2);
 | 
			
		||||
 | 
			
		||||
        // Check first table
 | 
			
		||||
        const questReward = result.tables.find((t) => t.name === 'QuestReward');
 | 
			
		||||
        expect(questReward).toBeDefined();
 | 
			
		||||
        expect(questReward?.schema).toBe('DBO');
 | 
			
		||||
        expect(questReward?.columns).toHaveLength(6);
 | 
			
		||||
 | 
			
		||||
        // Check second table
 | 
			
		||||
        const questRelation = result.tables.find(
 | 
			
		||||
            (t) => t.name === 'QuestRelation'
 | 
			
		||||
        );
 | 
			
		||||
        expect(questRelation).toBeDefined();
 | 
			
		||||
        expect(questRelation?.schema).toBe('DBO');
 | 
			
		||||
        expect(questRelation?.columns).toHaveLength(6);
 | 
			
		||||
 | 
			
		||||
        // Check foreign key relationships
 | 
			
		||||
        expect(result.relationships).toHaveLength(2); // Should have 2 FKs (one self-referential in QuestRelation, one from QuestReward to QuestRelation)
 | 
			
		||||
 | 
			
		||||
        // Check FK from QuestReward to QuestRelation
 | 
			
		||||
        const fkToRelation = result.relationships.find(
 | 
			
		||||
            (r) =>
 | 
			
		||||
                r.sourceTable === 'QuestReward' &&
 | 
			
		||||
                r.targetTable === 'QuestRelation'
 | 
			
		||||
        );
 | 
			
		||||
        expect(fkToRelation).toBeDefined();
 | 
			
		||||
        expect(fkToRelation?.sourceColumn).toBe('itsquestrel');
 | 
			
		||||
        expect(fkToRelation?.targetColumn).toBe('BOID');
 | 
			
		||||
 | 
			
		||||
        // Check self-referential FK in QuestRelation
 | 
			
		||||
        const selfRefFK = result.relationships.find(
 | 
			
		||||
            (r) =>
 | 
			
		||||
                r.sourceTable === 'QuestRelation' &&
 | 
			
		||||
                r.targetTable === 'QuestRelation' &&
 | 
			
		||||
                r.sourceColumn === 'itsparentrel'
 | 
			
		||||
        );
 | 
			
		||||
        expect(selfRefFK).toBeDefined();
 | 
			
		||||
        expect(selfRefFK?.targetColumn).toBe('BOID');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse multiple tables with circular dependencies', async () => {
 | 
			
		||||
        const sql = `
 | 
			
		||||
            CREATE TABLE [DBO].[Dragon](
 | 
			
		||||
                [DRAGONID] (VARCHAR)(32),
 | 
			
		||||
                [NAME] [VARCHAR](100),
 | 
			
		||||
                [ITSLAIRREL] [VARCHAR](32), FOREIGN KEY (itslairrel) REFERENCES DragonLair(LAIRID),
 | 
			
		||||
                [POWER] INT,
 | 
			
		||||
            ) ON [PRIMARY]
 | 
			
		||||
 | 
			
		||||
            CREATE TABLE [DBO].[DragonLair](
 | 
			
		||||
                [LAIRID] (VARCHAR)(32),
 | 
			
		||||
                [LOCATION] [VARCHAR](200),
 | 
			
		||||
                [ITSGUARDIAN] [VARCHAR](32), FOREIGN KEY (itsguardian) REFERENCES Dragon(DRAGONID),
 | 
			
		||||
                [TREASURES] INT,
 | 
			
		||||
            ) ON [PRIMARY]
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        const result = await fromSQLServer(sql);
 | 
			
		||||
 | 
			
		||||
        // Should create both tables despite circular dependency
 | 
			
		||||
        expect(result.tables).toHaveLength(2);
 | 
			
		||||
 | 
			
		||||
        const dragon = result.tables.find((t) => t.name === 'Dragon');
 | 
			
		||||
        expect(dragon).toBeDefined();
 | 
			
		||||
 | 
			
		||||
        const dragonLair = result.tables.find((t) => t.name === 'DragonLair');
 | 
			
		||||
        expect(dragonLair).toBeDefined();
 | 
			
		||||
 | 
			
		||||
        // Check foreign key relationships (may have one or both depending on parser behavior with circular deps)
 | 
			
		||||
        expect(result.relationships.length).toBeGreaterThanOrEqual(1);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle exact user input format', async () => {
 | 
			
		||||
        // Exact copy of the user's input with fantasy theme
 | 
			
		||||
        const sql = `CREATE TABLE [DBO].[WizardDef](
 | 
			
		||||
  [BOID]  (VARCHAR)(32),    
 | 
			
		||||
  [HASEXTRACNTCOL] BOOLEAN,  
 | 
			
		||||
  [HISTORYCD] [VARCHAR](128),  
 | 
			
		||||
  [HISTORYCDFIX] BOOLEAN,  
 | 
			
		||||
  [ITSADWIZARDREL]  [VARCHAR](32), FOREIGN KEY (itsadwizardrel) REFERENCES WizardRel(BOID), 
 | 
			
		||||
  [SHOWDETAILS] BOOLEAN,    ) ON [PRIMARY]
 | 
			
		||||
 | 
			
		||||
CREATE TABLE [DBO].[WizardRel](
 | 
			
		||||
  [ALIAS] CHAR (50),    
 | 
			
		||||
  [BOID]  (VARCHAR)(32),    
 | 
			
		||||
  [ISOPTIONAL] BOOLEAN,  
 | 
			
		||||
  [ITSARWIZARDREL]  [VARCHAR](32), FOREIGN KEY (itsarwizardrel) REFERENCES WizardRel(BOID), 
 | 
			
		||||
  [ITSARMETABO]  [VARCHAR](32), FOREIGN KEY (itsarmetabo) REFERENCES MetaBO(BOID), 
 | 
			
		||||
  [KEYATTR] CHAR (100),  ) ON [PRIMARY]`;
 | 
			
		||||
 | 
			
		||||
        const result = await fromSQLServer(sql);
 | 
			
		||||
 | 
			
		||||
        // This should create TWO tables, not just one
 | 
			
		||||
        expect(result.tables).toHaveLength(2);
 | 
			
		||||
 | 
			
		||||
        const wizardDef = result.tables.find((t) => t.name === 'WizardDef');
 | 
			
		||||
        expect(wizardDef).toBeDefined();
 | 
			
		||||
        expect(wizardDef?.columns).toHaveLength(6);
 | 
			
		||||
 | 
			
		||||
        const wizardRel = result.tables.find((t) => t.name === 'WizardRel');
 | 
			
		||||
        expect(wizardRel).toBeDefined();
 | 
			
		||||
        expect(wizardRel?.columns).toHaveLength(6);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,93 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { fromSQLServer } from '../sqlserver';
 | 
			
		||||
 | 
			
		||||
describe('SQL Server FK Verification', () => {
 | 
			
		||||
    it('should correctly parse FKs from complex fantasy SQL', async () => {
 | 
			
		||||
        const sql = `CREATE TABLE [DBO].[SpellDefinition](
 | 
			
		||||
  [SPELLID]  (VARCHAR)(32),    
 | 
			
		||||
  [HASVERBALCOMP] BOOLEAN,  
 | 
			
		||||
  [INCANTATION] [VARCHAR](128),  
 | 
			
		||||
  [INCANTATIONFIX] BOOLEAN,  
 | 
			
		||||
  [ITSCOMPONENTREL]  [VARCHAR](32), FOREIGN KEY (itscomponentrel) REFERENCES SpellComponent(SPELLID), 
 | 
			
		||||
  [SHOWVISUALS] BOOLEAN,    ) ON [PRIMARY]
 | 
			
		||||
 | 
			
		||||
CREATE TABLE [DBO].[SpellComponent](
 | 
			
		||||
  [ALIAS] CHAR (50),    
 | 
			
		||||
  [SPELLID]  (VARCHAR)(32),    
 | 
			
		||||
  [ISOPTIONAL] BOOLEAN,  
 | 
			
		||||
  [ITSPARENTCOMP]  [VARCHAR](32), FOREIGN KEY (itsparentcomp) REFERENCES SpellComponent(SPELLID), 
 | 
			
		||||
  [ITSSCHOOLMETA]  [VARCHAR](32), FOREIGN KEY (itsschoolmeta) REFERENCES MagicSchool(SCHOOLID), 
 | 
			
		||||
  [KEYATTR] CHAR (100),  ) ON [PRIMARY]`;
 | 
			
		||||
 | 
			
		||||
        const result = await fromSQLServer(sql);
 | 
			
		||||
 | 
			
		||||
        // Verify tables
 | 
			
		||||
        expect(result.tables).toHaveLength(2);
 | 
			
		||||
        expect(result.tables.map((t) => t.name).sort()).toEqual([
 | 
			
		||||
            'SpellComponent',
 | 
			
		||||
            'SpellDefinition',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        // Verify that FKs were found (even if MagicSchool doesn't exist)
 | 
			
		||||
        // The parsing should find 3 FKs initially, but linkRelationships will filter out the one to MagicSchool
 | 
			
		||||
        expect(result.relationships.length).toBeGreaterThanOrEqual(2);
 | 
			
		||||
 | 
			
		||||
        // Verify specific FKs that should exist
 | 
			
		||||
        const fk1 = result.relationships.find(
 | 
			
		||||
            (r) =>
 | 
			
		||||
                r.sourceTable === 'SpellDefinition' &&
 | 
			
		||||
                r.sourceColumn.toLowerCase() === 'itscomponentrel' &&
 | 
			
		||||
                r.targetTable === 'SpellComponent'
 | 
			
		||||
        );
 | 
			
		||||
        expect(fk1).toBeDefined();
 | 
			
		||||
        expect(fk1?.targetColumn).toBe('SPELLID');
 | 
			
		||||
        expect(fk1?.sourceTableId).toBeTruthy();
 | 
			
		||||
        expect(fk1?.targetTableId).toBeTruthy();
 | 
			
		||||
 | 
			
		||||
        const fk2 = result.relationships.find(
 | 
			
		||||
            (r) =>
 | 
			
		||||
                r.sourceTable === 'SpellComponent' &&
 | 
			
		||||
                r.sourceColumn.toLowerCase() === 'itsparentcomp' &&
 | 
			
		||||
                r.targetTable === 'SpellComponent'
 | 
			
		||||
        );
 | 
			
		||||
        expect(fk2).toBeDefined();
 | 
			
		||||
        expect(fk2?.targetColumn).toBe('SPELLID');
 | 
			
		||||
        expect(fk2?.sourceTableId).toBeTruthy();
 | 
			
		||||
        expect(fk2?.targetTableId).toBeTruthy();
 | 
			
		||||
 | 
			
		||||
        // Log for debugging
 | 
			
		||||
        console.log('\n=== FK Verification Results ===');
 | 
			
		||||
        console.log(
 | 
			
		||||
            'Tables:',
 | 
			
		||||
            result.tables.map((t) => `${t.schema}.${t.name}`)
 | 
			
		||||
        );
 | 
			
		||||
        console.log('Total FKs found:', result.relationships.length);
 | 
			
		||||
        result.relationships.forEach((r, i) => {
 | 
			
		||||
            console.log(
 | 
			
		||||
                `FK ${i + 1}: ${r.sourceTable}.${r.sourceColumn} -> ${r.targetTable}.${r.targetColumn}`
 | 
			
		||||
            );
 | 
			
		||||
            console.log(`  IDs: ${r.sourceTableId} -> ${r.targetTableId}`);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse inline FOREIGN KEY syntax correctly', async () => {
 | 
			
		||||
        // Simplified test with just one FK to ensure parsing works
 | 
			
		||||
        const sql = `CREATE TABLE [DBO].[WizardTower](
 | 
			
		||||
  [TOWERID] INT,
 | 
			
		||||
  [MASTERKEY] [VARCHAR](32), FOREIGN KEY (MASTERKEY) REFERENCES ArcaneGuild(GUILDID),
 | 
			
		||||
  [NAME] VARCHAR(100)
 | 
			
		||||
) ON [PRIMARY]
 | 
			
		||||
 | 
			
		||||
CREATE TABLE [DBO].[ArcaneGuild](
 | 
			
		||||
  [GUILDID] [VARCHAR](32),
 | 
			
		||||
  [GUILDNAME] VARCHAR(100)
 | 
			
		||||
) ON [PRIMARY]`;
 | 
			
		||||
 | 
			
		||||
        const result = await fromSQLServer(sql);
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(2);
 | 
			
		||||
        expect(result.relationships).toHaveLength(1);
 | 
			
		||||
        expect(result.relationships[0].sourceColumn).toBe('MASTERKEY');
 | 
			
		||||
        expect(result.relationships[0].targetColumn).toBe('GUILDID');
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -342,35 +342,6 @@ function parseCreateTableManually(
 | 
			
		||||
 | 
			
		||||
    // Process each part (column or constraint)
 | 
			
		||||
    for (const part of parts) {
 | 
			
		||||
        // Handle standalone FOREIGN KEY definitions (without CONSTRAINT keyword)
 | 
			
		||||
        // Format: FOREIGN KEY (column) REFERENCES Table(column)
 | 
			
		||||
        if (part.match(/^\s*FOREIGN\s+KEY/i)) {
 | 
			
		||||
            const fkMatch = part.match(
 | 
			
		||||
                /FOREIGN\s+KEY\s*\(([^)]+)\)\s+REFERENCES\s+(?:\[?(\w+)\]?\.)??\[?(\w+)\]?\s*\(([^)]+)\)/i
 | 
			
		||||
            );
 | 
			
		||||
            if (fkMatch) {
 | 
			
		||||
                const [
 | 
			
		||||
                    ,
 | 
			
		||||
                    sourceCol,
 | 
			
		||||
                    targetSchema = 'dbo',
 | 
			
		||||
                    targetTable,
 | 
			
		||||
                    targetCol,
 | 
			
		||||
                ] = fkMatch;
 | 
			
		||||
                relationships.push({
 | 
			
		||||
                    name: `FK_${tableName}_${sourceCol.trim().replace(/\[|\]/g, '')}`,
 | 
			
		||||
                    sourceTable: tableName,
 | 
			
		||||
                    sourceSchema: schema,
 | 
			
		||||
                    sourceColumn: sourceCol.trim().replace(/\[|\]/g, ''),
 | 
			
		||||
                    targetTable: targetTable || targetSchema,
 | 
			
		||||
                    targetSchema: targetTable ? targetSchema : 'dbo',
 | 
			
		||||
                    targetColumn: targetCol.trim().replace(/\[|\]/g, ''),
 | 
			
		||||
                    sourceTableId: tableId,
 | 
			
		||||
                    targetTableId: '', // Will be filled later
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Handle constraint definitions
 | 
			
		||||
        if (part.match(/^\s*CONSTRAINT/i)) {
 | 
			
		||||
            // Parse constraints
 | 
			
		||||
@@ -464,13 +435,6 @@ function parseCreateTableManually(
 | 
			
		||||
            columnMatch = part.match(/^\s*(\w+)\s+(\w+)\s+([\d,\s]+)\s+(.*)$/i);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Handle unusual format: [COLUMN_NAME] (VARCHAR)(32)
 | 
			
		||||
        if (!columnMatch) {
 | 
			
		||||
            columnMatch = part.match(
 | 
			
		||||
                /^\s*\[?(\w+)\]?\s+\((\w+)\)\s*\(([\d,\s]+|max)\)(.*)$/i
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (columnMatch) {
 | 
			
		||||
            const [, colName, baseType, typeArgs, rest] = columnMatch;
 | 
			
		||||
 | 
			
		||||
@@ -482,37 +446,7 @@ function parseCreateTableManually(
 | 
			
		||||
                const inlineFkMatch = rest.match(
 | 
			
		||||
                    /FOREIGN\s+KEY\s+REFERENCES\s+(?:\[?(\w+)\]?\.)??\[?(\w+)\]?\s*\(([^)]+)\)/i
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                // Also check if there's a FOREIGN KEY after a comma with column name
 | 
			
		||||
                // Format: , FOREIGN KEY (columnname) REFERENCES Table(column)
 | 
			
		||||
                if (!inlineFkMatch && rest.includes('FOREIGN KEY')) {
 | 
			
		||||
                    const fkWithColumnMatch = rest.match(
 | 
			
		||||
                        /,\s*FOREIGN\s+KEY\s*\((\w+)\)\s+REFERENCES\s+(?:\[?(\w+)\]?\.)??\[?(\w+)\]?\s*\(([^)]+)\)/i
 | 
			
		||||
                    );
 | 
			
		||||
                    if (fkWithColumnMatch) {
 | 
			
		||||
                        const [, srcCol, targetSchema, targetTable, targetCol] =
 | 
			
		||||
                            fkWithColumnMatch;
 | 
			
		||||
                        // Only process if srcCol matches current colName (case-insensitive)
 | 
			
		||||
                        if (srcCol.toLowerCase() === colName.toLowerCase()) {
 | 
			
		||||
                            // Create FK relationship
 | 
			
		||||
                            relationships.push({
 | 
			
		||||
                                name: `FK_${tableName}_${colName}`,
 | 
			
		||||
                                sourceTable: tableName,
 | 
			
		||||
                                sourceSchema: schema,
 | 
			
		||||
                                sourceColumn: colName,
 | 
			
		||||
                                targetTable: targetTable || targetSchema,
 | 
			
		||||
                                targetSchema: targetTable
 | 
			
		||||
                                    ? targetSchema || 'dbo'
 | 
			
		||||
                                    : 'dbo',
 | 
			
		||||
                                targetColumn: targetCol
 | 
			
		||||
                                    .trim()
 | 
			
		||||
                                    .replace(/\[|\]/g, ''),
 | 
			
		||||
                                sourceTableId: tableId,
 | 
			
		||||
                                targetTableId: '', // Will be filled later
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (inlineFkMatch) {
 | 
			
		||||
                if (inlineFkMatch) {
 | 
			
		||||
                    const [, targetSchema = 'dbo', targetTable, targetCol] =
 | 
			
		||||
                        inlineFkMatch;
 | 
			
		||||
                    relationships.push({
 | 
			
		||||
@@ -602,36 +536,10 @@ export async function fromSQLServer(
 | 
			
		||||
    try {
 | 
			
		||||
        // First, handle ALTER TABLE statements for foreign keys
 | 
			
		||||
        // Split by GO or semicolon for SQL Server
 | 
			
		||||
        let statements = sqlContent
 | 
			
		||||
        const statements = sqlContent
 | 
			
		||||
            .split(/(?:GO\s*$|;\s*$)/im)
 | 
			
		||||
            .filter((stmt) => stmt.trim().length > 0);
 | 
			
		||||
 | 
			
		||||
        // Additional splitting for CREATE TABLE statements that might not be separated by semicolons
 | 
			
		||||
        // If we have a statement with multiple CREATE TABLE, split them
 | 
			
		||||
        const expandedStatements: string[] = [];
 | 
			
		||||
        for (const stmt of statements) {
 | 
			
		||||
            // Check if this statement contains multiple CREATE TABLE statements
 | 
			
		||||
            if ((stmt.match(/CREATE\s+TABLE/gi) || []).length > 1) {
 | 
			
		||||
                // Split by ") ON [PRIMARY]" followed by CREATE TABLE
 | 
			
		||||
                const parts = stmt.split(
 | 
			
		||||
                    /\)\s*ON\s*\[PRIMARY\]\s*(?=CREATE\s+TABLE)/gi
 | 
			
		||||
                );
 | 
			
		||||
                for (let i = 0; i < parts.length; i++) {
 | 
			
		||||
                    let part = parts[i].trim();
 | 
			
		||||
                    // Re-add ") ON [PRIMARY]" to all parts except the last (which should already have it)
 | 
			
		||||
                    if (i < parts.length - 1 && part.length > 0) {
 | 
			
		||||
                        part += ') ON [PRIMARY]';
 | 
			
		||||
                    }
 | 
			
		||||
                    if (part.trim().length > 0) {
 | 
			
		||||
                        expandedStatements.push(part);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                expandedStatements.push(stmt);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        statements = expandedStatements;
 | 
			
		||||
 | 
			
		||||
        const alterTableStatements = statements.filter(
 | 
			
		||||
            (stmt) =>
 | 
			
		||||
                stmt.trim().toUpperCase().includes('ALTER TABLE') &&
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,17 +0,0 @@
 | 
			
		||||
Table "bruit"."100-AAB-CABAS-Mdap" {
 | 
			
		||||
  "qgs_fid" int [pk, not null]
 | 
			
		||||
  "geom" geometry
 | 
			
		||||
  "from" decimal(8,2)
 | 
			
		||||
  "to" decimal(8,2)
 | 
			
		||||
  "period" nvarchar(500)
 | 
			
		||||
  "objectid" float
 | 
			
		||||
  "insee" float
 | 
			
		||||
  "nom" nvarchar(500)
 | 
			
		||||
  "code_posta" int
 | 
			
		||||
  "ut" nvarchar(500)
 | 
			
		||||
  "territoire" nvarchar(500)
 | 
			
		||||
  "surface" float
 | 
			
		||||
  "perimetre" float
 | 
			
		||||
  "ccodter" float
 | 
			
		||||
  "numcom" nvarchar(500)
 | 
			
		||||
}
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
{"id":"mqqwkkodtkfm","name":"NTP_CANPT-db","createdAt":"2025-08-27T17:03:48.994Z","updatedAt":"2025-08-27T17:12:54.617Z","databaseType":"sql_server","tables":[{"id":"e4qecug35j4b7q75u1j3sdca5","name":"100-AAB-CABAS-Mdap","schema":"bruit","x":100,"y":100,"fields":[{"id":"04liixxb8yenudc6gqjjbgm1r","name":"qgs_fid","type":{"id":"int","name":"int"},"primaryKey":true,"unique":true,"nullable":false,"createdAt":1739267036546},{"id":"hr29n1e1jgybuac3gcerk7jyi","name":"geom","type":{"id":"geometry","name":"geometry"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546},{"id":"jcqh683op52ovfwqwe0w0i2or","name":"from","type":{"id":"decimal","name":"decimal"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546,"precision":8,"scale":2},{"id":"xev33ok0oqqom2n1tabpp5eds","name":"to","type":{"id":"decimal","name":"decimal"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546,"precision":8,"scale":2},{"id":"pj36qhdpl0vice9tsyiaaef4l","name":"period","type":{"id":"nvarchar","name":"nvarchar"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546,"collation":"French_CI_AS"},{"id":"l4ce4a68j9h7l46p8dg5qi09u","name":"objectid","type":{"id":"float","name":"float"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546},{"id":"fi4s2aahfjdeelfkgnrk4q5mk","name":"insee","type":{"id":"float","name":"float"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546},{"id":"ujsajf0t5xg0td614lpxk32py","name":"nom","type":{"id":"nvarchar","name":"nvarchar"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546,"collation":"French_CI_AS"},{"id":"9j0c54ez2t5dgr0ybzd0ksbuz","name":"code_posta","type":{"id":"int","name":"int"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546},{"id":"gybxvu42odvvjyfoe9zdn7tul","name":"ut","type":{"id":"nvarchar","name":"nvarchar"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546,"collation":"French_CI_AS"},{"id":"qon7xs001v9q8frad6jr9lrho","name":"territoire","type":{"id":"nvarchar","name":"nvarchar"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546,"collation":"French_CI_AS"},{"id":"aeqrfvw5dvig7t8zyjfiri707","name":"surface","type":{"id":"float","name":"float"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546},{"id":"eqbcy7gfd49a3a6ds6ne6fmzd","name":"perimetre","type":{"id":"float","name":"float"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546},{"id":"cbxmodo9l3keqxapqnlfjnqy2","name":"ccodter","type":{"id":"float","name":"float"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546},{"id":"c3j131aycof5kgyiypva428l3","name":"numcom","type":{"id":"nvarchar","name":"nvarchar"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1739267036546,"collation":"French_CI_AS"}],"indexes":[],"color":"#8a61f5","isView":false,"isMaterializedView":false,"createdAt":1739267036546,"diagramId":"mqqwkkodtkfm","expanded":true}],"relationships":[],"dependencies":[],"areas":[],"customTypes":[]}
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
Table "public"."guy_table" {
 | 
			
		||||
  "id" integer [pk, not null]
 | 
			
		||||
  "created_at" timestamp [not null]
 | 
			
		||||
  "column3" text
 | 
			
		||||
  "arrayfield" text[]
 | 
			
		||||
  "field_5" "character varying"
 | 
			
		||||
  "field_6" "character varying(100)"
 | 
			
		||||
}
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
{"id":"mqqwkkod7trl","name":"guy-db","databaseType":"postgresql","createdAt":"2025-09-10T18:45:32.817Z","updatedAt":"2025-09-10T19:15:21.682Z","tables":[{"id":"g2hv9mlo3qbyjnxdc44j1zxl2","name":"guy_table","schema":"public","x":100,"y":300,"fields":[{"id":"qdqgzmtxsi84ujfuktsvjuop8","name":"id","type":{"id":"integer","name":"integer"},"primaryKey":true,"unique":true,"nullable":false,"createdAt":1757529932816},{"id":"wsys99f86679ch6fbjryw0egr","name":"created_at","type":{"id":"timestamp_without_time_zone","name":"timestamp without time zone"},"primaryKey":false,"unique":false,"nullable":false,"createdAt":1757529932816},{"id":"ro39cba7sd290k90qjgzib8pi","name":"column3","type":{"id":"text","name":"text"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1757529932816},{"id":"6cntbu2orwk7kxlg0rcduqgbo","name":"arrayfield","type":{"id":"array","name":"array"},"primaryKey":false,"unique":false,"nullable":true,"createdAt":1757529932816},{"id":"7cz0ybdoov2m3wbgm9tlzatz0","name":"field_5","type":{"id":"character_varying","name":"character varying"},"unique":false,"nullable":true,"primaryKey":false,"createdAt":1757531685981},{"id":"zzwlyvqzz93oh0vv8f8qob103","name":"field_6","type":{"id":"character_varying","name":"character varying"},"unique":false,"nullable":true,"primaryKey":false,"createdAt":1757531713961,"characterMaximumLength":"100"}],"indexes":[{"id":"r0w71lnbnje2j9cz1t9j64rya","name":"guy_table_pkey","unique":true,"fieldIds":["qdqgzmtxsi84ujfuktsvjuop8"],"createdAt":1757529932816,"isPrimaryKey":true}],"color":"#8eb7ff","isView":false,"isMaterializedView":false,"createdAt":1757529932816,"diagramId":"mqqwkkod7trl"}],"relationships":[],"dependencies":[],"areas":[],"customTypes":[]}
 | 
			
		||||
@@ -1,114 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { generateDBMLFromDiagram } from '../dbml-export';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import type { Diagram } from '@/lib/domain/diagram';
 | 
			
		||||
import { generateId } from '@/lib/utils';
 | 
			
		||||
 | 
			
		||||
describe('Composite Primary Key Name Export', () => {
 | 
			
		||||
    it('should export composite primary key with name in DBML', () => {
 | 
			
		||||
        const diagram: Diagram = {
 | 
			
		||||
            id: generateId(),
 | 
			
		||||
            name: 'Test',
 | 
			
		||||
            databaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
            createdAt: new Date(),
 | 
			
		||||
            updatedAt: new Date(),
 | 
			
		||||
            tables: [
 | 
			
		||||
                {
 | 
			
		||||
                    id: generateId(),
 | 
			
		||||
                    name: 'users_master_table',
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    x: 0,
 | 
			
		||||
                    y: 0,
 | 
			
		||||
                    color: '#FFF',
 | 
			
		||||
                    isView: false,
 | 
			
		||||
                    createdAt: Date.now(),
 | 
			
		||||
                    fields: [
 | 
			
		||||
                        {
 | 
			
		||||
                            id: generateId(),
 | 
			
		||||
                            name: 'master_user_id',
 | 
			
		||||
                            type: { id: 'bigint', name: 'bigint' },
 | 
			
		||||
                            nullable: false,
 | 
			
		||||
                            primaryKey: true,
 | 
			
		||||
                            unique: false,
 | 
			
		||||
                            createdAt: Date.now(),
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            id: generateId(),
 | 
			
		||||
                            name: 'tenant_id',
 | 
			
		||||
                            type: { id: 'bigint', name: 'bigint' },
 | 
			
		||||
                            nullable: false,
 | 
			
		||||
                            primaryKey: true,
 | 
			
		||||
                            unique: false,
 | 
			
		||||
                            createdAt: Date.now(),
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            id: generateId(),
 | 
			
		||||
                            name: 'tenant_user_id',
 | 
			
		||||
                            type: { id: 'bigint', name: 'bigint' },
 | 
			
		||||
                            nullable: false,
 | 
			
		||||
                            primaryKey: true,
 | 
			
		||||
                            unique: false,
 | 
			
		||||
                            createdAt: Date.now(),
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            id: generateId(),
 | 
			
		||||
                            name: 'enabled',
 | 
			
		||||
                            type: { id: 'boolean', name: 'boolean' },
 | 
			
		||||
                            nullable: true,
 | 
			
		||||
                            primaryKey: false,
 | 
			
		||||
                            unique: false,
 | 
			
		||||
                            createdAt: Date.now(),
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                    indexes: [
 | 
			
		||||
                        {
 | 
			
		||||
                            id: generateId(),
 | 
			
		||||
                            name: 'users_master_table_index_1',
 | 
			
		||||
                            unique: true,
 | 
			
		||||
                            fieldIds: ['dummy1', 'dummy2'], // Will be replaced
 | 
			
		||||
                            createdAt: Date.now(),
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            relationships: [],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Fix field IDs in the index and add PK index
 | 
			
		||||
        const table = diagram.tables![0];
 | 
			
		||||
        const masterUserIdField = table.fields.find(
 | 
			
		||||
            (f) => f.name === 'master_user_id'
 | 
			
		||||
        );
 | 
			
		||||
        const tenantIdField = table.fields.find((f) => f.name === 'tenant_id');
 | 
			
		||||
        const tenantUserIdField = table.fields.find(
 | 
			
		||||
            (f) => f.name === 'tenant_user_id'
 | 
			
		||||
        );
 | 
			
		||||
        table.indexes[0].fieldIds = [tenantIdField!.id, tenantUserIdField!.id];
 | 
			
		||||
 | 
			
		||||
        // Add the PK index with name
 | 
			
		||||
        table.indexes.push({
 | 
			
		||||
            id: generateId(),
 | 
			
		||||
            name: 'moshe',
 | 
			
		||||
            unique: true,
 | 
			
		||||
            isPrimaryKey: true,
 | 
			
		||||
            fieldIds: [
 | 
			
		||||
                masterUserIdField!.id,
 | 
			
		||||
                tenantIdField!.id,
 | 
			
		||||
                tenantUserIdField!.id,
 | 
			
		||||
            ],
 | 
			
		||||
            createdAt: Date.now(),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
        // Check that the DBML contains the composite PK with name
 | 
			
		||||
        expect(result.standardDbml).toContain(
 | 
			
		||||
            '(master_user_id, tenant_id, tenant_user_id) [pk, name: "moshe"]'
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Check that the unique index is also present
 | 
			
		||||
        expect(result.standardDbml).toContain(
 | 
			
		||||
            '(tenant_id, tenant_user_id) [unique, name: "users_master_table_index_1"]'
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1383,9 +1383,12 @@ Ref "fk_0_table_2_id_fk":"table_1"."id" < "table_2"."id"
 | 
			
		||||
        const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
        // Check that the inline DBML has proper indentation
 | 
			
		||||
        // Note: indexes on primary key fields should be filtered out
 | 
			
		||||
        expect(result.inlineDbml).toContain(`Table "table_1" {
 | 
			
		||||
  "id" bigint [pk, not null]
 | 
			
		||||
 | 
			
		||||
  Indexes {
 | 
			
		||||
    id [name: "index_1"]
 | 
			
		||||
  }
 | 
			
		||||
}`);
 | 
			
		||||
 | 
			
		||||
        expect(result.inlineDbml).toContain(`Table "table_2" {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,438 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { generateDBMLFromDiagram } from '../dbml-export';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import type { Diagram } from '@/lib/domain/diagram';
 | 
			
		||||
import type { DBTable } from '@/lib/domain/db-table';
 | 
			
		||||
import type { DBField } from '@/lib/domain/db-field';
 | 
			
		||||
 | 
			
		||||
describe('DBML Export - Fix Multiline Table Names', () => {
 | 
			
		||||
    // Helper to generate test IDs and timestamps
 | 
			
		||||
    let idCounter = 0;
 | 
			
		||||
    const testId = () => `test-id-${++idCounter}`;
 | 
			
		||||
    const testTime = Date.now();
 | 
			
		||||
 | 
			
		||||
    // Helper to create a field
 | 
			
		||||
    const createField = (overrides: Partial<DBField>): DBField =>
 | 
			
		||||
        ({
 | 
			
		||||
            id: testId(),
 | 
			
		||||
            name: 'field',
 | 
			
		||||
            type: { id: 'text', name: 'text' },
 | 
			
		||||
            primaryKey: false,
 | 
			
		||||
            nullable: true,
 | 
			
		||||
            unique: false,
 | 
			
		||||
            createdAt: testTime,
 | 
			
		||||
            ...overrides,
 | 
			
		||||
        }) as DBField;
 | 
			
		||||
 | 
			
		||||
    // Helper to create a table
 | 
			
		||||
    const createTable = (overrides: Partial<DBTable>): DBTable =>
 | 
			
		||||
        ({
 | 
			
		||||
            id: testId(),
 | 
			
		||||
            name: 'table',
 | 
			
		||||
            fields: [],
 | 
			
		||||
            indexes: [],
 | 
			
		||||
            createdAt: testTime,
 | 
			
		||||
            x: 0,
 | 
			
		||||
            y: 0,
 | 
			
		||||
            width: 200,
 | 
			
		||||
            ...overrides,
 | 
			
		||||
        }) as DBTable;
 | 
			
		||||
 | 
			
		||||
    // Helper to create a diagram
 | 
			
		||||
    const createDiagram = (overrides: Partial<Diagram>): Diagram =>
 | 
			
		||||
        ({
 | 
			
		||||
            id: testId(),
 | 
			
		||||
            name: 'diagram',
 | 
			
		||||
            databaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
            tables: [],
 | 
			
		||||
            relationships: [],
 | 
			
		||||
            createdAt: testTime,
 | 
			
		||||
            updatedAt: testTime,
 | 
			
		||||
            ...overrides,
 | 
			
		||||
        }) as Diagram;
 | 
			
		||||
 | 
			
		||||
    describe('DBML Generation with Special Characters', () => {
 | 
			
		||||
        it('should handle table names with special characters', () => {
 | 
			
		||||
            const diagram = createDiagram({
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        name: 'user-profiles',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'user-name',
 | 
			
		||||
                                type: { id: 'varchar', name: 'varchar' },
 | 
			
		||||
                                nullable: true,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
            // Should properly quote table names with special characters
 | 
			
		||||
            expect(result.standardDbml).toContain('Table "user-profiles"');
 | 
			
		||||
 | 
			
		||||
            // Field names with special characters should also be quoted
 | 
			
		||||
            expect(result.standardDbml).toContain('"user-name"');
 | 
			
		||||
 | 
			
		||||
            // Should not have any errors
 | 
			
		||||
            expect(result.error).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle schema-qualified table names', () => {
 | 
			
		||||
            const diagram = createDiagram({
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        schema: 'my-schema',
 | 
			
		||||
                        name: 'my-table',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
            // Should properly quote schema and table names
 | 
			
		||||
            expect(result.standardDbml).toContain(
 | 
			
		||||
                'Table "my-schema"."my-table"'
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Should not have any errors
 | 
			
		||||
            expect(result.error).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle table names with spaces', () => {
 | 
			
		||||
            const diagram = createDiagram({
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        name: 'user profiles',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
            // Should properly quote table names with spaces
 | 
			
		||||
            expect(result.standardDbml).toContain('Table "user profiles"');
 | 
			
		||||
 | 
			
		||||
            // Should not have any errors
 | 
			
		||||
            expect(result.error).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle schema names with spaces', () => {
 | 
			
		||||
            const diagram = createDiagram({
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        schema: 'my schema',
 | 
			
		||||
                        name: 'my_table',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
            // Should properly quote schema with spaces
 | 
			
		||||
            expect(result.standardDbml).toContain(
 | 
			
		||||
                'Table "my schema"."my_table"'
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Should not have any errors
 | 
			
		||||
            expect(result.error).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle table names with dots', () => {
 | 
			
		||||
            const diagram = createDiagram({
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        name: 'app.config',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
            // Should properly quote table names with dots
 | 
			
		||||
            expect(result.standardDbml).toContain('Table "app.config"');
 | 
			
		||||
 | 
			
		||||
            // Should not have any errors
 | 
			
		||||
            expect(result.error).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should not have line breaks in table declarations', () => {
 | 
			
		||||
            const diagram = createDiagram({
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        schema: 'very-long-schema-name-with-dashes',
 | 
			
		||||
                        name: 'very-long-table-name-with-special-characters',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
            // Table declaration should be on a single line
 | 
			
		||||
            const tableDeclarations =
 | 
			
		||||
                result.standardDbml.match(/Table\s+[^{]+\{/g) || [];
 | 
			
		||||
            tableDeclarations.forEach((decl) => {
 | 
			
		||||
                // Should not contain newlines before the opening brace
 | 
			
		||||
                expect(decl).not.toContain('\n');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // The full qualified name should be on one line
 | 
			
		||||
            expect(result.standardDbml).toMatch(
 | 
			
		||||
                /Table\s+"very-long-schema-name-with-dashes"\."very-long-table-name-with-special-characters"\s*\{/
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Multiple tables and relationships', () => {
 | 
			
		||||
        it('should handle multiple tables with special characters', () => {
 | 
			
		||||
            const parentTableId = testId();
 | 
			
		||||
            const childTableId = testId();
 | 
			
		||||
            const parentIdField = testId();
 | 
			
		||||
            const childParentIdField = testId();
 | 
			
		||||
 | 
			
		||||
            const diagram = createDiagram({
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        id: parentTableId,
 | 
			
		||||
                        schema: 'auth-schema',
 | 
			
		||||
                        name: 'user-accounts',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                id: parentIdField,
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'uuid', name: 'uuid' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        id: childTableId,
 | 
			
		||||
                        schema: 'app-schema',
 | 
			
		||||
                        name: 'user-profiles',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'uuid', name: 'uuid' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                            createField({
 | 
			
		||||
                                id: childParentIdField,
 | 
			
		||||
                                name: 'account-id',
 | 
			
		||||
                                type: { id: 'uuid', name: 'uuid' },
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
                relationships: [
 | 
			
		||||
                    {
 | 
			
		||||
                        id: testId(),
 | 
			
		||||
                        name: 'fk_profiles_accounts',
 | 
			
		||||
                        sourceTableId: childTableId,
 | 
			
		||||
                        targetTableId: parentTableId,
 | 
			
		||||
                        sourceFieldId: childParentIdField,
 | 
			
		||||
                        targetFieldId: parentIdField,
 | 
			
		||||
                        sourceCardinality: 'many',
 | 
			
		||||
                        targetCardinality: 'one',
 | 
			
		||||
                        createdAt: testTime,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
            // Should contain both tables properly quoted
 | 
			
		||||
            expect(result.standardDbml).toContain(
 | 
			
		||||
                'Table "auth-schema"."user-accounts"'
 | 
			
		||||
            );
 | 
			
		||||
            expect(result.standardDbml).toContain(
 | 
			
		||||
                'Table "app-schema"."user-profiles"'
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Should contain the relationship reference
 | 
			
		||||
            expect(result.standardDbml).toContain('Ref');
 | 
			
		||||
 | 
			
		||||
            // Should contain field names with dashes properly quoted
 | 
			
		||||
            expect(result.standardDbml).toContain('"account-id"');
 | 
			
		||||
 | 
			
		||||
            // Should not have any errors
 | 
			
		||||
            expect(result.error).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should work correctly with inline DBML format', () => {
 | 
			
		||||
            const parentTableId = testId();
 | 
			
		||||
            const childTableId = testId();
 | 
			
		||||
            const parentIdField = testId();
 | 
			
		||||
            const childParentIdField = testId();
 | 
			
		||||
 | 
			
		||||
            const diagram = createDiagram({
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        id: parentTableId,
 | 
			
		||||
                        name: 'parent-table',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                id: parentIdField,
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        id: childTableId,
 | 
			
		||||
                        name: 'child-table',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                            createField({
 | 
			
		||||
                                id: childParentIdField,
 | 
			
		||||
                                name: 'parent-id',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
                relationships: [
 | 
			
		||||
                    {
 | 
			
		||||
                        id: testId(),
 | 
			
		||||
                        name: 'fk_child_parent',
 | 
			
		||||
                        sourceTableId: childTableId,
 | 
			
		||||
                        targetTableId: parentTableId,
 | 
			
		||||
                        sourceFieldId: childParentIdField,
 | 
			
		||||
                        targetFieldId: parentIdField,
 | 
			
		||||
                        sourceCardinality: 'many',
 | 
			
		||||
                        targetCardinality: 'one',
 | 
			
		||||
                        createdAt: testTime,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
            // Both standard and inline should be generated
 | 
			
		||||
            expect(result.standardDbml).toBeDefined();
 | 
			
		||||
            expect(result.inlineDbml).toBeDefined();
 | 
			
		||||
 | 
			
		||||
            // Inline version should contain inline references
 | 
			
		||||
            expect(result.inlineDbml).toContain('ref:');
 | 
			
		||||
 | 
			
		||||
            // Both should properly quote table names
 | 
			
		||||
            expect(result.standardDbml).toContain('Table "parent-table"');
 | 
			
		||||
            expect(result.inlineDbml).toContain('Table "parent-table"');
 | 
			
		||||
            expect(result.standardDbml).toContain('Table "child-table"');
 | 
			
		||||
            expect(result.inlineDbml).toContain('Table "child-table"');
 | 
			
		||||
 | 
			
		||||
            // Should not have any errors
 | 
			
		||||
            expect(result.error).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Edge cases', () => {
 | 
			
		||||
        it('should handle empty table names gracefully', () => {
 | 
			
		||||
            const diagram = createDiagram({
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        name: '',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'id',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
            // Should not throw error
 | 
			
		||||
            expect(result.error).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle Unicode characters in names', () => {
 | 
			
		||||
            const diagram = createDiagram({
 | 
			
		||||
                tables: [
 | 
			
		||||
                    createTable({
 | 
			
		||||
                        name: 'użytkownik',
 | 
			
		||||
                        fields: [
 | 
			
		||||
                            createField({
 | 
			
		||||
                                name: 'identyfikator',
 | 
			
		||||
                                type: { id: 'integer', name: 'integer' },
 | 
			
		||||
                                primaryKey: true,
 | 
			
		||||
                                nullable: false,
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
 | 
			
		||||
            // Should handle Unicode characters
 | 
			
		||||
            expect(result.standardDbml).toContain('Table "użytkownik"');
 | 
			
		||||
            expect(result.standardDbml).toContain('"identyfikator"');
 | 
			
		||||
 | 
			
		||||
            // Should not have any errors
 | 
			
		||||
            expect(result.error).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,67 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { diagramFromJSONInput } from '@/lib/export-import-utils';
 | 
			
		||||
import { generateDBMLFromDiagram } from '../dbml-export';
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import * as path from 'path';
 | 
			
		||||
 | 
			
		||||
describe('DBML Export - Diagram Case 1 Tests', () => {
 | 
			
		||||
    it('should handle case 1 diagram', { timeout: 30000 }, async () => {
 | 
			
		||||
        // Read the JSON file
 | 
			
		||||
        const jsonPath = path.join(__dirname, 'cases', '1.json');
 | 
			
		||||
        const jsonContent = fs.readFileSync(jsonPath, 'utf-8');
 | 
			
		||||
 | 
			
		||||
        // Parse the JSON and convert to diagram
 | 
			
		||||
        const diagram = diagramFromJSONInput(jsonContent);
 | 
			
		||||
 | 
			
		||||
        // Generate DBML from the diagram
 | 
			
		||||
        const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
        const generatedDBML = result.standardDbml;
 | 
			
		||||
 | 
			
		||||
        // Read the expected DBML file
 | 
			
		||||
        const dbmlPath = path.join(__dirname, 'cases', '1.dbml');
 | 
			
		||||
        const expectedDBML = fs.readFileSync(dbmlPath, 'utf-8');
 | 
			
		||||
 | 
			
		||||
        // Compare the generated DBML with the expected DBML
 | 
			
		||||
        expect(generatedDBML).toBe(expectedDBML);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle case 2 diagram', { timeout: 30000 }, async () => {
 | 
			
		||||
        // Read the JSON file
 | 
			
		||||
        const jsonPath = path.join(__dirname, 'cases', '2.json');
 | 
			
		||||
        const jsonContent = fs.readFileSync(jsonPath, 'utf-8');
 | 
			
		||||
 | 
			
		||||
        // Parse the JSON and convert to diagram
 | 
			
		||||
        const diagram = diagramFromJSONInput(jsonContent);
 | 
			
		||||
 | 
			
		||||
        // Generate DBML from the diagram
 | 
			
		||||
        const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
        const generatedDBML = result.standardDbml;
 | 
			
		||||
 | 
			
		||||
        // Read the expected DBML file
 | 
			
		||||
        const dbmlPath = path.join(__dirname, 'cases', '2.dbml');
 | 
			
		||||
        const expectedDBML = fs.readFileSync(dbmlPath, 'utf-8');
 | 
			
		||||
 | 
			
		||||
        // Compare the generated DBML with the expected DBML
 | 
			
		||||
        expect(generatedDBML).toBe(expectedDBML);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle case 3 diagram', { timeout: 30000 }, async () => {
 | 
			
		||||
        // Read the JSON file
 | 
			
		||||
        const jsonPath = path.join(__dirname, 'cases', '3.json');
 | 
			
		||||
        const jsonContent = fs.readFileSync(jsonPath, 'utf-8');
 | 
			
		||||
 | 
			
		||||
        // Parse the JSON and convert to diagram
 | 
			
		||||
        const diagram = diagramFromJSONInput(jsonContent);
 | 
			
		||||
 | 
			
		||||
        // Generate DBML from the diagram
 | 
			
		||||
        const result = generateDBMLFromDiagram(diagram);
 | 
			
		||||
        const generatedDBML = result.standardDbml;
 | 
			
		||||
 | 
			
		||||
        // Read the expected DBML file
 | 
			
		||||
        const dbmlPath = path.join(__dirname, 'cases', '3.dbml');
 | 
			
		||||
        const expectedDBML = fs.readFileSync(dbmlPath, 'utf-8');
 | 
			
		||||
 | 
			
		||||
        // Compare the generated DBML with the expected DBML
 | 
			
		||||
        expect(generatedDBML).toBe(expectedDBML);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { importer } from '@dbml/core';
 | 
			
		||||
import { exportBaseSQL } from '@/lib/data/sql-export/export-sql-script';
 | 
			
		||||
import { exportBaseSQL } from '@/lib/data/export-metadata/export-sql-script';
 | 
			
		||||
import type { Diagram } from '@/lib/domain/diagram';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import type { DBTable } from '@/lib/domain/db-table';
 | 
			
		||||
@@ -596,13 +596,6 @@ const normalizeCharTypeFormat = (dbml: string): string => {
 | 
			
		||||
        .replace(/character \(([0-9]+)\)/g, 'character($1)');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Fix array types that are incorrectly quoted by DBML importer
 | 
			
		||||
const fixArrayTypes = (dbml: string): string => {
 | 
			
		||||
    // Remove quotes around array types like "text[]" -> text[]
 | 
			
		||||
    // Matches patterns like: "fieldname" "type[]" and replaces with "fieldname" type[]
 | 
			
		||||
    return dbml.replace(/(\s+"[^"]+"\s+)"([^"\s]+\[\])"/g, '$1$2');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Fix table definitions with incorrect bracket syntax
 | 
			
		||||
const fixTableBracketSyntax = (dbml: string): string => {
 | 
			
		||||
    // Fix patterns like Table [schema].[table] to Table "schema"."table"
 | 
			
		||||
@@ -612,62 +605,6 @@ const fixTableBracketSyntax = (dbml: string): string => {
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Fix table names that have been broken across multiple lines
 | 
			
		||||
const fixMultilineTableNames = (dbml: string): string => {
 | 
			
		||||
    // Match Table declarations that might have line breaks in the table name
 | 
			
		||||
    // This regex captures:
 | 
			
		||||
    // - Table keyword
 | 
			
		||||
    // - Optional quoted schema with dot
 | 
			
		||||
    // - Table name that might be broken across lines (until the opening brace)
 | 
			
		||||
    return dbml.replace(
 | 
			
		||||
        /Table\s+((?:"[^"]*"\.)?"[^"]*(?:\n[^"]*)*")\s*\{/g,
 | 
			
		||||
        (_, tableName) => {
 | 
			
		||||
            // Remove line breaks within the table name
 | 
			
		||||
            const fixedTableName = tableName.replace(/\n\s*/g, '');
 | 
			
		||||
            return `Table ${fixedTableName} {`;
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Restore composite primary key names in the DBML
 | 
			
		||||
const restoreCompositePKNames = (dbml: string, tables: DBTable[]): string => {
 | 
			
		||||
    if (!tables || tables.length === 0) return dbml;
 | 
			
		||||
 | 
			
		||||
    let result = dbml;
 | 
			
		||||
 | 
			
		||||
    tables.forEach((table) => {
 | 
			
		||||
        // Check if this table has a PK index with a name
 | 
			
		||||
        const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey);
 | 
			
		||||
        if (pkIndex?.name) {
 | 
			
		||||
            const primaryKeyFields = table.fields.filter((f) => f.primaryKey);
 | 
			
		||||
            if (primaryKeyFields.length >= 1) {
 | 
			
		||||
                // Build the column list for the composite PK
 | 
			
		||||
                const columnList = primaryKeyFields
 | 
			
		||||
                    .map((f) => f.name)
 | 
			
		||||
                    .join(', ');
 | 
			
		||||
 | 
			
		||||
                // Build the table identifier pattern
 | 
			
		||||
                const tableIdentifier = table.schema
 | 
			
		||||
                    ? `"${table.schema.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\."${table.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`
 | 
			
		||||
                    : `"${table.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`;
 | 
			
		||||
 | 
			
		||||
                // Pattern to match the composite PK index line
 | 
			
		||||
                // Match patterns like: (col1, col2, col3) [pk]
 | 
			
		||||
                const pkPattern = new RegExp(
 | 
			
		||||
                    `(Table ${tableIdentifier} \\{[^}]*?Indexes \\{[^}]*?)(\\(${columnList.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\) \\[pk\\])`,
 | 
			
		||||
                    'gs'
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                // Replace with the named version
 | 
			
		||||
                const replacement = `$1(${columnList}) [pk, name: "${pkIndex.name}"]`;
 | 
			
		||||
                result = result.replace(pkPattern, replacement);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Restore schema information that may have been stripped by the DBML importer
 | 
			
		||||
const restoreTableSchemas = (dbml: string, tables: DBTable[]): string => {
 | 
			
		||||
    if (!tables || tables.length === 0) return dbml;
 | 
			
		||||
@@ -933,16 +870,14 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
 | 
			
		||||
            ...table,
 | 
			
		||||
            name: safeTableName,
 | 
			
		||||
            fields: processedFields,
 | 
			
		||||
            indexes: (table.indexes || [])
 | 
			
		||||
                .filter((index) => !index.isPrimaryKey) // Filter out PK indexes as they're handled separately
 | 
			
		||||
                .map((index) => ({
 | 
			
		||||
                    ...index,
 | 
			
		||||
                    name: index.name
 | 
			
		||||
                        ? /[^\w]/.test(index.name)
 | 
			
		||||
                            ? `"${index.name.replace(/"/g, '\\"')}"`
 | 
			
		||||
                            : index.name
 | 
			
		||||
                        : `idx_${Math.random().toString(36).substring(2, 8)}`,
 | 
			
		||||
                })),
 | 
			
		||||
            indexes: (table.indexes || []).map((index) => ({
 | 
			
		||||
                ...index,
 | 
			
		||||
                name: index.name
 | 
			
		||||
                    ? /[^\w]/.test(index.name)
 | 
			
		||||
                        ? `"${index.name.replace(/"/g, '\\"')}"`
 | 
			
		||||
                        : index.name
 | 
			
		||||
                    : `idx_${Math.random().toString(36).substring(2, 8)}`,
 | 
			
		||||
            })),
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -992,15 +927,11 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        standard = fixArrayTypes(
 | 
			
		||||
            normalizeCharTypeFormat(
 | 
			
		||||
                fixMultilineTableNames(
 | 
			
		||||
                    fixTableBracketSyntax(
 | 
			
		||||
                        importer.import(
 | 
			
		||||
                            baseScript,
 | 
			
		||||
                            databaseTypeToImportFormat(diagram.databaseType)
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
        standard = normalizeCharTypeFormat(
 | 
			
		||||
            fixTableBracketSyntax(
 | 
			
		||||
                importer.import(
 | 
			
		||||
                    baseScript,
 | 
			
		||||
                    databaseTypeToImportFormat(diagram.databaseType)
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
@@ -1008,17 +939,12 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
 | 
			
		||||
        // Restore schema information that may have been stripped by DBML importer
 | 
			
		||||
        standard = restoreTableSchemas(standard, uniqueTables);
 | 
			
		||||
 | 
			
		||||
        // Restore composite primary key names
 | 
			
		||||
        standard = restoreCompositePKNames(standard, uniqueTables);
 | 
			
		||||
 | 
			
		||||
        // Prepend Enum DBML to the standard output
 | 
			
		||||
        if (enumsDBML) {
 | 
			
		||||
            standard = enumsDBML + '\n\n' + standard;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        inline = fixArrayTypes(
 | 
			
		||||
            normalizeCharTypeFormat(convertToInlineRefs(standard))
 | 
			
		||||
        );
 | 
			
		||||
        inline = normalizeCharTypeFormat(convertToInlineRefs(standard));
 | 
			
		||||
 | 
			
		||||
        // Clean up excessive empty lines in both outputs
 | 
			
		||||
        standard = standard.replace(/\n\s*\n\s*\n/g, '\n\n');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,190 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { importDBMLToDiagram } from '../dbml-import';
 | 
			
		||||
import { exportPostgreSQL } from '@/lib/data/sql-export/export-per-type/postgresql';
 | 
			
		||||
import { exportMySQL } from '@/lib/data/sql-export/export-per-type/mysql';
 | 
			
		||||
import { exportMSSQL } from '@/lib/data/sql-export/export-per-type/mssql';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
 | 
			
		||||
describe('Composite Primary Key with Name', () => {
 | 
			
		||||
    it('should preserve composite primary key name in DBML import and SQL export', async () => {
 | 
			
		||||
        const dbmlContent = `
 | 
			
		||||
Table "landlord"."users_master_table" {
 | 
			
		||||
  "master_user_id" bigint [not null]
 | 
			
		||||
  "tenant_id" bigint [not null]
 | 
			
		||||
  "tenant_user_id" bigint [not null]
 | 
			
		||||
  "enabled" boolean
 | 
			
		||||
 | 
			
		||||
  Indexes {
 | 
			
		||||
    (master_user_id, tenant_id, tenant_user_id) [pk, name: "idx_users_master_table_master_user_id_tenant_id_tenant_user_id"]
 | 
			
		||||
    (tenant_id, tenant_user_id) [unique, name: "index_1"]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
        // Import DBML
 | 
			
		||||
        const diagram = await importDBMLToDiagram(dbmlContent, {
 | 
			
		||||
            databaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Check that the composite PK name was captured
 | 
			
		||||
        expect(diagram.tables).toBeDefined();
 | 
			
		||||
        const table = diagram.tables![0];
 | 
			
		||||
 | 
			
		||||
        // Check for the PK index
 | 
			
		||||
        const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey);
 | 
			
		||||
        expect(pkIndex).toBeDefined();
 | 
			
		||||
        expect(pkIndex!.name).toBe(
 | 
			
		||||
            'idx_users_master_table_master_user_id_tenant_id_tenant_user_id'
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Check that fields are marked as primary keys
 | 
			
		||||
        const pkFields = table.fields.filter((f) => f.primaryKey);
 | 
			
		||||
        expect(pkFields).toHaveLength(3);
 | 
			
		||||
        expect(pkFields.map((f) => f.name)).toEqual([
 | 
			
		||||
            'master_user_id',
 | 
			
		||||
            'tenant_id',
 | 
			
		||||
            'tenant_user_id',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        // Check that we have both the PK index and the unique index
 | 
			
		||||
        expect(table.indexes).toHaveLength(2);
 | 
			
		||||
        const uniqueIndex = table.indexes.find((idx) => !idx.isPrimaryKey);
 | 
			
		||||
        expect(uniqueIndex!.name).toBe('index_1');
 | 
			
		||||
        expect(uniqueIndex!.unique).toBe(true);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should export composite primary key with CONSTRAINT name in PostgreSQL', async () => {
 | 
			
		||||
        const dbmlContent = `
 | 
			
		||||
Table "users" {
 | 
			
		||||
  "id" bigint [not null]
 | 
			
		||||
  "tenant_id" bigint [not null]
 | 
			
		||||
  
 | 
			
		||||
  Indexes {
 | 
			
		||||
    (id, tenant_id) [pk, name: "pk_users_composite"]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
        const diagram = await importDBMLToDiagram(dbmlContent, {
 | 
			
		||||
            databaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const sqlScript = exportPostgreSQL({ diagram });
 | 
			
		||||
 | 
			
		||||
        // Check that the SQL contains the named constraint
 | 
			
		||||
        expect(sqlScript).toContain(
 | 
			
		||||
            'CONSTRAINT "pk_users_composite" PRIMARY KEY ("id", "tenant_id")'
 | 
			
		||||
        );
 | 
			
		||||
        expect(sqlScript).not.toContain('PRIMARY KEY ("id", "tenant_id"),'); // Should not have unnamed PK
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should export composite primary key with CONSTRAINT name in MySQL', async () => {
 | 
			
		||||
        const dbmlContent = `
 | 
			
		||||
Table "orders" {
 | 
			
		||||
  "order_id" int [not null]
 | 
			
		||||
  "product_id" int [not null]
 | 
			
		||||
  
 | 
			
		||||
  Indexes {
 | 
			
		||||
    (order_id, product_id) [pk, name: "orders_order_product_pk"]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
        const diagram = await importDBMLToDiagram(dbmlContent, {
 | 
			
		||||
            databaseType: DatabaseType.MYSQL,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const sqlScript = exportMySQL({ diagram });
 | 
			
		||||
 | 
			
		||||
        // Check that the SQL contains the named constraint
 | 
			
		||||
        expect(sqlScript).toContain(
 | 
			
		||||
            'CONSTRAINT `orders_order_product_pk` PRIMARY KEY (`order_id`, `product_id`)'
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should export composite primary key with CONSTRAINT name in MSSQL', async () => {
 | 
			
		||||
        const dbmlContent = `
 | 
			
		||||
Table "products" {
 | 
			
		||||
  "category_id" int [not null]
 | 
			
		||||
  "product_id" int [not null]
 | 
			
		||||
  
 | 
			
		||||
  Indexes {
 | 
			
		||||
    (category_id, product_id) [pk, name: "pk_products"]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
        const diagram = await importDBMLToDiagram(dbmlContent, {
 | 
			
		||||
            databaseType: DatabaseType.SQL_SERVER,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const sqlScript = exportMSSQL({ diagram });
 | 
			
		||||
 | 
			
		||||
        // Check that the SQL contains the named constraint
 | 
			
		||||
        expect(sqlScript).toContain(
 | 
			
		||||
            'CONSTRAINT [pk_products] PRIMARY KEY ([category_id], [product_id])'
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should merge duplicate PK index with name', async () => {
 | 
			
		||||
        const dbmlContent = `
 | 
			
		||||
Table "test" {
 | 
			
		||||
  "a" int [not null]
 | 
			
		||||
  "b" int [not null]
 | 
			
		||||
  
 | 
			
		||||
  Indexes {
 | 
			
		||||
    (a, b) [pk]
 | 
			
		||||
    (a, b) [name: "test_pk_name"]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
        const diagram = await importDBMLToDiagram(dbmlContent, {
 | 
			
		||||
            databaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        expect(diagram.tables).toBeDefined();
 | 
			
		||||
        const table = diagram.tables![0];
 | 
			
		||||
 | 
			
		||||
        // Should capture the name from the duplicate index
 | 
			
		||||
        const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey);
 | 
			
		||||
        expect(pkIndex).toBeDefined();
 | 
			
		||||
        expect(pkIndex!.name).toBe('test_pk_name');
 | 
			
		||||
 | 
			
		||||
        // Should only have the PK index
 | 
			
		||||
        expect(table.indexes).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
        // Fields should be marked as primary keys
 | 
			
		||||
        expect(table.fields.filter((f) => f.primaryKey)).toHaveLength(2);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle composite PK without name', async () => {
 | 
			
		||||
        const dbmlContent = `
 | 
			
		||||
Table "simple" {
 | 
			
		||||
  "x" int [not null]
 | 
			
		||||
  "y" int [not null]
 | 
			
		||||
  
 | 
			
		||||
  Indexes {
 | 
			
		||||
    (x, y) [pk]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
        const diagram = await importDBMLToDiagram(dbmlContent, {
 | 
			
		||||
            databaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        expect(diagram.tables).toBeDefined();
 | 
			
		||||
        const table = diagram.tables![0];
 | 
			
		||||
 | 
			
		||||
        // PK index should not exist for composite PK without name
 | 
			
		||||
        const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey);
 | 
			
		||||
        expect(pkIndex).toBeDefined();
 | 
			
		||||
 | 
			
		||||
        const sqlScript = exportPostgreSQL({ diagram });
 | 
			
		||||
 | 
			
		||||
        // Should have unnamed PRIMARY KEY
 | 
			
		||||
        expect(sqlScript).toContain('PRIMARY KEY ("x", "y")');
 | 
			
		||||
        expect(sqlScript).toContain('CONSTRAINT');
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -177,7 +177,7 @@ Table ranks {
 | 
			
		||||
            expect(wizardsTable?.fields).toHaveLength(11);
 | 
			
		||||
 | 
			
		||||
            // Check indexes
 | 
			
		||||
            expect(wizardsTable?.indexes).toHaveLength(3);
 | 
			
		||||
            expect(wizardsTable?.indexes).toHaveLength(2);
 | 
			
		||||
            const emailIndex = wizardsTable?.indexes.find((idx) =>
 | 
			
		||||
                idx.name.includes('email')
 | 
			
		||||
            );
 | 
			
		||||
@@ -920,7 +920,7 @@ Note dragon_note {
 | 
			
		||||
            expect(hoardsTable).toBeDefined();
 | 
			
		||||
 | 
			
		||||
            // Verify all indexes are imported correctly
 | 
			
		||||
            expect(hoardsTable?.indexes).toHaveLength(4); // 3 from DBML + 1 implicit PK index
 | 
			
		||||
            expect(hoardsTable?.indexes).toHaveLength(3); // Should have 3 indexes as defined in DBML
 | 
			
		||||
 | 
			
		||||
            // Verify named indexes
 | 
			
		||||
            const uniqueDragonIndex = hoardsTable?.indexes.find(
 | 
			
		||||
@@ -1119,7 +1119,7 @@ Table "public_3"."comments" {
 | 
			
		||||
            ).toBe('timestamp');
 | 
			
		||||
 | 
			
		||||
            // Check posts indexes thoroughly
 | 
			
		||||
            expect(postsTable?.indexes).toHaveLength(3);
 | 
			
		||||
            expect(postsTable?.indexes).toHaveLength(2);
 | 
			
		||||
 | 
			
		||||
            // Index 1: Composite unique index on (content, user_id)
 | 
			
		||||
            const compositeIndex = postsTable?.indexes.find(
 | 
			
		||||
@@ -1154,7 +1154,7 @@ Table "public_3"."comments" {
 | 
			
		||||
 | 
			
		||||
            // Check comments table
 | 
			
		||||
            expect(commentsTable?.fields).toHaveLength(5);
 | 
			
		||||
            expect(commentsTable?.indexes).toHaveLength(2);
 | 
			
		||||
            expect(commentsTable?.indexes).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
            // Index: Unique index on id
 | 
			
		||||
            const idIndex = commentsTable?.indexes.find(
 | 
			
		||||
 
 | 
			
		||||
@@ -154,7 +154,7 @@ Note note_1750185617764 {
 | 
			
		||||
 | 
			
		||||
            // Should not throw
 | 
			
		||||
            const parser = new Parser();
 | 
			
		||||
            expect(() => parser.parse(sanitized, 'dbmlv2')).not.toThrow();
 | 
			
		||||
            expect(() => parser.parse(sanitized, 'dbml')).not.toThrow();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
import type { CompilerError } from '@dbml/core/types/parse/error';
 | 
			
		||||
 | 
			
		||||
export interface DBMLError {
 | 
			
		||||
    message: string;
 | 
			
		||||
    line: number;
 | 
			
		||||
@@ -11,12 +9,28 @@ export function parseDBMLError(error: unknown): DBMLError | null {
 | 
			
		||||
        if (typeof error === 'string') {
 | 
			
		||||
            const parsed = JSON.parse(error);
 | 
			
		||||
            if (parsed.diags?.[0]) {
 | 
			
		||||
                const parsedError = parsed as CompilerError;
 | 
			
		||||
                return getFirstErrorFromCompileError(parsedError);
 | 
			
		||||
                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 CompilerError;
 | 
			
		||||
            return getFirstErrorFromCompileError(parsed);
 | 
			
		||||
            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);
 | 
			
		||||
@@ -24,25 +38,3 @@ export function parseDBMLError(error: unknown): DBMLError | null {
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getFirstErrorFromCompileError = (
 | 
			
		||||
    error: CompilerError
 | 
			
		||||
): DBMLError | null => {
 | 
			
		||||
    const diags = (error.diags ?? []).sort((a, b) => {
 | 
			
		||||
        if (a.location.start.line === b.location.start.line) {
 | 
			
		||||
            return a.location.start.column - b.location.start.column;
 | 
			
		||||
        }
 | 
			
		||||
        return a.location.start.line - b.location.start.line;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (diags.length > 0) {
 | 
			
		||||
        const firstDiag = diags[0];
 | 
			
		||||
        return {
 | 
			
		||||
            message: firstDiag.message,
 | 
			
		||||
            line: firstDiag.location.start.line,
 | 
			
		||||
            column: firstDiag.location.start.column,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import { findDataTypeDataById } from '@/lib/data/data-types/data-types';
 | 
			
		||||
import { defaultTableColor } from '@/lib/colors';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import type Field from '@dbml/core/types/model_structure/field';
 | 
			
		||||
import { getTableIndexesWithPrimaryKey, type DBIndex } from '@/lib/domain';
 | 
			
		||||
import type { DBIndex } from '@/lib/domain';
 | 
			
		||||
import {
 | 
			
		||||
    DBCustomTypeKind,
 | 
			
		||||
    type DBCustomType,
 | 
			
		||||
@@ -100,7 +100,6 @@ interface DBMLIndex {
 | 
			
		||||
    columns: (string | DBMLIndexColumn)[];
 | 
			
		||||
    unique?: boolean;
 | 
			
		||||
    name?: string;
 | 
			
		||||
    pk?: boolean; // Primary key index flag
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface DBMLTable {
 | 
			
		||||
@@ -223,7 +222,7 @@ export const importDBMLToDiagram = async (
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const parsedData = parser.parse(sanitizedContent, 'dbmlv2');
 | 
			
		||||
        const parsedData = parser.parse(sanitizedContent, 'dbml');
 | 
			
		||||
 | 
			
		||||
        // Handle case where no schemas are found
 | 
			
		||||
        if (!parsedData.schemas || parsedData.schemas.length === 0) {
 | 
			
		||||
@@ -388,19 +387,15 @@ export const importDBMLToDiagram = async (
 | 
			
		||||
                                    );
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                // For PK indexes, only use the name if explicitly provided
 | 
			
		||||
                                // For regular indexes, generate a default name if needed
 | 
			
		||||
                                // Generate a consistent index name
 | 
			
		||||
                                const indexName =
 | 
			
		||||
                                    dbmlIndex.name ||
 | 
			
		||||
                                    (!dbmlIndex.pk
 | 
			
		||||
                                        ? `idx_${table.name}_${indexColumns.join('_')}`
 | 
			
		||||
                                        : undefined);
 | 
			
		||||
                                    `idx_${table.name}_${indexColumns.join('_')}`;
 | 
			
		||||
 | 
			
		||||
                                return {
 | 
			
		||||
                                    columns: indexColumns,
 | 
			
		||||
                                    unique: dbmlIndex.unique || false,
 | 
			
		||||
                                    name: indexName,
 | 
			
		||||
                                    pk: Boolean(dbmlIndex.pk) || false,
 | 
			
		||||
                                };
 | 
			
		||||
                            }) || [],
 | 
			
		||||
                    });
 | 
			
		||||
@@ -489,126 +484,29 @@ export const importDBMLToDiagram = async (
 | 
			
		||||
                };
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Process composite primary keys from indexes with [pk] attribute
 | 
			
		||||
            let compositePKFields: string[] = [];
 | 
			
		||||
            let compositePKIndexName: string | undefined;
 | 
			
		||||
 | 
			
		||||
            // Find PK indexes and mark fields as primary keys
 | 
			
		||||
            table.indexes?.forEach((dbmlIndex) => {
 | 
			
		||||
                if (dbmlIndex.pk) {
 | 
			
		||||
                    // Extract column names from the columns array
 | 
			
		||||
                    compositePKFields = dbmlIndex.columns.map((col) =>
 | 
			
		||||
                        typeof col === 'string' ? col : col.value
 | 
			
		||||
                    );
 | 
			
		||||
                    // Only store the name if it was explicitly provided (not undefined)
 | 
			
		||||
                    if (dbmlIndex.name) {
 | 
			
		||||
                        compositePKIndexName = dbmlIndex.name;
 | 
			
		||||
                    }
 | 
			
		||||
                    // Mark fields as primary keys
 | 
			
		||||
                    dbmlIndex.columns.forEach((col) => {
 | 
			
		||||
                        const columnName =
 | 
			
		||||
                            typeof col === 'string' ? col : col.value;
 | 
			
		||||
                        const field = fields.find((f) => f.name === columnName);
 | 
			
		||||
                        if (field) {
 | 
			
		||||
                            field.primaryKey = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // If we found a PK without a name, look for a duplicate index with just a name
 | 
			
		||||
            if (compositePKFields.length > 0 && !compositePKIndexName) {
 | 
			
		||||
                table.indexes?.forEach((dbmlIndex) => {
 | 
			
		||||
                    if (
 | 
			
		||||
                        !dbmlIndex.pk &&
 | 
			
		||||
                        dbmlIndex.name &&
 | 
			
		||||
                        dbmlIndex.columns.length === compositePKFields.length
 | 
			
		||||
                    ) {
 | 
			
		||||
                        // Check if columns match
 | 
			
		||||
                        const indexColumns = dbmlIndex.columns.map((col) =>
 | 
			
		||||
                            typeof col === 'string' ? col : col.value
 | 
			
		||||
                        );
 | 
			
		||||
                        if (
 | 
			
		||||
                            indexColumns.every(
 | 
			
		||||
                                (col, i) => col === compositePKFields[i]
 | 
			
		||||
                            )
 | 
			
		||||
                        ) {
 | 
			
		||||
                            compositePKIndexName = dbmlIndex.name;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Convert DBML indexes to ChartDB indexes (excluding PK indexes and their duplicates)
 | 
			
		||||
            // Convert DBML indexes to ChartDB indexes
 | 
			
		||||
            const indexes: DBIndex[] =
 | 
			
		||||
                table.indexes
 | 
			
		||||
                    ?.filter((dbmlIndex) => {
 | 
			
		||||
                        // Skip PK indexes - we'll handle them separately
 | 
			
		||||
                        if (dbmlIndex.pk) return false;
 | 
			
		||||
 | 
			
		||||
                        // Skip duplicate indexes that match the composite PK
 | 
			
		||||
                        // (when user has both [pk] and [name: "..."] on same fields)
 | 
			
		||||
                        if (
 | 
			
		||||
                            compositePKFields.length > 0 &&
 | 
			
		||||
                            dbmlIndex.columns.length ===
 | 
			
		||||
                                compositePKFields.length &&
 | 
			
		||||
                            dbmlIndex.columns.every((col, i) => {
 | 
			
		||||
                                const colName =
 | 
			
		||||
                                    typeof col === 'string' ? col : col.value;
 | 
			
		||||
                                return colName === compositePKFields[i];
 | 
			
		||||
                            })
 | 
			
		||||
                        ) {
 | 
			
		||||
                            return false;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        return true;
 | 
			
		||||
                    })
 | 
			
		||||
                    .map((dbmlIndex) => {
 | 
			
		||||
                        const fieldIds = dbmlIndex.columns.map((columnName) => {
 | 
			
		||||
                            const field = fields.find(
 | 
			
		||||
                                (f) => f.name === columnName
 | 
			
		||||
                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}`
 | 
			
		||||
                            );
 | 
			
		||||
                            if (!field) {
 | 
			
		||||
                                throw new Error(
 | 
			
		||||
                                    `Index references non-existent column: ${columnName}`
 | 
			
		||||
                                );
 | 
			
		||||
                            }
 | 
			
		||||
                            return field.id;
 | 
			
		||||
                        });
 | 
			
		||||
                        }
 | 
			
		||||
                        return field.id;
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                        return {
 | 
			
		||||
                            id: generateId(),
 | 
			
		||||
                            name:
 | 
			
		||||
                                dbmlIndex.name ||
 | 
			
		||||
                                `idx_${table.name}_${(dbmlIndex.columns as string[]).join('_')}`,
 | 
			
		||||
                            fieldIds,
 | 
			
		||||
                            unique: dbmlIndex.unique || false,
 | 
			
		||||
                            createdAt: Date.now(),
 | 
			
		||||
                        };
 | 
			
		||||
                    }) || [];
 | 
			
		||||
 | 
			
		||||
            // Add PK as an index if it exists and has a name
 | 
			
		||||
            // Only create the PK index if there's an explicit name for it
 | 
			
		||||
            if (compositePKFields.length >= 1 && compositePKIndexName) {
 | 
			
		||||
                const pkFieldIds = compositePKFields.map((columnName) => {
 | 
			
		||||
                    const field = fields.find((f) => f.name === columnName);
 | 
			
		||||
                    if (!field) {
 | 
			
		||||
                        throw new Error(
 | 
			
		||||
                            `PK references non-existent column: ${columnName}`
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
                    return field.id;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                indexes.push({
 | 
			
		||||
                    id: generateId(),
 | 
			
		||||
                    name: compositePKIndexName,
 | 
			
		||||
                    fieldIds: pkFieldIds,
 | 
			
		||||
                    unique: true,
 | 
			
		||||
                    isPrimaryKey: true,
 | 
			
		||||
                    createdAt: Date.now(),
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
                    return {
 | 
			
		||||
                        id: generateId(),
 | 
			
		||||
                        name:
 | 
			
		||||
                            dbmlIndex.name ||
 | 
			
		||||
                            `idx_${table.name}_${(dbmlIndex.columns as string[]).join('_')}`,
 | 
			
		||||
                        fieldIds,
 | 
			
		||||
                        unique: dbmlIndex.unique || false,
 | 
			
		||||
                        createdAt: Date.now(),
 | 
			
		||||
                    };
 | 
			
		||||
                }) || [];
 | 
			
		||||
 | 
			
		||||
            // Extract table note/comment
 | 
			
		||||
            let tableComment: string | undefined;
 | 
			
		||||
@@ -623,7 +521,7 @@ export const importDBMLToDiagram = async (
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const tableToReturn: DBTable = {
 | 
			
		||||
            return {
 | 
			
		||||
                id: generateId(),
 | 
			
		||||
                name: table.name.replace(/['"]/g, ''),
 | 
			
		||||
                schema:
 | 
			
		||||
@@ -642,13 +540,6 @@ export const importDBMLToDiagram = async (
 | 
			
		||||
                createdAt: Date.now(),
 | 
			
		||||
                comments: tableComment,
 | 
			
		||||
            } satisfies DBTable;
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                ...tableToReturn,
 | 
			
		||||
                indexes: getTableIndexesWithPrimaryKey({
 | 
			
		||||
                    table: tableToReturn,
 | 
			
		||||
                }),
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Create relationships using the refs
 | 
			
		||||
 
 | 
			
		||||
@@ -1,192 +0,0 @@
 | 
			
		||||
import { describe, it, expect } from 'vitest';
 | 
			
		||||
import { createTablesFromMetadata } from '@/lib/data/import-metadata/import/tables';
 | 
			
		||||
import { DatabaseType } from '../database-type';
 | 
			
		||||
import type { DatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
 | 
			
		||||
 | 
			
		||||
describe('Composite Primary Key Name from Metadata Import', () => {
 | 
			
		||||
    it('should capture composite primary key name from metadata indexes', () => {
 | 
			
		||||
        const metadata: DatabaseMetadata = {
 | 
			
		||||
            database_name: 'test_db',
 | 
			
		||||
            version: '',
 | 
			
		||||
            fk_info: [],
 | 
			
		||||
            pk_info: [
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    column: 'master_user_id',
 | 
			
		||||
                    pk_def: 'PRIMARY KEY (master_user_id, tenant_id, tenant_user_id)',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    column: 'tenant_id',
 | 
			
		||||
                    pk_def: 'PRIMARY KEY (master_user_id, tenant_id, tenant_user_id)',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    column: 'tenant_user_id',
 | 
			
		||||
                    pk_def: 'PRIMARY KEY (master_user_id, tenant_id, tenant_user_id)',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            columns: [
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    name: 'master_user_id',
 | 
			
		||||
                    ordinal_position: 1,
 | 
			
		||||
                    type: 'bigint',
 | 
			
		||||
                    character_maximum_length: null,
 | 
			
		||||
                    precision: null,
 | 
			
		||||
                    nullable: false,
 | 
			
		||||
                    default: '',
 | 
			
		||||
                    collation: '',
 | 
			
		||||
                    comment: '',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    name: 'tenant_id',
 | 
			
		||||
                    ordinal_position: 2,
 | 
			
		||||
                    type: 'bigint',
 | 
			
		||||
                    character_maximum_length: null,
 | 
			
		||||
                    precision: null,
 | 
			
		||||
                    nullable: false,
 | 
			
		||||
                    default: '',
 | 
			
		||||
                    collation: '',
 | 
			
		||||
                    comment: '',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    name: 'tenant_user_id',
 | 
			
		||||
                    ordinal_position: 3,
 | 
			
		||||
                    type: 'bigint',
 | 
			
		||||
                    character_maximum_length: null,
 | 
			
		||||
                    precision: null,
 | 
			
		||||
                    nullable: false,
 | 
			
		||||
                    default: '',
 | 
			
		||||
                    collation: '',
 | 
			
		||||
                    comment: '',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    name: 'enabled',
 | 
			
		||||
                    ordinal_position: 4,
 | 
			
		||||
                    type: 'boolean',
 | 
			
		||||
                    character_maximum_length: null,
 | 
			
		||||
                    precision: null,
 | 
			
		||||
                    nullable: true,
 | 
			
		||||
                    default: '',
 | 
			
		||||
                    collation: '',
 | 
			
		||||
                    comment: '',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            indexes: [
 | 
			
		||||
                // The composite PK index named "moshe"
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    name: 'moshe',
 | 
			
		||||
                    column: 'master_user_id',
 | 
			
		||||
                    index_type: 'btree',
 | 
			
		||||
                    cardinality: 0,
 | 
			
		||||
                    size: 8192,
 | 
			
		||||
                    unique: true,
 | 
			
		||||
                    column_position: 1,
 | 
			
		||||
                    direction: 'asc',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    name: 'moshe',
 | 
			
		||||
                    column: 'tenant_id',
 | 
			
		||||
                    index_type: 'btree',
 | 
			
		||||
                    cardinality: 0,
 | 
			
		||||
                    size: 8192,
 | 
			
		||||
                    unique: true,
 | 
			
		||||
                    column_position: 2,
 | 
			
		||||
                    direction: 'asc',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    name: 'moshe',
 | 
			
		||||
                    column: 'tenant_user_id',
 | 
			
		||||
                    index_type: 'btree',
 | 
			
		||||
                    cardinality: 0,
 | 
			
		||||
                    size: 8192,
 | 
			
		||||
                    unique: true,
 | 
			
		||||
                    column_position: 3,
 | 
			
		||||
                    direction: 'asc',
 | 
			
		||||
                },
 | 
			
		||||
                // Another unique index
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    name: 'users_master_table_index_1',
 | 
			
		||||
                    column: 'tenant_id',
 | 
			
		||||
                    index_type: 'btree',
 | 
			
		||||
                    cardinality: 0,
 | 
			
		||||
                    size: 8192,
 | 
			
		||||
                    unique: true,
 | 
			
		||||
                    column_position: 1,
 | 
			
		||||
                    direction: 'asc',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    name: 'users_master_table_index_1',
 | 
			
		||||
                    column: 'tenant_user_id',
 | 
			
		||||
                    index_type: 'btree',
 | 
			
		||||
                    cardinality: 0,
 | 
			
		||||
                    size: 8192,
 | 
			
		||||
                    unique: true,
 | 
			
		||||
                    column_position: 2,
 | 
			
		||||
                    direction: 'asc',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            tables: [
 | 
			
		||||
                {
 | 
			
		||||
                    schema: 'landlord',
 | 
			
		||||
                    table: 'users_master_table',
 | 
			
		||||
                    rows: 0,
 | 
			
		||||
                    type: 'BASE TABLE',
 | 
			
		||||
                    engine: '',
 | 
			
		||||
                    collation: '',
 | 
			
		||||
                    comment: '',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            views: [],
 | 
			
		||||
            custom_types: [],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const tables = createTablesFromMetadata({
 | 
			
		||||
            databaseMetadata: metadata,
 | 
			
		||||
            databaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        expect(tables).toHaveLength(1);
 | 
			
		||||
        const table = tables[0];
 | 
			
		||||
 | 
			
		||||
        // Check that the composite PK name was captured as "moshe" in the PK index
 | 
			
		||||
        const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey);
 | 
			
		||||
        expect(pkIndex).toBeDefined();
 | 
			
		||||
        expect(pkIndex!.name).toBe('moshe');
 | 
			
		||||
 | 
			
		||||
        // Check that primary key fields are marked correctly
 | 
			
		||||
        const pkFields = table.fields.filter((f) => f.primaryKey);
 | 
			
		||||
        expect(pkFields).toHaveLength(3);
 | 
			
		||||
        expect(pkFields.map((f) => f.name).sort()).toEqual([
 | 
			
		||||
            'master_user_id',
 | 
			
		||||
            'tenant_id',
 | 
			
		||||
            'tenant_user_id',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        // Check that we have both the PK index and the unique index
 | 
			
		||||
        expect(table.indexes).toHaveLength(2);
 | 
			
		||||
        const uniqueIndex = table.indexes.find((idx) => !idx.isPrimaryKey);
 | 
			
		||||
        expect(uniqueIndex!.name).toBe('users_master_table_index_1');
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,4 +1,7 @@
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
import type { DBCustomTypeInfo } from '@/lib/data/import-metadata/metadata-types/custom-type-info';
 | 
			
		||||
import { generateId } from '../utils';
 | 
			
		||||
import { schemaNameToDomainSchemaName } from './db-schema';
 | 
			
		||||
 | 
			
		||||
export enum DBCustomTypeKind {
 | 
			
		||||
    enum = 'enum',
 | 
			
		||||
@@ -12,12 +15,12 @@ export interface DBCustomTypeField {
 | 
			
		||||
 | 
			
		||||
export interface DBCustomType {
 | 
			
		||||
    id: string;
 | 
			
		||||
    schema?: string | null;
 | 
			
		||||
    schema?: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    kind: DBCustomTypeKind;
 | 
			
		||||
    values?: string[] | null; // For enum types
 | 
			
		||||
    fields?: DBCustomTypeField[] | null; // For composite types
 | 
			
		||||
    order?: number | null;
 | 
			
		||||
    values?: string[]; // For enum types
 | 
			
		||||
    fields?: DBCustomTypeField[]; // For composite types
 | 
			
		||||
    order?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const dbCustomTypeFieldSchema = z.object({
 | 
			
		||||
@@ -27,14 +30,30 @@ export const dbCustomTypeFieldSchema = z.object({
 | 
			
		||||
 | 
			
		||||
export const dbCustomTypeSchema: z.ZodType<DBCustomType> = z.object({
 | 
			
		||||
    id: z.string(),
 | 
			
		||||
    schema: z.string().or(z.null()).optional(),
 | 
			
		||||
    schema: z.string(),
 | 
			
		||||
    name: z.string(),
 | 
			
		||||
    kind: z.nativeEnum(DBCustomTypeKind),
 | 
			
		||||
    values: z.array(z.string()).or(z.null()).optional(),
 | 
			
		||||
    fields: z.array(dbCustomTypeFieldSchema).or(z.null()).optional(),
 | 
			
		||||
    order: z.number().or(z.null()).optional(),
 | 
			
		||||
    values: z.array(z.string()).optional(),
 | 
			
		||||
    fields: z.array(dbCustomTypeFieldSchema).optional(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const createCustomTypesFromMetadata = ({
 | 
			
		||||
    customTypes,
 | 
			
		||||
}: {
 | 
			
		||||
    customTypes: DBCustomTypeInfo[];
 | 
			
		||||
}): DBCustomType[] => {
 | 
			
		||||
    return customTypes.map((customType) => {
 | 
			
		||||
        return {
 | 
			
		||||
            id: generateId(),
 | 
			
		||||
            schema: schemaNameToDomainSchemaName(customType.schema),
 | 
			
		||||
            name: customType.type,
 | 
			
		||||
            kind: customType.kind as DBCustomTypeKind,
 | 
			
		||||
            values: customType.values,
 | 
			
		||||
            fields: customType.fields,
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const customTypeKindToLabel: Record<DBCustomTypeKind, string> = {
 | 
			
		||||
    enum: 'Enum',
 | 
			
		||||
    composite: 'Composite',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,10 @@
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
import type { ViewInfo } from '../data/import-metadata/metadata-types/view-info';
 | 
			
		||||
import { DatabaseType } from './database-type';
 | 
			
		||||
import { schemaNameToDomainSchemaName } from './db-schema';
 | 
			
		||||
import { decodeViewDefinition, type DBTable } from './db-table';
 | 
			
		||||
import { generateId } from '@/lib/utils';
 | 
			
		||||
import type { AST } from 'node-sql-parser';
 | 
			
		||||
 | 
			
		||||
export interface DBDependency {
 | 
			
		||||
    id: string;
 | 
			
		||||
@@ -17,3 +23,348 @@ export const dbDependencySchema: z.ZodType<DBDependency> = z.object({
 | 
			
		||||
    dependentTableId: z.string(),
 | 
			
		||||
    createdAt: z.number(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const astDatabaseTypes: Record<DatabaseType, string> = {
 | 
			
		||||
    [DatabaseType.POSTGRESQL]: 'postgresql',
 | 
			
		||||
    [DatabaseType.MYSQL]: 'postgresql',
 | 
			
		||||
    [DatabaseType.MARIADB]: 'postgresql',
 | 
			
		||||
    [DatabaseType.GENERIC]: 'postgresql',
 | 
			
		||||
    [DatabaseType.SQLITE]: 'postgresql',
 | 
			
		||||
    [DatabaseType.SQL_SERVER]: 'postgresql',
 | 
			
		||||
    [DatabaseType.CLICKHOUSE]: 'postgresql',
 | 
			
		||||
    [DatabaseType.COCKROACHDB]: 'postgresql',
 | 
			
		||||
    [DatabaseType.ORACLE]: 'postgresql',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createDependenciesFromMetadata = async ({
 | 
			
		||||
    views,
 | 
			
		||||
    tables,
 | 
			
		||||
    databaseType,
 | 
			
		||||
}: {
 | 
			
		||||
    views: ViewInfo[];
 | 
			
		||||
    tables: DBTable[];
 | 
			
		||||
    databaseType: DatabaseType;
 | 
			
		||||
}): Promise<DBDependency[]> => {
 | 
			
		||||
    if (!views || views.length === 0) {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { Parser } = await import('node-sql-parser');
 | 
			
		||||
    const parser = new Parser();
 | 
			
		||||
 | 
			
		||||
    const dependencies = views
 | 
			
		||||
        .flatMap((view) => {
 | 
			
		||||
            const viewSchema = schemaNameToDomainSchemaName(view.schema);
 | 
			
		||||
            const viewTable = tables.find(
 | 
			
		||||
                (table) =>
 | 
			
		||||
                    table.name === view.view_name && viewSchema === table.schema
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (!viewTable) {
 | 
			
		||||
                console.warn(
 | 
			
		||||
                    `Source table for view ${view.view_name} not found (schema: ${viewSchema})`
 | 
			
		||||
                );
 | 
			
		||||
                return []; // Skip this view and proceed to the next
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (view.view_definition) {
 | 
			
		||||
                try {
 | 
			
		||||
                    const decodedViewDefinition = decodeViewDefinition(
 | 
			
		||||
                        databaseType,
 | 
			
		||||
                        view.view_definition
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    let modifiedViewDefinition = '';
 | 
			
		||||
                    if (
 | 
			
		||||
                        databaseType === DatabaseType.MYSQL ||
 | 
			
		||||
                        databaseType === DatabaseType.MARIADB
 | 
			
		||||
                    ) {
 | 
			
		||||
                        modifiedViewDefinition = preprocessViewDefinitionMySQL(
 | 
			
		||||
                            decodedViewDefinition
 | 
			
		||||
                        );
 | 
			
		||||
                    } else if (databaseType === DatabaseType.SQL_SERVER) {
 | 
			
		||||
                        modifiedViewDefinition =
 | 
			
		||||
                            preprocessViewDefinitionSQLServer(
 | 
			
		||||
                                decodedViewDefinition
 | 
			
		||||
                            );
 | 
			
		||||
                    } else {
 | 
			
		||||
                        modifiedViewDefinition = preprocessViewDefinition(
 | 
			
		||||
                            decodedViewDefinition
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Parse using the appropriate dialect
 | 
			
		||||
                    const ast = parser.astify(modifiedViewDefinition, {
 | 
			
		||||
                        database: astDatabaseTypes[databaseType],
 | 
			
		||||
                        type: 'select', // Parsing a SELECT statement
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    let relatedTables = extractTablesFromAST(ast);
 | 
			
		||||
 | 
			
		||||
                    // Filter out duplicate tables without schema
 | 
			
		||||
                    relatedTables = filterDuplicateTables(relatedTables);
 | 
			
		||||
 | 
			
		||||
                    return relatedTables.map((relTable) => {
 | 
			
		||||
                        const relSchema = relTable.schema || view.schema; // Use view's schema if relSchema is undefined
 | 
			
		||||
                        const relTableName = relTable.tableName;
 | 
			
		||||
 | 
			
		||||
                        const table = tables.find(
 | 
			
		||||
                            (table) =>
 | 
			
		||||
                                table.name === relTableName &&
 | 
			
		||||
                                (table.schema || '') === relSchema
 | 
			
		||||
                        );
 | 
			
		||||
 | 
			
		||||
                        if (table) {
 | 
			
		||||
                            const dependency: DBDependency = {
 | 
			
		||||
                                id: generateId(),
 | 
			
		||||
                                schema: view.schema,
 | 
			
		||||
                                tableId: table.id, // related table
 | 
			
		||||
                                dependentSchema: table.schema,
 | 
			
		||||
                                dependentTableId: viewTable.id, // dependent view
 | 
			
		||||
                                createdAt: Date.now(),
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
                            return dependency;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            console.warn(
 | 
			
		||||
                                `Dependent table ${relSchema}.${relTableName} not found for view ${view.schema}.${view.view_name}`
 | 
			
		||||
                            );
 | 
			
		||||
                            return null;
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                    console.error(
 | 
			
		||||
                        `Error parsing view ${view.schema}.${view.view_name}:`,
 | 
			
		||||
                        error
 | 
			
		||||
                    );
 | 
			
		||||
                    return [];
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                console.warn(
 | 
			
		||||
                    `View definition missing for ${view.schema}.${view.view_name}`
 | 
			
		||||
                );
 | 
			
		||||
                return [];
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .filter((dependency) => dependency !== null);
 | 
			
		||||
 | 
			
		||||
    return dependencies;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Add this new function to filter out duplicate tables
 | 
			
		||||
function filterDuplicateTables(
 | 
			
		||||
    tables: { schema?: string; tableName: string }[]
 | 
			
		||||
): { schema?: string; tableName: string }[] {
 | 
			
		||||
    const tableMap = new Map<string, { schema?: string; tableName: string }>();
 | 
			
		||||
 | 
			
		||||
    for (const table of tables) {
 | 
			
		||||
        const key = table.tableName;
 | 
			
		||||
        const existingTable = tableMap.get(key);
 | 
			
		||||
 | 
			
		||||
        if (!existingTable || (table.schema && !existingTable.schema)) {
 | 
			
		||||
            tableMap.set(key, table);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Array.from(tableMap.values());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Preprocess the view_definition to remove schema from CREATE VIEW
 | 
			
		||||
function preprocessViewDefinition(viewDefinition: string): string {
 | 
			
		||||
    if (!viewDefinition) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove leading and trailing whitespace
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\s+/g, ' ').trim();
 | 
			
		||||
 | 
			
		||||
    // Replace escaped double quotes with regular ones
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\\"/g, '"');
 | 
			
		||||
 | 
			
		||||
    // Replace 'CREATE MATERIALIZED VIEW' with 'CREATE VIEW'
 | 
			
		||||
    viewDefinition = viewDefinition.replace(
 | 
			
		||||
        /CREATE\s+MATERIALIZED\s+VIEW/i,
 | 
			
		||||
        'CREATE VIEW'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Regular expression to match 'CREATE VIEW [schema.]view_name [ (column definitions) ] AS'
 | 
			
		||||
    // This regex captures the view name and skips any content between the view name and 'AS'
 | 
			
		||||
    const regex =
 | 
			
		||||
        /CREATE\s+VIEW\s+(?:(?:`[^`]+`|"[^"]+"|\w+)\.)?(?:`([^`]+)`|"([^"]+)"|(\w+))[\s\S]*?\bAS\b\s+/i;
 | 
			
		||||
 | 
			
		||||
    const match = viewDefinition.match(regex);
 | 
			
		||||
    let modifiedDefinition: string;
 | 
			
		||||
 | 
			
		||||
    if (match) {
 | 
			
		||||
        const viewName = match[1] || match[2] || match[3];
 | 
			
		||||
        // Extract the SQL after the 'AS' keyword
 | 
			
		||||
        const restOfDefinition = viewDefinition.substring(
 | 
			
		||||
            match.index! + match[0].length
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Replace double-quoted identifiers with unquoted ones
 | 
			
		||||
        let modifiedSQL = restOfDefinition.replace(/"(\w+)"/g, '$1');
 | 
			
		||||
 | 
			
		||||
        // Replace '::' type casts with 'CAST' expressions
 | 
			
		||||
        modifiedSQL = modifiedSQL.replace(
 | 
			
		||||
            /\(([^()]+)\)::(\w+)/g,
 | 
			
		||||
            'CAST($1 AS $2)'
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Remove ClickHouse-specific syntax that may still be present
 | 
			
		||||
        // For example, remove SETTINGS clauses inside the SELECT statement
 | 
			
		||||
        modifiedSQL = modifiedSQL.replace(/\bSETTINGS\b[\s\S]*$/i, '');
 | 
			
		||||
 | 
			
		||||
        modifiedDefinition = `CREATE VIEW ${viewName} AS ${modifiedSQL}`;
 | 
			
		||||
    } else {
 | 
			
		||||
        console.warn('Could not preprocess view definition:', viewDefinition);
 | 
			
		||||
        modifiedDefinition = viewDefinition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return modifiedDefinition;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Preprocess the view_definition for SQL Server
 | 
			
		||||
function preprocessViewDefinitionSQLServer(viewDefinition: string): string {
 | 
			
		||||
    if (!viewDefinition) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove BOM if present
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/^\uFEFF/, '');
 | 
			
		||||
 | 
			
		||||
    // Normalize whitespace
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\s+/g, ' ').trim();
 | 
			
		||||
 | 
			
		||||
    // Remove square brackets and replace with double quotes
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\[([^\]]+)\]/g, '"$1"');
 | 
			
		||||
 | 
			
		||||
    // Remove database names from fully qualified identifiers
 | 
			
		||||
    viewDefinition = viewDefinition.replace(
 | 
			
		||||
        /"([a-zA-Z0-9_]+)"\."([a-zA-Z0-9_]+)"\."([a-zA-Z0-9_]+)"/g,
 | 
			
		||||
        '"$2"."$3"'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Replace SQL Server functions with PostgreSQL equivalents
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\bGETDATE\(\)/gi, 'NOW()');
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\bISNULL\(/gi, 'COALESCE(');
 | 
			
		||||
 | 
			
		||||
    // Replace 'TOP N' with 'LIMIT N' at the end of the query
 | 
			
		||||
    const topMatch = viewDefinition.match(/SELECT\s+TOP\s+(\d+)/i);
 | 
			
		||||
    if (topMatch) {
 | 
			
		||||
        const topN = topMatch[1];
 | 
			
		||||
        viewDefinition = viewDefinition.replace(
 | 
			
		||||
            /SELECT\s+TOP\s+\d+/i,
 | 
			
		||||
            'SELECT'
 | 
			
		||||
        );
 | 
			
		||||
        viewDefinition = viewDefinition.replace(/;+\s*$/, ''); // Remove semicolons at the end
 | 
			
		||||
        viewDefinition += ` LIMIT ${topN}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/\n/g, ''); // Remove newlines
 | 
			
		||||
 | 
			
		||||
    // Adjust CREATE VIEW syntax
 | 
			
		||||
    const regex =
 | 
			
		||||
        /CREATE\s+VIEW\s+(?:"?([^".\s]+)"?\.)?"?([^".\s]+)"?\s+AS\s+/i;
 | 
			
		||||
    const match = viewDefinition.match(regex);
 | 
			
		||||
    let modifiedDefinition: string;
 | 
			
		||||
 | 
			
		||||
    if (match) {
 | 
			
		||||
        const viewName = match[2];
 | 
			
		||||
        const modifiedSQL = viewDefinition.substring(
 | 
			
		||||
            match.index! + match[0].length
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Remove semicolons at the end
 | 
			
		||||
        const finalSQL = modifiedSQL.replace(/;+\s*$/, '');
 | 
			
		||||
 | 
			
		||||
        modifiedDefinition = `CREATE VIEW "${viewName}" AS ${finalSQL}`;
 | 
			
		||||
    } else {
 | 
			
		||||
        console.warn('Could not preprocess view definition:', viewDefinition);
 | 
			
		||||
        modifiedDefinition = viewDefinition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return modifiedDefinition;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Preprocess the view_definition to remove schema from CREATE VIEW
 | 
			
		||||
function preprocessViewDefinitionMySQL(viewDefinition: string): string {
 | 
			
		||||
    if (!viewDefinition) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove any trailing semicolons
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/;\s*$/, '');
 | 
			
		||||
 | 
			
		||||
    // Remove backticks from identifiers
 | 
			
		||||
    viewDefinition = viewDefinition.replace(/`/g, '');
 | 
			
		||||
 | 
			
		||||
    // Remove unnecessary parentheses around joins and ON clauses
 | 
			
		||||
    viewDefinition = removeRedundantParentheses(viewDefinition);
 | 
			
		||||
 | 
			
		||||
    return viewDefinition;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function removeRedundantParentheses(sql: string): string {
 | 
			
		||||
    // Regular expressions to match unnecessary parentheses
 | 
			
		||||
    const patterns = [
 | 
			
		||||
        /\(\s*(JOIN\s+[^()]+?)\s*\)/gi,
 | 
			
		||||
        /\(\s*(ON\s+[^()]+?)\s*\)/gi,
 | 
			
		||||
        // Additional patterns if necessary
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    let prevSql;
 | 
			
		||||
    do {
 | 
			
		||||
        prevSql = sql;
 | 
			
		||||
        patterns.forEach((pattern) => {
 | 
			
		||||
            sql = sql.replace(pattern, '$1');
 | 
			
		||||
        });
 | 
			
		||||
    } while (sql !== prevSql);
 | 
			
		||||
 | 
			
		||||
    return sql;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function extractTablesFromAST(
 | 
			
		||||
    ast: AST | AST[]
 | 
			
		||||
): { schema?: string; tableName: string }[] {
 | 
			
		||||
    const tablesMap = new Map<string, { schema: string; tableName: string }>();
 | 
			
		||||
    const visitedNodes = new Set();
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
    function traverse(node: any) {
 | 
			
		||||
        if (!node || visitedNodes.has(node)) return;
 | 
			
		||||
        visitedNodes.add(node);
 | 
			
		||||
 | 
			
		||||
        if (Array.isArray(node)) {
 | 
			
		||||
            node.forEach(traverse);
 | 
			
		||||
        } else if (typeof node === 'object') {
 | 
			
		||||
            // Check if node represents a table
 | 
			
		||||
            if (
 | 
			
		||||
                Object.hasOwnProperty.call(node, 'table') &&
 | 
			
		||||
                typeof node.table === 'string'
 | 
			
		||||
            ) {
 | 
			
		||||
                let schema = node.db || node.schema;
 | 
			
		||||
                const tableName = node.table;
 | 
			
		||||
                if (tableName) {
 | 
			
		||||
                    // Assign default schema if undefined
 | 
			
		||||
                    schema = schemaNameToDomainSchemaName(schema) || '';
 | 
			
		||||
                    const key = `${schema}.${tableName}`;
 | 
			
		||||
                    if (!tablesMap.has(key)) {
 | 
			
		||||
                        tablesMap.set(key, { schema, tableName });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Recursively traverse all properties
 | 
			
		||||
            for (const key in node) {
 | 
			
		||||
                if (Object.hasOwnProperty.call(node, key)) {
 | 
			
		||||
                    traverse(node[key]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    traverse(ast);
 | 
			
		||||
 | 
			
		||||
    return Array.from(tablesMap.values());
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,11 @@ import {
 | 
			
		||||
    findDataTypeDataById,
 | 
			
		||||
    type DataType,
 | 
			
		||||
} from '../data/data-types/data-types';
 | 
			
		||||
import type { ColumnInfo } from '../data/import-metadata/metadata-types/column-info';
 | 
			
		||||
import type { AggregatedIndexInfo } from '../data/import-metadata/metadata-types/index-info';
 | 
			
		||||
import type { PrimaryKeyInfo } from '../data/import-metadata/metadata-types/primary-key-info';
 | 
			
		||||
import type { TableInfo } from '../data/import-metadata/metadata-types/table-info';
 | 
			
		||||
import { generateId } from '../utils';
 | 
			
		||||
import type { DatabaseType } from './database-type';
 | 
			
		||||
 | 
			
		||||
export interface DBField {
 | 
			
		||||
@@ -40,6 +45,64 @@ export const dbFieldSchema: z.ZodType<DBField> = z.object({
 | 
			
		||||
    comments: z.string().or(z.null()).optional(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const createFieldsFromMetadata = ({
 | 
			
		||||
    tableColumns,
 | 
			
		||||
    tablePrimaryKeys,
 | 
			
		||||
    aggregatedIndexes,
 | 
			
		||||
}: {
 | 
			
		||||
    tableColumns: ColumnInfo[];
 | 
			
		||||
    tableSchema?: string;
 | 
			
		||||
    tableInfo: TableInfo;
 | 
			
		||||
    tablePrimaryKeys: PrimaryKeyInfo[];
 | 
			
		||||
    aggregatedIndexes: AggregatedIndexInfo[];
 | 
			
		||||
}) => {
 | 
			
		||||
    const uniqueColumns = tableColumns.reduce((acc, col) => {
 | 
			
		||||
        if (!acc.has(col.name)) {
 | 
			
		||||
            acc.set(col.name, col);
 | 
			
		||||
        }
 | 
			
		||||
        return acc;
 | 
			
		||||
    }, new Map<string, ColumnInfo>());
 | 
			
		||||
 | 
			
		||||
    const sortedColumns = Array.from(uniqueColumns.values()).sort(
 | 
			
		||||
        (a, b) => a.ordinal_position - b.ordinal_position
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const tablePrimaryKeysColumns = tablePrimaryKeys.map((pk) =>
 | 
			
		||||
        pk.column.trim()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return sortedColumns.map(
 | 
			
		||||
        (col: ColumnInfo): DBField => ({
 | 
			
		||||
            id: generateId(),
 | 
			
		||||
            name: col.name,
 | 
			
		||||
            type: {
 | 
			
		||||
                id: col.type.split(' ').join('_').toLowerCase(),
 | 
			
		||||
                name: col.type.toLowerCase(),
 | 
			
		||||
            },
 | 
			
		||||
            primaryKey: tablePrimaryKeysColumns.includes(col.name),
 | 
			
		||||
            unique: Object.values(aggregatedIndexes).some(
 | 
			
		||||
                (idx) =>
 | 
			
		||||
                    idx.unique &&
 | 
			
		||||
                    idx.columns.length === 1 &&
 | 
			
		||||
                    idx.columns[0].name === col.name
 | 
			
		||||
            ),
 | 
			
		||||
            nullable: Boolean(col.nullable),
 | 
			
		||||
            ...(col.character_maximum_length &&
 | 
			
		||||
            col.character_maximum_length !== 'null'
 | 
			
		||||
                ? { characterMaximumLength: col.character_maximum_length }
 | 
			
		||||
                : {}),
 | 
			
		||||
            ...(col.precision?.precision
 | 
			
		||||
                ? { precision: col.precision.precision }
 | 
			
		||||
                : {}),
 | 
			
		||||
            ...(col.precision?.scale ? { scale: col.precision.scale } : {}),
 | 
			
		||||
            ...(col.default ? { default: col.default } : {}),
 | 
			
		||||
            ...(col.collation ? { collation: col.collation } : {}),
 | 
			
		||||
            createdAt: Date.now(),
 | 
			
		||||
            comments: col.comment ? col.comment : undefined,
 | 
			
		||||
        })
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const generateDBFieldSuffix = (
 | 
			
		||||
    field: DBField,
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
import type { AggregatedIndexInfo } from '../data/import-metadata/metadata-types/index-info';
 | 
			
		||||
import { generateId } from '../utils';
 | 
			
		||||
import type { DBField } from './db-field';
 | 
			
		||||
import { DatabaseType } from './database-type';
 | 
			
		||||
import type { DBTable } from './db-table';
 | 
			
		||||
 | 
			
		||||
export const INDEX_TYPES = [
 | 
			
		||||
    'btree',
 | 
			
		||||
@@ -28,7 +29,6 @@ export interface DBIndex {
 | 
			
		||||
    fieldIds: string[];
 | 
			
		||||
    createdAt: number;
 | 
			
		||||
    type?: IndexType | null;
 | 
			
		||||
    isPrimaryKey?: boolean | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const dbIndexSchema: z.ZodType<DBIndex> = z.object({
 | 
			
		||||
@@ -38,57 +38,29 @@ export const dbIndexSchema: z.ZodType<DBIndex> = z.object({
 | 
			
		||||
    fieldIds: z.array(z.string()),
 | 
			
		||||
    createdAt: z.number(),
 | 
			
		||||
    type: z.enum(INDEX_TYPES).optional(),
 | 
			
		||||
    isPrimaryKey: z.boolean().or(z.null()).optional(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const createIndexesFromMetadata = ({
 | 
			
		||||
    aggregatedIndexes,
 | 
			
		||||
    fields,
 | 
			
		||||
}: {
 | 
			
		||||
    aggregatedIndexes: AggregatedIndexInfo[];
 | 
			
		||||
    fields: DBField[];
 | 
			
		||||
}): DBIndex[] =>
 | 
			
		||||
    aggregatedIndexes.map(
 | 
			
		||||
        (idx): DBIndex => ({
 | 
			
		||||
            id: generateId(),
 | 
			
		||||
            name: idx.name,
 | 
			
		||||
            unique: Boolean(idx.unique),
 | 
			
		||||
            fieldIds: idx.columns
 | 
			
		||||
                .sort((a, b) => a.position - b.position)
 | 
			
		||||
                .map((c) => fields.find((f) => f.name === c.name)?.id)
 | 
			
		||||
                .filter((id): id is string => id !== undefined),
 | 
			
		||||
            createdAt: Date.now(),
 | 
			
		||||
            type: idx.index_type?.toLowerCase() as IndexType,
 | 
			
		||||
        })
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
export const databaseIndexTypes: { [key in DatabaseType]?: IndexType[] } = {
 | 
			
		||||
    [DatabaseType.POSTGRESQL]: ['btree', 'hash'],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getTablePrimaryKeyIndex = ({
 | 
			
		||||
    table,
 | 
			
		||||
}: {
 | 
			
		||||
    table: DBTable;
 | 
			
		||||
}): DBIndex | null => {
 | 
			
		||||
    const primaryKeyFields = table.fields.filter((f) => f.primaryKey);
 | 
			
		||||
    const existingPKIndex = table.indexes.find((idx) => idx.isPrimaryKey);
 | 
			
		||||
 | 
			
		||||
    if (primaryKeyFields.length === 0) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const pkFieldIds = primaryKeyFields.map((f) => f.id);
 | 
			
		||||
 | 
			
		||||
    if (existingPKIndex) {
 | 
			
		||||
        return {
 | 
			
		||||
            ...existingPKIndex,
 | 
			
		||||
            fieldIds: pkFieldIds,
 | 
			
		||||
        };
 | 
			
		||||
    } else {
 | 
			
		||||
        // Create new PK index for primary key(s)
 | 
			
		||||
        const pkIndex: DBIndex = {
 | 
			
		||||
            id: generateId(),
 | 
			
		||||
            name: `pk_${table.name}_${primaryKeyFields.map((f) => f.name).join('_')}`,
 | 
			
		||||
            fieldIds: pkFieldIds,
 | 
			
		||||
            unique: true,
 | 
			
		||||
            isPrimaryKey: true,
 | 
			
		||||
            createdAt: Date.now(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return pkIndex;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getTableIndexesWithPrimaryKey = ({
 | 
			
		||||
    table,
 | 
			
		||||
}: {
 | 
			
		||||
    table: DBTable;
 | 
			
		||||
}): DBIndex[] => {
 | 
			
		||||
    const primaryKeyIndex = getTablePrimaryKeyIndex({ table });
 | 
			
		||||
    const indexesWithoutPKIndex = table.indexes.filter(
 | 
			
		||||
        (idx) => !idx.isPrimaryKey
 | 
			
		||||
    );
 | 
			
		||||
    return primaryKeyIndex
 | 
			
		||||
        ? [primaryKeyIndex, ...indexesWithoutPKIndex]
 | 
			
		||||
        : indexesWithoutPKIndex;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,9 @@
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
import type { ForeignKeyInfo } from '../data/import-metadata/metadata-types/foreign-key-info';
 | 
			
		||||
import type { DBField } from './db-field';
 | 
			
		||||
import { schemaNameToDomainSchemaName } from './db-schema';
 | 
			
		||||
import type { DBTable } from './db-table';
 | 
			
		||||
import { generateId } from '@/lib/utils';
 | 
			
		||||
 | 
			
		||||
export interface DBRelationship {
 | 
			
		||||
    id: string;
 | 
			
		||||
@@ -35,6 +40,82 @@ export type RelationshipType =
 | 
			
		||||
    | 'many_to_many';
 | 
			
		||||
export type Cardinality = 'one' | 'many';
 | 
			
		||||
 | 
			
		||||
const determineCardinality = (
 | 
			
		||||
    field: DBField,
 | 
			
		||||
    isTablePKComplex: boolean
 | 
			
		||||
): Cardinality => {
 | 
			
		||||
    return field.unique || (field.primaryKey && !isTablePKComplex)
 | 
			
		||||
        ? 'one'
 | 
			
		||||
        : 'many';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createRelationshipsFromMetadata = ({
 | 
			
		||||
    foreignKeys,
 | 
			
		||||
    tables,
 | 
			
		||||
}: {
 | 
			
		||||
    foreignKeys: ForeignKeyInfo[];
 | 
			
		||||
    tables: DBTable[];
 | 
			
		||||
}): DBRelationship[] => {
 | 
			
		||||
    return foreignKeys
 | 
			
		||||
        .map((fk: ForeignKeyInfo): DBRelationship | null => {
 | 
			
		||||
            const schema = schemaNameToDomainSchemaName(fk.schema);
 | 
			
		||||
            const sourceTable = tables.find(
 | 
			
		||||
                (table) => table.name === fk.table && table.schema === schema
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            const targetSchema = schemaNameToDomainSchemaName(
 | 
			
		||||
                fk.reference_schema
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            const targetTable = tables.find(
 | 
			
		||||
                (table) =>
 | 
			
		||||
                    table.name === fk.reference_table &&
 | 
			
		||||
                    table.schema === targetSchema
 | 
			
		||||
            );
 | 
			
		||||
            const sourceField = sourceTable?.fields.find(
 | 
			
		||||
                (field) => field.name === fk.column
 | 
			
		||||
            );
 | 
			
		||||
            const targetField = targetTable?.fields.find(
 | 
			
		||||
                (field) => field.name === fk.reference_column
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            const isSourceTablePKComplex =
 | 
			
		||||
                (sourceTable?.fields.filter((field) => field.primaryKey) ?? [])
 | 
			
		||||
                    .length > 1;
 | 
			
		||||
            const isTargetTablePKComplex =
 | 
			
		||||
                (targetTable?.fields.filter((field) => field.primaryKey) ?? [])
 | 
			
		||||
                    .length > 1;
 | 
			
		||||
 | 
			
		||||
            if (sourceTable && targetTable && sourceField && targetField) {
 | 
			
		||||
                const sourceCardinality = determineCardinality(
 | 
			
		||||
                    sourceField,
 | 
			
		||||
                    isSourceTablePKComplex
 | 
			
		||||
                );
 | 
			
		||||
                const targetCardinality = determineCardinality(
 | 
			
		||||
                    targetField,
 | 
			
		||||
                    isTargetTablePKComplex
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                return {
 | 
			
		||||
                    id: generateId(),
 | 
			
		||||
                    name: fk.foreign_key_name,
 | 
			
		||||
                    sourceSchema: schema,
 | 
			
		||||
                    targetSchema: targetSchema,
 | 
			
		||||
                    sourceTableId: sourceTable.id,
 | 
			
		||||
                    targetTableId: targetTable.id,
 | 
			
		||||
                    sourceFieldId: sourceField.id,
 | 
			
		||||
                    targetFieldId: targetField.id,
 | 
			
		||||
                    sourceCardinality,
 | 
			
		||||
                    targetCardinality,
 | 
			
		||||
                    createdAt: Date.now(),
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        })
 | 
			
		||||
        .filter((rel) => rel !== null) as DBRelationship[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const determineRelationshipType = ({
 | 
			
		||||
    sourceCardinality,
 | 
			
		||||
    targetCardinality,
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user