Compare commits
28
Commits
9c8f6e20aa
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4097a5e5d | ||
|
|
e8f630ae98 | ||
|
|
b8e9ab07f2 | ||
|
|
44ec3d4c11 | ||
|
|
90fcd3867d | ||
|
|
4d85a69505 | ||
|
|
e9f0730405 | ||
|
|
c97bedc271 | ||
|
|
4f203ce4e7 | ||
|
|
000d40b4cb | ||
|
|
c40d8596c7 | ||
|
|
e51f524443 | ||
|
|
7ded5e949d | ||
|
|
42805c4a69 | ||
|
|
9357cd404a | ||
|
|
e1038ec8ea | ||
|
|
0420d50f15 | ||
|
|
246c42a214 | ||
|
|
3bbf00cdb0 | ||
|
|
9f2db66760 | ||
|
|
225e3eba4c | ||
|
|
3faf4be368 | ||
|
|
56462e6cc2 | ||
|
|
79c5b896fe | ||
|
|
3b5fba4063 | ||
|
|
b5e4724e03 | ||
|
|
44f6c18a84 | ||
|
|
eb86e1803d |
@@ -33,3 +33,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
/test
|
/test
|
||||||
.vscode
|
.vscode
|
||||||
|
.dump
|
||||||
|
|
||||||
|
/.bun-mariadb
|
||||||
@@ -8,21 +8,22 @@
|
|||||||
"@inquirer/prompts": "^8.3.0",
|
"@inquirer/prompts": "^8.3.0",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"commander": "^14.0.3",
|
"commander": "^14.0.3",
|
||||||
"inquirer": "^13.3.2",
|
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"mariadb": "^3.5.3",
|
"sanitize-html": "^2.17.6",
|
||||||
"mysql": "^2.18.1",
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/inquirer": "^9.0.9",
|
|
||||||
"@types/lodash": "^4.17.24",
|
"@types/lodash": "^4.17.24",
|
||||||
"@types/mysql": "^2.15.27",
|
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
|
"@types/sanitize-html": "^2.16.1",
|
||||||
|
"typescript": "~5.8.0",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5",
|
"typescript": ">=5",
|
||||||
},
|
},
|
||||||
|
"optionalPeers": [
|
||||||
|
"typescript",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
@@ -60,19 +61,11 @@
|
|||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||||
|
|
||||||
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
|
|
||||||
|
|
||||||
"@types/inquirer": ["@types/inquirer@9.0.10", "", { "dependencies": { "@types/through": "*", "rxjs": "^7.2.0" } }, "sha512-vFW2WbXwO9eZpRT5GJGFJ/shgyMNnYozmnjakt9jCQSS1lvqX8pZEQMjJ9RdDPct/YxwciQ8+V8OYn9euIrZDA=="],
|
|
||||||
|
|
||||||
"@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
|
"@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
|
||||||
|
|
||||||
"@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="],
|
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="],
|
"@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="],
|
||||||
|
|
||||||
"@types/through": ["@types/through@0.0.33", "", { "dependencies": { "@types/node": "*" } }, "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ=="],
|
"@types/sanitize-html": ["@types/sanitize-html@2.16.1", "", { "dependencies": { "htmlparser2": "^10.1" } }, "sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA=="],
|
||||||
|
|
||||||
"bignumber.js": ["bignumber.js@9.0.0", "", {}, "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="],
|
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||||
|
|
||||||
@@ -84,9 +77,21 @@
|
|||||||
|
|
||||||
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
||||||
|
|
||||||
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
"dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="],
|
||||||
|
|
||||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||||
|
|
||||||
|
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||||
|
|
||||||
|
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||||
|
|
||||||
|
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||||
|
|
||||||
|
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||||
|
|
||||||
|
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||||
|
|
||||||
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||||
|
|
||||||
"fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
|
"fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
|
||||||
|
|
||||||
@@ -94,48 +99,50 @@
|
|||||||
|
|
||||||
"fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="],
|
"fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="],
|
||||||
|
|
||||||
|
"htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="],
|
||||||
|
|
||||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||||
|
|
||||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
"is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="],
|
||||||
|
|
||||||
"inquirer": ["inquirer@13.4.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/core": "^11.1.10", "@inquirer/prompts": "^8.4.3", "@inquirer/type": "^4.0.5", "mute-stream": "^3.0.0", "run-async": "^4.0.6", "rxjs": "^7.8.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-EPd3IqieHSavSOXh+LZhrIkdQcOELWeRblLT6kslQr+cF9XTh/HxZdSt1YkHH1iq4dvqBnV42uwg2YlorgOy6g=="],
|
"launder": ["launder@1.7.1", "", { "dependencies": { "dayjs": "^1.11.7" } }, "sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw=="],
|
||||||
|
|
||||||
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
|
||||||
|
|
||||||
"lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
"lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="],
|
|
||||||
|
|
||||||
"mariadb": ["mariadb@3.5.3", "", { "dependencies": { "@types/geojson": "^7946.0.16", "@types/node": ">=20", "denque": "^2.1.0", "iconv-lite": "^0.7.2", "lru-cache": "^11.5.0" } }, "sha512-i053Kc0MgdUv/hu9mCyq67TYfPXFj3/MV8I7ZW5wvJNixIyXC0VztMPUjIVj/449nQo+BsxFD4Fdk/sA/uqKPQ=="],
|
|
||||||
|
|
||||||
"mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
|
"mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
|
||||||
|
|
||||||
"mysql": ["mysql@2.18.1", "", { "dependencies": { "bignumber.js": "9.0.0", "readable-stream": "2.3.7", "safe-buffer": "5.1.2", "sqlstring": "2.3.1" } }, "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig=="],
|
"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
|
||||||
|
|
||||||
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
"parse-srcset": ["parse-srcset@1.0.2", "", {}, "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="],
|
||||||
|
|
||||||
"readable-stream": ["readable-stream@2.3.7", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
"run-async": ["run-async@4.0.6", "", {}, "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ=="],
|
"postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="],
|
||||||
|
|
||||||
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
|
|
||||||
|
|
||||||
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
|
"sanitize-html": ["sanitize-html@2.17.6", "", { "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", "htmlparser2": "^12.0.0", "is-plain-object": "^5.0.0", "launder": "^1.7.1", "parse-srcset": "^1.0.2", "postcss": "^8.3.11" } }, "sha512-M4bo9tfv1yfhQZZKkc6dL07ALrGJtfvNOuhX3hU9AVPR/uPQ+nKOJBqTYc7LfMQblTW04mtSWDJWEyLvygJsLA=="],
|
||||||
|
|
||||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||||
|
|
||||||
"sqlstring": ["sqlstring@2.3.1", "", {}, "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||||
|
|
||||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||||
|
|
||||||
|
"sanitize-html/htmlparser2": ["htmlparser2@12.0.0", "", { "dependencies": { "domelementtype": "^3.0.0", "domhandler": "^6.0.0", "domutils": "^4.0.2", "entities": "^8.0.0" } }, "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw=="],
|
||||||
|
|
||||||
|
"sanitize-html/htmlparser2/domelementtype": ["domelementtype@3.0.0", "", {}, "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg=="],
|
||||||
|
|
||||||
|
"sanitize-html/htmlparser2/domhandler": ["domhandler@6.0.1", "", { "dependencies": { "domelementtype": "^3.0.0" } }, "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg=="],
|
||||||
|
|
||||||
|
"sanitize-html/htmlparser2/domutils": ["domutils@4.0.2", "", { "dependencies": { "dom-serializer": "^3.0.0", "domelementtype": "^3.0.0", "domhandler": "^6.0.0" } }, "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA=="],
|
||||||
|
|
||||||
|
"sanitize-html/htmlparser2/entities": ["entities@8.0.0", "", {}, "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA=="],
|
||||||
|
|
||||||
|
"sanitize-html/htmlparser2/domutils/dom-serializer": ["dom-serializer@3.1.1", "", { "dependencies": { "domelementtype": "^3.0.0", "domhandler": "^6.0.0", "entities": "^8.0.0" } }, "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-5
@@ -1,6 +1,4 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import init from "../../functions/init";
|
|
||||||
import grabDBDir from "../../utils/grab-db-dir";
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { select } from "@inquirer/prompts";
|
import { select } from "@inquirer/prompts";
|
||||||
import listTables from "./list-tables";
|
import listTables from "./list-tables";
|
||||||
@@ -9,9 +7,7 @@ export default function () {
|
|||||||
return new Command("admin")
|
return new Command("admin")
|
||||||
.description("View Tables and Data, Run SQL Queries, Etc.")
|
.description("View Tables and Data, Run SQL Queries, Etc.")
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
const { config } = await init();
|
console.log(chalk.bold(chalk.blue("\nBun MariaDB Admin\n")));
|
||||||
const { db_file_path } = grabDBDir({ config });
|
|
||||||
console.log(chalk.bold(chalk.blue("\nBun SQLite Admin\n")));
|
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
const paradigm = await select({
|
const paradigm = await select({
|
||||||
|
|||||||
Vendored
+24
-21
@@ -1,19 +1,18 @@
|
|||||||
import { Database } from "bun:sqlite";
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { select } from "@inquirer/prompts";
|
import { select } from "@inquirer/prompts";
|
||||||
import { AppData } from "../../data/app-data";
|
import { AppData } from "../../data/app-data";
|
||||||
import showEntries from "./show-entries";
|
import showEntries from "./show-entries";
|
||||||
import showFields from "./show-fields";
|
import showFields from "./show-fields";
|
||||||
import dbHandler from "../../lib/db-handler";
|
import dbHandler from "../../lib/db-handler";
|
||||||
|
import MariaDBQuoteGen from "../../lib/schema/mariadb-quote-gen";
|
||||||
export default async function listTables(params) {
|
export default async function listTables(params) {
|
||||||
const tables = (await dbHandler({
|
const tables = (await dbHandler({
|
||||||
query: `SELECT table_name FROM ${AppData["DbSchemaManagerTableName"]}`,
|
query: `SELECT table_name FROM ${MariaDBQuoteGen(AppData["DbSchemaManagerTableName"])}`,
|
||||||
})).payload;
|
})).payload;
|
||||||
if (!tables?.length) {
|
if (!tables?.length) {
|
||||||
console.log(chalk.yellow("\nNo tables found.\n"));
|
console.log(chalk.yellow("\nNo tables found.\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Level 1: table selection loop
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const tableName = await select({
|
const tableName = await select({
|
||||||
message: "Select a table:",
|
message: "Select a table:",
|
||||||
@@ -30,7 +29,6 @@ export default async function listTables(params) {
|
|||||||
break;
|
break;
|
||||||
if (tableName === "__exit__")
|
if (tableName === "__exit__")
|
||||||
return "__exit__";
|
return "__exit__";
|
||||||
// Level 2: action loop — stays here until "Go Back"
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const action = await select({
|
const action = await select({
|
||||||
message: `"${tableName}" — choose an action:`,
|
message: `"${tableName}" — choose an action:`,
|
||||||
@@ -56,23 +54,28 @@ export default async function listTables(params) {
|
|||||||
if (result === "__exit__")
|
if (result === "__exit__")
|
||||||
return "__exit__";
|
return "__exit__";
|
||||||
}
|
}
|
||||||
// if (action === "schema") {
|
if (action === "schema") {
|
||||||
// const columns = db
|
const columns = (await dbHandler({
|
||||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
query: `SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
|
||||||
// .all();
|
values: [tableName],
|
||||||
// console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`);
|
})).payload;
|
||||||
// console.table(
|
console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`);
|
||||||
// columns.map((c) => ({
|
if (columns?.length) {
|
||||||
// "#": c.cid,
|
console.table(columns.map((c) => ({
|
||||||
// Name: c.name,
|
"#": c.ORDINAL_POSITION,
|
||||||
// Type: c.type,
|
Name: c.COLUMN_NAME,
|
||||||
// "Not Null": c.notnull ? "YES" : "NO",
|
Type: c.COLUMN_TYPE,
|
||||||
// Default: c.dflt_value ?? "(none)",
|
Nullable: c.IS_NULLABLE,
|
||||||
// "Primary Key": c.pk ? "YES" : "NO",
|
Default: c.COLUMN_DEFAULT ?? "(none)",
|
||||||
// })),
|
Key: c.COLUMN_KEY || "",
|
||||||
// );
|
Extra: c.EXTRA || "",
|
||||||
// console.log();
|
})));
|
||||||
// }
|
}
|
||||||
|
else {
|
||||||
|
console.log(chalk.yellow("No columns found."));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+6
-7
@@ -1,4 +1,3 @@
|
|||||||
import { Database } from "bun:sqlite";
|
|
||||||
import { input } from "@inquirer/prompts";
|
import { input } from "@inquirer/prompts";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import dbHandler from "../../lib/db-handler";
|
import dbHandler from "../../lib/db-handler";
|
||||||
@@ -9,15 +8,15 @@ export default async function runSQL(params) {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const res = await dbHandler({ query: sql });
|
const res = await dbHandler({ query: sql });
|
||||||
if (res.payload) {
|
if (res.payload?.length) {
|
||||||
console.log(`\n${chalk.bold(`Result (${res.payload.length} row${res.payload.length !== 1 ? "s" : ""}):`)} \n`);
|
console.log(`\n${chalk.bold(`Result (${res.payload.length} row${res.payload.length !== 1 ? "s" : ""}):`)} \n`);
|
||||||
if (res.payload.length)
|
|
||||||
console.table(res.payload);
|
console.table(res.payload);
|
||||||
else
|
|
||||||
console.log(chalk.yellow("No res returned.\n"));
|
|
||||||
}
|
}
|
||||||
else if (res.single_res) {
|
else if (res.success) {
|
||||||
console.log(chalk.green(`\nSuccess! Affected rows: ${res.single_res.changes}, Last insert ID: ${res.single_res.lastInsertRowid}\n`));
|
console.log(chalk.green(`\nSuccess! Affected rows: ${res.insert_return?.affected_rows ?? res.count ?? 0}, Last insert ID: ${res.insert_return?.last_insert_id ?? "—"}\n`));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(chalk.red(`\nSQL Error: ${res.error || res.msg}\n`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|||||||
Vendored
+30
-22
@@ -1,31 +1,32 @@
|
|||||||
import { Database } from "bun:sqlite";
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { select, input } from "@inquirer/prompts";
|
import { select, input } from "@inquirer/prompts";
|
||||||
import dbHandler from "../../lib/db-handler";
|
import dbHandler from "../../lib/db-handler";
|
||||||
|
import MariaDBQuoteGen from "../../lib/schema/mariadb-quote-gen";
|
||||||
const LIMIT = 50;
|
const LIMIT = 50;
|
||||||
export default async function showEntries({ tableName }) {
|
export default async function showEntries({ tableName }) {
|
||||||
let page = 0;
|
let page = 0;
|
||||||
let searchField = null;
|
let searchField = null;
|
||||||
let searchTerm = null;
|
let searchTerm = null;
|
||||||
|
const quotedTable = MariaDBQuoteGen(tableName);
|
||||||
while (true) {
|
while (true) {
|
||||||
const offset = page * LIMIT;
|
const offset = page * LIMIT;
|
||||||
const rows = searchTerm
|
const rows = searchTerm
|
||||||
? (await dbHandler({
|
? (await dbHandler({
|
||||||
query: `SELECT * FROM "${tableName}" WHERE "${searchField}" LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
|
query: `SELECT * FROM ${quotedTable} WHERE ${MariaDBQuoteGen(searchField)} LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||||
values: [`%${searchTerm}%`],
|
values: [`%${searchTerm}%`],
|
||||||
})).payload
|
})).payload
|
||||||
: (await dbHandler({
|
: (await dbHandler({
|
||||||
query: `SELECT * FROM "${tableName}" LIMIT ${LIMIT} OFFSET ${offset}`,
|
query: `SELECT * FROM ${quotedTable} LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||||
})).payload;
|
})).payload;
|
||||||
const countRow = searchTerm
|
const countRow = searchTerm
|
||||||
? (await dbHandler({
|
? (await dbHandler({
|
||||||
query: `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${searchField}" LIKE ?`,
|
query: `SELECT COUNT(*) as count FROM ${quotedTable} WHERE ${MariaDBQuoteGen(searchField)} LIKE ?`,
|
||||||
values: [`%${searchTerm}%`],
|
values: [`%${searchTerm}%`],
|
||||||
})).payload
|
})).payload
|
||||||
: (await dbHandler({
|
: (await dbHandler({
|
||||||
query: `SELECT COUNT(*) as count FROM "${tableName}"`,
|
query: `SELECT COUNT(*) as count FROM ${quotedTable}`,
|
||||||
})).payload;
|
})).payload;
|
||||||
const total = countRow?.[0]?.count;
|
const total = Number(countRow?.[0]?.count || 0);
|
||||||
const searchInfo = searchTerm
|
const searchInfo = searchTerm
|
||||||
? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`)
|
? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`)
|
||||||
: "";
|
: "";
|
||||||
@@ -34,8 +35,7 @@ export default async function showEntries({ tableName }) {
|
|||||||
}
|
}
|
||||||
console.log(`\n${chalk.bold(tableName)} — Page ${page + 1}${searchInfo} (${rows.length} of ${total}):\n`);
|
console.log(`\n${chalk.bold(tableName)} — Page ${page + 1}${searchInfo} (${rows.length} of ${total}):\n`);
|
||||||
if (rows.length) {
|
if (rows.length) {
|
||||||
console.log(rows);
|
console.table(rows);
|
||||||
// if (rows.length) console.table(rows);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log(chalk.yellow("No rows found."));
|
console.log(chalk.yellow("No rows found."));
|
||||||
@@ -65,19 +65,27 @@ export default async function showEntries({ tableName }) {
|
|||||||
searchTerm = null;
|
searchTerm = null;
|
||||||
page = 0;
|
page = 0;
|
||||||
}
|
}
|
||||||
// if (action === "search") {
|
if (action === "search") {
|
||||||
// const columns = db
|
const columns = (await dbHandler({
|
||||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
query: `SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
|
||||||
// .all();
|
values: [tableName],
|
||||||
// searchField = await select({
|
})).payload;
|
||||||
// message: "Search by field:",
|
if (!columns?.length) {
|
||||||
// choices: columns.map((c) => ({ name: c.name, value: c.name })),
|
console.log(chalk.yellow("No columns found for search."));
|
||||||
// });
|
continue;
|
||||||
// searchTerm = await input({
|
}
|
||||||
// message: `Search term for "${searchField}":`,
|
searchField = await select({
|
||||||
// validate: (v) => v.trim().length > 0 || "Cannot be empty",
|
message: "Search by field:",
|
||||||
// });
|
choices: columns.map((c) => ({
|
||||||
// page = 0;
|
name: c.COLUMN_NAME,
|
||||||
// }
|
value: c.COLUMN_NAME,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
searchTerm = await input({
|
||||||
|
message: `Search term for "${searchField}":`,
|
||||||
|
validate: (v) => v.trim().length > 0 || "Cannot be empty",
|
||||||
|
});
|
||||||
|
page = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+56
-65
@@ -1,69 +1,60 @@
|
|||||||
import { Database } from "bun:sqlite";
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { select } from "@inquirer/prompts";
|
import { select } from "@inquirer/prompts";
|
||||||
|
import dbHandler from "../../lib/db-handler";
|
||||||
export default async function showFields({ tableName, }) {
|
export default async function showFields({ tableName, }) {
|
||||||
// const columns = db
|
const columns = (await dbHandler({
|
||||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
query: `SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
|
||||||
// .all();
|
values: [tableName],
|
||||||
// const indexes = db
|
})).payload;
|
||||||
// .query<IndexInfo, []>(`PRAGMA index_list("${tableName}")`)
|
if (!columns?.length) {
|
||||||
// .all();
|
console.log(chalk.yellow(`\nNo columns found for "${tableName}".\n`));
|
||||||
// const foreignKeys = db
|
return;
|
||||||
// .query<ForeignKey, []>(`PRAGMA foreign_key_list("${tableName}")`)
|
}
|
||||||
// .all();
|
const indexes = (await dbHandler({
|
||||||
// const indexedFields = new Map<string, { unique: boolean }>();
|
query: `SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE, INDEX_TYPE FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY INDEX_NAME, SEQ_IN_INDEX`,
|
||||||
// for (const idx of indexes) {
|
values: [tableName],
|
||||||
// const cols = db
|
})).payload;
|
||||||
// .query<IndexColumn, []>(`PRAGMA index_info("${idx.name}")`)
|
const foreignKeys = (await dbHandler({
|
||||||
// .all();
|
query: `SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND REFERENCED_TABLE_NAME IS NOT NULL`,
|
||||||
// for (const col of cols) {
|
values: [tableName],
|
||||||
// indexedFields.set(col.name, { unique: idx.unique === 1 });
|
})).payload;
|
||||||
// }
|
while (true) {
|
||||||
// }
|
const fieldName = await select({
|
||||||
// while (true) {
|
message: `"${tableName}" — select a field:`,
|
||||||
// const fieldName = await select({
|
choices: [
|
||||||
// message: `"${tableName}" — select a field:`,
|
...columns.map((c) => ({
|
||||||
// choices: [
|
name: c.COLUMN_NAME,
|
||||||
// ...columns.map((c) => ({ name: c.name, value: c.name })),
|
value: c.COLUMN_NAME,
|
||||||
// { name: chalk.dim("← Go Back"), value: "__back__" },
|
})),
|
||||||
// { name: chalk.dim("✕ Exit"), value: "__exit__" },
|
{ name: chalk.dim("← Go Back"), value: "__back__" },
|
||||||
// ],
|
{ name: chalk.dim("✕ Exit"), value: "__exit__" },
|
||||||
// });
|
],
|
||||||
// if (fieldName === "__back__") break;
|
});
|
||||||
// if (fieldName === "__exit__") return "__exit__";
|
if (fieldName === "__back__")
|
||||||
// const col = columns.find((c) => c.name === fieldName)!;
|
break;
|
||||||
// const idx = indexedFields.get(fieldName);
|
if (fieldName === "__exit__")
|
||||||
// const fk = foreignKeys.find((f) => f.from === fieldName);
|
return "__exit__";
|
||||||
// console.log(`\n${chalk.bold(`Field: "${fieldName}"`)}\n`);
|
const col = columns.find((c) => c.COLUMN_NAME === fieldName);
|
||||||
// console.log(` ${chalk.dim("Table")} ${tableName}`);
|
const colIndexes = indexes?.filter((i) => i.COLUMN_NAME === fieldName) || [];
|
||||||
// console.log(` ${chalk.dim("Column #")} ${col.cid}`);
|
const fk = foreignKeys?.find((f) => f.COLUMN_NAME === fieldName);
|
||||||
// console.log(
|
console.log(`\n${chalk.bold(`Field: "${fieldName}"`)}\n`);
|
||||||
// ` ${chalk.dim("Type")} ${col.type || chalk.italic("(none)")}`,
|
console.log(` ${chalk.dim("Table")} ${tableName}`);
|
||||||
// );
|
console.log(` ${chalk.dim("Column #")} ${col.ORDINAL_POSITION}`);
|
||||||
// console.log(
|
console.log(` ${chalk.dim("Type")} ${col.COLUMN_TYPE}`);
|
||||||
// ` ${chalk.dim("Primary Key")} ${col.pk ? chalk.green("YES") : "NO"}`,
|
console.log(` ${chalk.dim("Primary Key")} ${col.COLUMN_KEY === "PRI" ? chalk.green("YES") : "NO"}`);
|
||||||
// );
|
console.log(` ${chalk.dim("Not Null")} ${col.IS_NULLABLE === "NO" ? chalk.yellow("YES") : "NO"}`);
|
||||||
// console.log(
|
console.log(` ${chalk.dim("Default")} ${col.COLUMN_DEFAULT ?? chalk.italic("(none)")}`);
|
||||||
// ` ${chalk.dim("Not Null")} ${col.notnull ? chalk.yellow("YES") : "NO"}`,
|
console.log(` ${chalk.dim("Extra")} ${col.EXTRA || chalk.italic("(none)")}`);
|
||||||
// );
|
console.log(` ${chalk.dim("Indexed")} ${colIndexes.length ? chalk.cyan(colIndexes.map((i) => i.INDEX_NAME).join(", ")) : "NO"}`);
|
||||||
// console.log(
|
if (fk) {
|
||||||
// ` ${chalk.dim("Default")} ${col.dflt_value ?? chalk.italic("(none)")}`,
|
console.log(` ${chalk.dim("Foreign Key")} ${chalk.magenta(`${fk.REFERENCED_TABLE_NAME}(${fk.REFERENCED_COLUMN_NAME})`)}`);
|
||||||
// );
|
}
|
||||||
// console.log(
|
else {
|
||||||
// ` ${chalk.dim("Indexed")} ${idx ? chalk.cyan("YES") : "NO"}`,
|
console.log(` ${chalk.dim("Foreign Key")} NO`);
|
||||||
// );
|
}
|
||||||
// console.log(
|
if (col.COLUMN_COMMENT) {
|
||||||
// ` ${chalk.dim("Unique")} ${idx?.unique ? chalk.cyan("YES") : "NO"}`,
|
console.log(` ${chalk.dim("Comment")} ${col.COLUMN_COMMENT}`);
|
||||||
// );
|
}
|
||||||
// if (fk) {
|
console.log();
|
||||||
// console.log(
|
}
|
||||||
// ` ${chalk.dim("Foreign Key")} ${chalk.magenta(`${fk.table}(${fk.to})`)}`,
|
|
||||||
// );
|
|
||||||
// console.log(` ${chalk.dim("On Update")} ${fk.on_update}`);
|
|
||||||
// console.log(` ${chalk.dim("On Delete")} ${fk.on_delete}`);
|
|
||||||
// } else {
|
|
||||||
// console.log(` ${chalk.dim("Foreign Key")} NO`);
|
|
||||||
// }
|
|
||||||
// console.log();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+21
-10
@@ -1,22 +1,33 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import init from "../functions/init";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import grabDBDir from "../utils/grab-db-dir";
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import grabDBDir from "../utils/grab-db-dir";
|
||||||
import grabDBBackupFileName from "../utils/grab-db-backup-file-name";
|
import grabDBBackupFileName from "../utils/grab-db-backup-file-name";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import trimBackups from "../utils/trim-backups";
|
import trimBackups from "../utils/trim-backups";
|
||||||
|
import { dumpDatabase } from "../utils/mariadb-dump-restore";
|
||||||
export default function () {
|
export default function () {
|
||||||
return new Command("backup")
|
return new Command("backup")
|
||||||
.description("Backup Database")
|
.description("Backup MariaDB database (SQL dump)")
|
||||||
.action(async (opts) => {
|
.action(async () => {
|
||||||
console.log(`Backing up database ...`);
|
console.log(`Backing up database ...`);
|
||||||
const { config } = await init();
|
const config = global.CONFIG;
|
||||||
const { backup_dir, db_file_path } = grabDBDir({ config });
|
const { backup_dir } = grabDBDir({ config });
|
||||||
const new_db_file_name = grabDBBackupFileName({ config });
|
if (!fs.existsSync(backup_dir)) {
|
||||||
fs.cpSync(db_file_path, path.join(backup_dir, new_db_file_name));
|
fs.mkdirSync(backup_dir, { recursive: true });
|
||||||
|
}
|
||||||
|
const backup_file_name = grabDBBackupFileName({ config });
|
||||||
|
const backup_path = path.join(backup_dir, backup_file_name);
|
||||||
|
try {
|
||||||
|
const sql = await dumpDatabase(config);
|
||||||
|
fs.writeFileSync(backup_path, sql, "utf-8");
|
||||||
trimBackups({ config });
|
trimBackups({ config });
|
||||||
console.log(`${chalk.bold(chalk.green(`DB Backup Success!`))}`);
|
console.log(`${chalk.bold(chalk.green(`DB Backup Success!`))} → ${backup_path}`);
|
||||||
process.exit();
|
process.exit(0);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(chalk.red(`Backup ERROR => ${error.message}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
import { Command } from "commander";
|
||||||
|
export default function (): Command;
|
||||||
Vendored
+72
@@ -0,0 +1,72 @@
|
|||||||
|
import { Command } from "commander";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import grabDBDir from "../utils/grab-db-dir";
|
||||||
|
import { dumpDatabase } from "../utils/mariadb-dump-restore";
|
||||||
|
import { writeExportArchive } from "../utils/export-archive";
|
||||||
|
import trimExports from "../utils/trim-exports";
|
||||||
|
import { resolveDataFile, supportedDataFileNames, } from "../utils/resolve-and-load-data-file";
|
||||||
|
import { AppData } from "../data/app-data";
|
||||||
|
function defaultExportFileName(dbName, format) {
|
||||||
|
return `${dbName}-${Date.now()}.${format}`;
|
||||||
|
}
|
||||||
|
export default function () {
|
||||||
|
return new Command("export")
|
||||||
|
.description("Export database SQL dump + schema into a portable archive")
|
||||||
|
.option("-o, --output <path>", "Output archive path (.tar.gz or .zip). Defaults to db exports dir")
|
||||||
|
.option("-f, --format <format>", "Archive format when --output is omitted: tar.gz | zip", "tar.gz")
|
||||||
|
.action(async (opts) => {
|
||||||
|
console.log(`Exporting database ...`);
|
||||||
|
const config = global.CONFIG;
|
||||||
|
const { db_dir, export_dir } = grabDBDir({ config });
|
||||||
|
if (!fs.existsSync(export_dir)) {
|
||||||
|
fs.mkdirSync(export_dir, { recursive: true });
|
||||||
|
}
|
||||||
|
const schemaResolved = (global.SCHEMA_FILE_PATH &&
|
||||||
|
fs.existsSync(global.SCHEMA_FILE_PATH) && {
|
||||||
|
path: global.SCHEMA_FILE_PATH,
|
||||||
|
basename: path.basename(global.SCHEMA_FILE_PATH),
|
||||||
|
}) ||
|
||||||
|
resolveDataFile(db_dir, AppData.DbSchemaFileName);
|
||||||
|
if (!schemaResolved) {
|
||||||
|
console.error(chalk.red(`Schema file not found in \`${db_dir}\` (${supportedDataFileNames(AppData.DbSchemaFileName)})`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const formatRaw = String(opts.format || "tar.gz").toLowerCase();
|
||||||
|
const format = formatRaw === "zip" ? "zip" : "tar.gz";
|
||||||
|
let outPath;
|
||||||
|
if (opts.output) {
|
||||||
|
outPath = path.resolve(opts.output);
|
||||||
|
const parent = path.dirname(outPath);
|
||||||
|
if (!fs.existsSync(parent)) {
|
||||||
|
fs.mkdirSync(parent, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
outPath = path.join(export_dir, defaultExportFileName(config.db_name, format));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const sql = await dumpDatabase(config);
|
||||||
|
const schema = fs.readFileSync(schemaResolved.path, "utf-8");
|
||||||
|
await writeExportArchive({
|
||||||
|
contents: {
|
||||||
|
sql,
|
||||||
|
schema,
|
||||||
|
schemaFileName: schemaResolved.basename,
|
||||||
|
},
|
||||||
|
outPath,
|
||||||
|
});
|
||||||
|
if (path.dirname(outPath) === export_dir) {
|
||||||
|
trimExports({ config });
|
||||||
|
}
|
||||||
|
console.log(`${chalk.bold(chalk.green(`DB Export Success!`))} → ${outPath}`);
|
||||||
|
console.log(chalk.dim(`Contains: dump.sql + ${schemaResolved.basename}`));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(chalk.red(`Export ERROR => ${error.message}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
import { Command } from "commander";
|
||||||
|
export default function (): Command;
|
||||||
Vendored
+94
@@ -0,0 +1,94 @@
|
|||||||
|
import { Command } from "commander";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import { select } from "@inquirer/prompts";
|
||||||
|
import grabDBDir from "../utils/grab-db-dir";
|
||||||
|
import grabSortedExports from "../utils/grab-sorted-exports";
|
||||||
|
import grabBackupData from "../utils/grab-backup-data";
|
||||||
|
import { restoreDatabase } from "../utils/mariadb-dump-restore";
|
||||||
|
import { isArchivePath, isSqlPath, readExportArchive, } from "../utils/export-archive";
|
||||||
|
import { resolveDataFile } from "../utils/resolve-and-load-data-file";
|
||||||
|
import { AppData } from "../data/app-data";
|
||||||
|
function formatChoice(name, index) {
|
||||||
|
const { backup_date } = grabBackupData({ backup_name: name });
|
||||||
|
const time = Number.isNaN(backup_date.getTime())
|
||||||
|
? "unknown date"
|
||||||
|
: `${backup_date.toDateString()} ${backup_date.getHours()}:${backup_date.getMinutes()}:${backup_date.getSeconds().toString().padStart(2, "0")}`;
|
||||||
|
return `#${index + 1}: ${name} (${time})`;
|
||||||
|
}
|
||||||
|
export default function () {
|
||||||
|
return new Command("import")
|
||||||
|
.description("Import an SQL dump, or a full export archive (SQL + schema)")
|
||||||
|
.argument("[file]", "Path to .sql file or export archive (.tar.gz / .tar / .zip)")
|
||||||
|
.option("--sql-only", "When importing an archive, restore SQL only (skip writing schema)")
|
||||||
|
.option("--schema-only", "When importing an archive, write schema only (skip SQL restore)")
|
||||||
|
.action(async (fileArg, opts) => {
|
||||||
|
console.log(`Importing database ...`);
|
||||||
|
const config = global.CONFIG;
|
||||||
|
const { db_dir, export_dir } = grabDBDir({ config });
|
||||||
|
try {
|
||||||
|
let filePath = fileArg ? path.resolve(fileArg) : "";
|
||||||
|
if (!filePath) {
|
||||||
|
const candidates = grabSortedExports({ config }).filter((b) => isArchivePath(b));
|
||||||
|
if (!candidates[0]) {
|
||||||
|
console.error(`No export archives found in \`${export_dir}\`. Use \`export\`, or pass a file path.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const selected = await select({
|
||||||
|
message: "Select an export to import:",
|
||||||
|
choices: candidates.map((b, i) => ({
|
||||||
|
name: formatChoice(b, i),
|
||||||
|
value: b,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
filePath = path.join(export_dir, selected);
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.error(chalk.red(`File not found: ${filePath}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (isSqlPath(filePath)) {
|
||||||
|
if (opts.schemaOnly) {
|
||||||
|
console.error(chalk.red(`--schema-only requires an export archive, not a plain .sql file.`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const sql = fs.readFileSync(filePath, "utf-8");
|
||||||
|
await restoreDatabase(config, sql);
|
||||||
|
console.log(`${chalk.bold(chalk.green(`DB Import Success!`))} ← ${filePath}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
if (!isArchivePath(filePath)) {
|
||||||
|
console.error(chalk.red(`Unsupported file type: ${filePath}. Use .sql, .tar.gz, .tar, or .zip.`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (opts.sqlOnly && opts.schemaOnly) {
|
||||||
|
console.error(chalk.red(`Cannot combine --sql-only and --schema-only.`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const { sql, schema, schemaFileName } = await readExportArchive(filePath);
|
||||||
|
if (!opts.schemaOnly) {
|
||||||
|
await restoreDatabase(config, sql);
|
||||||
|
console.log(chalk.green(`SQL restored from archive`));
|
||||||
|
}
|
||||||
|
if (!opts.sqlOnly) {
|
||||||
|
const existing = resolveDataFile(db_dir, AppData.DbSchemaFileName);
|
||||||
|
const schemaPath = path.join(db_dir, schemaFileName);
|
||||||
|
// Remove a differently-named schema so only one format remains
|
||||||
|
if (existing &&
|
||||||
|
path.resolve(existing.path) !== path.resolve(schemaPath)) {
|
||||||
|
fs.unlinkSync(existing.path);
|
||||||
|
console.log(chalk.dim(`Removed previous schema file: ${existing.basename}`));
|
||||||
|
}
|
||||||
|
fs.writeFileSync(schemaPath, schema, "utf-8");
|
||||||
|
console.log(chalk.green(`Schema written → ${schemaPath}`));
|
||||||
|
}
|
||||||
|
console.log(`${chalk.bold(chalk.green(`DB Import Success!`))} ← ${filePath}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(chalk.red(`Import ERROR => ${error.message}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Vendored
-4
@@ -1,6 +1,2 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
/**
|
|
||||||
* # Declare Global Variables
|
|
||||||
*/
|
|
||||||
declare global { }
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
+6
@@ -4,7 +4,11 @@ import schema from "./schema";
|
|||||||
import typedef from "./typedef";
|
import typedef from "./typedef";
|
||||||
import backup from "./backup";
|
import backup from "./backup";
|
||||||
import restore from "./restore";
|
import restore from "./restore";
|
||||||
|
import exportCmd from "./export";
|
||||||
|
import importCmd from "./import";
|
||||||
import admin from "./admin";
|
import admin from "./admin";
|
||||||
|
import init from "../functions/init";
|
||||||
|
init();
|
||||||
/**
|
/**
|
||||||
* # Describe Program
|
* # Describe Program
|
||||||
*/
|
*/
|
||||||
@@ -19,6 +23,8 @@ program.addCommand(schema());
|
|||||||
program.addCommand(typedef());
|
program.addCommand(typedef());
|
||||||
program.addCommand(backup());
|
program.addCommand(backup());
|
||||||
program.addCommand(restore());
|
program.addCommand(restore());
|
||||||
|
program.addCommand(exportCmd());
|
||||||
|
program.addCommand(importCmd());
|
||||||
program.addCommand(admin());
|
program.addCommand(admin());
|
||||||
/**
|
/**
|
||||||
* # Handle Unavailable Commands
|
* # Handle Unavailable Commands
|
||||||
|
|||||||
Vendored
+18
-12
@@ -1,5 +1,4 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import init from "../functions/init";
|
|
||||||
import grabDBDir from "../utils/grab-db-dir";
|
import grabDBDir from "../utils/grab-db-dir";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
@@ -7,16 +6,17 @@ import grabSortedBackups from "../utils/grab-sorted-backups";
|
|||||||
import { select } from "@inquirer/prompts";
|
import { select } from "@inquirer/prompts";
|
||||||
import grabBackupData from "../utils/grab-backup-data";
|
import grabBackupData from "../utils/grab-backup-data";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { restoreDatabase } from "../utils/mariadb-dump-restore";
|
||||||
export default function () {
|
export default function () {
|
||||||
return new Command("restore")
|
return new Command("restore")
|
||||||
.description("Restore Database")
|
.description("Restore MariaDB database from an SQL dump")
|
||||||
.action(async (opts) => {
|
.action(async () => {
|
||||||
console.log(`Restoring up database ...`);
|
console.log(`Restoring database ...`);
|
||||||
const { config } = await init();
|
const config = global.CONFIG;
|
||||||
const { backup_dir, db_file_path } = grabDBDir({ config });
|
const { backup_dir } = grabDBDir({ config });
|
||||||
const backups = grabSortedBackups({ config });
|
const backups = grabSortedBackups({ config }).filter((b) => b.endsWith(".sql"));
|
||||||
if (!backups?.[0]) {
|
if (!backups?.[0]) {
|
||||||
console.error(`No Backups to restore. Use the \`backup\` command to create a backup`);
|
console.error(`No SQL backups to restore. Use the \`backup\` command to create a backup.`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -32,13 +32,19 @@ export default function () {
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
fs.cpSync(path.join(backup_dir, selected_backup), db_file_path);
|
const backup_path = path.join(backup_dir, selected_backup);
|
||||||
console.log(`${chalk.bold(chalk.green(`DB Restore Success!`))}`);
|
if (!fs.existsSync(backup_path)) {
|
||||||
process.exit();
|
console.error(`Backup file not found: ${backup_path}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const sql = fs.readFileSync(backup_path, "utf-8");
|
||||||
|
await restoreDatabase(config, sql);
|
||||||
|
console.log(`${chalk.bold(chalk.green(`DB Restore Success!`))} ← ${selected_backup}`);
|
||||||
|
process.exit(0);
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(`Backup Restore ERROR => ${error.message}`);
|
console.error(`Backup Restore ERROR => ${error.message}`);
|
||||||
process.exit();
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+9
-19
@@ -1,38 +1,29 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import { MariaDBSchemaManager } from "../lib/mariadb/db-schema-manager";
|
|
||||||
import init from "../functions/init";
|
|
||||||
import grabDirNames from "../data/grab-dir-names";
|
import grabDirNames from "../data/grab-dir-names";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
||||||
import _ from "lodash";
|
|
||||||
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
|
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { writeLiveSchema } from "../functions/live-schema";
|
import { writeLiveSchema } from "../functions/live-schema";
|
||||||
import grabDBDir from "../utils/grab-db-dir";
|
import createDBSchema from "../lib/schema/create-db-schema";
|
||||||
import { cpSync } from "fs";
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return new Command("schema")
|
return new Command("schema")
|
||||||
.description("Build DB From Schema")
|
.description("Build DB From Schema")
|
||||||
.option("-v, --vector", "Recreate Vector Tables. This will drop and rebuild all vector tables")
|
|
||||||
.option("-t, --typedef", "Generate typescript type definitions")
|
.option("-t, --typedef", "Generate typescript type definitions")
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
console.log(`Starting process ...`);
|
console.log(`Starting process ...`);
|
||||||
const { config, dbSchema } = await init();
|
const config = global.CONFIG;
|
||||||
const { ROOT_DIR, BUN_MARIADB_TEMP_DB_FILE_PATH } = grabDirNames();
|
const dbSchema = global.DB_SCHEMA;
|
||||||
const { db_file_path } = grabDBDir({ config });
|
const { ROOT_DIR } = grabDirNames();
|
||||||
cpSync(db_file_path, BUN_MARIADB_TEMP_DB_FILE_PATH);
|
|
||||||
try {
|
try {
|
||||||
const isVector = Boolean(opts.vector || opts.v);
|
|
||||||
const isTypeDef = Boolean(opts.typedef || opts.t);
|
const isTypeDef = Boolean(opts.typedef || opts.t);
|
||||||
const finaldbSchema = appendDefaultFieldsToDbSchema({
|
const finaldbSchema = appendDefaultFieldsToDbSchema({
|
||||||
dbSchema,
|
dbSchema,
|
||||||
});
|
});
|
||||||
const manager = new MariaDBSchemaManager({
|
await createDBSchema({
|
||||||
schema: finaldbSchema,
|
db_schema: finaldbSchema,
|
||||||
recreate_vector_table: isVector,
|
config,
|
||||||
});
|
});
|
||||||
await manager.syncSchema();
|
|
||||||
manager.close();
|
|
||||||
if (isTypeDef && config.typedef_file_path) {
|
if (isTypeDef && config.typedef_file_path) {
|
||||||
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
|
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
|
||||||
dbSchemaToTypeDef({
|
dbSchemaToTypeDef({
|
||||||
@@ -43,11 +34,10 @@ export default function () {
|
|||||||
}
|
}
|
||||||
writeLiveSchema({ schema: finaldbSchema });
|
writeLiveSchema({ schema: finaldbSchema });
|
||||||
console.log(`${chalk.bold(chalk.green(`DB Schema setup success!`))}`);
|
console.log(`${chalk.bold(chalk.green(`DB Schema setup success!`))}`);
|
||||||
process.exit();
|
process.exit(0);
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
cpSync(BUN_MARIADB_TEMP_DB_FILE_PATH, db_file_path);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+9
-11
@@ -1,5 +1,4 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import init from "../functions/init";
|
|
||||||
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import grabDirNames from "../data/grab-dir-names";
|
import grabDirNames from "../data/grab-dir-names";
|
||||||
@@ -7,25 +6,24 @@ import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-
|
|||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
export default function () {
|
export default function () {
|
||||||
return new Command("typedef")
|
return new Command("typedef")
|
||||||
.description("Build DB From Schema")
|
.description("Generate TypeScript type definitions from the schema")
|
||||||
.action(async (opts) => {
|
.action(async () => {
|
||||||
console.log(`Creating Type Definition From DB Schema ...`);
|
console.log(`Creating Type Definition From DB Schema ...`);
|
||||||
const { config, dbSchema } = await init();
|
const config = global.CONFIG;
|
||||||
|
const dbSchema = global.DB_SCHEMA;
|
||||||
const { ROOT_DIR } = grabDirNames();
|
const { ROOT_DIR } = grabDirNames();
|
||||||
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
|
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
|
||||||
if (config.typedef_file_path) {
|
if (!config.typedef_file_path) {
|
||||||
|
console.error(`\`typedef_file_path\` is required in bun-mariadb.config to generate types.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
|
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
|
||||||
dbSchemaToTypeDef({
|
dbSchemaToTypeDef({
|
||||||
dbSchema: finaldbSchema,
|
dbSchema: finaldbSchema,
|
||||||
dst_file: out_file,
|
dst_file: out_file,
|
||||||
config,
|
config,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error(``);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
|
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
|
||||||
process.exit();
|
process.exit(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+8
-1
@@ -1,6 +1,13 @@
|
|||||||
export declare const AppData: {
|
export declare const AppData: {
|
||||||
readonly ConfigFileName: "bun-sqlite.config.ts";
|
readonly ConfigFileName: "bun-mariadb.config";
|
||||||
readonly MaxBackups: 10;
|
readonly MaxBackups: 10;
|
||||||
|
readonly MaxExports: 10;
|
||||||
readonly DefaultBackupDirName: ".backups";
|
readonly DefaultBackupDirName: ".backups";
|
||||||
|
readonly DefaultExportDirName: ".exports";
|
||||||
readonly DbSchemaManagerTableName: "__db_schema_manager__";
|
readonly DbSchemaManagerTableName: "__db_schema_manager__";
|
||||||
|
readonly DbSchemaFileName: "schema";
|
||||||
|
/** Priority order when resolving config/schema files */
|
||||||
|
readonly SupportedDataFileExtensions: readonly [".ts", ".js", ".json", ".yaml", ".yml"];
|
||||||
|
readonly MaxInitRetries: 50;
|
||||||
|
readonly InitRetryIntervalMilliseconds: 5000;
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+8
-1
@@ -1,6 +1,13 @@
|
|||||||
export const AppData = {
|
export const AppData = {
|
||||||
ConfigFileName: "bun-sqlite.config.ts",
|
ConfigFileName: "bun-mariadb.config",
|
||||||
MaxBackups: 10,
|
MaxBackups: 10,
|
||||||
|
MaxExports: 10,
|
||||||
DefaultBackupDirName: ".backups",
|
DefaultBackupDirName: ".backups",
|
||||||
|
DefaultExportDirName: ".exports",
|
||||||
DbSchemaManagerTableName: "__db_schema_manager__",
|
DbSchemaManagerTableName: "__db_schema_manager__",
|
||||||
|
DbSchemaFileName: "schema",
|
||||||
|
/** Priority order when resolving config/schema files */
|
||||||
|
SupportedDataFileExtensions: [".ts", ".js", ".json", ".yaml", ".yml"],
|
||||||
|
MaxInitRetries: 50,
|
||||||
|
InitRetryIntervalMilliseconds: 5000,
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+2
-3
@@ -1,12 +1,11 @@
|
|||||||
import type { BunSQLiteConfig } from "../types";
|
import type { BunMariaDBConfig } from "../types";
|
||||||
type Params = {
|
type Params = {
|
||||||
config?: BunSQLiteConfig;
|
config?: BunMariaDBConfig;
|
||||||
};
|
};
|
||||||
export default function grabDirNames(params?: Params): {
|
export default function grabDirNames(params?: Params): {
|
||||||
ROOT_DIR: string;
|
ROOT_DIR: string;
|
||||||
BUN_MARIADB_DIR: string;
|
BUN_MARIADB_DIR: string;
|
||||||
BUN_MARIADB_TEMP_DIR: string;
|
BUN_MARIADB_TEMP_DIR: string;
|
||||||
BUN_MARIADB_LIVE_SCHEMA: string;
|
BUN_MARIADB_LIVE_SCHEMA: string;
|
||||||
BUN_MARIADB_TEMP_DB_FILE_PATH: string;
|
|
||||||
};
|
};
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Vendored
+1
-3
@@ -1,15 +1,13 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
export default function grabDirNames(params) {
|
export default function grabDirNames(params) {
|
||||||
const ROOT_DIR = process.cwd();
|
const ROOT_DIR = process.cwd();
|
||||||
const BUN_MARIADB_DIR = path.join(ROOT_DIR, ".bun-sqlite");
|
const BUN_MARIADB_DIR = path.join(ROOT_DIR, ".bun-mariadb");
|
||||||
const BUN_MARIADB_TEMP_DIR = path.join(BUN_MARIADB_DIR, ".tmp");
|
const BUN_MARIADB_TEMP_DIR = path.join(BUN_MARIADB_DIR, ".tmp");
|
||||||
const BUN_MARIADB_TEMP_DB_FILE_PATH = path.join(BUN_MARIADB_TEMP_DIR, "temp.db");
|
|
||||||
const BUN_MARIADB_LIVE_SCHEMA = path.join(BUN_MARIADB_DIR, "live-schema.json");
|
const BUN_MARIADB_LIVE_SCHEMA = path.join(BUN_MARIADB_DIR, "live-schema.json");
|
||||||
return {
|
return {
|
||||||
ROOT_DIR,
|
ROOT_DIR,
|
||||||
BUN_MARIADB_DIR,
|
BUN_MARIADB_DIR,
|
||||||
BUN_MARIADB_TEMP_DIR,
|
BUN_MARIADB_TEMP_DIR,
|
||||||
BUN_MARIADB_LIVE_SCHEMA,
|
BUN_MARIADB_LIVE_SCHEMA,
|
||||||
BUN_MARIADB_TEMP_DB_FILE_PATH,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import { type BunMariaDBConfig } from "../types";
|
||||||
|
type Params = {
|
||||||
|
config: BunMariaDBConfig;
|
||||||
|
};
|
||||||
|
export default function grabMariaDBClient({ config, }: Params): Bun.SQL | undefined;
|
||||||
|
export {};
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
import path from "path";
|
||||||
|
import grabDirNames from "../data/grab-dir-names";
|
||||||
|
import {} from "../types";
|
||||||
|
import { SQL } from "bun";
|
||||||
|
/** Reuse one client per database name to avoid exhausting MariaDB connections. */
|
||||||
|
const clientCache = new Map();
|
||||||
|
export default function grabMariaDBClient({ config, }) {
|
||||||
|
const cacheKey = config.db_name || "__default__";
|
||||||
|
const cached = clientCache.get(cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
// Prefer the process-wide client when it matches the requested database
|
||||||
|
if (global.MARIADB_CLIENT &&
|
||||||
|
(!global.CONFIG?.db_name || global.CONFIG.db_name === config.db_name)) {
|
||||||
|
clientCache.set(cacheKey, global.MARIADB_CLIENT);
|
||||||
|
return global.MARIADB_CLIENT;
|
||||||
|
}
|
||||||
|
const { ROOT_DIR } = grabDirNames();
|
||||||
|
try {
|
||||||
|
const tls = config.ssl_ca
|
||||||
|
? {
|
||||||
|
ca: Bun.file(path.resolve(ROOT_DIR, config.ssl_ca)),
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
};
|
||||||
|
const MariaDBClient = new SQL({
|
||||||
|
hostname: process.env.BUN_MARIADB_SERVER_HOST,
|
||||||
|
username: process.env.BUN_MARIADB_SERVER_USERNAME,
|
||||||
|
password: process.env.BUN_MARIADB_SERVER_PASSWORD,
|
||||||
|
database: config.db_name,
|
||||||
|
port: process.env.BUN_MARIADB_SERVER_PORT
|
||||||
|
? Number(process.env.BUN_MARIADB_SERVER_PORT)
|
||||||
|
: undefined,
|
||||||
|
...config.db_config,
|
||||||
|
tls,
|
||||||
|
adapter: "mariadb",
|
||||||
|
});
|
||||||
|
clientCache.set(cacheKey, MariaDBClient);
|
||||||
|
return MariaDBClient;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(`Couldn't grab MariaDB Client => ` + error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+12
-2
@@ -1,2 +1,12 @@
|
|||||||
import type { BunSQLiteConfigReturn } from "../types";
|
import { type BunMariaDBConfig, type BUN_MARIADB_DatabaseSchemaType } from "../types";
|
||||||
export default function init(): Promise<BunSQLiteConfigReturn>;
|
/**
|
||||||
|
* # Declare Global Variables
|
||||||
|
*/
|
||||||
|
declare global {
|
||||||
|
var CONFIG: BunMariaDBConfig;
|
||||||
|
var DB_SCHEMA: BUN_MARIADB_DatabaseSchemaType;
|
||||||
|
var MARIADB_CLIENT: Bun.SQL;
|
||||||
|
var CONFIG_FILE_PATH: string;
|
||||||
|
var SCHEMA_FILE_PATH: string;
|
||||||
|
}
|
||||||
|
export default function init(): void;
|
||||||
|
|||||||
Vendored
+57
-18
@@ -2,42 +2,81 @@ import path from "path";
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { AppData } from "../data/app-data";
|
import { AppData } from "../data/app-data";
|
||||||
import grabDirNames from "../data/grab-dir-names";
|
import grabDirNames from "../data/grab-dir-names";
|
||||||
export default async function init() {
|
import { RequiredENVs, } from "../types";
|
||||||
|
import setMariaDBClient from "./set-mariadb-client";
|
||||||
|
import { resolveAndLoadDataFile, supportedDataFileNames, } from "../utils/resolve-and-load-data-file";
|
||||||
|
export default function init() {
|
||||||
try {
|
try {
|
||||||
const { ROOT_DIR } = grabDirNames();
|
const { ROOT_DIR } = grabDirNames();
|
||||||
const { ConfigFileName } = AppData;
|
const { ConfigFileName } = AppData;
|
||||||
const ConfigFilePath = path.join(ROOT_DIR, ConfigFileName);
|
const loadedConfig = resolveAndLoadDataFile(ROOT_DIR, ConfigFileName);
|
||||||
if (!fs.existsSync(ConfigFilePath)) {
|
if (!loadedConfig) {
|
||||||
console.log("ConfigFilePath", ConfigFilePath);
|
console.error(`Please create a ${supportedDataFileNames(ConfigFileName)} file at the root of your project.`);
|
||||||
console.error(`Please create a \`${ConfigFileName}\` file at the root of your project.`);
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const Config = loadedConfig.data;
|
||||||
|
if (!Config || typeof Config !== "object") {
|
||||||
|
console.error(`Invalid config in \`${loadedConfig.path}\`. Expected a config object${loadedConfig.format === "ts" || loadedConfig.format === "js" ? " (export default)" : ""}.`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const ConfigImport = await import(ConfigFilePath);
|
|
||||||
const Config = ConfigImport["default"];
|
|
||||||
if (!Config.db_name) {
|
if (!Config.db_name) {
|
||||||
console.error(`\`db_name\` is required in your config`);
|
console.error(`\`db_name\` is required in your config`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
if (!Config.db_schema_file_name) {
|
RequiredENVs.forEach((env_key) => {
|
||||||
console.error(`\`db_schema_file_name\` is required in your config`);
|
if (env_key == "BUN_MARIADB_SERVER_PORT" ||
|
||||||
|
env_key == "BUN_MARIADB_SERVER_SSL_KEY_PATH") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!process.env[env_key]) {
|
||||||
|
console.error(`\`${env_key}\` env variable is required.`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
let db_dir = ROOT_DIR;
|
});
|
||||||
if (Config.db_dir) {
|
if (!Config.db_dir) {
|
||||||
db_dir = path.resolve(ROOT_DIR, Config.db_dir);
|
console.error(`\`db_dir\` is required in your config. This directory holds all database related configuration. Also note that a ${supportedDataFileNames(AppData.DbSchemaFileName)} file is also required in this directory to define your database schema`);
|
||||||
if (!fs.existsSync(Config.db_dir)) {
|
process.exit(1);
|
||||||
fs.mkdirSync(Config.db_dir, { recursive: true });
|
|
||||||
}
|
}
|
||||||
|
const db_dir = path.resolve(ROOT_DIR, Config.db_dir);
|
||||||
|
if (!fs.existsSync(db_dir)) {
|
||||||
|
fs.mkdirSync(db_dir, { recursive: true });
|
||||||
|
}
|
||||||
|
const loadedSchema = resolveAndLoadDataFile(db_dir, AppData.DbSchemaFileName);
|
||||||
|
if (!loadedSchema) {
|
||||||
|
console.error(`Please create a schema file (${supportedDataFileNames(AppData.DbSchemaFileName)}) in \`${db_dir}\`${loadedConfig.format === "ts" || loadedConfig.format === "js" ? ". Don't forget to export a default module from .ts/.js files." : "."}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const DbSchema = loadedSchema.data;
|
||||||
|
if (!DbSchema || typeof DbSchema !== "object") {
|
||||||
|
console.error(`Invalid schema in \`${loadedSchema.path}\`. Expected a schema object${loadedSchema.format === "ts" || loadedSchema.format === "js" ? " (export default)" : ""}.`);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const DBSchemaFilePath = path.join(db_dir, Config.db_schema_file_name);
|
|
||||||
const DbSchemaImport = await import(DBSchemaFilePath);
|
|
||||||
const DbSchema = DbSchemaImport["default"];
|
|
||||||
const backup_dir = Config.db_backup_dir || AppData["DefaultBackupDirName"];
|
const backup_dir = Config.db_backup_dir || AppData["DefaultBackupDirName"];
|
||||||
const BackupDir = path.resolve(db_dir, backup_dir);
|
const BackupDir = path.resolve(db_dir, backup_dir);
|
||||||
if (!fs.existsSync(BackupDir)) {
|
if (!fs.existsSync(BackupDir)) {
|
||||||
fs.mkdirSync(BackupDir, { recursive: true });
|
fs.mkdirSync(BackupDir, { recursive: true });
|
||||||
}
|
}
|
||||||
return { config: Config, dbSchema: DbSchema };
|
const ExportDir = path.resolve(db_dir, AppData["DefaultExportDirName"]);
|
||||||
|
if (!fs.existsSync(ExportDir)) {
|
||||||
|
fs.mkdirSync(ExportDir, { recursive: true });
|
||||||
|
}
|
||||||
|
global.CONFIG = Config;
|
||||||
|
global.DB_SCHEMA = DbSchema;
|
||||||
|
global.CONFIG_FILE_PATH = loadedConfig.path;
|
||||||
|
global.SCHEMA_FILE_PATH = loadedSchema.path;
|
||||||
|
if (!global.CONFIG) {
|
||||||
|
console.error(`Couldn't grab global Config.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (!global.DB_SCHEMA) {
|
||||||
|
console.error(`Couldn't grab Database Schema.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
setMariaDBClient({ config: Config });
|
||||||
|
if (!global.MARIADB_CLIENT) {
|
||||||
|
console.error(`Couldn't set MariaDB Client.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(`Initialization ERROR => ` + error.message);
|
console.error(`Initialization ERROR => ` + error.message);
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import { type BunMariaDBConfig } from "../types";
|
||||||
|
type Params = {
|
||||||
|
config: BunMariaDBConfig;
|
||||||
|
};
|
||||||
|
export default function setMariaDBClient({ config }: Params): void;
|
||||||
|
export {};
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import {} from "../types";
|
||||||
|
import grabMariaDBClient from "./grab-mariadb-client";
|
||||||
|
export default function setMariaDBClient({ config }) {
|
||||||
|
try {
|
||||||
|
const MariaDBClient = grabMariaDBClient({ config });
|
||||||
|
if (!MariaDBClient) {
|
||||||
|
throw new Error(`Couldn't grab MariaDB Client`);
|
||||||
|
}
|
||||||
|
global.MARIADB_CLIENT = MariaDBClient;
|
||||||
|
// const test = await MariaDBClient.unsafe(`SHOW DATABASES`);
|
||||||
|
// if (!test.count) {
|
||||||
|
// console.error(`MariaDBClient Error: Database not ready.`);
|
||||||
|
// console.log(test);
|
||||||
|
// process.exit(1);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(`ERROR setting MariaDB Client => ` + error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+2
@@ -17,3 +17,5 @@ declare const BunMariaDB: {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
export default BunMariaDB;
|
export default BunMariaDB;
|
||||||
|
export type * from "./types";
|
||||||
|
export { UsersOmitedFields, MariaDBCollations, MariaDBCharsets, TextFieldTypesArray, BUN_MARIADB_DATATYPES, MariaDBIndexTypes, ServerQueryOperators, ServerQueryEqualities, SQlComparisons, DataCrudRequestMethods, DataCrudRequestMethodsLowerCase, DsqlCrudActions, QueryFields, DockerComposeServices, IndexTypes, DefaultFields, RequiredENVs, } from "./types";
|
||||||
|
|||||||
Vendored
+3
@@ -1,3 +1,4 @@
|
|||||||
|
import init from "./functions/init";
|
||||||
import DbDelete from "./lib/mariadb/db-delete";
|
import DbDelete from "./lib/mariadb/db-delete";
|
||||||
import DbInsert from "./lib/mariadb/db-insert";
|
import DbInsert from "./lib/mariadb/db-insert";
|
||||||
import DbSelect from "./lib/mariadb/db-select";
|
import DbSelect from "./lib/mariadb/db-select";
|
||||||
@@ -5,6 +6,7 @@ import DbSQL from "./lib/mariadb/db-sql";
|
|||||||
import DbUpdate from "./lib/mariadb/db-update";
|
import DbUpdate from "./lib/mariadb/db-update";
|
||||||
import grabDbSchema from "./utils/grab-db-schema";
|
import grabDbSchema from "./utils/grab-db-schema";
|
||||||
import grabJoinFieldsFromQueryObject from "./utils/grab-join-fields-from-query-object";
|
import grabJoinFieldsFromQueryObject from "./utils/grab-join-fields-from-query-object";
|
||||||
|
init();
|
||||||
const BunMariaDB = {
|
const BunMariaDB = {
|
||||||
select: DbSelect,
|
select: DbSelect,
|
||||||
insert: DbInsert,
|
insert: DbInsert,
|
||||||
@@ -17,3 +19,4 @@ const BunMariaDB = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
export default BunMariaDB;
|
export default BunMariaDB;
|
||||||
|
export { UsersOmitedFields, MariaDBCollations, MariaDBCharsets, TextFieldTypesArray, BUN_MARIADB_DATATYPES, MariaDBIndexTypes, ServerQueryOperators, ServerQueryEqualities, SQlComparisons, DataCrudRequestMethods, DataCrudRequestMethodsLowerCase, DsqlCrudActions, QueryFields, DockerComposeServices, IndexTypes, DefaultFields, RequiredENVs, } from "./types";
|
||||||
|
|||||||
Vendored
+4
-9
@@ -1,20 +1,15 @@
|
|||||||
import type { ConnectionConfig } from "mariadb";
|
import type { BunMariaDBConfig, DBResponseObject } from "../types";
|
||||||
import type { BUN_MARIADB_TableSchemaType, DBResponseObject } from "../types";
|
|
||||||
type Param = {
|
type Param = {
|
||||||
query: string;
|
query: string;
|
||||||
values?: string[] | object;
|
values?: any[];
|
||||||
noErrorLogs?: boolean;
|
config?: BunMariaDBConfig;
|
||||||
database?: string;
|
|
||||||
tableSchema?: BUN_MARIADB_TableSchemaType;
|
|
||||||
config?: ConnectionConfig;
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* # Main DB Handler Function
|
* # Main DB Handler Function
|
||||||
* @requires DSQL_DB_CONN - Gobal Variable for Datasquirel Database
|
|
||||||
*/
|
*/
|
||||||
export default function dbHandler<T extends {
|
export default function dbHandler<T extends {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
} = {
|
} = {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
}>({ query, values, noErrorLogs, database, config, }: Param): Promise<DBResponseObject>;
|
}>({ query, values, config }: Param): Promise<DBResponseObject<T>>;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Vendored
+45
-29
@@ -1,46 +1,62 @@
|
|||||||
import grabDBConnection from "./grab-db-connection";
|
import grabMariaDBClient from "../functions/grab-mariadb-client";
|
||||||
|
/**
|
||||||
|
* Resolve a shared MariaDB client. Prefer the global client so schema sync
|
||||||
|
* and CRUD do not open a new connection per query.
|
||||||
|
*/
|
||||||
|
function resolveClient(config) {
|
||||||
|
const resolvedConfig = config || global.CONFIG;
|
||||||
|
// Same database as the global client → reuse it
|
||||||
|
if (global.MARIADB_CLIENT &&
|
||||||
|
(!resolvedConfig?.db_name ||
|
||||||
|
!global.CONFIG?.db_name ||
|
||||||
|
resolvedConfig.db_name === global.CONFIG.db_name)) {
|
||||||
|
return global.MARIADB_CLIENT;
|
||||||
|
}
|
||||||
|
if (resolvedConfig) {
|
||||||
|
return grabMariaDBClient({ config: resolvedConfig });
|
||||||
|
}
|
||||||
|
return global.MARIADB_CLIENT;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* # Main DB Handler Function
|
* # Main DB Handler Function
|
||||||
* @requires DSQL_DB_CONN - Gobal Variable for Datasquirel Database
|
|
||||||
*/
|
*/
|
||||||
export default async function dbHandler({ query, values, noErrorLogs, database, config, }) {
|
export default async function dbHandler({ query, values, config }) {
|
||||||
let CONNECTION;
|
|
||||||
let results = null;
|
|
||||||
try {
|
try {
|
||||||
CONNECTION = await grabDBConnection({ database, config });
|
const CLIENT = resolveClient(config);
|
||||||
if (query && values) {
|
if (!CLIENT) {
|
||||||
const queryResults = await CONNECTION.query(query, values);
|
throw new Error(`Couldn't grab MariaDB Client.`);
|
||||||
results = queryResults[0];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const queryResults = await CONNECTION.query(query);
|
|
||||||
results = queryResults[0];
|
|
||||||
}
|
}
|
||||||
|
const res = await CLIENT.unsafe(query, values);
|
||||||
|
const count = res.count;
|
||||||
|
const res_array = (() => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(res));
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.message &&
|
return undefined;
|
||||||
typeof error.message == "string" &&
|
|
||||||
error.message.match(/Access denied for user.*password/i)) {
|
|
||||||
throw new Error("Authentication Failed!");
|
|
||||||
}
|
}
|
||||||
if (!noErrorLogs) {
|
})();
|
||||||
console.log(error);
|
const last_insert_id = res_array?.[0] ? res_array[0]?.id : undefined;
|
||||||
}
|
const affected_rows = res_array?.[0] ? res_array.length : undefined;
|
||||||
results = null;
|
const insert_return = {
|
||||||
}
|
count,
|
||||||
finally {
|
last_insert_id,
|
||||||
await CONNECTION?.end();
|
affected_rows,
|
||||||
}
|
};
|
||||||
if (results) {
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
payload: Array.isArray(results) ? results : undefined,
|
payload: res_array,
|
||||||
single_res: Array.isArray(results) ? undefined : results,
|
single_res: res_array?.[0],
|
||||||
|
insert_return,
|
||||||
|
count,
|
||||||
|
db_res: res,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
catch (error) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
error: "DB Handler Error => " + error.message,
|
||||||
|
msg: "DB Handler Error => " + error.message,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-6
@@ -1,6 +0,0 @@
|
|||||||
import type { Connection } from "mariadb";
|
|
||||||
import type { DsqlConnectionParam } from "../types";
|
|
||||||
/**
|
|
||||||
* # Grab General CONNECTION for DSQL
|
|
||||||
*/
|
|
||||||
export default function grabDBConnection(param?: DsqlConnectionParam): Promise<Connection>;
|
|
||||||
Vendored
-15
@@ -1,15 +0,0 @@
|
|||||||
import * as mariadb from "mariadb";
|
|
||||||
import grabDSQLConnectionConfig from "./grab-dsql-connection-config";
|
|
||||||
/**
|
|
||||||
* # Grab General CONNECTION for DSQL
|
|
||||||
*/
|
|
||||||
export default async function grabDBConnection(param) {
|
|
||||||
const config = grabDSQLConnectionConfig(param);
|
|
||||||
try {
|
|
||||||
return await mariadb.createConnection(config);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.log(`Error Grabbing DSQL Connection =>`, config);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-7
@@ -1,7 +0,0 @@
|
|||||||
import type { ConnectionConfig } from "mariadb";
|
|
||||||
type Return = ConnectionConfig["ssl"] | undefined;
|
|
||||||
/**
|
|
||||||
* # Grab SSL
|
|
||||||
*/
|
|
||||||
export default function grabDbSSL(): Return;
|
|
||||||
export {};
|
|
||||||
Vendored
-20
@@ -1,20 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
/**
|
|
||||||
* # Grab SSL
|
|
||||||
*/
|
|
||||||
export default function grabDbSSL() {
|
|
||||||
const caProivdedPath = process.env.DSQL_SSL_CA_CERT;
|
|
||||||
if (!caProivdedPath?.match(/./)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const caFilePath = path.resolve(process.cwd(), caProivdedPath);
|
|
||||||
if (!fs.existsSync(caFilePath)) {
|
|
||||||
console.log(`${caFilePath} does not exist`);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ca: fs.readFileSync(caFilePath),
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
-6
@@ -1,6 +0,0 @@
|
|||||||
import { type ConnectionConfig } from "mariadb";
|
|
||||||
import type { DsqlConnectionParam } from "../types";
|
|
||||||
/**
|
|
||||||
* # Grab General CONNECTION for DSQL
|
|
||||||
*/
|
|
||||||
export default function grabDSQLConnectionConfig(param?: DsqlConnectionParam): ConnectionConfig;
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
import {} from "mariadb";
|
|
||||||
import grabDbSSL from "./grab-db-ssl";
|
|
||||||
/**
|
|
||||||
* # Grab General CONNECTION for DSQL
|
|
||||||
*/
|
|
||||||
export default function grabDSQLConnectionConfig(param) {
|
|
||||||
const CONN_TIMEOUT = 10000;
|
|
||||||
const config = {
|
|
||||||
host: process.env.DSQL_DB_HOST,
|
|
||||||
user: process.env.DSQL_DB_USERNAME,
|
|
||||||
password: process.env.DSQL_DB_PASSWORD,
|
|
||||||
// database: param?.dbFullName,
|
|
||||||
port: process.env.DSQL_DB_PORT
|
|
||||||
? Number(process.env.DSQL_DB_PORT)
|
|
||||||
: undefined,
|
|
||||||
charset: "utf8mb4",
|
|
||||||
ssl: grabDbSSL(),
|
|
||||||
bigIntAsNumber: true,
|
|
||||||
supportBigNumbers: true,
|
|
||||||
bigNumberStrings: false,
|
|
||||||
dateStrings: true,
|
|
||||||
metaAsArray: true,
|
|
||||||
socketTimeout: CONN_TIMEOUT,
|
|
||||||
connectTimeout: CONN_TIMEOUT,
|
|
||||||
compress: true,
|
|
||||||
...param?.config,
|
|
||||||
};
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
+1
-1
@@ -3,5 +3,5 @@ type Params = {
|
|||||||
table: string;
|
table: string;
|
||||||
data: any | any[];
|
data: any | any[];
|
||||||
};
|
};
|
||||||
export default function ({ sql: passed_sql, table, data }: Params): Promise<string>;
|
export default function ({ sql: passedSql, table, data }: Params): Promise<string>;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
+21
-20
@@ -1,24 +1,25 @@
|
|||||||
import init from "../functions/init";
|
function quoteIdentifier(identifier) {
|
||||||
export default async function ({ sql: passed_sql, table, data }) {
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||||
let sql = passed_sql;
|
}
|
||||||
const { dbSchema } = await init();
|
export default async function ({ sql: passedSql, table, data }) {
|
||||||
const table_schema = dbSchema.tables.find((t) => t.tableName == table);
|
const config = global.CONFIG;
|
||||||
const now = Date.now();
|
const dbSchema = global.DB_SCHEMA;
|
||||||
if (table_schema?.tableName) {
|
const tableSchema = dbSchema?.tables.find((t) => t.tableName == table);
|
||||||
const set_sql_arr = Object.keys(Array.isArray(data) ? data[0] : data).map((field) => `${field} = excluded.${field}`);
|
if (!tableSchema?.tableName) {
|
||||||
set_sql_arr.push(`updated_at = ${now}`);
|
return passedSql;
|
||||||
const set_sql = set_sql_arr.join(", ");
|
|
||||||
const unique_fields = table_schema.fields.filter((f) => f.unique);
|
|
||||||
for (let i = 0; i < unique_fields.length; i++) {
|
|
||||||
const field = unique_fields[i];
|
|
||||||
sql += ` ON CONFLICT(${field?.fieldName}) DO UPDATE SET ${set_sql}`;
|
|
||||||
}
|
}
|
||||||
if (table_schema.uniqueConstraints?.[0]) {
|
const dataObject = Array.isArray(data) ? data[0] : data;
|
||||||
for (let i = 0; i < table_schema.uniqueConstraints.length; i++) {
|
const dataKeys = Object.keys(dataObject || {});
|
||||||
const constraint = table_schema.uniqueConstraints[i];
|
if (dataKeys.length === 0) {
|
||||||
sql += ` ON CONFLICT(${constraint?.constraintTableFields?.map((c) => c.value)?.join(", ")}) DO UPDATE SET ${set_sql}`;
|
return passedSql;
|
||||||
}
|
}
|
||||||
|
const hasUniqueField = tableSchema.fields.some((field) => field.unique) || Boolean(tableSchema.uniqueConstraints?.length);
|
||||||
|
if (!hasUniqueField) {
|
||||||
|
return passedSql;
|
||||||
}
|
}
|
||||||
}
|
const setSql = dataKeys
|
||||||
return sql;
|
.filter((field) => field !== "updated_at")
|
||||||
|
.map((field) => `${quoteIdentifier(field)} = VALUES(${quoteIdentifier(field)})`);
|
||||||
|
setSql.push(`updated_at = ${Date.now()}`);
|
||||||
|
return `${passedSql} ON DUPLICATE KEY UPDATE ${setSql.join(", ")}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+3
-2
@@ -1,4 +1,4 @@
|
|||||||
import type { APIResponseObject, ServerQueryParam } from "../../types";
|
import type { BunMariaDBConfig, DBResponseObject, ServerQueryParam } from "../../types";
|
||||||
type Params<Schema extends {
|
type Params<Schema extends {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
} = {
|
} = {
|
||||||
@@ -7,10 +7,11 @@ type Params<Schema extends {
|
|||||||
table: Table;
|
table: Table;
|
||||||
query?: ServerQueryParam<Schema>;
|
query?: ServerQueryParam<Schema>;
|
||||||
targetId?: number | string;
|
targetId?: number | string;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
};
|
};
|
||||||
export default function DbDelete<Schema extends {
|
export default function DbDelete<Schema extends {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
} = {
|
} = {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
}, Table extends string = string>({ table, query, targetId, }: Params<Schema, Table>): Promise<APIResponseObject>;
|
}, Table extends string = string>({ table, query, targetId, config, }: Params<Schema, Table>): Promise<DBResponseObject>;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Vendored
+33
-20
@@ -1,7 +1,10 @@
|
|||||||
import DbClient from ".";
|
import dbHandler from "../db-handler";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import sqlGenerator from "../../utils/sql-generator";
|
import sqlGenerator from "../../utils/sql-generator";
|
||||||
export default async function DbDelete({ table, query, targetId, }) {
|
function quoteIdentifier(identifier) {
|
||||||
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||||
|
}
|
||||||
|
export default async function DbDelete({ table, query, targetId, config, }) {
|
||||||
let sqlObj = null;
|
let sqlObj = null;
|
||||||
try {
|
try {
|
||||||
let finalQuery = query || {};
|
let finalQuery = query || {};
|
||||||
@@ -9,32 +12,17 @@ export default async function DbDelete({ table, query, targetId, }) {
|
|||||||
finalQuery = _.merge(finalQuery, {
|
finalQuery = _.merge(finalQuery, {
|
||||||
query: {
|
query: {
|
||||||
id: {
|
id: {
|
||||||
value: String(targetId),
|
value: Number(targetId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
sqlObj = sqlGenerator({
|
sqlObj = sqlGenerator({
|
||||||
tableName: table,
|
tableName: quoteIdentifier(table),
|
||||||
genObject: finalQuery,
|
genObject: finalQuery,
|
||||||
});
|
});
|
||||||
const whereClause = sqlObj.string.match(/WHERE .*/)?.[0];
|
const whereClause = sqlObj.string.match(/WHERE .*/)?.[0];
|
||||||
if (whereClause) {
|
if (!whereClause) {
|
||||||
let sql = `DELETE FROM ${table} ${whereClause}`;
|
|
||||||
sqlObj.string = sql;
|
|
||||||
const res = DbClient.run(sql, sqlObj.values);
|
|
||||||
return {
|
|
||||||
success: Boolean(res.changes),
|
|
||||||
postInsertReturn: {
|
|
||||||
affectedRows: res.changes,
|
|
||||||
insertId: Number(res.lastInsertRowid),
|
|
||||||
},
|
|
||||||
debug: {
|
|
||||||
sqlObj,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
msg: `No WHERE clause`,
|
msg: `No WHERE clause`,
|
||||||
@@ -43,6 +31,31 @@ export default async function DbDelete({ table, query, targetId, }) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
let sql = `DELETE FROM ${quoteIdentifier(table)} ${whereClause}`;
|
||||||
|
sql += ` RETURNING *`;
|
||||||
|
sqlObj.string = sql;
|
||||||
|
const res = await dbHandler({
|
||||||
|
query: sql,
|
||||||
|
values: sqlObj.values,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
if (!res.success) {
|
||||||
|
return {
|
||||||
|
msg: "Database delete failed",
|
||||||
|
...res,
|
||||||
|
success: false,
|
||||||
|
debug: {
|
||||||
|
sqlObj,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
..._.omit(res, ["payload"]),
|
||||||
|
success: Boolean(res.insert_return?.last_insert_id),
|
||||||
|
debug: {
|
||||||
|
sqlObj,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Vendored
+3
-2
@@ -1,4 +1,4 @@
|
|||||||
import type { APIResponseObject } from "../../types";
|
import type { BunMariaDBConfig, DBResponseObject } from "../../types";
|
||||||
type Params<Schema extends {
|
type Params<Schema extends {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
} = {
|
} = {
|
||||||
@@ -7,10 +7,11 @@ type Params<Schema extends {
|
|||||||
table: Table;
|
table: Table;
|
||||||
data: Schema[];
|
data: Schema[];
|
||||||
update_on_duplicate?: boolean;
|
update_on_duplicate?: boolean;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
};
|
};
|
||||||
export default function DbInsert<Schema extends {
|
export default function DbInsert<Schema extends {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
} = {
|
} = {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
}, Table extends string = string>({ table, data, update_on_duplicate, }: Params<Schema, Table>): Promise<APIResponseObject>;
|
}, Table extends string = string>({ table, data, update_on_duplicate, config, }: Params<Schema, Table>): Promise<DBResponseObject>;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Vendored
+34
-10
@@ -1,10 +1,16 @@
|
|||||||
import DbClient from ".";
|
import dbHandler from "../db-handler";
|
||||||
import sqlInsertGenerator from "../../utils/sql-insert-generator";
|
import sqlInsertGenerator from "../../utils/sql-insert-generator";
|
||||||
|
import { sanitizeHtmlFieldsBatch } from "../../utils/sanitize-html-fields";
|
||||||
import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql";
|
import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql";
|
||||||
export default async function DbInsert({ table, data, update_on_duplicate, }) {
|
export default async function DbInsert({ table, data, update_on_duplicate, config, }) {
|
||||||
let sqlObj = null;
|
let sqlObj = null;
|
||||||
try {
|
try {
|
||||||
const finalData = data.map((d) => ({
|
const sanitizedData = sanitizeHtmlFieldsBatch({
|
||||||
|
table,
|
||||||
|
data,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
const finalData = sanitizedData.map((d) => ({
|
||||||
created_at: Date.now(),
|
created_at: Date.now(),
|
||||||
updated_at: Date.now(),
|
updated_at: Date.now(),
|
||||||
...d,
|
...d,
|
||||||
@@ -14,18 +20,36 @@ export default async function DbInsert({ table, data, update_on_duplicate, }) {
|
|||||||
tableName: table,
|
tableName: table,
|
||||||
data: finalData,
|
data: finalData,
|
||||||
}) || null;
|
}) || null;
|
||||||
let sql = sqlObj?.query || "";
|
if (!sqlObj) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
msg: "No insert SQL generated",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let sql = sqlObj.query;
|
||||||
if (update_on_duplicate && data[0]) {
|
if (update_on_duplicate && data[0]) {
|
||||||
sql = await grabDuplicateSafeInsertSql({ data, table, sql });
|
sql = await grabDuplicateSafeInsertSql({ data, table, sql });
|
||||||
}
|
}
|
||||||
(sqlObj || {}).query = sql;
|
sql += ` RETURNING *`;
|
||||||
const res = DbClient.run(sql, sqlObj?.values || []);
|
const res = await dbHandler({
|
||||||
|
query: sql,
|
||||||
|
values: sqlObj.values,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
if (!res.success) {
|
||||||
return {
|
return {
|
||||||
success: Boolean(Number(res.lastInsertRowid)),
|
msg: "Database insert failed",
|
||||||
postInsertReturn: {
|
...res,
|
||||||
affectedRows: res.changes,
|
success: false,
|
||||||
insertId: Number(res.lastInsertRowid),
|
debug: {
|
||||||
|
sqlObj,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
success: Boolean(res?.insert_return?.affected_rows ||
|
||||||
|
res.insert_return?.last_insert_id),
|
||||||
debug: {
|
debug: {
|
||||||
sqlObj,
|
sqlObj,
|
||||||
},
|
},
|
||||||
|
|||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
#!/usr/bin/env bun
|
|
||||||
import type { BUN_MARIADB_DatabaseSchemaType } from "../../types";
|
|
||||||
declare class MariaDBSchemaManager {
|
|
||||||
private db_manager_table_name;
|
|
||||||
private recreate_vector_table;
|
|
||||||
private db_schema;
|
|
||||||
constructor({ schema, recreate_vector_table, }: {
|
|
||||||
schema: BUN_MARIADB_DatabaseSchemaType;
|
|
||||||
recreate_vector_table?: boolean;
|
|
||||||
});
|
|
||||||
syncSchema(): Promise<void>;
|
|
||||||
private getDatabaseName;
|
|
||||||
private quoteIdentifier;
|
|
||||||
private tableSchemaWhere;
|
|
||||||
private run;
|
|
||||||
private query;
|
|
||||||
private createDbManagerTable;
|
|
||||||
private insertDbManagerTable;
|
|
||||||
private removeDbManagerTable;
|
|
||||||
private getExistingTables;
|
|
||||||
private getLiveTableNames;
|
|
||||||
private dropRemovedTables;
|
|
||||||
private syncTable;
|
|
||||||
private resolveTable;
|
|
||||||
private createTable;
|
|
||||||
private buildTableOptions;
|
|
||||||
private updateTable;
|
|
||||||
private getTableColumns;
|
|
||||||
private addColumn;
|
|
||||||
private checkIfTableExists;
|
|
||||||
private recreateTable;
|
|
||||||
private insertRows;
|
|
||||||
private buildColumnDefinition;
|
|
||||||
private mapDataType;
|
|
||||||
private buildForeignKeyConstraint;
|
|
||||||
private syncIndexes;
|
|
||||||
close(): void;
|
|
||||||
}
|
|
||||||
export { MariaDBSchemaManager };
|
|
||||||
-419
@@ -1,419 +0,0 @@
|
|||||||
#!/usr/bin/env bun
|
|
||||||
import _ from "lodash";
|
|
||||||
import dbHandler from "../db-handler";
|
|
||||||
import { AppData } from "../../data/app-data";
|
|
||||||
import { readLiveSchema } from "../../functions/live-schema";
|
|
||||||
class MariaDBSchemaManager {
|
|
||||||
db_manager_table_name;
|
|
||||||
recreate_vector_table;
|
|
||||||
db_schema;
|
|
||||||
constructor({ schema, recreate_vector_table = false, }) {
|
|
||||||
this.db_manager_table_name = AppData["DbSchemaManagerTableName"];
|
|
||||||
this.recreate_vector_table = recreate_vector_table;
|
|
||||||
this.db_schema = schema;
|
|
||||||
}
|
|
||||||
async syncSchema() {
|
|
||||||
console.log("Starting schema synchronization...");
|
|
||||||
await this.createDbManagerTable();
|
|
||||||
const existingTables = await this.getExistingTables();
|
|
||||||
const schemaTables = this.db_schema.tables.map((t) => t.tableName);
|
|
||||||
for (const table of this.db_schema.tables) {
|
|
||||||
await this.syncTable(table, existingTables);
|
|
||||||
}
|
|
||||||
await this.dropRemovedTables(existingTables, schemaTables);
|
|
||||||
console.log("Schema synchronization complete!");
|
|
||||||
}
|
|
||||||
getDatabaseName() {
|
|
||||||
return this.db_schema.dbName || this.db_schema.dbSlug;
|
|
||||||
}
|
|
||||||
quoteIdentifier(identifier) {
|
|
||||||
return `\`${identifier.replace(/`/g, "``")}\``;
|
|
||||||
}
|
|
||||||
tableSchemaWhere(tableName) {
|
|
||||||
const databaseName = this.getDatabaseName();
|
|
||||||
if (databaseName) {
|
|
||||||
return {
|
|
||||||
where: "TABLE_SCHEMA = ?",
|
|
||||||
values: [databaseName, tableName],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
where: "TABLE_SCHEMA = DATABASE()",
|
|
||||||
values: [tableName],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
async run(query, values) {
|
|
||||||
const res = await dbHandler({
|
|
||||||
query,
|
|
||||||
values: values,
|
|
||||||
config: this.getDatabaseName()
|
|
||||||
? { database: this.getDatabaseName() }
|
|
||||||
: undefined,
|
|
||||||
});
|
|
||||||
if (!res.success) {
|
|
||||||
throw new Error(`Database query failed: ${query}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async query(query, values) {
|
|
||||||
const res = await dbHandler({
|
|
||||||
query,
|
|
||||||
values: values,
|
|
||||||
config: this.getDatabaseName()
|
|
||||||
? { database: this.getDatabaseName() }
|
|
||||||
: undefined,
|
|
||||||
});
|
|
||||||
if (!res.success) {
|
|
||||||
throw new Error(`Database query failed: ${query}`);
|
|
||||||
}
|
|
||||||
return (res.payload || []);
|
|
||||||
}
|
|
||||||
async createDbManagerTable() {
|
|
||||||
await this.run(`
|
|
||||||
CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(this.db_manager_table_name)} (
|
|
||||||
table_name VARCHAR(255) NOT NULL PRIMARY KEY,
|
|
||||||
created_at BIGINT NOT NULL,
|
|
||||||
updated_at BIGINT NOT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
async insertDbManagerTable(tableName) {
|
|
||||||
const now = Date.now();
|
|
||||||
await this.run(`INSERT INTO ${this.quoteIdentifier(this.db_manager_table_name)} (table_name, created_at, updated_at) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE updated_at = VALUES(updated_at)`, [tableName, now, now]);
|
|
||||||
}
|
|
||||||
async removeDbManagerTable(tableName) {
|
|
||||||
await this.run(`DELETE FROM ${this.quoteIdentifier(this.db_manager_table_name)} WHERE table_name = ?`, [tableName]);
|
|
||||||
}
|
|
||||||
async getExistingTables() {
|
|
||||||
const rows = await this.query(`SELECT table_name FROM ${this.quoteIdentifier(this.db_manager_table_name)}`);
|
|
||||||
return rows.map((row) => row.table_name);
|
|
||||||
}
|
|
||||||
async getLiveTableNames() {
|
|
||||||
const tableSchemaWhere = this.tableSchemaWhere("");
|
|
||||||
const rows = await this.query(`SELECT TABLE_NAME FROM information_schema.TABLES WHERE ${tableSchemaWhere.where} AND TABLE_TYPE = 'BASE TABLE'`, tableSchemaWhere.values);
|
|
||||||
return rows.map((row) => row.TABLE_NAME);
|
|
||||||
}
|
|
||||||
async dropRemovedTables(existingTables, schemaTables) {
|
|
||||||
console.log(`Cleaning up tables ...`);
|
|
||||||
const tablesToDrop = existingTables.filter((tableName) => !schemaTables.includes(tableName) &&
|
|
||||||
!schemaTables.some((schemaTable) => tableName.startsWith(`${schemaTable}_`)));
|
|
||||||
const currentSchema = readLiveSchema();
|
|
||||||
if (currentSchema?.tables?.[0]) {
|
|
||||||
for (const table of currentSchema.tables) {
|
|
||||||
if (!table?.tableName)
|
|
||||||
continue;
|
|
||||||
const doesTableExist = schemaTables.find((tableName) => tableName === table.tableName);
|
|
||||||
if (!doesTableExist) {
|
|
||||||
tablesToDrop.push(table.tableName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const tableName of tablesToDrop) {
|
|
||||||
console.log(`Dropping table: ${tableName}`);
|
|
||||||
await this.run(`DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`);
|
|
||||||
await this.removeDbManagerTable(tableName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async syncTable(table, existingTables) {
|
|
||||||
let tableExists = existingTables.includes(table.tableName);
|
|
||||||
const liveTables = await this.getLiveTableNames();
|
|
||||||
if (table.tableNameOld && table.tableNameOld !== table.tableName) {
|
|
||||||
if (liveTables.includes(table.tableNameOld)) {
|
|
||||||
console.log(`Renaming table: ${table.tableNameOld} -> ${table.tableName}`);
|
|
||||||
await this.run(`RENAME TABLE ${this.quoteIdentifier(table.tableNameOld)} TO ${this.quoteIdentifier(table.tableName)}`);
|
|
||||||
await this.insertDbManagerTable(table.tableName);
|
|
||||||
await this.removeDbManagerTable(table.tableNameOld);
|
|
||||||
tableExists = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!tableExists) {
|
|
||||||
await this.createTable(table);
|
|
||||||
await this.insertDbManagerTable(table.tableName);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
await this.updateTable(table);
|
|
||||||
await this.insertDbManagerTable(table.tableName);
|
|
||||||
}
|
|
||||||
await this.syncIndexes(table);
|
|
||||||
}
|
|
||||||
resolveTable(table) {
|
|
||||||
if (!table.parentTableName) {
|
|
||||||
return _.cloneDeep(table);
|
|
||||||
}
|
|
||||||
const parentTable = this.db_schema.tables.find((schemaTable) => schemaTable.tableName === table.parentTableName);
|
|
||||||
if (!parentTable) {
|
|
||||||
throw new Error(`Parent table \`${table.parentTableName}\` not found for \`${table.tableName}\``);
|
|
||||||
}
|
|
||||||
return _.merge({}, parentTable, {
|
|
||||||
tableName: table.tableName,
|
|
||||||
tableDescription: table.tableDescription,
|
|
||||||
collation: table.collation,
|
|
||||||
fields: [...(parentTable.fields || []), ...(table.fields || [])],
|
|
||||||
indexes: [...(parentTable.indexes || []), ...(table.indexes || [])],
|
|
||||||
uniqueConstraints: [
|
|
||||||
...(parentTable.uniqueConstraints || []),
|
|
||||||
...(table.uniqueConstraints || []),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async createTable(table) {
|
|
||||||
if (!table.tableName.match(/_temp_\d+$/)) {
|
|
||||||
console.log(`Creating table: ${table.tableName}`);
|
|
||||||
}
|
|
||||||
const newTable = this.resolveTable(table);
|
|
||||||
const columnDefinitions = [];
|
|
||||||
const foreignKeys = [];
|
|
||||||
for (const field of newTable.fields || []) {
|
|
||||||
columnDefinitions.push(this.buildColumnDefinition(field));
|
|
||||||
if (field.foreignKey && !newTable.isVector) {
|
|
||||||
foreignKeys.push(this.buildForeignKeyConstraint(field));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (newTable.uniqueConstraints) {
|
|
||||||
for (const constraint of newTable.uniqueConstraints) {
|
|
||||||
if (constraint.constraintTableFields &&
|
|
||||||
constraint.constraintTableFields.length > 0) {
|
|
||||||
const fields = constraint.constraintTableFields
|
|
||||||
.map((field) => this.quoteIdentifier(field.value))
|
|
||||||
.join(", ");
|
|
||||||
const constraintName = constraint.constraintName ||
|
|
||||||
`unique_${fields.replace(/`/g, "")}`;
|
|
||||||
columnDefinitions.push(`CONSTRAINT ${this.quoteIdentifier(constraintName)} UNIQUE (${fields})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const sql = `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(newTable.tableName)} (${[...columnDefinitions, ...foreignKeys].join(", ")})${this.buildTableOptions(newTable)}`;
|
|
||||||
await this.run(sql);
|
|
||||||
}
|
|
||||||
buildTableOptions(table) {
|
|
||||||
const options = ["ENGINE=InnoDB"];
|
|
||||||
if (table.collation) {
|
|
||||||
options.push("DEFAULT CHARSET=utf8mb4", `COLLATE ${table.collation}`);
|
|
||||||
}
|
|
||||||
return ` ${options.join(" ")}`;
|
|
||||||
}
|
|
||||||
async updateTable(table) {
|
|
||||||
console.log(`Updating table: ${table.tableName}`);
|
|
||||||
await this.recreateTable(table);
|
|
||||||
}
|
|
||||||
async getTableColumns(tableName) {
|
|
||||||
const tableSchemaWhere = this.tableSchemaWhere(tableName);
|
|
||||||
const rows = await this.query(`SELECT COLUMN_NAME AS name, COLUMN_TYPE AS type FROM information_schema.COLUMNS WHERE ${tableSchemaWhere.where} AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`, tableSchemaWhere.values);
|
|
||||||
return rows.map((row) => ({
|
|
||||||
name: row.COLUMN_NAME,
|
|
||||||
type: row.COLUMN_TYPE,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
async addColumn(tableName, field) {
|
|
||||||
console.log(`Adding column: ${tableName}.${field.fieldName}`);
|
|
||||||
const columnDef = this.buildColumnDefinition(field)
|
|
||||||
.replace(/PRIMARY KEY/gi, "")
|
|
||||||
.replace(/AUTO_INCREMENT/gi, "")
|
|
||||||
.replace(/UNIQUE/gi, "")
|
|
||||||
.trim();
|
|
||||||
await this.run(`ALTER TABLE ${this.quoteIdentifier(tableName)} ADD COLUMN ${columnDef}`);
|
|
||||||
}
|
|
||||||
async checkIfTableExists(table) {
|
|
||||||
const tableSchemaWhere = this.tableSchemaWhere(table);
|
|
||||||
const row = await this.query(`SELECT 1 AS exists FROM information_schema.TABLES WHERE ${tableSchemaWhere.where} AND TABLE_NAME = ? LIMIT 1`, tableSchemaWhere.values);
|
|
||||||
return Boolean(row[0]?.exists);
|
|
||||||
}
|
|
||||||
async recreateTable(table) {
|
|
||||||
if (table.isVector && !this.recreate_vector_table) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const doesTableExist = await this.checkIfTableExists(table.tableName);
|
|
||||||
if (table.isVector) {
|
|
||||||
let existingRows = [];
|
|
||||||
if (doesTableExist) {
|
|
||||||
existingRows = await this.query(`SELECT * FROM ${this.quoteIdentifier(table.tableName)}`);
|
|
||||||
await this.run(`DROP TABLE ${this.quoteIdentifier(table.tableName)}`);
|
|
||||||
}
|
|
||||||
await this.createTable(table);
|
|
||||||
if (existingRows.length > 0) {
|
|
||||||
await this.insertRows(table.tableName, existingRows);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tempTableName = `${table.tableName}_temp_${Date.now()}`;
|
|
||||||
const existingColumns = await this.getTableColumns(table.tableName);
|
|
||||||
const columnsToKeep = table.fields
|
|
||||||
.filter((field) => existingColumns.some((column) => column.name === field.fieldName))
|
|
||||||
.map((field) => field.fieldName)
|
|
||||||
.filter((fieldName) => Boolean(fieldName));
|
|
||||||
await this.createTable({ ...table, tableName: tempTableName });
|
|
||||||
if (columnsToKeep.length > 0) {
|
|
||||||
const columnList = columnsToKeep
|
|
||||||
.map((column) => this.quoteIdentifier(column))
|
|
||||||
.join(", ");
|
|
||||||
await this.run(`INSERT INTO ${this.quoteIdentifier(tempTableName)} (${columnList}) SELECT ${columnList} FROM ${this.quoteIdentifier(table.tableName)}`);
|
|
||||||
}
|
|
||||||
await this.run(`DROP TABLE ${this.quoteIdentifier(table.tableName)}`);
|
|
||||||
await this.run(`RENAME TABLE ${this.quoteIdentifier(tempTableName)} TO ${this.quoteIdentifier(table.tableName)}`);
|
|
||||||
}
|
|
||||||
async insertRows(tableName, rows) {
|
|
||||||
for (const row of rows) {
|
|
||||||
const columns = Object.keys(row);
|
|
||||||
if (columns.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const values = columns.map((column) => row[column]);
|
|
||||||
const columnList = columns
|
|
||||||
.map((column) => this.quoteIdentifier(column))
|
|
||||||
.join(", ");
|
|
||||||
const placeholders = columns.map(() => "?").join(", ");
|
|
||||||
await this.run(`INSERT INTO ${this.quoteIdentifier(tableName)} (${columnList}) VALUES (${placeholders})`, values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildColumnDefinition(field) {
|
|
||||||
if (!field.fieldName) {
|
|
||||||
throw new Error("Field name is required");
|
|
||||||
}
|
|
||||||
const parts = [this.quoteIdentifier(field.fieldName)];
|
|
||||||
parts.push(this.mapDataType(field));
|
|
||||||
if (field.primaryKey) {
|
|
||||||
parts.push("PRIMARY KEY");
|
|
||||||
if (field.autoIncrement) {
|
|
||||||
parts.push("AUTO_INCREMENT");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (field.notNullValue || field.primaryKey) {
|
|
||||||
if (!field.primaryKey) {
|
|
||||||
parts.push("NOT NULL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (field.unique && !field.primaryKey) {
|
|
||||||
parts.push("UNIQUE");
|
|
||||||
}
|
|
||||||
if (field.defaultValue !== undefined) {
|
|
||||||
if (typeof field.defaultValue === "string") {
|
|
||||||
parts.push(`DEFAULT '${field.defaultValue.replace(/'/g, "''")}'`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
parts.push(`DEFAULT ${field.defaultValue}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (field.defaultValueLiteral) {
|
|
||||||
parts.push(`DEFAULT ${field.defaultValueLiteral}`);
|
|
||||||
}
|
|
||||||
if (field.onUpdate) {
|
|
||||||
parts.push(`ON UPDATE ${field.onUpdate}`);
|
|
||||||
}
|
|
||||||
else if (field.onUpdateLiteral) {
|
|
||||||
parts.push(`ON UPDATE ${field.onUpdateLiteral}`);
|
|
||||||
}
|
|
||||||
return parts.join(" ");
|
|
||||||
}
|
|
||||||
mapDataType(field) {
|
|
||||||
const dataType = field.dataType?.toLowerCase() || "text";
|
|
||||||
const vectorSize = field.vectorSize || 1536;
|
|
||||||
if (field.isVector) {
|
|
||||||
return `LONGTEXT COMMENT 'vector_size=${vectorSize}'`;
|
|
||||||
}
|
|
||||||
if (dataType.includes("int") ||
|
|
||||||
dataType === "bigint" ||
|
|
||||||
dataType === "smallint" ||
|
|
||||||
dataType === "tinyint") {
|
|
||||||
if (field.integerLength) {
|
|
||||||
return `INT(${field.integerLength})`;
|
|
||||||
}
|
|
||||||
return "INT";
|
|
||||||
}
|
|
||||||
if (dataType === "bigint") {
|
|
||||||
if (field.integerLength) {
|
|
||||||
return `BIGINT(${field.integerLength})`;
|
|
||||||
}
|
|
||||||
return "BIGINT";
|
|
||||||
}
|
|
||||||
if (dataType === "smallint") {
|
|
||||||
if (field.integerLength) {
|
|
||||||
return `SMALLINT(${field.integerLength})`;
|
|
||||||
}
|
|
||||||
return "SMALLINT";
|
|
||||||
}
|
|
||||||
if (dataType === "tinyint") {
|
|
||||||
if (field.integerLength) {
|
|
||||||
return `TINYINT(${field.integerLength})`;
|
|
||||||
}
|
|
||||||
return "TINYINT";
|
|
||||||
}
|
|
||||||
if (dataType.includes("double")) {
|
|
||||||
return "DOUBLE";
|
|
||||||
}
|
|
||||||
if (dataType.includes("float")) {
|
|
||||||
return "FLOAT";
|
|
||||||
}
|
|
||||||
if (dataType.includes("decimal") || dataType.includes("numeric")) {
|
|
||||||
if (field.integerLength && field.decimals) {
|
|
||||||
return `DECIMAL(${field.integerLength}, ${field.decimals})`;
|
|
||||||
}
|
|
||||||
return "DECIMAL";
|
|
||||||
}
|
|
||||||
if (dataType.includes("blob") || dataType.includes("binary")) {
|
|
||||||
return "BLOB";
|
|
||||||
}
|
|
||||||
if (dataType === "boolean" || dataType === "bool") {
|
|
||||||
return "TINYINT(1)";
|
|
||||||
}
|
|
||||||
if (dataType.includes("timestamp")) {
|
|
||||||
return "TIMESTAMP";
|
|
||||||
}
|
|
||||||
if (dataType.includes("datetime")) {
|
|
||||||
return "DATETIME";
|
|
||||||
}
|
|
||||||
if (dataType.includes("date") || dataType.includes("time")) {
|
|
||||||
return "DATETIME";
|
|
||||||
}
|
|
||||||
if (dataType.includes("varchar")) {
|
|
||||||
if (field.integerLength) {
|
|
||||||
return `VARCHAR(${field.integerLength})`;
|
|
||||||
}
|
|
||||||
return "VARCHAR(255)";
|
|
||||||
}
|
|
||||||
return "TEXT";
|
|
||||||
}
|
|
||||||
buildForeignKeyConstraint(field) {
|
|
||||||
const fk = field.foreignKey;
|
|
||||||
const constraintName = fk.foreignKeyName
|
|
||||||
? `CONSTRAINT ${this.quoteIdentifier(fk.foreignKeyName)} `
|
|
||||||
: "";
|
|
||||||
let constraint = `${constraintName}FOREIGN KEY (${this.quoteIdentifier(field.fieldName)}) REFERENCES ${this.quoteIdentifier(fk.destinationTableName)}(${this.quoteIdentifier(fk.destinationTableColumnName)})`;
|
|
||||||
if (fk.cascadeDelete) {
|
|
||||||
constraint += " ON DELETE CASCADE";
|
|
||||||
}
|
|
||||||
if (fk.cascadeUpdate) {
|
|
||||||
constraint += " ON UPDATE CASCADE";
|
|
||||||
}
|
|
||||||
return constraint;
|
|
||||||
}
|
|
||||||
async syncIndexes(table) {
|
|
||||||
if (!table.indexes || table.indexes.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tableSchemaWhere = this.tableSchemaWhere(table.tableName);
|
|
||||||
const rows = await this.query(`SELECT INDEX_NAME AS name FROM information_schema.STATISTICS WHERE ${tableSchemaWhere.where} AND TABLE_NAME = ? AND INDEX_NAME <> 'PRIMARY' GROUP BY INDEX_NAME ORDER BY INDEX_NAME`, tableSchemaWhere.values);
|
|
||||||
const existingIndexes = rows.map((row) => row.name);
|
|
||||||
for (const indexName of existingIndexes) {
|
|
||||||
const stillExists = table.indexes.some((index) => index.indexName === indexName);
|
|
||||||
if (!stillExists) {
|
|
||||||
console.log(`Dropping index: ${indexName}`);
|
|
||||||
await this.run(`DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteIdentifier(table.tableName)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const index of table.indexes) {
|
|
||||||
if (!index.indexName ||
|
|
||||||
!index.indexTableFields ||
|
|
||||||
index.indexTableFields.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!existingIndexes.includes(index.indexName)) {
|
|
||||||
console.log(`Creating index: ${index.indexName}`);
|
|
||||||
const fields = index.indexTableFields
|
|
||||||
.map((field) => this.quoteIdentifier(field))
|
|
||||||
.join(", ");
|
|
||||||
await this.run(`CREATE INDEX ${this.quoteIdentifier(index.indexName)} ON ${this.quoteIdentifier(table.tableName)} (${fields})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close() { }
|
|
||||||
}
|
|
||||||
export { MariaDBSchemaManager };
|
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import type { BUN_MARIADB_DatabaseSchemaType, BunSQLiteConfig } from "../../types";
|
import type { BUN_MARIADB_DatabaseSchemaType, BunMariaDBConfig } from "../../types";
|
||||||
type Params = {
|
type Params = {
|
||||||
dbSchema: BUN_MARIADB_DatabaseSchemaType;
|
dbSchema: BUN_MARIADB_DatabaseSchemaType;
|
||||||
config: BunSQLiteConfig;
|
config: BunMariaDBConfig;
|
||||||
};
|
};
|
||||||
export default function dbSchemaToType({ config, dbSchema, }: Params): string[] | undefined;
|
export default function dbSchemaToType({ config, dbSchema, }: Params): string[] | undefined;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ export default function dbSchemaToType({ config, dbSchema, }) {
|
|||||||
let datasquirelSchema = dbSchema;
|
let datasquirelSchema = dbSchema;
|
||||||
if (!datasquirelSchema)
|
if (!datasquirelSchema)
|
||||||
return;
|
return;
|
||||||
let tableNames = `export const BunSQLiteTables = [\n${datasquirelSchema.tables
|
let tableNames = `export const BunMariaDBTables = [\n${datasquirelSchema.tables
|
||||||
.map((tbl) => ` "${tbl.tableName}",`)
|
.map((tbl) => ` "${tbl.tableName}",`)
|
||||||
.join("\n")}\n] as const`;
|
.join("\n")}\n] as const`;
|
||||||
const dbTablesSchemas = datasquirelSchema.tables;
|
const dbTablesSchemas = datasquirelSchema.tables;
|
||||||
|
|||||||
Vendored
+3
-2
@@ -1,4 +1,4 @@
|
|||||||
import type { APIResponseObject, ServerQueryParam } from "../../types";
|
import type { BunMariaDBConfig, DBResponseObject, ServerQueryParam } from "../../types";
|
||||||
type Params<Schema extends {
|
type Params<Schema extends {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
} = {
|
} = {
|
||||||
@@ -8,10 +8,11 @@ type Params<Schema extends {
|
|||||||
table: Table;
|
table: Table;
|
||||||
count?: boolean;
|
count?: boolean;
|
||||||
targetId?: number | string;
|
targetId?: number | string;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
};
|
};
|
||||||
export default function DbSelect<Schema extends {
|
export default function DbSelect<Schema extends {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
} = {
|
} = {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
}, Table extends string = string>({ table, query, count, targetId, }: Params<Schema, Table>): Promise<APIResponseObject<Schema>>;
|
}, Table extends string = string>({ table, query, count, targetId, config, }: Params<Schema, Table>): Promise<DBResponseObject<Schema>>;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Vendored
+58
-21
@@ -1,8 +1,10 @@
|
|||||||
import mysql from "mysql";
|
import dbHandler from "../db-handler";
|
||||||
import DbClient from ".";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import sqlGenerator from "../../utils/sql-generator";
|
import sqlGenerator from "../../utils/sql-generator";
|
||||||
export default async function DbSelect({ table, query, count, targetId, }) {
|
function quoteIdentifier(identifier) {
|
||||||
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||||
|
}
|
||||||
|
export default async function DbSelect({ table, query, count, targetId, config, }) {
|
||||||
let sqlObj = null;
|
let sqlObj = null;
|
||||||
try {
|
try {
|
||||||
let finalQuery = query || {};
|
let finalQuery = query || {};
|
||||||
@@ -10,39 +12,74 @@ export default async function DbSelect({ table, query, count, targetId, }) {
|
|||||||
finalQuery = _.merge(finalQuery, {
|
finalQuery = _.merge(finalQuery, {
|
||||||
query: {
|
query: {
|
||||||
id: {
|
id: {
|
||||||
value: String(targetId),
|
value: Number(targetId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
sqlObj = sqlGenerator({
|
sqlObj = sqlGenerator({
|
||||||
tableName: table,
|
tableName: quoteIdentifier(table),
|
||||||
genObject: finalQuery,
|
genObject: finalQuery,
|
||||||
});
|
});
|
||||||
let sql = mysql.format(sqlObj.string, sqlObj.values);
|
const res = await dbHandler({
|
||||||
const res = DbClient.query(sql);
|
query: sqlObj.string,
|
||||||
const batchRes = res.all();
|
values: sqlObj.values,
|
||||||
let resp = {
|
config,
|
||||||
success: Boolean(batchRes[0]),
|
});
|
||||||
payload: batchRes,
|
if (!res.success) {
|
||||||
singleRes: batchRes[0],
|
return {
|
||||||
|
msg: "Database select failed",
|
||||||
|
...res,
|
||||||
|
success: false,
|
||||||
debug: {
|
debug: {
|
||||||
sqlObj,
|
sqlObj,
|
||||||
sql,
|
sql: sqlObj.string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const batchRes = (res.payload || []);
|
||||||
|
let resp = {
|
||||||
|
success: true,
|
||||||
|
payload: batchRes,
|
||||||
|
single_res: batchRes[0],
|
||||||
|
debug: {
|
||||||
|
sqlObj,
|
||||||
|
sql: sqlObj.string,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (count) {
|
if (count) {
|
||||||
let count_sql_object = sqlGenerator({
|
const countSqlObject = sqlGenerator({
|
||||||
tableName: table,
|
tableName: quoteIdentifier(table),
|
||||||
genObject: finalQuery,
|
genObject: finalQuery,
|
||||||
count,
|
count,
|
||||||
});
|
});
|
||||||
let count_sql = mysql.format(count_sql_object.string, count_sql_object.values);
|
const countSql = `SELECT COUNT(*) AS count FROM (${countSqlObject.string}) AS c`;
|
||||||
count_sql = `SELECT COUNT(*) FROM (${count_sql}) as c`;
|
const countRes = await dbHandler({
|
||||||
const count_res = DbClient.query(count_sql).all();
|
query: countSql,
|
||||||
const count_val = count_res[0]?.["COUNT(*)"];
|
values: countSqlObject.values,
|
||||||
resp["count"] = Number(count_val);
|
config,
|
||||||
resp["debug"]["count_sql"] = count_sql;
|
});
|
||||||
|
if (!countRes.success) {
|
||||||
|
return {
|
||||||
|
...resp,
|
||||||
|
success: false,
|
||||||
|
msg: "Database count failed",
|
||||||
|
debug: {
|
||||||
|
...resp.debug,
|
||||||
|
count_sql: countSql,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const countRows = countRes.payload || [];
|
||||||
|
const countVal = countRows[0]?.count ?? countRows[0]?.["count"];
|
||||||
|
resp = {
|
||||||
|
...resp,
|
||||||
|
count: Number(countVal),
|
||||||
|
debug: {
|
||||||
|
...resp.debug,
|
||||||
|
count_sql: countSql,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
import type { APIResponseObject, SQLInsertGenValueType } from "../../types";
|
import type { DBResponseObject, SQLInsertGenValueType } from "../../types";
|
||||||
type Params = {
|
type Params = {
|
||||||
sql: string;
|
sql: string;
|
||||||
values?: SQLInsertGenValueType[];
|
values?: SQLInsertGenValueType[];
|
||||||
@@ -7,5 +7,5 @@ export default function DbSQL<T extends {
|
|||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
} = {
|
} = {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
}>({ sql, values }: Params): Promise<APIResponseObject<T>>;
|
}>({ sql, values }: Params): Promise<DBResponseObject<T>>;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Vendored
+31
-16
@@ -1,24 +1,39 @@
|
|||||||
import DbClient from ".";
|
import dbHandler from "../db-handler";
|
||||||
import _ from "lodash";
|
|
||||||
export default async function DbSQL({ sql, values }) {
|
export default async function DbSQL({ sql, values }) {
|
||||||
try {
|
try {
|
||||||
const trimmed_sql = sql.trim();
|
const trimmedSql = sql.trim();
|
||||||
const res = trimmed_sql.match(/^select/i)
|
const isSelect = trimmedSql.match(/^select/i);
|
||||||
? DbClient.query(trimmed_sql).all(...(values || []))
|
const res = await dbHandler({
|
||||||
: DbClient.run(trimmed_sql, values || []);
|
query: trimmedSql,
|
||||||
|
values: values,
|
||||||
|
});
|
||||||
|
if (!res.success) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
msg: "Database query failed",
|
||||||
payload: Array.isArray(res) ? res : undefined,
|
...res,
|
||||||
singleRes: Array.isArray(res) ? res?.[0] : undefined,
|
success: false,
|
||||||
postInsertReturn: Array.isArray(res)
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
affectedRows: res.changes,
|
|
||||||
insertId: Number(res.lastInsertRowid),
|
|
||||||
},
|
|
||||||
debug: {
|
debug: {
|
||||||
sqlObj: {
|
sqlObj: {
|
||||||
sql: trimmed_sql,
|
sql: trimmedSql,
|
||||||
|
values,
|
||||||
|
},
|
||||||
|
sql,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const payload = isSelect ? (res.payload || []) : undefined;
|
||||||
|
const single_res = isSelect ? payload?.[0] : res.single_res;
|
||||||
|
const singleRaw = res.single_res;
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
success: isSelect
|
||||||
|
? Boolean(single_res) || Boolean(payload?.[0])
|
||||||
|
: true,
|
||||||
|
payload,
|
||||||
|
single_res,
|
||||||
|
debug: {
|
||||||
|
sqlObj: {
|
||||||
|
sql: trimmedSql,
|
||||||
values,
|
values,
|
||||||
},
|
},
|
||||||
sql,
|
sql,
|
||||||
|
|||||||
Vendored
+3
-2
@@ -1,4 +1,4 @@
|
|||||||
import type { APIResponseObject, ServerQueryParam } from "../../types";
|
import type { BunMariaDBConfig, DBResponseObject, ServerQueryParam } from "../../types";
|
||||||
type Params<Schema extends {
|
type Params<Schema extends {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
} = {
|
} = {
|
||||||
@@ -8,10 +8,11 @@ type Params<Schema extends {
|
|||||||
data: Schema;
|
data: Schema;
|
||||||
query?: ServerQueryParam<Schema>;
|
query?: ServerQueryParam<Schema>;
|
||||||
targetId?: number | string;
|
targetId?: number | string;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
};
|
};
|
||||||
export default function DbUpdate<Schema extends {
|
export default function DbUpdate<Schema extends {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
} = {
|
} = {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
}, Table extends string = string>({ table, data, query, targetId, }: Params<Schema, Table>): Promise<APIResponseObject>;
|
}, Table extends string = string>({ table, data, query, targetId, config, }: Params<Schema, Table>): Promise<DBResponseObject>;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Vendored
+44
-24
@@ -1,7 +1,11 @@
|
|||||||
import DbClient from ".";
|
import dbHandler from "../db-handler";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import sqlGenerator from "../../utils/sql-generator";
|
import sqlGenerator from "../../utils/sql-generator";
|
||||||
export default async function DbUpdate({ table, data, query, targetId, }) {
|
import sanitizeHtmlFields from "../../utils/sanitize-html-fields";
|
||||||
|
function quoteIdentifier(identifier) {
|
||||||
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||||
|
}
|
||||||
|
export default async function DbUpdate({ table, data, query, targetId, config, }) {
|
||||||
let sqlObj = { string: "", values: [] };
|
let sqlObj = { string: "", values: [] };
|
||||||
try {
|
try {
|
||||||
let finalQuery = query || {};
|
let finalQuery = query || {};
|
||||||
@@ -9,22 +13,33 @@ export default async function DbUpdate({ table, data, query, targetId, }) {
|
|||||||
finalQuery = _.merge(finalQuery, {
|
finalQuery = _.merge(finalQuery, {
|
||||||
query: {
|
query: {
|
||||||
id: {
|
id: {
|
||||||
value: String(targetId),
|
value: Number(targetId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const sqlQueryObj = sqlGenerator({
|
const sqlQueryObj = sqlGenerator({
|
||||||
tableName: table,
|
tableName: quoteIdentifier(table),
|
||||||
genObject: finalQuery,
|
genObject: finalQuery,
|
||||||
});
|
});
|
||||||
let values = [];
|
|
||||||
const whereClause = sqlQueryObj.string.match(/WHERE .*/)?.[0];
|
const whereClause = sqlQueryObj.string.match(/WHERE .*/)?.[0];
|
||||||
if (whereClause) {
|
if (!whereClause) {
|
||||||
let sql = `UPDATE ${table} SET`;
|
return {
|
||||||
|
success: false,
|
||||||
|
msg: `No WHERE clause`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let values = [];
|
||||||
|
let sql = ``;
|
||||||
|
sql += `UPDATE ${quoteIdentifier(table)} SET`;
|
||||||
|
const sanitizedData = sanitizeHtmlFields({
|
||||||
|
table,
|
||||||
|
data,
|
||||||
|
config,
|
||||||
|
});
|
||||||
const finalData = {
|
const finalData = {
|
||||||
updated_at: Date.now(),
|
updated_at: Date.now(),
|
||||||
...data,
|
...sanitizedData,
|
||||||
};
|
};
|
||||||
const keys = Object.keys(finalData);
|
const keys = Object.keys(finalData);
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
@@ -32,36 +47,41 @@ export default async function DbUpdate({ table, data, query, targetId, }) {
|
|||||||
if (!key)
|
if (!key)
|
||||||
continue;
|
continue;
|
||||||
const isLast = i == keys.length - 1;
|
const isLast = i == keys.length - 1;
|
||||||
sql += ` ${key}=?`;
|
sql += ` ${quoteIdentifier(key)}=?`;
|
||||||
const value = finalData[key];
|
values.push(finalData[key] ?? null);
|
||||||
values.push(value || null);
|
|
||||||
if (!isLast) {
|
if (!isLast) {
|
||||||
sql += `,`;
|
sql += `,`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sql += ` ${whereClause}`;
|
sql += ` ${whereClause}`;
|
||||||
values = [...values, ...sqlQueryObj.values];
|
values = [...values, ...sqlQueryObj.values];
|
||||||
|
const res = await dbHandler({
|
||||||
|
query: sql,
|
||||||
|
values: values,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
if (res.error) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
sqlObj.string = sql;
|
sqlObj.string = sql;
|
||||||
sqlObj.values = values;
|
sqlObj.values = values;
|
||||||
const res = DbClient.run(sql, values);
|
let updated_sql = ``;
|
||||||
|
let updated_sql_values = [];
|
||||||
|
updated_sql += `SELECT * FROM ${quoteIdentifier(table)} ${whereClause}`;
|
||||||
|
updated_sql_values = [...sqlQueryObj.values];
|
||||||
|
const updated_res = await dbHandler({
|
||||||
|
query: updated_sql,
|
||||||
|
values: updated_sql_values,
|
||||||
|
config,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: Boolean(res.changes),
|
...updated_res,
|
||||||
postInsertReturn: {
|
|
||||||
affectedRows: res.changes,
|
|
||||||
insertId: Number(res.lastInsertRowid),
|
|
||||||
},
|
|
||||||
debug: {
|
debug: {
|
||||||
sqlObj,
|
sqlObj,
|
||||||
},
|
},
|
||||||
|
db_res: res.db_res,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
msg: `No WHERE clause`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
catch (error) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import type { BUN_MARIADB_DatabaseSchemaType, BunSQLiteConfig } from "../../types";
|
import type { BUN_MARIADB_DatabaseSchemaType, BunMariaDBConfig } from "../../types";
|
||||||
type Params = {
|
type Params = {
|
||||||
dbSchema: BUN_MARIADB_DatabaseSchemaType;
|
dbSchema: BUN_MARIADB_DatabaseSchemaType;
|
||||||
dst_file: string;
|
dst_file: string;
|
||||||
config: BunSQLiteConfig;
|
config: BunMariaDBConfig;
|
||||||
};
|
};
|
||||||
export default function dbSchemaToTypeDef({ dbSchema, dst_file, config, }: Params): void;
|
export default function dbSchemaToTypeDef({ dbSchema, dst_file, config, }: Params): void;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Vendored
-2
@@ -1,2 +0,0 @@
|
|||||||
import type { BUN_MARIADB_DatabaseSchemaType } from "../../types";
|
|
||||||
export declare const DbSchema: BUN_MARIADB_DatabaseSchemaType;
|
|
||||||
Vendored
-5
@@ -1,5 +0,0 @@
|
|||||||
import _ from "lodash";
|
|
||||||
export const DbSchema = {
|
|
||||||
dbName: "travis-ai",
|
|
||||||
tables: [],
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
|
||||||
|
export type BuildColumnDefinitionOptions = {
|
||||||
|
/** UNIQUE is managed by syncUniqueConstraints on existing tables */
|
||||||
|
omitUnique?: boolean;
|
||||||
|
};
|
||||||
|
export default function buildColumnDefinition(field: BUN_MARIADB_FieldSchemaType, options?: BuildColumnDefinitionOptions): string;
|
||||||
|
/** Whether schema field requires NOT NULL */
|
||||||
|
export declare function fieldRequiresNotNull(field: BUN_MARIADB_FieldSchemaType): boolean;
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
import isVectorField from "./is-vector-field";
|
||||||
|
import mapDataType from "./map-data-types";
|
||||||
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
export default function buildColumnDefinition(field, options = {}) {
|
||||||
|
if (!field.fieldName) {
|
||||||
|
throw new Error("Field name is required");
|
||||||
|
}
|
||||||
|
const parts = [MariaDBQuoteGen(field.fieldName)];
|
||||||
|
parts.push(mapDataType(field));
|
||||||
|
if (field.autoIncrement) {
|
||||||
|
parts.push("AUTO_INCREMENT");
|
||||||
|
}
|
||||||
|
// Vector columns used in VECTOR INDEX must be NOT NULL
|
||||||
|
if (field.notNullValue ||
|
||||||
|
field.primaryKey ||
|
||||||
|
isVectorField(field)) {
|
||||||
|
if (!field.primaryKey) {
|
||||||
|
parts.push("NOT NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// VECTOR columns cannot be UNIQUE in the usual sense
|
||||||
|
if (!options.omitUnique &&
|
||||||
|
field.unique &&
|
||||||
|
!field.primaryKey &&
|
||||||
|
!isVectorField(field)) {
|
||||||
|
parts.push("UNIQUE");
|
||||||
|
}
|
||||||
|
if (field.defaultValue !== undefined) {
|
||||||
|
if (typeof field.defaultValue === "string") {
|
||||||
|
parts.push(`DEFAULT '${field.defaultValue.replace(/'/g, "''")}'`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parts.push(`DEFAULT ${field.defaultValue}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (field.defaultValueLiteral) {
|
||||||
|
parts.push(`DEFAULT ${field.defaultValueLiteral}`);
|
||||||
|
}
|
||||||
|
if (field.onUpdate) {
|
||||||
|
parts.push(`ON UPDATE ${field.onUpdate}`);
|
||||||
|
}
|
||||||
|
else if (field.onUpdateLiteral) {
|
||||||
|
parts.push(`ON UPDATE ${field.onUpdateLiteral}`);
|
||||||
|
}
|
||||||
|
return parts.join(" ");
|
||||||
|
}
|
||||||
|
/** Whether schema field requires NOT NULL */
|
||||||
|
export function fieldRequiresNotNull(field) {
|
||||||
|
return Boolean(field.notNullValue || field.primaryKey || isVectorField(field));
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
|
||||||
|
export declare function defaultForeignKeyName(tableName: string, fieldName: string): string;
|
||||||
|
export declare function resolveForeignKeyName(field: BUN_MARIADB_FieldSchemaType, tableName: string): string;
|
||||||
|
export default function buildForeignKeyConstraint(field: BUN_MARIADB_FieldSchemaType, tableName: string): string;
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
export function defaultForeignKeyName(tableName, fieldName) {
|
||||||
|
return `fk_${tableName}_${fieldName}`;
|
||||||
|
}
|
||||||
|
export function resolveForeignKeyName(field, tableName) {
|
||||||
|
const fieldName = field.fieldName || "column";
|
||||||
|
return field.foreignKey?.foreignKeyName || defaultForeignKeyName(tableName, fieldName);
|
||||||
|
}
|
||||||
|
export default function buildForeignKeyConstraint(field, tableName) {
|
||||||
|
const fk = field.foreignKey;
|
||||||
|
const constraintName = resolveForeignKeyName(field, tableName);
|
||||||
|
let constraint = `CONSTRAINT ${MariaDBQuoteGen(constraintName)} FOREIGN KEY (${MariaDBQuoteGen(field.fieldName)}) REFERENCES ${MariaDBQuoteGen(fk.destinationTableName)}(${MariaDBQuoteGen(fk.destinationTableColumnName)})`;
|
||||||
|
if (fk.cascadeDelete) {
|
||||||
|
constraint += " ON DELETE CASCADE";
|
||||||
|
}
|
||||||
|
if (fk.cascadeUpdate) {
|
||||||
|
constraint += " ON UPDATE CASCADE";
|
||||||
|
}
|
||||||
|
return constraint;
|
||||||
|
}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
import type { BUN_MARIADB_TableSchemaType } from "../../types";
|
||||||
|
export default function buildTableOptions(table: BUN_MARIADB_TableSchemaType): string;
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
export default function buildTableOptions(table) {
|
||||||
|
const options = ["ENGINE=InnoDB"];
|
||||||
|
if (table.collation) {
|
||||||
|
options.push("DEFAULT CHARSET=utf8mb4", `COLLATE ${table.collation}`);
|
||||||
|
}
|
||||||
|
return ` ${options.join(" ")}`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
import type { CreateDBSchemaParams } from "../../types";
|
||||||
|
export default function createDBManagerTable({ db_schema, config, }: CreateDBSchemaParams): Promise<void>;
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import { AppData } from "../../data/app-data";
|
||||||
|
import dbHandler from "../db-handler";
|
||||||
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
export default async function createDBManagerTable({ db_schema, config, }) {
|
||||||
|
let sql = ``;
|
||||||
|
sql += `CREATE TABLE IF NOT EXISTS ${MariaDBQuoteGen(AppData["DbSchemaManagerTableName"])} (\n`;
|
||||||
|
sql += ` table_name VARCHAR(255) NOT NULL PRIMARY KEY,\n`;
|
||||||
|
sql += ` created_at BIGINT NOT NULL,\n`;
|
||||||
|
sql += ` updated_at BIGINT NOT NULL\n`;
|
||||||
|
sql += `) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci\n`;
|
||||||
|
await dbHandler({
|
||||||
|
query: sql,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
import type { CreateDBSchemaParams } from "../../types";
|
||||||
|
export default function createDBSchema(params: CreateDBSchemaParams): Promise<void>;
|
||||||
Vendored
+17
@@ -0,0 +1,17 @@
|
|||||||
|
import createDBManagerTable from "./create-db-manager-table";
|
||||||
|
import handleDBSchemaTables from "./handle-db-schema-tables";
|
||||||
|
import orderDBSchema from "./order-db-schema";
|
||||||
|
export default async function createDBSchema(params) {
|
||||||
|
/**
|
||||||
|
* Create Schema Manager Table
|
||||||
|
*/
|
||||||
|
await createDBManagerTable(params);
|
||||||
|
/**
|
||||||
|
* Reorder Tables (parents before children with FKs)
|
||||||
|
*/
|
||||||
|
const ordered_db_schema = await orderDBSchema(params);
|
||||||
|
/**
|
||||||
|
* Handle Tables (create / update / drop)
|
||||||
|
*/
|
||||||
|
await handleDBSchemaTables({ ...params, db_schema: ordered_db_schema });
|
||||||
|
}
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import type { BUN_MARIADB_TableSchemaType, BunMariaDBConfig } from "../../types";
|
||||||
|
export default function createTable({ table, config, }: {
|
||||||
|
table: BUN_MARIADB_TableSchemaType;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<void>;
|
||||||
Vendored
+41
@@ -0,0 +1,41 @@
|
|||||||
|
import buildColumnDefinition from "./build-column-definition";
|
||||||
|
import buildForeignKeyConstraint from "./build-foreign-key-constraint";
|
||||||
|
import buildTableOptions from "./build-table-options";
|
||||||
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
import runSchemaQuery from "./run-schema-query";
|
||||||
|
export default async function createTable({ table, config, }) {
|
||||||
|
if (!table.tableName.match(/_temp_\d+$/)) {
|
||||||
|
console.log(`Creating table: ${table.tableName}`);
|
||||||
|
}
|
||||||
|
const columnDefinitions = [];
|
||||||
|
const foreignKeys = [];
|
||||||
|
const primaryKeys = [];
|
||||||
|
for (const field of table.fields || []) {
|
||||||
|
columnDefinitions.push(buildColumnDefinition(field));
|
||||||
|
if (field.primaryKey && field.fieldName) {
|
||||||
|
primaryKeys.push(field.fieldName);
|
||||||
|
}
|
||||||
|
if (field.foreignKey) {
|
||||||
|
foreignKeys.push(buildForeignKeyConstraint(field, table.tableName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (primaryKeys.length > 0) {
|
||||||
|
const pkCols = primaryKeys.map((k) => MariaDBQuoteGen(k)).join(", ");
|
||||||
|
columnDefinitions.push(`PRIMARY KEY (${pkCols})`);
|
||||||
|
}
|
||||||
|
if (table.uniqueConstraints) {
|
||||||
|
for (const constraint of table.uniqueConstraints) {
|
||||||
|
const columns = (constraint.constraintTableFields || [])
|
||||||
|
.map((field) => field.value)
|
||||||
|
.filter((value) => Boolean(value));
|
||||||
|
if (columns.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const fields = columns.map((col) => MariaDBQuoteGen(col)).join(", ");
|
||||||
|
const constraintName = constraint.constraintName || `unique_${columns.join("_")}`;
|
||||||
|
columnDefinitions.push(`CONSTRAINT ${MariaDBQuoteGen(constraintName)} UNIQUE (${fields})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sql = `CREATE TABLE IF NOT EXISTS ${MariaDBQuoteGen(table.tableName)} (${[...columnDefinitions, ...foreignKeys].join(", ")})${buildTableOptions(table)}`;
|
||||||
|
await runSchemaQuery({ query: sql, config });
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
import type { CreateDBSchemaParams } from "../../types";
|
||||||
|
export default function getExistingTablesFromTablesManagerTable({ config, }: CreateDBSchemaParams): Promise<(string | undefined)[]>;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { AppData } from "../../data/app-data";
|
||||||
|
import dbHandler from "../db-handler";
|
||||||
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
export default async function getExistingTablesFromTablesManagerTable({ config, }) {
|
||||||
|
const rows = await dbHandler({
|
||||||
|
query: `SELECT table_name FROM ${MariaDBQuoteGen(AppData["DbSchemaManagerTableName"])}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
return rows.payload?.map((row) => row.table_name) || [];
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
import type { BunMariaDBConfig } from "../../types";
|
||||||
|
export type ColumnInfoRow = {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
comment?: string;
|
||||||
|
isNullable: boolean;
|
||||||
|
columnDefault: string | null;
|
||||||
|
extra: string;
|
||||||
|
};
|
||||||
|
export default function getTableColumnsGemini({ tableName, config, }: {
|
||||||
|
tableName: string;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<ColumnInfoRow[]>;
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
import { querySchemaRows } from "./run-schema-query";
|
||||||
|
import schemaCondition from "./schema-condition";
|
||||||
|
export default async function getTableColumnsGemini({ tableName, config, }) {
|
||||||
|
const schemaCond = schemaCondition(config);
|
||||||
|
const rows = await querySchemaRows({
|
||||||
|
query: `
|
||||||
|
SELECT
|
||||||
|
c.COLUMN_NAME,
|
||||||
|
c.COLUMN_TYPE,
|
||||||
|
c.COLUMN_COMMENT,
|
||||||
|
c.IS_NULLABLE,
|
||||||
|
c.COLUMN_DEFAULT,
|
||||||
|
c.EXTRA,
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.CHECK_CONSTRAINTS cc
|
||||||
|
WHERE cc.CONSTRAINT_SCHEMA = c.TABLE_SCHEMA
|
||||||
|
AND cc.TABLE_NAME = c.TABLE_NAME
|
||||||
|
AND cc.CHECK_CLAUSE LIKE CONCAT('%json_valid(\`', c.COLUMN_NAME, '\`)%')
|
||||||
|
) AS IS_JSON
|
||||||
|
FROM information_schema.COLUMNS c
|
||||||
|
WHERE ${schemaCond.where.replace(/\bTABLE_SCHEMA\b/g, "c.TABLE_SCHEMA")}
|
||||||
|
AND c.TABLE_NAME = ?
|
||||||
|
ORDER BY c.ORDINAL_POSITION
|
||||||
|
`,
|
||||||
|
values: [...schemaCond.values, tableName],
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
return rows.map((row) => {
|
||||||
|
const isJson = Number(row.IS_JSON) === 1;
|
||||||
|
// Normalize longtext with a json_valid constraint to "json"
|
||||||
|
let resolvedType = row.COLUMN_TYPE;
|
||||||
|
if (isJson && row.COLUMN_TYPE.toLowerCase() === "longtext") {
|
||||||
|
resolvedType = "json";
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: row.COLUMN_NAME,
|
||||||
|
type: resolvedType,
|
||||||
|
comment: row.COLUMN_COMMENT,
|
||||||
|
isNullable: String(row.IS_NULLABLE).toUpperCase() === "YES",
|
||||||
|
columnDefault: row.COLUMN_DEFAULT,
|
||||||
|
extra: row.EXTRA || "",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
import type { BunMariaDBConfig } from "../../types";
|
||||||
|
export type ColumnInfoRow = {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
comment?: string;
|
||||||
|
isNullable: boolean;
|
||||||
|
columnDefault: string | null;
|
||||||
|
extra: string;
|
||||||
|
};
|
||||||
|
export default function getTableColumns({ tableName, config, }: {
|
||||||
|
tableName: string;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<ColumnInfoRow[]>;
|
||||||
Vendored
+18
@@ -0,0 +1,18 @@
|
|||||||
|
import { querySchemaRows } from "./run-schema-query";
|
||||||
|
import schemaCondition from "./schema-condition";
|
||||||
|
export default async function getTableColumns({ tableName, config, }) {
|
||||||
|
const schemaCond = schemaCondition(config);
|
||||||
|
const rows = await querySchemaRows({
|
||||||
|
query: `SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT, IS_NULLABLE, COLUMN_DEFAULT, EXTRA FROM information_schema.COLUMNS WHERE ${schemaCond.where} AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
|
||||||
|
values: [...schemaCond.values, tableName],
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
return rows.map((row) => ({
|
||||||
|
name: row.COLUMN_NAME,
|
||||||
|
type: row.COLUMN_TYPE,
|
||||||
|
comment: row.COLUMN_COMMENT,
|
||||||
|
isNullable: String(row.IS_NULLABLE).toUpperCase() === "YES",
|
||||||
|
columnDefault: row.COLUMN_DEFAULT,
|
||||||
|
extra: row.EXTRA || "",
|
||||||
|
}));
|
||||||
|
}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
import type { CreateDBSchemaTableHandlerParams } from "../../types";
|
||||||
|
export default function handleDBSchemaTable({ db_schema, config, table, db_manager_table_name, existing_live_table, }: CreateDBSchemaTableHandlerParams): Promise<void>;
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
import createTable from "./create-table";
|
||||||
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
import resolveTable from "./resolve-table";
|
||||||
|
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||||
|
import schemaCondition from "./schema-condition";
|
||||||
|
import { dropObsoleteForeignKeys, ensureForeignKeys, } from "./sync-foreign-keys";
|
||||||
|
import syncIndexes from "./sync-indexes";
|
||||||
|
import syncPrimaryKey from "./sync-primary-key";
|
||||||
|
import syncTableOptions from "./sync-table-options";
|
||||||
|
import syncUniqueConstraints from "./sync-unique-constraints";
|
||||||
|
import updateTable from "./update-table";
|
||||||
|
import upsertDbManagerTable, { removeDbManagerTable, } from "./upsert-db-manager-table";
|
||||||
|
export default async function handleDBSchemaTable({ db_schema, config, table, db_manager_table_name, existing_live_table, }) {
|
||||||
|
const resolvedTable = resolveTable(table, db_schema);
|
||||||
|
let tableExistsTracked = Boolean(db_manager_table_name);
|
||||||
|
let tableExistsLive = Boolean(existing_live_table?.TABLE_NAME);
|
||||||
|
if (resolvedTable.tableNameOld &&
|
||||||
|
resolvedTable.tableNameOld !== resolvedTable.tableName) {
|
||||||
|
// Only hit information_schema when a rename is declared
|
||||||
|
const schemaCond = schemaCondition(config);
|
||||||
|
const liveTables = await querySchemaRows({
|
||||||
|
query: `SELECT TABLE_NAME FROM information_schema.TABLES WHERE ${schemaCond.where} AND TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = ?`,
|
||||||
|
values: [...schemaCond.values, resolvedTable.tableNameOld],
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
if (liveTables.length > 0) {
|
||||||
|
console.log(`Renaming table: ${resolvedTable.tableNameOld} -> ${resolvedTable.tableName}`);
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `RENAME TABLE ${MariaDBQuoteGen(resolvedTable.tableNameOld)} TO ${MariaDBQuoteGen(resolvedTable.tableName)}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
await upsertDbManagerTable({
|
||||||
|
tableName: resolvedTable.tableName,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
await removeDbManagerTable({
|
||||||
|
tableName: resolvedTable.tableNameOld,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
tableExistsTracked = true;
|
||||||
|
tableExistsLive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!tableExistsTracked && !tableExistsLive) {
|
||||||
|
await createTable({ table: resolvedTable, config });
|
||||||
|
await upsertDbManagerTable({
|
||||||
|
tableName: resolvedTable.tableName,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Columns first (also drops FKs on removed/modified columns)
|
||||||
|
await updateTable({
|
||||||
|
table: resolvedTable,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
await upsertDbManagerTable({
|
||||||
|
tableName: resolvedTable.tableName,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Order matters:
|
||||||
|
// 1. Table options (engine/collation)
|
||||||
|
// 2. Drop obsolete/changed FKs (unlocks index cleanup)
|
||||||
|
// 3. Primary key
|
||||||
|
// 4. Indexes / uniques (FK columns need supporting indexes)
|
||||||
|
// 5. Ensure foreign keys exist
|
||||||
|
await syncTableOptions({ table: resolvedTable, config });
|
||||||
|
await dropObsoleteForeignKeys({ table: resolvedTable, config });
|
||||||
|
await syncPrimaryKey({ table: resolvedTable, config });
|
||||||
|
await syncIndexes({ table: resolvedTable, config });
|
||||||
|
await syncUniqueConstraints({ table: resolvedTable, config });
|
||||||
|
await ensureForeignKeys({ table: resolvedTable, config });
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
import type { CreateDBSchemaParams } from "../../types";
|
||||||
|
export default function handleDBSchemaTables(params: CreateDBSchemaParams): Promise<void>;
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
import _ from "lodash";
|
||||||
|
import { AppData } from "../../data/app-data";
|
||||||
|
import handleDBSchemaTable from "./handle-db-schema-table";
|
||||||
|
import getExistingTablesFromTablesManagerTable from "./get-existing-tables-from-tables-manager-table";
|
||||||
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||||
|
import schemaCondition from "./schema-condition";
|
||||||
|
import { removeDbManagerTable } from "./upsert-db-manager-table";
|
||||||
|
export default async function handleDBSchemaTables(params) {
|
||||||
|
const { db_schema, config } = params;
|
||||||
|
/**
|
||||||
|
* Grab Tables that have been recorded in the Schema
|
||||||
|
* Manager Table
|
||||||
|
*/
|
||||||
|
const existing_schema_tables = await getExistingTablesFromTablesManagerTable(params);
|
||||||
|
const schemaCond = schemaCondition(config);
|
||||||
|
const existing_live_tables = await querySchemaRows({
|
||||||
|
query: `SELECT TABLE_NAME FROM information_schema.TABLES WHERE ${schemaCond.where} AND TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME != ?`,
|
||||||
|
values: [...schemaCond.values, AppData["DbSchemaManagerTableName"]],
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
for (let i = 0; i < db_schema.tables.length; i++) {
|
||||||
|
const table = db_schema.tables[i];
|
||||||
|
if (!table)
|
||||||
|
continue;
|
||||||
|
const existing_table = existing_schema_tables.find((t) => t == table.tableName);
|
||||||
|
const existing_live_table = existing_live_tables.find((t) => t.TABLE_NAME == table.tableName);
|
||||||
|
await handleDBSchemaTable({
|
||||||
|
...params,
|
||||||
|
table,
|
||||||
|
db_manager_table_name: existing_table,
|
||||||
|
existing_live_table,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Drop tables tracked by the manager but no longer in the schema.
|
||||||
|
* Skip drops when remaining schema tables still reference the table via FK
|
||||||
|
* (e.g. external tables like `users` that are referenced but not managed).
|
||||||
|
*/
|
||||||
|
const schemaTableNames = db_schema.tables.map((t) => t.tableName);
|
||||||
|
const tablesToDrop = _.uniq(existing_schema_tables.filter((tableName) => Boolean(tableName) && !schemaTableNames.includes(tableName)));
|
||||||
|
if (tablesToDrop.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fkRows = await querySchemaRows({
|
||||||
|
query: `SELECT TABLE_NAME, REFERENCED_TABLE_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE ${schemaCond.where} AND REFERENCED_TABLE_NAME IS NOT NULL`,
|
||||||
|
values: schemaCond.values,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
const referencedByRemaining = new Map();
|
||||||
|
for (const row of fkRows) {
|
||||||
|
if (!row.REFERENCED_TABLE_NAME ||
|
||||||
|
!tablesToDrop.includes(row.REFERENCED_TABLE_NAME)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Referenced by a table we are keeping
|
||||||
|
if (schemaTableNames.includes(row.TABLE_NAME) ||
|
||||||
|
!tablesToDrop.includes(row.TABLE_NAME)) {
|
||||||
|
const list = referencedByRemaining.get(row.REFERENCED_TABLE_NAME) || [];
|
||||||
|
if (!list.includes(row.TABLE_NAME)) {
|
||||||
|
list.push(row.TABLE_NAME);
|
||||||
|
}
|
||||||
|
referencedByRemaining.set(row.REFERENCED_TABLE_NAME, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const safeToDrop = [];
|
||||||
|
for (const tableName of tablesToDrop) {
|
||||||
|
const dependents = referencedByRemaining.get(tableName);
|
||||||
|
if (dependents && dependents.length > 0) {
|
||||||
|
console.warn(`Skipping drop of table \`${tableName}\`: still referenced by ${dependents.join(", ")}. Removing from schema manager tracking only.`);
|
||||||
|
await removeDbManagerTable({ tableName, config });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
safeToDrop.push(tableName);
|
||||||
|
}
|
||||||
|
if (safeToDrop.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 0`, config });
|
||||||
|
try {
|
||||||
|
for (const tableName of safeToDrop) {
|
||||||
|
console.log(`Dropping table: ${tableName}`);
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `DROP TABLE IF EXISTS ${MariaDBQuoteGen(tableName)}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
await removeDbManagerTable({ tableName, config });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 1`, config });
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
|
||||||
|
/**
|
||||||
|
* True when a field is a MariaDB native vector column.
|
||||||
|
*/
|
||||||
|
export default function isVectorField(field?: BUN_MARIADB_FieldSchemaType): boolean;
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* True when a field is a MariaDB native vector column.
|
||||||
|
*/
|
||||||
|
export default function isVectorField(field) {
|
||||||
|
if (!field)
|
||||||
|
return false;
|
||||||
|
return field.isVector === true || field.dataType === "VECTOR";
|
||||||
|
}
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
|
||||||
|
export default function mapDataType(field: BUN_MARIADB_FieldSchemaType): string;
|
||||||
Vendored
+92
@@ -0,0 +1,92 @@
|
|||||||
|
export default function mapDataType(field) {
|
||||||
|
const dataType = field.dataType?.toUpperCase() || "TEXT";
|
||||||
|
const vectorSize = field.vectorSize || 1536;
|
||||||
|
// Native MariaDB VECTOR type (11.7+). Prefer this over LONGTEXT storage.
|
||||||
|
if (field.isVector || dataType === "VECTOR") {
|
||||||
|
return `VECTOR(${vectorSize})`;
|
||||||
|
}
|
||||||
|
switch (dataType) {
|
||||||
|
case "CHAR":
|
||||||
|
return `CHAR(${field.dataLength || 255})`;
|
||||||
|
case "VARCHAR":
|
||||||
|
return `VARCHAR(${field.dataLength || 255})`;
|
||||||
|
case "TEXT":
|
||||||
|
return "TEXT";
|
||||||
|
case "TINYTEXT":
|
||||||
|
return "TINYTEXT";
|
||||||
|
case "MEDIUMTEXT":
|
||||||
|
return "MEDIUMTEXT";
|
||||||
|
case "LONGTEXT":
|
||||||
|
return "LONGTEXT";
|
||||||
|
case "TINYINT":
|
||||||
|
return field.dataLength
|
||||||
|
? `TINYINT(${field.dataLength})`
|
||||||
|
: "TINYINT";
|
||||||
|
case "SMALLINT":
|
||||||
|
return field.dataLength
|
||||||
|
? `SMALLINT(${field.dataLength})`
|
||||||
|
: "SMALLINT";
|
||||||
|
case "MEDIUMINT":
|
||||||
|
return field.dataLength
|
||||||
|
? `MEDIUMINT(${field.dataLength})`
|
||||||
|
: "MEDIUMINT";
|
||||||
|
case "INT":
|
||||||
|
return field.dataLength ? `INT(${field.dataLength})` : "INT";
|
||||||
|
case "BIGINT":
|
||||||
|
return field.dataLength ? `BIGINT(${field.dataLength})` : "BIGINT";
|
||||||
|
case "FLOAT":
|
||||||
|
return "FLOAT";
|
||||||
|
case "DOUBLE":
|
||||||
|
return "DOUBLE";
|
||||||
|
case "DECIMAL":
|
||||||
|
if (field.dataLength && field.decimals) {
|
||||||
|
return `DECIMAL(${field.dataLength}, ${field.decimals})`;
|
||||||
|
}
|
||||||
|
return "DECIMAL(10,2)";
|
||||||
|
case "BINARY":
|
||||||
|
return `BINARY(${field.dataLength || 1})`;
|
||||||
|
case "VARBINARY":
|
||||||
|
return `VARBINARY(${field.dataLength || 255})`;
|
||||||
|
case "BLOB":
|
||||||
|
return "BLOB";
|
||||||
|
case "TINYBLOB":
|
||||||
|
return "TINYBLOB";
|
||||||
|
case "MEDIUMBLOB":
|
||||||
|
return "MEDIUMBLOB";
|
||||||
|
case "LONGBLOB":
|
||||||
|
return "LONGBLOB";
|
||||||
|
case "DATE":
|
||||||
|
return "DATE";
|
||||||
|
case "TIME":
|
||||||
|
return "TIME";
|
||||||
|
case "DATETIME":
|
||||||
|
return "DATETIME";
|
||||||
|
case "TIMESTAMP":
|
||||||
|
return "TIMESTAMP";
|
||||||
|
case "YEAR":
|
||||||
|
return "YEAR";
|
||||||
|
case "UUID":
|
||||||
|
return "UUID";
|
||||||
|
case "JSON":
|
||||||
|
return "JSON";
|
||||||
|
case "INET6":
|
||||||
|
return "INET6";
|
||||||
|
case "BOOL":
|
||||||
|
case "BOOLEAN":
|
||||||
|
return "TINYINT(1)";
|
||||||
|
case "ENUM": {
|
||||||
|
const enumVals = (field.options || [])
|
||||||
|
.map((v) => `'${String(v).replace(/'/g, "''")}'`)
|
||||||
|
.join(", ");
|
||||||
|
return `ENUM(${enumVals || "''"})`;
|
||||||
|
}
|
||||||
|
case "SET": {
|
||||||
|
const setVals = (field.options || [])
|
||||||
|
.map((v) => `'${String(v).replace(/'/g, "''")}'`)
|
||||||
|
.join(", ");
|
||||||
|
return `SET(${setVals || "''"})`;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "TEXT";
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export default function MariaDBQuoteGen(str: string): string;
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
export default function MariaDBQuoteGen(str) {
|
||||||
|
return `\`${str.replace(/`/g, "``")}\``;
|
||||||
|
}
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
import type { BUN_MARIADB_DatabaseSchemaType, CreateDBSchemaParams } from "../../types";
|
||||||
|
export default function orderDBSchema({ db_schema, }: CreateDBSchemaParams): Promise<BUN_MARIADB_DatabaseSchemaType>;
|
||||||
Vendored
+57
@@ -0,0 +1,57 @@
|
|||||||
|
import _ from "lodash";
|
||||||
|
export default async function orderDBSchema({ db_schema, }) {
|
||||||
|
let new_db_schema = _.cloneDeep(db_schema);
|
||||||
|
const tables = new_db_schema.tables;
|
||||||
|
let new_tables_set = new Set();
|
||||||
|
let new_tables_start_set = new Set();
|
||||||
|
let new_tables_end_set = new Set();
|
||||||
|
function setParentTable(table) {
|
||||||
|
const fields = table.fields;
|
||||||
|
let does_table_have_foreign_keys = false;
|
||||||
|
for (let f = 0; f < fields.length; f++) {
|
||||||
|
const field = fields[f];
|
||||||
|
if (!field)
|
||||||
|
continue;
|
||||||
|
const dst_table_name = field.foreignKey?.destinationTableName;
|
||||||
|
if (dst_table_name) {
|
||||||
|
const fk_table = tables.find((tb) => tb.tableName == dst_table_name);
|
||||||
|
if (fk_table &&
|
||||||
|
!new_tables_start_set.has(fk_table) &&
|
||||||
|
!new_tables_end_set.has(fk_table)) {
|
||||||
|
setParentTable(fk_table);
|
||||||
|
}
|
||||||
|
new_tables_end_set.add(table);
|
||||||
|
if (fk_table) {
|
||||||
|
new_tables_start_set.add(fk_table);
|
||||||
|
}
|
||||||
|
does_table_have_foreign_keys = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { does_table_have_foreign_keys };
|
||||||
|
}
|
||||||
|
for (let i = 0; i < tables.length; i++) {
|
||||||
|
const table = tables[i];
|
||||||
|
if (!table) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let { does_table_have_foreign_keys } = setParentTable(table);
|
||||||
|
if (does_table_have_foreign_keys) {
|
||||||
|
new_tables_end_set.add(table);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
new_tables_start_set.add(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parsed_tables = [
|
||||||
|
...Array.from(new_tables_start_set),
|
||||||
|
...Array.from(new_tables_end_set),
|
||||||
|
];
|
||||||
|
for (let nt = 0; nt < parsed_tables.length; nt++) {
|
||||||
|
const new_table = parsed_tables[nt];
|
||||||
|
if (new_table) {
|
||||||
|
new_tables_set.add(new_table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_db_schema.tables = Array.from(new_tables_set);
|
||||||
|
return new_db_schema;
|
||||||
|
}
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
import type { BUN_MARIADB_DatabaseSchemaType, BUN_MARIADB_TableSchemaType } from "../../types";
|
||||||
|
export default function resolveTable(table: BUN_MARIADB_TableSchemaType, db_schema: BUN_MARIADB_DatabaseSchemaType): BUN_MARIADB_TableSchemaType;
|
||||||
Vendored
+33
@@ -0,0 +1,33 @@
|
|||||||
|
import _ from "lodash";
|
||||||
|
export default function resolveTable(table, db_schema) {
|
||||||
|
if (!table.parentTableName) {
|
||||||
|
return _.cloneDeep(table);
|
||||||
|
}
|
||||||
|
const parentTable = db_schema.tables.find((schemaTable) => schemaTable.tableName === table.parentTableName);
|
||||||
|
if (!parentTable) {
|
||||||
|
throw new Error(`Parent table \`${table.parentTableName}\` not found for \`${table.tableName}\``);
|
||||||
|
}
|
||||||
|
const mergedFieldsMap = new Map();
|
||||||
|
(parentTable.fields || []).forEach((f) => {
|
||||||
|
if (f.fieldName)
|
||||||
|
mergedFieldsMap.set(f.fieldName, _.cloneDeep(f));
|
||||||
|
});
|
||||||
|
(table.fields || []).forEach((f) => {
|
||||||
|
if (f.fieldName) {
|
||||||
|
const existing = mergedFieldsMap.get(f.fieldName) || {};
|
||||||
|
mergedFieldsMap.set(f.fieldName, _.merge({}, existing, f));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
..._.cloneDeep(parentTable),
|
||||||
|
tableName: table.tableName,
|
||||||
|
tableDescription: table.tableDescription || parentTable.tableDescription,
|
||||||
|
collation: table.collation || parentTable.collation,
|
||||||
|
fields: Array.from(mergedFieldsMap.values()),
|
||||||
|
indexes: _.uniqBy([...(parentTable.indexes || []), ...(table.indexes || [])], "indexName"),
|
||||||
|
uniqueConstraints: [
|
||||||
|
...(parentTable.uniqueConstraints || []),
|
||||||
|
...(table.uniqueConstraints || []),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
import type { BunMariaDBConfig } from "../../types";
|
||||||
|
export default function runSchemaQuery({ query, values, config, }: {
|
||||||
|
query: string;
|
||||||
|
values?: any[];
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<void>;
|
||||||
|
export declare function querySchemaRows<T extends Record<string, any>>({ query, values, config, }: {
|
||||||
|
query: string;
|
||||||
|
values?: any[];
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<T[]>;
|
||||||
Vendored
+22
@@ -0,0 +1,22 @@
|
|||||||
|
import dbHandler from "../db-handler";
|
||||||
|
export default async function runSchemaQuery({ query, values, config, }) {
|
||||||
|
const res = await dbHandler({
|
||||||
|
query,
|
||||||
|
values,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
if (!res.success) {
|
||||||
|
throw new Error(`Database query failed: ${query} ... ERROR: ${res.error || res.msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function querySchemaRows({ query, values, config, }) {
|
||||||
|
const res = await dbHandler({
|
||||||
|
query,
|
||||||
|
values,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
if (!res.success) {
|
||||||
|
throw new Error(`Database query failed: ${query} ... ERROR: ${res.error || res.msg}`);
|
||||||
|
}
|
||||||
|
return (res.payload || []);
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import type { BunMariaDBConfig } from "../../types";
|
||||||
|
export default function schemaCondition(config?: BunMariaDBConfig): {
|
||||||
|
where: string;
|
||||||
|
values: string[];
|
||||||
|
};
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
export default function schemaCondition(config) {
|
||||||
|
const databaseName = config?.db_name || global.CONFIG?.db_name;
|
||||||
|
if (databaseName) {
|
||||||
|
return {
|
||||||
|
where: "TABLE_SCHEMA = ?",
|
||||||
|
values: [databaseName],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
where: "TABLE_SCHEMA = DATABASE()",
|
||||||
|
values: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
import type { BUN_MARIADB_FieldSchemaType, BUN_MARIADB_TableSchemaType, BunMariaDBConfig } from "../../types";
|
||||||
|
export type DesiredForeignKey = {
|
||||||
|
name: string;
|
||||||
|
column: string;
|
||||||
|
refTable: string;
|
||||||
|
refColumn: string;
|
||||||
|
cascadeDelete: boolean;
|
||||||
|
cascadeUpdate: boolean;
|
||||||
|
field: BUN_MARIADB_FieldSchemaType;
|
||||||
|
};
|
||||||
|
export type LiveForeignKey = {
|
||||||
|
name: string;
|
||||||
|
column: string;
|
||||||
|
refTable: string;
|
||||||
|
refColumn: string;
|
||||||
|
deleteRule: string;
|
||||||
|
updateRule: string;
|
||||||
|
};
|
||||||
|
export declare function grabDesiredForeignKeys(table: BUN_MARIADB_TableSchemaType): DesiredForeignKey[];
|
||||||
|
export declare function grabLiveForeignKeys({ tableName, config, }: {
|
||||||
|
tableName: string;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<LiveForeignKey[]>;
|
||||||
|
export declare function dropForeignKey({ tableName, constraintName, config, }: {
|
||||||
|
tableName: string;
|
||||||
|
constraintName: string;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<void>;
|
||||||
|
export declare function dropForeignKeysOnColumns({ tableName, columns, config, }: {
|
||||||
|
tableName: string;
|
||||||
|
columns: string[];
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Drop foreign keys that are removed from schema or need recreation
|
||||||
|
* (name/cascade/target changed). Run before index cleanup.
|
||||||
|
*/
|
||||||
|
export declare function dropObsoleteForeignKeys({ table, config, }: {
|
||||||
|
table: BUN_MARIADB_TableSchemaType;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Ensure all desired foreign keys exist. Run after indexes/uniques.
|
||||||
|
*/
|
||||||
|
export declare function ensureForeignKeys({ table, config, }: {
|
||||||
|
table: BUN_MARIADB_TableSchemaType;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<void>;
|
||||||
|
export default function syncForeignKeys({ table, config, }: {
|
||||||
|
table: BUN_MARIADB_TableSchemaType;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<void>;
|
||||||
Vendored
+180
@@ -0,0 +1,180 @@
|
|||||||
|
import buildForeignKeyConstraint, { resolveForeignKeyName, } from "./build-foreign-key-constraint";
|
||||||
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||||
|
function normalizeRule(rule) {
|
||||||
|
return String(rule || "RESTRICT").toUpperCase().replace(/_/g, " ");
|
||||||
|
}
|
||||||
|
function ruleIsCascade(rule) {
|
||||||
|
return normalizeRule(rule) === "CASCADE";
|
||||||
|
}
|
||||||
|
function fkIdentityKey(fk) {
|
||||||
|
return `${fk.column}\0${fk.refTable}\0${fk.refColumn}`;
|
||||||
|
}
|
||||||
|
function rulesMatch(live, desired) {
|
||||||
|
return (ruleIsCascade(live.deleteRule) === desired.cascadeDelete &&
|
||||||
|
ruleIsCascade(live.updateRule) === desired.cascadeUpdate);
|
||||||
|
}
|
||||||
|
export function grabDesiredForeignKeys(table) {
|
||||||
|
const desired = [];
|
||||||
|
for (const field of table.fields || []) {
|
||||||
|
const fk = field.foreignKey;
|
||||||
|
if (!field.fieldName ||
|
||||||
|
!fk?.destinationTableName ||
|
||||||
|
!fk.destinationTableColumnName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
desired.push({
|
||||||
|
name: resolveForeignKeyName(field, table.tableName),
|
||||||
|
column: field.fieldName,
|
||||||
|
refTable: fk.destinationTableName,
|
||||||
|
refColumn: fk.destinationTableColumnName,
|
||||||
|
cascadeDelete: Boolean(fk.cascadeDelete),
|
||||||
|
cascadeUpdate: Boolean(fk.cascadeUpdate),
|
||||||
|
field,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return desired;
|
||||||
|
}
|
||||||
|
export async function grabLiveForeignKeys({ tableName, config, }) {
|
||||||
|
const databaseName = config?.db_name || global.CONFIG?.db_name;
|
||||||
|
const schemaWhere = databaseName
|
||||||
|
? "rc.CONSTRAINT_SCHEMA = ?"
|
||||||
|
: "rc.CONSTRAINT_SCHEMA = DATABASE()";
|
||||||
|
const schemaValues = databaseName ? [databaseName] : [];
|
||||||
|
const rows = await querySchemaRows({
|
||||||
|
query: `
|
||||||
|
SELECT
|
||||||
|
rc.CONSTRAINT_NAME,
|
||||||
|
kcu.COLUMN_NAME,
|
||||||
|
kcu.REFERENCED_TABLE_NAME,
|
||||||
|
kcu.REFERENCED_COLUMN_NAME,
|
||||||
|
rc.DELETE_RULE,
|
||||||
|
rc.UPDATE_RULE
|
||||||
|
FROM information_schema.REFERENTIAL_CONSTRAINTS rc
|
||||||
|
INNER JOIN information_schema.KEY_COLUMN_USAGE kcu
|
||||||
|
ON rc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
|
||||||
|
AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
|
||||||
|
AND rc.TABLE_NAME = kcu.TABLE_NAME
|
||||||
|
WHERE ${schemaWhere}
|
||||||
|
AND rc.TABLE_NAME = ?
|
||||||
|
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
|
||||||
|
ORDER BY rc.CONSTRAINT_NAME, kcu.ORDINAL_POSITION
|
||||||
|
`,
|
||||||
|
values: [...schemaValues, tableName],
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
// Single-column FKs only (schema model is one FK per field)
|
||||||
|
const byName = new Map();
|
||||||
|
for (const row of rows) {
|
||||||
|
if (byName.has(row.CONSTRAINT_NAME)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
byName.set(row.CONSTRAINT_NAME, {
|
||||||
|
name: row.CONSTRAINT_NAME,
|
||||||
|
column: row.COLUMN_NAME,
|
||||||
|
refTable: row.REFERENCED_TABLE_NAME,
|
||||||
|
refColumn: row.REFERENCED_COLUMN_NAME,
|
||||||
|
deleteRule: row.DELETE_RULE,
|
||||||
|
updateRule: row.UPDATE_RULE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [...byName.values()];
|
||||||
|
}
|
||||||
|
export async function dropForeignKey({ tableName, constraintName, config, }) {
|
||||||
|
console.log(`Dropping foreign key: ${constraintName} on ${tableName}`);
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `ALTER TABLE ${MariaDBQuoteGen(tableName)} DROP FOREIGN KEY ${MariaDBQuoteGen(constraintName)}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export async function dropForeignKeysOnColumns({ tableName, columns, config, }) {
|
||||||
|
if (columns.length === 0)
|
||||||
|
return;
|
||||||
|
const live = await grabLiveForeignKeys({ tableName, config });
|
||||||
|
const colSet = new Set(columns);
|
||||||
|
for (const fk of live) {
|
||||||
|
if (colSet.has(fk.column)) {
|
||||||
|
await dropForeignKey({
|
||||||
|
tableName,
|
||||||
|
constraintName: fk.name,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Drop foreign keys that are removed from schema or need recreation
|
||||||
|
* (name/cascade/target changed). Run before index cleanup.
|
||||||
|
*/
|
||||||
|
export async function dropObsoleteForeignKeys({ table, config, }) {
|
||||||
|
const desired = grabDesiredForeignKeys(table);
|
||||||
|
const live = await grabLiveForeignKeys({
|
||||||
|
tableName: table.tableName,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
const keepNames = new Set();
|
||||||
|
const droppedNames = new Set();
|
||||||
|
const dropOnce = async (name) => {
|
||||||
|
if (droppedNames.has(name))
|
||||||
|
return;
|
||||||
|
droppedNames.add(name);
|
||||||
|
await dropForeignKey({
|
||||||
|
tableName: table.tableName,
|
||||||
|
constraintName: name,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
for (const want of desired) {
|
||||||
|
const byIdentity = live.find((existing) => !keepNames.has(existing.name) &&
|
||||||
|
!droppedNames.has(existing.name) &&
|
||||||
|
fkIdentityKey(existing) === fkIdentityKey(want));
|
||||||
|
if (byIdentity &&
|
||||||
|
rulesMatch(byIdentity, want) &&
|
||||||
|
byIdentity.name === want.name) {
|
||||||
|
keepNames.add(byIdentity.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Changed relationship → drop so ensureForeignKeys can recreate
|
||||||
|
if (byIdentity) {
|
||||||
|
await dropOnce(byIdentity.name);
|
||||||
|
}
|
||||||
|
const byName = live.find((existing) => existing.name === want.name);
|
||||||
|
if (byName && byName !== byIdentity) {
|
||||||
|
await dropOnce(byName.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const existing of live) {
|
||||||
|
if (keepNames.has(existing.name) || droppedNames.has(existing.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Not in schema anymore
|
||||||
|
await dropOnce(existing.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Ensure all desired foreign keys exist. Run after indexes/uniques.
|
||||||
|
*/
|
||||||
|
export async function ensureForeignKeys({ table, config, }) {
|
||||||
|
const desired = grabDesiredForeignKeys(table);
|
||||||
|
const live = await grabLiveForeignKeys({
|
||||||
|
tableName: table.tableName,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
for (const want of desired) {
|
||||||
|
const exists = live.some((existing) => fkIdentityKey(existing) === fkIdentityKey(want) &&
|
||||||
|
rulesMatch(existing, want) &&
|
||||||
|
existing.name === want.name);
|
||||||
|
if (exists) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
console.log(`Creating foreign key: ${want.name} (${want.column} → ${want.refTable}.${want.refColumn}) on ${table.tableName}`);
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `ALTER TABLE ${MariaDBQuoteGen(table.tableName)} ADD ${buildForeignKeyConstraint(want.field, table.tableName)}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default async function syncForeignKeys({ table, config, }) {
|
||||||
|
await dropObsoleteForeignKeys({ table, config });
|
||||||
|
await ensureForeignKeys({ table, config });
|
||||||
|
}
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import type { BUN_MARIADB_TableSchemaType, BunMariaDBConfig } from "../../types";
|
||||||
|
export default function syncIndexes({ table, config, }: {
|
||||||
|
table: BUN_MARIADB_TableSchemaType;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<void>;
|
||||||
Vendored
+153
@@ -0,0 +1,153 @@
|
|||||||
|
import isVectorField from "./is-vector-field";
|
||||||
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||||
|
import schemaCondition from "./schema-condition";
|
||||||
|
import { grabDesiredUniqueConstraints } from "./sync-unique-constraints";
|
||||||
|
function isVectorIndexDef(index, table) {
|
||||||
|
if (index.indexType === "VECTOR")
|
||||||
|
return true;
|
||||||
|
const firstFieldName = index.indexTableFields?.[0];
|
||||||
|
if (!firstFieldName)
|
||||||
|
return false;
|
||||||
|
const field = table.fields?.find((f) => f.fieldName === firstFieldName);
|
||||||
|
return isVectorField(field);
|
||||||
|
}
|
||||||
|
function vectorDistanceMetric(index) {
|
||||||
|
if (index.vectorDistanceMetric === "cosine")
|
||||||
|
return "cosine";
|
||||||
|
if (index.vectorDistanceMetric === "euclidean")
|
||||||
|
return "euclidean";
|
||||||
|
return "euclidean";
|
||||||
|
}
|
||||||
|
/** Normalize schema index type vs information_schema.INDEX_TYPE */
|
||||||
|
function indexTypesMatch(liveType, schemaIndex, table) {
|
||||||
|
const live = (liveType || "").toUpperCase();
|
||||||
|
const schemaType = schemaIndex.indexType?.toUpperCase();
|
||||||
|
if (isVectorIndexDef(schemaIndex, table)) {
|
||||||
|
// MariaDB may report VECTOR indexes as BTREE or VECTOR depending on version
|
||||||
|
return live === "VECTOR" || live === "BTREE" || live === "";
|
||||||
|
}
|
||||||
|
if (!schemaType || schemaType === "BTREE") {
|
||||||
|
// Default / unspecified → BTREE
|
||||||
|
return live === "BTREE" || live === "";
|
||||||
|
}
|
||||||
|
if (schemaType === "FULLTEXT") {
|
||||||
|
return live === "FULLTEXT";
|
||||||
|
}
|
||||||
|
if (schemaType === "SPATIAL") {
|
||||||
|
return live === "SPATIAL";
|
||||||
|
}
|
||||||
|
if (schemaType === "HASH") {
|
||||||
|
return live === "HASH";
|
||||||
|
}
|
||||||
|
return live === schemaType;
|
||||||
|
}
|
||||||
|
async function createIndex({ table, index, config, }) {
|
||||||
|
if (!index.indexName ||
|
||||||
|
!index.indexTableFields ||
|
||||||
|
index.indexTableFields.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isVectorIndexDef(index, table)) {
|
||||||
|
console.log(`Creating Vector index: ${index.indexName}`);
|
||||||
|
const targetField = MariaDBQuoteGen(index.indexTableFields[0]);
|
||||||
|
const distanceMetric = vectorDistanceMetric(index);
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `ALTER TABLE ${MariaDBQuoteGen(table.tableName)} ADD VECTOR INDEX ${MariaDBQuoteGen(index.indexName)} (${targetField}) M=8 DISTANCE=${distanceMetric}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`Creating standard index: ${index.indexName}`);
|
||||||
|
const fields = index.indexTableFields
|
||||||
|
.map((field) => MariaDBQuoteGen(field))
|
||||||
|
.join(", ");
|
||||||
|
const typeUpper = index.indexType?.toUpperCase();
|
||||||
|
const isSpecialType = typeUpper === "FULLTEXT" || typeUpper === "SPATIAL";
|
||||||
|
const indexPrefix = isSpecialType ? `${typeUpper} ` : "";
|
||||||
|
const indexSuffix = !isSpecialType && (typeUpper === "BTREE" || typeUpper === "HASH")
|
||||||
|
? ` USING ${typeUpper}`
|
||||||
|
: "";
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `CREATE ${indexPrefix}INDEX ${MariaDBQuoteGen(index.indexName)} ON ${MariaDBQuoteGen(table.tableName)} (${fields})${indexSuffix}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default async function syncIndexes({ table, config, }) {
|
||||||
|
const schemaCond = schemaCondition(config);
|
||||||
|
const rows = await querySchemaRows({
|
||||||
|
query: `SELECT INDEX_NAME, COLUMN_NAME, INDEX_TYPE FROM information_schema.STATISTICS WHERE ${schemaCond.where} AND TABLE_NAME = ? AND INDEX_NAME <> 'PRIMARY' ORDER BY INDEX_NAME, SEQ_IN_INDEX`,
|
||||||
|
values: [...schemaCond.values, table.tableName],
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Indexes required by foreign keys / unique constraints cannot be dropped
|
||||||
|
* freely. Skip those when cleaning up schema indexes.
|
||||||
|
*/
|
||||||
|
const protectedConstraintRows = await querySchemaRows({
|
||||||
|
query: `SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE FROM information_schema.TABLE_CONSTRAINTS WHERE ${schemaCond.where} AND TABLE_NAME = ? AND CONSTRAINT_TYPE IN ('FOREIGN KEY', 'UNIQUE')`,
|
||||||
|
values: [...schemaCond.values, table.tableName],
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
const protectedIndexNames = new Set(protectedConstraintRows.map((r) => r.CONSTRAINT_NAME));
|
||||||
|
for (const constraint of grabDesiredUniqueConstraints(table)) {
|
||||||
|
protectedIndexNames.add(constraint.name);
|
||||||
|
}
|
||||||
|
const existingIndexesMap = new Map();
|
||||||
|
for (const row of rows) {
|
||||||
|
if (!existingIndexesMap.has(row.INDEX_NAME)) {
|
||||||
|
existingIndexesMap.set(row.INDEX_NAME, {
|
||||||
|
columns: [],
|
||||||
|
type: row.INDEX_TYPE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
existingIndexesMap.get(row.INDEX_NAME).columns.push(row.COLUMN_NAME);
|
||||||
|
}
|
||||||
|
for (const [indexName, details] of existingIndexesMap.entries()) {
|
||||||
|
if (protectedIndexNames.has(indexName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const schemaIndex = table.indexes?.find((i) => i.indexName === indexName);
|
||||||
|
if (!schemaIndex) {
|
||||||
|
console.log(`Dropping index: ${indexName}`);
|
||||||
|
try {
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `DROP INDEX ${MariaDBQuoteGen(indexName)} ON ${MariaDBQuoteGen(table.tableName)}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
existingIndexesMap.delete(indexName);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
if (String(err?.message || "").includes("needed in a foreign key constraint")) {
|
||||||
|
console.warn(`Skipping drop of index ${indexName}: required by a foreign key constraint`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const schemaColumns = schemaIndex.indexTableFields || [];
|
||||||
|
const columnsMatch = details.columns.length === schemaColumns.length &&
|
||||||
|
details.columns.every((col, idx) => col === schemaColumns[idx]);
|
||||||
|
const typeMatch = indexTypesMatch(details.type, schemaIndex, table);
|
||||||
|
if (!columnsMatch || !typeMatch) {
|
||||||
|
console.log(`Recreating changed index: ${indexName}`);
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `DROP INDEX ${MariaDBQuoteGen(indexName)} ON ${MariaDBQuoteGen(table.tableName)}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
existingIndexesMap.delete(indexName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const index of table.indexes || []) {
|
||||||
|
if (!index.indexName ||
|
||||||
|
!index.indexTableFields ||
|
||||||
|
index.indexTableFields.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!existingIndexesMap.has(index.indexName)) {
|
||||||
|
await createIndex({ table, index, config });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import type { BUN_MARIADB_TableSchemaType, BunMariaDBConfig } from "../../types";
|
||||||
|
export declare function grabDesiredPrimaryKeyColumns(table: BUN_MARIADB_TableSchemaType): string[];
|
||||||
|
export default function syncPrimaryKey({ table, config, }: {
|
||||||
|
table: BUN_MARIADB_TableSchemaType;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<void>;
|
||||||
Vendored
+52
@@ -0,0 +1,52 @@
|
|||||||
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||||
|
import schemaCondition from "./schema-condition";
|
||||||
|
function columnsEqual(a, b) {
|
||||||
|
return a.length === b.length && a.every((col, i) => col === b[i]);
|
||||||
|
}
|
||||||
|
export function grabDesiredPrimaryKeyColumns(table) {
|
||||||
|
return (table.fields || [])
|
||||||
|
.filter((field) => field.primaryKey && field.fieldName)
|
||||||
|
.map((field) => field.fieldName);
|
||||||
|
}
|
||||||
|
async function grabLivePrimaryKeyColumns({ tableName, config, }) {
|
||||||
|
const schemaCond = schemaCondition(config);
|
||||||
|
const rows = await querySchemaRows({
|
||||||
|
query: `
|
||||||
|
SELECT COLUMN_NAME, ORDINAL_POSITION
|
||||||
|
FROM information_schema.KEY_COLUMN_USAGE
|
||||||
|
WHERE ${schemaCond.where}
|
||||||
|
AND TABLE_NAME = ?
|
||||||
|
AND CONSTRAINT_NAME = 'PRIMARY'
|
||||||
|
ORDER BY ORDINAL_POSITION
|
||||||
|
`,
|
||||||
|
values: [...schemaCond.values, tableName],
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
return rows.map((row) => row.COLUMN_NAME);
|
||||||
|
}
|
||||||
|
export default async function syncPrimaryKey({ table, config, }) {
|
||||||
|
const desired = grabDesiredPrimaryKeyColumns(table);
|
||||||
|
const live = await grabLivePrimaryKeyColumns({
|
||||||
|
tableName: table.tableName,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
if (columnsEqual(desired, live)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (live.length > 0) {
|
||||||
|
console.log(`Dropping primary key on ${table.tableName}`);
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `ALTER TABLE ${MariaDBQuoteGen(table.tableName)} DROP PRIMARY KEY`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (desired.length > 0) {
|
||||||
|
const cols = desired.map((col) => MariaDBQuoteGen(col)).join(", ");
|
||||||
|
console.log(`Creating primary key (${desired.join(", ")}) on ${table.tableName}`);
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `ALTER TABLE ${MariaDBQuoteGen(table.tableName)} ADD PRIMARY KEY (${cols})`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import type { BUN_MARIADB_TableSchemaType, BunMariaDBConfig } from "../../types";
|
||||||
|
export default function syncTableOptions({ table, config, }: {
|
||||||
|
table: BUN_MARIADB_TableSchemaType;
|
||||||
|
config?: BunMariaDBConfig;
|
||||||
|
}): Promise<void>;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user