diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcb77d5e..a49c000f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,10 @@ jobs: - uses: actions/checkout@v4 - run: npm ci - run: npm test + - name: Install ODBC dependencies + run: | + sudo apt-get update + sudo apt-get install -y unixodbc-dev freetds-dev - name: Set up cargo cache uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - run: cargo fmt --all -- --check @@ -42,21 +46,43 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - database: ["postgres", "mysql", "mssql"] + include: + - database: postgres + container: postgres + db_url: "postgres://root:Password123!@127.0.0.1/sqlpage" + - database: mysql + container: mysql + db_url: "mysql://root:Password123!@127.0.0.1/sqlpage" + - database: mssql + container: mssql + db_url: "mssql://root:Password123!@127.0.0.1/sqlpage" + - database: odbc + container: postgres + db_url: "Driver={PostgreSQL Unicode};Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!" + setup_odbc: true steps: - uses: actions/checkout@v4 - name: Set up cargo cache uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + - name: Install ODBC dependencies + run: | + sudo apt-get update + sudo apt-get install -y unixodbc-dev freetds-dev + - name: Setup ODBC for testing + if: matrix.setup_odbc + run: | + # Install PostgreSQL ODBC driver (automatically registers the driver) + sudo apt-get install -y odbc-postgresql - name: Start database container - run: docker compose up --wait ${{ matrix.database }} + run: docker compose up --wait ${{ matrix.container }} - name: Show container logs if: failure() - run: docker compose logs ${{ matrix.database }} + run: docker compose logs ${{ matrix.container }} - name: Run tests against ${{ matrix.database }} timeout-minutes: 5 run: cargo test env: - DATABASE_URL: ${{ matrix.database }}://root:Password123!@127.0.0.1/sqlpage + DATABASE_URL: ${{ matrix.db_url }} RUST_BACKTRACE: 1 RUST_LOG: sqlpage=debug @@ -73,7 +99,7 @@ jobs: RUST_BACKTRACE: 1 - name: Upload Windows binary uses: actions/upload-artifact@v4 - with: + with: name: sqlpage-windows-debug path: "target/debug/sqlpage.exe" diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 72fb4cd6..7865ab24 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -20,10 +20,8 @@ jobs: node-version: lts/* cache: 'npm' cache-dependency-path: ./tests/end-to-end/package-lock.json - - name: Install dependencies - run: | - npm ci - npx playwright install --with-deps chromium + - run: sudo apt-get update && sudo apt-get install -y unixodbc-dev + - run: npm ci && npx playwright install --with-deps chromium - name: build sqlpage run: cargo build working-directory: ./examples/official-site diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7d413177..97dcf3ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,6 +83,10 @@ jobs: container: quay.io/pypa/manylinux_2_28_x86_64 steps: - uses: actions/checkout@v4 + - name: Install ODBC dependencies + run: | + yum update -y + yum install -y unixODBC-devel freetds-devel - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: diff --git a/Cargo.lock b/Cargo.lock index 8c519785..08cd460f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags", + "bitflags 2.9.4", "bytes", "futures-core", "futures-sink", @@ -31,7 +31,7 @@ dependencies = [ "actix-tls", "actix-utils", "base64 0.22.1", - "bitflags", + "bitflags 2.9.4", "brotli 8.0.2", "bytes", "bytestring", @@ -100,7 +100,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e11eb847f49a700678ea2fa73daeb3208061afa2b9d1a8527c03390f4c4a1c6b" dependencies = [ - "darling", + "darling 0.20.11", "parse-size", "proc-macro2", "quote", @@ -265,9 +265,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -327,6 +327,33 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.9.4", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -388,9 +415,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "argon2" @@ -487,9 +514,9 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 1.1.2", "slab", - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] [[package]] @@ -628,9 +655,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" dependencies = [ "aws-lc-sys", "zeroize", @@ -638,22 +665,23 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd" +checksum = "ee74396bee4da70c2e27cf94762714c911725efe69d9e2672f998512a67a4ce4" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", + "libloading", ] [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -661,7 +689,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -700,7 +728,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags", + "bitflags 2.9.4", "cexpr", "clang-sys", "itertools 0.13.0", @@ -714,6 +742,12 @@ dependencies = [ "syn", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.4" @@ -741,6 +775,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + [[package]] name = "blocking" version = "1.6.2" @@ -823,11 +866,25 @@ dependencies = [ "bytes", ] +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.9.4", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + [[package]] name = "cc" -version = "1.2.38" +version = "1.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ "find-msvc-tools", "jobserver", @@ -835,6 +892,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -850,6 +913,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.42" @@ -861,7 +930,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -877,9 +946,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", "clap_derive", @@ -887,9 +956,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -930,6 +999,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -941,9 +1020,9 @@ dependencies = [ [[package]] name = "config" -version = "0.15.16" +version = "0.15.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef036f0ecf99baef11555578630e2cca559909b4c50822dbba828c252d21c49" +checksum = "680d3ac2fe066c43300ec831c978871e50113a708d58ab13d231bd92deca5adb" dependencies = [ "async-trait", "convert_case 0.6.0", @@ -1011,6 +1090,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -1027,6 +1116,30 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + [[package]] name = "core2" version = "0.4.0" @@ -1137,6 +1250,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1170,8 +1289,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -1188,13 +1317,38 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", "syn", ] @@ -1238,12 +1392,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -1261,7 +1415,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", "syn", @@ -1341,9 +1495,15 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + [[package]] name = "displaydoc" version = "0.2.5" @@ -1355,6 +1515,15 @@ dependencies = [ "syn", ] +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + [[package]] name = "dlv-list" version = "0.5.2" @@ -1370,6 +1539,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + [[package]] name = "dunce" version = "1.0.5" @@ -1503,7 +1678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] [[package]] @@ -1588,6 +1763,33 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1765,9 +1967,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" @@ -2156,7 +2358,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags", + "bitflags 2.9.4", "cfg-if", "libc", ] @@ -2215,6 +2417,28 @@ dependencies = [ "syn", ] +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -2227,9 +2451,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.80" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -2312,9 +2536,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libflate" @@ -2347,7 +2571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-targets 0.53.4", ] [[package]] @@ -2362,9 +2586,9 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags", + "bitflags 2.9.4", "libc", - "redox_syscall", + "redox_syscall 0.5.17", ] [[package]] @@ -2378,6 +2602,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -2445,9 +2675,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -2492,6 +2722,36 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.4", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + [[package]] name = "nom" version = "7.1.3" @@ -2580,6 +2840,28 @@ dependencies = [ "libm", ] +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "oauth2" version = "5.0.0" @@ -2599,15 +2881,238 @@ dependencies = [ "url", ] +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.9.4", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.9.4", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.9.4", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.4", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.4", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.4", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.9.4", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.9.4", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] +[[package]] +name = "odbc-api" +version = "19.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "564dc5b7c737a3821f60e3b9608320098a02203ab4a38be09bcd759225329554" +dependencies = [ + "atoi", + "log", + "odbc-sys", + "thiserror 2.0.16", + "widestring", + "winit", +] + +[[package]] +name = "odbc-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb069b57ebbad5234fb7197af7ee0c40daceb3946a86fa8d3f7a38393bf2770" + [[package]] name = "oid-registry" version = "0.7.1" @@ -2672,6 +3177,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + [[package]] name = "ordered-float" version = "2.10.1" @@ -2739,7 +3253,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.17", "smallvec", "windows-targets 0.52.6", ] @@ -2922,8 +3436,8 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", - "windows-sys 0.61.0", + "rustix 1.1.2", + "windows-sys 0.61.1", ] [[package]] @@ -2984,6 +3498,15 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -3067,6 +3590,12 @@ dependencies = [ "getrandom 0.3.3", ] +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "rcgen" version = "0.13.2" @@ -3080,13 +3609,22 @@ dependencies = [ "yasna", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags", + "bitflags 2.9.4", ] [[package]] @@ -3122,9 +3660,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -3134,9 +3672,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -3192,7 +3730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags", + "bitflags 2.9.4", "serde", "serde_derive", ] @@ -3257,17 +3795,30 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags", + "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys", - "windows-sys 0.61.0", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.1", ] [[package]] @@ -3365,13 +3916,22 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] [[package]] @@ -3420,12 +3980,12 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" +checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a" dependencies = [ - "bitflags", - "core-foundation", + "bitflags 2.9.4", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3449,9 +4009,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.225" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" dependencies = [ "serde_core", "serde_derive", @@ -3481,18 +4041,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" dependencies = [ "proc-macro2", "quote", @@ -3556,9 +4116,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" dependencies = [ "base64 0.22.1", "chrono", @@ -3576,11 +4136,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", "syn", @@ -3645,6 +4205,15 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + [[package]] name = "socket2" version = "0.5.10" @@ -3686,7 +4255,7 @@ dependencies = [ [[package]] name = "sqlpage" -version = "0.37.1" +version = "0.38.0-beta.1" dependencies = [ "actix-multipart", "actix-rt", @@ -3753,14 +4322,14 @@ dependencies = [ [[package]] name = "sqlx-core-oldapi" -version = "0.6.48" +version = "0.6.49-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed037e8dea82b291adcd9a21aba47949a52030eeb9b5b9647b826f4057e45078" +checksum = "24c2973ebc05979c3b7a4320295f84a415be97aebb7910647800663b7b2a5f04" dependencies = [ "ahash", "atoi", "base64 0.22.1", - "bitflags", + "bitflags 2.9.4", "byteorder", "bytes", "chrono", @@ -3790,6 +4359,7 @@ dependencies = [ "md-5", "memchr", "num-bigint", + "odbc-api", "once_cell", "paste", "percent-encoding", @@ -3816,9 +4386,9 @@ dependencies = [ [[package]] name = "sqlx-macros-oldapi" -version = "0.6.48" +version = "0.6.49-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e189524d405a2276f662a651b8ce707e337915eda7f5b70fba0ca1f0186d68" +checksum = "3c196a3ccd537626101867252eea16e4e8001807cb1bd44bae3aa0f1100e7581" dependencies = [ "dotenvy", "either", @@ -3836,9 +4406,9 @@ dependencies = [ [[package]] name = "sqlx-oldapi" -version = "0.6.48" +version = "0.6.49-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c52a7c0d985b7f74ef2fdb9b7a49ad178b2e106baa8b9e6407fab2462bd300" +checksum = "ff2377a4a219c316df80cafe895cb392880c67fb1603f2ea59ae8fcf7e01edfc" dependencies = [ "sqlx-core-oldapi", "sqlx-macros-oldapi", @@ -3846,9 +4416,9 @@ dependencies = [ [[package]] name = "sqlx-rt-oldapi" -version = "0.6.48" +version = "0.6.49-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718710e5feeb9fb51739d101c24c7955421a201941bae0ca891df315779a5bdf" +checksum = "e1c63c39d6f4ab613de85eb7598b93cde3b8212df8635d627f8bb9a70d3cc6b0" dependencies = [ "once_cell", "tokio", @@ -3908,15 +4478,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix", - "windows-sys 0.61.0", + "rustix 1.1.2", + "windows-sys 0.61.1", ] [[package]] @@ -4112,6 +4682,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +dependencies = [ + "indexmap 2.11.4", + "toml_datetime", + "toml_parser", + "winnow", +] + [[package]] name = "toml_parser" version = "1.0.3" @@ -4307,6 +4889,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -4348,9 +4940,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -4361,9 +4953,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -4373,11 +4965,24 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4385,9 +4990,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -4398,18 +5003,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.80" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -4444,24 +5059,39 @@ dependencies = [ "web-sys", ] +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.1", +] + [[package]] name = "windows-core" -version = "0.62.0" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.0", + "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" dependencies = [ "proc-macro2", "quote", @@ -4470,21 +5100,15 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.0" @@ -4497,7 +5121,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -4506,7 +5130,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" dependencies = [ - "windows-link 0.2.0", + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -4533,16 +5166,31 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.4", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows-link 0.2.0", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -4563,11 +5211,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" dependencies = [ - "windows-link 0.1.3", + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -4578,6 +5226,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4590,6 +5244,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4602,6 +5262,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4626,6 +5292,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4638,6 +5310,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4650,6 +5328,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4662,6 +5346,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4674,6 +5364,46 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winit" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +dependencies = [ + "android-activity", + "atomic-waker", + "bitflags 2.9.4", + "block2", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "xkbcommon-dl", +] + [[package]] name = "winnow" version = "0.7.13" @@ -4712,6 +5442,25 @@ dependencies = [ "time", ] +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.9.4", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "yaml-rust2" version = "0.10.4" diff --git a/Cargo.toml b/Cargo.toml index 6f0c89c0..c026173a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sqlpage" -version = "0.37.1" +version = "0.38.0-beta.1" edition = "2021" description = "Build data user interfaces entirely in SQL. A web server that takes .sql files and formats the query result using pre-made configurable professional-looking components." keywords = ["web", "sql", "framework"] @@ -8,12 +8,7 @@ license = "MIT" homepage = "https://sql-page.com/" repository = "https://github.com/sqlpage/SQLPage" documentation = "https://docs.rs/sqlpage" -include = [ - "/src", - "/README.md", - "/build.rs", - "/sqlpage", -] +include = ["/src", "/README.md", "/build.rs", "/sqlpage"] [profile.superoptimized] inherits = "release" @@ -23,7 +18,7 @@ panic = "abort" codegen-units = 2 [dependencies] -sqlx = { package = "sqlx-oldapi", version = "0.6.48", default-features = false, features = [ +sqlx = { package = "sqlx-oldapi", version = "0.6.49-beta.3", default-features = false, features = [ "any", "runtime-tokio-rustls", "migrate", @@ -31,6 +26,7 @@ sqlx = { package = "sqlx-oldapi", version = "0.6.48", default-features = false, "postgres", "mysql", "mssql", + "odbc", "chrono", "json", "uuid", @@ -49,7 +45,10 @@ anyhow = "1" serde = "1" serde_json = { version = "1.0.82", features = ["preserve_order", "raw_value"] } lambda-web = { version = "0.2.1", features = ["actix4"], optional = true } -sqlparser = { version = "0.59.0", default-features = false, features = ["std", "visitor",] } +sqlparser = { version = "0.59.0", default-features = false, features = [ + "std", + "visitor", +] } async-stream = "0.3" async-trait = "0.1.61" async-recursion = "1.0.0" diff --git a/Dockerfile b/Dockerfile index 625e054d..8e930dec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,37 @@ -FROM --platform=$BUILDPLATFORM rust:1.87-slim AS builder +FROM --platform=$BUILDPLATFORM rust:1.90-slim AS builder WORKDIR /usr/src/sqlpage ARG TARGETARCH ARG BUILDARCH RUN apt-get update && \ + mkdir -p /opt/sqlpage-libs && \ if [ "$TARGETARCH" = "$BUILDARCH" ]; then \ rustup target list --installed > TARGET && \ echo gcc > LINKER && \ - apt-get install -y gcc libgcc-s1 cmake && \ - cp /lib/*/libgcc_s.so.1 .; \ + apt-get install -y gcc libgcc-s1 cmake unixodbc-dev libodbc2 libltdl7 && \ + LIBDIR="/lib/*"; \ + USRLIBDIR="/usr/lib/*"; \ elif [ "$TARGETARCH" = "arm64" ]; then \ echo aarch64-unknown-linux-gnu > TARGET && \ echo aarch64-linux-gnu-gcc > LINKER && \ - apt-get install -y gcc-aarch64-linux-gnu libgcc-s1-arm64-cross && \ - cp /usr/aarch64-linux-gnu/lib/libgcc_s.so.1 .; \ + dpkg --add-architecture arm64 && apt-get update && \ + apt-get install -y gcc-aarch64-linux-gnu libgcc-s1-arm64-cross unixodbc-dev:arm64 libodbc2:arm64 libltdl7:arm64 && \ + LIBDIR="/lib/aarch64-linux-gnu"; \ + USRLIBDIR="/usr/lib/aarch64-linux-gnu"; \ elif [ "$TARGETARCH" = "arm" ]; then \ echo armv7-unknown-linux-gnueabihf > TARGET && \ echo arm-linux-gnueabihf-gcc > LINKER && \ - apt-get install -y gcc-arm-linux-gnueabihf libgcc-s1-armhf-cross cmake libclang1 && \ + dpkg --add-architecture armhf && apt-get update && \ + apt-get install -y gcc-arm-linux-gnueabihf libgcc-s1-armhf-cross cmake libclang1 clang unixodbc-dev:armhf libodbc2:armhf libltdl7:armhf && \ cargo install --force --locked bindgen-cli && \ - echo "-I/usr/lib/gcc-cross/arm-linux-gnueabihf/12/include -I/usr/arm-linux-gnueabihf/include" > BINDGEN_EXTRA_CLANG_ARGS; \ - cp /usr/arm-linux-gnueabihf/lib/libgcc_s.so.1 .; \ + SYSROOT=$(arm-linux-gnueabihf-gcc -print-sysroot); \ + echo "--sysroot=$SYSROOT -I$SYSROOT/usr/include -I$SYSROOT/usr/include/arm-linux-gnueabihf" > BINDGEN_EXTRA_CLANG_ARGS; \ + LIBDIR="/lib/arm-linux-gnueabihf"; \ + USRLIBDIR="/usr/lib/arm-linux-gnueabihf"; \ else \ echo "Unsupported cross compilation target: $TARGETARCH"; \ exit 1; \ fi && \ + cp $LIBDIR/libgcc_s.so.1 $USRLIBDIR/libodbc.so.2 $USRLIBDIR/libltdl.so.7 /opt/sqlpage-libs/ && \ rustup target add $(cat TARGET) && \ cargo init . @@ -54,7 +62,7 @@ ENV SQLPAGE_WEB_ROOT=/var/www ENV SQLPAGE_CONFIGURATION_DIRECTORY=/etc/sqlpage WORKDIR /var/www COPY --from=builder /usr/src/sqlpage/sqlpage.bin /usr/local/bin/sqlpage -COPY --from=builder /usr/src/sqlpage/libgcc_s.so.1 /lib/libgcc_s.so.1 +COPY --from=builder /opt/sqlpage-libs/* /lib/ USER sqlpage COPY --from=builder --chown=sqlpage:sqlpage /usr/src/sqlpage/sqlpage/sqlpage.db sqlpage/sqlpage.db EXPOSE 8080 diff --git a/README.md b/README.md index 051ca3f4..72f6294f 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ select - [PostgreSQL](https://www.postgresql.org/), and other compatible databases such as *YugabyteDB*, *CockroachDB* and *Aurora*. - [MySQL](https://www.mysql.com/), and other compatible databases such as *MariaDB* and *TiDB*. - [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server), and all compatible databases and providers such as *Azure SQL* and *Amazon RDS*. +- **ODBC-compatible databases** such as *ClickHouse*, *MongoDB*, *DuckDB*, *Oracle*, *Snowflake*, *BigQuery*, *IBM DB2*, and many others through ODBC drivers. ## Get started @@ -175,6 +176,34 @@ An alternative for Mac OS users is to use [SQLPage's homebrew package](https://f - In a terminal, run the following commands: - `brew install sqlpage` + +### ODBC Setup + +You can skip this section if you want to use one of the built-in database drivers (SQLite, PostgreSQL, MySQL, Microsoft SQL Server). + +SQLPage supports ODBC connections to connect to databases that don't have native drivers, such as Oracle, Snowflake, BigQuery, IBM DB2, and many others. + +ODBC support requires an ODBC driver manager and appropriate database drivers to be installed on your system. + +#### Install ODBC + + - On windows, it's installed by default. + - On linux: `sudo apt-get install -y unixodbc odbcinst unixodbc-common libodbcinst2` + - On mac: `brew install unixodbc` + + +#### Install your ODBC database driver + - [DuckDB](https://duckdb.org/docs/stable/clients/odbc/overview.html) + - [Snowflake](https://docs.snowflake.com/en/developer-guide/odbc/odbc) + - [BigQuery](https://cloud.google.com/bigquery/docs/reference/odbc-jdbc-drivers) + - For other databases, follow your database's official odbc install instructions. + +#### Connect to your database + + - Find your [connection string](https://www.connectionstrings.com/). It will look like this: `Driver={SnowflakeDSIIDriver};Server=xyz.snowflakecomputing.com;Database=MY_DB;Schema=PUBLIC;UID=my_user;PWD=my_password` + - Use it in the [DATABASE_URL configuration option](./configuration.md) + + ## How it works ![architecture diagram](./docs/architecture-detailed.png) diff --git a/configuration.md b/configuration.md index 2fd78166..5be0f1ca 100644 --- a/configuration.md +++ b/configuration.md @@ -66,7 +66,7 @@ If you have a `.env` file in the current directory or in any of its parent direc The `database_url` parameter sets all the connection parameters for the database, including - - the database engine type (`sqlite`, `postgres`, `mysql`, `mssql`, etc.) + - the database engine type (`sqlite`, `postgres`, `mysql`, `mssql`, or ODBC connection strings) - the username and password - the host (or ip adress) and port - the database name @@ -87,6 +87,26 @@ A full connection string for a PostgreSQL database might look like this: postgres://my_user:p%40ss@localhost:5432/my_database?sslmode=verify-ca&sslrootcert=/path/to/ca.pem&sslcert=/path/to/cert.pem&sslkey=/path/to/key.pem&application_name=my_application ``` +#### ODBC Connection Strings + +For ODBC-compatible databases (Oracle, Snowflake, BigQuery, IBM DB2, etc.), you can use ODBC connection strings directly: + +```bash +# Using a Data Source Name (DSN) +DATABASE_URL="DSN=MyDatabase" + +# Using inline connection parameters +DATABASE_URL="Driver={PostgreSQL};Server=localhost;Port=5432;Database=mydb;UID=myuser;PWD=mypassword" + +# Oracle example +DATABASE_URL="Driver={Oracle ODBC Driver};Server=localhost:1521/XE;UID=hr;PWD=password" + +# Snowflake example +DATABASE_URL="Driver={SnowflakeDSIIDriver};Server=account.snowflakecomputing.com;Database=mydb;UID=user;PWD=password" +``` + +ODBC drivers must be installed and configured on your system. On Linux, you typically need `unixodbc` and the appropriate database-specific ODBC driver. + If the `database_password` configuration parameter is set, it will override any password specified in the `database_url`. It does not need to be percent-encoded. This allows you to keep the password separate from the connection string, which can be useful for security purposes, especially when storing configurations in version control systems. diff --git a/docker-compose.yml b/docker-compose.yml index 561fc39c..0d92fb83 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,4 +59,4 @@ services: image: mariadb environment: MYSQL_ROOT_PASSWORD: Password123! - MYSQL_DATABASE: sqlpage \ No newline at end of file + MYSQL_DATABASE: sqlpage diff --git a/lambda.Dockerfile b/lambda.Dockerfile index e627a21a..fcd476d4 100644 --- a/lambda.Dockerfile +++ b/lambda.Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.87-alpine as builder +FROM rust:1.90-alpine as builder RUN rustup component add clippy rustfmt RUN apk add --no-cache musl-dev zip WORKDIR /usr/src/sqlpage diff --git a/src/app_config.rs b/src/app_config.rs index b80e05ec..be5b7449 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -18,8 +18,8 @@ impl AppConfig { let mut config = if let Some(config_file) = &cli.config_file { if !config_file.is_file() { return Err(anyhow::anyhow!( - "Configuration file does not exist: {:?}", - config_file + "Configuration file does not exist: {}", + config_file.display() )); } log::debug!("Loading configuration from file: {}", config_file.display()); @@ -85,14 +85,14 @@ impl AppConfig { fn validate(&self) -> anyhow::Result<()> { if !self.web_root.is_dir() { return Err(anyhow::anyhow!( - "Web root is not a valid directory: {:?}", - self.web_root + "Web root is not a valid directory: {}", + self.web_root.display() )); } if !self.configuration_directory.is_dir() { return Err(anyhow::anyhow!( - "Configuration directory is not a valid directory: {:?}", - self.configuration_directory + "Configuration directory is not a valid directory: {}", + self.configuration_directory.display() )); } if self.database_connection_acquire_timeout_seconds <= 0.0 { diff --git a/src/filesystem.rs b/src/filesystem.rs index 7ca08f5a..185fe748 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -1,9 +1,10 @@ +use crate::webserver::database::SupportedDatabase; use crate::webserver::ErrorWithStatus; use crate::webserver::{make_placeholder, Database}; use crate::{AppState, TEMPLATES_DIR}; use anyhow::Context; use chrono::{DateTime, Utc}; -use sqlx::any::{AnyKind, AnyStatement, AnyTypeInfo}; +use sqlx::any::{AnyStatement, AnyTypeInfo}; use sqlx::postgres::types::PgTimeTz; use sqlx::{Postgres, Statement, Type}; use std::fmt::Write; @@ -27,7 +28,7 @@ impl FileSystem { You can host sql files directly in your database by creating the following table: \n\ {} \n\ The error while trying to use the database file system is: {e:#}", - DbFsQueries::get_create_table_sql(db.connection.any_kind()) + DbFsQueries::get_create_table_sql(db.info.database_type) ); None } @@ -159,8 +160,9 @@ impl FileSystem { } } else { anyhow::bail!( - "Unsupported path: {path:?}. Path component '{component:?}' is not allowed." - ); + "Unsupported path: {}. Path component '{component:?}' is not allowed.", + path.display() + ); } } } @@ -206,32 +208,28 @@ pub struct DbFsQueries { impl DbFsQueries { #[must_use] - pub fn get_create_table_sql(db_kind: AnyKind) -> &'static str { - match db_kind { - AnyKind::Mssql => "CREATE TABLE sqlpage_files(path NVARCHAR(255) NOT NULL PRIMARY KEY, contents VARBINARY(MAX), last_modified DATETIME2(3) NOT NULL DEFAULT CURRENT_TIMESTAMP);", - AnyKind::Postgres => "CREATE TABLE IF NOT EXISTS sqlpage_files(path VARCHAR(255) NOT NULL PRIMARY KEY, contents BYTEA, last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP);", + pub fn get_create_table_sql(dbms: SupportedDatabase) -> &'static str { + match dbms { + SupportedDatabase::Mssql => "CREATE TABLE sqlpage_files(path NVARCHAR(255) NOT NULL PRIMARY KEY, contents VARBINARY(MAX), last_modified DATETIME2(3) NOT NULL DEFAULT CURRENT_TIMESTAMP);", + SupportedDatabase::Postgres => "CREATE TABLE IF NOT EXISTS sqlpage_files(path VARCHAR(255) NOT NULL PRIMARY KEY, contents BYTEA, last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP);", _ => "CREATE TABLE IF NOT EXISTS sqlpage_files(path VARCHAR(255) NOT NULL PRIMARY KEY, contents BLOB, last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP);", } } async fn init(db: &Database) -> anyhow::Result { log::debug!("Initializing database filesystem queries"); - let db_kind = db.connection.any_kind(); Ok(Self { - was_modified: Self::make_was_modified_query(db, db_kind).await?, - read_file: Self::make_read_file_query(db, db_kind).await?, - exists: Self::make_exists_query(db, db_kind).await?, + was_modified: Self::make_was_modified_query(db).await?, + read_file: Self::make_read_file_query(db).await?, + exists: Self::make_exists_query(db).await?, }) } - async fn make_was_modified_query( - db: &Database, - db_kind: AnyKind, - ) -> anyhow::Result> { + async fn make_was_modified_query(db: &Database) -> anyhow::Result> { let was_modified_query = format!( "SELECT 1 from sqlpage_files WHERE last_modified >= {} AND path = {}", - make_placeholder(db_kind, 1), - make_placeholder(db_kind, 2) + make_placeholder(db.info.kind, 1), + make_placeholder(db.info.kind, 2) ); let param_types: &[AnyTypeInfo; 2] = &[ PgTimeTz::type_info().into(), @@ -241,26 +239,20 @@ impl DbFsQueries { db.prepare_with(&was_modified_query, param_types).await } - async fn make_read_file_query( - db: &Database, - db_kind: AnyKind, - ) -> anyhow::Result> { + async fn make_read_file_query(db: &Database) -> anyhow::Result> { let read_file_query = format!( "SELECT contents from sqlpage_files WHERE path = {}", - make_placeholder(db_kind, 1), + make_placeholder(db.info.kind, 1), ); let param_types: &[AnyTypeInfo; 1] = &[>::type_info().into()]; log::debug!("Preparing the database filesystem read_file_query: {read_file_query}"); db.prepare_with(&read_file_query, param_types).await } - async fn make_exists_query( - db: &Database, - db_kind: AnyKind, - ) -> anyhow::Result> { + async fn make_exists_query(db: &Database) -> anyhow::Result> { let exists_query = format!( "SELECT 1 from sqlpage_files WHERE path = {}", - make_placeholder(db_kind, 1), + make_placeholder(db.info.kind, 1), ); let param_types: &[AnyTypeInfo; 1] = &[>::type_info().into()]; db.prepare_with(&exists_query, param_types).await @@ -349,18 +341,18 @@ async fn test_sql_file_read_utf8() -> anyhow::Result<()> { use sqlx::Executor; let config = app_config::tests::test_config(); let state = AppState::init(&config).await?; - let create_table_sql = DbFsQueries::get_create_table_sql(state.db.connection.any_kind()); + let create_table_sql = DbFsQueries::get_create_table_sql(state.db.info.database_type); state .db .connection .execute(format!("DROP TABLE IF EXISTS sqlpage_files; {create_table_sql}").as_str()) .await?; - let db_kind = state.db.connection.any_kind(); + let dbms = state.db.info.kind; let insert_sql = format!( "INSERT INTO sqlpage_files(path, contents) VALUES ({}, {})", - make_placeholder(db_kind, 1), - make_placeholder(db_kind, 2) + make_placeholder(dbms, 1), + make_placeholder(dbms, 2) ); sqlx::query(&insert_sql) .bind("unit test file.txt") diff --git a/src/render.rs b/src/render.rs index 09e9f051..aecd118b 100644 --- a/src/render.rs +++ b/src/render.rs @@ -211,7 +211,7 @@ impl HeaderContext { Some("none") => actix_web::cookie::SameSite::None, Some("lax") => actix_web::cookie::SameSite::Lax, None | Some("strict") => actix_web::cookie::SameSite::Strict, // strict by default - Some(other) => bail!("Cookie: invalid value for same_site: {}", other), + Some(other) => bail!("Cookie: invalid value for same_site: {other}"), }); let secure = obj.get("secure"); cookie.set_secure(secure != Some(&json!(false)) && secure != Some(&json!(0))); @@ -392,7 +392,7 @@ async fn verify_password_async( ) -> Result, anyhow::Error> { tokio::task::spawn_blocking(move || { let hash = password_hash::PasswordHash::new(&password_hash) - .map_err(|e| anyhow::anyhow!("invalid value for the password_hash property: {}", e))?; + .map_err(|e| anyhow::anyhow!("invalid value for the password_hash property: {e}"))?; let phfs = &[&argon2::Argon2::default() as &dyn password_hash::PasswordVerifier]; Ok(hash.verify_password(phfs, password)) }) @@ -735,7 +735,7 @@ impl HtmlRenderContext { data: &JsonValue, ) -> anyhow::Result<()> { if Self::is_shell_component(component_name) { - bail!("There cannot be more than a single shell per page. You are trying to open the {} component, but a shell component is already opened for the current page. You can fix this by removing the extra shell component, or by moving this component to the top of the SQL file, before any other component that displays data.", component_name); + bail!("There cannot be more than a single shell per page. You are trying to open the {component_name} component, but a shell component is already opened for the current page. You can fix this by removing the extra shell component, or by moving this component to the top of the SQL file, before any other component that displays data."); } if component_name == "log" { diff --git a/src/webserver/database/connect.rs b/src/webserver/database/connect.rs index e758a714..b89041b0 100644 --- a/src/webserver/database/connect.rs +++ b/src/webserver/database/connect.rs @@ -1,14 +1,18 @@ use std::{mem::take, time::Duration}; use super::Database; -use crate::{app_config::AppConfig, ON_CONNECT_FILE, ON_RESET_FILE}; +use crate::{ + app_config::AppConfig, + webserver::database::{DbInfo, SupportedDatabase}, + ON_CONNECT_FILE, ON_RESET_FILE, +}; use anyhow::Context; use futures_util::future::BoxFuture; use sqlx::{ any::{Any, AnyConnectOptions, AnyKind}, pool::PoolOptions, sqlite::{Function, SqliteFunctionCtx}, - ConnectOptions, Executor, + ConnectOptions, Connection, Executor, }; impl Database { @@ -33,8 +37,9 @@ impl Database { set_custom_connect_options(&mut connect_options, config); log::debug!("Connecting to database: {database_url}"); let mut retries = config.database_connection_retries; - let connection = loop { - match Self::create_pool_options(config, connect_options.kind()) + let db_kind = connect_options.kind(); + let pool = loop { + match Self::create_pool_options(config, db_kind) .connect_with(connect_options.clone()) .await { @@ -50,18 +55,28 @@ impl Database { } } }; - log::debug!("Initialized database pool: {connection:#?}"); - Ok(Database { connection }) + let dbms_name: String = pool.acquire().await?.dbms_name().await?; + let database_type = SupportedDatabase::from_dbms_name(&dbms_name); + + log::debug!("Initialized {dbms_name} database pool: {pool:#?}"); + Ok(Database { + connection: pool, + info: DbInfo { + dbms_name, + database_type, + kind: db_kind, + }, + }) } - fn create_pool_options(config: &AppConfig, db_kind: AnyKind) -> PoolOptions { + fn create_pool_options(config: &AppConfig, kind: AnyKind) -> PoolOptions { let mut pool_options = PoolOptions::new() .max_connections(if let Some(max) = config.max_database_pool_connections { max } else { // Different databases have a different number of max concurrent connections allowed by default - match db_kind { - AnyKind::Postgres => 50, + match kind { + AnyKind::Postgres | AnyKind::Odbc => 50, // Default to PostgreSQL-like limits for Generic AnyKind::MySql => 75, AnyKind::Sqlite => { if config.database_url.contains(":memory:") { @@ -77,7 +92,7 @@ impl Database { config .database_connection_idle_timeout_seconds .map(Duration::from_secs_f64) - .or_else(|| match db_kind { + .or_else(|| match kind { AnyKind::Sqlite => None, _ => Some(Duration::from_secs(30 * 60)), }), @@ -86,7 +101,7 @@ impl Database { config .database_connection_max_lifetime_seconds .map(Duration::from_secs_f64) - .or_else(|| match db_kind { + .or_else(|| match kind { AnyKind::Sqlite => None, _ => Some(Duration::from_secs(60 * 60)), }), diff --git a/src/webserver/database/csv_import.rs b/src/webserver/database/csv_import.rs index 12b79970..ee440056 100644 --- a/src/webserver/database/csv_import.rs +++ b/src/webserver/database/csv_import.rs @@ -256,13 +256,13 @@ async fn compute_column_indices( Ok(col_idxs) } -fn create_insert_stmt(kind: AnyKind, csv_import: &CsvImport) -> String { +fn create_insert_stmt(db_kind: AnyKind, csv_import: &CsvImport) -> String { let columns = csv_import.columns.join(", "); let placeholders = csv_import .columns .iter() .enumerate() - .map(|(i, _)| make_placeholder(kind, i + 1)) + .map(|(i, _)| make_placeholder(db_kind, i + 1)) .fold(String::new(), |mut acc, f| { if !acc.is_empty() { acc.push_str(", "); diff --git a/src/webserver/database/error_highlighting.rs b/src/webserver/database/error_highlighting.rs index 48ae3278..4c1c3ef2 100644 --- a/src/webserver/database/error_highlighting.rs +++ b/src/webserver/database/error_highlighting.rs @@ -86,7 +86,7 @@ pub fn display_stmt_db_error( anyhow::Error::new(NiceDatabaseError { source_file: source_file.to_path_buf(), db_err, - query: stmt.query.to_string(), + query: stmt.query.clone(), query_position: Some(stmt.query_position), }) } diff --git a/src/webserver/database/execute_queries.rs b/src/webserver/database/execute_queries.rs index 351148aa..9d242f55 100644 --- a/src/webserver/database/execute_queries.rs +++ b/src/webserver/database/execute_queries.rs @@ -302,7 +302,7 @@ fn debug_row(r: &AnyRow) { } fn clone_anyhow_err(source_file: &Path, err: &anyhow::Error) -> anyhow::Error { - let mut e = anyhow!("{source_file:?} contains a syntax error preventing SQLPage from parsing and preparing its SQL statements."); + let mut e = anyhow!("{} contains a syntax error preventing SQLPage from parsing and preparing its SQL statements.", source_file.display()); for c in err.chain().rev() { e = e.context(c.to_string()); } diff --git a/src/webserver/database/mod.rs b/src/webserver/database/mod.rs index 49e465c5..1ae59a7a 100644 --- a/src/webserver/database/mod.rs +++ b/src/webserver/database/mod.rs @@ -13,10 +13,58 @@ mod sql_to_json; pub use sql::ParsedSqlFile; use sql::{DbPlaceHolder, DB_PLACEHOLDERS}; use sqlx::any::AnyKind; +// SupportedDatabase is defined in this module + +/// Supported database types in `SQLPage`. Represents an actual DBMS, not a sqlx backend kind (like "Odbc") +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SupportedDatabase { + Sqlite, + Postgres, + MySql, + Mssql, + Generic, +} + +impl SupportedDatabase { + /// Detect the database type from a connection's `dbms_name` + #[must_use] + pub fn from_dbms_name(dbms_name: &str) -> Self { + match dbms_name.to_lowercase().as_str() { + "sqlite" | "sqlite3" => Self::Sqlite, + "postgres" | "postgresql" => Self::Postgres, + "mysql" | "mariadb" => Self::MySql, + "mssql" | "sql server" | "microsoft sql server" => Self::Mssql, + _ => Self::Generic, + } + } + + /// Get the display name for the database + #[must_use] + pub fn display_name(self) -> &'static str { + match self { + Self::Sqlite => "SQLite", + Self::Postgres => "PostgreSQL", + Self::MySql => "MySQL", + Self::Mssql => "Microsoft SQL Server", + Self::Generic => "Generic", + } + } +} pub struct Database { pub connection: sqlx::AnyPool, + pub info: DbInfo, } + +#[derive(Debug, Clone)] +pub struct DbInfo { + pub dbms_name: String, + /// The actual database we are connected to. Can be "Generic" when using an unknown ODBC driver + pub database_type: SupportedDatabase, + /// The sqlx database backend we are using. Can be "Odbc", in which case we need to use `database_type` to know what database we are actually using. + pub kind: AnyKind, +} + impl Database { pub async fn close(&self) -> anyhow::Result<()> { log::info!("Closing all database connections..."); @@ -40,13 +88,13 @@ impl std::fmt::Display for Database { #[inline] #[must_use] -pub fn make_placeholder(db_kind: AnyKind, arg_number: usize) -> String { - if let Some((_, placeholder)) = DB_PLACEHOLDERS.iter().find(|(kind, _)| *kind == db_kind) { +pub fn make_placeholder(dbms: AnyKind, arg_number: usize) -> String { + if let Some((_, placeholder)) = DB_PLACEHOLDERS.iter().find(|(kind, _)| *kind == dbms) { match *placeholder { DbPlaceHolder::PrefixedNumber { prefix } => format!("{prefix}{arg_number}"), DbPlaceHolder::Positional { placeholder } => placeholder.to_string(), } } else { - unreachable!("missing db_kind: {db_kind:?} in DB_PLACEHOLDERS ({DB_PLACEHOLDERS:?})") + unreachable!("missing dbms: {dbms:?} in DB_PLACEHOLDERS ({DB_PLACEHOLDERS:?})") } } diff --git a/src/webserver/database/sql.rs b/src/webserver/database/sql.rs index 97631b02..5b685809 100644 --- a/src/webserver/database/sql.rs +++ b/src/webserver/database/sql.rs @@ -2,8 +2,10 @@ use super::csv_import::{extract_csv_copy_statement, CsvImport}; use super::sqlpage_functions::functions::SqlPageFunctionName; use super::sqlpage_functions::{are_params_extractable, func_call_to_param}; use super::syntax_tree::StmtParam; +use super::SupportedDatabase; use crate::file_cache::AsyncFromStrWithState; use crate::webserver::database::error_highlighting::quote_source_with_highlight; +use crate::webserver::database::DbInfo; use crate::{AppState, Database}; use async_trait::async_trait; use sqlparser::ast::helpers::attached_token::AttachedToken; @@ -31,9 +33,9 @@ pub struct ParsedSqlFile { impl ParsedSqlFile { #[must_use] pub fn new(db: &Database, sql: &str, source_path: &Path) -> ParsedSqlFile { - let dialect = dialect_for_db(db.connection.any_kind()); log::debug!("Parsing SQL file {}", source_path.display()); - let parsed_statements = match parse_sql(dialect.as_ref(), sql) { + let dialect = dialect_for_db(db.info.database_type); + let parsed_statements = match parse_sql(&db.info, dialect.as_ref(), sql) { Ok(parsed) => parsed, Err(err) => return Self::from_err(err, source_path), }; @@ -118,10 +120,12 @@ pub(super) enum SimpleSelectValue { } fn parse_sql<'a>( + db_info: &'a DbInfo, dialect: &'a dyn Dialect, sql: &'a str, ) -> anyhow::Result + 'a> { - log::trace!("Parsing SQL: {sql}"); + log::trace!("Parsing {} SQL: {sql}", db_info.dbms_name); + let tokens = Tokenizer::new(dialect, sql) .tokenize_with_location() .map_err(|err| { @@ -129,14 +133,13 @@ fn parse_sql<'a>( anyhow::Error::new(err).context(format!("The SQLPage parser could not understand the SQL file. Tokenization failed. Please check for syntax errors:\n{}", quote_source_with_highlight(sql, location.line, location.column))) })?; let mut parser = Parser::new(dialect).with_tokens_with_locations(tokens); - let db_kind = kind_of_dialect(dialect); let mut has_error = false; Ok(std::iter::from_fn(move || { if has_error { // Return the first error and ignore the rest return None; } - let statement = parse_single_statement(&mut parser, db_kind, sql); + let statement = parse_single_statement(&mut parser, db_info, sql); if let Some(ParsedStatement::Error(_)) = &statement { has_error = true; } @@ -144,9 +147,10 @@ fn parse_sql<'a>( })) } -fn transform_to_positional_placeholders(stmt: &mut StmtWithParams, db_kind: AnyKind) { - if let Some((_, DbPlaceHolder::Positional { placeholder })) = - DB_PLACEHOLDERS.iter().find(|(kind, _)| *kind == db_kind) +fn transform_to_positional_placeholders(stmt: &mut StmtWithParams, kind: AnyKind) { + if let Some((_, DbPlaceHolder::Positional { placeholder })) = DB_PLACEHOLDERS + .iter() + .find(|(placeholder_kind, _)| *placeholder_kind == kind) { let mut new_params = Vec::new(); let mut query = stmt.query.clone(); @@ -166,7 +170,7 @@ fn transform_to_positional_placeholders(stmt: &mut StmtWithParams, db_kind: AnyK fn parse_single_statement( parser: &mut Parser<'_>, - db_kind: AnyKind, + db_info: &DbInfo, source_sql: &str, ) -> Option { if parser.peek_token() == EOF { @@ -181,8 +185,9 @@ fn parse_single_statement( while parser.consume_token(&SemiColon) { semicolon = true; } - let mut params = ParameterExtractor::extract_parameters(&mut stmt, db_kind); - if let Some(parsed) = extract_set_variable(&mut stmt, &mut params, db_kind) { + let mut params = ParameterExtractor::extract_parameters(&mut stmt, db_info.clone()); + let dbms = db_info.database_type; + if let Some(parsed) = extract_set_variable(&mut stmt, &mut params, db_info) { return Some(parsed); } if let Some(csv_import) = extract_csv_copy_statement(&mut stmt) { @@ -198,7 +203,7 @@ fn parse_single_statement( "Invalid SQLPage function call found in:\n{stmt}" )))); } - let json_columns = extract_json_columns(&stmt, db_kind); + let json_columns = extract_json_columns(&stmt, dbms); let query = format!( "{stmt}{semicolon}", semicolon = if semicolon { ";" } else { "" } @@ -210,7 +215,7 @@ fn parse_single_statement( delayed_functions, json_columns, }; - transform_to_positional_placeholders(&mut stmt_with_params, db_kind); + transform_to_positional_placeholders(&mut stmt_with_params, db_info.kind); log::debug!("Final transformed statement: {}", stmt_with_params.query); Some(ParsedStatement::StmtWithParams(stmt_with_params)) } @@ -237,26 +242,12 @@ fn syntax_error(err: ParserError, parser: &Parser, sql: &str) -> ParsedStatement ))) } -fn dialect_for_db(db_kind: AnyKind) -> Box { - match db_kind { - AnyKind::Postgres => Box::new(PostgreSqlDialect {}), - AnyKind::Mssql => Box::new(MsSqlDialect {}), - AnyKind::MySql => Box::new(MySqlDialect {}), - AnyKind::Sqlite => Box::new(SQLiteDialect {}), - } -} - -fn kind_of_dialect(dialect: &dyn Dialect) -> AnyKind { - if dialect.is::() { - AnyKind::Postgres - } else if dialect.is::() { - AnyKind::Mssql - } else if dialect.is::() { - AnyKind::MySql - } else if dialect.is::() { - AnyKind::Sqlite - } else { - unreachable!("Unknown dialect") +fn dialect_for_db(dbms: SupportedDatabase) -> Box { + match dbms { + SupportedDatabase::Postgres | SupportedDatabase::Generic => Box::new(PostgreSqlDialect {}), // Default to PostgreSQL dialect for Generic + SupportedDatabase::Mssql => Box::new(MsSqlDialect {}), + SupportedDatabase::MySql => Box::new(MySqlDialect {}), + SupportedDatabase::Sqlite => Box::new(SQLiteDialect {}), } } @@ -473,7 +464,7 @@ fn is_simple_select_placeholder(e: &Expr) -> bool { fn extract_set_variable( stmt: &mut Statement, params: &mut Vec, - db_kind: AnyKind, + db_info: &DbInfo, ) -> Option { if let Statement::Set(Set::SingleAssignment { variable: ObjectName(name), @@ -496,7 +487,7 @@ fn extract_set_variable( if let Err(err) = validate_function_calls(&select_stmt) { return Some(ParsedStatement::Error(err)); } - let json_columns = extract_json_columns(&select_stmt, db_kind); + let json_columns = extract_json_columns(&select_stmt, db_info.database_type); let mut value = StmtWithParams { query: select_stmt.to_string(), query_position: extract_query_start(&select_stmt), @@ -504,7 +495,7 @@ fn extract_set_variable( delayed_functions, json_columns, }; - transform_to_positional_placeholders(&mut value, db_kind); + transform_to_positional_placeholders(&mut value, db_info.kind); return Some(ParsedStatement::SetVariable { variable, value }); } } @@ -512,7 +503,7 @@ fn extract_set_variable( } struct ParameterExtractor { - db_kind: AnyKind, + db_info: DbInfo, parameters: Vec, } @@ -522,7 +513,7 @@ pub enum DbPlaceHolder { Positional { placeholder: &'static str }, } -pub const DB_PLACEHOLDERS: [(AnyKind, DbPlaceHolder); 4] = [ +pub const DB_PLACEHOLDERS: [(AnyKind, DbPlaceHolder); 5] = [ ( AnyKind::Sqlite, DbPlaceHolder::PrefixedNumber { prefix: "?" }, @@ -539,16 +530,20 @@ pub const DB_PLACEHOLDERS: [(AnyKind, DbPlaceHolder); 4] = [ AnyKind::Mssql, DbPlaceHolder::PrefixedNumber { prefix: "@p" }, ), + ( + AnyKind::Odbc, + DbPlaceHolder::Positional { placeholder: "?" }, + ), ]; /// For positional parameters, we use a temporary placeholder during parameter extraction, /// And then replace it with the actual placeholder during statement rewriting. const TEMP_PLACEHOLDER_PREFIX: &str = "@SQLPAGE_TEMP"; -fn get_placeholder_prefix(db_kind: AnyKind) -> &'static str { +fn get_placeholder_prefix(kind: AnyKind) -> &'static str { if let Some((_, DbPlaceHolder::PrefixedNumber { prefix })) = DB_PLACEHOLDERS .iter() - .find(|(kind, _prefix)| *kind == db_kind) + .find(|(placeholder_kind, _prefix)| *placeholder_kind == kind) { prefix } else { @@ -559,10 +554,10 @@ fn get_placeholder_prefix(db_kind: AnyKind) -> &'static str { impl ParameterExtractor { fn extract_parameters( sql_ast: &mut sqlparser::ast::Statement, - db_kind: AnyKind, + db_info: DbInfo, ) -> Vec { let mut this = Self { - db_kind, + db_info, parameters: vec![], }; let _ = sql_ast.visit(&mut this); @@ -585,10 +580,10 @@ impl ParameterExtractor { } fn make_placeholder_for_index(&self, index: usize) -> Expr { - let name = make_tmp_placeholder(self.db_kind, index); - let data_type = match self.db_kind { - AnyKind::MySql => DataType::Char(None), - AnyKind::Mssql => DataType::Varchar(Some(CharacterLength::Max)), + let name = make_tmp_placeholder(self.db_info.kind, index); + let data_type = match self.db_info.database_type { + SupportedDatabase::MySql => DataType::Char(None), + SupportedDatabase::Mssql => DataType::Varchar(Some(CharacterLength::Max)), _ => DataType::Text, }; let value = Expr::value(Value::Placeholder(name)); @@ -605,7 +600,7 @@ impl ParameterExtractor { } fn is_own_placeholder(&self, param: &str) -> bool { - let prefix = get_placeholder_prefix(self.db_kind); + let prefix = get_placeholder_prefix(self.db_info.kind); if let Some(param) = param.strip_prefix(prefix) { if let Ok(index) = param.parse::() { return index <= self.parameters.len() + 1; @@ -823,9 +818,9 @@ fn function_arg_expr(arg: &mut FunctionArg) -> Option<&mut Expr> { #[inline] #[must_use] -pub fn make_tmp_placeholder(db_kind: AnyKind, arg_number: usize) -> String { +pub fn make_tmp_placeholder(kind: AnyKind, arg_number: usize) -> String { let prefix = if let Some((_, DbPlaceHolder::PrefixedNumber { prefix })) = - DB_PLACEHOLDERS.iter().find(|(kind, _)| *kind == db_kind) + DB_PLACEHOLDERS.iter().find(|(db_typ, _)| *db_typ == kind) { prefix } else { @@ -885,7 +880,7 @@ impl VisitorMut for ParameterExtractor { left, op: BinaryOperator::StringConcat, right, - } if self.db_kind == AnyKind::Mssql => { + } if self.db_info.database_type == SupportedDatabase::Mssql => { let left = std::mem::replace(left.as_mut(), Expr::value(Value::Null)); let right = std::mem::replace(right.as_mut(), Expr::value(Value::Null)); *value = Expr::Function(Function { @@ -909,7 +904,7 @@ impl VisitorMut for ParameterExtractor { Expr::Cast { kind: kind @ CastKind::DoubleColon, .. - } if self.db_kind != AnyKind::Postgres => { + } if self.db_info.database_type != SupportedDatabase::Postgres => { log::warn!("Casting with '::' is not supported on your database. \ For backwards compatibility with older SQLPage versions, we will transform it to CAST(... AS ...)."); *kind = CastKind::Cast; @@ -960,9 +955,9 @@ fn sqlpage_func_name(func_name_parts: &[ObjectNamePart]) -> &str { } } -fn extract_json_columns(stmt: &Statement, db_kind: AnyKind) -> Vec { +fn extract_json_columns(stmt: &Statement, dbms: SupportedDatabase) -> Vec { // Only extract JSON columns for databases without native JSON support - if matches!(db_kind, AnyKind::Postgres | AnyKind::Mssql) { + if matches!(dbms, SupportedDatabase::Postgres | SupportedDatabase::Mssql) { return Vec::new(); } @@ -1094,7 +1089,8 @@ mod test { fn test_statement_rewrite() { let mut ast = parse_postgres_stmt("select $a from t where $x > $a OR $x = sqlpage.cookie('cookoo')"); - let parameters = ParameterExtractor::extract_parameters(&mut ast, AnyKind::Postgres); + let db_info = create_test_db_info(SupportedDatabase::Postgres); + let parameters = ParameterExtractor::extract_parameters(&mut ast, db_info); // $a -> $1 // $x -> $2 // sqlpage.cookie(...) -> $3 @@ -1118,7 +1114,8 @@ mod test { #[test] fn test_statement_rewrite_sqlite() { let mut ast = parse_stmt("select $x, :y from t", &SQLiteDialect {}); - let parameters = ParameterExtractor::extract_parameters(&mut ast, AnyKind::Sqlite); + let db_info = create_test_db_info(SupportedDatabase::Sqlite); + let parameters = ParameterExtractor::extract_parameters(&mut ast, db_info); assert_eq!( ast.to_string(), "SELECT CAST(?1 AS TEXT), CAST(?2 AS TEXT) FROM t" @@ -1132,13 +1129,28 @@ mod test { ); } - const ALL_DIALECTS: &[(&dyn Dialect, AnyKind)] = &[ - (&PostgreSqlDialect {}, AnyKind::Postgres), - (&MsSqlDialect {}, AnyKind::Mssql), - (&MySqlDialect {}, AnyKind::MySql), - (&SQLiteDialect {}, AnyKind::Sqlite), + const ALL_DIALECTS: &[(&dyn Dialect, SupportedDatabase)] = &[ + (&PostgreSqlDialect {}, SupportedDatabase::Postgres), + (&MsSqlDialect {}, SupportedDatabase::Mssql), + (&MySqlDialect {}, SupportedDatabase::MySql), + (&SQLiteDialect {}, SupportedDatabase::Sqlite), ]; + fn create_test_db_info(database_type: SupportedDatabase) -> DbInfo { + let (dbms_name, kind) = match database_type { + SupportedDatabase::Postgres => ("PostgreSQL".to_string(), AnyKind::Postgres), + SupportedDatabase::Mssql => ("Microsoft SQL Server".to_string(), AnyKind::Mssql), + SupportedDatabase::MySql => ("MySQL".to_string(), AnyKind::MySql), + SupportedDatabase::Sqlite => ("SQLite".to_string(), AnyKind::Sqlite), + SupportedDatabase::Generic => ("Generic".to_string(), AnyKind::Postgres), // fallback + }; + DbInfo { + dbms_name, + database_type, + kind, + } + } + #[test] fn test_extract_toplevel_delayed_functions() { let mut ast = parse_stmt( @@ -1175,7 +1187,8 @@ mod test { // The order of the function arguments should be preserved // Otherwise the statement parameters will be bound to the wrong arguments let sql = "select $a as a, sqlpage.exec('xxx', x = $b) as b, $c as c from t"; - let all = parse_sql(&PostgreSqlDialect {}, sql) + let db_info = create_test_db_info(SupportedDatabase::Postgres); + let all = parse_sql(&db_info, &PostgreSqlDialect {}, sql) .unwrap() .collect::>(); assert_eq!(all.len(), 1); @@ -1215,10 +1228,11 @@ mod test { #[test] fn test_sqlpage_function_with_argument() { - for &(dialect, kind) in ALL_DIALECTS { + for &(dialect, _kind) in ALL_DIALECTS { let sql = "select sqlpage.fetch($x)"; let mut ast = parse_stmt(sql, dialect); - let parameters = ParameterExtractor::extract_parameters(&mut ast, kind); + let db_info = create_test_db_info(SupportedDatabase::Postgres); + let parameters = ParameterExtractor::extract_parameters(&mut ast, db_info); assert_eq!( parameters, [StmtParam::FunctionCall(SqlPageFunctionCall { @@ -1233,9 +1247,10 @@ mod test { #[test] fn test_set_variable() { let sql = "set x = $y"; - for &(dialect, db_kind) in ALL_DIALECTS { + for &(dialect, dbms) in ALL_DIALECTS { let mut parser = Parser::new(dialect).try_with_sql(sql).unwrap(); - let stmt = parse_single_statement(&mut parser, db_kind, sql); + let db_info = create_test_db_info(dbms); + let stmt = parse_single_statement(&mut parser, &db_info, sql); if let Some(ParsedStatement::SetVariable { variable, value: StmtWithParams { query, params, .. }, @@ -1257,31 +1272,31 @@ mod test { #[test] fn is_own_placeholder() { assert!(ParameterExtractor { - db_kind: AnyKind::Postgres, + db_info: create_test_db_info(SupportedDatabase::Postgres), parameters: vec![] } .is_own_placeholder("$1")); assert!(ParameterExtractor { - db_kind: AnyKind::Postgres, + db_info: create_test_db_info(SupportedDatabase::Postgres), parameters: vec![StmtParam::Get("x".to_string())] } .is_own_placeholder("$2")); assert!(!ParameterExtractor { - db_kind: AnyKind::Postgres, + db_info: create_test_db_info(SupportedDatabase::Postgres), parameters: vec![] } .is_own_placeholder("$2")); assert!(ParameterExtractor { - db_kind: AnyKind::Sqlite, + db_info: create_test_db_info(SupportedDatabase::Sqlite), parameters: vec![] } .is_own_placeholder("?1")); assert!(!ParameterExtractor { - db_kind: AnyKind::Sqlite, + db_info: create_test_db_info(SupportedDatabase::Sqlite), parameters: vec![] } .is_own_placeholder("$1")); @@ -1293,7 +1308,8 @@ mod test { "select '' || $1 from [a schema].[a table]", &MsSqlDialect {}, ); - let parameters = ParameterExtractor::extract_parameters(&mut ast, AnyKind::Mssql); + let db_info = create_test_db_info(SupportedDatabase::Mssql); + let parameters = ParameterExtractor::extract_parameters(&mut ast, db_info); assert_eq!( ast.to_string(), "SELECT CONCAT('', CAST(@p1 AS VARCHAR(MAX))) FROM [a schema].[a table]" @@ -1331,10 +1347,22 @@ mod test { &MsSqlDialect {}, ]; for &dialect in dialects { + use std::any::Any; use SimpleSelectValue::{Dynamic, Static}; use StmtParam::PostOrGet; - let parsed: Vec = parse_sql(dialect, sql).unwrap().collect(); + let db_info = if dialect.type_id() == (PostgreSqlDialect {}).type_id() { + create_test_db_info(SupportedDatabase::Postgres) + } else if dialect.type_id() == (SQLiteDialect {}).type_id() { + create_test_db_info(SupportedDatabase::Sqlite) + } else if dialect.type_id() == (MySqlDialect {}).type_id() { + create_test_db_info(SupportedDatabase::MySql) + } else if dialect.type_id() == (MsSqlDialect {}).type_id() { + create_test_db_info(SupportedDatabase::Mssql) + } else { + create_test_db_info(SupportedDatabase::Generic) + }; + let parsed: Vec = parse_sql(&db_info, dialect, sql).unwrap().collect(); match &parsed[..] { [ParsedStatement::StaticSimpleSelect(q)] => assert_eq!( q, @@ -1368,9 +1396,10 @@ mod test { #[test] fn test_extract_set_variable() { let sql = "set x = 42"; - for &(dialect, db_kind) in ALL_DIALECTS { + for &(dialect, dbms) in ALL_DIALECTS { let mut parser = Parser::new(dialect).try_with_sql(sql).unwrap(); - let stmt = parse_single_statement(&mut parser, db_kind, sql); + let db_info = create_test_db_info(dbms); + let stmt = parse_single_statement(&mut parser, &db_info, sql); if let Some(ParsedStatement::SetVariable { variable, value: StmtWithParams { query, params, .. }, @@ -1462,7 +1491,7 @@ mod test { "; let stmt = parse_postgres_stmt(sql); - let json_columns = extract_json_columns(&stmt, AnyKind::Sqlite); + let json_columns = extract_json_columns(&stmt, SupportedDatabase::Sqlite); assert_eq!( json_columns, @@ -1477,9 +1506,10 @@ mod test { #[test] fn test_set_variable_with_sqlpage_function() { let sql = "set x = sqlpage.url_encode(some_db_function())"; - for &(dialect, db_kind) in ALL_DIALECTS { + for &(dialect, dbms) in ALL_DIALECTS { let mut parser = Parser::new(dialect).try_with_sql(sql).unwrap(); - let stmt = parse_single_statement(&mut parser, db_kind, sql); + let db_info = create_test_db_info(dbms); + let stmt = parse_single_statement(&mut parser, &db_info, sql); let Some(ParsedStatement::SetVariable { variable, value: @@ -1523,7 +1553,7 @@ mod test { "#; let stmt = parse_stmt(sql, &SQLiteDialect {}); - let json_columns = extract_json_columns(&stmt, AnyKind::Sqlite); + let json_columns = extract_json_columns(&stmt, SupportedDatabase::Sqlite); assert!(json_columns.contains(&"item".to_string())); assert!(!json_columns.contains(&"title".to_string())); @@ -1603,9 +1633,10 @@ mod test { #[test] fn test_set_variable_error_handling() { let sql = "set x = db_function(sqlpage.fetch(other_db_function()))"; - for &(dialect, db_kind) in ALL_DIALECTS { + for &(dialect, dbms) in ALL_DIALECTS { let mut parser = Parser::new(dialect).try_with_sql(sql).unwrap(); - let stmt = parse_single_statement(&mut parser, db_kind, sql); + let db_info = create_test_db_info(dbms); + let stmt = parse_single_statement(&mut parser, &db_info, sql); if let Some(ParsedStatement::Error(err)) = stmt { assert!( err.to_string().contains("Invalid SQLPage function call"), diff --git a/src/webserver/database/sqlpage_functions/functions.rs b/src/webserver/database/sqlpage_functions/functions.rs index 5df1946f..58beebaf 100644 --- a/src/webserver/database/sqlpage_functions/functions.rs +++ b/src/webserver/database/sqlpage_functions/functions.rs @@ -347,7 +347,7 @@ pub(crate) async fn hash_password(password: Option) -> anyhow::Result( "iat" => Some(claims.issue_time().timestamp().to_string()), "sub" => Some(claims.subject().to_string()), "auth_time" => claims.auth_time().map(|t| t.timestamp().to_string()), - "nonce" => claims.nonce().map(|n| n.secret().to_string()), // Assuming Nonce has secret() + "nonce" => claims.nonce().map(|n| n.secret().clone()), // Assuming Nonce has secret() "acr" => claims.auth_context_ref().map(|acr| acr.to_string()), // amr requires serialization: handled separately if needed "azp" => claims.authorized_party().map(|azp| azp.to_string()), diff --git a/src/webserver/database/syntax_tree.rs b/src/webserver/database/syntax_tree.rs index 1a2cfa36..1558912d 100644 --- a/src/webserver/database/syntax_tree.rs +++ b/src/webserver/database/syntax_tree.rs @@ -176,8 +176,8 @@ pub(super) async fn extract_req_param<'a>( get_val.map(SingleOrVec::as_json_str) } } - StmtParam::Error(x) => anyhow::bail!("{}", x), - StmtParam::Literal(x) => Some(Cow::Owned(x.to_string())), + StmtParam::Error(x) => anyhow::bail!("{x}"), + StmtParam::Literal(x) => Some(Cow::Owned(x.clone())), StmtParam::Null => None, StmtParam::Concat(args) => concat_params(&args[..], request, db_connection).await?, StmtParam::JsonObject(args) => { diff --git a/src/webserver/oidc.rs b/src/webserver/oidc.rs index 23607a91..4d04077c 100644 --- a/src/webserver/oidc.rs +++ b/src/webserver/oidc.rs @@ -221,7 +221,7 @@ impl OidcState { let nonce_verifier = |nonce: Option<&Nonce>| check_nonce(nonce, expected_nonce); let claims: OidcClaims = id_token .into_claims(&verifier, nonce_verifier) - .map_err(|e| anyhow::anyhow!("Could not verify the ID token: {}", e))?; + .map_err(|e| anyhow::anyhow!("Could not verify the ID token: {e}"))?; Ok(claims) } } diff --git a/tests/core/mod.rs b/tests/core/mod.rs index cc240403..e168bee0 100644 --- a/tests/core/mod.rs +++ b/tests/core/mod.rs @@ -48,7 +48,7 @@ async fn test_routing_with_db_fs() { let state = AppState::init(&config).await.unwrap(); let create_table_sql = - sqlpage::filesystem::DbFsQueries::get_create_table_sql(state.db.connection.any_kind()); + sqlpage::filesystem::DbFsQueries::get_create_table_sql(state.db.info.database_type); state .db .connection diff --git a/tests/sql_test_files/mod.rs b/tests/sql_test_files/mod.rs index 27fe00f4..a761a67d 100644 --- a/tests/sql_test_files/mod.rs +++ b/tests/sql_test_files/mod.rs @@ -81,18 +81,9 @@ async fn run_sql_test( let test_file_path = test_file.to_string_lossy().replace('\\', "/"); let stem = test_file.file_stem().unwrap().to_str().unwrap(); - let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite::memory:".to_string()); - let db_type = if db_url.starts_with("postgres") { - "postgres" - } else if db_url.starts_with("mysql") || db_url.starts_with("mariadb") { - "mysql" - } else if db_url.starts_with("mssql") { - "mssql" - } else if db_url.starts_with("sqlite") { - "sqlite" - } else { - panic!("Unknown database type in DATABASE_URL: {db_url}"); - }; + let app_state = app_data.get_ref(); + let db = &app_state.db; + let db_type = format!("{:?}", db.info.database_type).to_lowercase(); if stem.contains(&format!("_no{db_type}")) { return Ok(TestResult::Skipped(format!(