diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c1d1243..b5b0f036 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,7 +126,7 @@ importers: dependencies: '@astrojs/tailwind': specifier: ^5.1.5 - version: 5.1.5(astro@5.5.4(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@4.36.0)(typescript@5.8.2)(yaml@2.7.0))(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2)))(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2)) + version: 5.1.5(astro@5.5.4(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@4.36.0)(typescript@4.9.4)(yaml@2.7.0))(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)))(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)) '@nanostores/persistent': specifier: ^0.10.2 version: 0.10.2(nanostores@0.11.4) @@ -135,7 +135,7 @@ importers: version: 0.5.0(nanostores@0.11.4)(solid-js@1.9.5) astro: specifier: ^5.5.4 - version: 5.5.4(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@4.36.0)(typescript@5.8.2)(yaml@2.7.0) + version: 5.5.4(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@4.36.0)(typescript@4.9.4)(yaml@2.7.0) astro-icon: specifier: ^1.1.5 version: 1.1.5 @@ -154,7 +154,7 @@ importers: devDependencies: '@astrojs/solid-js': specifier: ^5.0.5 - version: 5.0.5(@testing-library/jest-dom@6.6.3)(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(solid-devtools@0.30.1(solid-js@1.9.5)(vite@5.4.14(@types/node@22.13.11)(lightningcss@1.29.1)))(solid-js@1.9.5)(yaml@2.7.0) + version: 5.0.5(@testing-library/jest-dom@6.6.3)(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(solid-devtools@0.30.1(solid-js@1.9.5)(vite@5.4.14(@types/node@16.18.126)(lightningcss@1.29.1)))(solid-js@1.9.5)(yaml@2.7.0) '@eslint/js': specifier: ^9.23.0 version: 9.23.0 @@ -163,7 +163,7 @@ importers: version: 1.2.17 '@tailwindcss/typography': specifier: ^0.5.16 - version: 0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2))) + version: 0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4))) '@types/dateformat': specifier: ^5.0.3 version: 5.0.3 @@ -175,10 +175,10 @@ importers: version: 1.3.1(eslint@9.23.0(jiti@2.4.2)) eslint-plugin-solid: specifier: ^0.14.5 - version: 0.14.5(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) + version: 0.14.5(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4) eslint-plugin-tailwindcss: specifier: ^3.18.0 - version: 3.18.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2))) + version: 3.18.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4))) globals: specifier: ^16.0.0 version: 16.0.0 @@ -196,10 +196,10 @@ importers: version: 0.33.5 tailwindcss: specifier: ^3.4.17 - version: 3.4.17(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2)) + version: 3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)) typescript-eslint: specifier: ^8.27.0 - version: 8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) + version: 8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4) examples/coffee-vector-search: dependencies: @@ -6067,6 +6067,28 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.24.2 + '@astrojs/solid-js@5.0.5(@testing-library/jest-dom@6.6.3)(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(solid-devtools@0.30.1(solid-js@1.9.5)(vite@5.4.14(@types/node@16.18.126)(lightningcss@1.29.1)))(solid-js@1.9.5)(yaml@2.7.0)': + dependencies: + solid-js: 1.9.5 + vite: 6.2.2(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0) + vite-plugin-solid: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.2.2(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)) + optionalDependencies: + solid-devtools: 0.30.1(solid-js@1.9.5)(vite@5.4.14(@types/node@16.18.126)(lightningcss@1.29.1)) + transitivePeerDependencies: + - '@testing-library/jest-dom' + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + '@astrojs/solid-js@5.0.5(@testing-library/jest-dom@6.6.3)(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(solid-devtools@0.30.1(solid-js@1.9.5)(vite@5.4.14(@types/node@22.13.11)(lightningcss@1.29.1)))(solid-js@1.9.5)(yaml@2.7.0)': dependencies: solid-js: 1.9.5 @@ -6148,6 +6170,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@astrojs/tailwind@5.1.5(astro@5.5.4(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@4.36.0)(typescript@4.9.4)(yaml@2.7.0))(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)))(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4))': + dependencies: + astro: 5.5.4(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@4.36.0)(typescript@4.9.4)(yaml@2.7.0) + autoprefixer: 10.4.21(postcss@8.5.3) + postcss: 8.5.3 + postcss-load-config: 4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)) + tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)) + transitivePeerDependencies: + - ts-node + '@astrojs/tailwind@5.1.5(astro@5.5.4(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@4.36.0)(typescript@5.8.2)(yaml@2.7.0))(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2)))(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2))': dependencies: astro: 5.5.4(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@4.36.0)(typescript@5.8.2)(yaml@2.7.0) @@ -7339,6 +7371,14 @@ snapshots: dependencies: tslib: 2.8.1 + '@tailwindcss/typography@0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)))': + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + postcss-selector-parser: 6.0.10 + tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)) + '@tailwindcss/typography@0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2)))': dependencies: lodash.castarray: 4.4.0 @@ -7564,6 +7604,23 @@ snapshots: '@types/node': 22.13.11 optional: true + '@typescript-eslint/eslint-plugin@8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4))(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4) + '@typescript-eslint/scope-manager': 8.27.0 + '@typescript-eslint/type-utils': 8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4) + '@typescript-eslint/utils': 8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4) + '@typescript-eslint/visitor-keys': 8.27.0 + eslint: 9.23.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@4.9.4) + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/eslint-plugin@8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -7581,6 +7638,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4)': + dependencies: + '@typescript-eslint/scope-manager': 8.27.0 + '@typescript-eslint/types': 8.27.0 + '@typescript-eslint/typescript-estree': 8.27.0(typescript@4.9.4) + '@typescript-eslint/visitor-keys': 8.27.0 + debug: 4.4.0 + eslint: 9.23.0(jiti@2.4.2) + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': dependencies: '@typescript-eslint/scope-manager': 8.27.0 @@ -7598,6 +7667,17 @@ snapshots: '@typescript-eslint/types': 8.27.0 '@typescript-eslint/visitor-keys': 8.27.0 + '@typescript-eslint/type-utils@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4)': + dependencies: + '@typescript-eslint/typescript-estree': 8.27.0(typescript@4.9.4) + '@typescript-eslint/utils': 8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4) + debug: 4.4.0 + eslint: 9.23.0(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@4.9.4) + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/type-utils@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': dependencies: '@typescript-eslint/typescript-estree': 8.27.0(typescript@5.8.2) @@ -7611,6 +7691,20 @@ snapshots: '@typescript-eslint/types@8.27.0': {} + '@typescript-eslint/typescript-estree@8.27.0(typescript@4.9.4)': + dependencies: + '@typescript-eslint/types': 8.27.0 + '@typescript-eslint/visitor-keys': 8.27.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.1.0(typescript@4.9.4) + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/typescript-estree@8.27.0(typescript@5.8.2)': dependencies: '@typescript-eslint/types': 8.27.0 @@ -7625,6 +7719,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4)': + dependencies: + '@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.27.0 + '@typescript-eslint/types': 8.27.0 + '@typescript-eslint/typescript-estree': 8.27.0(typescript@4.9.4) + eslint: 9.23.0(jiti@2.4.2) + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': dependencies: '@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@2.4.2)) @@ -7928,6 +8033,101 @@ snapshots: valid-filename: 4.0.0 zod: 3.24.2 + astro@5.5.4(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@4.36.0)(typescript@4.9.4)(yaml@2.7.0): + dependencies: + '@astrojs/compiler': 2.11.0 + '@astrojs/internal-helpers': 0.6.1 + '@astrojs/markdown-remark': 6.3.1 + '@astrojs/telemetry': 3.2.0 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.1.4(rollup@4.36.0) + acorn: 8.14.1 + aria-query: 5.3.2 + axobject-query: 4.1.0 + boxen: 8.0.1 + ci-info: 4.2.0 + clsx: 2.1.1 + common-ancestor-path: 1.0.1 + cookie: 1.0.2 + cssesc: 3.0.0 + debug: 4.4.0 + deterministic-object-hash: 2.0.2 + devalue: 5.1.1 + diff: 5.2.0 + dlv: 1.1.3 + dset: 3.1.4 + es-module-lexer: 1.6.0 + esbuild: 0.25.1 + estree-walker: 3.0.3 + flattie: 1.1.1 + github-slugger: 2.0.0 + html-escaper: 3.0.3 + http-cache-semantics: 4.1.1 + js-yaml: 4.1.0 + kleur: 4.1.5 + magic-string: 0.30.17 + magicast: 0.3.5 + mrmime: 2.0.1 + neotraverse: 0.6.18 + p-limit: 6.2.0 + p-queue: 8.1.0 + package-manager-detector: 1.1.0 + picomatch: 4.0.2 + prompts: 2.4.2 + rehype: 13.0.2 + semver: 7.7.1 + shiki: 3.2.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.12 + tsconfck: 3.1.5(typescript@4.9.4) + ultrahtml: 1.5.3 + unist-util-visit: 5.0.0 + unstorage: 1.15.0 + vfile: 6.0.3 + vite: 6.2.2(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0) + vitefu: 1.0.6(vite@6.2.2(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)) + xxhash-wasm: 1.1.0 + yargs-parser: 21.1.1 + yocto-spinner: 0.2.1 + zod: 3.24.2 + zod-to-json-schema: 3.24.5(zod@3.24.2) + zod-to-ts: 1.2.0(typescript@4.9.4)(zod@3.24.2) + optionalDependencies: + sharp: 0.33.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/kv' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - yaml + astro@5.5.4(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@4.36.0)(typescript@5.8.2)(yaml@2.7.0): dependencies: '@astrojs/compiler': 2.11.0 @@ -8717,6 +8917,19 @@ snapshots: dependencies: eslint: 9.23.0(jiti@2.4.2) + eslint-plugin-solid@0.14.5(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4): + dependencies: + '@typescript-eslint/utils': 8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4) + eslint: 9.23.0(jiti@2.4.2) + estraverse: 5.3.0 + is-html: 2.0.0 + kebab-case: 1.0.2 + known-css-properties: 0.30.0 + style-to-object: 1.0.8 + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + eslint-plugin-solid@0.14.5(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2): dependencies: '@typescript-eslint/utils': 8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) @@ -8730,6 +8943,12 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-tailwindcss@3.18.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4))): + dependencies: + fast-glob: 3.3.3 + postcss: 8.5.3 + tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)) + eslint-plugin-tailwindcss@3.18.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2))): dependencies: fast-glob: 3.3.3 @@ -10560,6 +10779,14 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.3 + postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)): + dependencies: + lilconfig: 3.1.3 + yaml: 2.7.0 + optionalDependencies: + postcss: 8.5.3 + ts-node: 10.9.2(@types/node@16.18.126)(typescript@4.9.4) + postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2)): dependencies: lilconfig: 3.1.3 @@ -11237,6 +11464,20 @@ snapshots: smol-toml@1.3.1: {} + solid-devtools@0.30.1(solid-js@1.9.5)(vite@5.4.14(@types/node@16.18.126)(lightningcss@1.29.1)): + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) + '@babel/types': 7.26.10 + '@solid-devtools/debugger': 0.23.4(solid-js@1.9.5) + '@solid-devtools/shared': 0.13.2(solid-js@1.9.5) + solid-js: 1.9.5 + optionalDependencies: + vite: 5.4.14(@types/node@16.18.126)(lightningcss@1.29.1) + transitivePeerDependencies: + - supports-color + optional: true + solid-devtools@0.30.1(solid-js@1.9.5)(vite@5.4.14(@types/node@22.13.11)(lightningcss@1.29.1)): dependencies: '@babel/core': 7.26.10 @@ -11445,6 +11686,33 @@ snapshots: dependencies: tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2)) + tailwindcss@3.4.17(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.3 + postcss-import: 15.1.0(postcss@8.5.3) + postcss-js: 4.0.1(postcss@8.5.3) + postcss-load-config: 4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4)) + postcss-nested: 6.2.0(postcss@8.5.3) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.11)(typescript@5.8.2)): dependencies: '@alloc/quick-lru': 5.2.0 @@ -11541,6 +11809,10 @@ snapshots: trough@2.2.0: {} + ts-api-utils@2.1.0(typescript@4.9.4): + dependencies: + typescript: 4.9.4 + ts-api-utils@2.1.0(typescript@5.8.2): dependencies: typescript: 5.8.2 @@ -11598,6 +11870,10 @@ snapshots: ts-poet: 6.11.0 ts-proto-descriptors: 2.0.0 + tsconfck@3.1.5(typescript@4.9.4): + optionalDependencies: + typescript: 4.9.4 + tsconfck@3.1.5(typescript@5.8.2): optionalDependencies: typescript: 5.8.2 @@ -11627,6 +11903,16 @@ snapshots: dependencies: semver: 7.7.1 + typescript-eslint@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4): + dependencies: + '@typescript-eslint/eslint-plugin': 8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4))(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4) + '@typescript-eslint/parser': 8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4) + '@typescript-eslint/utils': 8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@4.9.4) + eslint: 9.23.0(jiti@2.4.2) + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + typescript-eslint@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2): dependencies: '@typescript-eslint/eslint-plugin': 8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) @@ -11831,6 +12117,21 @@ snapshots: rollup: 2.79.2 vite: 6.2.2(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0) + vite-plugin-solid@2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.2.2(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)): + dependencies: + '@babel/core': 7.26.10 + '@types/babel__core': 7.20.5 + babel-preset-solid: 1.9.5(@babel/core@7.26.10) + merge-anything: 5.1.7 + solid-js: 1.9.5 + solid-refresh: 0.6.3(solid-js@1.9.5) + vite: 6.2.2(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0) + vitefu: 1.0.6(vite@6.2.2(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)) + optionalDependencies: + '@testing-library/jest-dom': 6.6.3 + transitivePeerDependencies: + - supports-color + vite-plugin-solid@2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.2.2(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)): dependencies: '@babel/core': 7.26.10 @@ -11857,6 +12158,17 @@ snapshots: - supports-color - typescript + vite@5.4.14(@types/node@16.18.126)(lightningcss@1.29.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.36.0 + optionalDependencies: + '@types/node': 16.18.126 + fsevents: 2.3.3 + lightningcss: 1.29.1 + optional: true + vite@5.4.14(@types/node@22.13.11)(lightningcss@1.29.1): dependencies: esbuild: 0.21.5 @@ -11868,6 +12180,18 @@ snapshots: lightningcss: 1.29.1 optional: true + vite@6.2.2(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0): + dependencies: + esbuild: 0.25.1 + postcss: 8.5.3 + rollup: 4.36.0 + optionalDependencies: + '@types/node': 16.18.126 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.29.1 + yaml: 2.7.0 + vite@6.2.2(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0): dependencies: esbuild: 0.25.1 @@ -11880,6 +12204,10 @@ snapshots: lightningcss: 1.29.1 yaml: 2.7.0 + vitefu@1.0.6(vite@6.2.2(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)): + optionalDependencies: + vite: 6.2.2(@types/node@16.18.126)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0) + vitefu@1.0.6(vite@6.2.2(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)): optionalDependencies: vite: 6.2.2(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0) @@ -12173,6 +12501,11 @@ snapshots: dependencies: zod: 3.24.2 + zod-to-ts@1.2.0(typescript@4.9.4)(zod@3.24.2): + dependencies: + typescript: 4.9.4 + zod: 3.24.2 + zod-to-ts@1.2.0(typescript@5.8.2)(zod@3.24.2): dependencies: typescript: 5.8.2 diff --git a/trailbase-core/bindings/ColumnOption.ts b/trailbase-core/bindings/ColumnOption.ts index eaf7a670..1e5d5dbd 100644 --- a/trailbase-core/bindings/ColumnOption.ts +++ b/trailbase-core/bindings/ColumnOption.ts @@ -1,5 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConflictResolution } from "./ConflictResolution"; import type { GeneratedExpressionMode } from "./GeneratedExpressionMode"; import type { ReferentialAction } from "./ReferentialAction"; -export type ColumnOption = "Null" | "NotNull" | { "Default": string } | { "Unique": { is_primary: boolean, } } | { "ForeignKey": { foreign_table: string, referred_columns: Array, on_delete: ReferentialAction | null, on_update: ReferentialAction | null, } } | { "Check": string } | { "OnUpdate": string } | { "Generated": { expr: string, mode: GeneratedExpressionMode | null, } }; +export type ColumnOption = "Null" | "NotNull" | { "Default": string } | { "Unique": { is_primary: boolean, conflict_clause: ConflictResolution | null, } } | { "ForeignKey": { foreign_table: string, referred_columns: Array, on_delete: ReferentialAction | null, on_update: ReferentialAction | null, } } | { "Check": string } | { "OnUpdate": string } | { "Generated": { expr: string, mode: GeneratedExpressionMode | null, } }; diff --git a/trailbase-core/bindings/ConflictResolution.ts b/trailbase-core/bindings/ConflictResolution.ts new file mode 100644 index 00000000..58ea6fd6 --- /dev/null +++ b/trailbase-core/bindings/ConflictResolution.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Conflict resolution types + */ +export type ConflictResolution = "Rollback" | "Abort" | "Fail" | "Ignore" | "Replace"; diff --git a/trailbase-core/bindings/UniqueConstraint.ts b/trailbase-core/bindings/UniqueConstraint.ts index d8d9dee3..edbb0d19 100644 --- a/trailbase-core/bindings/UniqueConstraint.ts +++ b/trailbase-core/bindings/UniqueConstraint.ts @@ -1,8 +1,11 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConflictResolution } from "./ConflictResolution"; export type UniqueConstraint = { name: string | null, /** * Identifiers of the columns that are unique. - * TODO: Should be indexed/ordered column. + * + * TODO: Should be indexed/ordered column, e.g. ASC/DESC: + * https://www.sqlite.org/syntax/indexed-column.html */ -columns: Array, }; +columns: Array, conflict_clause: ConflictResolution | null, }; diff --git a/trailbase-core/js/admin/src/components/tables/CreateAlterColumnForm.tsx b/trailbase-core/js/admin/src/components/tables/CreateAlterColumnForm.tsx index e064d24b..873616b6 100644 --- a/trailbase-core/js/admin/src/components/tables/CreateAlterColumnForm.tsx +++ b/trailbase-core/js/admin/src/components/tables/CreateAlterColumnForm.tsx @@ -415,7 +415,9 @@ function ColumnOptionsFields(props: { props.onChange( setUnique( props.value, - value ? { is_primary: false } : undefined, + value + ? { is_primary: false, conflict_clause: null } + : undefined, ), ); }} @@ -724,7 +726,7 @@ export const primaryKeyPresets: [string, (colName: string) => Preset][] = [ return { data_type: "Blob", options: [ - { Unique: { is_primary: true } }, + { Unique: { is_primary: true, conflict_clause: null } }, { Check: `is_uuid_v7(${colName})` }, { Default: "(uuid_v7())" }, "NotNull", @@ -737,7 +739,10 @@ export const primaryKeyPresets: [string, (colName: string) => Preset][] = [ (_colName: string) => { return { data_type: "Integer", - options: [{ Unique: { is_primary: true } }, "NotNull"], + options: [ + { Unique: { is_primary: true, conflict_clause: null } }, + "NotNull", + ], }; }, ], diff --git a/trailbase-core/js/admin/src/lib/schema.ts b/trailbase-core/js/admin/src/lib/schema.ts index e0088160..664f5b38 100644 --- a/trailbase-core/js/admin/src/lib/schema.ts +++ b/trailbase-core/js/admin/src/lib/schema.ts @@ -1,6 +1,7 @@ import type { Column } from "@bindings/Column"; import type { ColumnOption } from "@bindings/ColumnOption"; import type { ReferentialAction } from "@bindings/ReferentialAction"; +import type { ConflictResolution } from "@bindings/ConflictResolution"; import type { Table } from "@bindings/Table"; import type { View } from "@bindings/View"; @@ -116,7 +117,10 @@ export function setForeignKey( return newOpts; } -export type Unique = { is_primary: boolean }; +export type Unique = { + is_primary: boolean; + conflict_clause: ConflictResolution | null; +}; export function getUnique(options: ColumnOption[]): Unique | undefined { return options.reduce((acc, cur: ColumnOption) => { diff --git a/trailbase-core/src/admin/rows/delete_rows.rs b/trailbase-core/src/admin/rows/delete_rows.rs index 3c865393..1872b1c6 100644 --- a/trailbase-core/src/admin/rows/delete_rows.rs +++ b/trailbase-core/src/admin/rows/delete_rows.rs @@ -133,7 +133,10 @@ mod tests { name: pk_col.clone(), data_type: ColumnDataType::Blob, options: vec![ - ColumnOption::Unique { is_primary: true }, + ColumnOption::Unique { + is_primary: true, + conflict_clause: None, + }, ColumnOption::Check(format!("(is_uuid_v7({pk_col}))")), ColumnOption::Default("(uuid_v7())".to_string()), ], diff --git a/trailbase-core/src/admin/table/alter_table.rs b/trailbase-core/src/admin/table/alter_table.rs index da453c6e..9280e94d 100644 --- a/trailbase-core/src/admin/table/alter_table.rs +++ b/trailbase-core/src/admin/table/alter_table.rs @@ -154,7 +154,10 @@ mod tests { columns: vec![Column { name: pk_col.clone(), data_type: ColumnDataType::Blob, - options: vec![ColumnOption::Unique { is_primary: true }], + options: vec![ColumnOption::Unique { + is_primary: true, + conflict_clause: None, + }], }], foreign_keys: vec![], unique: vec![], diff --git a/trailbase-core/src/records/json_to_sql.rs b/trailbase-core/src/records/json_to_sql.rs index 8650f729..2beda882 100644 --- a/trailbase-core/src/records/json_to_sql.rs +++ b/trailbase-core/src/records/json_to_sql.rs @@ -662,7 +662,7 @@ impl InsertQueryBuilder { let query = if !column_names.is_empty() { format!( r#"INSERT {conflict_clause} INTO "{table_name}" ({col_names}) VALUES ({placeholders}) {return_fragment}"#, - col_names = Self::build_col_names(column_names), + col_names = crate::schema::quote(column_names), placeholders = params.placeholders(), ) } else { @@ -673,21 +673,6 @@ impl InsertQueryBuilder { return Ok((query, params.named_params, params.files)); } - #[inline] - fn build_col_names(column_names: &[String]) -> String { - let mut s = String::new(); - for (i, name) in column_names.iter().enumerate() { - if i > 0 { - s.push_str(", \""); - } else { - s.push('"'); - } - s.push_str(name); - s.push('"'); - } - return s; - } - #[inline] fn conflict_resolution_clause(config: ConflictResolutionStrategy) -> &'static str { type C = ConflictResolutionStrategy; diff --git a/trailbase-core/src/schema.rs b/trailbase-core/src/schema.rs index 72959ec7..7965e136 100644 --- a/trailbase-core/src/schema.rs +++ b/trailbase-core/src/schema.rs @@ -2,8 +2,8 @@ use itertools::Itertools; use log::*; use serde::{Deserialize, Serialize}; use sqlite3_parser::ast::{ - fmt::ToTokens, ColumnDefinition, CreateTableBody, Expr, FromClause, Literal, Name, SelectTable, - Stmt, TableConstraint, TableOptions, + fmt::ToTokens, ColumnDefinition, CreateTableBody, Expr, FromClause, Literal, Name, QualifiedName, + SelectTable, Stmt, TableConstraint, TableOptions, }; use std::collections::HashMap; use thiserror::Error; @@ -36,33 +36,38 @@ pub struct ForeignKey { pub columns: Vec, pub foreign_table: String, pub referred_columns: Vec, + + // Only "ON DELETE" and "ON UPDATE" are supported in foreign key clause, i.e. no "ON INSERT": + // https://www.sqlite.org/syntax/foreign-key-clause.html pub on_delete: Option, pub on_update: Option, + // TODO: Missing DEFERRABLE. } impl ForeignKey { fn to_fragment(&self) -> String { - return format!( - "{name} FOREIGN KEY ({cols}) REFERENCES '{foreign_table}'{ref_col} {on_delete} {on_update}", - name = self - .name - .as_ref() - .map_or_else(|| "".to_string(), |n| format!("CONSTRAINT {n}")), - cols = quote(&self.columns), - foreign_table = self.foreign_table, - ref_col = match self.referred_columns.len() { - 0 => "".to_string(), - _ => format!("({})", quote(&self.referred_columns)), - }, - on_delete = self.on_delete.as_ref().map_or_else( - || "".to_string(), - |action| format!("ON DELETE {}", action.to_fragment()) - ), - on_update = self.on_update.as_ref().map_or_else( - || "".to_string(), - |action| format!("ON UPDATE {}", action.to_fragment()) - ), + let cols = quote(&self.columns); + let foreign_table = &self.foreign_table; + let ref_col = match self.referred_columns.len() { + 0 => "".to_string(), + _ => format!("({})", quote(&self.referred_columns)), + }; + + let on_delete = self.on_delete.as_ref().map_or_else( + || "".to_string(), + |action| format!("ON DELETE {}", action.to_fragment()), ); + let on_update = self.on_update.as_ref().map_or_else( + || "".to_string(), + |action| format!("ON UPDATE {}", action.to_fragment()), + ); + + return if let Some(ref name) = self.name { + format!( + "CONSTRAINT '{name}' FOREIGN KEY ({cols}) REFERENCES '{foreign_table}'{ref_col} {on_delete} {on_update}") + } else { + format!("FOREIGN KEY ({cols}) REFERENCES '{foreign_table}'{ref_col} {on_delete} {on_update}") + }; } } @@ -71,22 +76,31 @@ impl ForeignKey { #[derive(Clone, Debug, Serialize, Deserialize, TS, PartialEq)] pub struct UniqueConstraint { pub name: Option, + /// Identifiers of the columns that are unique. - /// TODO: Should be indexed/ordered column. + /// + /// TODO: Should be indexed/ordered column, e.g. ASC/DESC: + /// https://www.sqlite.org/syntax/indexed-column.html pub columns: Vec, + + pub conflict_clause: Option, } impl UniqueConstraint { fn to_fragment(&self) -> String { - return format!( - "{name}UNIQUE ({cols}) {conflict_clause}", - name = self - .name - .as_ref() - .map_or_else(|| "".to_string(), |n| format!("CONSTRAINT '{n}' ")), - cols = quote(&self.columns), - conflict_clause = "", - ); + let cols = quote(&self.columns); + + return match (self.name.as_ref(), &self.conflict_clause.as_ref()) { + (Some(name), Some(resolution)) => format!( + "CONSTRAINT '{name}' UNIQUE ({cols}) ON CONFLICT {}", + resolution.to_fragment() + ), + (Some(name), None) => format!("CONSTRAINT '{name}' UNIQUE ({cols})"), + (None, Some(resolution)) => { + format!("UNIQUE ({cols}) ON CONFLICT {}", resolution.to_fragment()) + } + (None, None) => format!("UNIQUE ({cols})"), + }; } } @@ -97,6 +111,47 @@ pub struct ColumnOrder { pub nulls_first: Option, } +/// Conflict resolution types +#[derive(Clone, Debug, Serialize, Deserialize, TS, PartialEq)] +pub enum ConflictResolution { + /// `ROLLBACK` + Rollback, + /// `ABORT` + Abort, // default + /// `FAIL` + Fail, + /// `IGNORE` + Ignore, + /// `REPLACE` + Replace, +} + +impl From for ConflictResolution { + fn from(res: sqlite3_parser::ast::ResolveType) -> Self { + use sqlite3_parser::ast::ResolveType; + match res { + ResolveType::Rollback => ConflictResolution::Rollback, + ResolveType::Abort => ConflictResolution::Abort, + ResolveType::Fail => ConflictResolution::Fail, + ResolveType::Ignore => ConflictResolution::Ignore, + ResolveType::Replace => ConflictResolution::Replace, + } + } +} + +impl ConflictResolution { + // https://www.sqlite.org/syntax/conflict-clause.html + fn to_fragment(&self) -> &'static str { + return match self { + Self::Rollback => "ROLLBACK", + Self::Abort => "ABORT", + Self::Fail => "FAIL", + Self::Ignore => "IGNORE", + Self::Replace => "REPLACE", + }; + } +} + #[derive(Clone, Debug, Serialize, Deserialize, TS, PartialEq)] pub enum ReferentialAction { Restrict, @@ -146,6 +201,8 @@ pub enum ColumnOption { // NOTE: Unique { is_primary: true} means PrimaryKey. Unique { is_primary: bool, + conflict_clause: Option, + // TODO: Missing ASC/DESC & AUTOINCREMENT for PK. }, ForeignKey { foreign_table: String, @@ -167,13 +224,15 @@ impl ColumnOption { Self::Null => "NULL".to_string(), Self::NotNull => "NOT NULL".to_string(), Self::Default(v) => format!("DEFAULT {v}"), - Self::Unique { is_primary } => { - if *is_primary { - "PRIMARY KEY".to_string() - } else { - "UNIQUE".to_string() - } - } + Self::Unique { + is_primary, + conflict_clause, + } => match (*is_primary, conflict_clause.as_ref()) { + (true, Some(res)) => format!("PRIMARY KEY ON CONFLICT {}", res.to_fragment()), + (true, None) => "PRIMARY KEY".to_string(), + (false, Some(res)) => format!("UNIQUE ON CONFLICT {}", res.to_fragment()), + (false, None) => "UNIQUE".to_string(), + }, Self::ForeignKey { foreign_table, referred_columns, @@ -185,7 +244,6 @@ impl ColumnOption { ref_col = match referred_columns.len() { 0 => "".to_string(), _ => format!("({})", quote(referred_columns)), - //_ => format!("({})", referred_columns.join(", ")), }, on_delete = on_delete.as_ref().map_or_else( || "".to_string(), @@ -349,10 +407,9 @@ impl Column { impl Column { pub fn is_primary(&self) -> bool { - self - .options - .iter() - .any(|opt| matches!(opt, ColumnOption::Unique { is_primary } if *is_primary )) + self.options.iter().any( + |opt| matches!(opt, ColumnOption::Unique { is_primary, conflict_clause: _ } if *is_primary ), + ) } } @@ -514,10 +571,16 @@ impl TryFrom for Table { TableConstraint::ForeignKey { columns, clause, - deref_clause: _, + deref_clause, } => { + if let Some(ref clause) = deref_clause { + // TOOD: Parse DEFERRABLE. + log::warn!("Unsupported DEFERRABLE in FK clause: {clause:?}"); + } + let mut on_delete: Option = None; let mut on_update: Option = None; + for arg in &clause.args { use sqlite3_parser::ast::RefArg; @@ -528,21 +591,31 @@ impl TryFrom for Table { RefArg::OnUpdate(action) => { on_update = Some((*action).into()); } - _ => {} + RefArg::OnInsert(action) => { + log::error!("Unexpected ON INSERT in FK clause: {action:?}"); + } + RefArg::Match(name) => { + // SQL supports FK MATCH clause, which is *not* supported by sqlite: + // https://www.sqlite.org/foreignkeys.html#fk_unsupported + log::warn!("Unsupported MATCH in FK clause: {name:?}"); + } } } Some(ForeignKey { - name: constraint.name.as_ref().map(|name| unquote(name.clone())), - foreign_table: unquote(clause.tbl_name.clone()), + name: constraint + .name + .as_ref() + .map(|name| unquote_name(name.clone())), + foreign_table: unquote_name(clause.tbl_name.clone()), columns: columns .iter() - .map(|c| unquote(c.col_name.clone())) + .map(|c| unquote_name(c.col_name.clone())) .collect(), referred_columns: clause.columns.as_ref().map_or_else(Vec::new, |columns| { columns .iter() - .map(|c| unquote(c.col_name.clone())) + .map(|c| unquote_name(c.col_name.clone())) .collect() }), on_update, @@ -558,13 +631,17 @@ impl TryFrom for Table { .filter_map(|constraint| match &constraint.constraint { TableConstraint::Unique { columns, - conflict_clause: _, + conflict_clause, } => Some(UniqueConstraint { - name: constraint.name.as_ref().map(|name| unquote(name.clone())), + name: constraint + .name + .as_ref() + .map(|name| unquote_name(name.clone())), columns: columns .iter() .map(|c| unquote_expr(c.expr.clone())) .collect(), + conflict_clause: conflict_clause.map(|c| c.into()), }), _ => None, }) @@ -584,7 +661,7 @@ impl TryFrom for Table { } = def; assert_eq!(name, col_name); - let name = unquote(col_name); + let name = unquote_name(col_name); assert!(!name.is_empty()); let data_type: ColumnDataType = match col_type { @@ -605,21 +682,8 @@ impl TryFrom for Table { }) .collect(); - // WARN: SQLite escaping is weird, altering a table adds double quote escaping and - // sqlite3_parser unlike sqlparser, doesn't parse out the escaping. - // - // sqlite> CREATE TABLE foo (x text); - // sqlite> SELECT sql FROM main.sqlite_schema; - // CREATE TABLE foo (x text) - // sqlite> ALTER TABLE foo RENAME TO bar - // sqlite> SELECT sql FROM main.sqlite_schema; - // CREATE TABLE "bar" (x text) - // - // TODO: factor out QualifiedNamed conversion. - let table_name = unquote(tbl_name.name); - Ok(Table { - name: table_name, + name: unquote_qualified(tbl_name), strict: options.contains(TableOptions::STRICT), columns, foreign_keys, @@ -633,7 +697,7 @@ impl TryFrom for Table { args: _args, .. } => Ok(Table { - name: unquote(tbl_name.name), + name: unquote_qualified(tbl_name), strict: false, columns: vec![], foreign_keys: vec![], @@ -660,9 +724,17 @@ impl From for ColumnOption { return match constraint { Constraint::PrimaryKey { - conflict_clause: _, .. - } => ColumnOption::Unique { is_primary: true }, - Constraint::Unique(_) => ColumnOption::Unique { is_primary: false }, + conflict_clause, + order: _, + auto_increment: _, + } => ColumnOption::Unique { + is_primary: true, + conflict_clause: conflict_clause.map(|c| c.into()), + }, + Constraint::Unique(conflict_clause) => ColumnOption::Unique { + is_primary: false, + conflict_clause: conflict_clause.map(|c| c.into()), + }, Constraint::Check(expr) => { // NOTE: This is not using unquote on purpose, since this is not an identifier. ColumnOption::Check(expr.to_string()) @@ -671,8 +743,11 @@ impl From for ColumnOption { let columns = clause.columns.unwrap_or(vec![]); ColumnOption::ForeignKey { - foreign_table: unquote(clause.tbl_name), - referred_columns: columns.into_iter().map(|c| unquote(c.col_name)).collect(), + foreign_table: unquote_name(clause.tbl_name), + referred_columns: columns + .into_iter() + .map(|c| unquote_name(c.col_name)) + .collect(), on_delete: None, on_update: None, } @@ -714,8 +789,8 @@ impl TryFrom for TableIndex { columns, where_clause, } => Ok(TableIndex { - name: unquote(idx_name.name), - table_name: unquote(tbl_name), + name: unquote_name(idx_name.name), + table_name: unquote_name(tbl_name), columns: columns .into_iter() .map(|order_expr| ColumnOrder { @@ -774,7 +849,7 @@ impl View { }; Ok(View { - name: unquote(view_name.name), + name: unquote_qualified(view_name), columns, query: SelectFormatter(*select).to_string(), temporary, @@ -788,15 +863,12 @@ impl View { } } -fn to_entry( - qn: sqlite3_parser::ast::QualifiedName, - alias: Option, -) -> (String, String) { +fn to_entry(qn: QualifiedName, alias: Option) -> (String, String) { return ( alias .and_then(|alias| { if let sqlite3_parser::ast::As::As(name) = alias { - return Some(unquote(name)); + return Some(unquote_name(name)); } None }) @@ -917,7 +989,7 @@ fn try_extract_column_mapping( } } ResultColumn::TableStar(name) => { - let name = unquote(name); + let name = unquote_name(name); let Some(table_name) = table_names.get(&name) else { return Err(SchemaError::Precondition( format!("Missing alias: {name}").into(), @@ -947,7 +1019,7 @@ fn try_extract_column_mapping( let name = alias .and_then(|alias| { if let sqlite3_parser::ast::As::As(name) = alias { - return Some(unquote(name)); + return Some(unquote_name(name)); } None }) @@ -966,8 +1038,8 @@ fn try_extract_column_mapping( }); } Expr::Qualified(qualifier, name) => { - let qualifier = unquote(qualifier); - let col_name = unquote(name); + let qualifier = unquote_name(qualifier); + let col_name = unquote_name(name); let Some(table_name) = table_names.get(&qualifier) else { return Err(SchemaError::Precondition( @@ -985,7 +1057,7 @@ fn try_extract_column_mapping( let name = alias .and_then(|alias| { if let sqlite3_parser::ast::As::As(name) = alias { - return Some(unquote(name)); + return Some(unquote_name(name)); } None }) @@ -1017,7 +1089,7 @@ fn try_extract_column_mapping( let Some(name) = alias.and_then(|alias| { if let sqlite3_parser::ast::As::As(name) = alias { - return Some(unquote(name)); + return Some(unquote_name(name)); } None }) else { @@ -1047,7 +1119,8 @@ fn try_extract_column_mapping( return Ok(Some(mapping)); } -fn quote(column_names: &[String]) -> String { +#[inline] +pub(crate) fn quote(column_names: &[String]) -> String { let mut s = String::new(); for (i, name) in column_names.iter().enumerate() { if i > 0 { @@ -1077,17 +1150,22 @@ fn unquote_string(s: String) -> String { }; } -fn unquote(name: Name) -> String { +fn unquote_name(name: Name) -> String { return unquote_string(name.0); } +fn unquote_qualified(name: QualifiedName) -> String { + // FIXME: unquoting of qualified name. + return unquote_name(name.name); +} + fn unquote_id(id: sqlite3_parser::ast::Id) -> String { return unquote_string(id.0); } fn unquote_expr(expr: Expr) -> String { return match expr { - Expr::Name(n) => unquote(n), + Expr::Name(n) => unquote_name(n), Expr::Id(id) => unquote_id(id), Expr::Literal(Literal::String(s)) => unquote_string(s), x => x.to_string(), @@ -1099,11 +1177,18 @@ mod tests { use super::*; use crate::table_metadata::sqlite3_parse_into_statement; + #[test] + fn test_quote() { + assert_eq!("", quote(&vec![])); + assert_eq!("''", quote(&vec!["".to_string()])); + assert_eq!("'foo', ''", quote(&vec!["foo".to_string(), "".to_string()])); + } + #[test] fn test_unquote() { - assert_eq!(unquote(Name("".to_string())), ""); - assert_eq!(unquote(Name("['``']".to_string())), "'``'"); - assert_eq!(unquote(Name("\"[]\"".to_string())), "[]"); + assert_eq!(unquote_name(Name("".to_string())), ""); + assert_eq!(unquote_name(Name("['``']".to_string())), "'``'"); + assert_eq!(unquote_name(Name("\"[]\"".to_string())), "[]"); } #[test] @@ -1142,7 +1227,7 @@ mod tests { user_id BLOB, email TEXT NOT NULL, email_visibility INTEGER DEFAULT FALSE NOT NULL, - username TEXT, + username TEXT UNIQUE ON CONFLICT ABORT, age INTEGER, double_age INTEGER GENERATED ALWAYS AS (2 * 'age') VIRTUAL, triple_age INTEGER AS (3 * age) STORED, @@ -1150,8 +1235,9 @@ mod tests { [index] TEXT, UNIQUE (email), - UNIQUE ([index]) ON CONFLICT FAIL, - CONSTRAINT optional_tbl_constraint_name CHECK(username != ''), + -- optional constraint name: + CONSTRAINT `unique` UNIQUE ([index]) ON CONFLICT FAIL, + CHECK(username != ''), FOREIGN KEY(user_id) REFERENCES 'table'('index') ON DELETE CASCADE ) STRICT; "# @@ -1243,7 +1329,10 @@ mod tests { name: "user".to_string(), data_type: ColumnDataType::Blob, options: vec![ - ColumnOption::Unique { is_primary: true }, + ColumnOption::Unique { + is_primary: true, + conflict_clause: None, + }, ColumnOption::ForeignKey { foreign_table: "_user".to_string(), referred_columns: vec!["id".to_string()], @@ -1270,7 +1359,10 @@ mod tests { Column { name: "id".to_string(), data_type: ColumnDataType::Blob, - options: vec![ColumnOption::Unique { is_primary: true }], + options: vec![ColumnOption::Unique { + is_primary: true, + conflict_clause: None, + }], }, Column { name: "author".to_string(), diff --git a/trailbase-core/src/table_metadata.rs b/trailbase-core/src/table_metadata.rs index 03575cb3..db070710 100644 --- a/trailbase-core/src/table_metadata.rs +++ b/trailbase-core/src/table_metadata.rs @@ -386,7 +386,7 @@ fn find_user_id_foreign_key_columns(columns: &[Column]) -> Vec { fn find_record_pk_column_index(columns: &[Column], tables: &[Table]) -> Option { let primary_key_col_index = columns.iter().position(|col| { for opt in &col.options { - if let ColumnOption::Unique { is_primary } = opt { + if let ColumnOption::Unique { is_primary, .. } = opt { return *is_primary; } } @@ -435,7 +435,7 @@ fn find_record_pk_column_index(columns: &[Column], tables: &[Table]) -> Option { return Some(index); } - ColumnOption::Unique { is_primary } if *is_primary => { + ColumnOption::Unique { is_primary, .. } if *is_primary => { is_pk = true; } _ => {} @@ -823,7 +823,7 @@ pub(crate) fn build_json_schema_recursive( } } } - ColumnOption::Unique { is_primary } => { + ColumnOption::Unique { is_primary, .. } => { // According to the SQL standard, PRIMARY KEY should always imply NOT NULL. // Unfortunately, due to a bug in some early versions, this is not the case in SQLite. // Unless the column is an INTEGER PRIMARY KEY or the table is a WITHOUT ROWID table or a