From 576286be71f60dd2fbc4b3e39e140deedd095942 Mon Sep 17 00:00:00 2001 From: awolverp Date: Thu, 18 Jun 2026 12:43:59 +0330 Subject: [PATCH 1/5] Start migrating from SeaQuery, to sqlparser-rs --- Cargo.lock | 543 ++-------- Cargo.toml | 37 +- rapidquery/__init__.py | 54 - rapidquery/_lib/__init__.pyi | 23 - rapidquery/_lib/common.pyi | 873 --------------- rapidquery/_lib/mysql.pyi | 3 - rapidquery/_lib/postgres.pyi | 3 - rapidquery/_lib/query.pyi | 1836 -------------------------------- rapidquery/_lib/schema.pyi | 857 --------------- rapidquery/_lib/sqlite.pyi | 3 - rapidquery/_lib/sqltypes.pyi | 797 -------------- src/common/column.rs | 427 -------- src/common/column_ref.rs | 340 ------ src/common/expression/expr.rs | 700 ------------ src/common/expression/func.rs | 331 ------ src/common/expression/mod.rs | 57 - src/common/foreign_key.rs | 434 -------- src/common/mod.rs | 35 - src/common/table_ref.rs | 319 ------ src/common/value.rs | 172 --- src/internal/macro_rules.rs | 203 ---- src/internal/mod.rs | 76 -- src/internal/parameters.rs | 12 - src/internal/repr.rs | 309 ------ src/internal/type_engine.rs | 436 -------- src/internal/uninitialized.rs | 189 ---- src/lib.rs | 31 +- src/mysql.rs | 5 - src/postgres.rs | 5 - src/query/base.rs | 42 - src/query/case.rs | 136 --- src/query/delete.rs | 220 ---- src/query/insert.rs | 463 -------- src/query/mod.rs | 56 - src/query/on_conflict.rs | 326 ------ src/query/ordering.rs | 114 -- src/query/returning.rs | 92 -- src/query/select.rs | 1241 --------------------- src/query/update.rs | 289 ----- src/query/window.rs | 218 ---- src/query/with.rs | 522 --------- src/schema/alter_table.rs | 620 ----------- src/schema/base.rs | 27 - src/schema/index.rs | 684 ------------ src/schema/mod.rs | 46 - src/schema/table.rs | 510 --------- src/schema/table_operations.rs | 364 ------- src/sqlite.rs | 5 - src/sqltypes/abstracts.rs | 138 --- src/sqltypes/binary.rs | 352 ------ src/sqltypes/datetimes.rs | 353 ------ src/sqltypes/json.rs | 213 ---- src/sqltypes/mod.rs | 105 -- src/sqltypes/others.rs | 491 --------- src/sqltypes/primitives.rs | 556 ---------- src/sqltypes/vector.rs | 337 ------ src/typeref.rs | 172 --- 57 files changed, 90 insertions(+), 17712 deletions(-) delete mode 100644 rapidquery/_lib/__init__.pyi delete mode 100644 rapidquery/_lib/common.pyi delete mode 100644 rapidquery/_lib/mysql.pyi delete mode 100644 rapidquery/_lib/postgres.pyi delete mode 100644 rapidquery/_lib/query.pyi delete mode 100644 rapidquery/_lib/schema.pyi delete mode 100644 rapidquery/_lib/sqlite.pyi delete mode 100644 rapidquery/_lib/sqltypes.pyi delete mode 100644 src/common/column.rs delete mode 100644 src/common/column_ref.rs delete mode 100644 src/common/expression/expr.rs delete mode 100644 src/common/expression/func.rs delete mode 100644 src/common/expression/mod.rs delete mode 100644 src/common/foreign_key.rs delete mode 100644 src/common/mod.rs delete mode 100644 src/common/table_ref.rs delete mode 100644 src/common/value.rs delete mode 100644 src/internal/macro_rules.rs delete mode 100644 src/internal/mod.rs delete mode 100644 src/internal/parameters.rs delete mode 100644 src/internal/repr.rs delete mode 100644 src/internal/type_engine.rs delete mode 100644 src/internal/uninitialized.rs delete mode 100644 src/mysql.rs delete mode 100644 src/postgres.rs delete mode 100644 src/query/base.rs delete mode 100644 src/query/case.rs delete mode 100644 src/query/delete.rs delete mode 100644 src/query/insert.rs delete mode 100644 src/query/mod.rs delete mode 100644 src/query/on_conflict.rs delete mode 100644 src/query/ordering.rs delete mode 100644 src/query/returning.rs delete mode 100644 src/query/select.rs delete mode 100644 src/query/update.rs delete mode 100644 src/query/window.rs delete mode 100644 src/query/with.rs delete mode 100644 src/schema/alter_table.rs delete mode 100644 src/schema/base.rs delete mode 100644 src/schema/index.rs delete mode 100644 src/schema/mod.rs delete mode 100644 src/schema/table.rs delete mode 100644 src/schema/table_operations.rs delete mode 100644 src/sqlite.rs delete mode 100644 src/sqltypes/abstracts.rs delete mode 100644 src/sqltypes/binary.rs delete mode 100644 src/sqltypes/datetimes.rs delete mode 100644 src/sqltypes/json.rs delete mode 100644 src/sqltypes/mod.rs delete mode 100644 src/sqltypes/others.rs delete mode 100644 src/sqltypes/primitives.rs delete mode 100644 src/sqltypes/vector.rs delete mode 100644 src/typeref.rs diff --git a/Cargo.lock b/Cargo.lock index b3a81d7..962c5a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,20 +3,14 @@ version = 4 [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "ar_archive_writer" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348" dependencies = [ - "libc", + "object", ] -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - [[package]] name = "autocfg" version = "1.5.0" @@ -29,18 +23,13 @@ version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - [[package]] name = "cc" -version = "1.2.33" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ + "find-msvc-tools", "shlex", ] @@ -51,51 +40,10 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "num-traits", - "windows-link 0.2.0", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "heck" @@ -103,73 +51,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "iana-time-zone" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "indexmap" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "inherent" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ipnetwork" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - [[package]] name = "libc" version = "0.2.175" @@ -188,55 +69,23 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[package]] -name = "mac_address" -version = "1.1.8" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" -dependencies = [ - "nix", - "winapi", -] +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "nix" -version = "0.29.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] -name = "num-traits" -version = "0.2.19" +name = "object" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ - "autocfg", + "memchr", ] [[package]] @@ -245,15 +94,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - [[package]] name = "parking_lot" version = "0.12.4" @@ -277,12 +117,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "pgvector" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b" - [[package]] name = "portable-atomic" version = "1.11.1" @@ -298,38 +132,44 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" +dependencies = [ + "ar_archive_writer", + "cc", +] + [[package]] name = "pyo3" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c738662e2181be11cb82487628404254902bb3225d8e9e99c31f3ef82a405c" +checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c" dependencies = [ - "chrono", "libc", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "rust_decimal", - "uuid", ] [[package]] name = "pyo3-build-config" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9ca0864a7dd3c133a7f3f020cbff2e12e88420da854c35540fd20ce2d60e435" +checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078" dependencies = [ - "python3-dll-a", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfc1956b709823164763a34cc42bbfd26b8730afa77809a3df8b94a3ae3b059" +checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b" dependencies = [ "libc", "pyo3-build-config", @@ -337,9 +177,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29dc660ad948bae134d579661d08033fbb1918f4529c3bbe3257a68f2009ddf2" +checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -349,26 +189,16 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78cd6c6d718acfcedf26c3d21fe0f053624368b0d44298c55d7138fde9331f7" +checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362" dependencies = [ "heck", "proc-macro2", - "pyo3-build-config", "quote", "syn", ] -[[package]] -name = "python3-dll-a" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d381ef313ae70b4da5f95f8a4de773c6aa5cd28f73adec4b4a31df70b66780d8" -dependencies = [ - "cc", -] - [[package]] name = "quote" version = "1.0.44" @@ -378,59 +208,43 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "rapidquery" version = "0.1.0" dependencies = [ - "chrono", - "indexmap", - "ipnetwork", - "mac_address", "parking_lot", - "pgvector", "pyo3", - "rust_decimal", - "sea-query", - "serde_json", - "uuid", + "sqlparser", ] [[package]] -name = "redox_syscall" -version = "0.5.17" +name = "recursive" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" dependencies = [ - "bitflags", + "recursive-proc-macro-impl", + "stacker", ] [[package]] -name = "rust_decimal" -version = "1.38.0" +name = "recursive-proc-macro-impl" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8975fc98059f365204d635119cf9c5a60ae67b841ed49b5422a9a7e56cdfac0" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" dependencies = [ - "arrayvec", - "num-traits", + "quote", + "syn", ] [[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.20" +name = "redox_syscall" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] [[package]] name = "scopeguard" @@ -439,45 +253,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sea-query" -version = "0.32.7" +name = "shlex" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a5d1c518eaf5eda38e5773f902b26ab6d5e9e9e2bb2349ca6c64cf96f80448c" -dependencies = [ - "chrono", - "inherent", - "ipnetwork", - "mac_address", - "ordered-float", - "pgvector", - "rust_decimal", - "serde_json", - "uuid", -] +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] -name = "serde" -version = "1.0.226" +name = "smallvec" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" -dependencies = [ - "serde_core", -] +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "serde_core" -version = "1.0.226" +name = "sqlparser" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "13c6d1b651dc4edf07eead2a0c6c78016ce971bc2c10da5266861b13f25e7cec" dependencies = [ - "serde_derive", + "log", + "recursive", + "sqlparser_derive", ] [[package]] -name = "serde_derive" -version = "1.0.226" +name = "sqlparser_derive" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "a6dd45d8fc1c79299bfbb7190e42ccbbdf6a5f52e4a6ad98d92357ea965bd289" dependencies = [ "proc-macro2", "quote", @@ -485,29 +287,18 @@ dependencies = [ ] [[package]] -name = "serde_json" -version = "1.0.142" +name = "stacker" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - [[package]] name = "syn" version = "2.0.117" @@ -531,179 +322,19 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" -[[package]] -name = "uuid" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" -dependencies = [ - "getrandom", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" - -[[package]] -name = "windows-result" -version = "0.3.4" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-strings" -version = "0.4.2" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -769,9 +400,3 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" diff --git a/Cargo.toml b/Cargo.toml index cf4e1e4..1ba3a0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,40 +27,13 @@ panic = "unwind" strip = true [dependencies] -pyo3 = { version = "0.28", default-features = false, features = ["macros", "extension-module", "chrono", "uuid", "rust_decimal", "generate-import-lib"] } -pgvector = { version = "~0.4", default-features = false } -ipnetwork = { version = "0.20", default-features = false } -mac_address = { version = "1.1", default-features = false } -uuid = { version = "1.18.1", default-features = false, features = ["v4"] } -chrono = { version = "0.4.27", default-features = false, features = ["clock"] } -serde_json = { version = "1", default-features = false, features = ["std"] } -rust_decimal = { version = "1.38.0", default-features = false } -indexmap = { version = "2.12.0", default-features = false, features = ["std"]} -parking_lot = { version = "0.12.4", default-features = false } - -[dependencies.sea-query] -version = "0.32.7" -default-features = false -features = [ - "backend-mysql", - "backend-postgres", - "backend-sqlite", - "thread-safe", - "with-chrono", - "with-json", - "with-rust_decimal", - "with-uuid", - "with-ipnetwork", - "with-mac_address", - "postgres-array", - "postgres-interval", - "postgres-vector", - "hashable-value" -] +pyo3 = { version = "0.29", default-features = false, features = ["macros"] } +parking_lot = { version = "0.12", default-features = false } +sqlparser = { version = "0.62", default-features = false, features = ["recursive", "recursive-protection", "visitor"] } [features] -default = ["optimize"] -optimize = [] +default = ["extension-module"] +extension-module = ["pyo3/extension-module"] [lints.clippy] dbg_macro = "warn" diff --git a/rapidquery/__init__.py b/rapidquery/__init__.py index 13d5942..1c44260 100644 --- a/rapidquery/__init__.py +++ b/rapidquery/__init__.py @@ -4,57 +4,3 @@ Build complex SQL queries effortlessly and efficiently, with a library that prioritizes both performance and ease of use. """ - -from ._lib import common as common -from ._lib import mysql as mysql -from ._lib import postgres as postgres -from ._lib import query as query -from ._lib import schema as schema -from ._lib import sqlite as sqlite -from ._lib import sqltypes as sqltypes - -# Export .common -Column = common.Column -ColumnRef = common.ColumnRef -Expr = common.Expr -ForeignKey = common.ForeignKey -Func = common.Func -TableName = common.TableName -Value = common.Value -all = common.all -any = common.any -not_ = common.not_ - -# Export .query -CaseStatement = query.CaseStatement -DeleteStatement = query.DeleteStatement -Frame = query.Frame -InsertStatement = query.InsertStatement -OnConflict = query.OnConflict -Ordering = query.Ordering -QueryStatement = query.QueryStatement -Returning = query.Returning -SelectLabel = query.SelectLabel -SelectStatement = query.SelectStatement -UpdateStatement = query.UpdateStatement -WindowStatement = query.WindowStatement -WithClause = query.WithClause -WithQuery = query.WithQuery - -# Export .schema -AlterTable = schema.AlterTable -AlterTableAddColumnOption = schema.AlterTableAddColumnOption -AlterTableAddForeignKeyOption = schema.AlterTableAddForeignKeyOption -AlterTableBaseOption = schema.AlterTableBaseOption -AlterTableDropColumnOption = schema.AlterTableDropColumnOption -AlterTableDropForeignKeyOption = schema.AlterTableDropForeignKeyOption -AlterTableModifyColumnOption = schema.AlterTableModifyColumnOption -AlterTableRenameColumnOption = schema.AlterTableRenameColumnOption -DropIndex = schema.DropIndex -DropTable = schema.DropTable -Index = schema.Index -IndexColumn = schema.IndexColumn -RenameTable = schema.RenameTable -SchemaStatement = schema.SchemaStatement -Table = schema.Table -TruncateTable = schema.TruncateTable diff --git a/rapidquery/_lib/__init__.pyi b/rapidquery/_lib/__init__.pyi deleted file mode 100644 index 978b592..0000000 --- a/rapidquery/_lib/__init__.pyi +++ /dev/null @@ -1,23 +0,0 @@ -""" -RapidQuery core which is written in Rust. -""" - -from __future__ import annotations - -from . import common as common -from . import mysql as mysql -from . import postgres as postgres -from . import query as query -from . import schema as schema -from . import sqlite as sqlite -from . import sqltypes as sqltypes - -__all__ = [ - "sqltypes", - "schema", - "query", - "common", - "sqlite", - "postgres", - "mysql", -] diff --git a/rapidquery/_lib/common.pyi b/rapidquery/_lib/common.pyi deleted file mode 100644 index 03688f1..0000000 --- a/rapidquery/_lib/common.pyi +++ /dev/null @@ -1,873 +0,0 @@ -from __future__ import annotations - -import typing - -from .query import SelectLabel, SelectStatement, WindowStatement -from .sqltypes import SQLTypeAbstract - -T = typing.TypeVar("T") -_ForeignKeyActions: typing.TypeAlias = typing.Literal[ - "CASCADE", "RESTRICT", "NO ACTION", "SET DEFAULT", "SET NULL" -] - -class _ColumnRefProperty(typing.Protocol): - __column_ref__: typing.Any - -class _TableNameProperty(typing.Protocol): - __table_name__: typing.Any - -_ColumnRefNew = typing.Union[ - "ColumnRef", - str, - _ColumnRefProperty, - type[_ColumnRefProperty], -] -_TableNameNew = typing.Union[ - "TableName", - str, - _TableNameProperty, - type[_TableNameProperty], -] -_ExprNew = typing.Any - -class Column(typing.Generic[T]): - """ - Defines a table column with its properties and constraints. - - Represents a complete column definition including: - - Column name and data type - - Constraints (primary key, unique, nullable) - - Auto-increment behavior - - Default values and generated columns - - Comments and extra specifications - - This class is used within Table to specify the structure - of table columns. It encapsulates all the properties that define how - a column behaves and what data it can store. - """ - - def __init__( - self, - name: str, - type: SQLTypeAbstract[T], - *, - primary_key: bool = False, - unique_key: bool = False, - nullable: bool | None = None, - auto_increment: bool = False, - extra: str | None = None, - comment: str | None = None, - default: typing.Any = ..., - generated: typing.Any = ..., - stored_generated: bool = False, - ) -> None: - """ - Construct a table column with column type. - - Args: - name: The name of this column as represented in the database. - type: The column's type. - primary_key: If `True`, marks this column as a primary key column. - unique_key: If `True`, marks this column as a unique key column. - nullable: If `False`, marks this column as "NOT NULL". If `True`, marks it as "NULL". - auto_increment: If `True`, marks this column as auto increment. Auto increment phrase is - depended on backend/dialect. - extra: Some extra options in custom string. - comment: (MySQL only) Column comment. - default: Represents default value for the column. - generated: Sets the column as generated. - stored_generated: Works with `generated` parameter together, which marks column as "STORED GENERATED". - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - def adapt(self, object: T | None) -> Value[T]: - """Shorthand for `Value(object, self.type)`.""" - ... - - @property - def auto_increment(self) -> bool: - """Whether this is a auto increment column.""" - ... - @auto_increment.setter - def auto_increment(self, value: bool) -> None: ... - @property - def comment(self) -> str | None: - """Comment describing this column.""" - ... - @comment.setter - def comment(self, value: str | None) -> None: ... - @property - def default(self) -> Expr | None: - """Default value for this column.""" - ... - @default.setter - def default(self, value: _ExprNew) -> None: ... - @property - def extra(self) -> str | None: - """Extra SQL specifications for this column.""" - ... - @extra.setter - def extra(self, value: str | None) -> None: ... - @property - def generated(self) -> Expr | None: - """Expression for generated column values.""" - ... - @generated.setter - def generated(self, value: _ExprNew) -> None: ... - @property - def name(self) -> str: - """Column name.""" - ... - @name.setter - def name(self, value: str) -> None: ... - @property - def nullable(self) -> bool | None: - """Whether this is a nullable column.""" - ... - @property - def primary_key(self) -> bool: - """Whether this is a nullable column.""" - ... - @primary_key.setter - def primary_key(self, value: bool) -> None: ... - @property - def stored_generated(self) -> bool: - """Whether this is a stored generated column.""" - ... - @stored_generated.setter - def stored_generated(self, value: bool) -> None: ... - @property - def type(self) -> SQLTypeAbstract[T]: - """Column type.""" - ... - - @property - def unique_key(self) -> bool: - """Whether this is a unique column.""" - ... - @unique_key.setter - def unique_key(self, value: bool) -> None: ... - @property - def __column_ref__(self) -> ColumnRef: ... - def to_expr(self) -> Expr: - """Shorthand for `Expr(self)`""" - ... - - def label(self, alias: str, window: WindowStatement | str | None = None) -> SelectLabel: - """Shorthand for `SelectLabel(self, alias, window)`""" - pass - -@typing.final -class ColumnRef: - """ - Represents a reference to a database column with optional table and schema qualification. - - This class is used to uniquely identify columns in SQL queries, supporting - schema-qualified and table-qualified column references. - - NOTE: this class is immutable and frozen, But you can use `.copy_with` method to make changes on it. - """ - - def __new__( - cls, - name: str, - table: str | None = ..., - schema: str | None = ..., - ) -> typing.Self: ... - def __copy__(self) -> typing.Self: ... - def __eq__(self, value: object, /) -> bool: ... - def __ne__(self, value: object, /) -> bool: ... - def __repr__(self, /) -> str: ... - def copy_with( - self, - *, - name: str = ..., - table: str | None = ..., - schema: str | None = ..., - ) -> typing.Self: - """ - Returns a copy of this `ColumnRef`, but with the changes you want. - """ - ... - - @property - def name(self) -> str: - """Column reference name.""" - ... - - @classmethod - def parse(cls, string: str) -> typing.Self: - """ - Parse a string representation of a column reference. - - Supports formats like: - - "column_name" - - "table.column_name" - - "schema.table.column_name" - """ - ... - - @property - def schema(self) -> str | None: - """Schema name""" - ... - - @property - def table(self) -> str | None: - """Table name""" - ... - - def __hash__(self) -> int: ... - def to_expr(self) -> Expr: - """Shorthand for `Expr(self)`""" - ... - - def label(self, alias: str, window: WindowStatement | str | None = None) -> SelectLabel: - """Shorthand for `SelectLabel(self, alias, window)`""" - pass - -@typing.final -class Expr: - """ - Represents a SQL expression that can be built into SQL code. - - This class provides a fluent interface for constructing complex SQL expressions - in a database-agnostic way. It supports arithmetic operations, comparisons, - logical operations, and database-specific functions. - - The class automatically handles SQL injection protection and proper quoting - when building the final SQL statement. - - NOTE: `Expr` is immutable, so by calling each method you will give a new instance - of it which includes new change(s). - """ - - def __new__(cls, value: _ExprNew, /) -> typing.Self: ... - def __add__(self, other: _ExprNew) -> typing.Self: - """Create an addition expression.""" - ... - - def __and__(self, other: _ExprNew) -> typing.Self: - """Create a logical AND expression.""" - ... - - def __eq__(self, other: _ExprNew) -> typing.Self: # type: ignore[override] - """Create an equality comparison expression.""" - ... - - def __ge__(self, other: _ExprNew) -> typing.Self: - """Create a greater-than-or-equal comparison expression.""" - ... - - def __gt__(self, other: _ExprNew) -> typing.Self: - """Create a greater-than comparison expression.""" - ... - - def __le__(self, other: _ExprNew) -> typing.Self: - """Create a less-than-or-equal comparison expression.""" - ... - - def __lshift__(self, other: _ExprNew) -> typing.Self: - """Create a bitwise left-shift expression.""" - ... - - def __lt__(self, other: _ExprNew) -> typing.Self: - """Create a less-than comparison expression.""" - ... - - def __mod__(self, other: _ExprNew) -> typing.Self: - """Create a modulo expression.""" - ... - - def __mul__(self, other: _ExprNew) -> typing.Self: - """Create a multiplication expression.""" - ... - - def __ne__(self, other: _ExprNew) -> typing.Self: # type: ignore[override] - """Create an inequality comparison expression.""" - ... - - def __neg__(self) -> typing.Self: - """Create a negation expression.""" - ... - - def __or__(self, other: _ExprNew) -> typing.Self: - """Create a logical OR expression.""" - ... - - def __radd__(self, other: _ExprNew) -> typing.Self: - """Create an addition expression.""" - ... - - def __rand__(self, other: _ExprNew) -> typing.Self: - """Create a logical AND expression.""" - ... - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - def __rlshift__(self, other: _ExprNew) -> typing.Self: - """Create a bitwise left-shift expression.""" - ... - - def __rmod__(self, other: _ExprNew) -> typing.Self: - """Create a modulo expression.""" - ... - - def __rmul__(self, other: _ExprNew) -> typing.Self: - """Create a multiplication expression.""" - ... - - def __ror__(self, other: _ExprNew) -> typing.Self: - """Create a logical OR expression.""" - ... - - def __rrshift__(self, other: _ExprNew) -> typing.Self: - """Create a bitwise right-shift expression.""" - ... - - def __rshift__(self, other: _ExprNew) -> typing.Self: - """Create a bitwise right-shift expression.""" - ... - - def __rsub__(self, other: _ExprNew) -> typing.Self: - """Create a subtraction expression.""" - ... - - def __rtruediv__(self, other: _ExprNew) -> typing.Self: - """Create a division expression.""" - ... - - def __sub__(self, other: _ExprNew) -> typing.Self: - """Create a subtraction expression.""" - ... - - def __truediv__(self, other: _ExprNew) -> typing.Self: - """Create a division expression.""" - ... - - @classmethod - def all(cls, statement: SelectStatement) -> typing.Self: - """Express a `ALL` sub-query expression.""" - ... - - @classmethod - def any(cls, statement: SelectStatement) -> typing.Self: - """Express a `ANY` sub-query expression.""" - ... - - @classmethod - def asterisk(cls) -> typing.Self: - """Returns asterisk '*' expression.""" - ... - - def between(self, a: _ExprNew, b: _ExprNew) -> typing.Self: - """Create a BETWEEN range comparison expression.""" - ... - - def bit_and(self, other: _ExprNew) -> typing.Self: - """Create a bitwise AND expression.""" - ... - - def bit_or(self, other: _ExprNew) -> typing.Self: - """Create a bitwise AND expression.""" - ... - - def cast_as(self, value: str) -> typing.Self: - """Create a `CAST` expression to convert to a specific SQL type.""" - ... - - @classmethod - def col(cls, value: _ColumnRefNew) -> typing.Self: - """ - Tries to convert the `value` into `ColumnRef`, and then converts it to `Expr`. - """ - ... - - @classmethod - def current_date(cls) -> typing.Self: - """Create an expression for the CURRENT_DATE SQL function.""" - ... - - @classmethod - def current_time(cls) -> typing.Self: - """Create an expression for the CURRENT_TIME SQL function.""" - ... - - @classmethod - def current_timestamp(cls) -> typing.Self: - """Create an expression for the CURRENT_TIMESTAMP SQL function.""" - ... - - @classmethod - def custom(cls, value: str) -> typing.Self: - """ - Create an expression from a custom SQL string. - - Warning: This method does not escape the input, so it should only - be used with trusted strings to avoid SQL injection vulnerabilities. - """ - ... - - @classmethod - def exists(cls, statement: SelectStatement) -> typing.Self: - """Express a `EXISTS` sub-query expression.""" - ... - - def in_(self, other: typing.Iterable[_ExprNew] | SelectStatement) -> typing.Self: - """Express a `IN` expression.""" - ... - - def is_(self, other: _ExprNew) -> typing.Self: - """Create an IS comparison expression.""" - ... - - def is_not(self, other: _ExprNew) -> typing.Self: - """Create an IS NOT comparison expression.""" - ... - - def is_not_null(self) -> typing.Self: - """Create an IS NOT NULL expression.""" - ... - - def is_null(self) -> typing.Self: - """Create an IS NULL expression.""" - ... - - def like(self, pattern: str, escape: str | None = ...) -> typing.Self: - """Create a `LIKE` pattern matching expression.""" - ... - - def not_between(self, a: _ExprNew, b: _ExprNew) -> typing.Self: - """Create a NOT BETWEEN range comparison expression.""" - ... - - def not_in(self, other: typing.Iterable[_ExprNew] | SelectStatement) -> typing.Self: - """Express a `NOT IN` expression.""" - ... - - def not_like(self, pattern: str, escape: str | None = ...) -> typing.Self: - """Create a NOT LIKE pattern matching expression.""" - ... - - def max(self) -> typing.Self: - """ - Create `MAX(self)` function call. - - Shorthand for `Func.max(self).to_expr()`. - """ - ... - - def min(self) -> typing.Self: - """ - Create `MIN(self)` function call. - - Shorthand for `Func.min(self).to_expr()`. - """ - ... - - def abs(self) -> typing.Self: - """ - Create `ABS(self)` function call. - - Shorthand for `Func.abs(self).to_expr()`. - """ - ... - - @classmethod - def null(cls) -> typing.Self: - """Create an expression representing the NULL value.""" - ... - - @classmethod - def some(cls, statement: SelectStatement) -> typing.Self: - """Express a `SOME` sub-query expression.""" - ... - - @classmethod - def val( - cls, - /, - value: T | None, - sql_type: SQLTypeAbstract[T] | None = ..., - ) -> typing.Self: - """Shorthand for `Expr(Value(value, sql_type))`""" - ... - - def label(self, alias: str, window: WindowStatement | str | None = None) -> SelectLabel: - """Shorthand for `SelectLabel(self, alias, window)`""" - pass - - def _to_sql(self, backend: str) -> str: ... - - __hash__ = None # type: ignore - -class ForeignKey: - """ - Specifies a foreign key relationship between tables. - - Defines referential integrity constraints including: - - Source columns (in the child table) - - Target columns (in the parent table) - - Actions for updates and deletes (CASCADE, RESTRICT, SET NULL, etc.) - - Optional naming for the constraint - - Foreign keys ensure data consistency by requiring that values in the - child table's columns match existing values in the parent table's columns. - """ - - def __init__( - self, - from_columns: typing.Iterable[_ColumnRefNew], - to_columns: typing.Iterable[_ColumnRefNew], - to_table: _TableNameNew | None = None, - name: str | None = None, - *, - on_delete: _ForeignKeyActions | None = None, - on_update: _ForeignKeyActions | None = None, - ) -> None: - """ - Construct a foreign key constraint. You can use it with `Table` or `AlterTable`. - - Args: - from_columns: An iterable of column references, or column names, or `Column` object. - The columns must defined and presented in the parent table. Cannot be empty. - to_columns: An iterable of column references, or column names, or `Column` object. - The columns must defined and presented in the target table. Cannot be empty. - to_table: The target table. If not specified, tries to detect it from `to_columns` argument. - name: The constraint name. - on_delete: The foreign key action for "ON DELETE". - on_update: The foreign key action for "ON UPDATE". - """ - ... - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @property - def from_columns(self) -> typing.Sequence[str]: - """Key columns.""" - ... - @from_columns.setter - def from_columns(self, value: typing.Iterable[_ColumnRefNew]) -> None: ... - @property - def name(self) -> str | None: - """Foreign key constraint name""" - ... - @name.setter - def name(self, value: str | None) -> None: ... - @property - def on_delete(self) -> _ForeignKeyActions | None: - """ON DELETE action.""" - ... - @on_delete.setter - def on_delete(self, value: _ForeignKeyActions | None) -> None: ... - @property - def on_update(self) -> _ForeignKeyActions | None: - """ON UPDATE action.""" - ... - @on_update.setter - def on_update(self, value: _ForeignKeyActions | None) -> None: ... - @property - def to_columns(self) -> typing.Sequence[str]: - """Referencing columns.""" - ... - @to_columns.setter - def to_columns(self, value: typing.Iterable[_ColumnRefNew]) -> None: ... - @property - def to_table(self) -> TableName: - """Referencing table.""" - ... - @to_table.setter - def to_table(self, value: TableName) -> None: ... - -@typing.final -class Func: - """ - Represents a SQL function call that can be used in expressions. - - This class provides a type-safe way to construct SQL function calls - with proper argument handling and database dialect support. - """ - - def __new__(cls, name: str, *args: _ExprNew) -> typing.Self: - """ - Construct a function expression. You can use ready-to-use classmethods instead of this. - - Args: - name: the function name in string. - args: function arguments. - """ - ... - - def __repr__(self, /) -> str: ... - @classmethod - def abs(cls, /, expr: _ExprNew) -> typing.Self: - """Create a ABS(expr) function call.""" - ... - - @classmethod - def avg(cls, /, expr: _ExprNew) -> typing.Self: - """Create a AVG(expr) function call.""" - ... - - @classmethod - def bit_and(cls, /, expr: _ExprNew) -> typing.Self: - """ - Create a BIT_AND(expr) function call - this is not supported on SQLite. - """ - ... - - @classmethod - def bit_or(cls, /, expr: _ExprNew) -> typing.Self: - """Create a BIT_OR(expr) function call - this is not supported on SQLite.""" - ... - - @classmethod - def char_length(cls, /, expr: _ExprNew) -> typing.Self: - """Create a CHAR_LENGTH(expr) function call.""" - ... - - @classmethod - def coalesce(cls, /, *exprs: _ExprNew) -> typing.Self: - """Create a COALESCE function call.""" - ... - - @classmethod - def count(cls, /, expr: _ExprNew) -> typing.Self: - """Create a COUNT(expr) function call.""" - ... - - @classmethod - def count_distinct(cls, /, expr: _ExprNew) -> typing.Self: - """Create a COUNT(DISTINCT expr) function call.""" - ... - - @classmethod - def dense_rank(cls, /) -> typing.Self: - """Create a DENSE_RANK() function call.""" - ... - - @classmethod - def greatest(cls, /, *exprs: _ExprNew) -> typing.Self: - """Create a GREATEST function call.""" - ... - - @classmethod - def if_null(cls, /, a: _ExprNew, b: _ExprNew) -> typing.Self: - """Create a IF_NULL(a, b) function call.""" - ... - - @classmethod - def least(cls, /, *exprs: _ExprNew) -> typing.Self: - """Create a LEAST function call.""" - ... - - @classmethod - def lower(cls, /, expr: _ExprNew) -> typing.Self: - """Create a LOWER(expr) function call.""" - ... - - @classmethod - def max(cls, /, expr: _ExprNew) -> typing.Self: - """Create a MAX(expr) function call.""" - ... - - @classmethod - def md5(cls, /, expr: _ExprNew) -> typing.Self: - """ - Create a MD5(expr) function call - this is only available in Postgres and MySQL. - """ - ... - - @classmethod - def min(cls, /, expr: _ExprNew) -> typing.Self: - """Create a MIN(expr) function call.""" - ... - - @classmethod - def now(cls) -> typing.Self: - """Create a NOW() function call.""" - ... - - @classmethod - def percent_rank(cls, /) -> typing.Self: - """Create a PERCENT_RANK() function call.""" - ... - - @classmethod - def random(cls, /) -> typing.Self: - """Create a RANDOM() function call.""" - ... - - @classmethod - def rank(cls, /) -> typing.Self: - """Create a RANK() function call.""" - ... - - @classmethod - def round(cls, /, expr: _ExprNew) -> typing.Self: - """Create a ROUND(expr) function call.""" - ... - - @classmethod - def round_with_precision(cls, /, a: _ExprNew, b: _ExprNew) -> typing.Self: - """Create a ROUND(a, b) function call.""" - ... - - @classmethod - def sum(cls, /, expr: _ExprNew) -> typing.Self: - """Create a SUM(expr) function call.""" - ... - - @classmethod - def upper(cls, /, expr: _ExprNew) -> typing.Self: - """Create a UPPER(expr) function call.""" - ... - - @classmethod - def cast_as(cls, /, expr: _ExprNew, alias: str) -> typing.Self: - """Call CAST function with a custom type.""" - ... - - def to_expr(self) -> Expr: - """Shorthand for `Expr(self)`""" - ... - - def label(self, alias: str, window: WindowStatement | str | None = None) -> SelectLabel: - """Shorthand for `SelectLabel(self, alias, window)`""" - pass - -@typing.final -class TableName: - """ - Represents a table name reference with optional schema, database, and alias. - - This class encapsulates a table name that can include: - - The base table name - - Optional schema/namespace qualification - - Optional database qualification (for systems that support it) - - The class provides parsing capabilities for string representations - and supports comparison operations. - - NOTE: this class is immutable and frozen. - """ - - def __new__( - cls, - name: str, - schema: str | None = None, - database: str | None = None, - alias: str | None = None, - ) -> typing.Self: ... - def __copy__(self) -> typing.Self: ... - def __eq__(self, value: object, /) -> bool: - """Return self==value.""" - ... - - def __ne__(self, value: object, /) -> bool: - """Return self!=value.""" - ... - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def alias(self) -> str | None: ... - def copy_with( - self, - *, - name: str = ..., - schema: str | None = ..., - database: str | None = ..., - alias: str | None = ..., - ) -> typing.Self: - """Create a shallow copy of this TableName.""" - ... - - @property - def database(self) -> str | None: ... - @property - def name(self) -> str: ... - @classmethod - def parse(cls, string: str) -> typing.Self: - """ - Parse a string representation of a table name. - - Supports formats like: - - "table_name" - - "schema.table_name" - - "database.schema.table_name" - """ - ... - - @property - def schema(self) -> str | None: ... - def __hash__(self) -> int: ... - -class Value(typing.Generic[T]): - """ - Bridges Python types, Rust types, and SQL types for seamless data conversion. - - This class handles validation, adaptation, and conversion between different - type systems used in the application stack. - - NOTE: this class is immutable and frozen. - """ - - def __init__( - self, - value: T | None, - sql_type: SQLTypeAbstract[T] | None = ..., - ) -> None: - """ - Construct a new `Value`. - - It can automatically detects the type of your value and selects appropriate Rust and SQL types. - For example: - - Python `int` becomes `BIGINT` SQL type (`BigIntegerType`) - - Python `dict` or `list` becomes `JSON` SQL type (`JsonType`) - - Python `float` becomes `DOUBLE` SQL type (`DoubleType`) - - However, for more accurate type selection, use the `sql_type` parameter. - """ - ... - - def __hash__(self, /) -> int: ... - def __repr__(self, /) -> str: ... - @property - def sql_type(self) -> SQLTypeAbstract[T]: - """The defined or detected SQL type.""" - ... - - @property - def value(self) -> T | None: - """Converts the adapted value back to Python.""" - ... - - def to_expr(self) -> Expr: - """Shorthand for `Expr(self)`""" - ... - -def all(arg1: Expr, *args: Expr) -> Expr: - """ - Create a logical AND condition that is true only if all conditions are true. - - This is equivalent to SQL's AND operator applied to multiple expressions. - """ - ... - -def any(arg1: Expr, *args: Expr) -> Expr: - """ - Create a logical OR condition that is true if any condition is true. - - This is equivalent to SQL's OR operator applied to multiple expressions. - """ - ... - -def not_(arg: Expr) -> Expr: - """Create a logical NOT.""" - ... diff --git a/rapidquery/_lib/mysql.pyi b/rapidquery/_lib/mysql.pyi deleted file mode 100644 index 4d21ee8..0000000 --- a/rapidquery/_lib/mysql.pyi +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import annotations - -__all__ = [] diff --git a/rapidquery/_lib/postgres.pyi b/rapidquery/_lib/postgres.pyi deleted file mode 100644 index 4d21ee8..0000000 --- a/rapidquery/_lib/postgres.pyi +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import annotations - -__all__ = [] diff --git a/rapidquery/_lib/query.pyi b/rapidquery/_lib/query.pyi deleted file mode 100644 index a9c6402..0000000 --- a/rapidquery/_lib/query.pyi +++ /dev/null @@ -1,1836 +0,0 @@ -from __future__ import annotations - -import typing - -from .common import Expr, Func, Value, _ColumnRefNew, _ExprNew, _TableNameNew - -_BackendName: typing.TypeAlias = typing.Literal[ - "sqlite", "postgresql", "postgres", "mysql" -] - -class CaseStatement: - """ - Represents a `CASE WHEN` SQL statement. - """ - - def __init__(self) -> None: - """ - Construct a `CASE WHEN` statement. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement( - rq.SelectLabel( - rq.CaseStatement() - .when(rq.Expr.col("aspect").in_([2, 4]), True) - .else_(False), - "is_even", - ), - ) - .from_table("glyph") - .to_sql("postgres") - ) - # SELECT (CASE WHEN ("aspect" IN (2, 4)) THEN TRUE ELSE FALSE END) AS "is_even" FROM "glyph" - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - def else_(self, result: _ExprNew) -> typing.Self: - """ - Ends the case statement with the final ELSE result. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement( - rq.SelectLabel( - rq.CaseStatement() - .when(rq.Expr.col("aspect") > 0, "positive") - .when(rq.Expr.col("aspect") < 0, "negative") - .else_("zero"), - "polarity", - ), - ) - .from_table("glyph") - .to_sql("postgres") - ) - # SELECT (CASE WHEN ("aspect" > 0) THEN 'positive' WHEN ("aspect" < 0) THEN 'negative' ELSE 'zero' END) AS "polarity" FROM "glyph" - ``` - """ - ... - - def when(self, condition: Expr, result: _ExprNew) -> typing.Self: - """ - Adds new `WHEN` to existing case statement. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement( - rq.CaseStatement() - .when( - rq.any( - rq.Expr.col("font_size") > 48, - rq.Expr.col("size_w") > 500, - ), - "large", - ) - .when( - rq.any( - rq.Expr.col("font_size").between(24, 48), - rq.Expr.col("size_w").between(300, 500), - ), - "medium", - ) - .else_("small") - .label("char_size"), - ) - .from_table("characters") - .to_sql("postgres") - ) - # SELECT - # (CASE WHEN ("font_size" > 48 OR "size_w" > 500) THEN 'large' - # WHEN (("font_size" BETWEEN 24 AND 48) OR ("size_w" BETWEEN 300 AND 500)) THEN 'medium' - # ELSE 'small' END) AS "char_size" - # FROM "characters" - ``` - """ - ... - - def to_expr(self) -> Expr: - """Shorthand for `Expr(self)`""" - ... - - def label( - self, alias: str, window: WindowStatement | str | None = None - ) -> SelectLabel: - """Shorthand for `SelectLabel(self, alias, window)`""" - pass - -class DeleteStatement(QueryStatement): - """ - Builds `DELETE` SQL statements with a fluent interface. - - Provides a chainable API for constructing DELETE queries with support for: - - WHERE conditions for filtering - - LIMIT for restricting deletion count - - ORDER BY for determining deletion order - - RETURNING clauses for getting deleted data - """ - - def __init__(self, table: _TableNameNew) -> None: - """ - Construct a `DELETE` statement. - - Args: - table: The table to delete from. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.DeleteStatement("glyph") - .where(rq.any(rq.Expr.col("id") < 1, rq.Expr.col("id") > 10)) - .to_sql("mysql") - ) - # DELETE FROM `glyph` WHERE `id` < 1 OR `id` > 10 - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - def clear_order_by(self) -> typing.Self: - """Remove orders from statement.""" - ... - - def clear_where(self) -> typing.Self: - """Remove where conditions from statement.""" - ... - - def from_table(self, table: _TableNameNew) -> typing.Self: - """ - Override the table to delete from. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.DeleteStatement("name_1") - .where(rq.Expr.col("id") == 1) - .from_table("name_2") - .to_sql("sqlite") - ) - # DELETE FROM "name_2" WHERE "id" = 1 - ``` - """ - ... - - def limit(self, n: int) -> typing.Self: - """Limit the number of rows to delete.""" - ... - - def order_by(self, clause: Ordering) -> typing.Self: - """ - Specify the order in which to delete rows. Typically used with - `.limit` method to delete specific rows. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.DeleteStatement("glyph") - .where(rq.Expr.col("id") < 100) - .limit(20) - .order_by(rq.Ordering("id", "DESC")) - .to_sql("postgres") - ) - # DELETE FROM "glyph" WHERE "id" < 100 ORDER BY "id" DESC LIMIT 20 - ``` - """ - ... - - def returning(self, clause: Returning) -> typing.Self: - """ - Specify columns to return from the inserted rows. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.DeleteStatement("glyph") - .where(rq.Expr.col("id") < 30) - .returning(rq.Returning("id")) - .to_sql("sqlite") - ) - # DELETE FROM "glyph" WHERE "id" < 30 RETURNING "id" - ``` - """ - ... - - def where(self, condition: Expr) -> typing.Self: - """ - Add a `WHERE` condition to filter rows to delete. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.DeleteStatement("glyph") - .where(rq.Expr.col("id") < 100) - .where(rq.Expr.col("name").like("%A")) - .to_sql("postgres") - ) - # DELETE FROM "glyph" WHERE "id" < 100 AND "name" LIKE '%A' - ``` - """ - ... - -@typing.final -class Frame: - """ - Defines the window frame start and end boundaries for window functions. - - A frame allows you to constrain the set of rows used by the window function - to a subset relative to the current row. - - Use the provided classmethods to construct specific frame boundaries. - """ - - @classmethod - def current_row(cls) -> typing.Self: - """The boundary is the current row being processed.""" - ... - - @classmethod - def following(cls, val: int) -> typing.Self: - """ - The boundary is a fixed number of rows/values after the current row. - - Args: - val: The number of rows or the value range following the current row. - """ - ... - - @classmethod - def preceding(cls, val: int) -> typing.Self: - """ - The boundary is a fixed number of rows/values before the current row. - - Args: - val: The number of rows or the value range preceding the current row. - """ - ... - - @classmethod - def unbounded_following(cls) -> typing.Self: - """The boundary is the last row of the partition.""" - ... - - @classmethod - def unbounded_preceding(cls) -> typing.Self: - """The boundary is the first row of the partition.""" - ... - -class InsertStatement(QueryStatement): - """ - Builds `INSERT` SQL statements with a fluent interface. - - Provides a chainable API for constructing INSERT queries with support for: - - Single or multiple row insertion - - Conflict resolution (UPSERT) - - RETURNING clauses - - REPLACE functionality - - Default values - """ - - def __init__(self, table: _TableNameNew) -> None: - """ - Construct a `INSERT` statement. - - Args: - table: The target table for insertion. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=4.21, image="123") - .to_sql("mysql") - ) - # INSERT INTO `glyph` (`aspect`, `image`) VALUES (4.21, '123') - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - def columns(self, *args: _ColumnRefNew) -> typing.Self: - """ - Specify (override) the columns for insertion. - - There's no need to use this method when you're specifying column - names in `.values` method. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .columns("aspect", "image") - .values(5.15, "12A") - .values(4.21, "123") - .to_sql("mysql") - ) - # INSERT INTO `glyph` (`aspect`, `image`) VALUES (5.15, '12A'), (4.21, '123') - ``` - """ - ... - - def into(self, table: _TableNameNew) -> typing.Self: - """ - Override the target table for insertion. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("name_1") - .values(aspect=4.21, image="123") - .into("name_2") - .to_sql("sqlite") - ) - # INSERT INTO "name_2" ("aspect", "image") VALUES (4.21, '123') - ``` - """ - ... - - def on_conflict(self, action: OnConflict) -> typing.Self: - """Specify conflict resolution behavior (UPSERT).""" - ... - - def or_default_values(self, rows: int = 1) -> typing.Self: - """ - Use DEFAULT VALUES if no values were specified. The `rows` - Specifies number of rows to insert with default values. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.InsertStatement("glyph").or_default_values().to_sql("postgres") - # INSERT INTO "glyph" VALUES (DEFAULT) - - stmt = ( - rq.InsertStatement("glyph") - .or_default_values() - .values(aspect=6.7) - .to_sql("postgres") - ) - # INSERT INTO "glyph" ("aspect") VALUES (6.7) - ``` - """ - ... - - def replace(self) -> typing.Self: - """ - Convert this INSERT to a REPLACE statement. - - REPLACE will delete existing rows that conflict with the new row - before inserting. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .replace() - .values(aspect=5.15, image="12A") - .to_sql("sqlite") - ) - # REPLACE INTO "glyph" ("aspect", "image") VALUES (5.15, '12A') - ``` - """ - ... - - def returning(self, clause: Returning) -> typing.Self: - """ - Specify columns to return from the inserted rows. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .values(image="12A") - .returning(rq.Returning.all()) - .to_sql("sqlite") - ) - # INSERT INTO "glyph" ("image") VALUES ('12A') RETURNING * - - stmt = ( - rq.InsertStatement("glyph") - .values(image="12A") - .returning(rq.Returning("id")) - .to_sql("sqlite") - ) - # INSERT INTO "glyph" ("image") VALUES ('12A') RETURNING "id" - ``` - """ - ... - - def select_from(self, statement: SelectStatement) -> typing.Self: - """ - Specify a select query whose values to be inserted. Raises `ValueError` if - `self`s columns length and `statement`s columns length has mismatch. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .columns("aspect", "image") - .select_from( - rq.SelectStatement(rq.Expr.col("aspect"), rq.Expr.col("image")) - .from_table("glyph") - .where(rq.Expr.col("image").like("0%")) - ) - .to_sql("mysql") - ) - # INSERT INTO `glyph` (`aspect`, `image`) - # SELECT `aspect` AS `aspect`, `image` AS `image` FROM `glyph` WHERE `image` LIKE '0%' - - stmt = ( - rq.InsertStatement("glyph") - .columns("image") - .select_from( - rq.SelectStatement("hello").where( - rq.not_(rq.Expr.exists(rq.SelectStatement("world"))) - ) - ) - .to_sql("postgres") - ) - # INSERT INTO "glyph" ("image") SELECT 'hello' WHERE NOT EXISTS(SELECT 'world') - ``` - """ - ... - - @typing.overload - def values(self, *args: _ExprNew) -> typing.Self: - """ - Specify values to insert. Also you can specify columns using keyword arguments. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .columns("aspect", "image") - .values(2, rq.Func.cast_as("2020-02-02 00:00:00", "DATE")) - .to_sql("postgres") - ) - # INSERT INTO "glyph" ("aspect", "image") VALUES (2, CAST('2020-02-02 00:00:00' AS DATE)) - ``` - """ - ... - - @typing.overload - def values(self, **kwds: _ExprNew) -> typing.Self: - """ - Specify values to insert. Also you can specify columns using keyword arguments. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=2, image=rq.Func.cast_as("2020-02-02 00:00:00", "DATE")) - .to_sql("postgres") - ) - # INSERT INTO "glyph" ("aspect", "image") VALUES (2, CAST('2020-02-02 00:00:00' AS DATE)) - ``` - """ - ... - -class OnConflict: - """ - Specifies conflict resolution behavior for `INSERT` statements. - - Handles situations where an `INSERT` would violate a unique constraint - or primary key. - - This corresponds to `INSERT ... ON CONFLICT` in PostgreSQL and - `INSERT ... ON DUPLICATE KEY UPDATE` in MySQL. - """ - - def __init__(self, *targets: _ColumnRefNew) -> None: - """ - Construct a new `OnConflict` instance. - - Args: - targets: Target columns. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict(rq.OnConflict("id").do_update(image="ex")) - ) - stmt.to_sql("postgres") - # INSERT INTO "glyph" ("aspect", "image") VALUES (3.1415, 'abcd') - # ON CONFLICT ("id") DO UPDATE SET "image" = 'ex' - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - def action_where(self, condition: Expr) -> typing.Self: - """ - Add a `WHERE` clause to the conflict action (conditional update). - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict( - rq.OnConflict("id") - .do_update(image="ex") - .action_where(rq.Expr.col("aspect").is_null()) - ) - ) - stmt.to_sql("postgres") - # INSERT INTO "glyph" ("aspect", "image") VALUES (3.1415, 'abcd') - # ON CONFLICT ("id") DO UPDATE SET "image" = 'ex' WHERE "aspect" IS NULL - - stmt.to_sql("mysql") - # INSERT INTO `glyph` (`aspect`, `image`) VALUES (3.1415, 'abcd') - # ON DUPLICATE KEY UPDATE `image` = 'ex' - ``` - """ - ... - - def do_nothing(self, *keys: _ColumnRefNew) -> typing.Self: - """ - Specify `DO NOTHING` action for conflicts. - - When a conflict occurs, the conflicting row will be skipped. - - `keys` parameter provides primary keys if you are using MySQL, for MySQL specific polyfill. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict(rq.OnConflict("id").do_nothing()) - ) - stmt.to_sql("postgres") - # INSERT INTO "glyph" ("aspect", "image") VALUES (3.1415, 'abcd') ON CONFLICT ("id") DO NOTHING - - stmt.to_sql("mysql") - # INSERT INTO `glyph` (`aspect`, `image`) VALUES (3.1415, 'abcd') ON DUPLICATE KEY IGNORE - - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict(rq.OnConflict("id").do_nothing("id")) - ) - stmt.to_sql("postgres") - # INSERT INTO "glyph" ("aspect", "image") VALUES (3.1415, 'abcd') ON CONFLICT ("id") DO NOTHING - - stmt.to_sql("mysql") - # INSERT INTO `glyph` (`aspect`, `image`) VALUES (3.1415, 'abcd') ON DUPLICATE KEY UPDATE `id` = `id` - ``` - """ - ... - - def do_update(self, *args: _ColumnRefNew, **kwds: _ExprNew) -> typing.Self: - """ - Specify `DO UPDATE` action for conflicts using column names, or with explicit values. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict(rq.OnConflict("id").do_update("aspect", image=rq.Expr(1) + 2)) - ) - stmt.to_sql("postgres") - # INSERT INTO "glyph" ("aspect", "image") VALUES (3.1415, 'abcd') - # ON CONFLICT ("id") DO UPDATE SET "aspect" = "excluded"."aspect", "image" = 1 + 2 - - stmt.to_sql("mysql") - # INSERT INTO `glyph` (`aspect`, `image`) VALUES (3.1415, 'abcd') - # ON DUPLICATE KEY UPDATE `aspect` = VALUES(`aspect`), `image` = 1 + 2 - ``` - """ - ... - - def target_where(self, condition: Expr) -> typing.Self: - """ - Add a `WHERE` clause to the conflict target (partial unique index). - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict( - rq.OnConflict("id") - .do_update(image="ex") - .target_where(rq.Expr.col("aspect").is_null()) - ) - ) - stmt.to_sql("postgres") - # INSERT INTO "glyph" ("aspect", "image") VALUES (3.1415, 'abcd') - # ON CONFLICT ("id") WHERE "aspect" IS NULL DO UPDATE SET "image" = 'ex' - - stmt.to_sql("mysql") - # INSERT INTO `glyph` (`aspect`, `image`) VALUES (3.1415, 'abcd') - # ON DUPLICATE KEY UPDATE `image` = 'ex' - ``` - """ - ... - -@typing.final -class Ordering: - """ - Specifies ordering behavior statements. - - NOTE: this class is immutable and frozen. - """ - - def __new__( - cls, - target: Expr | _ColumnRefNew, - order: typing.Literal["ASC", "DESC"] = "ASC", - null_ordering: typing.Literal["FIRST", "LAST"] | None = None, - ) -> typing.Self: - """ - Construct a new `Ordering` instance. - - Args: - target: Ordering target. Can be an expression (`Expr`) or a column. - order: Ascendant (`"ASC"`) or descendant (`"DESC"`). - null_ordering: Null ordering option. `"FIRST"` means `NULLS FIRST` SQL, and - `"LAST"` means `NULLS LAST` SQL. - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @property - def null_order(self) -> typing.Literal["FIRST", "LAST"] | None: ... - @property - def order(self) -> typing.Literal["ASC", "DESC"]: ... - @property - def target(self) -> Expr: - """Target expression.""" - ... - -class QueryStatement: - """Subclass of query statements.""" - - def build(self, backend: _BackendName, /) -> tuple[str, tuple[Value, ...]]: - """Build the SQL statement with parameter values.""" - ... - - def to_sql(self, backend: _BackendName, /) -> str: - """ - Build a SQL string representation. - - **This method is unsafe and can cause SQL injection.** use `.build()` method instead. - """ - ... - -@typing.final -class Returning: - """ - `RETURNING` clause. - - Works on PostgreSQL and SQLite>=3.35.0. - """ - - def __new__(cls, *args: Expr | _ColumnRefNew) -> typing.Self: - """ - Construct a new `RETURNING` clause. - - Args: - args: Returning expressions. Can be expressions (`Expr`) or column references. - If any `"*"`, or `ColumnRef("*")` found, ignores all others and returns `RETURNING *` clause. - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @classmethod - def all(cls) -> typing.Self: - """Same as `self("*")`.""" - ... - -class SelectLabel: - """ - Represents a column expression with an optional alias in a `SELECT` statement. - - Used to specify both the expression to select and an optional alias name - for the result column. - """ - - def __init__( - self, - expr: _ExprNew, - alias: str | None = None, - window: WindowStatement | str | None = None, - ) -> None: - """ - Construct a new `SelectLabel` instance. - - Args: - expr: Target expression to select. - alias: Target label, i.e. ` AS `. - window: Window statement or window name. - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @property - def alias(self) -> str | None: ... - @property - def expr(self) -> Expr: ... - @property - def window(self) -> WindowStatement | str | None: ... - -class SelectStatement(QueryStatement): - """ - Builds SELECT SQL statements with a fluent interface. - - Provides a chainable API for constructing SELECT queries with support for: - - Column selection with expressions and aliases - - Table and subquery sources - - Filtering with WHERE and HAVING - - Joins (inner, left, right, full, cross, lateral) - - Grouping and aggregation - - Ordering and pagination - - Set operations (UNION, EXCEPT, INTERSECT) - - Row locking for transactions - - DISTINCT queries - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement() - .columns("character", "fonts.name") - .from_table("characters") - .join("fonts", rq.Expr.col("characters.font_id") == rq.Expr.col("fonts.id"), "LEFT") - .where(rq.Expr.col("size_w").in_((3, 4))) - .where(rq.Expr.col("character").like("A%")) - .to_sql("postgres") - ) - # SELECT "character" AS "character", "fonts"."name" AS "name" FROM "characters" - # LEFT JOIN "fonts" ON "characters"."font_id" = "fonts"."id" - # WHERE "size_w" IN (3, 4) AND "character" LIKE 'A%' - ``` - """ - - def __init__(self, *exprs: SelectLabel | _ExprNew) -> None: - """ - Construct a new `SELECT` statement. - - Args: - exprs: Select expressions. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.SelectStatement(1, "hello", "font").to_sql("postgres") - # SELECT 1, 'hello', 'font' - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - def clear_order_by(self) -> typing.Self: - """Remove orders from statement.""" - ... - - def clear_where(self) -> typing.Self: - """Remove where conditions from statement.""" - ... - - def columns(self, *args: _ColumnRefNew) -> typing.Self: - """ - Add select target expressions. - - Works same as `self.exprs(Expr.col(i) for i in args)`, but much easier and faster. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement() - .columns("character", "size_w", "size_h") - .from_table("characters") - .to_sql("mysql") - ) - # SELECT `character` AS `character`, `size_w` AS `size_w`, `size_h` AS `size_h` FROM `characters` - ``` - """ - ... - - def distinct(self, *on: _ColumnRefNew) -> typing.Self: - """ - Changes `SELECT` statement into `SELECT DISTINCT` statement. - - Args: - on: Column references. If specified, uses *Postgres-ONLY* `SELECT DISTINCT ON ...` syntax. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement() - .distinct() - .columns("character", "size_w", "size_h") - .from_table("characters") - .to_sql("mysql") - ) - # SELECT DISTINCT `character` AS `character`, `size_w` AS `size_w`, `size_h` AS `size_h` FROM `characters` - - stmt = ( - rq.SelectStatement() - .distinct("character") - .columns("character", "size_w", "size_h") - .to_sql("postgres") - ) - # SELECT DISTINCT ON ("character") "character" AS "character", "size_w" AS "size_w", "size_h" AS "size_h" - ``` - """ - ... - - def exprs(self, *args: SelectLabel | _ExprNew) -> typing.Self: - """ - Add select target expressions. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement() - .exprs(rq.Func.max(rq.Expr.col("id")), rq.Expr.val(0) + 1 + 2 + 3) - .from_table("characters") - .to_sql("sqlite") - ) - # SELECT MAX("id"), 0 + 1 + 2 + 3 FROM "characters" - ``` - """ - ... - - def from_function(self, function: Expr | Func, alias: str) -> typing.Self: - """ - Select from function call. - - Args: - function: An expression or a function. If it is `Expr`, it should be a function call - and nothing more, or `ValueError` will be raised. - alias: label, i.e. `FROM AS `. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement(rq.Expr.asterisk()) - .from_function(rq.Func.random(), "func") - .to_sql("postgres") - ) - # SELECT * FROM RANDOM() AS "func" - ``` - """ - ... - - def from_subquery(self, subquery: SelectStatement, alias: str) -> typing.Self: - """ - Select from `SELECT` subquery. - - Args: - subquery: A select statement. - alias: label, i.e. `FROM AS `. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement() - .columns("image") - .from_subquery( - rq.SelectStatement().columns("image", "aspect").from_table("glyph"), - "subglyph", - ) - .to_sql("postgres") - ) - # SELECT "image" AS "image" FROM - # (SELECT "image" AS "image", "aspect" AS "aspect" FROM "glyph") AS "subglyph" - ``` - """ - ... - - def from_table(self, table: _TableNameNew) -> typing.Self: - """ - Select from table. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement(rq.Expr.asterisk()) - .from_table("characters") - .from_table("fonts") - .where(rq.Expr.col("fonts.id") == rq.Expr.col("characters.font_id")) - .to_sql("postgres") - ) - # SELECT * FROM "characters", "fonts" WHERE "fonts"."id" = "characters"."font_id" - ``` - """ - ... - - def group_by(self, *groups: Expr | _ColumnRefNew) -> typing.Self: - """ - Add group by expressions or column references. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement() - .columns("character") - .from_table("characters") - .group_by(rq.Expr.col("size_w"), rq.Expr.col("size_h")) - .to_sql("postgres") - ) - # SELECT "character" AS "character" FROM "characters" GROUP BY "size_w", "size_h" - ``` - """ - ... - - def having(self, condition: Expr) -> typing.Self: - """ - Add having condition expression. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement(rq.Func.max(rq.Expr.col("image"))) - .columns("aspect") - .from_table("glyph") - .group_by(rq.Expr.col("aspect")) - .having(rq.Expr.col("aspect") > 2) - .having(rq.Expr.col("aspect") < 8) - .to_sql("postgres") - ) - # SELECT MAX("image"), "aspect" AS "aspect" FROM "glyph" - # GROUP BY "aspect" HAVING "aspect" > 2 AND "aspect" < 8 - ``` - """ - ... - - def join( - self, - table: _TableNameNew, - on: Expr | None = None, - type: typing.Literal["CROSS", "FULL", "INNER", "LEFT", "RIGHT"] | None = None, - ) -> typing.Self: - """ - Join with other table. - - Args: - table: The table to join with. - on: Join condition. - type: Join type. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement() - .columns("character", "fonts.name") - .from_table("characters") - .join( - "fonts", - rq.Expr.col("characters.font_id") == rq.Expr.col("fonts.id"), - "RIGHT", - ) - .to_sql("mysql") - ) - # SELECT `character` AS `character`, `fonts`.`name` AS `name` FROM `characters` - # RIGHT JOIN `fonts` ON `characters`.`font_id` = `fonts`.`id` - ``` - """ - ... - - def join_function( - self, - function: Func | Expr, - alias: str, - on: Expr | None = None, - type: typing.Literal["CROSS", "FULL", "INNER", "LEFT", "RIGHT"] | None = None, - ) -> typing.Self: - """ - Join with a function call. - - Args: - function: An expression or a function. If it is `Expr`, it should be a function call - and nothing more, or `ValueError` will be raised. - alias: label. - on: Join condition. - type: Join type. - """ - ... - - def join_subquery( - self, - subquery: SelectStatement, - alias: str, - on: Expr | None = None, - type: typing.Literal["CROSS", "FULL", "INNER", "LEFT", "RIGHT"] | None = None, - lateral: bool = False, - ) -> typing.Self: - """ - Join with other `SELECT` statement. - - Args: - subquery: Select statement. - alias: label. - on: Join condition. - type: Join type. - literal: If `True`, uses `JOIN LATERAL` syntax, which *is not supported by SQLite*. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement() - .columns("name") - .from_table("fonts") - .join_subquery( - rq.SelectStatement().columns("id").from_table("glyph"), - "sub_glyph", - rq.Expr.col("fonts.id") == rq.Expr.col("sub_glyph.id"), - "LEFT", - ) - .to_sql("mysql") - ) - # SELECT `name` AS `name` FROM `fonts` - # LEFT JOIN (SELECT `id` AS `id` FROM `glyph`) AS `sub_glyph` ON `fonts`.`id` = `sub_glyph`.`id` - ``` - """ - ... - - def limit(self, n: int) -> typing.Self: - """Limit the number of rows to select.""" - ... - - def lock( - self, - type: typing.Literal[ - "UPDATE", "NO KEY UPDATE", "SHARE", "KEY SHARE" - ] = "UPDATE", - behavior: typing.Literal["NOWAIT", "SKIP"] | None = None, - tables: typing.Iterable[_TableNameNew] = (), - ) -> typing.Self: - """ - Row locking (if is supported by backend/dialect). - - Args: - type: Row locking type. - behavior: Row locking behavior. - tables: Row locking tables. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement() - .columns("character") - .from_table("characters") - .where(rq.Expr.col("id") == 5) - .lock() - .to_sql("postgres") - ) - # SELECT "character" AS "character" FROM "characters" WHERE "id" = 5 FOR UPDATE - - stmt = ( - rq.SelectStatement() - .columns("character") - .from_table("characters") - .where(rq.Expr.col("id") == 5) - .lock(tables=["glyph"]) - .to_sql("postgres") - ) - # SELECT "character" AS "character" FROM "characters" WHERE "id" = 5 FOR UPDATE OF "glyph" - - stmt = ( - rq.SelectStatement() - .columns("character") - .from_table("characters") - .where(rq.Expr.col("id") == 5) - .lock(behavior="NOWAIT") - .to_sql("postgres") - ) - # SELECT "character" AS "character" FROM "characters" WHERE "id" = 5 FOR UPDATE NOWAIT - - stmt = ( - rq.SelectStatement() - .columns("character") - .from_table("characters") - .where(rq.Expr.col("id") == 5) - .lock("SHARE", behavior="SKIP", tables=["glyph"]) - .to_sql("postgres") - ) - # SELECT "character" AS "character" FROM "characters" WHERE "id" = 5 FOR SHARE OF "glyph" SKIP LOCKED - ``` - """ - ... - - def offset(self, n: int) -> typing.Self: - """Set offset.""" - ... - - def order_by(self, clause: Ordering) -> typing.Self: - """Specify the order in which to delete rows.""" - ... - - def union( - self, - statement: SelectStatement, - type: typing.Literal["ALL", "INTERSECT", "DISTINCT", "EXCEPT"] = "DISTINCT", - ) -> typing.Self: - """ - Union with multiple `SELECT` statement that **must have the same selected fields**. - - Args: - statement: Select statement. - type: Union type. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement("hello") - .union(rq.SelectStatement("world"), "ALL") - .to_sql("postgres") - ) - # SELECT 'hello' UNION ALL (SELECT 'world') - ``` - """ - ... - - def where(self, condition: Expr) -> typing.Self: - """ - Add select `WHERE` condition. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement() - .columns("character") - .from_table("characters") - .where(rq.Expr.col("id") == 5) - .where(rq.Expr.col("name").like("%A")) - .to_sql("postgres") - ) - # SELECT "character" AS "character" FROM "characters" WHERE "id" = 5 AND "name" LIKE '%A' - ``` - """ - ... - - def window(self, name: str, statement: WindowStatement) -> typing.Self: - """ - Add `WINDOW` to statement. - - Args: - name: Window name. - statement: Window statement. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.SelectStatement( - rq.Expr.col("character").label("w", "C") - ) - .window("C", rq.WindowStatement("font_size")) - .from_table("characters") - .to_sql("postgres") - ) - # SELECT "character" OVER "C" AS "w" FROM "characters" WINDOW "C" AS (PARTITION BY "font_size") - ``` - """ - ... - - def to_expr(self) -> Expr: - """Shorthand for `Expr(self)`""" - ... - - def label( - self, alias: str, window: WindowStatement | str | None = None - ) -> SelectLabel: - """Shorthand for `SelectLabel(self, alias, window)`""" - pass - -class UpdateStatement(QueryStatement): - """ - Builds UPDATE SQL statements with a fluent interface. - - Provides a chainable API for constructing UPDATE queries with support for: - - Setting column values - - WHERE conditions for filtering - - LIMIT for restricting update count - - ORDER BY for determining update order - - RETURNING clauses for getting updated data - """ - - def __init__(self, table: _TableNameNew) -> None: - """ - Construct a new `UPDATE` statement. - - Args: - table: The table to update. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.UpdateStatement("glyph") - .values(aspect=1.23, image=123) - .where(rq.Expr.col("id") == 1) - .to_sql("sqlite") - ) - # UPDATE "glyph" SET "aspect" = 1.23, "image" = 123 WHERE "id" = 1 - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - def clear_order_by(self) -> typing.Self: - """Remove orders from statement.""" - ... - - def clear_where(self) -> typing.Self: - """Remove where conditions from statement.""" - ... - - def from_table(self, table: _TableNameNew) -> typing.Self: - """ - Update using data from another table (`UPDATE .. FROM ..`). - - MySQL doesn't support the UPDATE FROM syntax. And the current implementation attempt to - tranform it to the UPDATE JOIN syntax, which only works for one join target. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.UpdateStatement("glyph") - .values(tokens=rq.Expr.col("characters.character")) - .from_table("characters") - .where(rq.Expr.col("glyph.image") == rq.Expr.col("characters.user_data")) - ) - stmt.to_sql("postgres") - # UPDATE "glyph" SET "tokens" = "characters"."character" FROM "characters" WHERE "glyph"."image" = "characters"."user_data" - - stmt.to_sql("mysql") - # UPDATE `glyph` JOIN `characters` ON `glyph`.`image` = `characters`.`user_data` SET `glyph`.`tokens` = `characters`.`character` - ``` - """ - ... - - def limit(self, n: int) -> typing.Self: - """Limit the number of rows to update.""" - ... - - def order_by(self, clause: Ordering) -> typing.Self: - """ - Specify the order in which to delete rows. Typically used with - `.limit` method to delete specific rows. - """ - ... - - def returning(self, clause: Returning) -> typing.Self: - """ - Specify columns to return from the inserted rows. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.UpdateStatement("glyph") - .values(aspect=1.23, image=123) - .returning(rq.Returning.all()) - .to_sql("sqlite") - ) - # UPDATE "glyph" SET "aspect" = 1.23, "image" = 123 RETURNING * - - stmt = ( - rq.UpdateStatement("glyph") - .values(aspect=1.23, image=123) - .returning(rq.Returning("id")) - .to_sql("sqlite") - ) - # UPDATE "glyph" SET "aspect" = 1.23, "image" = 123 RETURNING "id" - ``` - """ - ... - - def table(self, table: _TableNameNew) -> typing.Self: - """ - Override the table to update. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.UpdateStatement("name_1") - .values(aspect=1.23, image=123) - .table("name_2") - .to_sql("sqlite") - ) - # UPDATE "name_2" SET "aspect" = 1.23, "image" = 123 - ``` - """ - ... - - def values(self, **kwds: _ExprNew) -> typing.Self: - """ - Specify columns and their new values. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.UpdateStatement("glyph") - .values(aspect=1.23, image=123) - .values(font_id=20) - .to_sql("sqlite") - ) - # UPDATE "name_2" SET "aspect" = 1.23, "image" = 123, "font_id" = 20 - ``` - """ - ... - - def where(self, condition: Expr) -> typing.Self: - """ - Add a WHERE condition to filter rows to update. - - Examples: - ```python - import rapidquery as rq - - stmt = ( - rq.UpdateStatement("glyph") - .values(aspect=1.23, image=123) - .where(rq.Expr.col("id") > 10) - .where(rq.Expr.col("id") < 20) - .to_sql("sqlite") - ) - # UPDATE "glyph" SET "aspect" = 1.23, "image" = 123 WHERE "id" > 10 AND "id" < 20 - ``` - """ - ... - -class WindowStatement: - """ - Represents an `OVER` clause used with window functions. - - Window functions perform calculations across a set of table rows that are - somehow related to the current row. This class builds the specification - of that set (the window), including partitioning, ordering, and framing. - - # References: - 1. - 2. - 3. - """ - - def __init__(self, *partition_by: Expr | _ColumnRefNew) -> None: - """ - Construct a new `WINDOW` specification. - - Args: - *partition_by: One or more columns or expressions to divide - the result set into partitions. - - Examples: - ```python - import rapidquery as rq - - # Example 1: Defining a simple frame - stmt = ( - rq.SelectStatement( - rq.Expr.col("character").label( - "C", - rq.WindowStatement("font_size").frame( - "ROWS", - rq.Frame.preceding(10), - ) - ) - ) - .from_table("characters") - .to_sql("postgres") - ) - - # Example 2: Defining a frame with start and end - stmt = ( - rq.SelectStatement( - rq.Expr.col("character").label( - "C", - rq.WindowStatement("font_size").frame( - "ROWS", - rq.Frame.unbounded_preceding(), - rq.Frame.unbounded_following(), - ) - ) - ) - .from_table("characters") - .to_sql("postgres") - ) - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - def frame( - self, - frame_type: typing.Literal["ROWS", "RANGE"], - frame_start: Frame, - frame_end: Frame | None = None, - ) -> typing.Self: - """ - Set the window frame clause. - - Args: - frame_type: Either "ROWS" (physical rows) or "RANGE" (logical values). - frame_start: The starting boundary of the frame. - frame_end: Optional ending boundary. If not provided, the frame - typically defaults to the current row or the database default. - """ - ... - - def order_by(self, clause: Ordering) -> typing.Self: - """ - Specify the order of rows within each partition. - - Args: - clause: An `Ordering` instance defining the sort key and direction. - """ - ... - - def partition(self, partition_by: Expr | _ColumnRefNew) -> typing.Self: - """ - Add a column or expression to the `PARTITION BY` clause. - - Args: - partition_by: The column or expression to partition by. - """ - ... - -_CommonTableExpressionQuery = ( - SelectStatement | DeleteStatement | UpdateStatement | InsertStatement -) - -class WithClause: - """ - A WITH clause can contain one or multiple common table expressions (CTEs). - - These named queries can act as a "query local table" that are materialized during execution and - then can be used by the query prefixed with the WITH clause. - - A CTE is a name, column names and a query returning data for those columns. - - Some databases (like sqlite) restrict the acceptable kinds of queries inside of the WITH clause - CTEs. These databases only allow `SelectStatement`s to form a CTE. - - Other databases like postgres allow modification queries (UPDATE, DELETE) inside of the WITH - clause but they have to return a table. (They must have a RETURNING clause). - - RapidQuery doesn't check this or restrict the kind of CTE that you can create - in rust. This means that you can put an UPDATE or DELETE queries into WITH clause and RapidQuery - will succeed in generating that kind of sql query but the execution inside the database will - fail because they are invalid. - - It is your responsibility to ensure that the kind of WITH clause that you put together makes - sense and valid for that database that you are using. - - NOTE that for recursive WITH queries (in sql: "WITH RECURSIVE") you can only have a - single CTE inside of the WITH clause. That query must match certain - requirements: - * It is a query of UNION or UNION ALL of two queries. - * The first part of the query (the left side of the UNION) must be executable first in itself. - It must be non-recursive. (Cannot contain self reference) - * The self reference must appear in the right hand side of the UNION. - * The query can only have a single self-reference. - * Recursive data-modifying statements are not supported, but you can use the results of a - recursive SELECT query in a data-modifying statement. (like so: WITH RECURSIVE - cte_name(a,b,c,d) AS (SELECT ... UNION SELECT ... FROM ... JOIN cte_name ON ... WHERE ...) - DELETE FROM table WHERE table.a = cte_name.a) - - Note that this type is not a `QueryStatement`. To generate `WITH` statement, you must convert this type to - `WithQuery` type, using `WithClause.query` method. - """ - - def __init__(self) -> None: - """ - Construct a new `WithClause` instance. - - Note that this type is not a `QueryStatement`. To generate `WITH` statement, you must convert this type to - `WithQuery` type, using `WithClause.query` method. - - Examples: - ```python - import rapidquery as rq - - select = ( - rq.SelectStatement() - .columns("names.id", "names.name") - .from_table("filtered_names AS fil") - .join("names", rq.Expr.col("fil.name_id") == rq.Expr.col("names.id")) - ) - clause = ( - rq.WithClause() - .cte( - "source_filtered_names", - select.__copy__().where(rq.Expr.col("fil.user_id") == 5), - ) - .cte( - "dest_filtered_names", - select.where(rq.Expr.col("fil.user_id") == 8), - ) - .cte( - "missing_names", - rq.SelectStatement() - .columns("dest.name") - .from_table("dest_filtered_names AS dest") - .join( - "source_filtered_names AS source", - rq.Expr.col("dest.id") == rq.Expr.col("source.id"), - ) - .where(rq.Expr.col("source.id").is_null()), - ) - .query(rq.SelectStatement().columns("name").from_table("missing_names")) - ) - clause.to_sql("postgres") - # WITH - # "source_filtered_names" ("id", "name") AS ( - # SELECT "names"."id" AS "id", "names"."name" AS "name" FROM "filtered_names" AS "fil" - # JOIN "names" ON "fil"."name_id" = "names"."id" WHERE "fil"."user_id" = 5 - # ) , - # "dest_filtered_names" ("id", "name") AS ( - # SELECT "names"."id" AS "id", "names"."name" AS "name" FROM "filtered_names" AS "fil" - # JOIN "names" ON "fil"."name_id" = "names"."id" - # WHERE "fil"."user_id" = 8 - # ) , - # "missing_names" ("name") AS ( - # SELECT "dest"."name" AS "name" FROM "dest_filtered_names" AS "dest" - # JOIN "source_filtered_names" AS "source" ON "dest"."id" = "source"."id" - # WHERE "source"."id" IS NULL - # ) - # SELECT "name" AS "name" FROM "missing_names" - ``` - """ - ... - - def recursive(self) -> typing.Self: - """ - Sets whether this clause is a recursive with clause of not. - It will generate a 'WITH RECURSIVE' query. - """ - ... - - def cte( - self, - name: str, - query: _CommonTableExpressionQuery, - columns: typing.Iterable[str] = (), - materialized: bool | None = None, - ) -> typing.Self: - """ - Add a CTE to this with clause. - - Args: - name: The common table expression (CTE) name. - query: The query, which have to return data. - columns: Named columns for the CTE table definition. - If empty, tries to detect them from the `query`. - materialized: Some databases allow you to put “MATERIALIZED” or “NOT MATERIALIZED” in the - CTE definition. This will affect how during the execution of WithQuery the - CTE in the WithClause will be executed. If the database doesn’t support this - syntax this option specified here will be ignored and not appear in the - generated sql. - """ - ... - - def cycle( - self, - expr: _ExprNew | None = None, - set_as: str | None = None, - using: str | None = None, - ) -> typing.Self: - """ - For recursive `WithQuery` `WithClause`s the CYCLE sql clause can be specified to avoid creating - an infinite traversals that loops on graph cycles indefinitely. You specify an expression that - identifies a node in the graph and that will be used to determine during the iteration of - the execution of the query when appending of new values whether the new values are distinct new - nodes or are already visited and therefore they should be added again into the result. - - A query can have both SEARCH and CYCLE clauses. - - This setting is not meaningful if the query is not recursive. - Some databases don’t support this clause. In that case this option will be silently ignored. - - Examples: - ```python - base_query = rq.SelectStatement( - rq.Expr.col("id"), - 1, - rq.Expr.col("next"), - rq.Expr.col("value"), - ).from_table("table") - - cte_referencing = ( - rq.SelectStatement( - rq.Expr.col("id"), - rq.Expr.col("depth") + 1, - rq.Expr.col("next"), - rq.Expr.col("value"), - ) - .from_table("table") - .join( - "cte_traversal", - rq.Expr.col("cte_traversal.next") == rq.Expr.col("table.id"), - "INNER", - ) - ) - - clause = ( - rq.WithClause() - .recursive() - .cte( - "cte_traversal", - base_query.union(cte_referencing, "ALL"), - columns=["id", "depth", "next", "value"], - ) - .cycle(rq.Expr.col("id"), set_as="looped", using="traversal_path") - .query(rq.SelectStatement().columns("*").from_table("cte_traversal")) - ) - clause.to_sql("postgres") - # WITH RECURSIVE "cte_traversal" ("id", "depth", "next", "value") AS ( - # SELECT "id" AS "id", 1, "next" AS "next", "value" AS "value" FROM "table" - # UNION ALL ( - # SELECT "id" AS "id", "depth" + 1, "next" AS "next", "value" AS "value" FROM "table" - # INNER JOIN "cte_traversal" ON "cte_traversal"."next" = "table"."id" - # ) - # ) - # CYCLE "id" SET "looped" USING "traversal_path" SELECT * FROM "cte_traversal" - ``` - """ - ... - - def search( - self, - expr: SelectLabel | None = None, - order: typing.Literal["BREADTH", "DEPTH"] | str | None = None, - ) -> typing.Self: - """ - For recursive `WithQuery` `WithClause`s the traversing order can be specified in some databases - that support this functionality. - - A query can have both SEARCH and CYCLE clauses. - - This setting is not meaningful if the query is not recursive. - Some databases don’t support this clause. In that case this option will be silently ignored. - - The `SelectLabel` used must specify an alias which will be the name that you can use to order - the result of the CTE. - - Examples: - ```python - base_query = ( - rq.SelectStatement() - .columns("id", "parent_id") - .from_table("nodes") - .where(rq.Expr.col("parent_id").is_null()) - ) - reference_query = ( - rq.SelectStatement() - .columns("n.id", "n.parent_id") - .from_table("nodes as n") - .join( - "tree as t", - rq.Expr.col("n.parent_id") == rq.Expr.col("t.id"), - ) - ) - - clause = ( - rq.WithClause() - .recursive() - .cte( - "tree", - base_query.union(reference_query, "ALL"), - columns=["id", "parent_id"], - ) - .search(rq.Expr.col("id").label("traversal_order"), "DEPTH") - .query( - rq.SelectStatement() - .columns("*") - .from_table("tree") - .order_by(rq.Ordering("traversal_order")) - ) - ) - clause.to_sql("postgresql") - # WITH RECURSIVE "tree" ("id", "parent_id") AS ( - # SELECT "id" AS "id", "parent_id" AS "parent_id" FROM "nodes" - # WHERE "parent_id" IS NULL - # UNION ALL ( - # SELECT "n"."id" AS "id", "n"."parent_id" AS "parent_id" - # FROM "nodes" AS "n" - # JOIN "tree" AS "t" ON "n"."parent_id" = "t"."id" - # ) - # ) - # SEARCH DEPTH FIRST BY "id" SET "traversal_order" - # SELECT * FROM "tree" ORDER BY "traversal_order" ASC - ``` - """ - ... - - def query(self, val: _CommonTableExpressionQuery) -> WithQuery: - """ - You can turn this into a `WithQuery` using this function. - The resulting WITH query will execute the argument query with this WITH clause. - """ - ... - -class WithQuery(QueryStatement): - """ - A WITH query. A simple SQL query that has a WITH clause (`WithClause`). - - It's recommended to use `WithClause.query` method instead of directly constructing - this type. - - For full description, see `WithClause`'s documentation. - """ - - def __init__( - self, - clause: WithClause, - query: _CommonTableExpressionQuery, - ) -> None: - """ - Construct a new `WithQuery` instance. - - Args: - clause: The `WITH` clause. - query: The final query. - """ - ... diff --git a/rapidquery/_lib/schema.pyi b/rapidquery/_lib/schema.pyi deleted file mode 100644 index 16d269c..0000000 --- a/rapidquery/_lib/schema.pyi +++ /dev/null @@ -1,857 +0,0 @@ -from __future__ import annotations - -import typing - -from .common import Column, Expr, ForeignKey, TableName, _ColumnRefNew, _TableNameNew - -_IndexColumnValue: typing.TypeAlias = IndexColumn | _ColumnRefNew -_IndexColumnOrder: typing.TypeAlias = typing.Literal["ASC", "DESC"] -_BackendName: typing.TypeAlias = typing.Literal["sqlite", "postgresql", "postgres", "mysql"] - -class AlterTable(SchemaStatement): - """ - Represents an `ALTER TABLE` SQL statement. - - Provides a flexible way to modify existing table structures by applying - one or more alteration operations such as adding/dropping columns, - modifying column definitions, or managing constraints. - - Multiple operations can be batched together in a single ALTER TABLE - statement for efficiency. - """ - - def __init__( - self, - name: _TableNameNew, - options: typing.Iterable[AlterTableBaseOption] = (), - ) -> None: - """ - Construct a new `ALTER TABLE` statement. - - Args: - name: The name of the table to alter. - options: Iterable of alteration operations to apply. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.AlterTable( - "users", - [ - rq.AlterTableDropColumnOption("updated_at"), - rq.AlterTableRenameColumnOption("name", "username"), - ], - ).to_sql("postgres") - # ALTER TABLE "users" DROP COLUMN "updated_at", RENAME COLUMN "name" TO "username" - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - def add_option(self, opt: AlterTableBaseOption) -> typing.Self: - """Add an alteration operation.""" - ... - - @property - def name(self) -> TableName: - """The name of the table to alter.""" - ... - @name.setter - def name(self, value: _TableNameNew) -> None: ... - @property - def options(self) -> typing.Sequence[AlterTableBaseOption]: - """The list of alteration operations to apply.""" - ... - @options.setter - def options(self, value: typing.Iterable[AlterTableBaseOption]) -> None: ... - -class AlterTableAddColumnOption(AlterTableBaseOption): - """ - `ALTER TABLE` operation to add a new column. - - Adds a column to an existing table with optional IF NOT EXISTS clause - to prevent errors if the column already exists. - - NOTE: this class is immutable and frozen. - """ - - def __init__(self, column: Column, if_not_exists: bool = False) -> None: - """ - Construct a new `AlterTableAddColumnOption` instance. - - Args: - column: The column definition to add. - if_not_exists: If `True`, uses `IF NOT EXISTS` clause. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.AlterTable( - "users", - [ - rq.AlterTableAddColumnOption( - rq.Column( - "updated_at", - rq.sqltypes.Timestamp(timezone=True), - default=rq.Expr.current_timestamp(), - ) - ) - ], - ).to_sql("postgres") - # ALTER TABLE "users" ADD COLUMN "updated_at" timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP - ``` - """ - ... - - def __repr__(self, /) -> str: ... - @property - def column(self) -> Column: - """The column definition to add.""" - ... - - @property - def if_not_exists(self) -> bool: - """Whether to use IF NOT EXISTS clause.""" - ... - -class AlterTableAddForeignKeyOption(AlterTableBaseOption): - """ - `ALTER TABLE` operation to add a foreign key constraint. - - Adds referential integrity between tables by creating a foreign key - relationship on an existing table. - """ - - def __init__(self, foreign_key: ForeignKey) -> None: - """ - Construct a new `AlterTableAddForeignKeyOption` instance. - - Args: - foreign_key: The foreign key definition to add. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.AlterTable( - "users", - [ - rq.AlterTableAddForeignKeyOption( - rq.ForeignKey(["font_id"], ["fonts.id"], on_delete="CASCADE") - ) - ], - ).to_sql("postgres") - # ALTER TABLE "users" ADD CONSTRAINT "fk_fonts_font_id__id" FOREIGN KEY ("font_id") REFERENCES "fonts" ("id") ON DELETE CASCADE - ``` - """ - ... - - def __repr__(self, /) -> str: ... - @property - def foreign_key(self) -> ForeignKey: - """The foreign key definition to add.""" - ... - -class AlterTableBaseOption: - """ - This abstract base class represents the different types of modifications - that can be made to an existing table structure, such as adding/dropping - columns, modifying column definitions, or managing foreign keys. - - NOTE: You cannot use this class as a parent (subclass), but you can use its children - as a parent (subclass). e.g. `AlterTableDropColumnOption`. - """ - -class AlterTableDropColumnOption(AlterTableBaseOption): - """ - `ALTER TABLE` operation to drop an existing column. - - Removes a column from the table. This operation may fail if the column - is referenced by other database objects. - """ - - def __init__(self, name: _ColumnRefNew) -> None: - """ - Construct a new `AlterTableDropColumnOption` instance. - - Args: - name: The column name/reference to drop. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.AlterTable( - "users", - [rq.AlterTableDropColumnOption("updated_at")], - ).to_sql("postgres") - # ALTER TABLE "users" DROP COLUMN "updated_at" - ``` - """ - ... - - def __repr__(self, /) -> str: ... - @property - def name(self) -> str: - """The column name to drop.""" - ... - -class AlterTableDropForeignKeyOption(AlterTableBaseOption): - """ - `ALTER TABLE` operation to drop a foreign key constraint. - - Removes a foreign key relationship by its constraint name. - """ - - def __init__(self, name: str) -> None: - """ - Construct a new `AlterTableDropForeignKeyOption` instance. - - Args: - name: The foreign key constraint name to drop. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.AlterTable( - "users", - [rq.AlterTableDropForeignKeyOption("fk_users_id_fonts_id")], - ).to_sql("postgres") - # ALTER TABLE "users" DROP CONSTRAINT "fk_users_id_fonts_id" - ``` - """ - ... - - def __repr__(self, /) -> str: ... - @property - def name(self) -> str: - """The foreign key constraint name to drop.""" - ... - -class AlterTableModifyColumnOption(AlterTableBaseOption): - """ - `ALTER TABLE` operation to modify a column definition. - - Changes properties of an existing column such as type, nullability, - default value, or other constraints. - """ - - def __init__(self, column: Column) -> None: - """ - Construct a new `AlterTableModifyColumnOption` instance. - - Args: - column: The column you want to modify with changes. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.AlterTable( - "users", - [ - rq.AlterTableModifyColumnOption( - rq.Column("updated_at", rq.sqltypes.Date(), default=None) - ) - ], - ).to_sql("postgres") - # ALTER TABLE "users" ALTER COLUMN "updated_at" TYPE date, ALTER COLUMN "updated_at" SET DEFAULT NULL - ``` - """ - ... - - def __repr__(self, /) -> str: ... - @property - def column(self) -> Column: ... - -class AlterTableRenameColumnOption(AlterTableBaseOption): - """ - `ALTER TABLE` operation to rename a column. - - Changes the name of an existing column without modifying its type - or constraints. - """ - - def __init__(self, from_name: _ColumnRefNew, to_name: _ColumnRefNew) -> None: - """ - Construct a new `AlterTableRenameColumnOption` instance. - - Args: - from_name: The column name/reference to rename. - to_name: New column name. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.AlterTable( - "users", - [rq.AlterTableRenameColumnOption("hello", "world")], - ).to_sql("postgres") - # ALTER TABLE "users" RENAME COLUMN "hello" TO "world" - ``` - """ - ... - - def __repr__(self, /) -> str: ... - @property - def from_name(self) -> str: - """The column name/reference to rename.""" - ... - - @property - def to_name(self) -> str: - """New column name.""" - ... - -class DropIndex(SchemaStatement): - """ - Represents a `DROP INDEX` SQL statement. - - Builds index deletion statements with support for: - - Conditional deletion (IF EXISTS) - - Table-specific index dropping - """ - - def __init__( - self, - name: str, - table: _TableNameNew, - if_exists: bool = False, - ) -> None: - """ - Construct a new `DROP INDEX` statement. - - Args: - name: The name of the index to drop. - table: The table from which to drop the index. - if_exists: If `True`, uses `IF EXISTS` clause (is not supported by MySQL). - - Examples: - ```python - import rapidquery as rq - - stmt = rq.DropIndex("idx_glyph_aspect", "glyph").to_sql("mysql") - # DROP INDEX `idx_glyph_aspect` ON `glyph` - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @property - def if_exists(self) -> bool: - """Whether to use IF EXISTS clause to avoid errors.""" - ... - - @if_exists.setter - def if_exists(self, value: bool) -> None: ... - @property - def name(self) -> str: - """The name of the index to drop.""" - ... - @name.setter - def name(self, value: str) -> None: ... - @property - def table(self) -> TableName: - """The table from which to drop the index.""" - ... - @table.setter - def table(self, value: _TableNameNew) -> None: ... - -class DropTable(SchemaStatement): - """ - Represents a `DROP TABLE` SQL statement. - - Builds table deletion statements with support for: - - Conditional deletion (IF EXISTS) to avoid errors - - CASCADE to drop dependent objects - - RESTRICT to prevent deletion if dependencies exist - """ - - def __init__( - self, - name: _TableNameNew, - *, - if_exists: bool = False, - cascade: bool = False, - restrict: bool = False, - ) -> None: - """ - Construct a new `DROP TABLE` statement. - - Args: - name: The table name to drop. - if_exists: If `True`, adds the `IF EXISTS` clause to avoid an error if the table does not exist. - cascade: If `True`, adds the `CASCADE` clause to automatically drop objects that depend on - the table (such as foreign keys or views). - restrict: If `True`, adds the `RESTRICT` clause to refuse to drop the table if any - objects depend on it. This is typically the default behavior in many SQL dialects. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.DropTable("glyph", if_exists=True, cascade=True, restrict=True).to_sql( - "mysql" - ) - # DROP TABLE IF EXISTS `glyph` RESTRICT CASCADE - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @property - def cascade(self) -> bool: - """Whether to automatically drop dependent objects.""" - ... - - @cascade.setter - def cascade(self, value: bool) -> None: ... - @property - def if_exists(self) -> bool: - """Whether to use the `IF EXISTS` clause.""" - ... - - @if_exists.setter - def if_exists(self, value: bool) -> None: ... - @property - def name(self) -> TableName: - """The table name to drop.""" - ... - - @name.setter - def name(self, value: _TableNameNew) -> None: ... - @property - def restrict(self) -> bool: - """Whether to prevent deletion if dependencies exist.""" - ... - - @restrict.setter - def restrict(self, value: bool) -> None: ... - -class Index(SchemaStatement): - """ - Represents a database index specification. - - This class defines the structure and properties of a database index, - including column definitions, uniqueness constraints, index type, - and partial indexing conditions. - - You can use it to generate `CREATE INDEX` SQL expressions. - """ - - def __init__( - self, - name: str | None, - columns: typing.Iterable[_IndexColumnValue], - table: _TableNameNew | None = None, - *, - primary: bool = False, - if_not_exists: bool = False, - nulls_not_distinct: bool = False, - unique: bool = False, - index_type: str | None = None, - where: Expr | None = None, - include: typing.Iterable[str] = (), - ) -> None: - """ - Construct a new database index specification. - Also you can use it to generate `CREATE INDEX` SQL expression. - - Args: - name: The index name. You should always set for indexes that aren't primary or unique, or - you want use them to generate `CREATE INDEX` statement. - columns: Column expressions to include in the index. You can use `IndexColumn` class - if you want to specify prefix or order. - table: The table on which to create the index. You should always set for indexes that - you want use them to generate `CREATE INDEX` statement. - primary: If `True`, means this is a primary key constraint. - if_not_exists: If `True`, uses `IF NOT EXISTS` clause. - nulls_not_distinct: If `True`, NULL values will be considered equal for uniqueness. - unique: If `True`, means this is a unique key constraint. - index_type: The type/algorithm for this index. e.g. `"HASH"`, `"BTREE"`, `"FULL TEXT"`. - where: Condition for partial indexing. - include: Additional columns to include in the index for covering queries. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.Index("idx_glyph_aspect", ["aspect"], "glyph", if_not_exists=True).to_sql( - "sqlite" - ) - # CREATE INDEX IF NOT EXISTS "idx_glyph_aspect" ON "glyph" ("aspect") - - stmt = rq.Index( - "idx_glyph_aspect", [rq.IndexColumn("aspect", "ASC", 128)], "glyph" - ).to_sql("mysql") - # CREATE INDEX `idx_glyph_aspect` ON `glyph` (`aspect` (128) ASC) - - stmt = rq.Index( - "idx_font_name_include_language", - ["name"], - "fonts", - include=["language"], - where=rq.Expr.col("aspect").in_([3, 4]), - ).to_sql("postgresql") - # CREATE INDEX "idx_font_name_include_language" ON "fonts" ("name") INCLUDE ("language") - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @property - def columns(self) -> typing.Sequence[IndexColumn]: - """The columns that make up this index.""" - ... - @columns.setter - def columns(self, value: typing.Iterable[_IndexColumnValue]) -> None: ... - @property - def if_not_exists(self) -> bool: - """Whether to use IF NOT EXISTS clause.""" - ... - @if_not_exists.setter - def if_not_exists(self, value: bool) -> None: ... - @property - def include(self) -> typing.Sequence[str]: - """Additional columns to include in the index for covering queries.""" - ... - @include.setter - def include(self, value: typing.Iterable[str]) -> None: ... - @property - def index_type(self) -> str | None: - """The type/algorithm for this index.""" - ... - @index_type.setter - def index_type(self, value: str | None) -> None: ... - @property - def name(self) -> str | None: - """Index name""" - ... - @name.setter - def name(self, value: str | None) -> None: ... - @property - def nulls_not_distinct(self) -> bool: - """Whether NULL values should be considered equal for uniqueness.""" - ... - @nulls_not_distinct.setter - def nulls_not_distinct(self, value: bool) -> None: ... - @property - def primary(self) -> bool: - """Whether this is a primary key constraint.""" - ... - @primary.setter - def primary(self, value: bool) -> None: ... - @property - def table(self) -> TableName | None: - """The table on which to create the index.""" - ... - @table.setter - def table(self, value: _TableNameNew | None) -> None: ... - @property - def unique(self) -> bool: - """Whether this is a unique constraint.""" - ... - @unique.setter - def unique(self, value: bool) -> None: ... - @property - def where(self) -> Expr | None: - """Condition for partial indexing.""" - ... - @where.setter - def where(self, value: object | None) -> None: ... - -@typing.final -class IndexColumn: - """ - Defines a column within an index specification. - - Represents a single column's participation in an index, including: - - The column name - - Optional prefix length (for partial indexing) - - Sort order (ascending or descending) - - Used within `Index` to specify which columns are indexed - and how they should be ordered. - - NOTE: this class is immutable and frozen. - """ - - def __new__( - cls, - name: str, - order: _IndexColumnOrder | None = None, - prefix: int | None = None, - ) -> typing.Self: - """ - Construct a new `IndexColumn` instance. - - Args: - name: The column name to be indexed. - order: The sort order for the column, typically 'ASC' for ascending - or 'DESC' for descending. If `None`, the database default is used. - prefix: The number of characters to index for string/text columns. - Used for prefix indexing to save space or handle large text fields. - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @property - def name(self) -> str: - """The name of the column to include in the index.""" - ... - - @property - def order(self) -> _IndexColumnOrder | None: - """Sort order for this column (e.g., ASC or DESC).""" - ... - - @property - def prefix(self) -> int | None: - """Number of characters to index for string columns (prefix indexing).""" - ... - -class RenameTable(SchemaStatement): - """ - Represents a `RENAME TABLE` SQL statement. - - Changes the name of an existing table to a new name. Both names can be - schema-qualified if needed. - """ - - def __init__(self, from_name: _TableNameNew, to_name: _TableNameNew) -> None: - """ - Construct a new `RENAME TABLE` statement. - - Args: - from_name: The current name of the table. - to_name: The new name for the table. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.RenameTable("users", "accounts").to_sql("mysql") - # RENAME TABLE `users` TO `accounts` - ``` - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @property - def from_name(self) -> TableName: - """The current name of the table.""" - ... - @from_name.setter - def from_name(self, value: _TableNameNew) -> None: ... - @property - def to_name(self) -> TableName: - """The new name for the table.""" - ... - @to_name.setter - def to_name(self, value: _TableNameNew) -> None: ... - -class SchemaStatement: - """Subclass of schema statements.""" - - def to_sql(self, backend: _BackendName, /) -> str: - """Build a SQL string representation.""" - ... - -class Table(SchemaStatement): - """ - Represents a complete database table definition. - - This class encapsulates all aspects of a table structure including: - - Column definitions with their types and constraints - - Indexes for query optimization - - Foreign key relationships for referential integrity - - Check constraints for data validation - - Table-level options like engine, collation, and character set - - Used to generate `CREATE TABLE` SQL statements with full schema specifications. - """ - - def __init__( - self, - name: TableName | str, - *args: Column | Index | ForeignKey | Expr, - if_not_exists: bool = False, - temporary: bool = False, - comment: str | None = None, - engine: str | None = None, - collate: str | None = None, - character_set: str | None = None, - extra: str | None = None, - ) -> None: - """ - Construct a new database table definition. Also you can use it - to generate `CREATE TABLE` SQL statements with full schema specifications. - - Args: - name: The name of this table as represented in the database. You can also specify database and schema here. - If you're using `TableName`, you can use `database` and `schema` parameters; but you can also do it with `str`. - Format: `"db_name.schema_name.table_name"`. - args: List of columns (`Column`s), indexes (`Index`s), foreign keys (`ForeignKey`s), or - check constraints (`Expr`s). - if_not_exists: If `True`, uses `IF NOT EXISTS` clause. - temporary: If `True`, marks the table as a temporary table. - comment: Comment describing the purpose of this table. - engine: Storage engine for the table (e.g., InnoDB, MyISAM for MySQL). - collate: Collation for string comparisons and sorting in this table. - character_set: Character set encoding for text data in this table. - extra: Additional table-specific options for the statement. - - Examples: - ```python - import rapidquery as rq - - table = rq.Table( - "public.characters", - rq.Column("id", rq.sqltypes.BigInteger(), primary_key=True, auto_increment=True), - rq.Column("character", rq.sqltypes.Char(1)), - rq.Column("font_size", rq.sqltypes.SmallInteger()), - rq.Column("font_id", rq.sqltypes.BigInteger()), - rq.ForeignKey(["font_id"], ["fonts.id"], on_delete="CASCADE"), - rq.Index("ix_characters_fonts_id", ["font_id"]), - rq.Expr(rq.Func.char_length(rq.Expr.col("character"))) == 1, - if_not_exists=True, - ) - - table.to_sql("postgres") - # CREATE TABLE IF NOT EXISTS "public"."characters" ( - # "id" bigserial PRIMARY KEY NOT NULL, - # "character" char(1) NOT NULL, - # "font_size" smallint NOT NULL, - # "font_id" bigint NOT NULL, - # CONSTRAINT "fk_fonts_font_id__id" FOREIGN KEY ("font_id") REFERENCES "fonts" ("id") ON DELETE CASCADE, - # CHECK (CHAR_LENGTH("character") = 1) - # ); - # CREATE INDEX IF NOT EXISTS "ix_characters_fonts_id" ON "public"."characters" ("font_id") - ``` - """ - ... - - @property - def __table_name__(self) -> TableName: ... - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @property - def character_set(self) -> str | None: - """Character set encoding for text data in this table.""" - ... - @character_set.setter - def character_set(self, value: str | None) -> None: ... - @property - def checks(self) -> typing.Sequence[Expr]: - """Table check constraints.""" - ... - @checks.setter - def checks(self, value: typing.Iterable[Expr]) -> None: ... - @property - def collate(self) -> str | None: - """Collation for string comparisons and sorting in this table.""" - ... - @collate.setter - def collate(self, value: str | None) -> None: ... - @property - def columns(self) -> typing.Sequence[Column]: - """Table columns.""" - ... - @columns.setter - def columns(self, value: typing.Iterable[Column]) -> None: ... - @property - def comment(self) -> str | None: - """Comment describing the purpose of this table.""" - ... - @comment.setter - def comment(self, value: str | None) -> None: ... - @property - def engine(self) -> str | None: - """Storage engine for the table (e.g., InnoDB, MyISAM for MySQL).""" - ... - @engine.setter - def engine(self, value: str | None) -> None: ... - @property - def extra(self) -> str | None: - """Additional table-specific options for the CREATE TABLE statement.""" - ... - @extra.setter - def extra(self, value: str | None) -> None: ... - @property - def foreign_keys(self) -> typing.Sequence[ForeignKey]: - """Table foreign keys.""" - ... - @foreign_keys.setter - def foreign_keys(self, value: typing.Iterable[ForeignKey]) -> None: ... - @property - def if_not_exists(self) -> bool: - """Whether to use IF NOT EXISTS clause to avoid errors if table exists.""" - ... - @if_not_exists.setter - def if_not_exists(self, value: bool) -> None: ... - @property - def indexes(self) -> typing.Sequence[Index]: - """Table indexes.""" - ... - @indexes.setter - def indexes(self, value: typing.Iterable[Index]) -> None: ... - @property - def name(self) -> TableName: - """The name of this table.""" - ... - - @property - def temporary(self) -> bool: - """Whether this is a temporary table that exists only for the session.""" - ... - @temporary.setter - def temporary(self, value: bool) -> None: ... - -class TruncateTable(SchemaStatement): - """ - Represents a `TRUNCATE TABLE` SQL statement. - - Quickly removes all rows from a table, typically faster than DELETE - and with different transaction and trigger behavior depending on the - database system. - - NOTE: SQLite doesn't support TRUNCATE statement. - """ - - def __init__(self, name: _TableNameNew) -> None: - """ - Construct a new `TRUNCATE TABLE` statement. - - Args: - name: The name of the table to truncate. - - Examples: - ```python - import rapidquery as rq - - stmt = rq.TruncateTable("users").to_sql("mysql") - # TRUNCATE TABLE `users` - ``` - - NOTE: SQLite doesn't support TRUNCATE statement. - """ - ... - - def __copy__(self) -> typing.Self: ... - def __repr__(self, /) -> str: ... - @property - def name(self) -> TableName: - """The name of the table to truncate.""" - ... - @name.setter - def name(self, value: _TableNameNew) -> None: ... diff --git a/rapidquery/_lib/sqlite.pyi b/rapidquery/_lib/sqlite.pyi deleted file mode 100644 index 4d21ee8..0000000 --- a/rapidquery/_lib/sqlite.pyi +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import annotations - -__all__ = [] diff --git a/rapidquery/_lib/sqltypes.pyi b/rapidquery/_lib/sqltypes.pyi deleted file mode 100644 index f6779dd..0000000 --- a/rapidquery/_lib/sqltypes.pyi +++ /dev/null @@ -1,797 +0,0 @@ -from __future__ import annotations - -import datetime -import decimal -import enum -import typing -import uuid - -T = typing.TypeVar("T") - -@typing.final -class Array(SQLTypeAbstract[list[T]]): - """ - Array column type for storing arrays of elements. - - Represents a column that stores arrays of a specified element type. - Useful in databases that support native array types (like PostgreSQL) - for storing lists of values in a single column. - """ - - def __new__(cls, element: SQLTypeAbstract[T]) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def element(self) -> SQLTypeAbstract[T]: ... - -@typing.final -class BigInteger(SQLTypeAbstract[int]): - """ - Large integer column type (BIGINT). - - Stores 64-bit integers for very large numeric values. Essential for - high-volume systems, timestamps, large counters, or when integer - overflow is a concern. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class BigUnsigned(SQLTypeAbstract[int]): - """ - Unsigned big integer column type. - - Stores very large positive integers only. Provides the maximum positive - integer range for high-volume systems or when very large positive - values are required. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class Binary(SQLTypeAbstract[bytes]): - """ - Fixed-length binary data column type (BINARY). - - Stores binary data of a fixed length. Values shorter than the specified - length are padded. Useful for storing hashes, keys, or other binary - data with consistent length. - """ - - def __new__(cls, length: int = 255) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def length(self) -> int: ... - -@typing.final -class Bit(SQLTypeAbstract[bytes]): - """ - Fixed-length bit string column type (BIT). - - Stores a fixed number of bits. Useful for storing boolean flags efficiently - or binary data where individual bits have meaning. - """ - - def __new__(cls, length: int) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def length(self) -> int: ... - -@typing.final -class Blob(SQLTypeAbstract[bytes]): - """ - Binary large object column type (BLOB). - - Stores large binary data such as images, documents, audio files, or - any binary content. Size limits vary by database system. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class Boolean(SQLTypeAbstract[bool]): - """ - Boolean column type (BOOLEAN). - - Stores true/false values. The standard way to store boolean data, - though implementation varies by database (some use TINYINT(1) or - similar representations). - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class Char(SQLTypeAbstract[str]): - """ - Fixed-length character string column type (CHAR). - - Represents a fixed-length character string. Values shorter than the - specified length are padded with spaces. Suitable for storing data - with consistent, known lengths like country codes or status flags. - """ - - def __new__(cls, length: int | None = ...) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def length(self) -> int | None: ... - -@typing.final -class Date(SQLTypeAbstract[datetime.date]): - """ - Date-only column type (DATE). - - Stores date information without time component. Ideal for birth dates, - deadlines, or any date-based data where time precision is not needed. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class DateTime(SQLTypeAbstract[datetime.datetime]): - """ - Date and time column type (DATETIME). - - Stores both date and time information without timezone awareness. - Suitable for recording timestamps, event times, or scheduling information - when timezone handling is managed at the application level. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class Decimal(SQLTypeAbstract[decimal.Decimal | int | float | str]): - """ - Exact numeric decimal column type (DECIMAL/NUMERIC). - - Stores exact numeric values with fixed precision and scale. Essential for - financial calculations, currency values, or any situation where exact - decimal representation is required without floating-point approximation. - """ - - def __new__(cls, context: tuple[int, int] | None = None) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def context(self) -> tuple[int, int] | None: ... - -@typing.final -class Double(SQLTypeAbstract[float | int]): - """ - Double-precision floating point column type (DOUBLE). - - Stores approximate numeric values with double precision. Provides higher - precision than FLOAT for scientific calculations or when more accuracy - is required in floating-point operations. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class Enum(SQLTypeAbstract[str | enum.Enum]): - """ - Enumeration column type (ENUM). - - Stores one value from a predefined set of allowed string values. - Provides type safety and storage efficiency for categorical data - with a fixed set of possible values. - """ - - def __new__(cls, name: str, variants: typing.Iterable[str]) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def name(self) -> str: ... - @property - def variants(self) -> typing.Sequence[str]: ... - -@typing.final -class Float(SQLTypeAbstract[float | int]): - """ - Single-precision floating point column type (FLOAT). - - Stores approximate numeric values with single precision. Suitable for - scientific calculations, measurements, or any numeric data where some - precision loss is acceptable in exchange for storage efficiency. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class INET(SQLTypeAbstract[str]): - """ - Internet address column type (INET). - - Stores IPv4 or IPv6 addresses, with or without subnet specification. - More flexible than CIDR type, allowing both host addresses and network ranges. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class Integer(SQLTypeAbstract[int]): - """ - Standard integer column type (INTEGER/INT). - - The most common integer type, typically storing 32-bit integers in the - range -2,147,483,648 to 2,147,483,647 (signed). Suitable for most - numeric data including IDs, quantities, and counters. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class JSON(SQLTypeAbstract[typing.Any]): - """ - JSON data column type (JSON). - - Stores JSON documents with validation and indexing capabilities. - Allows for flexible schema design and complex nested data structures - while maintaining some query capabilities. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class JSONBinary(SQLTypeAbstract[typing.Any]): - """ - Binary JSON column type (JSONB). - - Stores JSON documents in a binary format for improved performance. - Provides faster query and manipulation operations compared to text-based - JSON storage, with additional indexing capabilities. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class MacAddress(SQLTypeAbstract[str]): - """ - MAC address column type (MACADDR). - - Stores MAC (Media Access Control) addresses for network devices. - Provides validation and formatting for 6-byte MAC addresses. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -class SQLTypeAbstract(typing.Generic[T]): - """ - Base class for all SQL column data types. - - This abstract base class represents SQL data types that can be used in - column definitions. Each subclass implements a specific SQL data type - with its particular characteristics, constraints, and backend-specific - representations. - """ - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class SmallInteger(SQLTypeAbstract[int]): - """ - Small integer column type (SMALLINT). - - Typically stores integers in the range -32,768 to 32,767 (signed) or - 0 to 65,535 (unsigned). Good for moderate-sized counters or numeric codes. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class SmallUnsigned(SQLTypeAbstract[int]): - """ - Unsigned small integer column type. - - Stores moderate positive integers only, typically 0 to 65,535. Good for - larger counters or numeric codes that are always positive. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class String(SQLTypeAbstract[str]): - """ - Variable-length character string column type (VARCHAR). - - Represents a variable-length character string with a maximum length limit. - This is the most common string type for storing text data of varying lengths - like names, descriptions, or user input. - """ - - def __new__(cls, length: int | None = ...) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def length(self) -> int | None: ... - -@typing.final -class Text(SQLTypeAbstract[str]): - """ - Large text column type (TEXT). - - Represents a large text field capable of storing long strings without - a predefined length limit. Suitable for storing articles, comments, - descriptions, or any text content that may be very long. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class Time(SQLTypeAbstract[datetime.time]): - """ - Time-only column type (TIME). - - Stores time information without date component. Useful for storing - daily schedules, opening hours, or any time-based data that repeats - daily regardless of the specific date. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class Timestamp(SQLTypeAbstract[datetime.datetime]): - """ - Timestamp column type (TIMESTAMP). - - Stores timestamp values, often with automatic update capabilities. - Behavior varies by database system. - """ - - def __new__(cls, timezone: bool = False) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def timezone(self) -> bool: ... - -@typing.final -class TinyInteger(SQLTypeAbstract[int]): - """ - Very small integer column type (TINYINT). - - Typically stores integers in the range -128 to 127 (signed) or 0 to 255 - (unsigned). Useful for flags, small counters, or enumerated values. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class TinyUnsigned(SQLTypeAbstract[int]): - """ - Unsigned tiny integer column type. - - Stores small positive integers only, typically 0 to 255. Useful for - small counters, percentages, or enumerated values that are always positive. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class UUID(SQLTypeAbstract[uuid.UUID]): - """ - UUID column type (UUID). - - Stores universally unique identifiers. Ideal for distributed systems, - primary keys, or any situation where globally unique identifiers are - needed without central coordination. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class Unsigned(SQLTypeAbstract[int]): - """ - Unsigned integer column type. - - Stores positive integers only, typically 0 to 4,294,967,295. Doubles the - positive range compared to signed integers, useful for IDs and counters - that will never be negative. - """ - - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - -@typing.final -class VarBinary(SQLTypeAbstract[bytes]): - """ - Variable-length binary data column type (VARBINARY). - - Stores binary data of variable length up to a specified maximum. - More storage-efficient than BINARY for binary data of varying lengths. - """ - - def __new__(cls, length: int | None = None) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def length(self) -> int: ... - -@typing.final -class VarBit(SQLTypeAbstract[bytes]): - """ - Variable-length bit string column type (VARBIT). - - Stores a variable number of bits up to a specified maximum. More flexible - than fixed BIT type for bit strings of varying lengths. - """ - - def __new__(cls, length: int) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def length(self) -> int: ... - -@typing.final -class Vector(SQLTypeAbstract[list[float]]): - """ - Vector column type for storing mathematical vectors. - - Specialized type for storing vector data, often used in machine learning, - similarity search, or mathematical applications. - """ - - def __new__(cls, length: int | None = None) -> typing.Self: ... - def __repr__(self, /) -> str: - """Return repr(self).""" - ... - - @property - def __type_name__(self) -> str: - """ - Type name. e.g. `'INTEGER'`, `'STRING'` - - It also may be a property. This function must NOT raise any error. - """ - ... - - @property - def length(self) -> int | None: ... diff --git a/src/common/column.rs b/src/common/column.rs deleted file mode 100644 index fd71ef5..0000000 --- a/src/common/column.rs +++ /dev/null @@ -1,427 +0,0 @@ -use sea_query::IntoIden; - -use crate::common::expression::PyExpr; -use crate::internal::parameters::OptionalParam; -use crate::internal::repr::ReprFormatter; -use crate::internal::type_engine::TypeEngine; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, PyObject, RefBoundObject, ToSeaQuery}; -use crate::sqltypes::SQLTypeTrait; - -pub const OPT_PRIMARY_KEY: u8 = 1 << 0; -pub const OPT_UNIQUE_KEY: u8 = 1 << 1; -pub const OPT_NOT_NULL: u8 = 1 << 2; -pub const OPT_NULL: u8 = 1 << 3; -pub const OPT_AUTO_INCREMENT: u8 = 1 << 4; -pub const OPT_STORED_GENERATED: u8 = 1 << 5; - -crate::implement_pyclass! { - /// Defines a table column with its properties and constraints. - /// - /// Represents a complete column definition including: - /// - Column name and data type - /// - Constraints (primary key, unique, nullable) - /// - Auto-increment behavior - /// - Default values and generated columns - /// - Comments and extra specifications - /// - /// This class is used within Table to specify the structure - /// of table columns. It encapsulates all the properties that define how - /// a column behaves and what data it can store. - #[derive(Debug, Clone)] - mutable [subclass, generic] PyColumn(ColumnState) as "Column" { - pub name: String, - pub r#type: TypeEngine, - pub options: u8, - pub default: Option, - pub generated: Option, - pub extra: Option, - pub comment: Option, - } -} - -impl ToSeaQuery for ColumnState { - #[inline] - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::ColumnRef { - sea_query::ColumnRef::Column(sea_query::Alias::new(&self.name).into_iden()) - } -} - -impl ToSeaQuery for ColumnState { - #[inline] - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::ColumnDef { - let mut column_def = sea_query::ColumnDef::new_with_type( - sea_query::Alias::new(self.name.clone()), - self.r#type.to_sea_query_column_type(), - ); - - if self.options & OPT_PRIMARY_KEY > 0 { - column_def.primary_key(); - } - if self.options & OPT_AUTO_INCREMENT > 0 { - column_def.auto_increment(); - } - if self.options & OPT_NULL > 0 { - column_def.null(); - } - if self.options & OPT_NOT_NULL > 0 { - column_def.not_null(); - } - if self.options & OPT_UNIQUE_KEY > 0 { - column_def.unique_key(); - } - - if let Some(x) = &self.default { - column_def.default(x.0.clone()); - } - if let Some(x) = &self.generated { - column_def.generated(x.0.clone(), self.options & OPT_STORED_GENERATED > 0); - } - - if let Some(x) = &self.extra { - column_def.extra(x); - } - if let Some(x) = &self.comment { - column_def.comment(x); - } - - column_def - } -} - -#[pyo3::pymethods] -impl PyColumn { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> Self { - Self::uninit() - } - - #[ - pyo3( - signature=( - name, - r#type, - *, - primary_key=false, - unique_key=false, - nullable=None, - auto_increment=false, - extra=None, - comment=None, - default=OptionalParam::Undefined, - generated=OptionalParam::Undefined, - stored_generated=false, - ) - ) - ] - fn __init__( - &self, - name: String, - r#type: RefBoundObject<'_>, - primary_key: bool, - unique_key: bool, - nullable: Option, - auto_increment: bool, - extra: Option, - comment: Option, - default: OptionalParam, - generated: OptionalParam, - stored_generated: bool, - ) -> pyo3::PyResult<()> { - let sql_type = TypeEngine::new(r#type)?; - - let default_expr = match default { - OptionalParam::Defined(x) => Some(super::expression::PyExpr::try_from_specific_type( - &x, - Some(sql_type.clone()), - )?), - OptionalParam::Undefined => None, - }; - let generated_expr = match generated { - OptionalParam::Defined(x) => Some(super::expression::PyExpr::try_from(&x)?), - OptionalParam::Undefined => None, - }; - - let mut options = 0u8; - if primary_key { - options |= OPT_PRIMARY_KEY; - } - if unique_key { - options |= OPT_UNIQUE_KEY; - } - - match nullable { - Some(true) => options |= OPT_NULL, - Some(false) => options |= OPT_NOT_NULL, - None => (), - } - - if auto_increment { - options |= OPT_AUTO_INCREMENT; - } - if stored_generated { - options |= OPT_STORED_GENERATED; - } - - let state = ColumnState { - name, - r#type: sql_type, - options, - default: default_expr, - generated: generated_expr, - extra, - comment, - }; - self.0.set(state); - - Ok(()) - } - - /// Column name. - #[getter] - fn name(&self) -> String { - self.0.lock().name.clone() - } - - #[setter] - fn set_name(&self, value: String) { - self.0.lock().name = value; - } - - /// Column type. - #[getter] - fn r#type(&self, py: pyo3::Python) -> PyObject { - let lock = self.0.lock(); - lock.r#type.as_pyobject(py).unbind() - } - - #[getter] - fn primary_key(&self) -> bool { - self.0.lock().options & OPT_PRIMARY_KEY > 0 - } - - #[setter] - fn set_primary_key(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= OPT_PRIMARY_KEY; - } else { - lock.options &= !OPT_PRIMARY_KEY; - } - } - - #[getter] - fn unique_key(&self) -> bool { - self.0.lock().options & OPT_UNIQUE_KEY > 0 - } - - #[setter] - fn set_unique_key(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= OPT_UNIQUE_KEY; - } else { - lock.options &= !OPT_UNIQUE_KEY; - } - } - - #[getter] - fn auto_increment(&self) -> bool { - self.0.lock().options & OPT_AUTO_INCREMENT > 0 - } - - #[setter] - fn set_auto_increment(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= OPT_AUTO_INCREMENT; - } else { - lock.options &= !OPT_AUTO_INCREMENT; - } - } - - #[getter] - fn nullable(&self) -> Option { - let options = self.0.lock().options; - - if options & OPT_NOT_NULL > 0 { - Some(false) - } else if options & OPT_NULL > 0 { - Some(true) - } else { - None - } - } - - #[setter] - fn set_nullable(&self, value: Option) { - let mut lock = self.0.lock(); - - match value { - Some(true) => { - lock.options |= OPT_NULL; - lock.options &= !OPT_NOT_NULL; - } - Some(false) => { - lock.options |= OPT_NOT_NULL; - lock.options &= !OPT_NULL; - } - None => { - lock.options &= !OPT_NOT_NULL; - lock.options &= !OPT_NULL; - } - } - } - - #[getter] - fn stored_generated(&self) -> bool { - self.0.lock().options & OPT_STORED_GENERATED > 0 - } - - #[setter] - fn set_stored_generated(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= OPT_STORED_GENERATED; - } else { - lock.options &= !OPT_STORED_GENERATED; - } - } - - /// Extra SQL specifications for this column. - #[getter] - fn extra(&self) -> Option { - self.0.lock().extra.clone() - } - - #[setter] - fn set_extra(&self, val: Option) { - let mut lock = self.0.lock(); - lock.extra = val; - } - - /// Comment describing this column. - #[getter] - fn comment(&self) -> Option { - self.0.lock().comment.clone() - } - - #[setter] - fn set_comment(&self, val: Option) { - let mut lock = self.0.lock(); - lock.comment = val; - } - - /// Default value for this column. - #[getter] - fn default(&self) -> Option { - let lock = self.0.lock(); - lock.default.clone() - } - - #[setter] - fn set_default(&self, val: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let mut lock = self.0.lock(); - let sql_type = lock.r#type.clone(); - - let default = super::expression::PyExpr::try_from_specific_type(val, Some(sql_type))?; - lock.default = Some(default); - Ok(()) - } - - /// Expression for generated column values. - #[getter] - fn generated(&self) -> Option { - let lock = self.0.lock(); - lock.generated.clone() - } - - #[setter] - fn set_generated(&self, val: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let mut lock = self.0.lock(); - - let generated = super::expression::PyExpr::try_from_specific_type(val, None)?; - lock.generated = Some(generated); - Ok(()) - } - - /// Shorthand for `Value(object, self.type)`. - fn adapt(&self, object: BoundObject<'_>) -> pyo3::PyResult { - let lock = self.0.lock(); - let sql_type = lock.r#type.clone(); - - let result = super::value::ValueState::from_pyobject(sql_type, object)?; - Ok(result.into()) - } - - /// Shorthand for `SelectLabel(self, alias, window)` - #[pyo3(signature=(alias, window=None))] - fn label( - &self, - alias: String, - window: Option>, - ) -> pyo3::PyResult { - let window = match window { - Some(x) => Some(crate::query::select::SelectLabelWindow::try_from(x)?), - None => None, - }; - let expr = self.to_expr(); - - let state = crate::query::select::SelectLabelState { - expr, - alias: Some(alias), - window, - }; - Ok(state.into()) - } - - /// Shorthand for `Expr(self)` - fn to_expr(&self) -> PyExpr { - let col_ref = Self::__column_ref__(self); - PyExpr(sea_query::Expr::column(col_ref)) - } - - #[getter] - fn __column_ref__(&self) -> super::column_ref::PyColumnRef { - let lock = self.0.lock(); - - super::column_ref::PyColumnRef { - name: Some(sea_query::Alias::new(&lock.name).into_iden()), - table: None, - schema: None, - } - } - - fn __copy__(&self) -> Self { - let lock = self.0.lock(); - lock.clone().into() - } - - pub fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - ReprFormatter::new_with_pyref(&slf) - .quote("", &lock.name) - .display("type", &lock.r#type) - .optional_boolean("primary_key", lock.options & OPT_PRIMARY_KEY > 0) - .optional_boolean("unique_key", lock.options & OPT_UNIQUE_KEY > 0) - .optional_boolean("auto_increment", lock.options & OPT_AUTO_INCREMENT > 0) - .optional_display( - "nullable", - if lock.options & OPT_NOT_NULL > 0 { - Some(false) - } else if lock.options & OPT_NULL > 0 { - Some(true) - } else { - None - }, - ) - .optional_boolean("stored_generated", lock.options & OPT_STORED_GENERATED > 0) - .optional_quote("extra", lock.extra.as_ref()) - .optional_quote("comment", lock.comment.as_ref()) - .optional_map("default", lock.default.as_ref(), |x| x.__repr__()) - .optional_map("generated", lock.generated.as_ref(), |x| x.__repr__()) - .finish() - } -} diff --git a/src/common/column_ref.rs b/src/common/column_ref.rs deleted file mode 100644 index 59cdd71..0000000 --- a/src/common/column_ref.rs +++ /dev/null @@ -1,340 +0,0 @@ -use std::str::FromStr; - -use pyo3::types::{PyAnyMethods, PyStringMethods}; -use sea_query::IntoIden; - -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundKwargs, RefBoundObject}; - -crate::implement_pyclass! { - // NOTE: SQLTypes, PyExpr, PyFunc, PyTableName & PyColumnRef could never mark as subclass. - // these should be immutable and final types. - - /// Represents a reference to a database column with optional table and schema qualification. - /// - /// This class is used to uniquely identify columns in SQL queries, supporting - /// schema-qualified and table-qualified column references. - /// - /// NOTE: this class is immutable and frozen. - #[derive(Debug, Clone)] - [] PyColumnRef as "ColumnRef" { - /// Name of the referenced column. [`Option::None`] means '*'. - pub name: Option, - pub table: Option, - pub schema: Option, - } -} - -impl sea_query::IntoColumnRef for PyColumnRef { - fn into_column_ref(self) -> sea_query::ColumnRef { - if let Some(name) = self.name { - match (self.table, self.schema) { - (Some(table), Some(schema)) => { - sea_query::ColumnRef::SchemaTableColumn(schema, table, name) - } - (Some(table), None) => sea_query::ColumnRef::TableColumn(table, name), - _ => sea_query::ColumnRef::Column(name), - } - } else if let Some(table) = self.table { - sea_query::ColumnRef::TableAsterisk(table) - } else { - sea_query::ColumnRef::Asterisk - } - } -} - -impl FromStr for PyColumnRef { - type Err = pyo3::PyErr; - - fn from_str(string: &str) -> Result { - let string = string.trim().to_owned(); - - if string.is_empty() { - return Err(pyo3::exceptions::PyValueError::new_err( - "cannot parse an empty string", - )); - } - - // Possible formats: - // name - // table.name - // schema.table.name - let mut string: Vec = string.split('.').map(String::from).collect(); - - if string.len() > 3 { - return Err(pyo3::exceptions::PyValueError::new_err("invalid format")); - } - - let name = string.pop().unwrap(); - let table = string.pop().map(|x| sea_query::Alias::new(x).into_iden()); - let schema = string.pop().map(|x| sea_query::Alias::new(x).into_iden()); - - Ok(Self { - name: if name == "*" { - None - } else { - Some(sea_query::Alias::new(name).into_iden()) - }, - table, - schema, - }) - } -} - -impl TryFrom> for PyColumnRef { - type Error = pyo3::PyErr; - - fn try_from(value: RefBoundObject<'_>) -> Result { - unsafe { - if pyo3::ffi::Py_TYPE(value.as_ptr()) == crate::typeref::COLUMN_REF_TYPE { - let casted_value = value.cast_unchecked::(); - return Ok(casted_value.get().clone()); - } - - if let Ok(x) = value.cast_exact::() { - return Self::from_str(x.to_str()?); - } - - if let Some(result) = Self::try_from_property(value)? { - return Ok(result); - } - - crate::new_error!( - PyTypeError, - "expected ColumnRef, str, or object.to_column_ref property, got {}", - crate::internal::get_type_name(value.py(), value.as_ptr()) - ) - } - } -} - -impl PyColumnRef { - #[inline] - pub fn try_from_property(value: RefBoundObject) -> pyo3::PyResult> { - const PROPERTY_NAME: &std::ffi::CStr = c"__column_ref__"; - - let property = match value.getattr(PROPERTY_NAME) { - Ok(x) => Ok(Some(x)), - Err(err) if err.is_instance_of::(value.py()) => { - Ok(None) - } - Err(err) => Err(err), - }; - let property = property?; - - if property.is_none() { - return Ok(None); - } - - unsafe { - let property = property.unwrap_unchecked(); - - if pyo3::ffi::Py_TYPE(property.as_ptr()) == crate::typeref::COLUMN_REF_TYPE { - let casted_value = property.cast_unchecked::(); - return Ok(Some(casted_value.get().clone())); - } - - if let Ok(x) = property.extract::() { - return Self::from_str(&x).map(Some); - } - - crate::new_error!( - PyTypeError, - "__column_ref__ property returns something other than ColumnRef or str; returns {}", - crate::internal::get_type_name(property.py(), property.as_ptr()) - ) - } - } -} - -#[pyo3::pymethods] -impl PyColumnRef { - #[new] - #[pyo3(signature=(name, table=None, schema=None))] - fn __new__( - name: String, - table: Option, - schema: Option, - ) -> pyo3::PyResult { - let name = unsafe { - if name == "*" { - None - } else { - Some(name) - } - }; - - Ok(Self { - name: name.map(|x| sea_query::Alias::new(x).into_iden()), - table: table.map(|x| sea_query::Alias::new(x).into_iden()), - schema: schema.map(|x| sea_query::Alias::new(x).into_iden()), - }) - } - - #[getter] - fn name(&self) -> String { - match &self.name { - None => String::from("*"), - Some(x) => x.to_string(), - } - } - - #[getter] - fn table(&self) -> Option { - self.table.as_ref().map(|x| x.to_string()) - } - - #[getter] - fn schema(&self) -> Option { - self.schema.as_ref().map(|x| x.to_string()) - } - - /// Parse a string representation of a column reference. - /// - /// Supports formats like: - /// - "column_name" - /// - "table.column_name" - /// - "schema.table.column_name" - #[classmethod] - fn parse(_cls: &pyo3::Bound<'_, pyo3::types::PyType>, string: String) -> pyo3::PyResult { - Self::from_str(&string) - } - - #[pyo3(signature=(**kwds))] - fn copy_with(&self, kwds: Option>) -> pyo3::PyResult { - use pyo3::types::PyDictMethods; - - let mut cloned = self.clone(); - if kwds.is_none() { - return Ok(cloned); - } - - let kwds = unsafe { kwds.unwrap_unchecked() }; - - for (key, val) in kwds.iter() { - unsafe { - let key = key.extract::().unwrap_unchecked(); - - let val: Option = unsafe { - if pyo3::ffi::Py_IsNone(val.as_ptr()) == 1 { - None - } else if pyo3::ffi::PyUnicode_CheckExact(val.as_ptr()) == 1 { - Some(val.extract::().unwrap_unchecked()) - } else { - return crate::new_error!( - PyTypeError, - "expected str or None, got {}", - crate::internal::get_type_name(val.py(), val.as_ptr()) - ); - } - }; - - if key == "name" { - if val.is_none() { - return crate::new_error!(PyTypeError, "expected str for name, got None"); - } - - let val_str = unsafe { val.unwrap_unchecked() }; - if val_str == "*" { - cloned.name = None; - } else { - cloned.name = Some(sea_query::Alias::new(val_str).into_iden()); - } - } else if key == "table" { - cloned.table = val.map(|x| sea_query::Alias::new(x).into_iden()); - } else if key == "schema" { - cloned.schema = val.map(|x| sea_query::Alias::new(x).into_iden()); - } else { - return crate::new_error!( - PyTypeError, - "got an unexpected keyword argument '{}'", - key - ); - } - } - } - - Ok(cloned) - } - - /// Shorthand for `SelectLabel(self, alias, window)` - #[pyo3(signature=(alias, window=None))] - fn label( - &self, - alias: String, - window: Option>, - ) -> pyo3::PyResult { - let window = match window { - Some(x) => Some(crate::query::select::SelectLabelWindow::try_from(x)?), - None => None, - }; - let expr = self.to_expr(); - - let state = crate::query::select::SelectLabelState { - expr, - alias: Some(alias), - window, - }; - Ok(state.into()) - } - - fn __eq__(slf: pyo3::PyRef<'_, Self>, other: pyo3::PyRef<'_, Self>) -> bool { - if slf.as_ptr() == other.as_ptr() { - return true; - } - - slf.name == other.name && slf.schema == other.schema && slf.table == other.table - } - - fn __ne__(slf: pyo3::PyRef<'_, Self>, other: pyo3::PyRef<'_, Self>) -> bool { - if slf.as_ptr() == other.as_ptr() { - return false; - } - - slf.name != other.name || slf.schema != other.schema || slf.table != other.table - } - - fn __copy__(&self) -> Self { - self.clone() - } - - fn __hash__(&self) -> u64 { - use std::hash::{Hash, Hasher}; - - let mut state = std::hash::DefaultHasher::new(); - - if let Some(x) = &self.name { - x.to_string().hash(&mut state); - } - if let Some(x) = &self.table { - x.to_string().hash(&mut state); - } - if let Some(x) = &self.schema { - x.to_string().hash(&mut state); - } - - state.finish() - } - - /// Shorthand for `Expr(self)` - fn to_expr(&self) -> super::expression::PyExpr { - super::expression::PyExpr(sea_query::Expr::column(self.clone())) - } - - pub fn __repr__(&self) -> String { - let mut fmt = ReprFormatter::new("ColumnRef"); - - match &self.name { - Some(x) => { - fmt.iden("name", x); - } - None => { - fmt.pair("name", "*"); - } - } - fmt.optional_iden("table", self.table.as_ref()); - fmt.optional_iden("schema", self.schema.as_ref()); - - fmt.finish() - } -} diff --git a/src/common/expression/expr.rs b/src/common/expression/expr.rs deleted file mode 100644 index 9ec08ae..0000000 --- a/src/common/expression/expr.rs +++ /dev/null @@ -1,700 +0,0 @@ -use pyo3::types::PyAnyMethods; - -use crate::internal::repr::ReprFormatter; -use crate::internal::type_engine::TypeEngine; -use crate::internal::{BoundObject, RefBoundObject, ToSeaQuery}; - -crate::implement_pyclass! { - // NOTE: SQLTypes, PyExpr, PyFunc, PyTableName & PyColumnRef could never mark as subclass. - // these should be immutable and final types. - - /// Represents a SQL expression that can be built into SQL code. - /// - /// This class provides a fluent interface for constructing complex SQL expressions - /// in a database-agnostic way. It supports arithmetic operations, comparisons, - /// logical operations, and database-specific functions. - /// - /// The class automatically handles SQL injection protection and proper quoting - /// when building the final SQL statement. - /// - /// NOTE: `Expr` is immutable, so by calling each method you will give a new instance - /// of it which includes new change(s). - #[derive(Debug, Clone)] - [] PyExpr as "Expr" (pub sea_query::SimpleExpr); -} - -impl PyExpr { - pub fn try_from_specific_type( - value: RefBoundObject<'_>, - type_engine: Option, - ) -> pyo3::PyResult { - unsafe { - let py = value.py(); - let type_ptr = pyo3::ffi::Py_TYPE(value.as_ptr()); - - if type_ptr == crate::typeref::EXPR_TYPE { - let casted_value = value.cast_unchecked::(); - - return Ok(casted_value.get().clone()); - } - - if pyo3::ffi::PyObject_TypeCheck(value.as_ptr(), crate::typeref::VALUE_TYPE) == 1 { - let casted_value = value.cast_unchecked::(); - let unbound = casted_value.get(); - - return Ok(Self(unbound.0.lock().simple_expr(py)?)); - } - - if type_ptr == crate::typeref::COLUMN_REF_TYPE { - let casted_value = value.cast_unchecked::(); - let cloned = casted_value.get().clone(); - - return Ok(Self(sea_query::Expr::column(cloned))); - } - - if type_ptr == crate::typeref::FUNC_TYPE { - let casted_value = value.cast_unchecked::(); - let cloned = casted_value.get().clone(); - - return Ok(Self(sea_query::SimpleExpr::FunctionCall(cloned.0))); - } - - if pyo3::ffi::PyObject_TypeCheck(value.as_ptr(), crate::typeref::SELECT_STATEMENT_TYPE) - == 1 - { - let casted_value = - value.cast_unchecked::(); - - let inner_value = casted_value.get(); - let result = sea_query::SimpleExpr::SubQuery( - None, - Box::new(sea_query::SubQueryStatement::SelectStatement( - inner_value.0.lock().to_sea_query(py), - )), - ); - - return Ok(Self(result)); - } - - if pyo3::ffi::PyObject_TypeCheck(value.as_ptr(), crate::typeref::CASE_STATEMENT_TYPE) - == 1 - { - let casted_value = value.cast_unchecked::(); - - let inner_value = casted_value.get(); - let result = - sea_query::SimpleExpr::Case(Box::new(inner_value.0.lock().to_sea_query(py))); - - return Ok(Self(result)); - } - - if pyo3::ffi::PyTuple_Check(value.as_ptr()) == 1 { - use pyo3::types::PyTupleMethods; - - let casted_value = value.cast_unchecked::(); - let mut arr = Vec::with_capacity(casted_value.len()); - - for item in casted_value.iter() { - arr.push(Self::try_from(&item)?); - } - - let result = sea_query::Expr::tuple(arr.into_iter().map(|x| x.0)); - return Ok(Self(result.into())); - } - - if let Some(result) = Self::try_from_property(value)? { - return Ok(result); - } - - if let Some(result) = crate::common::column_ref::PyColumnRef::try_from_property(value)? - { - return Ok(Self(sea_query::Expr::column(result))); - } - - let type_engine = match type_engine { - Some(x) => x, - None => TypeEngine::infer_pyobject(value)?, - }; - - let result = - crate::common::value::ValueState::from_pyobject(type_engine, value.clone())? - .simple_expr(py)?; - - Ok(Self(result)) - } - } - - #[inline] - pub fn try_from_property(value: RefBoundObject) -> pyo3::PyResult> { - const PROPERTY_NAME: &std::ffi::CStr = c"__expr__"; - - let property = match value.getattr(PROPERTY_NAME) { - Ok(x) => Ok(Some(x)), - Err(err) if err.is_instance_of::(value.py()) => { - Ok(None) - } - Err(err) => Err(err), - }; - let property = property?; - - if property.is_none() { - return Ok(None); - } - - unsafe { - let property = property.unwrap_unchecked(); - - if pyo3::ffi::Py_TYPE(property.as_ptr()) == crate::typeref::EXPR_TYPE { - let casted_value = property.cast_into_unchecked::(); - return Ok(Some(casted_value.get().clone())); - } - - crate::new_error!( - PyTypeError, - "__expr__ property returns something other than Expr; returns {}", - crate::internal::get_type_name(property.py(), property.as_ptr()) - ) - } - } -} - -impl From for PyExpr { - fn from(value: sea_query::SimpleExpr) -> Self { - Self(value) - } -} - -impl TryFrom> for PyExpr { - type Error = pyo3::PyErr; - - fn try_from(value: RefBoundObject<'_>) -> Result { - Self::try_from_specific_type(value, None) - } -} - -#[pyo3::pymethods] -impl PyExpr { - #[new] - #[pyo3(signature = (value, /))] - fn __new__(value: RefBoundObject<'_>) -> pyo3::PyResult { - Self::try_from(value) - } - - /// Shorthand for `Expr(Value(value, sql_type))` - #[classmethod] - #[pyo3(signature=(value, sql_type=None))] - fn val( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - value: RefBoundObject<'_>, - sql_type: Option>, - ) -> pyo3::PyResult { - unsafe { - if pyo3::ffi::Py_TYPE(value.as_ptr()) == crate::typeref::VALUE_TYPE { - let casted_value = value.cast_unchecked::(); - let unbound = casted_value.get(); - - return Ok(Self(unbound.0.lock().simple_expr(value.py())?)); - } - - let type_engine = { - if let Some(sql_type) = sql_type { - TypeEngine::new(&sql_type)? - } else { - TypeEngine::infer_pyobject(value)? - } - }; - - let result = - crate::common::value::ValueState::from_pyobject(type_engine, value.clone())? - .simple_expr(value.py())?; - - Ok(Self(result)) - } - } - - /// Tries to convert the `value` into `ColumnRef`, and then converts it to `Expr`. - #[classmethod] - fn col( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - value: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let column_ref = crate::common::column_ref::PyColumnRef::try_from(value)?; - Ok(Self(sea_query::Expr::column(column_ref))) - } - - /// Returns asterisk '*' expression. - #[classmethod] - fn asterisk(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - sea_query::Expr::column(sea_query::Asterisk).into() - } - - /// Create an expression from a custom SQL string. - /// - /// Warning: This method does not escape the input, so it should only - /// be used with trusted strings to avoid SQL injection vulnerabilities. - #[classmethod] - fn custom(_cls: &pyo3::Bound<'_, pyo3::types::PyType>, value: String) -> Self { - sea_query::SimpleExpr::Custom(value).into() - } - - /// Create an expression for the CURRENT_DATE SQL function. - #[classmethod] - fn current_date(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - sea_query::SimpleExpr::Keyword(sea_query::Keyword::CurrentDate).into() - } - - /// Create an expression for the CURRENT_TIME SQL function. - #[classmethod] - fn current_time(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - sea_query::SimpleExpr::Keyword(sea_query::Keyword::CurrentTime).into() - } - - /// Create an expression for the CURRENT_TIMESTAMP SQL function. - #[classmethod] - fn current_timestamp(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - sea_query::SimpleExpr::Keyword(sea_query::Keyword::CurrentTimestamp).into() - } - - /// Create an expression representing the NULL value. - #[classmethod] - fn null(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - sea_query::SimpleExpr::Keyword(sea_query::Keyword::Null).into() - } - - /// Express a `EXISTS` sub-query expression. - #[classmethod] - fn exists( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - statement: RefBoundObject<'_>, - ) -> pyo3::PyResult { - unsafe { - if pyo3::ffi::PyObject_TypeCheck( - statement.as_ptr(), - crate::typeref::SELECT_STATEMENT_TYPE, - ) != 1 - { - return crate::new_error!( - PyTypeError, - "expected SelectStatement, got {}", - crate::internal::get_type_name(statement.py(), statement.as_ptr()) - ); - } - - let casted_statement = - statement.cast_unchecked::(); - - let inner_statement = casted_statement.get(); - - let result = inner_statement.0.lock().to_sea_query(_cls.py()); - - Ok(Self(sea_query::Expr::exists(result))) - } - } - - /// Express a `ANY` sub-query expression. - #[classmethod] - fn any( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - statement: RefBoundObject<'_>, - ) -> pyo3::PyResult { - unsafe { - if pyo3::ffi::PyObject_TypeCheck( - statement.as_ptr(), - crate::typeref::SELECT_STATEMENT_TYPE, - ) != 1 - { - return crate::new_error!( - PyTypeError, - "expected SelectStatement, got {}", - crate::internal::get_type_name(statement.py(), statement.as_ptr()) - ); - } - - let casted_statement = - statement.cast_unchecked::(); - - let inner_statement = casted_statement.get(); - - let result = inner_statement.0.lock().to_sea_query(_cls.py()); - - Ok(Self(sea_query::Expr::any(result))) - } - } - - /// Express a `SOME` sub-query expression. - #[classmethod] - fn some( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - statement: RefBoundObject<'_>, - ) -> pyo3::PyResult { - unsafe { - if pyo3::ffi::PyObject_TypeCheck( - statement.as_ptr(), - crate::typeref::SELECT_STATEMENT_TYPE, - ) != 1 - { - return crate::new_error!( - PyTypeError, - "expected SelectStatement, got {}", - crate::internal::get_type_name(statement.py(), statement.as_ptr()) - ); - } - - let casted_statement = - statement.cast_unchecked::(); - - let inner_statement = casted_statement.get(); - - let result = inner_statement.0.lock().to_sea_query(_cls.py()); - - Ok(Self(sea_query::Expr::some(result))) - } - } - - /// Express a `ALL` sub-query expression. - #[classmethod] - fn all( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - statement: RefBoundObject<'_>, - ) -> pyo3::PyResult { - unsafe { - if pyo3::ffi::PyObject_TypeCheck( - statement.as_ptr(), - crate::typeref::SELECT_STATEMENT_TYPE, - ) != 1 - { - return crate::new_error!( - PyTypeError, - "expected SelectStatement, got {}", - crate::internal::get_type_name(statement.py(), statement.as_ptr()) - ); - } - - let casted_statement = - statement.cast_unchecked::(); - - let inner_statement = casted_statement.get(); - let result = inner_statement.0.lock().to_sea_query(_cls.py()); - - Ok(Self(sea_query::Expr::all(result))) - } - } - - /// Express a `IN` expression. - fn in_(slf: pyo3::PyRef<'_, Self>, other: RefBoundObject<'_>) -> pyo3::PyResult { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(other.as_ptr(), crate::typeref::SELECT_STATEMENT_TYPE) - == 1 - { - let casted_statement = - other.cast_unchecked::(); - - let inner_statement = casted_statement.get(); - let result = inner_statement.0.lock().to_sea_query(slf.py()); - let result = sea_query::ExprTrait::in_subquery(slf.0.clone(), result); - - return Ok(result.into()); - } - } - - let mut exprs: Vec = Vec::new(); - - for item in other.try_iter()? { - let item = item?; - let item = Self::try_from(&item)?; - exprs.push(item.0); - } - - Ok(sea_query::ExprTrait::is_in(slf.0.clone(), exprs).into()) - } - - /// Express a `NOT IN` expression. - fn not_in(slf: pyo3::PyRef<'_, Self>, other: RefBoundObject<'_>) -> pyo3::PyResult { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(other.as_ptr(), crate::typeref::SELECT_STATEMENT_TYPE) - == 1 - { - let casted_statement = - other.cast_unchecked::(); - - let inner_statement = casted_statement.get(); - let result = inner_statement.0.lock().to_sea_query(slf.py()); - let result = sea_query::ExprTrait::not_in_subquery(slf.0.clone(), result); - - return Ok(result.into()); - } - } - - let mut exprs: Vec = Vec::new(); - - for item in other.try_iter()? { - let item = item?; - let item = Self::try_from(&item)?; - exprs.push(item.0); - } - - Ok(sea_query::ExprTrait::is_not_in(slf.0.clone(), exprs).into()) - } - - /// Create a `CAST` expression to convert to a specific SQL type. - fn cast_as(slf: pyo3::PyRef<'_, Self>, value: String) -> Self { - slf.0.clone().cast_as(sea_query::Alias::new(value)).into() - } - - /// Create a `LIKE` pattern matching expression. - #[pyo3(signature=(pattern, escape=None))] - fn like(slf: pyo3::PyRef<'_, Self>, pattern: String, escape: Option) -> Self { - let e = sea_query::LikeExpr::new(pattern); - - if let Some(x) = escape { - sea_query::ExprTrait::like(slf.0.clone(), e.escape(x)).into() - } else { - sea_query::ExprTrait::like(slf.0.clone(), e).into() - } - } - - /// Create a NOT LIKE pattern matching expression. - #[pyo3(signature=(pattern, escape=None))] - fn not_like(slf: pyo3::PyRef<'_, Self>, pattern: String, escape: Option) -> Self { - let e = sea_query::LikeExpr::new(pattern); - - if let Some(x) = escape { - sea_query::ExprTrait::not_like(slf.0.clone(), e.escape(x)).into() - } else { - sea_query::ExprTrait::not_like(slf.0.clone(), e).into() - } - } - - /// Create an equality comparison expression. - fn __eq__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::eq(slf.0.clone(), other.0).into()) - } - - /// Create an inequality comparison expression. - fn __ne__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::ne(slf.0.clone(), other.0).into()) - } - - /// Create a greater-than comparison expression. - fn __gt__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::gt(slf.0.clone(), other.0).into()) - } - - /// Create a greater-than-or-equal comparison expression. - fn __ge__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::gte(slf.0.clone(), other.0).into()) - } - - /// Create a less-than comparison expression. - fn __lt__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::lt(slf.0.clone(), other.0).into()) - } - - /// Create a less-than-or-equal comparison expression. - fn __le__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::lte(slf.0.clone(), other.0).into()) - } - - /// Create an addition expression. - fn __add__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::add(slf.0.clone(), other.0).into()) - } - - /// Create an subtraction expression. - fn __sub__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::sub(slf.0.clone(), other.0).into()) - } - - /// Create a logical AND expression. - fn __and__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::and(slf.0.clone(), other.0).into()) - } - - fn __neg__<'a>(slf: pyo3::PyRef<'a, Self>) -> pyo3::PyResult { - Ok(sea_query::ExprTrait::mul(slf.0.clone(), -1).into()) - } - - /// Create a logical OR expression. - fn __or__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::or(slf.0.clone(), other.0).into()) - } - - fn bit_and<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::bit_and(slf.0.clone(), other.0).into()) - } - - fn bit_or<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::bit_or(slf.0.clone(), other.0).into()) - } - - /// Create a division expression. - fn __truediv__<'a>( - slf: pyo3::PyRef<'a, Self>, - other: RefBoundObject<'a>, - ) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::div(slf.0.clone(), other.0).into()) - } - - /// Create an IS comparison expression. - fn is_<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::is(slf.0.clone(), other.0).into()) - } - - /// Create an IS NOT comparison expression. - fn is_not<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::is_not(slf.0.clone(), other.0).into()) - } - - /// Create an IS NULL expression. - fn is_null(slf: pyo3::PyRef<'_, Self>) -> Self { - sea_query::ExprTrait::is_null(slf.0.clone()).into() - } - - /// Create an IS NOT NULL expression. - fn is_not_null(slf: pyo3::PyRef<'_, Self>) -> Self { - sea_query::ExprTrait::is_not_null(slf.0.clone()).into() - } - - /// Create a bitwise left shift expression. - fn __lshift__<'a>( - slf: pyo3::PyRef<'a, Self>, - other: RefBoundObject<'a>, - ) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::left_shift(slf.0.clone(), other.0).into()) - } - - /// Create a bitwise right shift expression. - fn __rshift__<'a>( - slf: pyo3::PyRef<'a, Self>, - other: RefBoundObject<'a>, - ) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::right_shift(slf.0.clone(), other.0).into()) - } - - /// Create a modulo expression. - fn __mod__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::modulo(slf.0.clone(), other.0).into()) - } - - /// Create a multiplication expression. - fn __mul__<'a>(slf: pyo3::PyRef<'a, Self>, other: RefBoundObject<'a>) -> pyo3::PyResult { - let other = Self::try_from(other)?; - Ok(sea_query::ExprTrait::mul(slf.0.clone(), other.0).into()) - } - - /// Create a BETWEEN range comparison expression. - fn between<'a>( - slf: pyo3::PyRef<'a, Self>, - a: RefBoundObject<'a>, - b: RefBoundObject<'a>, - ) -> pyo3::PyResult { - let a = Self::try_from(a)?; - let b = Self::try_from(b)?; - - Ok(sea_query::ExprTrait::between(slf.0.clone(), a.0, b.0).into()) - } - - /// Create a NOT BETWEEN range comparison expression. - fn not_between<'a>( - slf: pyo3::PyRef<'a, Self>, - a: RefBoundObject<'a>, - b: RefBoundObject<'a>, - ) -> pyo3::PyResult { - let a = Self::try_from(a)?; - let b = Self::try_from(b)?; - - Ok(sea_query::ExprTrait::not_between(slf.0.clone(), a.0, b.0).into()) - } - - /// Create `MAX(self)` function call. - /// - /// Shorthand for `Func.max(self).to_expr()` - fn max(&self) -> Self { - let expr: sea_query::SimpleExpr = sea_query::Func::max(self.0.clone()).into(); - Self(expr) - } - - /// Create `MIN(self)` function call. - /// - /// Shorthand for `Func.min(self).to_expr()` - fn min(&self) -> Self { - let expr: sea_query::SimpleExpr = sea_query::Func::min(self.0.clone()).into(); - Self(expr) - } - - /// Create `ABS(self)` function call. - /// - /// Shorthand for `Func.abs(self).to_expr()` - fn abs(&self) -> Self { - let expr: sea_query::SimpleExpr = sea_query::Func::abs(self.0.clone()).into(); - Self(expr) - } - - /// Shorthand for `SelectLabel(self, alias, window)` - #[pyo3(signature=(alias, window=None))] - fn label( - &self, - alias: String, - window: Option>, - ) -> pyo3::PyResult { - let window = match window { - Some(x) => Some(crate::query::select::SelectLabelWindow::try_from(x)?), - None => None, - }; - - let state = crate::query::select::SelectLabelState { - expr: self.clone(), - alias: Some(alias), - window, - }; - Ok(state.into()) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn _to_sql(&self, _py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let builder = crate::internal::get_schema_builder(backend)?; - let mut sql = String::new(); - - let assert_unwind = std::panic::AssertUnwindSafe(|| { - sea_query::QueryBuilder::prepare_simple_expr(&*builder, &self.0, &mut sql) - }); - - std::panic::catch_unwind(assert_unwind) - .map_err(|_| pyo3::PyErr::new::("build failed"))?; - - Ok(sql) - } - - // TODO: sqlite_*, pg_*, mysql_* - - pub fn __repr__(&self) -> String { - #[cfg(not(debug_assertions))] - { - ReprFormatter::new("Expr").pair("", "...").finish() - } - - #[cfg(debug_assertions)] - { - ReprFormatter::new("Expr").debug("", &self.0).finish() - } - } -} diff --git a/src/common/expression/func.rs b/src/common/expression/func.rs deleted file mode 100644 index 5cd209e..0000000 --- a/src/common/expression/func.rs +++ /dev/null @@ -1,331 +0,0 @@ -use pyo3::types::PyTupleMethods; -use sea_query::IntoIden; - -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, RefBoundObject}; - -crate::implement_pyclass! { - // NOTE: SQLTypes, PyExpr, PyFunc, PyTableName & PyColumnRef could never mark as subclass. - // these should be immutable and final types. - - /// Represents a SQL function call that can be used in expressions. - /// - /// This class provides a type-safe way to construct SQL function calls - /// with proper argument handling and database dialect support. - #[derive(Debug, Clone)] - [] PyFunc as "Func" (pub sea_query::FunctionCall); -} - -#[pyo3::pymethods] -impl PyFunc { - #[new] - #[pyo3(signature=(name, *args))] - pub fn __new__(name: String, args: BoundArgs<'_>) -> pyo3::PyResult { - let mut function_call = sea_query::Func::cust(sea_query::Alias::new(name)); - - for item in args.iter() { - let expr = super::expr::PyExpr::try_from(&item)?; - function_call = function_call.arg(expr.0); - } - - Ok(Self(function_call)) - } - - /// Create a NOW() function call. - #[classmethod] - fn now(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - Self(sea_query::Func::cust(sea_query::Alias::new("NOW"))) - } - - /// Create a SUM(expr) function call. - #[classmethod] - fn sum( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::sum(expr.0))) - } - - /// Create a MIN(expr) function call. - #[classmethod] - fn min( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::min(expr.0))) - } - - /// Create a MAX(expr) function call. - #[classmethod] - fn max( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::max(expr.0))) - } - - /// Create a ABS(expr) function call. - #[classmethod] - fn abs( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::abs(expr.0))) - } - - /// Create a AVG(expr) function call. - #[classmethod] - fn avg( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::avg(expr.0))) - } - - /// Create a COUNT(expr) function call. - #[classmethod] - fn count( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::count(expr.0))) - } - - /// Create a COUNT(DISTINCT expr) function call. - #[classmethod] - fn count_distinct( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::count_distinct(expr.0))) - } - - /// Create a IF_NULL(a, b) function call. - #[classmethod] - fn if_null( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - a: RefBoundObject<'_>, - b: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let a = super::expr::PyExpr::try_from(a)?; - let b = super::expr::PyExpr::try_from(b)?; - - Ok(Self(sea_query::Func::if_null(a.0, b.0))) - } - - /// Create a GREATEST function call. - #[classmethod] - #[pyo3(signature=(*exprs))] - fn greatest( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - exprs: BoundArgs<'_>, - ) -> pyo3::PyResult { - let mut simple_exprs = Vec::with_capacity(exprs.len()); - - for expr in exprs.iter() { - let expr = super::expr::PyExpr::try_from(&expr)?; - simple_exprs.push(expr.0); - } - - Ok(Self(sea_query::Func::greatest(simple_exprs))) - } - - /// Create a LEAST function call. - #[classmethod] - #[pyo3(signature=(*exprs))] - fn least( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - exprs: BoundArgs<'_>, - ) -> pyo3::PyResult { - let mut simple_exprs = Vec::with_capacity(exprs.len()); - - for expr in exprs.iter() { - let expr = super::expr::PyExpr::try_from(&expr)?; - simple_exprs.push(expr.0); - } - - Ok(Self(sea_query::Func::least(simple_exprs))) - } - - /// Create a CHAR_LENGTH(expr) function call. - #[classmethod] - fn char_length( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::char_length(expr.0))) - } - - /// Create a COALESCE function call. - #[classmethod] - #[pyo3(signature=(*exprs))] - fn coalesce( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - exprs: BoundArgs<'_>, - ) -> pyo3::PyResult { - let mut simple_exprs = Vec::with_capacity(exprs.len()); - - for expr in exprs.iter() { - let expr = super::expr::PyExpr::try_from(&expr)?; - simple_exprs.push(expr.0); - } - - Ok(Self(sea_query::Func::coalesce(simple_exprs))) - } - - /// Create a LOWER(expr) function call. - #[classmethod] - fn lower( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::lower(expr.0))) - } - - /// Create a UPPER(expr) function call. - #[classmethod] - fn upper( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::upper(expr.0))) - } - - /// Create a BIT_AND(expr) function call - this is not supported on SQLite. - #[classmethod] - fn bit_and( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::bit_and(expr.0))) - } - - /// Create a BIT_OR(expr) function call - this is not supported on SQLite. - #[classmethod] - fn bit_or( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::bit_or(expr.0))) - } - - /// Create a RANDOM() function call. - #[classmethod] - fn random(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - Self(sea_query::Func::random()) - } - - /// Create a RANK() function call. - #[classmethod] - fn rank(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - Self(sea_query::Func::cust("RANK")) - } - - /// Create a DENSE_RANK() function call. - #[classmethod] - fn dense_rank(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - Self(sea_query::Func::cust("DENSE_RANK")) - } - - /// Create a PERCENT_RANK() function call. - #[classmethod] - fn percent_rank(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - Self(sea_query::Func::cust("PERCENT_RANK")) - } - - /// Create a ROUND(expr) function call. - #[classmethod] - fn round( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::round(expr.0))) - } - - /// Create a ROUND(a, b) function call. - #[classmethod] - fn round_with_precision( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - a: RefBoundObject<'_>, - b: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let a = super::expr::PyExpr::try_from(a)?; - let b = super::expr::PyExpr::try_from(b)?; - - Ok(Self(sea_query::Func::round_with_precision(a.0, b.0))) - } - - /// Create a MD5(expr) function call - this is only available in Postgres and MySQL. - #[classmethod] - fn md5( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::md5(expr.0))) - } - - #[classmethod] - fn cast_as( - _cls: &pyo3::Bound<'_, pyo3::types::PyType>, - expr: RefBoundObject<'_>, - alias: String, - ) -> pyo3::PyResult { - let expr = super::expr::PyExpr::try_from(expr)?; - Ok(Self(sea_query::Func::cast_as( - expr.0, - sea_query::Alias::new(alias).into_iden(), - ))) - } - - /// Shorthand for `SelectLabel(self, alias, window)` - #[pyo3(signature=(alias, window=None))] - fn label( - &self, - alias: String, - window: Option>, - ) -> pyo3::PyResult { - let window = match window { - Some(x) => Some(crate::query::select::SelectLabelWindow::try_from(x)?), - None => None, - }; - let expr = self.to_expr(); - - let state = crate::query::select::SelectLabelState { - expr, - alias: Some(alias), - window, - }; - Ok(state.into()) - } - - /// Shorthand for `Expr(self)` - fn to_expr(&self) -> super::expr::PyExpr { - super::expr::PyExpr(sea_query::SimpleExpr::FunctionCall(self.0.clone())) - } - - pub fn __repr__(&self) -> String { - #[cfg(not(debug_assertions))] - { - ReprFormatter::new("Func").pair("", "...").finish() - } - - #[cfg(debug_assertions)] - { - ReprFormatter::new("Func").debug("", &self.0).finish() - } - } -} diff --git a/src/common/expression/mod.rs b/src/common/expression/mod.rs deleted file mode 100644 index 82c61f1..0000000 --- a/src/common/expression/mod.rs +++ /dev/null @@ -1,57 +0,0 @@ -mod expr; -mod func; - -pub use expr::*; -pub use func::*; - -use crate::internal::{BoundArgs, PyObject}; - -/// Create a logical AND condition that is true only if all conditions are true. -/// -/// This is equivalent to SQL's AND operator applied to multiple expressions. -/// -/// @signature (arg1: Expr, *args: Expr) -> Expr -#[pyo3::pyfunction] -#[pyo3(signature=(arg1, *args))] -pub fn all(arg1: pyo3::Bound<'_, PyExpr>, args: BoundArgs<'_>) -> pyo3::PyResult { - let py = arg1.py(); - let mut expr = arg1.unbind(); - - for m in args { - let m = m.cast_into_exact::()?; - - let result = sea_query::ExprTrait::and(expr.get().0.clone(), m.get().0.clone()); - expr = pyo3::Py::new(py, PyExpr(result))?; - } - - Ok(expr.into_any()) -} - -/// Create a logical OR condition that is true if any condition is true. -/// -/// This is equivalent to SQL's OR operator applied to multiple expressions. -/// -/// @signature (arg1: Expr, *args: Expr) -> Expr -#[pyo3::pyfunction] -#[pyo3(signature=(arg1, *args))] -pub fn any(arg1: pyo3::Bound<'_, PyExpr>, args: BoundArgs<'_>) -> pyo3::PyResult { - let py = arg1.py(); - let mut expr = arg1.unbind(); - - for m in args { - let m = m.cast_into_exact::()?; - - let result = sea_query::ExprTrait::or(expr.get().0.clone(), m.get().0.clone()); - expr = pyo3::Py::new(py, PyExpr(result))?; - } - - Ok(expr.into_any()) -} - -/// Create a logical NOT. -/// -/// @signature (arg: Expr) -> Expr -#[pyo3::pyfunction] -pub fn not_(arg: &pyo3::Bound<'_, PyExpr>) -> PyExpr { - sea_query::ExprTrait::not(arg.get().0.clone()).into() -} diff --git a/src/common/foreign_key.rs b/src/common/foreign_key.rs deleted file mode 100644 index ba72806..0000000 --- a/src/common/foreign_key.rs +++ /dev/null @@ -1,434 +0,0 @@ -use super::table_ref::PyTableName; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, RefBoundObject, ToSeaQuery}; - -#[inline] -fn map_str_to_foreign_key_action(value: String) -> pyo3::PyResult { - match value.to_lowercase().as_str() { - "cascade" => Ok(sea_query::ForeignKeyAction::Cascade), - "no action" => Ok(sea_query::ForeignKeyAction::NoAction), - "restrict" => Ok(sea_query::ForeignKeyAction::Restrict), - "set default" => Ok(sea_query::ForeignKeyAction::SetDefault), - "set null" => Ok(sea_query::ForeignKeyAction::SetNull), - _ => Err(pyo3::exceptions::PyValueError::new_err(format!( - "unknown foreign key action: {value}", - ))), - } -} - -#[inline] -fn map_foreign_key_action_to_str(value: sea_query::ForeignKeyAction) -> String { - match value { - sea_query::ForeignKeyAction::Cascade => String::from("CASCADE"), - sea_query::ForeignKeyAction::NoAction => String::from("NO ACTION"), - sea_query::ForeignKeyAction::Restrict => String::from("RESTRICT"), - sea_query::ForeignKeyAction::SetDefault => String::from("SET DEFAULT"), - sea_query::ForeignKeyAction::SetNull => String::from("SET NULL"), - } -} - -crate::implement_pyclass! { - /// Specifies a foreign key relationship between tables. - /// - /// Defines referential integrity constraints including: - /// - Source columns (in the child table) - /// - Target columns (in the parent table) - /// - Actions for updates and deletes (CASCADE, RESTRICT, SET NULL, etc.) - /// - Optional naming for the constraint - /// - /// Foreign keys ensure data consistency by requiring that values in the - /// child table's columns match existing values in the parent table's columns. - #[derive(Debug, Clone)] - mutable [subclass] PyForeignKey(ForeignKeyState) as "ForeignKey" { - /// Foreign key constraint name - pub name: Option, - - /// To table - pub to_table: PyTableName, - - /// To columns - pub to_columns: Vec, - - // /// From table - // pub from_table: Option, - - /// From columns - pub from_columns: Vec, - - /// On delete action - pub on_delete: Option, - - /// On update action - pub on_update: Option, - } -} - -impl ToSeaQuery for ForeignKeyState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::ForeignKeyCreateStatement { - let mut stmt = sea_query::ForeignKeyCreateStatement::new(); - stmt.to_tbl(self.to_table.clone()); - - if let Some(x) = &self.name { - stmt.name(x); - } - - for c in &self.from_columns { - stmt.from_col(sea_query::Alias::new(c)); - } - for c in &self.to_columns { - stmt.to_col(sea_query::Alias::new(c)); - } - - if let Some(x) = self.on_delete { - stmt.on_delete(x); - } - if let Some(x) = self.on_update { - stmt.on_update(x); - } - - stmt - } -} - -impl ToSeaQuery for ForeignKeyState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::TableForeignKey { - let mut stmt = sea_query::TableForeignKey::new(); - stmt.to_tbl(self.to_table.clone()); - - if let Some(x) = &self.name { - stmt.name(x); - } - - for c in &self.from_columns { - stmt.from_col(sea_query::Alias::new(c)); - } - for c in &self.to_columns { - stmt.to_col(sea_query::Alias::new(c)); - } - - if let Some(x) = self.on_delete { - stmt.on_delete(x); - } - if let Some(x) = self.on_update { - stmt.on_update(x); - } - - stmt - } -} - -#[pyo3::pymethods] -impl PyForeignKey { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> Self { - Self::uninit() - } - - #[ - pyo3( - signature=( - from_columns, - to_columns, - to_table=None, - name=None, - *, - on_delete=None, - on_update=None - ) - ) - ] - fn __init__( - &self, - from_columns: Vec>, - to_columns: Vec>, - to_table: Option>, - name: Option, - on_delete: Option, - on_update: Option, - ) -> pyo3::PyResult<()> { - // Validate & convert actions - let on_delete = match on_delete { - None => None, - Some(x) => Some(map_str_to_foreign_key_action(x)?), - }; - let on_update = match on_update { - None => None, - Some(x) => Some(map_str_to_foreign_key_action(x)?), - }; - - let mut to_table = match to_table { - Some(x) => Some(PyTableName::try_from(x)?), - None => None, - }; - - // Validate from/to_columns - if from_columns.is_empty() { - return crate::new_error!(PyValueError, "from_columns cannot be empty."); - } - if to_columns.is_empty() { - return crate::new_error!(PyValueError, "to_columns cannot be empty."); - } - if from_columns.len() != to_columns.len() { - return crate::new_error!( - PyValueError, - "from_columns and to_columns must have same length ({} != {})", - from_columns.len(), - to_columns.len() - ); - } - - // Convert from_columns - let mut from_columns_str = Vec::with_capacity(from_columns.len()); - - for col in from_columns.into_iter() { - let col_ref = super::column_ref::PyColumnRef::try_from(&col)?; - - match col_ref.name { - Some(x) => from_columns_str.push(x.to_string()), - None => { - return crate::new_error!( - PyValueError, - "ForeignKey cannot accept asterisk '*' as column" - ); - } - } - } - - // Convert to_columns - let mut to_columns_str = Vec::with_capacity(to_columns.len()); - - for col in to_columns.into_iter() { - let col_ref = super::column_ref::PyColumnRef::try_from(&col)?; - - match &col_ref.name { - Some(x) => to_columns_str.push(x.to_string()), - None => { - return crate::new_error!( - PyValueError, - "ForeignKey cannot accept asterisk '*' as column" - ); - } - } - - if let Some(detected_table_name) = col_ref.table { - let detected_table_name = PyTableName { - name: detected_table_name, - schema: col_ref.schema, - database: None, - alias: None, - }; - - if let Some(last) = &to_table { - if last.ne(&detected_table_name) { - return crate::new_error!(PyValueError, "multiple target table found"); - } - } - - to_table = Some(detected_table_name); - } - } - - if to_table.is_none() { - return crate::new_error!( - PyValueError, - "No target table found. You should specify the target table with `to_table`, or \ - `to_columns` parameter." - ); - } - let to_table = unsafe { to_table.unwrap_unchecked() }; - - // let name = match name { - // Some(x) => x, - // None => { - // let mut s = format!("fk_{}", to_table.name.to_string()); - - // for col in from_columns_str.iter() { - // s.push('_'); - // s += col; - // } - - // s.push('_'); - - // for col in to_columns_str.iter() { - // s.push('_'); - // s += col; - // } - - // s - // } - // }; - - let result = ForeignKeyState { - name, - to_table, - to_columns: to_columns_str, - from_columns: from_columns_str, - on_delete, - on_update, - }; - self.0.set(result); - Ok(()) - } - - /// Foreign key constraint name - #[getter] - fn name(&self) -> Option { - self.0.lock().name.clone() - } - - #[setter] - fn set_name(&self, val: Option) { - let mut lock = self.0.lock(); - lock.name = val; - } - - /// Referencing table. - #[getter] - fn to_table(&self) -> PyTableName { - self.0.lock().to_table.clone() - } - - #[setter] - fn set_to_table(&self, value: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let mut lock = self.0.lock(); - lock.to_table = super::table_ref::PyTableName::try_from(value)?; - Ok(()) - } - - /// Key columns. - #[getter] - #[allow(clippy::wrong_self_convention)] - fn from_columns(&self) -> Vec { - self.0.lock().from_columns.clone() - } - - #[setter] - fn set_from_columns(&self, val: Vec>) -> pyo3::PyResult<()> { - let mut lock = self.0.lock(); - - if val.is_empty() { - return Err(pyo3::exceptions::PyValueError::new_err( - "from_columns is empty", - )); - } - if val.len() != lock.to_columns.len() { - return Err(pyo3::exceptions::PyValueError::new_err(format!( - "from_columns and to_columns must have same length ({} != {})", - val.len(), - lock.to_columns.len() - ))); - } - - let mut from_columns_str = Vec::with_capacity(val.len()); - for col in val.into_iter() { - let col_ref = super::column_ref::PyColumnRef::try_from(&col)?; - - match col_ref.name { - Some(x) => from_columns_str.push(x.to_string()), - None => { - return Err(pyo3::exceptions::PyValueError::new_err( - "ForeignKey cannot accept asterisk '*' as column", - )) - } - } - } - - lock.from_columns = from_columns_str; - Ok(()) - } - - /// Referencing columns. - #[getter] - fn to_columns(&self) -> Vec { - self.0.lock().to_columns.clone() - } - - #[setter] - fn set_to_columns(&self, val: Vec>) -> pyo3::PyResult<()> { - let mut lock = self.0.lock(); - - if val.is_empty() { - return Err(pyo3::exceptions::PyValueError::new_err( - "to_columns is empty", - )); - } - if val.len() != lock.from_columns.len() { - return Err(pyo3::exceptions::PyValueError::new_err(format!( - "to_columns and to_columns must have same length ({} != {})", - val.len(), - lock.to_columns.len() - ))); - } - - let mut to_columns_str = Vec::with_capacity(val.len()); - for col in val.into_iter() { - let col_ref = super::column_ref::PyColumnRef::try_from(&col)?; - - match col_ref.name { - Some(x) => to_columns_str.push(x.to_string()), - None => { - return Err(pyo3::exceptions::PyValueError::new_err( - "ForeignKey cannot accept asterisk '*' as column", - )) - } - } - } - - lock.to_columns = to_columns_str; - Ok(()) - } - - /// ON DELETE action. - #[getter] - fn on_delete(&self) -> Option { - self.0.lock().on_delete.map(map_foreign_key_action_to_str) - } - - #[setter] - fn set_on_delete(&self, value: Option) -> pyo3::PyResult<()> { - let mut lock = self.0.lock(); - lock.on_delete = match value { - None => None, - Some(x) => Some(map_str_to_foreign_key_action(x)?), - }; - Ok(()) - } - - /// ON UPDATE action. - #[getter] - fn on_update(&self) -> Option { - self.0.lock().on_update.map(map_foreign_key_action_to_str) - } - - #[setter] - fn set_on_update(&self, value: Option) -> pyo3::PyResult<()> { - let mut lock = self.0.lock(); - lock.on_update = match value { - None => None, - Some(x) => Some(map_str_to_foreign_key_action(x)?), - }; - Ok(()) - } - - fn __copy__(&self) -> Self { - let lock = self.0.lock(); - lock.clone().into() - } - - pub fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - ReprFormatter::new_with_pyref(&slf) - .optional_quote("name", lock.name.as_ref()) - .map("to_table", &lock.to_table, |x| x.__repr__()) - .debug("to_columns", &lock.to_columns) - .debug("from_columns", &lock.from_columns) - .optional_map("on_delete", lock.on_delete, map_foreign_key_action_to_str) - .optional_map("on_update", lock.on_update, map_foreign_key_action_to_str) - .finish() - } -} diff --git a/src/common/mod.rs b/src/common/mod.rs deleted file mode 100644 index 0789a7b..0000000 --- a/src/common/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub mod column; -pub mod column_ref; -pub mod expression; -pub mod foreign_key; -pub mod table_ref; -pub mod value; - -#[pyo3::pymodule(name = "common")] -pub mod common_module { - #[pymodule_export] - use super::value::PyValue; - - #[pymodule_export] - use super::column_ref::PyColumnRef; - - #[pymodule_export] - use super::table_ref::PyTableName; - - #[pymodule_export] - use super::expression::all; - #[pymodule_export] - use super::expression::any; - #[pymodule_export] - use super::expression::not_; - #[pymodule_export] - use super::expression::PyExpr; - #[pymodule_export] - use super::expression::PyFunc; - - #[pymodule_export] - use super::column::PyColumn; - - #[pymodule_export] - use super::foreign_key::PyForeignKey; -} diff --git a/src/common/table_ref.rs b/src/common/table_ref.rs deleted file mode 100644 index ec0ee9f..0000000 --- a/src/common/table_ref.rs +++ /dev/null @@ -1,319 +0,0 @@ -use pyo3::types::PyAnyMethods; -use sea_query::IntoIden; -use std::str::FromStr; - -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundKwargs, RefBoundObject}; - -crate::implement_pyclass! { - // NOTE: SQLTypes, PyExpr, PyFunc, PyTableName & PyColumnRef could never mark as subclass. - // these should be immutable and final types. - - /// Represents a table name reference with optional schema, database, and alias. - /// - /// This class encapsulates a table name that can include: - /// - The base table name - /// - Optional schema/namespace qualification - /// - Optional database qualification (for systems that support it) - /// - /// The class provides parsing capabilities for string representations - /// and supports comparison operations. - /// - /// NOTE: this class is immutable and frozen. - #[derive(Debug, Clone, PartialEq)] - [] PyTableName as "TableName" { - pub name: sea_query::DynIden, - pub schema: Option, - pub database: Option, - pub alias: Option, - } -} - -impl sea_query::IntoTableRef for PyTableName { - fn into_table_ref(self) -> sea_query::TableRef { - match (self.schema, self.database, self.alias) { - (Some(schema), Some(database), Some(alias)) => { - sea_query::TableRef::DatabaseSchemaTableAlias(database, schema, self.name, alias) - } - (Some(schema), None, Some(alias)) => { - sea_query::TableRef::SchemaTableAlias(schema, self.name, alias) - } - (Some(schema), Some(database), None) => { - sea_query::TableRef::DatabaseSchemaTable(database, schema, self.name) - } - (Some(schema), None, None) => sea_query::TableRef::SchemaTable(schema, self.name), - (None, None, Some(alias)) => sea_query::TableRef::TableAlias(self.name, alias), - _ => sea_query::TableRef::Table(self.name), - } - } -} - -impl TryFrom> for PyTableName { - type Error = pyo3::PyErr; - - fn try_from(value: RefBoundObject<'_>) -> Result { - unsafe { - if pyo3::ffi::Py_TYPE(value.as_ptr()) == crate::typeref::TABLE_NAME_TYPE { - let casted_value = value.cast_unchecked::(); - return Ok(casted_value.get().clone()); - } - - if let Ok(x) = value.extract::() { - return Self::from_str(&x); - } - - if let Some(result) = Self::try_from_property(value)? { - return Ok(result); - } - - crate::new_error!( - PyTypeError, - "expected TableName or str or object.__table_name__ property, got {}", - crate::internal::get_type_name(value.py(), value.as_ptr()) - ) - } - } -} - -impl FromStr for PyTableName { - type Err = pyo3::PyErr; - - fn from_str(s: &str) -> Result { - let s = s.trim(); - if s.is_empty() { - return Err(pyo3::PyErr::new::( - "cannot parse an empty string", - )); - } - - // Possible formats: - // name - // schema.name - // database.schema.name - // database.schema.name AS alias - let mut s: Vec = s.split('.').map(String::from).collect(); - - if s.len() > 3 { - return Err(pyo3::PyErr::new::( - "invalid format", - )); - } - - let name = s.pop().unwrap(); - let schema = s.pop().map(|x| sea_query::Alias::new(x).into_iden()); - let database = s.pop().map(|x| sea_query::Alias::new(x).into_iden()); - - let (name, alias) = match name.to_ascii_lowercase().find("as") { - None => (name, None), - Some(position) => ( - name[..position].trim().to_string(), - Some(name[position + 2..].trim().to_string()), - ), - }; - - Ok(Self { - name: sea_query::Alias::new(name).into_iden(), - schema, - database, - alias: alias.map(|x| sea_query::Alias::new(x).into_iden()), - }) - } -} - -impl PyTableName { - #[inline] - pub fn try_from_property(value: RefBoundObject) -> pyo3::PyResult> { - const PROPERTY_NAME: &std::ffi::CStr = c"__table_name__"; - - let property = match value.getattr(PROPERTY_NAME) { - Ok(x) => Ok(Some(x)), - Err(err) if err.is_instance_of::(value.py()) => { - Ok(None) - } - Err(err) => Err(err), - }; - let property = property?; - - if property.is_none() { - return Ok(None); - } - - unsafe { - let property = property.unwrap_unchecked(); - - if pyo3::ffi::Py_TYPE(property.as_ptr()) == crate::typeref::TABLE_NAME_TYPE { - let casted_property = property.cast_unchecked::(); - return Ok(Some(casted_property.get().clone())); - } - - if let Ok(x) = property.extract::() { - return Self::from_str(&x).map(Some); - } - - crate::new_error!( - PyTypeError, - "__table_name__ property returns something other than TableName or str; returns {}", - crate::internal::get_type_name(property.py(), property.as_ptr()) - ) - } - } -} - -#[pyo3::pymethods] -impl PyTableName { - #[new] - #[pyo3(signature=(name, schema=None, database=None, alias=None))] - fn new( - name: String, - schema: Option, - database: Option, - alias: Option, - ) -> Self { - Self { - name: sea_query::Alias::new(name).into_iden(), - schema: schema.map(|x| sea_query::Alias::new(x).into_iden()), - database: database.map(|x| sea_query::Alias::new(x).into_iden()), - alias: alias.map(|x| sea_query::Alias::new(x).into_iden()), - } - } - - /// Parse a string representation of a table name. - /// - /// Supports formats like: - /// - "table_name" - /// - "schema.table_name" - /// - "database.schema.table_name" - /// - "database.schema.table_name as alias" - #[classmethod] - fn parse(_cls: &pyo3::Bound<'_, pyo3::types::PyType>, string: String) -> pyo3::PyResult { - Self::from_str(&string) - } - - #[getter] - fn name(&self) -> String { - self.name.to_string() - } - - #[getter] - fn schema(&self) -> Option { - self.schema.as_ref().map(|x| x.to_string()) - } - - #[getter] - fn database(&self) -> Option { - self.database.as_ref().map(|x| x.to_string()) - } - - #[getter] - fn alias(&self) -> Option { - self.alias.as_ref().map(|x| x.to_string()) - } - - /// Create a shallow copy of this TableName. - #[pyo3(signature=(**kwds))] - fn copy_with(&self, kwds: Option>) -> pyo3::PyResult { - use pyo3::types::PyDictMethods; - - let mut cloned = self.clone(); - if kwds.is_none() { - return Ok(cloned); - } - - let kwds = unsafe { kwds.unwrap_unchecked() }; - - for (key, val) in kwds.iter() { - #[cfg(debug_assertions)] - let key = key.extract::().unwrap(); - - #[cfg(not(debug_assertions))] - let key = unsafe { key.extract::().unwrap_unchecked() }; - - // All of values are Option - let val = unsafe { - if pyo3::ffi::Py_IsNone(val.as_ptr()) == 1 { - None - } else if pyo3::ffi::PyUnicode_CheckExact(val.as_ptr()) == 1 { - Some(val.extract::().unwrap_unchecked()) - } else { - return crate::new_error!( - PyTypeError, - "expected str or None, got {}", - crate::internal::get_type_name(val.py(), val.as_ptr()) - ); - } - }; - - if key == "name" { - if let Some(x) = val { - // Ignore name=None - cloned.name = sea_query::Alias::new(x).into_iden(); - } - } else if key == "database" { - cloned.database = val.map(|x| sea_query::Alias::new(x).into_iden()); - } else if key == "schema" { - cloned.schema = val.map(|x| sea_query::Alias::new(x).into_iden()); - } else if key == "alias" { - cloned.alias = val.map(|x| sea_query::Alias::new(x).into_iden()); - } else { - return crate::new_error!( - PyTypeError, - "got an unexpected keyword argument '{}'", - key - ); - } - } - - Ok(cloned) - } - - fn __eq__(slf: pyo3::PyRef<'_, Self>, other: &pyo3::Bound<'_, Self>) -> pyo3::PyResult { - if slf.as_ptr() == other.as_ptr() { - return Ok(true); - } - - let other = other.get(); - Ok(slf.eq(other)) - } - - fn __ne__(slf: pyo3::PyRef<'_, Self>, other: &pyo3::Bound<'_, Self>) -> pyo3::PyResult { - if slf.as_ptr() == other.as_ptr() { - return Ok(false); - } - - let other = other.get(); - Ok(slf.ne(other)) - } - - fn __copy__(&self) -> Self { - self.clone() - } - - fn __hash__(&self) -> u64 { - use std::hash::{Hash, Hasher}; - - let mut state = std::hash::DefaultHasher::new(); - - self.name.to_string().hash(&mut state); - - if let Some(x) = &self.database { - x.to_string().hash(&mut state); - } - if let Some(x) = &self.schema { - x.to_string().hash(&mut state); - } - if let Some(x) = &self.alias { - x.to_string().hash(&mut state); - } - - state.finish() - } - - pub fn __repr__(&self) -> String { - ReprFormatter::new("TableName") - .iden("name", &self.name) - .optional_iden("schema", self.schema.as_ref()) - .optional_iden("database", self.database.as_ref()) - .optional_iden("alias", self.alias.as_ref()) - .finish() - } -} diff --git a/src/common/value.rs b/src/common/value.rs deleted file mode 100644 index 450f38c..0000000 --- a/src/common/value.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::internal::repr::ReprFormatter; -use crate::internal::type_engine::TypeEngine; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, PyObject}; -use crate::sqltypes::SQLTypeTrait; - -crate::implement_pyclass! { - /// Bridges Python types, Rust types, and SQL types for seamless data conversion. - /// - /// This class handles validation, adaptation, and conversion between different - /// type systems used in the application stack. - /// - /// It can automatically detects the type of your value and selects appropriate Rust and SQL types. - /// For example: - /// - Python `int` becomes `BIGINT` SQL type (`BigIntegerType`) - /// - Python `dict` or `list` becomes `JSON` SQL type (`JsonType`) - /// - Python `float` becomes `DOUBLE` SQL type (`DoubleType`) - /// - /// However, for more accurate type selection, it's recommended to use the `sql_type` parameter. - /// - /// NOTE: this class is immutable and frozen. - mutable [subclass] PyValue(ValueState) as "Value" { - sql_type: TypeEngine, - serialized: Option, - deserialized: Option, - } -} - -impl ValueState { - #[inline(always)] - pub unsafe fn new_unchecked( - sql_type: TypeEngine, - serialized: Option, - deserialized: Option, - ) -> Self { - Self { - sql_type, - serialized, - deserialized, - } - } - - pub fn from_pyobject(sql_type: TypeEngine, object: BoundObject<'_>) -> pyo3::PyResult { - unsafe { - if pyo3::ffi::Py_IsNone(object.as_ptr()) == 0 { - sql_type.validate(object.py(), object.as_ptr())?; - } else { - // There's no need to do validation. - // The `ValueState` will handle `NoneType`s itself - } - - Ok(Self::new_unchecked(sql_type, None, Some(object.unbind()))) - } - } - - pub fn from_sea_query_value(py: pyo3::Python, value: sea_query::Value) -> Self { - let sql_type = TypeEngine::infer_value(py, &value); - - unsafe { Self::new_unchecked(sql_type, Some(value), None) } - } - - pub fn simple_expr(&mut self, py: pyo3::Python) -> pyo3::PyResult { - if let Some(x) = &self.serialized { - return Ok(sea_query::SimpleExpr::Value(x.clone())); - } - - let result = unsafe { - let deserialized = self.deserialized.as_ref().unwrap_unchecked(); - - if pyo3::ffi::Py_IsNone(deserialized.as_ptr()) == 1 { - sea_query::Value::Bool(None) - } else { - self.sql_type.serialize(py, deserialized.as_ptr())? - } - }; - - self.serialized = Some(result.clone()); - Ok(sea_query::SimpleExpr::Value(result)) - } - - pub fn clone_ref(&self, py: pyo3::Python) -> Self { - Self { - sql_type: self.sql_type.clone(), - serialized: self.serialized.clone(), - deserialized: self.deserialized.as_ref().map(|x| x.clone_ref(py)), - } - } -} - -#[pyo3::pymethods] -impl PyValue { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> Self { - Self::uninit() - } - - #[pyo3(signature=(value, sql_type=None))] - fn __init__( - &self, - value: BoundObject<'_>, - sql_type: Option>, - ) -> pyo3::PyResult<()> { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(value.as_ptr(), crate::typeref::VALUE_TYPE) == 1 { - let py = value.py(); - - let casted_value = value.cast_into_unchecked::(); - let state = casted_value.get().0.lock().clone_ref(py); - - self.0.set(state); - return Ok(()); - } - } - - let type_engine = { - if let Some(sql_type) = sql_type { - TypeEngine::new(&sql_type)? - } else { - TypeEngine::infer_pyobject(&value)? - } - }; - - let result = ValueState::from_pyobject(type_engine, value)?; - self.0.set(result); - Ok(()) - } - - #[getter] - fn sql_type<'a>(&self, py: pyo3::Python<'a>) -> BoundObject<'a> { - let lock = self.0.lock(); - lock.sql_type.as_pyobject(py) - } - - /// Converts the adapted value back to a Python type. - #[getter] - fn value<'py>(&self, py: pyo3::Python<'py>) -> pyo3::PyResult> { - let mut lock = self.0.lock(); - - if let Some(x) = &lock.deserialized { - return Ok(x.bind(py).clone()); - } - - let deserialized = unsafe { - lock.sql_type - .deserialize(py, lock.serialized.as_ref().unwrap()) - .map(|x| pyo3::Bound::from_owned_ptr(py, x))? - }; - lock.deserialized = Some(deserialized.clone().unbind()); - - Ok(deserialized) - } - - fn __hash__(&self, py: pyo3::Python) -> pyo3::PyResult { - self.value(py) - .map(|x| unsafe { pyo3::ffi::PyObject_Hash(x.as_ptr()) }) - } - - /// Shorthand for `Expr(self)` - fn to_expr(&self, py: pyo3::Python) -> pyo3::PyResult { - let mut lock = self.0.lock(); - lock.simple_expr(py).map(super::expression::PyExpr) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult { - let value = slf.value(slf.py())?; - - Ok(ReprFormatter::new_with_pyref(&slf) - .debug("", &value) - .finish()) - } -} diff --git a/src/internal/macro_rules.rs b/src/internal/macro_rules.rs deleted file mode 100644 index b9457bc..0000000 --- a/src/internal/macro_rules.rs +++ /dev/null @@ -1,203 +0,0 @@ -/// Implement python classes using `#[pyo3::pyclass]` macro. -/// -/// Usage: -/// ```rust,no-run -/// implement_pyclass! { -/// [generic] PyMyClass as "MyClass" { field: String } -/// } -/// ``` -#[macro_export] -macro_rules! implement_pyclass { - ( - $(#[$outer:meta])* - [$($pyclass_args:tt)*] $struct_name:ident as $python_name:literal $($rest:tt)* - ) => { - #[ - pyo3::pyclass( - module = "rapidquery._lib", - name = $python_name, - frozen, - immutable_type, - skip_from_py_object, - $($pyclass_args)* - ) - ] - $(#[$outer])* - pub struct $struct_name $($rest)* - }; - - ( - $(#[$outer:meta])* - immutable [$($pyclass_args:tt)*] $struct_name:ident($state_name:ident) as $python_name:literal $($rest:tt)* - ) => { - $(#[$outer])* - pub struct $state_name $($rest)* - - $crate::implement_pyclass! { - $(#[$outer])* - [$($pyclass_args)*] $struct_name as $python_name (pub $crate::internal::uninitialized::ImmutableUninit<$state_name>); - } - - impl $struct_name { - #[inline] - #[must_use] - pub fn uninit() -> Self { - Self( - $crate::internal::uninitialized::ImmutableUninit::uninit() - ) - } - } - - impl From<$state_name> for $struct_name { - fn from(value: $state_name) -> Self { - Self( - $crate::internal::uninitialized::ImmutableUninit::new(value) - ) - } - } - - impl AsRef<$state_name> for $struct_name { - fn as_ref(&self) -> &$state_name { - self.0.as_ref() - } - } - }; - - ( - $(#[$outer:meta])* - mutable [$($pyclass_args:tt)*] $struct_name:ident($state_name:ident) as $python_name:literal $($rest:tt)* - ) => { - $(#[$outer])* - pub struct $state_name $($rest)* - - $crate::implement_pyclass! { - $(#[$outer])* - [$($pyclass_args)*] $struct_name as $python_name (pub $crate::internal::uninitialized::MutableUninit<$state_name>); - } - - impl $struct_name { - #[inline] - #[must_use] - pub fn uninit() -> Self { - Self( - $crate::internal::uninitialized::MutableUninit::uninit() - ) - } - } - - impl From<$state_name> for $struct_name { - fn from(value: $state_name) -> Self { - Self( - $crate::internal::uninitialized::MutableUninit::new(value) - ) - } - } - }; -} - -/// Creates new [`PyErr`] -/// -/// Usage: -/// ```rust,no-run -/// new_raw_error!(PyValueError, "Message") -/// new_raw_error!(PyValueError, "Message {}", arg1) -/// ``` -#[macro_export] -macro_rules! new_py_error { - ($name:ident, $message:expr) => { - ::pyo3::exceptions::$name::new_err($message) - }; - ($name:ident, $message:expr, $($args:tt)*) => { - ::pyo3::exceptions::$name::new_err( - format!($message, $($args)*) - ) - }; -} - -/// Creates new [`Err(PyErr)`] -/// -/// Usage: -/// ```rust,no-run -/// new_error!(PyValueError, "Message") -/// new_error!(PyValueError, "Message {}", arg1) -/// ``` -#[macro_export] -macro_rules! new_error { - ($name:ident, $message:expr) => { - Err($crate::new_py_error!($name, $message)) - }; - ($name:ident, $message:expr, $($args:tt)*) => { - Err($crate::new_py_error!($name, $message, $($args)*)) - }; -} - -#[macro_export] -macro_rules! build_schema_statement { - ($backend:expr, $stmt:expr) => {{ - let builder = $crate::internal::get_schema_builder($backend)?; - let assert_unwind = std::panic::AssertUnwindSafe(|| $stmt.build_any(&*builder)); - - std::panic::catch_unwind(assert_unwind) - .map_err(|_| pyo3::PyErr::new::("build failed")) - }}; -} - -#[macro_export] -macro_rules! build_query_statement { - ($backend:expr, $stmt:expr) => {{ - let builder = $crate::internal::get_query_builder($backend)?; - let mut sql = String::with_capacity(255); - - let assert_unwind = - std::panic::AssertUnwindSafe(|| $stmt.build_collect_any_into(&*builder, &mut sql)); - - std::panic::catch_unwind(assert_unwind) - .map_err(|_| pyo3::PyErr::new::("build failed"))?; - - Ok(sql) - }}; -} - -#[macro_export] -macro_rules! build_query_parts { - ($py:expr, $backend:expr, $stmt:expr) => {{ - let builder = $crate::internal::get_query_builder($backend)?; - - let (placeholder, numbered) = builder.placeholder(); - let mut sql = sea_query::SqlWriterValues::new(placeholder, numbered); - - let assert_unwind = - std::panic::AssertUnwindSafe(|| $stmt.build_collect_any_into(&*builder, &mut sql)); - - std::panic::catch_unwind(assert_unwind) - .map_err(|_| pyo3::PyErr::new::("build failed"))?; - - let (sql, values) = sql.into_parts(); - - let values = { - values - .into_iter() - .map(|x| $crate::common::value::ValueState::from_sea_query_value($py, x)) - .map($crate::common::value::PyValue::from) - }; - - unsafe { - let tuple_ptr = pyo3::ffi::PyTuple_New(values.len() as isize); - if tuple_ptr.is_null() { - return Err(pyo3::PyErr::fetch($py)); - } - - for (index, key) in values.enumerate() { - let key = pyo3::Py::new($py, key).unwrap().into_ptr(); - - if pyo3::ffi::PyTuple_SetItem(tuple_ptr, index as isize, key) == -1 { - pyo3::ffi::Py_XDECREF(tuple_ptr); - pyo3::ffi::Py_XDECREF(key); - return Err(pyo3::PyErr::fetch($py)); - } - } - - Ok((sql, pyo3::Bound::from_owned_ptr($py, tuple_ptr))) - } - }}; -} diff --git a/src/internal/mod.rs b/src/internal/mod.rs deleted file mode 100644 index 08a17f4..0000000 --- a/src/internal/mod.rs +++ /dev/null @@ -1,76 +0,0 @@ -pub mod macro_rules; -pub mod parameters; -pub mod repr; -pub mod type_engine; -pub mod uninitialized; - -/// Returns the type name of a [`pyo3::ffi::PyObject`]. -/// -/// Returns `""` on failure. -#[inline] -pub fn get_type_name<'a>(py: pyo3::Python<'a>, obj: *mut pyo3::ffi::PyObject) -> String { - use pyo3::types::{PyStringMethods, PyTypeMethods}; - - unsafe { - let type_ = pyo3::ffi::Py_TYPE(obj); - - if type_.is_null() { - String::from("") - } else { - let obj = pyo3::types::PyType::from_borrowed_type_ptr(py, type_); - - obj.fully_qualified_name() - .map(|x| x.to_string_lossy().into_owned()) - .unwrap_or_else(|_| String::from("")) - } - } -} - -pub type PyObject = pyo3::Py; -pub type BoundArgs<'a> = &'a pyo3::Bound<'a, pyo3::types::PyTuple>; -pub type BoundKwargs<'a> = &'a pyo3::Bound<'a, pyo3::types::PyDict>; -pub type BoundObject<'a> = pyo3::Bound<'a, pyo3::PyAny>; -pub type RefBoundObject<'a> = &'a pyo3::Bound<'a, pyo3::PyAny>; - -pub trait ToSeaQuery { - /// Convert to sea_query structures. - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> Output; -} - -#[inline(always)] -pub fn get_schema_builder( - name: impl AsRef, -) -> pyo3::PyResult> { - let name = name.as_ref(); - - if name == "sqlite" { - Ok(Box::new(sea_query::SqliteQueryBuilder)) - } else if name == "mysql" { - Ok(Box::new(sea_query::MysqlQueryBuilder)) - } else if name == "postgresql" || name == "postgres" { - Ok(Box::new(sea_query::PostgresQueryBuilder)) - } else { - Err(pyo3::exceptions::PyValueError::new_err(format!( - "invalid backend value, got {name}" - ))) - } -} - -#[inline(always)] -pub fn get_query_builder( - name: impl AsRef, -) -> pyo3::PyResult> { - let name = name.as_ref(); - - if name == "sqlite" { - Ok(Box::new(sea_query::SqliteQueryBuilder)) - } else if name == "mysql" { - Ok(Box::new(sea_query::MysqlQueryBuilder)) - } else if name == "postgresql" || name == "postgres" { - Ok(Box::new(sea_query::PostgresQueryBuilder)) - } else { - Err(pyo3::exceptions::PyValueError::new_err(format!( - "invalid backend value, got {name}" - ))) - } -} diff --git a/src/internal/parameters.rs b/src/internal/parameters.rs deleted file mode 100644 index 4d9bfc4..0000000 --- a/src/internal/parameters.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub enum OptionalParam<'a> { - Undefined, - Defined(pyo3::Bound<'a, pyo3::PyAny>), -} - -impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for OptionalParam<'py> { - type Error = pyo3::PyErr; - - fn extract(obj: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> Result { - Ok(Self::Defined(obj.to_owned())) - } -} diff --git a/src/internal/repr.rs b/src/internal/repr.rs deleted file mode 100644 index 892d029..0000000 --- a/src/internal/repr.rs +++ /dev/null @@ -1,309 +0,0 @@ -/// A helper to generate __repr__ methods. -#[derive(Default, Clone, Debug)] -pub struct ReprFormatter { - buf: String, - started: bool, -} - -#[derive(Debug)] -pub struct ReprVec { - key: &'static str, - optional: bool, - items: String, -} - -#[derive(Debug)] -pub struct ReprNestedVec { - index: usize, - items: String, -} - -impl ReprFormatter { - #[inline] - pub fn new(name: impl AsRef) -> Self { - Self { - buf: String::from("<") + name.as_ref(), - started: false, - } - } - - #[inline] - pub fn new_with_pyref(pyref: &pyo3::PyRef<'_, T>) -> Self { - Self::new(crate::internal::get_type_name(pyref.py(), pyref.as_ptr())) - } - - #[inline] - pub fn vec(&self, key: &'static str, optional: bool) -> ReprVec { - ReprVec { - key, - optional, - items: String::new(), - } - } - - pub fn take(&mut self) -> Self { - std::mem::take(self) - } - - pub fn finish(&mut self) -> String { - self.take().buf + ">" - } - - #[inline] - pub fn write(&mut self, part: impl AsRef) -> &mut Self { - if self.started { - self.buf.push(' '); - self.buf.push_str(part.as_ref()); - } else { - self.buf.push(' '); - self.buf.push_str(part.as_ref()); - self.started = true; - } - - self - } - - #[inline] - pub fn pair(&mut self, key: &'static str, value: impl AsRef) -> &mut Self { - if key.is_empty() { - self.write(value) - } else { - self.write(format!("{}={}", key, value.as_ref())) - } - } - - #[inline] - pub fn quote(&mut self, key: &'static str, value: impl AsRef) -> &mut Self { - if key.is_empty() { - self.write(format!("'{}'", value.as_ref())) - } else { - self.write(format!("{}='{}'", key, value.as_ref())) - } - } - - #[inline] - pub fn iden(&mut self, key: &'static str, value: &sea_query::DynIden) -> &mut Self { - self.quote(key, value.to_string()) - } - - #[inline] - pub fn debug(&mut self, key: &'static str, value: impl std::fmt::Debug) -> &mut Self { - self.pair(key, format!("{value:?}")) - } - - #[inline] - pub fn display(&mut self, key: &'static str, value: impl std::fmt::Display) -> &mut Self { - self.pair(key, value.to_string()) - } - - #[inline] - pub fn map(&mut self, key: &'static str, value: T, func: F) -> &mut Self - where - R: AsRef, - F: FnOnce(T) -> R, - { - self.pair(key, func(value)) - } - - #[inline] - pub fn optional_pair( - &mut self, - key: &'static str, - value: Option>, - ) -> &mut Self { - match value { - Some(x) => self.pair(key, x), - None => self, - } - } - - #[inline] - pub fn optional_quote( - &mut self, - key: &'static str, - value: Option>, - ) -> &mut Self { - match value { - Some(x) => self.quote(key, x), - None => self, - } - } - - #[inline] - pub fn optional_iden( - &mut self, - key: &'static str, - value: Option<&sea_query::DynIden>, - ) -> &mut Self { - match value { - Some(x) => self.iden(key, x), - None => self, - } - } - - #[inline] - pub fn optional_debug( - &mut self, - key: &'static str, - value: Option, - ) -> &mut Self { - match value { - Some(x) => self.debug(key, x), - None => self, - } - } - - #[inline] - pub fn optional_display( - &mut self, - key: &'static str, - value: Option, - ) -> &mut Self { - match value { - Some(x) => self.display(key, x), - None => self, - } - } - - #[inline] - pub fn optional_boolean(&mut self, key: &'static str, value: bool) -> &mut Self { - if value { - self.pair(key, "true") - } else { - self - } - } - - #[inline] - pub fn optional_map( - &mut self, - key: &'static str, - value: Option, - func: F, - ) -> &mut Self - where - R: AsRef, - F: FnOnce(T) -> R, - { - match value { - Some(x) => self.map(key, x, func), - None => self, - } - } -} - -impl ReprVec { - #[inline] - pub fn vec(&self, index: usize) -> ReprNestedVec { - ReprNestedVec { - index, - items: String::new(), - } - } - - #[inline] - pub fn push(&mut self, index: usize, item: impl AsRef) -> &mut Self { - if index > 0 { - self.items.push_str(", "); - } - self.items.push_str(item.as_ref()); - self - } - - #[inline] - pub fn quote(&mut self, index: usize, item: impl AsRef) -> &mut Self { - if index > 0 { - self.items.push_str(", "); - } - self.items.push('\''); - self.items.push_str(item.as_ref()); - self.items.push('\''); - self - } - - #[inline] - pub fn quote_iter(&mut self, iterator: I) -> &mut Self - where - D: AsRef, - I: Iterator, - { - for (index, item) in iterator.enumerate() { - self.quote(index, item); - } - self - } - - #[inline] - pub fn display_iter(&mut self, iterator: I) -> &mut Self - where - D: std::fmt::Display, - I: Iterator, - { - for (index, item) in iterator.enumerate() { - self.push(index, item.to_string()); - } - self - } - - pub fn finish(&mut self, fmt: &mut ReprFormatter) { - if self.optional && self.items.is_empty() { - return; - } - - self.items.insert(0, '['); - self.items.push(']'); - fmt.pair(self.key, std::mem::take(&mut self.items)); - } -} - -impl ReprNestedVec { - #[inline] - pub fn push(&mut self, index: usize, item: impl AsRef) -> &mut Self { - if index > 0 { - self.items.push_str(", "); - } - self.items.push_str(item.as_ref()); - self - } - - #[inline] - pub fn quote(&mut self, index: usize, item: impl AsRef) -> &mut Self { - if index > 0 { - self.items.push_str(", "); - } - self.items.push('\''); - self.items.push_str(item.as_ref()); - self.items.push('\''); - self - } - - #[inline] - pub fn quote_iter(&mut self, iterator: I) -> &mut Self - where - D: AsRef, - I: Iterator, - { - for (index, item) in iterator.enumerate() { - self.quote(index, item); - } - self - } - - #[inline] - pub fn display_iter(&mut self, iterator: I) -> &mut Self - where - D: std::fmt::Display, - I: Iterator, - { - for (index, item) in iterator.enumerate() { - self.push(index, item.to_string()); - } - self - } - - pub fn finish(&mut self, fmtvec: &mut ReprVec) { - self.items.insert(0, '['); - self.items.push(']'); - fmtvec.push(self.index, std::mem::take(&mut self.items)); - } -} diff --git a/src/internal/type_engine.rs b/src/internal/type_engine.rs deleted file mode 100644 index 039eab2..0000000 --- a/src/internal/type_engine.rs +++ /dev/null @@ -1,436 +0,0 @@ -use crate::internal::RefBoundObject; -use crate::sqltypes::SQLTypeTrait; - -/// Type engine is an enum which can control validations, serializations, -/// and deserializations of column types. -/// -/// [`SQLTypeTrait`] is implemented for this type. -pub struct TypeEngine( - pub std::sync::Arc, - pub std::ptr::NonNull, -); - -// SAFETY: PyObjects are safe between threads. -unsafe impl Send for TypeEngine {} -unsafe impl Sync for TypeEngine {} - -impl Clone for TypeEngine { - fn clone(&self) -> Self { - unsafe { - pyo3::ffi::Py_INCREF(self.1.as_ptr()); - } - - Self(self.0.clone(), self.1) - } -} - -impl Drop for TypeEngine { - fn drop(&mut self) { - unsafe { - pyo3::ffi::Py_DECREF(self.1.as_ptr()); - } - } -} - -macro_rules! wrap_typeengine { - ($result:expr, $ptr:expr) => { - unsafe { - pyo3::ffi::Py_INCREF($ptr); - Self( - std::sync::Arc::new($result), - std::ptr::NonNull::new_unchecked($ptr), - ) - } - }; - - ($py:expr, $result:expr, $initializer:expr) => { - unsafe { - let object_ptr = pyo3::Py::new($py, ($initializer, crate::sqltypes::PySQLTypeAbstract)) - .unwrap() - .into_ptr(); - - Self( - std::sync::Arc::new($result), - std::ptr::NonNull::new_unchecked(object_ptr), - ) - } - }; -} - -macro_rules! check_native_column_types { - ( - $object:expr, - $type_ptr:expr, - $($type:ty => $constant:ident,)* - ) => { - $( - if $type_ptr == crate::typeref::$constant { - let val = $object.cast_unchecked::<$type>(); - return Ok(wrap_typeengine!(val.get().clone(), $object.as_ptr())); - } - )* - }; -} - -macro_rules! infer_rules { - ( - $py:expr, - $object_ptr:expr, - $type_ptr:expr, - $(($cond:expr) => $result:expr,)* - ) => { - $( - if $cond { - return Ok(wrap_typeengine!($py, $result, $result)); - } - )* - }; -} - -impl TypeEngine { - /// Creates a new [`TypeEngine`]. - pub fn new(object: RefBoundObject<'_>) -> pyo3::PyResult { - unsafe { - let typ = pyo3::ffi::Py_TYPE(object.as_ptr()); - - check_native_column_types!( - object, - typ, - crate::sqltypes::PyBlobType => BLOB_COLUMN_TYPE, - crate::sqltypes::PyBinaryType => BINARY_COLUMN_TYPE, - crate::sqltypes::PyVarBinaryType => VAR_BINARY_COLUMN_TYPE, - crate::sqltypes::PyBitType => BIT_COLUMN_TYPE, - crate::sqltypes::PyVarBitType => VAR_BIT_COLUMN_TYPE, - crate::sqltypes::PyDateTimeType => DATETIME_COLUMN_TYPE, - crate::sqltypes::PyTimestampType => TIMESTAMP_COLUMN_TYPE, - crate::sqltypes::PyTimeType => TIME_COLUMN_TYPE, - crate::sqltypes::PyDateType => DATE_COLUMN_TYPE, - crate::sqltypes::PyJSONType => JSON_COLUMN_TYPE, - crate::sqltypes::PyJSONBinaryType => JSON_BINARY_COLUMN_TYPE, - crate::sqltypes::PyDecimalType => DECIMAL_COLUMN_TYPE, - crate::sqltypes::PyUUIDType => UUID_COLUMN_TYPE, - crate::sqltypes::PyINETType => INET_COLUMN_TYPE, - crate::sqltypes::PyMacAddressType => MAC_ADDRESS_COLUMN_TYPE, - crate::sqltypes::PyBooleanType => BOOLEAN_COLUMN_TYPE, - crate::sqltypes::PyTinyIntegerType => TINY_INTEGER_COLUMN_TYPE, - crate::sqltypes::PySmallIntegerType => SMALL_INTEGER_COLUMN_TYPE, - crate::sqltypes::PyIntegerType => INTEGER_COLUMN_TYPE, - crate::sqltypes::PyBigIntegerType => BIG_INTEGER_COLUMN_TYPE, - crate::sqltypes::PyTinyUnsignedType => TINY_UNSIGNED_COLUMN_TYPE, - crate::sqltypes::PySmallUnsignedType => SMALL_UNSIGNED_COLUMN_TYPE, - crate::sqltypes::PyUnsignedType => UNSIGNED_COLUMN_TYPE, - crate::sqltypes::PyBigUnsignedType => BIG_UNSIGNED_COLUMN_TYPE, - crate::sqltypes::PyFloatType => FLOAT_COLUMN_TYPE, - crate::sqltypes::PyDoubleType => DOUBLE_COLUMN_TYPE, - crate::sqltypes::PyTextType => TEXT_COLUMN_TYPE, - crate::sqltypes::PyCharType => CHAR_COLUMN_TYPE, - crate::sqltypes::PyStringType => STRING_COLUMN_TYPE, - crate::sqltypes::PyVectorType => VECTOR_COLUMN_TYPE, - crate::sqltypes::PyArrayType => ARRAY_COLUMN_TYPE, - crate::sqltypes::PyEnumType => ENUM_COLUMN_TYPE, - ); - - crate::new_error!( - PyTypeError, - "expected SQLTypeAbstract, got {:?}", - crate::internal::get_type_name(object.py(), object.as_ptr()) - ) - } - } - - /// Tries to guess a native column type depends on type of the `object`. - #[cfg_attr(feature = "optimize", optimize(speed))] - pub fn infer_pyobject(object: RefBoundObject<'_>) -> pyo3::PyResult { - unsafe { - let object_ptr = object.as_ptr(); - let object_type_ptr = pyo3::ffi::Py_TYPE(object_ptr); - - if pyo3::ffi::PyLong_CheckExact(object_ptr) == 1 { - return Ok(wrap_typeengine!( - object.py(), - crate::sqltypes::PyBigIntegerType, - crate::sqltypes::PyBigIntegerType - )); - } - - infer_rules!( - object.py(), - object_ptr, - object_type_ptr, - - (pyo3::ffi::Py_IsNone(object_ptr) == 1) => crate::sqltypes::PyBooleanType, - (pyo3::ffi::PyBool_Check(object_ptr) == 1) => crate::sqltypes::PyBooleanType, - (pyo3::ffi::PyFloat_CheckExact(object_ptr) == 1) => crate::sqltypes::PyDoubleType, - (pyo3::ffi::PyUnicode_CheckExact(object_ptr) == 1) => crate::sqltypes::PyTextType, - (pyo3::ffi::PyBytes_CheckExact(object_ptr) == 1) => crate::sqltypes::PyBlobType, - - (object_type_ptr == crate::typeref::STD_DATETIME_TYPE) => crate::sqltypes::PyDateTimeType, - (object_type_ptr == crate::typeref::STD_DATE_TYPE) => crate::sqltypes::PyDateType, - (object_type_ptr == crate::typeref::STD_TIME_TYPE) => crate::sqltypes::PyTimeType, - (object_type_ptr == crate::typeref::STD_DECIMAL_TYPE) => crate::sqltypes::PyDecimalType(None), - (object_type_ptr == crate::typeref::STD_UUID_TYPE) => crate::sqltypes::PyUUIDType, - ); - - if (pyo3::ffi::PyDict_CheckExact(object_ptr) == 1 - || pyo3::ffi::PyList_CheckExact(object_ptr) == 1) - && crate::sqltypes::_validate_json_object(object.py(), object_ptr).is_ok() - { - return Ok(wrap_typeengine!( - object.py(), - crate::sqltypes::PyJSONType, - crate::sqltypes::PyJSONType - )); - } - - crate::new_error!( - PyTypeError, - "Could not infer column/sql type for {:?}", - crate::internal::get_type_name(object.py(), object.as_ptr()) - ) - } - } - - /// Tries to guess a native column type depends on type of the `object`. - #[cfg_attr(feature = "optimize", optimize(speed))] - pub fn infer_value(py: pyo3::Python<'_>, object: &sea_query::Value) -> Self { - match object { - sea_query::Value::Bool(_) => wrap_typeengine!( - py, - crate::sqltypes::PyBooleanType, - crate::sqltypes::PyBooleanType - ), - sea_query::Value::TinyInt(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyTinyIntegerType, - crate::sqltypes::PyTinyIntegerType - ) - } - sea_query::Value::SmallInt(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PySmallIntegerType, - crate::sqltypes::PySmallIntegerType - ) - } - sea_query::Value::Int(_) => wrap_typeengine!( - py, - crate::sqltypes::PyIntegerType, - crate::sqltypes::PyIntegerType - ), - sea_query::Value::BigInt(_) => wrap_typeengine!( - py, - crate::sqltypes::PyBigIntegerType, - crate::sqltypes::PyBigIntegerType - ), - sea_query::Value::TinyUnsigned(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyTinyUnsignedType, - crate::sqltypes::PyTinyUnsignedType - ) - } - sea_query::Value::SmallUnsigned(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PySmallUnsignedType, - crate::sqltypes::PySmallUnsignedType - ) - } - sea_query::Value::Unsigned(_) => wrap_typeengine!( - py, - crate::sqltypes::PyUnsignedType, - crate::sqltypes::PyUnsignedType - ), - sea_query::Value::BigUnsigned(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyBigUnsignedType, - crate::sqltypes::PyBigUnsignedType - ) - } - sea_query::Value::Float(_) => wrap_typeengine!( - py, - crate::sqltypes::PyFloatType, - crate::sqltypes::PyFloatType - ), - sea_query::Value::Double(_) => wrap_typeengine!( - py, - crate::sqltypes::PyDoubleType, - crate::sqltypes::PyDoubleType - ), - sea_query::Value::String(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyStringType(None), - crate::sqltypes::PyStringType(None) - ) - } - sea_query::Value::Char(_) => wrap_typeengine!( - py, - crate::sqltypes::PyCharType(None), - crate::sqltypes::PyCharType(None) - ), - sea_query::Value::Bytes(_) => { - wrap_typeengine!(py, crate::sqltypes::PyBlobType, crate::sqltypes::PyBlobType) - } - sea_query::Value::Json(_) => { - wrap_typeengine!(py, crate::sqltypes::PyJSONType, crate::sqltypes::PyJSONType) - } - sea_query::Value::ChronoDate(_) => { - wrap_typeengine!(py, crate::sqltypes::PyDateType, crate::sqltypes::PyDateType) - } - sea_query::Value::ChronoTime(_) => { - wrap_typeengine!(py, crate::sqltypes::PyTimeType, crate::sqltypes::PyTimeType) - } - sea_query::Value::ChronoDateTime(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyDateTimeType, - crate::sqltypes::PyDateTimeType - ) - } - sea_query::Value::ChronoDateTimeUtc(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyDateTimeType, - crate::sqltypes::PyDateTimeType - ) - } - sea_query::Value::ChronoDateTimeLocal(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyDateTimeType, - crate::sqltypes::PyDateTimeType - ) - } - sea_query::Value::ChronoDateTimeWithTimeZone(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyDateTimeType, - crate::sqltypes::PyDateTimeType - ) - } - sea_query::Value::Uuid(_) => { - wrap_typeengine!(py, crate::sqltypes::PyUUIDType, crate::sqltypes::PyUUIDType) - } - sea_query::Value::Decimal(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyDecimalType(None), - crate::sqltypes::PyDecimalType(None) - ) - } - sea_query::Value::Array(_, Some(nested)) => { - let nested_type_engine = { - if nested.is_empty() { - // Vec is empty so the nested type is not important - wrap_typeengine!( - py, - crate::sqltypes::PyBooleanType, - crate::sqltypes::PyBooleanType - ) - } else { - Self::infer_value(py, &nested[0]) - } - }; - - wrap_typeengine!( - py, - crate::sqltypes::PyArrayType(nested_type_engine), - crate::sqltypes::PyArrayType(nested_type_engine.clone()) - ) - } - sea_query::Value::Array(_, None) => { - // Vec is None so the nested type is not important - let nested_type_engine = wrap_typeengine!( - py, - crate::sqltypes::PyBooleanType, - crate::sqltypes::PyBooleanType - ); - wrap_typeengine!( - py, - crate::sqltypes::PyArrayType(nested_type_engine), - crate::sqltypes::PyArrayType(nested_type_engine.clone()) - ) - } - sea_query::Value::Vector(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyVectorType(None), - crate::sqltypes::PyVectorType(None) - ) - } - sea_query::Value::IpNetwork(_) => { - wrap_typeengine!(py, crate::sqltypes::PyINETType, crate::sqltypes::PyINETType) - } - sea_query::Value::MacAddress(_) => { - wrap_typeengine!( - py, - crate::sqltypes::PyMacAddressType, - crate::sqltypes::PyMacAddressType - ) - } - } - } - - pub fn as_pyobject<'py>(&self, py: pyo3::Python<'py>) -> pyo3::Bound<'py, pyo3::PyAny> { - unsafe { pyo3::Bound::from_borrowed_ptr(py, self.1.as_ptr()) } - } -} - -impl SQLTypeTrait for TypeEngine { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - self.0.to_sea_query_column_type() - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - self.0.validate(py, ptr) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - self.0.deserialize(py, value) - } - - fn to_sql_type_name(&self) -> String { - self.0.to_sql_type_name() - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - self.0.serialize(py, ptr) - } -} - -impl std::fmt::Display for TypeEngine { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - unsafe { - pyo3::Python::attach_unchecked(|py| { - let object = self.as_pyobject(py); - std::fmt::Display::fmt(&object, f) - }) - } - } -} - -impl std::fmt::Debug for TypeEngine { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - unsafe { - pyo3::Python::attach_unchecked(|py| { - let object = self.as_pyobject(py); - std::fmt::Debug::fmt(&object, f) - }) - } - } -} diff --git a/src/internal/uninitialized.rs b/src/internal/uninitialized.rs deleted file mode 100644 index aa796c1..0000000 --- a/src/internal/uninitialized.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! Helpers for creating __init__ methods for types. - -use ::std::cell::UnsafeCell; -use ::std::sync::Arc; - -/// An uninitialized value which can nominally be written to only once. -#[repr(transparent)] -pub struct ImmutableUninit(UnsafeCell>); - -/// An uninitialized Arc-Mutex. -#[repr(transparent)] -pub struct MutableUninit(Arc<::parking_lot::Mutex>>); - -impl ImmutableUninit { - /// Creates a new uninitialized instance. - #[inline] - #[must_use] - pub const fn uninit() -> Self { - Self(UnsafeCell::new(None)) - } - - /// Creates a new initialized instance. - #[inline] - #[must_use] - pub const fn new(val: T) -> Self { - Self(UnsafeCell::new(Some(val))) - } - - pub fn is_initialized(&self) -> bool { - unsafe { - let inner = &*self.0.get(); - inner.is_some() - } - } - - /// Initializes the self by `val`. - /// - /// # Safety - /// - It is Undefined Behavior to call this while any other reference(s) to the wrapped value are alive. - /// - Mutating the wrapped value through other means while the returned reference is alive is Undefined Behavior. - #[inline] - pub unsafe fn set(&self, val: T) { - let _ = (*self.0.get()).replace(val); - } - - /// Gets a reference to the wrapped value. - #[inline] - pub fn get_checked(&self) -> Option<&T> { - unsafe { - let inner = &*self.0.get(); - inner.as_ref() - } - } - - /// Unwraps the value, consuming the [`ImmutableUninit`]. - pub fn into_inner(self) -> Option { - self.0.into_inner() - } -} - -impl AsRef for ImmutableUninit { - /// Gets a reference to the wrapped value. - /// - /// # Panics - /// Panics if you forgot to call [`ImmutableUninit::set()`] - #[inline] - fn as_ref(&self) -> &T { - unsafe { - let inner = &*self.0.get(); - - inner.as_ref().expect( - "Object did not initialized yet. This happens when you forget to call __init__ \ - method. This is a critical issue.", - ) - } - } -} -impl Default for ImmutableUninit { - /// Creates a new uninitialized instance, like [`ImmutableUninit::uninit`] - #[inline] - fn default() -> Self { - Self::uninit() - } -} -impl Clone for ImmutableUninit { - fn clone(&self) -> Self { - let res = Self::uninit(); - - if let Some(x) = self.get_checked() { - unsafe { - res.set(x.clone()); - } - } - - res - } -} -impl ::std::fmt::Debug for ImmutableUninit { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut tuple = f.debug_tuple("ImmutableUninit"); - - match self.get_checked() { - Some(x) => tuple.field(&x).finish(), - None => tuple.field(&"").finish(), - } - } -} -impl From for ImmutableUninit { - fn from(value: T) -> Self { - ImmutableUninit::new(value) - } -} -unsafe impl Send for ImmutableUninit {} -unsafe impl Sync for ImmutableUninit {} - -impl MutableUninit { - /// Creates a new uninitialized instance. - #[inline] - #[must_use] - pub fn uninit() -> Self { - Self(Arc::new(::parking_lot::Mutex::new(None))) - } - - /// Creates a new initialized instance. - #[inline] - #[must_use] - pub fn new(val: T) -> Self { - Self(Arc::new(::parking_lot::Mutex::new(Some(val)))) - } - - pub fn is_initialized(&self) -> bool { - self.0.lock().is_some() - } - - /// Initializes the self by `val`. - #[inline] - pub fn set(&self, val: T) { - let _ = (*self.0.lock()).replace(val); - } - - /// Gets a reference to the wrapped value. - /// - /// # Panics - /// Panics if you forgot to call [`ImmutableUninit::set()`] - pub fn lock(&self) -> parking_lot::MappedMutexGuard<'_, T> { - let inner = self.0.lock(); - assert!( - inner.as_ref().is_some(), - "Object did not initialized yet. This happens when you forget to call __init__ \ - method. This is a critical issue.", - ); - - unsafe { parking_lot::MutexGuard::map(inner, |x| x.as_mut().unwrap_unchecked()) } - } - - /// Unwraps the value, consuming the [`MutableInit`]. - pub fn into_inner(self) -> Arc<::parking_lot::Mutex>> { - self.0 - } -} - -impl Default for MutableUninit { - /// Creates a new uninitialized instance, like [`MutableUninit::uninit`] - #[inline] - fn default() -> Self { - Self::uninit() - } -} -impl Clone for MutableUninit { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} -impl ::std::fmt::Debug for MutableUninit { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut tuple = f.debug_tuple("MutableUninit"); - - match &*self.0.lock() { - Some(x) => tuple.field(&x).finish(), - None => tuple.field(&"").finish(), - } - } -} -impl From for MutableUninit { - #[inline] - fn from(value: T) -> Self { - Self::new(value) - } -} diff --git a/src/lib.rs b/src/lib.rs index 84f0465..ea027cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,42 +5,13 @@ #![warn(clippy::print_stdout)] #![warn(clippy::print_stderr)] #![warn(clippy::dbg_macro)] -#![feature(optimize_attribute)] -#![feature(once_cell_try)] - -pub mod internal; -mod typeref; - -mod common; -mod mysql; -mod postgres; -mod query; -mod schema; -mod sqlite; -mod sqltypes; /// RapidQuery core which is written in Rust. #[pyo3::pymodule(gil_used = false)] mod _lib { - #[pymodule_export] - use super::common::common_module; - #[pymodule_export] - use super::mysql::mysql_module; - #[pymodule_export] - use super::postgres::postgres_module; - #[pymodule_export] - use super::query::query_module; - #[pymodule_export] - use super::schema::schema_module; - #[pymodule_export] - use super::sqlite::sqlite_module; - #[pymodule_export] - use super::sqltypes::sqltypes_module; - #[pymodule_init] #[cold] - fn init(m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { - crate::typeref::initialize_typeref(m.py()); + fn init(_m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { Ok(()) } } diff --git a/src/mysql.rs b/src/mysql.rs deleted file mode 100644 index 2a2e0fc..0000000 --- a/src/mysql.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// MySQL-Only functions -/// -/// **Comming Soon ...** -#[pyo3::pymodule(name = "mysql")] -pub mod mysql_module {} diff --git a/src/postgres.rs b/src/postgres.rs deleted file mode 100644 index 16e8dea..0000000 --- a/src/postgres.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// Postgres-Only functions -/// -/// **Comming Soon ...** -#[pyo3::pymodule(name = "postgres")] -pub mod postgres_module {} diff --git a/src/query/base.rs b/src/query/base.rs deleted file mode 100644 index 4c8d2fa..0000000 --- a/src/query/base.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::internal::{BoundArgs, BoundKwargs, BoundObject}; - -crate::implement_pyclass! { - /// Subclass of query statements. - #[derive(Debug, Clone, Copy)] - [subclass] PyQueryStatement as "QueryStatement"; -} - -#[pyo3::pymethods] -impl PyQueryStatement { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> Self { - Self - } - - fn __init__(&self) {} - - /// Build a SQL string representation. - /// - /// **This method is unsafe and can cause SQL injection.** use `.build()` method instead. - #[pyo3(signature = (backend, /))] - #[allow(unused_variables)] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - Err(pyo3::exceptions::PyNotImplementedError::new_err(())) - } - - /// Build the SQL statement with parameter values. - /// - /// @signature (self, backend: _BackendName, /) -> tuple[str, tuple[Value, ...]] - #[pyo3(signature = (backend, /))] - #[allow(unused_variables)] - fn build<'a>( - &self, - py: pyo3::Python<'a>, - backend: String, - ) -> pyo3::PyResult<(String, BoundObject<'a>)> { - Err(pyo3::exceptions::PyNotImplementedError::new_err(())) - } -} diff --git a/src/query/case.rs b/src/query/case.rs deleted file mode 100644 index 106b38d..0000000 --- a/src/query/case.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::common::expression::PyExpr; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, RefBoundObject, ToSeaQuery}; - -crate::implement_pyclass! { - mutable [subclass] PyCaseStatement(CaseStatementState) as "CaseStatement" { - pub when: Vec<(PyExpr, PyExpr)>, - pub r#else: Option, - } -} - -impl Clone for CaseStatementState { - fn clone(&self) -> Self { - Self { - when: self.when.clone(), - r#else: self.r#else.clone(), - } - } -} - -impl ToSeaQuery for CaseStatementState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::CaseStatement { - let mut stmt = sea_query::CaseStatement::new(); - - for (x, y) in self.when.iter() { - stmt = stmt.case(x.0.clone(), y.0.clone()); - } - - if let Some(x) = &self.r#else { - stmt = stmt.finally(x.0.clone()); - } - - stmt - } -} - -#[pyo3::pymethods] -impl PyCaseStatement { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> Self { - Self::uninit() - } - - pub fn __init__(&self) -> pyo3::PyResult<()> { - self.0.set(CaseStatementState { - when: vec![], - r#else: None, - }); - Ok(()) - } - - fn when<'a>( - slf: pyo3::PyRef<'a, Self>, - condition: RefBoundObject<'a>, - result: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::Py_TYPE(condition.as_ptr()) != crate::typeref::EXPR_TYPE { - return crate::new_error!( - PyTypeError, - "expected Expr as condition, got {}", - crate::internal::get_type_name(condition.py(), condition.as_ptr()) - ); - } - - let condition = condition.cast_unchecked::().get().clone(); - let result = PyExpr::try_from(result)?; - - slf.0.lock().when.push((condition, result)); - } - - Ok(slf) - } - - fn else_<'a>( - slf: pyo3::PyRef<'a, Self>, - result: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - let result = PyExpr::try_from(result)?; - slf.0.lock().r#else = Some(result); - - Ok(slf) - } - - /// Shorthand for `Expr(self)` - fn to_expr(&self, py: pyo3::Python) -> PyExpr { - let stmt = self.0.lock().to_sea_query(py); - PyExpr(sea_query::SimpleExpr::Case(Box::new(stmt))) - } - - /// Shorthand for `SelectLabel(self, alias, window)` - #[pyo3(signature=(alias, window=None))] - fn label( - &self, - py: pyo3::Python, - alias: String, - window: Option>, - ) -> pyo3::PyResult { - let window = match window { - Some(x) => Some(crate::query::select::SelectLabelWindow::try_from(x)?), - None => None, - }; - let expr = self.to_expr(py); - - let state = crate::query::select::SelectLabelState { - expr, - alias: Some(alias), - window, - }; - Ok(state.into()) - } - - fn __copy__(&self) -> Self { - let lock = self.0.lock(); - lock.clone().into() - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - let mut fmt = ReprFormatter::new_with_pyref(&slf); - - fmt.vec("when", false) - .display_iter( - lock.when - .iter() - .map(|x| format!("{} => {}", x.0.__repr__(), x.1.__repr__())), - ) - .finish(&mut fmt); - - fmt.optional_map("else", lock.r#else.as_ref(), |x| x.__repr__()) - .finish() - } -} diff --git a/src/query/delete.rs b/src/query/delete.rs deleted file mode 100644 index 5663c7a..0000000 --- a/src/query/delete.rs +++ /dev/null @@ -1,220 +0,0 @@ -use super::base::PyQueryStatement; -use super::ordering::PyOrdering; -use super::returning::PyReturning; -use crate::common::expression::PyExpr; -use crate::common::table_ref::PyTableName; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, RefBoundObject, ToSeaQuery}; - -crate::implement_pyclass! { - /// Builds DELETE SQL statements with a fluent interface. - /// - /// Provides a chainable API for constructing DELETE queries with support for: - /// - WHERE conditions for filtering - /// - LIMIT for restricting deletion count - /// - ORDER BY for determining deletion order - /// - RETURNING clauses for getting deleted data - #[derive(Clone)] - mutable [subclass, extends=PyQueryStatement] PyDeleteStatement(DeleteStatementState) as "DeleteStatement" { - pub table: PyTableName, - pub r#where: Option, - pub limit: Option, - pub returning_clause: Option, - pub orders: Vec, - } -} - -impl ToSeaQuery for DeleteStatementState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::DeleteStatement { - let mut stmt = sea_query::DeleteStatement::new(); - stmt.from_table(self.table.clone()); - - if let Some(x) = &self.r#where { - stmt.and_where(x.0.clone()); - } - if let Some(x) = self.limit { - stmt.limit(x); - } - - if let Some(x) = &self.returning_clause { - stmt.returning(x.0.to_sea_query(py)); - } - - for order in self.orders.iter() { - if let Some(x) = order.null_order { - stmt.order_by_expr_with_nulls(order.target.0.clone(), order.order.clone(), x); - } else { - stmt.order_by_expr(order.target.0.clone(), order.order.clone()); - } - } - - stmt - } -} - -#[pyo3::pymethods] -impl PyDeleteStatement { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PyQueryStatement) { - (Self::uninit(), PyQueryStatement) - } - - pub fn __init__(&self, table: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let table = PyTableName::try_from(table)?; - - let state = DeleteStatementState { - table, - r#where: None, - limit: None, - returning_clause: None, - orders: vec![], - }; - self.0.set(state); - Ok(()) - } - - /// Specify the table to delete from. - #[allow(clippy::wrong_self_convention)] - fn from_table<'a>( - slf: pyo3::PyRef<'a, Self>, - table: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - let table = PyTableName::try_from(table)?; - - { - let mut lock = slf.0.lock(); - lock.table = table; - } - Ok(slf) - } - - /// Limit the number of rows to delete. - fn limit(slf: pyo3::PyRef<'_, Self>, n: u64) -> pyo3::PyRef<'_, Self> { - { - let mut lock = slf.0.lock(); - lock.limit = Some(n); - } - - slf - } - - /// Specify columns to return from the inserted rows. - #[pyo3(signature=(clause))] - fn returning<'a>( - slf: pyo3::PyRef<'a, Self>, - clause: pyo3::Bound<'_, PyReturning>, - ) -> pyo3::PyResult> { - { - let mut lock = slf.0.lock(); - lock.returning_clause = Some(clause.get().clone()); - } - Ok(slf) - } - - /// Add a WHERE condition to filter rows to delete. - fn r#where<'a>( - slf: pyo3::PyRef<'a, Self>, - condition: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::Py_TYPE(condition.as_ptr()) != crate::typeref::EXPR_TYPE { - return crate::new_error!( - PyTypeError, - "expected Expr, got {}", - crate::internal::get_type_name(condition.py(), condition.as_ptr()) - ); - } - - let condition = condition.cast_unchecked::().get().clone(); - let mut lock = slf.0.lock(); - - match std::mem::take(&mut lock.r#where) { - None => { - lock.r#where = Some(condition); - } - Some(x) => { - lock.r#where = Some(PyExpr(x.0.and(condition.0))); - } - } - } - - Ok(slf) - } - - /// Remove where conditions from statement. - fn clear_where(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { - slf.0.lock().r#where = None; - slf - } - - /// Specify the order in which to delete rows. Typically used with - /// `.limit` method to delete specific rows. - #[pyo3(signature=(clause))] - fn order_by<'a>( - slf: pyo3::PyRef<'a, Self>, - clause: pyo3::Bound<'_, PyOrdering>, - ) -> pyo3::PyResult> { - { - let mut lock = slf.0.lock(); - lock.orders.push(clause.get().clone()); - } - - Ok(slf) - } - - /// Remove orders from statement. - fn clear_order_by(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { - slf.0.lock().orders.clear(); - slf - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_query_statement!(backend, stmt) - } - - #[pyo3(signature = (backend, /))] - fn build<'a>( - &self, - py: pyo3::Python<'a>, - backend: String, - ) -> pyo3::PyResult<(String, BoundObject<'a>)> { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_query_parts!(py, backend, stmt) - } - - fn __copy__<'a>(&self, py: pyo3::Python<'a>) -> pyo3::PyResult> { - let lock = self.0.lock(); - pyo3::Bound::new(py, (lock.clone().into(), PyQueryStatement)) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - let mut fmt = ReprFormatter::new_with_pyref(&slf) - .map("table", &lock.table, |x| x.__repr__()) - .optional_map("where", lock.r#where.as_ref(), |x| x.__repr__()) - .optional_display("limit", lock.limit.as_ref()) - .take(); - - fmt.vec("orders", true) - .display_iter(lock.orders.iter().map(|x| x.__repr__())) - .finish(&mut fmt); - - fmt.optional_map("returning", lock.returning_clause.as_ref(), |x| { - x.__repr__() - }) - .finish() - } -} diff --git a/src/query/insert.rs b/src/query/insert.rs deleted file mode 100644 index 8f7bb82..0000000 --- a/src/query/insert.rs +++ /dev/null @@ -1,463 +0,0 @@ -use super::base::PyQueryStatement; -use super::on_conflict::PyOnConflict; -use super::returning::PyReturning; -use crate::common::column_ref::PyColumnRef; -use crate::common::expression::PyExpr; -use crate::common::table_ref::PyTableName; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, PyObject, RefBoundObject, ToSeaQuery}; - -#[derive(Debug, Default)] -pub enum InsertValueSource { - #[default] - None, - Single(Vec), - Many(Vec>), - Select( - /// Always is `PySelectStatement` - PyObject, - ), -} - -crate::implement_pyclass! { - /// Builds INSERT SQL statements with a fluent interface. - /// - /// Provides a chainable API for constructing INSERT queries with support for: - /// - Single or multiple row insertion - /// - Conflict resolution (UPSERT) - /// - RETURNING clauses - /// - REPLACE functionality - /// - Default values - mutable [subclass, extends=PyQueryStatement] PyInsertStatement(InsertStatementState) as "InsertStatement" { - pub replace: bool, - pub table: PyTableName, - pub columns: Vec, - pub source: InsertValueSource, - - /// Always is `Option`. - pub on_conflict: Option, - pub returning_clause: Option, - pub default_values: Option, - } -} - -impl InsertValueSource { - fn clone_ref(&self, py: pyo3::Python) -> Self { - match self { - Self::Single(x) => Self::Single(x.clone()), - Self::Many(x) => Self::Many(x.clone()), - Self::Select(x) => Self::Select(x.clone_ref(py)), - Self::None => Self::None, - } - } -} - -impl InsertStatementState { - fn clone_ref(&self, py: pyo3::Python) -> Self { - Self { - replace: self.replace, - table: self.table.clone(), - columns: self.columns.clone(), - source: self.source.clone_ref(py), - on_conflict: self.on_conflict.as_ref().map(|x| x.clone_ref(py)), - returning_clause: self.returning_clause.clone(), - default_values: self.default_values, - } - } -} - -impl ToSeaQuery for InsertStatementState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::InsertStatement { - let mut stmt = sea_query::InsertStatement::new(); - stmt.into_table(self.table.clone()); - - if self.replace { - stmt.replace(); - } - stmt.columns(self.columns.iter().map(sea_query::Alias::new)); - - match &self.source { - InsertValueSource::None => (), - InsertValueSource::Single(x) => { - stmt.values(x.iter().map(|x| x.0.clone())).unwrap(); - } - InsertValueSource::Many(x) => { - for y in x.iter() { - stmt.values(y.iter().map(|x| x.0.clone())).unwrap(); - } - } - InsertValueSource::Select(subquery) => unsafe { - let subquery = - subquery.cast_bound_unchecked::(py); - - stmt.select_from(subquery.get().0.lock().to_sea_query(py)) - .unwrap(); - }, - } - - if let Some(x) = &self.on_conflict { - let py_oc = unsafe { x.cast_bound_unchecked::(py) }; - stmt.on_conflict(py_oc.get().0.lock().to_sea_query(py)); - } - if let Some(rows) = self.default_values { - stmt.or_default_values_many(rows); - } - if let Some(x) = &self.returning_clause { - stmt.returning(x.0.to_sea_query(py)); - } - - stmt - } -} - -impl InsertStatementState { - #[inline] - fn values_from_dictionary( - &mut self, - kwds: pyo3::Bound<'_, pyo3::types::PyDict>, - ) -> pyo3::PyResult<()> { - use pyo3::types::{PyAnyMethods, PyDictMethods}; - - if !self.columns.is_empty() && self.columns.len() != kwds.len() { - return Err(pyo3::PyErr::new::( - "values length isn't equal to columns length - this occurres when you're calling \ - `.values()` method multiple times with different columns.", - )); - } - - let mut cols = Vec::with_capacity(kwds.len()); - let mut vals = Vec::with_capacity(kwds.len()); - - unsafe { - for (key, value) in kwds.into_iter() { - let key = key.extract::().unwrap_unchecked(); - - cols.push(key); - vals.push(PyExpr::try_from(&value)?); - } - } - - match std::mem::take(&mut self.source) { - InsertValueSource::None | InsertValueSource::Select(_) => { - self.source = InsertValueSource::Single(vals); - } - InsertValueSource::Single(oldvals) => { - self.source = InsertValueSource::Many(vec![oldvals, vals]); - } - InsertValueSource::Many(mut arr_of_vals) => { - arr_of_vals.push(vals); - self.source = InsertValueSource::Many(arr_of_vals); - } - } - self.columns = cols; - - Ok(()) - } - - #[inline] - fn values_from_tuple(&mut self, args: BoundArgs<'_>) -> pyo3::PyResult<()> { - use pyo3::types::PyTupleMethods; - - if !self.columns.is_empty() && self.columns.len() != args.len() { - return Err(pyo3::PyErr::new::( - "values length isn't equal to columns length", - )); - } - - let mut vals = Vec::with_capacity(args.len()); - for value in args.iter() { - vals.push(PyExpr::try_from(&value)?); - } - - match std::mem::take(&mut self.source) { - InsertValueSource::None | InsertValueSource::Select(_) => { - self.source = InsertValueSource::Single(vals); - } - InsertValueSource::Single(oldvals) => { - self.source = InsertValueSource::Many(vec![oldvals, vals]); - } - InsertValueSource::Many(mut arr_of_vals) => { - arr_of_vals.push(vals); - self.source = InsertValueSource::Many(arr_of_vals); - } - } - - Ok(()) - } -} - -#[pyo3::pymethods] -impl PyInsertStatement { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PyQueryStatement) { - (Self::uninit(), PyQueryStatement) - } - - pub fn __init__(&self, table: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let table = PyTableName::try_from(table)?; - - let state = InsertStatementState { - replace: false, - table, - columns: vec![], - source: InsertValueSource::None, - on_conflict: None, - returning_clause: None, - default_values: None, - }; - self.0.set(state); - Ok(()) - } - - /// Convert this INSERT to a REPLACE statement. - /// - /// REPLACE will delete existing rows that conflict with the new row - /// before inserting. - fn replace(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { - { - let mut lock = slf.0.lock(); - lock.replace = true; - } - - slf - } - - /// Specify the target table for insertion. - fn into<'a>( - slf: pyo3::PyRef<'a, Self>, - table: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - let table = PyTableName::try_from(table)?; - - { - let mut lock = slf.0.lock(); - lock.table = table; - } - Ok(slf) - } - - /// Specify the columns for insertion. - /// - /// There's no need to use this method when you're specifying column - /// names in `.values` method. - #[pyo3(signature=(*args))] - fn columns<'a>( - slf: pyo3::PyRef<'a, Self>, - args: BoundArgs<'a>, - ) -> pyo3::PyResult> { - use pyo3::types::PyTupleMethods; - - let mut columns = Vec::with_capacity(args.len()); - - for col in args.iter() { - let column_ref = PyColumnRef::try_from(&col)?; - - match column_ref.name { - Some(x) => columns.push(x.to_string()), - None => { - return Err(pyo3::exceptions::PyValueError::new_err( - "Insert.columns cannot accept asterisk '*'", - )) - } - } - } - - { - let mut lock = slf.0.lock(); - lock.columns = columns; - } - Ok(slf) - } - - /// Specify values to insert. Also you can specify columns using keyword arguments. - #[pyo3(signature=(*args, **kwds))] - fn values<'a>( - slf: pyo3::PyRef<'a, Self>, - args: BoundArgs<'a>, - kwds: Option>, - ) -> pyo3::PyResult> { - use pyo3::types::PyTupleMethods; - - if !PyTupleMethods::is_empty(args) && kwds.is_some() { - return crate::new_error!( - PyTypeError, - "cannot use both args and kwargs at the same time" - ); - } - - if let Some(kwds) = kwds { - let mut lock = slf.0.lock(); - lock.values_from_dictionary(kwds.clone())?; - } else { - let mut lock = slf.0.lock(); - lock.values_from_tuple(args)?; - } - - Ok(slf) - } - - /// Specify a select query whose values to be inserted. - fn select_from<'a>( - slf: pyo3::PyRef<'a, Self>, - statement: BoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::PyObject_TypeCheck( - statement.as_ptr(), - crate::typeref::SELECT_STATEMENT_TYPE, - ) == 0 - { - return crate::new_error!( - PyTypeError, - "expected SelectStatement, got {}", - crate::internal::get_type_name(statement.py(), statement.as_ptr()) - ); - } - - // We have to return error if columns mismatch, like what SeaQuery does - let cast = statement.cast_unchecked::(); - - let val_len = cast.get().0.lock().exprs.len(); - let col_len = slf.0.lock().columns.len(); - - if col_len != val_len { - return crate::new_error!( - PyValueError, - "columns and values length mismatch: {} != {}", - col_len, - val_len, - ); - } - } - - { - let mut lock = slf.0.lock(); - lock.source = InsertValueSource::Select(statement.unbind()); - } - Ok(slf) - } - - /// Use DEFAULT VALUES if no values were specified. The `rows` - /// Specifies number of rows to insert with default values. - #[pyo3(signature=(rows=1))] - fn or_default_values(slf: pyo3::PyRef<'_, Self>, rows: u32) -> pyo3::PyRef<'_, Self> { - { - let mut lock = slf.0.lock(); - lock.default_values = Some(rows); - } - - slf - } - - /// Specify conflict resolution behavior (UPSERT). - fn on_conflict<'a>( - slf: pyo3::PyRef<'a, Self>, - action: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(action.as_ptr(), crate::typeref::ON_CONFLICT_TYPE) == 0 - { - return crate::new_error!( - PyTypeError, - "expected OnConflict, got {}", - crate::internal::get_type_name(action.py(), action.as_ptr()) - ); - } - - let mut lock = slf.0.lock(); - lock.on_conflict = Some(action.clone().unbind()); - } - - Ok(slf) - } - - /// Specify columns to return from the inserted rows. - #[pyo3(signature=(clause))] - fn returning<'a>( - slf: pyo3::PyRef<'a, Self>, - clause: pyo3::Bound<'_, PyReturning>, - ) -> pyo3::PyResult> { - { - let mut lock = slf.0.lock(); - lock.returning_clause = Some(clause.get().clone()); - } - Ok(slf) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_query_statement!(backend, stmt) - } - - #[pyo3(signature = (backend, /))] - fn build<'a>( - &self, - py: pyo3::Python<'a>, - backend: String, - ) -> pyo3::PyResult<(String, BoundObject<'a>)> { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_query_parts!(py, backend, stmt) - } - - fn __copy__<'a>(&self, py: pyo3::Python<'a>) -> pyo3::PyResult> { - let lock = self.0.lock(); - pyo3::Bound::new(py, (lock.clone_ref(py).into(), PyQueryStatement)) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - let mut fmt = ReprFormatter::new_with_pyref(&slf) - .optional_boolean("replace", lock.replace) - .map("into", &lock.table, |x| x.__repr__()) - .take(); - - fmt.vec("columns", true) - .quote_iter(lock.columns.iter()) - .finish(&mut fmt); - - fmt.optional_display("on_conflict", lock.on_conflict.as_ref()); - - match &lock.source { - InsertValueSource::None => { - fmt.optional_display("default_rows", lock.default_values); - } - InsertValueSource::Select(x) => { - fmt.display("select_from", x); - } - InsertValueSource::Single(x) => { - fmt.vec("values", true) - .display_iter(x.iter().map(|x| x.__repr__())) - .finish(&mut fmt); - } - InsertValueSource::Many(x) => { - let mut fmtvec = fmt.vec("values", true); - - for (index, nested) in x.iter().enumerate() { - fmtvec - .vec(index) - .display_iter(nested.iter().map(|x| x.__repr__())) - .finish(&mut fmtvec); - } - - fmtvec.finish(&mut fmt); - } - } - - fmt.optional_map("returning", lock.returning_clause.as_ref(), |x| { - x.__repr__() - }) - .finish() - } -} diff --git a/src/query/mod.rs b/src/query/mod.rs deleted file mode 100644 index deccbbf..0000000 --- a/src/query/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -pub mod base; -pub mod case; -pub mod delete; -pub mod insert; -pub mod on_conflict; -pub mod ordering; -pub mod returning; -pub mod select; -pub mod update; -pub mod window; -pub mod with; - -#[pyo3::pymodule(name = "query")] -pub mod query_module { - #[pymodule_export] - use super::base::PyQueryStatement; - - #[pymodule_export] - use super::on_conflict::PyOnConflict; - - #[pymodule_export] - use super::returning::PyReturning; - - #[pymodule_export] - use super::insert::PyInsertStatement; - - #[pymodule_export] - use super::ordering::PyOrdering; - - #[pymodule_export] - use super::delete::PyDeleteStatement; - - #[pymodule_export] - use super::update::PyUpdateStatement; - - #[pymodule_export] - use super::window::PyFrame; - - #[pymodule_export] - use super::window::PyWindowStatement; - - #[pymodule_export] - use super::select::PySelectLabel; - - #[pymodule_export] - use super::select::PySelectStatement; - - #[pymodule_export] - use super::case::PyCaseStatement; - - #[pymodule_export] - use super::with::PyWithClause; - - #[pymodule_export] - use super::with::PyWithQuery; -} diff --git a/src/query/on_conflict.rs b/src/query/on_conflict.rs deleted file mode 100644 index 4354599..0000000 --- a/src/query/on_conflict.rs +++ /dev/null @@ -1,326 +0,0 @@ -use pyo3::types::{PyAnyMethods, PyDictMethods, PyTupleMethods}; -use sea_query::IntoIden; - -use crate::common::column_ref::PyColumnRef; -use crate::common::expression::PyExpr; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, RefBoundObject, ToSeaQuery}; - -#[derive(Debug, Clone)] -pub enum OnConflictUpdate { - Column(sea_query::DynIden), - Expr(sea_query::DynIden, PyExpr), -} - -#[derive(Debug, Clone, Default)] -pub enum OnConflictAction { - #[default] - None, - DoNothing(Vec), - DoUpdate(Vec), -} - -crate::implement_pyclass! { - /// Specifies conflict resolution behavior for INSERT statements. - /// - /// Handles situations where an INSERT would violate a unique constraint - /// or primary key. - /// - /// This corresponds to INSERT ... ON CONFLICT in PostgreSQL and - /// INSERT ... ON DUPLICATE KEY UPDATE in MySQL. - #[derive(Debug, Clone)] - mutable [subclass] PyOnConflict(OnConflictState) as "OnConflict" { - targets: Vec, - action: OnConflictAction, - target_where: Option, - action_where: Option, - } -} - -impl ToSeaQuery for OnConflictState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::OnConflict { - let mut stmt = sea_query::OnConflict::columns(self.targets.clone()); - - match &self.action { - OnConflictAction::None => (), - OnConflictAction::DoNothing(x) => { - if x.is_empty() { - stmt.do_nothing(); - } else { - stmt.do_nothing_on(x.iter().cloned()); - } - } - OnConflictAction::DoUpdate(x) => { - let mut columns = Vec::new(); - let mut exprs = Vec::new(); - - for val in x.iter() { - match val { - OnConflictUpdate::Column(name) => columns.push(name.clone()), - OnConflictUpdate::Expr(name, expr) => { - exprs.push((name.clone(), expr.0.clone())); - } - } - } - - stmt.update_columns(columns); - stmt.values(exprs); - } - } - - if let Some(x) = &self.action_where { - stmt.action_and_where(x.0.clone()); - } - if let Some(x) = &self.target_where { - stmt.target_and_where(x.0.clone()); - } - - stmt - } -} - -impl OnConflictState { - #[inline] - fn update_from_dictionary( - &mut self, - kwds: pyo3::Bound<'_, pyo3::types::PyDict>, - ) -> pyo3::PyResult<()> { - let mut actions = Vec::with_capacity(kwds.len()); - - for (key, val) in kwds.iter() { - unsafe { - let val = PyExpr::try_from(&val)?; - - let action = OnConflictUpdate::Expr( - sea_query::Alias::new(key.extract::().unwrap_unchecked()).into_iden(), - val, - ); - actions.push(action); - } - } - - match &mut self.action { - OnConflictAction::DoNothing(_) | OnConflictAction::None => { - self.action = OnConflictAction::DoUpdate(actions); - } - OnConflictAction::DoUpdate(x) => { - x.append(&mut actions); - } - } - - Ok(()) - } - - #[inline] - fn update_from_tuple(&mut self, args: BoundArgs<'_>) -> pyo3::PyResult<()> { - let mut actions = Vec::with_capacity(args.len()); - - for key in args.iter() { - let column_ref = PyColumnRef::try_from(&key)?; - match column_ref.name { - Some(x) => actions.push(OnConflictUpdate::Column(x)), - None => { - return Err(pyo3::exceptions::PyValueError::new_err( - "OnConflict cannot accept asterisk '*' as column", - )) - } - } - } - - match &mut self.action { - OnConflictAction::DoNothing(_) | OnConflictAction::None => { - self.action = OnConflictAction::DoUpdate(actions); - } - OnConflictAction::DoUpdate(x) => { - x.append(&mut actions); - } - } - - Ok(()) - } -} - -#[pyo3::pymethods] -impl PyOnConflict { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> Self { - Self::uninit() - } - - #[pyo3(signature=(*targets))] - fn __init__(&self, targets: BoundArgs<'_>) -> pyo3::PyResult<()> { - if targets.is_empty() { - let state = OnConflictState { - targets: vec![], - action: OnConflictAction::None, - target_where: None, - action_where: None, - }; - - self.0.set(state); - return Ok(()); - } - - let mut normalized_targets = Vec::with_capacity(targets.len()); - for item in targets.iter() { - let column_ref = PyColumnRef::try_from(&item)?; - match column_ref.name { - Some(x) => normalized_targets.push(x), - None => { - return Err(pyo3::exceptions::PyValueError::new_err( - "OnConflict cannot accept asterisk '*' as target", - )) - } - } - } - - let state = OnConflictState { - targets: normalized_targets, - action: OnConflictAction::None, - target_where: None, - action_where: None, - }; - self.0.set(state); - Ok(()) - } - - /// Specify DO NOTHING action for conflicts. - /// - /// When a conflict occurs, the conflicting row will be skipped. - /// - /// `keys` parameter provides primary keys if you are using MySQL, for MySQL specific polyfill. - #[pyo3(signature=(*keys))] - fn do_nothing<'a>( - slf: pyo3::PyRef<'a, Self>, - keys: &pyo3::Bound<'a, pyo3::types::PyTuple>, - ) -> pyo3::PyResult> { - if keys.is_empty() { - let mut lock = slf.0.lock(); - lock.action = OnConflictAction::DoNothing(vec![]); - drop(lock); - - return Ok(slf); - } - - let mut normalized_keys: Vec = Vec::with_capacity(keys.len()); - for item in keys.iter() { - let column_ref = PyColumnRef::try_from(&item)?; - match column_ref.name { - Some(x) => normalized_keys.push(x), - None => { - return Err(pyo3::exceptions::PyValueError::new_err( - "OnConflict cannot accept asterisk '*' as column", - )) - } - } - } - - { - let mut lock = slf.0.lock(); - - match &mut lock.action { - OnConflictAction::DoUpdate(_) | OnConflictAction::None => { - lock.action = OnConflictAction::DoNothing(normalized_keys); - } - OnConflictAction::DoNothing(x) => { - x.append(&mut normalized_keys); - } - } - } - - Ok(slf) - } - - /// Specify DO UPDATE action for conflicts using column names, or with explicit values. - #[pyo3(signature=(*args, **kwds))] - fn do_update<'a>( - slf: pyo3::PyRef<'a, Self>, - args: BoundArgs<'a>, - kwds: Option>, - ) -> pyo3::PyResult> { - if !PyTupleMethods::is_empty(args) { - let mut lock = slf.0.lock(); - lock.update_from_tuple(args)?; - } - if let Some(kwds) = kwds { - let mut lock = slf.0.lock(); - lock.update_from_dictionary(kwds.clone())?; - } - - Ok(slf) - } - - /// Add a WHERE clause to the conflict target (partial unique index). - fn target_where<'a>( - slf: pyo3::PyRef<'a, Self>, - condition: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::Py_TYPE(condition.as_ptr()) != crate::typeref::EXPR_TYPE { - return crate::new_error!( - PyTypeError, - "expected Expr, got {}", - crate::internal::get_type_name(slf.py(), condition.as_ptr()) - ); - } - - let mut lock = slf.0.lock(); - lock.target_where = Some(condition.cast_unchecked::().get().clone()); - } - - Ok(slf) - } - - /// Add a WHERE clause to the conflict action (conditional update). - fn action_where<'a>( - slf: pyo3::PyRef<'a, Self>, - condition: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::Py_TYPE(condition.as_ptr()) != crate::typeref::EXPR_TYPE { - return crate::new_error!( - PyTypeError, - "expected Expr, got {}", - crate::internal::get_type_name(slf.py(), condition.as_ptr()) - ); - } - - let mut lock = slf.0.lock(); - lock.action_where = Some(condition.cast_unchecked::().get().clone()); - } - - Ok(slf) - } - - fn __copy__(&self) -> Self { - let lock = self.0.lock(); - lock.clone().into() - } - - pub fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - let mut fmt = ReprFormatter::new_with_pyref(&slf); - - fmt.vec("targets", true) - .quote_iter(lock.targets.iter().map(|x| x.to_string())) - .finish(&mut fmt); - - match &lock.action { - OnConflictAction::DoNothing(_) => { - fmt.quote("action", "DO NOTHING"); - } - OnConflictAction::DoUpdate(_) => { - fmt.quote("action", "DO UPDATE"); - } - OnConflictAction::None => (), - } - - fmt.optional_map("target_where", lock.target_where.as_ref(), |x| x.__repr__()) - .optional_map("action_where", lock.action_where.as_ref(), |x| x.__repr__()) - .finish() - } -} diff --git a/src/query/ordering.rs b/src/query/ordering.rs deleted file mode 100644 index e15d891..0000000 --- a/src/query/ordering.rs +++ /dev/null @@ -1,114 +0,0 @@ -use sea_query::IntoColumnRef; - -use crate::common::column_ref::PyColumnRef; -use crate::common::expression::PyExpr; -use crate::internal::repr::ReprFormatter; -use crate::internal::BoundObject; - -#[inline] -fn map_order_to_str(order: &sea_query::Order) -> String { - match order { - sea_query::Order::Asc => String::from("ASC"), - sea_query::Order::Desc => String::from("DESC"), - _ => unsafe { std::hint::unreachable_unchecked() }, - } -} - -#[inline] -fn map_null_ordering_to_str(order: Option) -> Option { - match order { - Some(sea_query::NullOrdering::First) => Some(String::from("FIRST")), - Some(sea_query::NullOrdering::Last) => Some(String::from("LAST")), - None => None, - } -} - -crate::implement_pyclass! { - // NOTE: It's a very simple clause, so I think it's OK to be a final type. - - /// Specifies ordering behavior for UPDATE, DELETE, and SELECT statements. - #[derive(Debug, Clone)] - [] PyOrdering as "Ordering" { - pub target: PyExpr, - pub order: sea_query::Order, - pub null_order: Option, - } -} - -#[pyo3::pymethods] -impl PyOrdering { - #[new] - #[pyo3(signature=(target, order = String::from("ASC"), null_order=None))] - fn __new__( - target: BoundObject<'_>, - order: String, - null_order: Option, - ) -> pyo3::PyResult { - let target = unsafe { - if pyo3::ffi::Py_TYPE(target.as_ptr()) == crate::typeref::EXPR_TYPE { - target.cast_unchecked::().get().clone() - } else { - let column_ref = PyColumnRef::try_from(&target)?; - - PyExpr(sea_query::SimpleExpr::Column(column_ref.into_column_ref())) - } - }; - - let order = match order.to_ascii_lowercase().as_str() { - "asc" => sea_query::Order::Asc, - "desc" => sea_query::Order::Desc, - _ => { - return Err(pyo3::exceptions::PyValueError::new_err(format!( - "unknown order: {order}" - ))) - } - }; - - let null_order = match null_order { - None => None, - Some(x) => match x.to_ascii_lowercase().as_str() { - "first" => Some(sea_query::NullOrdering::First), - "last" => Some(sea_query::NullOrdering::Last), - _ => { - return Err(pyo3::exceptions::PyValueError::new_err(format!( - "unknown null_order: {x}" - ))) - } - }, - }; - - Ok(Self { - target, - order, - null_order, - }) - } - - /// Target expression. - #[getter] - fn target(&self) -> PyExpr { - self.target.clone() - } - - #[getter] - fn order(&self) -> String { - map_order_to_str(&self.order) - } - - #[getter] - fn null_order(&self) -> Option { - map_null_ordering_to_str(self.null_order) - } - - fn __copy__(&self) -> Self { - self.clone() - } - - pub fn __repr__(&self) -> String { - ReprFormatter::new("Ordering") - .map("target", &self.target, |x| x.__repr__()) - .map("order", &self.order, map_order_to_str) - .optional_quote("null_ordering", map_null_ordering_to_str(self.null_order)) - .finish() - } -} diff --git a/src/query/returning.rs b/src/query/returning.rs deleted file mode 100644 index 5871af5..0000000 --- a/src/query/returning.rs +++ /dev/null @@ -1,92 +0,0 @@ -use pyo3::types::PyTupleMethods; -use sea_query::IntoColumnRef; - -use crate::common::column_ref::PyColumnRef; -use crate::common::expression::PyExpr; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, ToSeaQuery}; - -#[derive(Debug, Clone)] -pub enum ReturningState { - All, - Exprs(Vec), -} - -crate::implement_pyclass! { - // NOTE: It's a very simple clause, so I think it's OK to be a final type. - - /// RETURNING clause. - /// - /// Works on PostgreSQL and SQLite>=3.35.0. - #[derive(Debug, Clone)] - [] PyReturning as "Returning" (pub ReturningState); -} - -impl ToSeaQuery for ReturningState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::ReturningClause { - match self { - Self::All => sea_query::ReturningClause::All, - Self::Exprs(x) => { - sea_query::ReturningClause::Exprs(x.iter().map(|x| x.0.clone()).collect()) - } - } - } -} - -#[pyo3::pymethods] -impl PyReturning { - /// Specify columns you need to return - #[new] - #[pyo3(signature=(*args))] - pub fn __new__(args: BoundArgs<'_>) -> pyo3::PyResult { - let mut columns = Vec::with_capacity(args.len()); - - for col in args.iter() { - unsafe { - if pyo3::ffi::Py_TYPE(col.as_ptr()) == crate::typeref::EXPR_TYPE { - let value = col.cast_into_unchecked::(); - - columns.push(value.get().clone()); - continue; - } - } - - let column_ref = PyColumnRef::try_from(&col)?; - if column_ref.name.is_none() { - return Ok(Self(ReturningState::All)); - } - - columns.push(PyExpr(column_ref.into_column_ref().into())); - } - - Ok(Self(ReturningState::Exprs(columns))) - } - - /// Return all columns. Same as `self.columns("*")`. - #[classmethod] - fn all(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - Self(ReturningState::All) - } - - fn __copy__(&self) -> Self { - self.clone() - } - - pub fn __repr__(&self) -> String { - let mut fmt = ReprFormatter::new("Returning"); - - match &self.0 { - ReturningState::All => { - fmt.pair("", "*"); - } - ReturningState::Exprs(x) => { - fmt.vec("", false) - .display_iter(x.iter().map(|x| x.__repr__())) - .finish(&mut fmt); - } - } - - fmt.finish() - } -} diff --git a/src/query/select.rs b/src/query/select.rs deleted file mode 100644 index f2dc887..0000000 --- a/src/query/select.rs +++ /dev/null @@ -1,1241 +0,0 @@ -use super::base::PyQueryStatement; -use super::ordering::PyOrdering; -use crate::common::column_ref::PyColumnRef; -use crate::common::expression::{PyExpr, PyFunc}; -use crate::common::table_ref::PyTableName; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, PyObject, RefBoundObject, ToSeaQuery}; -use crate::query::window::PyWindowStatement; - -use pyo3::types::{PyAnyMethods, PyTupleMethods}; -use sea_query::{IntoColumnRef, IntoIden}; - -/// Window type in [`PySelectLabel`] -pub enum SelectLabelWindow { - Name(sea_query::DynIden), - Query( - /// Always is `PyWindowStatement` - PyObject, - ), -} - -/// Select references in [`PySelectStatement`] -/// -/// It exactly does what [`sea_query::TableRef`] does -pub enum SelectReference { - SubQuery( - /// Always is `PySelectStatement` - PyObject, - sea_query::DynIden, - ), - Func(PyFunc, sea_query::DynIden), - TableName(PyTableName), -} - -/// Distinct mode in [`PySelectStatement`] -#[derive(Debug, Clone, Default)] -pub enum DistinctMode { - #[default] - None, - Distinct, - DistinctOn(Vec), -} - -/// Lock mode and options in [`PySelectStatement`] -#[derive(Debug, Clone)] -pub struct LockMode { - pub r#type: sea_query::LockType, - pub behavior: Option, - pub tables: Vec, -} - -/// Join mode and options in [`PySelectStatement`] -pub struct JoinMode { - pub r#type: sea_query::JoinType, - pub reference: SelectReference, - pub on: Option, - pub lateral: bool, -} - -crate::implement_pyclass! { - /// Represents a column expression with an optional alias in a SELECT statement. - /// - /// Used to specify both the expression to select and an optional alias name - /// for the result column. - immutable [subclass] PySelectLabel(SelectLabelState) as "SelectLabel" { - pub expr: PyExpr, - pub alias: Option, - pub window: Option, - } -} -crate::implement_pyclass! { - /// Builds SELECT SQL statements with a fluent interface. - /// - /// Provides a chainable API for constructing SELECT queries with support for: - /// - Column selection with expressions and aliases - /// - Table and subquery sources - /// - Filtering with WHERE and HAVING - /// - Joins (inner, left, right, full, cross, lateral) - /// - Grouping and aggregation - /// - Ordering and pagination - /// - Set operations (UNION, EXCEPT, INTERSECT) - /// - Row locking for transactions - /// - DISTINCT queries - mutable [subclass, extends=PyQueryStatement] PySelectStatement(SelectStatementState) as "SelectStatement" { - pub references: Vec, - - /// Always is `Vec` - pub exprs: Vec, - - pub r#where: Option, - pub groups: Vec, - - pub having: Option, - pub orders: Vec, - pub distinct: DistinctMode, - pub joins: Vec, - pub lock: Option, - pub limit: Option, - pub offset: Option, - - /// Always is `Option<(_, PyWindowStatement)>` - pub window: Option<(sea_query::DynIden, PyObject)>, - - /// Always is `Option<(_, PySelectStatement)>` - pub unions: Vec<(sea_query::UnionType, PyObject)>, - - // TODO - // pub table_sample: Option, - // pub index_hint: Option, - } -} - -#[allow(clippy::derivable_impls)] -impl Default for SelectStatementState { - fn default() -> Self { - Self { - references: Default::default(), - exprs: Default::default(), - r#where: Default::default(), - groups: Default::default(), - unions: Default::default(), - having: Default::default(), - orders: Default::default(), - distinct: Default::default(), - joins: Default::default(), - lock: Default::default(), - limit: Default::default(), - offset: Default::default(), - window: Default::default(), - } - } -} - -impl SelectLabelState { - fn clone_ref(&self, py: pyo3::Python) -> Self { - Self { - expr: self.expr.clone(), - alias: self.alias.clone(), - window: self.window.as_ref().map(|x| match x { - SelectLabelWindow::Name(x) => SelectLabelWindow::Name(x.clone()), - SelectLabelWindow::Query(x) => SelectLabelWindow::Query(x.clone_ref(py)), - }), - } - } -} - -impl SelectReference { - fn clone_ref(&self, py: pyo3::Python) -> Self { - match self { - Self::Func(x, y) => Self::Func(x.clone(), y.clone()), - Self::TableName(x) => Self::TableName(x.clone()), - Self::SubQuery(x, y) => Self::SubQuery(x.clone_ref(py), y.clone()), - } - } -} - -impl JoinMode { - fn clone_ref(&self, py: pyo3::Python) -> Self { - Self { - r#type: self.r#type, - reference: self.reference.clone_ref(py), - on: self.on.clone(), - lateral: self.lateral, - } - } -} - -impl SelectStatementState { - fn clone_ref(&self, py: pyo3::Python) -> Self { - Self { - references: self.references.iter().map(|x| x.clone_ref(py)).collect(), - exprs: self.exprs.iter().map(|x| x.clone_ref(py)).collect(), - r#where: self.r#where.clone(), - groups: self.groups.clone(), - having: self.having.clone(), - orders: self.orders.clone(), - distinct: self.distinct.clone(), - joins: self.joins.iter().map(|x| x.clone_ref(py)).collect(), - lock: self.lock.clone(), - limit: self.limit, - offset: self.offset, - window: self - .window - .as_ref() - .map(|(x, y)| (x.clone(), y.clone_ref(py))), - unions: self - .unions - .iter() - .map(|(x, y)| (*x, y.clone_ref(py))) - .collect(), - } - } -} - -impl TryFrom> for SelectLabelWindow { - type Error = pyo3::PyErr; - - fn try_from(value: RefBoundObject<'_>) -> Result { - unsafe { - // Window statement - if pyo3::ffi::PyObject_TypeCheck(value.as_ptr(), crate::typeref::WINDOW_STATEMENT_TYPE) - == 1 - { - Ok(Self::Query(value.clone().unbind())) - } - // String - else if pyo3::ffi::PyUnicode_CheckExact(value.as_ptr()) == 1 { - let window_name = value.extract::().unwrap_unchecked(); - Ok(Self::Name(sea_query::Alias::new(window_name).into_iden())) - } - // Other types - else { - crate::new_error!( - PyTypeError, - "expected WindowStatement or str for SelectLabel window, got {}", - crate::internal::get_type_name(value.py(), value.as_ptr()) - ) - } - } - } -} - -#[inline] -pub fn cast_into_select_label<'a>(value: BoundObject<'a>) -> pyo3::PyResult> { - unsafe { - // SelectLabel itself - if pyo3::ffi::PyObject_TypeCheck(value.as_ptr(), crate::typeref::SELECT_LABEL_TYPE) == 1 { - return Ok(value); - } - - let state = SelectLabelState { - expr: PyExpr::try_from(&value)?, - alias: None, - window: None, - }; - let result: PySelectLabel = state.into(); - pyo3::Bound::new(value.py(), result).map(|x| x.into_any()) - } -} - -#[inline] -fn map_str_to_join_type(value: Option) -> pyo3::PyResult { - match value.map(|x| x.to_ascii_uppercase()) { - Some(x) => match x.as_str() { - "CROSS" => Ok(sea_query::JoinType::CrossJoin), - "FULL" => Ok(sea_query::JoinType::FullOuterJoin), - "INNER" => Ok(sea_query::JoinType::InnerJoin), - "LEFT" => Ok(sea_query::JoinType::LeftJoin), - "RIGHT" => Ok(sea_query::JoinType::RightJoin), - _ => { - crate::new_error!( - PyValueError, - "acceptable join types are 'CROSS', 'FULL', 'INNER', 'RIGHT', and 'LEFT'. got \ - {}", - x - ) - } - }, - None => Ok(sea_query::JoinType::Join), - } -} - -impl SelectReference { - fn from_table(value: RefBoundObject<'_>) -> pyo3::PyResult { - let table = PyTableName::try_from(value)?; - Ok(Self::TableName(table)) - } - - fn from_subquery(value: RefBoundObject<'_>, alias: String) -> pyo3::PyResult { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(value.as_ptr(), crate::typeref::SELECT_STATEMENT_TYPE) - == 0 - { - return crate::new_error!( - PyValueError, - "expected SelectStatement, got {}", - crate::internal::get_type_name(value.py(), value.as_ptr()) - ); - } - } - let alias = sea_query::Alias::new(alias).into_iden(); - - Ok(Self::SubQuery(value.clone().unbind(), alias)) - } - - fn from_function(value: RefBoundObject<'_>, alias: String) -> pyo3::PyResult { - let function = unsafe { - let type_ptr = pyo3::ffi::Py_TYPE(value.as_ptr()); - - // Func type - if type_ptr == crate::typeref::FUNC_TYPE { - value.cast_unchecked::().get().clone() - } - // Expr type - else if type_ptr == crate::typeref::EXPR_TYPE { - let casted_expr = value.cast_unchecked::(); - let get_casted_expr = casted_expr.get(); - - if let sea_query::SimpleExpr::FunctionCall(x) = &get_casted_expr.0 { - PyFunc(x.clone()) - } else { - return crate::new_error!(PyValueError, "given Expr is not a function call"); - } - } - // Other types - else { - return crate::new_error!( - PyValueError, - "expected Func or Expr, got {}", - crate::internal::get_type_name(value.py(), value.as_ptr()) - ); - } - }; - let alias = sea_query::Alias::new(alias).into_iden(); - - Ok(Self::Func(function, alias)) - } -} - -impl std::fmt::Display for SelectReference { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::TableName(x) => write!(f, "{}", x.__repr__()), - Self::Func(x, alias) => write!(f, "({}, {})", x.__repr__(), alias.to_string()), - Self::SubQuery(x, alias) => write!(f, "({}, {})", x, alias.to_string()), - } - } -} - -impl std::fmt::Display for JoinMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("(")?; - - match self.r#type { - sea_query::JoinType::Join => (), - sea_query::JoinType::CrossJoin => write!(f, "CROSS, ")?, - sea_query::JoinType::FullOuterJoin => write!(f, "FULL, ")?, - sea_query::JoinType::InnerJoin => write!(f, "INNER, ")?, - sea_query::JoinType::LeftJoin => write!(f, "LEFT, ")?, - sea_query::JoinType::RightJoin => write!(f, "RIGHT, ")?, - } - - if let Some(on) = &self.on { - write!(f, "{}, {}", self.reference, on.__repr__())?; - } else { - write!(f, "{}", self.reference)?; - } - - if self.lateral { - write!(f, ", LATERAL")?; - } - - f.write_str(")") - } -} - -impl std::fmt::Display for LockMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "(")?; - match self.r#type { - sea_query::LockType::KeyShare => write!(f, "KEY SHARE")?, - sea_query::LockType::NoKeyUpdate => write!(f, "NO KEY UPDATE")?, - sea_query::LockType::Share => write!(f, "SHARE")?, - sea_query::LockType::Update => write!(f, "UPDATE")?, - } - - if let Some(x) = self.behavior { - match x { - sea_query::LockBehavior::Nowait => write!(f, ", NOWAIT")?, - sea_query::LockBehavior::SkipLocked => write!(f, ", SKIP")?, - } - } - - if self.tables.is_empty() { - return write!(f, ")"); - } - - write!(f, ", [")?; - for (index, item) in self.tables.iter().enumerate() { - if index == 0 { - write!(f, "{}", item.__repr__())?; - } else { - write!(f, ", {}", item.__repr__())?; - } - } - write!(f, "])") - } -} - -impl ToSeaQuery for SelectLabelWindow { - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::WindowSelectType { - match self { - Self::Name(x) => sea_query::WindowSelectType::Name(x.clone()), - SelectLabelWindow::Query(x) => { - let statement = unsafe { x.cast_bound_unchecked::(py) }; - let lock = statement.get().0.lock(); - - sea_query::WindowSelectType::Query(lock.to_sea_query(py)) - } - } - } -} - -impl ToSeaQuery for SelectLabelState { - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::SelectExpr { - if self.alias.is_none() { - // If expr is column, setting alias can optimize the SQL query. - let default_alias = { - match &self.expr.0 { - sea_query::SimpleExpr::Column(sea_query::ColumnRef::Column(name)) => { - Some(name.clone()) - } - sea_query::SimpleExpr::Column(sea_query::ColumnRef::TableColumn(_, name)) => { - Some(name.clone()) - } - sea_query::SimpleExpr::Column(sea_query::ColumnRef::SchemaTableColumn( - _, - _, - name, - )) => Some(name.clone()), - _ => None, - } - }; - - sea_query::SelectExpr { - expr: self.expr.0.clone(), - alias: default_alias, - window: self.window.as_ref().map(|x| x.to_sea_query(py)), - } - } else { - sea_query::SelectExpr { - expr: self.expr.0.clone(), - alias: self - .alias - .as_ref() - .map(|x| sea_query::Alias::new(x).into_iden()), - - window: self.window.as_ref().map(|x| x.to_sea_query(py)), - } - } - } -} - -impl ToSeaQuery for SelectStatementState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::SelectStatement { - let mut stmt = sea_query::SelectStatement::new(); - - // Distinct mode - match &self.distinct { - DistinctMode::None => (), - DistinctMode::Distinct => { - stmt.distinct(); - } - DistinctMode::DistinctOn(cols) => { - stmt.distinct_on(cols.clone()); - } - } - - // References - for table in self.references.iter() { - match table { - SelectReference::TableName(x) => unsafe { - stmt.from(x.clone()); - }, - SelectReference::Func(x, alias) => unsafe { - stmt.from_function(x.0.clone(), alias.clone()); - }, - SelectReference::SubQuery(x, alias) => unsafe { - let x = unsafe { x.cast_bound_unchecked::(py) }; - let lock = x.get().0.lock(); - - stmt.from_subquery(lock.to_sea_query(py), alias.clone()); - }, - } - } - - // Columns - stmt.exprs(self.exprs.iter().map(|x| unsafe { - let expr = x.cast_bound_unchecked::(py); - expr.get().0.as_ref().to_sea_query(py) - })); - - // Groups - stmt.add_group_by(self.groups.iter().map(|x| x.0.clone())); - - // Condition - stmt.and_where_option(self.r#where.as_ref().map(|x| x.0.clone())); - - // Having - if let Some(x) = &self.having { - stmt.and_having(x.0.clone()); - } - - // Limit & Offset - if let Some(n) = self.limit { - stmt.limit(n); - } - if let Some(n) = self.offset { - stmt.offset(n); - } - - // Orders - for order in self.orders.iter() { - if let Some(x) = order.null_order { - stmt.order_by_expr_with_nulls(order.target.0.clone(), order.order.clone(), x); - } else { - stmt.order_by_expr(order.target.0.clone(), order.order.clone()); - } - } - - // Lock mode - if let Some(lock) = &self.lock { - match (lock.behavior, lock.tables.is_empty()) { - (Some(behavior), false) => { - stmt.lock_with_tables_behavior( - lock.r#type, - lock.tables.iter().cloned(), - behavior, - ); - } - (Some(behavior), true) => { - stmt.lock_with_behavior(lock.r#type, behavior); - } - (None, false) => { - stmt.lock_with_tables(lock.r#type, lock.tables.iter().cloned()); - } - (None, true) => { - stmt.lock(lock.r#type); - } - } - } - - // Unions - stmt.unions(self.unions.iter().map(|(union_type, union_stmt)| { - let union_stmt = unsafe { union_stmt.cast_bound_unchecked::(py) }; - let union_lock = union_stmt.get().0.lock(); - - (*union_type, union_lock.to_sea_query(py)) - })); - - // Joins - for join in self.joins.iter() { - let condition = - sea_query::Condition::all().add_option(join.on.as_ref().map(|x| x.0.clone())); - - match (&join.reference, join.lateral) { - (SelectReference::TableName(x), _) => { - stmt.join(join.r#type, x.clone(), condition); - } - (SelectReference::Func(func, alias), _) => { - stmt.join( - join.r#type, - sea_query::TableRef::FunctionCall(func.0.clone(), alias.clone()), - condition, - ); - } - (SelectReference::SubQuery(subquery, alias), true) => { - let subquery_stmt = - unsafe { subquery.cast_bound_unchecked::(py) }; - let subquery_lock = subquery_stmt.get().0.lock(); - - stmt.join_lateral( - join.r#type, - subquery_lock.to_sea_query(py), - alias.clone(), - condition, - ); - } - (SelectReference::SubQuery(subquery, alias), false) => { - let subquery_stmt = - unsafe { subquery.cast_bound_unchecked::(py) }; - let subquery_lock = subquery_stmt.get().0.lock(); - - stmt.join_subquery( - join.r#type, - subquery_lock.to_sea_query(py), - alias.clone(), - condition, - ); - } - } - } - - // Window - if let Some((window_name, window)) = &self.window { - let window = unsafe { window.cast_bound_unchecked::(py) }; - let lock = window.get().0.lock(); - - stmt.window(window_name.clone(), lock.to_sea_query(py)); - } - - stmt - } -} - -#[pyo3::pymethods] -impl PySelectLabel { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> Self { - Self::uninit() - } - - #[pyo3(signature=(expr, alias=None, window=None))] - fn __init__( - &self, - expr: RefBoundObject<'_>, - alias: Option, - window: Option>, - ) -> pyo3::PyResult<()> { - let window = match window { - Some(x) => Some(SelectLabelWindow::try_from(x)?), - None => None, - }; - let expr = PyExpr::try_from(expr)?; - - let state = SelectLabelState { - expr, - alias, - window, - }; - unsafe { - self.0.set(state); - } - Ok(()) - } - - #[getter] - fn expr(&self) -> PyExpr { - self.0.as_ref().expr.clone() - } - - #[getter] - fn alias(&self) -> Option { - self.0.as_ref().alias.clone() - } - - #[getter] - fn window<'a>(&self, py: pyo3::Python<'a>) -> Option> { - use pyo3::IntoPyObjectExt; - - let inner = self.0.as_ref(); - - match &inner.window { - Some(ref select_window) => match select_window { - SelectLabelWindow::Name(name) => { - Some(name.to_string().into_bound_py_any(py).unwrap()) - } - SelectLabelWindow::Query(w) => Some(w.bind(py).clone()), - }, - None => None, - } - } - - fn __copy__(&self, py: pyo3::Python<'_>) -> Self { - let lock = self.0.as_ref(); - lock.clone_ref(py).into() - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let inner = slf.0.as_ref(); - - let mut fmt = ReprFormatter::new_with_pyref(&slf) - .map("expr", &inner.expr, |x| x.__repr__()) - .optional_quote("alias", inner.alias.as_ref()) - .take(); - - match &inner.window { - Some(SelectLabelWindow::Name(x)) => { - fmt.iden("window", x); - } - Some(SelectLabelWindow::Query(x)) => { - fmt.display("window", x); - } - None => {} - } - - fmt.finish() - } -} - -#[pyo3::pymethods] -impl PySelectStatement { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PyQueryStatement) { - (Self::uninit(), PyQueryStatement) - } - - #[pyo3(signature=(*exprs))] - pub fn __init__(&self, exprs: BoundArgs<'_>) -> pyo3::PyResult<()> { - let mut casted = Vec::new(); - for item in exprs.iter() { - casted.push(cast_into_select_label(item)?.unbind()); - } - - let state = SelectStatementState { - exprs: casted, - ..Default::default() - }; - self.0.set(state); - Ok(()) - } - - #[pyo3(signature=(*on))] - fn distinct<'a>( - slf: pyo3::PyRef<'a, Self>, - on: BoundArgs<'a>, - ) -> pyo3::PyResult> { - if on.is_empty() { - // Distinct mode without specific column - slf.0.lock().distinct = DistinctMode::Distinct; - } else { - let mut cols = Vec::new(); - - for item in on.iter() { - cols.push(PyColumnRef::try_from(&item)?); - } - - slf.0.lock().distinct = DistinctMode::DistinctOn(cols); - } - - Ok(slf) - } - - #[pyo3(signature=(*args))] - fn columns<'a>( - slf: pyo3::PyRef<'a, Self>, - args: BoundArgs<'a>, - ) -> pyo3::PyResult> { - let mut casted = Vec::new(); - for item in args.iter() { - let column_ref = PyColumnRef::try_from(&item)?; - - let state = SelectLabelState { - expr: PyExpr(sea_query::SimpleExpr::Column(column_ref.into_column_ref())), - alias: None, - window: None, - }; - let result: PySelectLabel = state.into(); - - casted.push(pyo3::Py::new(slf.py(), result)?.into_any()); - } - - slf.0.lock().exprs.append(&mut casted); - Ok(slf) - } - - #[pyo3(signature=(*args))] - fn exprs<'a>( - slf: pyo3::PyRef<'a, Self>, - args: BoundArgs<'a>, - ) -> pyo3::PyResult> { - let mut casted = Vec::new(); - for item in args.iter() { - casted.push(cast_into_select_label(item)?.unbind()); - } - - slf.0.lock().exprs.append(&mut casted); - Ok(slf) - } - - #[allow(clippy::wrong_self_convention)] - fn from_table<'a>( - slf: pyo3::PyRef<'a, Self>, - table: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - let reference = SelectReference::from_table(table)?; - - slf.0.lock().references.push(reference); - Ok(slf) - } - - #[allow(clippy::wrong_self_convention)] - fn from_subquery<'a>( - slf: pyo3::PyRef<'a, Self>, - subquery: RefBoundObject<'a>, - alias: String, - ) -> pyo3::PyResult> { - if subquery.as_ptr() == slf.as_ptr() { - return crate::new_error!(PyValueError, "select statement cannot select from itself!"); - } - - let reference = SelectReference::from_subquery(subquery, alias)?; - - slf.0.lock().references.push(reference); - Ok(slf) - } - - #[allow(clippy::wrong_self_convention)] - fn from_function<'a>( - slf: pyo3::PyRef<'a, Self>, - function: RefBoundObject<'a>, - alias: String, - ) -> pyo3::PyResult> { - let reference = SelectReference::from_function(function, alias)?; - - slf.0.lock().references.push(reference); - Ok(slf) - } - - fn limit(slf: pyo3::PyRef<'_, Self>, n: u64) -> pyo3::PyRef<'_, Self> { - slf.0.lock().limit = Some(n); - slf - } - - fn offset(slf: pyo3::PyRef<'_, Self>, n: u64) -> pyo3::PyRef<'_, Self> { - slf.0.lock().offset = Some(n); - slf - } - - fn r#where<'a>( - slf: pyo3::PyRef<'a, Self>, - condition: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::Py_TYPE(condition.as_ptr()) != crate::typeref::EXPR_TYPE { - return crate::new_error!( - PyTypeError, - "expected Expr, got {}", - crate::internal::get_type_name(condition.py(), condition.as_ptr()) - ); - } - - let condition = condition.cast_unchecked::().get().clone(); - let mut lock = slf.0.lock(); - - match std::mem::take(&mut lock.r#where) { - None => { - lock.r#where = Some(condition); - } - Some(x) => { - lock.r#where = Some(PyExpr(x.0.and(condition.0))); - } - } - } - - Ok(slf) - } - - /// Remove where conditions from statement. - fn clear_where(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { - slf.0.lock().r#where = None; - slf - } - - /// Specify the order in which to delete rows. - #[pyo3(signature=(clause))] - fn order_by<'a>( - slf: pyo3::PyRef<'a, Self>, - clause: pyo3::Bound<'_, PyOrdering>, - ) -> pyo3::PyResult> { - { - let mut lock = slf.0.lock(); - lock.orders.push(clause.get().clone()); - } - - Ok(slf) - } - - /// Remove orders from statement. - fn clear_order_by(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { - slf.0.lock().orders.clear(); - slf - } - - fn having<'a>( - slf: pyo3::PyRef<'a, Self>, - condition: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::Py_TYPE(condition.as_ptr()) != crate::typeref::EXPR_TYPE { - return crate::new_error!( - PyTypeError, - "expected Expr, got {}", - crate::internal::get_type_name(condition.py(), condition.as_ptr()) - ); - } - - let condition = condition.cast_unchecked::().get().clone(); - let mut lock = slf.0.lock(); - - match std::mem::take(&mut lock.having) { - None => { - lock.having = Some(condition); - } - Some(x) => { - lock.having = Some(PyExpr(x.0.and(condition.0))); - } - } - } - - Ok(slf) - } - - #[pyo3(signature=(r#type=String::from("UPDATE"), behavior=None, tables=Vec::new()))] - fn lock( - slf: pyo3::PyRef<'_, Self>, - r#type: String, - behavior: Option, - tables: Vec, - ) -> pyo3::PyResult> { - let lock_type = match r#type.to_ascii_uppercase().as_str() { - "UPDATE" => sea_query::LockType::Update, - "NO KEY UPDATE" => sea_query::LockType::NoKeyUpdate, - "SHARE" => sea_query::LockType::Share, - "KEY SHARE" => sea_query::LockType::KeyShare, - _ => { - return crate::new_error!( - PyValueError, - "acceptable lock types are 'UPDATE', 'NO KEY UPDATE', 'SHARE', and 'KEY \ - SHARE'. got {}", - r#type - ) - } - }; - let lock_behavior = match behavior.map(|x| x.to_ascii_uppercase()) { - Some(x) => match x.as_str() { - "NOWAIT" => Some(sea_query::LockBehavior::Nowait), - "SKIP" => Some(sea_query::LockBehavior::SkipLocked), - _ => { - return crate::new_error!( - PyValueError, - "acceptable lock behaviors are 'NOWAIT', and 'SKIP'. got {}", - x - ) - } - }, - None => None, - }; - - let mut tbs = Vec::with_capacity(tables.len()); - - for table in tables.into_iter() { - tbs.push(PyTableName::try_from(table.bind(slf.py()))?); - } - - slf.0.lock().lock = Some(LockMode { - r#type: lock_type, - behavior: lock_behavior, - tables: tbs, - }); - Ok(slf) - } - - #[pyo3(signature=(*groups))] - fn group_by<'a>( - slf: pyo3::PyRef<'a, Self>, - groups: BoundArgs<'a>, - ) -> pyo3::PyResult> { - let mut exprs = Vec::with_capacity(groups.len()); - - for expr in groups.iter() { - let target = unsafe { - if pyo3::ffi::Py_TYPE(expr.as_ptr()) == crate::typeref::EXPR_TYPE { - expr.cast_unchecked::().get().clone() - } else { - let column_ref = PyColumnRef::try_from(&expr)?; - - PyExpr(sea_query::SimpleExpr::Column(column_ref.into_column_ref())) - } - }; - - exprs.push(target); - } - - slf.0.lock().groups.append(&mut exprs); - Ok(slf) - } - - #[pyo3(signature=(statement, r#type=String::from("DISTINCT")))] - fn union<'a>( - slf: pyo3::PyRef<'a, Self>, - statement: BoundObject<'a>, - r#type: String, - ) -> pyo3::PyResult> { - unsafe { - if statement.as_ptr() == slf.as_ptr() { - return crate::new_error!( - PyValueError, - "select statement cannot select from itself!" - ); - } - - if pyo3::ffi::PyObject_TypeCheck( - statement.as_ptr(), - crate::typeref::SELECT_STATEMENT_TYPE, - ) == 0 - { - return crate::new_error!( - PyValueError, - "expected SelectStatement, got {}", - crate::internal::get_type_name(statement.py(), statement.as_ptr()) - ); - } - } - - let union_type = match r#type.to_ascii_uppercase().as_str() { - "ALL" => sea_query::UnionType::All, - "INTERSECT" => sea_query::UnionType::Intersect, - "DISTINCT" => sea_query::UnionType::Distinct, - "EXCEPT" => sea_query::UnionType::Except, - _ => { - return crate::new_error!( - PyValueError, - "acceptable union types are 'ALL', 'INTERSECT', 'DISTINCT', and 'EXCEPT'. got \ - {}", - r#type - ) - } - }; - - slf.0.lock().unions.push((union_type, statement.unbind())); - Ok(slf) - } - - #[pyo3(signature=(table, on=None, r#type=None))] - fn join<'a>( - slf: pyo3::PyRef<'a, Self>, - table: RefBoundObject<'a>, - on: Option>, - r#type: Option, - ) -> pyo3::PyResult> { - let reference = SelectReference::from_table(table)?; - let join_mode = JoinMode { - r#type: map_str_to_join_type(r#type)?, - reference, - on: match on { - Some(x) => Some(PyExpr::try_from(x)?), - None => None, - }, - lateral: false, - }; - - slf.0.lock().joins.push(join_mode); - Ok(slf) - } - - #[pyo3(signature=(function, alias, on=None, r#type=None))] - fn join_function<'a>( - slf: pyo3::PyRef<'a, Self>, - function: RefBoundObject<'a>, - alias: String, - on: Option>, - r#type: Option, - ) -> pyo3::PyResult> { - let reference = SelectReference::from_function(function, alias)?; - - let join_mode = JoinMode { - r#type: map_str_to_join_type(r#type)?, - reference, - on: match on { - Some(x) => Some(PyExpr::try_from(x)?), - None => None, - }, - lateral: false, - }; - - slf.0.lock().joins.push(join_mode); - Ok(slf) - } - - #[pyo3(signature=(subquery, alias, on=None, r#type=None, lateral=false))] - fn join_subquery<'a>( - slf: pyo3::PyRef<'a, Self>, - subquery: RefBoundObject<'a>, - alias: String, - on: Option>, - r#type: Option, - lateral: bool, - ) -> pyo3::PyResult> { - if subquery.as_ptr() == slf.as_ptr() { - return crate::new_error!(PyValueError, "select statement cannot select from itself!"); - } - - let reference = SelectReference::from_subquery(subquery, alias)?; - - let join_mode = JoinMode { - r#type: map_str_to_join_type(r#type)?, - reference, - on: match on { - Some(x) => Some(PyExpr::try_from(x)?), - None => None, - }, - lateral, - }; - slf.0.lock().joins.push(join_mode); - Ok(slf) - } - - #[pyo3(signature=(name, statement))] - fn window<'a>( - slf: pyo3::PyRef<'a, Self>, - name: String, - statement: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::PyObject_TypeCheck( - statement.as_ptr(), - crate::typeref::WINDOW_STATEMENT_TYPE, - ) == 0 - { - return crate::new_error!( - PyTypeError, - "expected WindowStatement, got {}", - crate::internal::get_type_name(statement.py(), statement.as_ptr()) - ); - } - } - - slf.0.lock().window = Some(( - sea_query::Alias::new(name).into_iden(), - statement.clone().unbind(), - )); - Ok(slf) - } - - /// Shorthand for `Expr(self)` - fn to_expr(&self, py: pyo3::Python) -> PyExpr { - let stmt = self.0.lock().to_sea_query(py); - let subquery = sea_query::SubQueryStatement::SelectStatement(stmt); - PyExpr(sea_query::SimpleExpr::SubQuery(None, Box::new(subquery))) - } - - /// Shorthand for `SelectLabel(self, alias, window)` - #[pyo3(signature=(alias, window=None))] - fn label( - &self, - py: pyo3::Python, - alias: String, - window: Option>, - ) -> pyo3::PyResult { - let window = match window { - Some(x) => Some(crate::query::select::SelectLabelWindow::try_from(x)?), - None => None, - }; - let expr = self.to_expr(py); - - let state = crate::query::select::SelectLabelState { - expr, - alias: Some(alias), - window, - }; - Ok(state.into()) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_query_statement!(backend, stmt) - } - - #[pyo3(signature = (backend, /))] - fn build<'a>( - &self, - py: pyo3::Python<'a>, - backend: String, - ) -> pyo3::PyResult<(String, BoundObject<'a>)> { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_query_parts!(py, backend, stmt) - } - - fn __copy__<'a>(&self, py: pyo3::Python<'a>) -> pyo3::PyResult> { - let lock = self.0.lock(); - pyo3::Bound::new(py, (lock.clone_ref(py).into(), PyQueryStatement)) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - let mut fmt = ReprFormatter::new_with_pyref(&slf); - - match &lock.distinct { - DistinctMode::None => (), - DistinctMode::Distinct => { - fmt.pair("distinct", "true"); - } - DistinctMode::DistinctOn(x) => { - fmt.vec("distinct", false) - .display_iter(x.iter().map(|x| x.__repr__())) - .finish(&mut fmt); - } - } - - fmt.vec("references", false) - .display_iter(lock.references.iter()) - .finish(&mut fmt); - - fmt.vec("exprs", false) - .display_iter(lock.exprs.iter()) - .finish(&mut fmt); - - fmt.optional_map("where", lock.r#where.as_ref(), |x| x.__repr__()); - - fmt.vec("groups", true) - .display_iter(lock.groups.iter().map(|x| x.__repr__())) - .finish(&mut fmt); - - fmt.optional_map("having", lock.having.as_ref(), |x| x.__repr__()); - - fmt.vec("orders", true) - .display_iter(lock.orders.iter().map(|x| x.__repr__())) - .finish(&mut fmt); - - fmt.vec("joins", true) - .display_iter(lock.joins.iter()) - .finish(&mut fmt); - - fmt.optional_display("lock", lock.lock.as_ref()) - .optional_display("offset", lock.offset) - .optional_display("limit", lock.limit) - .optional_map("window", lock.window.as_ref(), |(name, stmt)| { - format!("('{}', {})", name.to_string(), stmt) - }); - - fmt.vec("unions", true) - .display_iter( - lock.unions - .iter() - .map(|(union_type, stmt)| match union_type { - sea_query::UnionType::All => format!("(ALL, {})", stmt), - sea_query::UnionType::Distinct => format!("(DISTINCT, {})", stmt), - sea_query::UnionType::Except => format!("(EXCEPT, {})", stmt), - sea_query::UnionType::Intersect => format!("(INTERSECT, {})", stmt), - }), - ) - .finish(&mut fmt); - - fmt.finish() - } -} diff --git a/src/query/update.rs b/src/query/update.rs deleted file mode 100644 index cf46b9f..0000000 --- a/src/query/update.rs +++ /dev/null @@ -1,289 +0,0 @@ -use sea_query::IntoIden; - -use super::base::PyQueryStatement; -use super::ordering::PyOrdering; -use super::returning::PyReturning; -use crate::common::expression::PyExpr; -use crate::common::table_ref::PyTableName; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, RefBoundObject, ToSeaQuery}; - -crate::implement_pyclass! { - /// Builds UPDATE SQL statements with a fluent interface. - /// - /// Provides a chainable API for constructing UPDATE queries with support for: - /// - Setting column values - /// - WHERE conditions for filtering - /// - LIMIT for restricting update count - /// - ORDER BY for determining update order - /// - RETURNING clauses for getting updated data - #[derive(Clone)] - mutable [subclass, extends=PyQueryStatement] PyUpdateStatement(UpdateStatementState) as "UpdateStatement" { - pub table: PyTableName, - pub from_table: Option, - pub values: Vec<(sea_query::DynIden, PyExpr)>, - pub r#where: Option, - pub limit: Option, - pub returning_clause: Option, - pub orders: Vec, - } -} - -impl ToSeaQuery for UpdateStatementState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::UpdateStatement { - let mut stmt = sea_query::UpdateStatement::new(); - stmt.table(self.table.clone()); - - if let Some(x) = &self.from_table { - stmt.from(x.clone()); - } - stmt.values(self.values.iter().map(|(x, y)| (x.clone(), y.0.clone()))); - - if let Some(x) = &self.r#where { - stmt.and_where(x.0.clone()); - } - if let Some(x) = self.limit { - stmt.limit(x); - } - if let Some(x) = &self.returning_clause { - stmt.returning(x.0.to_sea_query(py)); - } - - for order in self.orders.iter() { - if let Some(x) = order.null_order { - stmt.order_by_expr_with_nulls(order.target.0.clone(), order.order.clone(), x); - } else { - stmt.order_by_expr(order.target.0.clone(), order.order.clone()); - } - } - - stmt - } -} - -#[pyo3::pymethods] -impl PyUpdateStatement { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PyQueryStatement) { - (Self::uninit(), PyQueryStatement) - } - - pub fn __init__(&self, table: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let table = PyTableName::try_from(table)?; - - let state = UpdateStatementState { - table, - from_table: None, - values: vec![], - r#where: None, - limit: None, - returning_clause: None, - orders: vec![], - }; - self.0.set(state); - Ok(()) - } - - /// Specify the table to update. - fn table<'a>( - slf: pyo3::PyRef<'a, Self>, - table: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - let table = PyTableName::try_from(table)?; - - { - let mut lock = slf.0.lock(); - lock.table = table; - } - Ok(slf) - } - - /// Update using data from another table (`UPDATE .. FROM ..`). - /// - /// MySQL doesn't support the UPDATE FROM syntax. And the current implementation attempt to - /// tranform it to the UPDATE JOIN syntax, which only works for one join target. - #[allow(clippy::wrong_self_convention)] - fn from_table<'a>( - slf: pyo3::PyRef<'a, Self>, - table: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - let table = PyTableName::try_from(table)?; - - { - let mut lock = slf.0.lock(); - lock.from_table = Some(table); - } - Ok(slf) - } - - /// Limit the number of rows to update. - fn limit(slf: pyo3::PyRef<'_, Self>, n: u64) -> pyo3::PyRef<'_, Self> { - { - let mut lock = slf.0.lock(); - lock.limit = Some(n); - } - - slf - } - - /// Specify columns to return from the inserted rows. - #[pyo3(signature=(clause))] - fn returning<'a>( - slf: pyo3::PyRef<'a, Self>, - clause: pyo3::Bound<'_, PyReturning>, - ) -> pyo3::PyResult> { - { - let mut lock = slf.0.lock(); - lock.returning_clause = Some(clause.get().clone()); - } - Ok(slf) - } - - /// Add a WHERE condition to filter rows to update. - fn r#where<'a>( - slf: pyo3::PyRef<'a, Self>, - condition: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::Py_TYPE(condition.as_ptr()) != crate::typeref::EXPR_TYPE { - return crate::new_error!( - PyTypeError, - "expected Expr, got {}", - crate::internal::get_type_name(condition.py(), condition.as_ptr()) - ); - } - - let condition = condition.cast_unchecked::().get().clone(); - let mut lock = slf.0.lock(); - - match std::mem::take(&mut lock.r#where) { - None => { - lock.r#where = Some(condition); - } - Some(x) => { - lock.r#where = Some(PyExpr(x.0.and(condition.0))); - } - } - } - - Ok(slf) - } - - /// Remove where conditions from statement. - fn clear_where(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { - slf.0.lock().r#where = None; - slf - } - - /// Specify the order in which to delete rows. Typically used with - /// `.limit` method to delete specific rows. - #[pyo3(signature=(clause))] - fn order_by<'a>( - slf: pyo3::PyRef<'a, Self>, - clause: pyo3::Bound<'_, PyOrdering>, - ) -> pyo3::PyResult> { - { - let mut lock = slf.0.lock(); - lock.orders.push(clause.get().clone()); - } - - Ok(slf) - } - - /// Remove orders from statement. - fn clear_order_by(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> { - slf.0.lock().orders.clear(); - slf - } - - /// Specify columns and their new values. - #[pyo3(signature=(**kwds))] - fn values<'a>( - slf: pyo3::PyRef<'a, Self>, - kwds: Option>, - ) -> pyo3::PyResult> { - use pyo3::types::{PyAnyMethods, PyDictMethods}; - - if kwds.is_none() { - return Ok(slf); - } - - let kwds = unsafe { kwds.unwrap_unchecked() }; - let mut values = Vec::with_capacity(kwds.len()); - - unsafe { - for (key, value) in kwds.iter() { - let key = key.extract::().unwrap_unchecked(); - values.push(( - (sea_query::Alias::new(key).into_iden()), - PyExpr::try_from(&value)?, - )); - } - } - - { - let mut lock = slf.0.lock(); - lock.values.append(&mut values); - } - Ok(slf) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_query_statement!(backend, stmt) - } - - #[pyo3(signature = (backend, /))] - fn build<'a>( - &self, - py: pyo3::Python<'a>, - backend: String, - ) -> pyo3::PyResult<(String, BoundObject<'a>)> { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_query_parts!(py, backend, stmt) - } - - fn __copy__<'a>(&self, py: pyo3::Python<'a>) -> pyo3::PyResult> { - let lock = self.0.lock(); - pyo3::Bound::new(py, (lock.clone().into(), PyQueryStatement)) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - let mut fmt = ReprFormatter::new_with_pyref(&slf) - .map("table", &lock.table, |x| x.__repr__()) - .optional_map("from_table", lock.from_table.as_ref(), |x| x.__repr__()) - .take(); - - fmt.vec("values", false) - .display_iter( - lock.values - .iter() - .map(|x| format!("('{}', {})", x.0.to_string(), x.1.__repr__())), - ) - .finish(&mut fmt); - - fmt.vec("orders", true) - .display_iter(lock.orders.iter().map(|x| x.__repr__())) - .finish(&mut fmt); - - fmt.optional_display("limit", lock.limit) - .optional_map("where", lock.r#where.as_ref(), |x| x.__repr__()) - .optional_map("returning", lock.returning_clause.as_ref(), |x| { - x.__repr__() - }) - .finish() - } -} diff --git a/src/query/window.rs b/src/query/window.rs deleted file mode 100644 index 8dd94b6..0000000 --- a/src/query/window.rs +++ /dev/null @@ -1,218 +0,0 @@ -use pyo3::types::PyTupleMethods; -use sea_query::{IntoColumnRef, OverStatement}; - -use super::ordering::PyOrdering; -use crate::common::column_ref::PyColumnRef; -use crate::common::expression::PyExpr; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, RefBoundObject, ToSeaQuery}; - -/// Frame clause -#[derive(Debug, Clone, PartialEq)] -pub struct FrameClause { - pub r#type: sea_query::FrameType, - pub start: sea_query::Frame, - pub end: Option, -} - -crate::implement_pyclass! { - /// Window frame start and frame end clause. Use its classmethods. - #[derive(Debug, Clone)] - [] PyFrame as "Frame" (pub sea_query::Frame); -} -crate::implement_pyclass! { - /// Window expression. - /// - /// # References: - /// - /// 1. - /// 2. - /// 3. - #[derive(Clone)] - mutable [subclass] PyWindowStatement(WindowStatementState) as "WindowStatement" { - pub partition_by: Vec, - pub orders: Vec, - pub frame: Option - } -} - -#[pyo3::pymethods] -impl PyFrame { - #[classmethod] - fn unbounded_preceding(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - Self(sea_query::Frame::UnboundedPreceding) - } - - #[classmethod] - fn current_row(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - Self(sea_query::Frame::CurrentRow) - } - - #[classmethod] - fn unbounded_following(_cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self { - Self(sea_query::Frame::UnboundedFollowing) - } - - #[classmethod] - fn following(_cls: &pyo3::Bound<'_, pyo3::types::PyType>, val: u32) -> Self { - Self(sea_query::Frame::Following(val)) - } - - #[classmethod] - fn preceding(_cls: &pyo3::Bound<'_, pyo3::types::PyType>, val: u32) -> Self { - Self(sea_query::Frame::Preceding(val)) - } -} - -impl ToSeaQuery for WindowStatementState { - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::WindowStatement { - let mut stmt = sea_query::WindowStatement::new(); - - for partition in &self.partition_by { - stmt.add_partition_by(partition.0.clone()); - } - - if let Some(x) = &self.frame { - stmt.frame(x.r#type.clone(), x.start.clone(), x.end.clone()); - } - - for order in self.orders.iter() { - if let Some(x) = order.null_order { - stmt.order_by_expr_with_nulls(order.target.0.clone(), order.order.clone(), x); - } else { - stmt.order_by_expr(order.target.0.clone(), order.order.clone()); - } - } - - stmt - } -} - -#[pyo3::pymethods] -impl PyWindowStatement { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> Self { - Self::uninit() - } - - #[pyo3(signature=(*partition_by))] - pub fn __init__(&self, partition_by: BoundArgs<'_>) -> pyo3::PyResult<()> { - let mut partitions = Vec::with_capacity(partition_by.len()); - - for partition in partition_by.iter() { - let partition = unsafe { - if pyo3::ffi::Py_TYPE(partition.as_ptr()) == crate::typeref::EXPR_TYPE { - partition.cast_unchecked::().get().clone() - } else { - let column_ref = PyColumnRef::try_from(&partition)?; - - PyExpr(sea_query::SimpleExpr::Column(column_ref.into_column_ref())) - } - }; - - partitions.push(partition); - } - - let state = WindowStatementState { - partition_by: partitions, - orders: Vec::new(), - frame: None, - }; - self.0.set(state); - Ok(()) - } - - /// Partition by column or custom expression. - fn partition<'a>( - slf: pyo3::PyRef<'a, Self>, - partition_by: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - let partition_by = unsafe { - if pyo3::ffi::Py_TYPE(partition_by.as_ptr()) == crate::typeref::EXPR_TYPE { - partition_by.cast_unchecked::().get().clone() - } else { - let column_ref = PyColumnRef::try_from(partition_by)?; - - PyExpr(sea_query::SimpleExpr::Column(column_ref.into_column_ref())) - } - }; - - { - let mut lock = slf.0.lock(); - lock.partition_by.push(partition_by); - } - Ok(slf) - } - - #[pyo3(signature=(clause))] - fn order_by<'a>( - slf: pyo3::PyRef<'a, Self>, - clause: pyo3::Bound<'_, PyOrdering>, - ) -> pyo3::PyResult> { - { - let mut lock = slf.0.lock(); - lock.orders.push(clause.get().clone()); - } - - Ok(slf) - } - - #[pyo3(signature=(frame_type, frame_start, frame_end=None))] - fn frame<'a>( - slf: pyo3::PyRef<'a, Self>, - frame_type: String, - frame_start: pyo3::Bound<'_, PyFrame>, - frame_end: Option>, - ) -> pyo3::PyResult> { - let frame_type = match frame_type.to_ascii_lowercase().as_str() { - "rows" => sea_query::FrameType::Rows, - "range" => sea_query::FrameType::Range, - _ => { - return Err(pyo3::exceptions::PyValueError::new_err(format!( - "invalid frame type, expected 'rows' or 'range'; got {:?}", - frame_type - ))); - } - }; - - { - let mut lock = slf.0.lock(); - lock.frame = Some(FrameClause { - r#type: frame_type, - start: frame_start.get().0.clone(), - end: frame_end.map(|x| x.get().0.clone()), - }) - } - Ok(slf) - } - - fn __copy__(&self) -> Self { - let lock = self.0.lock(); - lock.clone().into() - } - - pub fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - let mut fmt = ReprFormatter::new_with_pyref(&slf); - - fmt.vec("partition_by", true) - .display_iter(lock.partition_by.iter().map(|x| x.__repr__())) - .finish(&mut fmt); - - fmt.vec("orders", true) - .display_iter(lock.orders.iter().map(|x| x.__repr__())) - .finish(&mut fmt); - - fmt.optional_map("frame_type", lock.frame.as_ref(), |x| { - format!("{:?}", x.r#type) - }) - .optional_map("frame_start", lock.frame.as_ref(), |x| { - format!("{:?}", x.start) - }) - .optional_map("frame_end", lock.frame.as_ref(), |x| format!("{:?}", x.end)) - .finish() - } -} diff --git a/src/query/with.rs b/src/query/with.rs deleted file mode 100644 index 4a8c5b1..0000000 --- a/src/query/with.rs +++ /dev/null @@ -1,522 +0,0 @@ -use sea_query::IntoIden; - -use crate::common::expression::PyExpr; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, PyObject, RefBoundObject, ToSeaQuery}; -use crate::new_error; -use crate::query::base::PyQueryStatement; - -pub enum CommonTableExpressionQuery { - Select( - /// Always is `SelectStatement` - PyObject, - ), - Delete( - /// Always is `DeleteStatement` - PyObject, - ), - Update( - /// Always is `UpdateStatement` - PyObject, - ), - Insert( - /// Always is `InsertStatement` - PyObject, - ), -} - -pub struct CommonTableExpression { - pub name: String, - pub query: CommonTableExpressionQuery, - pub columns: Vec, - pub materialized: Option, -} - -crate::implement_pyclass! { - /// A WITH clause can contain one or multiple common table expressions (CTEs). - /// - /// These named queries can act as a "query local table" that are materialized during execution and - /// then can be used by the query prefixed with the WITH clause. - /// - /// A CTE is a name, column names and a query returning data for those columns. - /// - /// Some databases (like sqlite) restrict the acceptable kinds of queries inside of the WITH clause - /// CTEs. These databases only allow `SelectStatement`s to form a CTE. - /// - /// Other databases like postgres allow modification queries (UPDATE, DELETE) inside of the WITH - /// clause but they have to return a table. (They must have a RETURNING clause). - /// - /// RapidQuery doesn't check this or restrict the kind of CTE that you can create - /// in rust. This means that you can put an UPDATE or DELETE queries into WITH clause and RapidQuery - /// will succeed in generating that kind of sql query but the execution inside the database will - /// fail because they are invalid. - /// - /// It is your responsibility to ensure that the kind of WITH clause that you put together makes - /// sense and valid for that database that you are using. - /// - /// NOTE that for recursive WITH queries (in sql: "WITH RECURSIVE") you can only have a - /// single CTE inside of the WITH clause. That query must match certain - /// requirements: - /// * It is a query of UNION or UNION ALL of two queries. - /// * The first part of the query (the left side of the UNION) must be executable first in itself. - /// It must be non-recursive. (Cannot contain self reference) - /// * The self reference must appear in the right hand side of the UNION. - /// * The query can only have a single self-reference. - /// * Recursive data-modifying statements are not supported, but you can use the results of a - /// recursive SELECT query in a data-modifying statement. (like so: WITH RECURSIVE - /// cte_name(a,b,c,d) AS (SELECT ... UNION SELECT ... FROM ... JOIN cte_name ON ... WHERE ...) - /// DELETE FROM table WHERE table.a = cte_name.a) - /// - /// Recursive with query generation will raise `RuntimeError` if you specify more than one CTE. - mutable [subclass] PyWithClause(WithClauseState) as "WithClause" { - pub recursive: bool, - pub cte_expressions: Vec, - pub cycle: Option, - pub search: Option, - } -} -crate::implement_pyclass! { - /// A WITH query. A simple SQL query that has a WITH clause (`WithClause`). - /// - /// For full description, see `WithClause`'s documentation. - mutable [subclass, extends=PyQueryStatement] PyWithQuery(WithQueryState) as "WithQuery" { - /// Always is `PyWithClause` - pub with_clause: PyObject, - pub query: CommonTableExpressionQuery, - } -} - -impl TryFrom> for CommonTableExpressionQuery { - type Error = pyo3::PyErr; - - fn try_from(value: RefBoundObject) -> Result { - unsafe { - let value_ptr = value.as_ptr(); - - // SelectStatement - if pyo3::ffi::PyObject_TypeCheck(value_ptr, crate::typeref::SELECT_STATEMENT_TYPE) == 1 - { - Ok(Self::Select(value.clone().unbind())) - } - // DeleteStatement - else if pyo3::ffi::PyObject_TypeCheck( - value_ptr, - crate::typeref::DELETE_STATEMENT_TYPE, - ) == 1 - { - Ok(Self::Delete(value.clone().unbind())) - } - // UpdateStatement - else if pyo3::ffi::PyObject_TypeCheck( - value_ptr, - crate::typeref::UPDATE_STATEMENT_TYPE, - ) == 1 - { - Ok(Self::Update(value.clone().unbind())) - } - // InsertStatement - else if pyo3::ffi::PyObject_TypeCheck( - value_ptr, - crate::typeref::INSERT_STATEMENT_TYPE, - ) == 1 - { - Ok(Self::Insert(value.clone().unbind())) - } - // Other types - else { - crate::new_error!( - PyTypeError, - "expected SelectStatement, DeleteStatement, UpdateStatement, or \ - InsertStatement, got {}", - crate::internal::get_type_name(value.py(), value_ptr) - ) - } - } - } -} - -#[inline(always)] -fn cast_into_with_clause<'a, 'b>(py: pyo3::Python<'a>, value: &'b PyObject) -> &'b PyWithClause -where - 'a: 'b, -{ - let bound = value.bind(py); - - unsafe { - let casted = bound.cast_unchecked::(); - casted.get() - } -} - -impl std::fmt::Display for CommonTableExpressionQuery { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Select(x) => write!(f, "{x}"), - Self::Update(x) => write!(f, "{x}"), - Self::Delete(x) => write!(f, "{x}"), - Self::Insert(x) => write!(f, "{x}"), - } - } -} - -impl std::fmt::Display for CommonTableExpression { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "('{}', {}", self.name, self.query)?; - - if !self.columns.is_empty() { - write!(f, ", {:?}", self.columns)?; - } - match self.materialized { - Some(true) => write!(f, ", materialized")?, - Some(false) => write!(f, ", not materialized")?, - None => {} - } - - write!(f, ")") - } -} - -impl ToSeaQuery for CommonTableExpression { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::CommonTableExpression { - let (mut stmt, select_used) = { - match &self.query { - CommonTableExpressionQuery::Select(x) if self.columns.is_empty() => { - let select = - unsafe { x.cast_bound_unchecked::(py) }; - - let result = select.get().0.lock().to_sea_query(py); - - (sea_query::CommonTableExpression::from_select(result), true) - } - _ => (sea_query::CommonTableExpression::new(), false), - } - }; - stmt.table_name(sea_query::Alias::new(&self.name).into_iden()); - - match &self.query { - CommonTableExpressionQuery::Select(x) => { - if !select_used { - let delete = - unsafe { x.cast_bound_unchecked::(py) }; - - let result = delete.get().0.lock().to_sea_query(py); - stmt.query(result); - } - } - CommonTableExpressionQuery::Delete(x) => { - let delete = - unsafe { x.cast_bound_unchecked::(py) }; - - let result = delete.get().0.lock().to_sea_query(py); - stmt.query(result); - } - CommonTableExpressionQuery::Update(x) => { - let update = - unsafe { x.cast_bound_unchecked::(py) }; - - let result = update.get().0.lock().to_sea_query(py); - stmt.query(result); - } - CommonTableExpressionQuery::Insert(x) => { - let insert = - unsafe { x.cast_bound_unchecked::(py) }; - - let result = insert.get().0.lock().to_sea_query(py); - stmt.query(result); - } - } - - if !self.columns.is_empty() { - stmt.columns(self.columns.iter().map(sea_query::Alias::new)); - } - if let Some(x) = self.materialized { - stmt.materialized(x); - } - - stmt - } -} - -impl ToSeaQuery for WithClauseState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::WithClause { - let mut stmt = sea_query::WithClause::new(); - - if self.recursive { - stmt.recursive(true); - } - for cte in self.cte_expressions.iter() { - stmt.cte(cte.to_sea_query(py)); - } - if let Some(x) = &self.cycle { - stmt.cycle(x.clone()); - } - if let Some(x) = &self.search { - stmt.search(x.clone()); - } - - stmt - } -} - -impl ToSeaQuery for WithQueryState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::WithQuery { - let clause = unsafe { - let x = cast_into_with_clause(py, &self.with_clause); - x.0.lock().to_sea_query(py) - }; - - match &self.query { - CommonTableExpressionQuery::Select(x) => { - let select = - unsafe { x.cast_bound_unchecked::(py) }; - - let result = select.get().0.lock().to_sea_query(py); - clause.query(result) - } - CommonTableExpressionQuery::Delete(x) => { - let delete = - unsafe { x.cast_bound_unchecked::(py) }; - - let result = delete.get().0.lock().to_sea_query(py); - clause.query(result) - } - CommonTableExpressionQuery::Update(x) => { - let update = - unsafe { x.cast_bound_unchecked::(py) }; - - let result = update.get().0.lock().to_sea_query(py); - clause.query(result) - } - CommonTableExpressionQuery::Insert(x) => { - let insert = - unsafe { x.cast_bound_unchecked::(py) }; - - let result = insert.get().0.lock().to_sea_query(py); - clause.query(result) - } - } - } -} - -#[pyo3::pymethods] -impl PyWithClause { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> Self { - Self::uninit() - } - - fn __init__(&self) -> pyo3::PyResult<()> { - let state = WithClauseState { - recursive: false, - cte_expressions: vec![], - cycle: None, - search: None, - }; - self.0.set(state); - Ok(()) - } - - /// Sets whether this clause is a recursive with clause of not. - /// If set to true it will generate a 'WITH RECURSIVE' query. - /// - /// You can only specify a single CTE containing a union query - /// if this is set to true. - fn recursive<'a>(slf: pyo3::PyRef<'a, Self>) -> pyo3::PyRef<'a, Self> { - slf.0.lock().recursive = true; - slf - } - - /// Add a CTE to this with clause. - #[pyo3(signature=(name, query, columns=Vec::new(), materialized=None))] - fn cte<'a>( - slf: pyo3::PyRef<'a, Self>, - name: String, - query: RefBoundObject<'a>, - columns: Vec, - materialized: Option, - ) -> pyo3::PyResult> { - let query = CommonTableExpressionQuery::try_from(query)?; - - slf.0.lock().cte_expressions.push(CommonTableExpression { - name, - query, - columns, - materialized, - }); - Ok(slf) - } - - /// For recursive `WithQuery` `WithClause`s the CYCLE sql clause can be specified to avoid creating - /// an infinite traversals that loops on graph cycles indefinitely. You specify an expression that - /// identifies a node in the graph and that will be used to determine during the iteration of - /// the execution of the query when appending of new values whether the new values are distinct new - /// nodes or are already visited and therefore they should be added again into the result. - /// - /// A query can have both SEARCH and CYCLE clauses. - /// - /// This setting is not meaningful if the query is not recursive. - /// Some databases don’t support this clause. In that case this option will be silently ignored. - fn cycle<'a>( - slf: pyo3::PyRef<'a, Self>, - expr: Option>, - set_as: Option, - using: Option, - ) -> pyo3::PyResult> { - let mut cycle = sea_query::Cycle::new(); - - if let Some(x) = expr { - let val = PyExpr::try_from(x)?; - cycle.expr(val.0); - } - if let Some(x) = set_as { - cycle.set(sea_query::Alias::new(x)); - } - if let Some(x) = using { - cycle.using(sea_query::Alias::new(x)); - } - - slf.0.lock().cycle = Some(cycle); - Ok(slf) - } - - /// For recursive `WithQuery` `WithClause`s the traversing order can be specified in some databases - /// that support this functionality. - /// - /// A query can have both SEARCH and CYCLE clauses. - /// - /// This setting is not meaningful if the query is not recursive. - /// Some databases don’t support this clause. In that case this option will be silently ignored. - /// - /// The `expr` used must specify an alias which will be the name that you can use to order - /// the result of the CTE. - fn search<'a>( - slf: pyo3::PyRef<'a, Self>, - expr: Option>, - order: Option, - ) -> pyo3::PyResult> { - let mut search = sea_query::Search::new(); - - if let Some(x) = expr { - let val = x.cast_into::()?; - - search.expr(val.get().as_ref().to_sea_query(slf.py())); - } - - if let Some(x) = order { - match x.to_ascii_lowercase().as_str() { - "breadth" => { - search.order(sea_query::SearchOrder::BREADTH); - } - "depth" => { - search.order(sea_query::SearchOrder::DEPTH); - } - _ => return new_error!(PyValueError, "unknown order: {}", x), - } - } - slf.0.lock().search = Some(search); - Ok(slf) - } - - /// You can turn this into a `WithQuery` using this function. - /// The resulting WITH query will execute the argument query with this WITH clause. - fn query<'a>( - slf: pyo3::PyRef<'a, Self>, - val: RefBoundObject<'a>, - ) -> pyo3::PyResult> { - let query = CommonTableExpressionQuery::try_from(val)?; - - let state = WithQueryState { - with_clause: unsafe { pyo3::Bound::from_borrowed_ptr(slf.py(), slf.as_ptr()).unbind() }, - query, - }; - let result: PyWithQuery = state.into(); - - pyo3::Bound::new(slf.py(), (result, PyQueryStatement)) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - let mut fmt = ReprFormatter::new_with_pyref(&slf) - .optional_boolean("recursive", lock.recursive) - .take(); - - fmt.vec("cte", false) - .display_iter(lock.cte_expressions.iter()) - .finish(&mut fmt); - - fmt.finish() - } -} - -#[pyo3::pymethods] -impl PyWithQuery { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PyQueryStatement) { - (Self::uninit(), PyQueryStatement) - } - - fn __init__(&self, clause: BoundObject, query: RefBoundObject) -> pyo3::PyResult<()> { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(clause.as_ptr(), crate::typeref::WITH_CLAUSE_TYPE) == 0 - { - return crate::new_error!( - PyTypeError, - "expected WithClause, got {}", - crate::internal::get_type_name(clause.py(), clause.as_ptr()) - ); - } - } - let query = CommonTableExpressionQuery::try_from(query)?; - - let state = WithQueryState { - with_clause: clause.unbind(), - query, - }; - self.0.set(state); - Ok(()) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - use sea_query::QueryStatementBuilder; - - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_query_statement!(backend, stmt) - } - - #[pyo3(signature = (backend, /))] - fn build<'a>( - &self, - py: pyo3::Python<'a>, - backend: String, - ) -> pyo3::PyResult<(String, BoundObject<'a>)> { - use sea_query::QueryStatementBuilder; - - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_query_parts!(py, backend, stmt) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - ReprFormatter::new_with_pyref(&slf) - .display("clause", &lock.with_clause) - .display("query", &lock.query) - .finish() - } -} diff --git a/src/schema/alter_table.rs b/src/schema/alter_table.rs deleted file mode 100644 index 20121c1..0000000 --- a/src/schema/alter_table.rs +++ /dev/null @@ -1,620 +0,0 @@ -use super::base::PySchemaStatement; -use crate::common::column::PyColumn; -use crate::common::column_ref::PyColumnRef; -use crate::common::foreign_key::PyForeignKey; -use crate::common::table_ref::PyTableName; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, PyObject, RefBoundObject, ToSeaQuery}; - -use pyo3::PyTypeInfo; -use sea_query::IntoIden; - -crate::implement_pyclass! { - // Base class for all ALTER TABLE operation types. - /// - /// This abstract base class represents the different types of modifications - /// that can be made to an existing table structure, such as adding/dropping - /// columns, modifying column definitions, or managing foreign keys. - #[derive(Debug, Clone, Copy)] - [subclass] PyAlterTableBaseOption as "AlterTableBaseOption"; -} -crate::implement_pyclass! { - /// ALTER TABLE operation to add a new column. - /// - /// Adds a column to an existing table with optional IF NOT EXISTS clause - /// to prevent errors if the column already exists. - #[derive(Debug)] - immutable [subclass, extends=PyAlterTableBaseOption] PyAlterTableAddColumnOption(AlterTableAddColumnOptionState) - as "AlterTableAddColumnOption" { - /// Always is `PyColumn` - column: PyObject, - if_not_exists: bool, - } -} -crate::implement_pyclass! { - /// ALTER TABLE operation to add a foreign key constraint. - /// - /// Adds referential integrity between tables by creating a foreign key - /// relationship on an existing table. - #[derive(Debug)] - immutable [subclass, extends=PyAlterTableBaseOption] PyAlterTableAddForeignKeyOption(AlterTableAddForeignKeyOptionState) - as "AlterTableAddForeignKeyOption" { - /// Always is `PyForeignKey` - foreign_key: PyObject, - } -} -crate::implement_pyclass! { - /// ALTER TABLE operation to drop an existing column. - /// - /// Removes a column from the table. This operation may fail if the column - /// is referenced by other database objects. - #[derive(Debug, Clone)] - immutable [subclass, extends=PyAlterTableBaseOption] PyAlterTableDropColumnOption(AlterTableDropColumnOptionState) - as "AlterTableDropColumnOption" { name: sea_query::DynIden } -} -crate::implement_pyclass! { - /// ALTER TABLE operation to drop a foreign key constraint. - /// - /// Removes a foreign key relationship by its constraint name. - #[derive(Debug, Clone)] - immutable [subclass, extends=PyAlterTableBaseOption] PyAlterTableDropForeignKeyOption(AlterTableDropForeignKeyOptionState) - as "AlterTableDropForeignKeyOption" { name: sea_query::DynIden } -} -crate::implement_pyclass! { - /// ALTER TABLE operation to modify a column definition. - /// - /// Changes properties of an existing column such as type, nullability, - /// default value, or other constraints. - #[derive(Debug)] - immutable [subclass, extends=PyAlterTableBaseOption] PyAlterTableModifyColumnOption(AlterTableModifyColumnOptionState) - as "AlterTableModifyColumnOption" { - /// Always is `PyColumn` - column: PyObject, - } -} -crate::implement_pyclass! { - /// ALTER TABLE operation to rename a column. - /// - /// Changes the name of an existing column without modifying its type - /// or constraints. - #[derive(Debug, Clone)] - immutable [subclass, extends=PyAlterTableBaseOption] PyAlterTableRenameColumnOption(AlterTableRenameColumnOptionState) - as "AlterTableRenameColumnOption" { - from_name: sea_query::DynIden, - to_name: sea_query::DynIden, - } -} -crate::implement_pyclass! { - /// Represents an ALTER TABLE SQL statement. - /// - /// Provides a flexible way to modify existing table structures by applying - /// one or more alteration operations such as adding/dropping columns, - /// modifying column definitions, or managing constraints. - /// - /// Multiple operations can be batched together in a single ALTER TABLE - /// statement for efficiency. - #[derive(Debug)] - mutable [subclass, extends=PySchemaStatement] PyAlterTable(AlterTableState) as "AlterTable" { - name: PyTableName, - options: Vec, - } -} - -#[pyo3::pymethods] -impl PyAlterTableAddColumnOption { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__( - args: BoundArgs<'_>, - kwds: Option>, - ) -> (Self, PyAlterTableBaseOption) { - (Self::uninit(), PyAlterTableBaseOption) - } - - #[pyo3(signature = (column, if_not_exists=false))] - fn __init__(&self, column: RefBoundObject<'_>, if_not_exists: bool) -> pyo3::PyResult<()> { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(column.as_ptr(), crate::typeref::COLUMN_TYPE) == 0 { - return crate::new_error!( - PyTypeError, - "expected Column, got {}", - crate::internal::get_type_name(column.py(), column.as_ptr()) - ); - } - } - - let result = AlterTableAddColumnOptionState { - column: column.clone().unbind(), - if_not_exists, - }; - unsafe { - self.0.set(result); - } - Ok(()) - } - - #[getter] - fn column(&self, py: pyo3::Python) -> PyObject { - self.0.as_ref().column.clone_ref(py) - } - - #[getter] - fn if_not_exists(&self) -> bool { - self.0.as_ref().if_not_exists - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let inner = slf.0.as_ref(); - - ReprFormatter::new_with_pyref(&slf) - .display("column", &inner.column) - .optional_boolean("if_not_exists", inner.if_not_exists) - .finish() - } -} - -#[pyo3::pymethods] -impl PyAlterTableAddForeignKeyOption { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__( - args: BoundArgs<'_>, - kwds: Option>, - ) -> (Self, PyAlterTableBaseOption) { - (Self::uninit(), PyAlterTableBaseOption) - } - - #[pyo3(signature = (foreign_key))] - fn __init__(&self, foreign_key: RefBoundObject<'_>) -> pyo3::PyResult<()> { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(foreign_key.as_ptr(), crate::typeref::FOREIGN_KEY_TYPE) - == 0 - { - return crate::new_error!( - PyTypeError, - "expected ForeignKey, got {}", - crate::internal::get_type_name(foreign_key.py(), foreign_key.as_ptr()) - ); - } - } - - let result = AlterTableAddForeignKeyOptionState { - foreign_key: foreign_key.clone().unbind(), - }; - unsafe { - self.0.set(result); - } - Ok(()) - } - - #[getter] - fn foreign_key(&self, py: pyo3::Python) -> PyObject { - self.0.as_ref().foreign_key.clone_ref(py) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let inner = slf.0.as_ref(); - - ReprFormatter::new_with_pyref(&slf) - .display("foreign_key", &inner.foreign_key) - .finish() - } -} - -#[pyo3::pymethods] -impl PyAlterTableDropColumnOption { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__( - args: BoundArgs<'_>, - kwds: Option>, - ) -> (Self, PyAlterTableBaseOption) { - (Self::uninit(), PyAlterTableBaseOption) - } - - #[pyo3(signature=(name))] - fn __init__(&self, name: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let column_ref = PyColumnRef::try_from(name)?; - - match column_ref.name { - Some(x) => { - let result = AlterTableDropColumnOptionState { name: x }; - unsafe { - self.0.set(result); - } - Ok(()) - } - None => crate::new_error!( - PyValueError, - "AlterTableDropColumnOption cannot accept asterisk '*' as name" - ), - } - } - - #[getter] - fn name(&self) -> String { - self.0.as_ref().name.to_string() - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let inner = slf.0.as_ref(); - - ReprFormatter::new_with_pyref(&slf) - .iden("name", &inner.name) - .finish() - } -} - -#[pyo3::pymethods] -impl PyAlterTableDropForeignKeyOption { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__( - args: BoundArgs<'_>, - kwds: Option>, - ) -> (Self, PyAlterTableBaseOption) { - (Self::uninit(), PyAlterTableBaseOption) - } - - #[pyo3(signature=(name))] - fn __init__(&self, name: String) -> pyo3::PyResult<()> { - let result = AlterTableDropForeignKeyOptionState { - name: sea_query::Alias::new(name).into_iden(), - }; - unsafe { - self.0.set(result); - } - Ok(()) - } - - #[getter] - fn name(&self) -> String { - self.0.as_ref().name.to_string() - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let inner = slf.0.as_ref(); - - ReprFormatter::new_with_pyref(&slf) - .iden("name", &inner.name) - .finish() - } -} - -#[pyo3::pymethods] -impl PyAlterTableModifyColumnOption { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__( - args: BoundArgs<'_>, - kwds: Option>, - ) -> (Self, PyAlterTableBaseOption) { - (Self::uninit(), PyAlterTableBaseOption) - } - - #[pyo3(signature = (column))] - fn __init__(&self, column: RefBoundObject<'_>) -> pyo3::PyResult<()> { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(column.as_ptr(), crate::typeref::COLUMN_TYPE) == 0 { - return crate::new_error!( - PyTypeError, - "expected Column, got {}", - crate::internal::get_type_name(column.py(), column.as_ptr()) - ); - } - } - - let result = AlterTableModifyColumnOptionState { - column: column.clone().unbind(), - }; - unsafe { - self.0.set(result); - } - Ok(()) - } - - #[getter] - fn column(&self, py: pyo3::Python) -> PyObject { - self.0.as_ref().column.clone_ref(py) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let inner = slf.0.as_ref(); - - ReprFormatter::new_with_pyref(&slf) - .display("column", &inner.column) - .finish() - } -} - -#[pyo3::pymethods] -impl PyAlterTableRenameColumnOption { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__( - args: BoundArgs<'_>, - kwds: Option>, - ) -> (Self, PyAlterTableBaseOption) { - (Self::uninit(), PyAlterTableBaseOption) - } - - #[pyo3(signature = (from_name, to_name))] - fn __init__( - &self, - from_name: RefBoundObject<'_>, - to_name: RefBoundObject<'_>, - ) -> pyo3::PyResult<()> { - let from_column_ref = PyColumnRef::try_from(from_name)?; - let to_column_ref = PyColumnRef::try_from(to_name)?; - - if from_column_ref.name.is_none() { - return Err(pyo3::exceptions::PyValueError::new_err( - "AlterTableRenameColumnOption cannot accept asterisk '*' as from_name", - )); - } - if to_column_ref.name.is_none() { - return Err(pyo3::exceptions::PyValueError::new_err( - "AlterTableRenameColumnOption cannot accept asterisk '*' as to_name", - )); - } - - unsafe { - let result = AlterTableRenameColumnOptionState { - from_name: from_column_ref.name.unwrap_unchecked(), - to_name: to_column_ref.name.unwrap_unchecked(), - }; - self.0.set(result); - Ok(()) - } - } - - #[getter] - #[allow(clippy::wrong_self_convention)] - fn from_name(&self) -> String { - self.0.as_ref().from_name.clone().to_string() - } - - #[getter] - fn to_name(&self) -> String { - self.0.as_ref().to_name.clone().to_string() - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let inner = slf.0.as_ref(); - - ReprFormatter::new_with_pyref(&slf) - .iden("from_name", &inner.from_name) - .iden("to_name", &inner.to_name) - .finish() - } -} - -impl ToSeaQuery for AlterTableState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::TableAlterStatement { - let mut stmt = sea_query::TableAlterStatement::new(); - stmt.table(self.name.clone()); - - for op in self.options.iter() { - unsafe { - let op_type = pyo3::ffi::Py_TYPE(op.as_ptr()); - - if op_type == PyAlterTableAddColumnOption::type_object_raw(py) { - let add_column_opt = op.cast_bound_unchecked::(py); - let get_add_column_opt = add_column_opt.get().as_ref(); - let py_column = get_add_column_opt - .column - .cast_bound_unchecked::(py) - .get() - .clone(); - - let column_def: sea_query::ColumnDef = py_column.0.lock().to_sea_query(py); - - if get_add_column_opt.if_not_exists { - stmt.add_column_if_not_exists(column_def); - } else { - stmt.add_column(column_def); - } - } else if op_type == PyAlterTableAddForeignKeyOption::type_object_raw(py) { - let add_fk = op.cast_bound_unchecked::(py); - let get_add_fk = add_fk.get().as_ref(); - - let py_fk = get_add_fk - .foreign_key - .cast_bound_unchecked::(py) - .get() - .clone(); - - let table_fk: sea_query::TableForeignKey = py_fk.0.lock().to_sea_query(py); - stmt.add_foreign_key(&table_fk); - } else if op_type == PyAlterTableDropColumnOption::type_object_raw(py) { - let bound = op.cast_bound_unchecked::(py); - let x = bound.get(); - - stmt.drop_column(x.as_ref().name.clone()); - } else if op_type == PyAlterTableDropForeignKeyOption::type_object_raw(py) { - let bound = op.cast_bound_unchecked::(py); - let x = bound.get(); - - stmt.drop_foreign_key(x.0.as_ref().name.clone()); - } else if op_type == PyAlterTableModifyColumnOption::type_object_raw(py) { - let bound = op.cast_bound_unchecked::(py); - let x = bound.get().as_ref(); - - let py_column = x.column.cast_bound_unchecked::(py).get().clone(); - - let column_def: sea_query::ColumnDef = py_column.0.lock().to_sea_query(py); - stmt.modify_column(column_def); - } else if op_type == PyAlterTableRenameColumnOption::type_object_raw(py) { - let bound = op.cast_bound_unchecked::(py); - let x = bound.get().as_ref(); - - stmt.rename_column(x.from_name.clone(), x.to_name.clone()); - } - } - } - - stmt - } -} - -#[pyo3::pymethods] -impl PyAlterTable { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PySchemaStatement) { - (Self::uninit(), PySchemaStatement) - } - - #[pyo3(signature = (name, options=Vec::new()))] - fn __init__( - &self, - name: RefBoundObject<'_>, - options: Vec>, - ) -> pyo3::PyResult<()> { - let name = PyTableName::try_from(name)?; - - unsafe { - for opt in options.iter() { - if pyo3::ffi::PyObject_TypeCheck( - opt.as_ptr(), - crate::typeref::ALTER_TABLE_BASE_OPTION_TYPE, - ) == 0 - { - return crate::new_error!( - PyTypeError, - "expected AlterTableBaseOption, got {}", - crate::internal::get_type_name(opt.py(), opt.as_ptr()) - ); - } - } - } - - let state = AlterTableState { - name, - options: options.into_iter().map(|x| x.unbind()).collect(), - }; - self.0.set(state); - Ok(()) - } - - /// The name of the table to alter. - #[getter] - fn name(&self) -> PyTableName { - self.0.lock().name.clone() - } - - #[setter] - fn set_name(&self, val: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let val = PyTableName::try_from(val)?; - - let mut lock = self.0.lock(); - lock.name = val; - Ok(()) - } - - /// The list of alteration operations to apply. - #[getter] - fn options(&self, py: pyo3::Python) -> Vec { - self.0 - .lock() - .options - .iter() - .map(|x| x.clone_ref(py)) - .collect() - } - - #[setter] - fn set_options(&self, val: Vec>) -> pyo3::PyResult<()> { - unsafe { - for opt in val.iter() { - if pyo3::ffi::PyObject_TypeCheck( - opt.as_ptr(), - crate::typeref::ALTER_TABLE_BASE_OPTION_TYPE, - ) == 0 - { - return crate::new_error!( - PyTypeError, - "expected AlterTableBaseOption, got {}", - crate::internal::get_type_name(opt.py(), opt.as_ptr()) - ); - } - } - } - - let mut lock = self.0.lock(); - lock.options = val.into_iter().map(|x| x.unbind()).collect(); - Ok(()) - } - - /// Add an alteration operation to this ALTER TABLE statement. - fn add_option<'a>( - slf: pyo3::PyRef<'a, Self>, - opt: BoundObject<'a>, - ) -> pyo3::PyResult> { - unsafe { - if pyo3::ffi::PyObject_TypeCheck( - opt.as_ptr(), - crate::typeref::ALTER_TABLE_BASE_OPTION_TYPE, - ) == 0 - { - return crate::new_error!( - PyTypeError, - "expected AlterTableBaseOption, got {}", - crate::internal::get_type_name(opt.py(), opt.as_ptr()) - ); - } - } - - { - let mut lock = slf.0.lock(); - lock.options.push(opt.unbind()); - } - Ok(slf) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - - // Prevent "No alter option found" panic - if lock.options.is_empty() { - return crate::new_error!( - PyRuntimeError, - "No alter option found. You have to specify at least one alter option." - ); - } - - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_schema_statement!(backend, stmt) - } - - fn __copy__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult> { - let lock = self.0.lock(); - - let new_state = AlterTableState { - name: lock.name.clone(), - options: lock.options.iter().map(|x| x.clone_ref(py)).collect(), - }; - - pyo3::Py::new(py, (new_state.into(), PySchemaStatement)) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let inner = slf.0.lock(); - - ReprFormatter::new_with_pyref(&slf) - .map("name", &inner.name, |x| x.__repr__()) - .finish() - } -} diff --git a/src/schema/base.rs b/src/schema/base.rs deleted file mode 100644 index a50af53..0000000 --- a/src/schema/base.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::internal::{BoundArgs, BoundKwargs}; - -crate::implement_pyclass! { - /// Subclass of schema statements. - #[derive(Debug, Clone, Copy)] - [subclass] PySchemaStatement as "SchemaStatement"; -} - -#[pyo3::pymethods] -impl PySchemaStatement { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> Self { - Self - } - - fn __init__(&self) {} - - /// Build a SQL string representation. - #[pyo3(signature = (backend, /))] - #[allow(unused_variables)] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, backend: String) -> pyo3::PyResult { - Err(pyo3::exceptions::PyNotImplementedError::new_err(())) - } -} diff --git a/src/schema/index.rs b/src/schema/index.rs deleted file mode 100644 index 2ff6169..0000000 --- a/src/schema/index.rs +++ /dev/null @@ -1,684 +0,0 @@ -use sea_query::IntoIden; - -use super::base::PySchemaStatement; -use crate::common::expression::PyExpr; -use crate::common::table_ref::PyTableName; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, RefBoundObject, ToSeaQuery}; - -#[inline] -fn map_str_to_index_order(value: String) -> pyo3::PyResult { - match value.to_ascii_uppercase().as_str() { - "ASC" => Ok(sea_query::IndexOrder::Asc), - "DESC" => Ok(sea_query::IndexOrder::Desc), - _ => Err(pyo3::exceptions::PyValueError::new_err(format!( - "unknown order: {}", - value - ))), - } -} - -#[inline] -fn map_index_order_to_str(value: &sea_query::IndexOrder) -> String { - match value { - sea_query::IndexOrder::Asc => String::from("ASC"), - sea_query::IndexOrder::Desc => String::from("DESC"), - } -} - -pub const OPT_PRIMARY: u8 = 1 << 0; -pub const OPT_UNIQUE: u8 = 1 << 1; -pub const OPT_IF_NOT_EXISTS: u8 = 1 << 2; -pub const OPT_NULLS_NOT_DISTINCT: u8 = 1 << 3; - -crate::implement_pyclass! { - /// Defines a column within an index specification. - /// - /// Represents a single column's participation in an index, including: - /// - The column name - /// - Optional prefix length (for partial indexing) - /// - Sort order (ascending or descending) - /// - /// Used within `Index` to specify which columns are indexed - /// and how they should be ordered. - /// - /// NOTE: this class is immutable and frozen. - #[derive(Debug, Clone)] - [] PyIndexColumn as "IndexColumn" { - pub name: sea_query::DynIden, - pub order: Option, - pub prefix: Option, - } -} -crate::implement_pyclass! { - /// Represents a database index specification. - /// - /// This class defines the structure and properties of a database index, - /// including column definitions, uniqueness constraints, index type, - /// and partial indexing conditions. - /// - /// You can use it to generate `CREATE INDEX` SQL expressions. - #[derive(Debug)] - mutable [subclass, extends=PySchemaStatement] PyIndex(IndexState) as "Index" { - pub name: Option, - pub columns: Vec, - pub table: Option, - pub options: u8, - pub index_type: Option, - pub r#where: Option, - pub include: Vec, - } -} -crate::implement_pyclass! { - /// Represents a DROP INDEX SQL statement. - /// - /// Builds index deletion statements with support for: - /// - Conditional deletion (IF EXISTS) - /// - Table-specific index dropping - #[derive(Debug)] - mutable [subclass, extends=PySchemaStatement] PyDropIndex(DropIndexState) as "DropIndex" { - pub name: String, - pub table: PyTableName, - pub if_exists: bool, - } -} - -impl sea_query::IntoIndexColumn for PyIndexColumn { - fn into_index_column(self) -> sea_query::IndexColumn { - match (self.prefix, self.order) { - (Some(p), Some(o)) => (self.name, p, o).into_index_column(), - (Some(p), None) => (self.name, p).into_index_column(), - (None, Some(o)) => (self.name, o).into_index_column(), - (None, None) => self.name.into_index_column(), - } - } -} - -impl TryFrom> for PyIndexColumn { - type Error = pyo3::PyErr; - - fn try_from(value: RefBoundObject<'_>) -> Result { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(value.as_ptr(), crate::typeref::INDEX_COLUMN_TYPE) == 1 - { - let casted_value = value.cast_unchecked::(); - return Ok(casted_value.get().clone()); - } - - let column_ref = - crate::common::column_ref::PyColumnRef::try_from(value).map_err(|_| { - crate::new_py_error!( - PyTypeError, - "expected IndexColumn, ColumnRef, Column, str, or object.to_column_ref \ - property, got {}", - crate::internal::get_type_name(value.py(), value.as_ptr()) - ) - })?; - - match column_ref.name { - Some(x) => { - let inner = Self { - name: x, - order: None, - prefix: None, - }; - Ok(inner) - } - None => Err(pyo3::exceptions::PyValueError::new_err( - "IndexColumn cannot accept asterisk '*' as column", - )), - } - } - } -} - -#[pyo3::pymethods] -impl PyIndexColumn { - #[new] - #[pyo3(signature=(name, order=None, prefix=None))] - fn __new__(name: String, order: Option, prefix: Option) -> pyo3::PyResult { - let state = Self { - name: sea_query::Alias::new(name).into_iden(), - order: match order { - None => None, - Some(x) => Some(map_str_to_index_order(x)?), - }, - prefix, - }; - Ok(state) - } - - /// The name of the column to include in the index. - #[getter] - fn name(&self) -> String { - self.name.to_string() - } - - /// Number of characters to index for string columns (prefix indexing). - #[getter] - fn prefix(&self) -> Option { - self.prefix - } - - /// Sort order for this column. - #[getter] - fn order(&self) -> Option { - self.order.as_ref().map(map_index_order_to_str) - } - - fn __copy__(&self) -> Self { - self.clone() - } - - fn __repr__(&self) -> String { - ReprFormatter::new("IndexColumn") - .iden("name", &self.name) - .optional_display("prefix", self.prefix) - .optional_map("order", self.order.as_ref(), map_index_order_to_str) - .finish() - } -} - -impl Clone for IndexState { - fn clone(&self) -> Self { - Self { - name: self.name.clone(), - columns: self.columns.clone(), - table: self.table.clone(), - options: self.options, - index_type: self.index_type.clone(), - r#where: self.r#where.clone(), - include: self.include.clone(), - } - } -} - -impl ToSeaQuery for IndexState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::IndexCreateStatement { - let mut stmt = sea_query::IndexCreateStatement::new(); - - if let Some(x) = &self.name { - stmt.name(x); - } - if let Some(x) = &self.table { - stmt.table(x.clone()); - } - - for col in self.columns.iter() { - stmt.col(col.clone()); - } - - if let Some(x) = &self.index_type { - stmt.index_type(sea_query::IndexType::Custom( - sea_query::Alias::new(x.clone()).into_iden(), - )); - } - - for include in self.include.iter() { - stmt.include(sea_query::Alias::new(include)); - } - - if self.options & OPT_PRIMARY > 0 { - stmt.primary(); - } - if self.options & OPT_IF_NOT_EXISTS > 0 { - stmt.if_not_exists(); - } - if self.options & OPT_NULLS_NOT_DISTINCT > 0 { - stmt.nulls_not_distinct(); - } - if self.options & OPT_UNIQUE > 0 { - stmt.unique(); - } - - stmt - } -} - -#[pyo3::pymethods] -impl PyIndex { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PySchemaStatement) { - (Self::uninit(), PySchemaStatement) - } - - #[ - pyo3( - signature = ( - name, - columns, - table=None, - *, - primary=false, - if_not_exists=false, - nulls_not_distinct=false, - unique=false, - index_type=None, - r#where=None, - include=Vec::new() - ) - ) - ] - fn __init__( - &self, - name: Option, - columns: Vec>, - table: Option>, - primary: bool, - if_not_exists: bool, - nulls_not_distinct: bool, - unique: bool, - index_type: Option, - r#where: Option>, - include: Vec, - ) -> pyo3::PyResult<()> { - let table = match table { - Some(x) => Some(PyTableName::try_from(x)?), - None => None, - }; - - let r#where = match r#where { - None => None, - Some(x) => unsafe { - if pyo3::ffi::Py_TYPE(x.as_ptr()) != crate::typeref::EXPR_TYPE { - return crate::new_error!( - PyTypeError, - "expected Expr or None, got {}", - crate::internal::get_type_name(x.py(), x.as_ptr()) - ); - } - - Some(x.cast_unchecked::().get().clone()) - }, - }; - - if columns.is_empty() { - return Err(pyo3::exceptions::PyValueError::new_err( - "columns cannot be empty", - )); - } - - let mut cols = Vec::with_capacity(columns.len()); - for c in columns.into_iter() { - cols.push(PyIndexColumn::try_from(&c)?); - } - - let mut options = 0u8; - if primary { - options |= OPT_PRIMARY; - } - if unique { - options |= OPT_UNIQUE; - } - if nulls_not_distinct { - options |= OPT_NULLS_NOT_DISTINCT; - } - if if_not_exists { - options |= OPT_IF_NOT_EXISTS; - } - - let state = IndexState { - name, - columns: cols, - table, - options, - index_type, - r#where, - include, - }; - self.0.set(state); - - Ok(()) - } - - /// Index name. - #[getter] - fn name(&self) -> Option { - let lock = self.0.lock(); - lock.name.clone() - } - - #[setter] - fn set_name(&self, val: Option) { - let mut lock = self.0.lock(); - lock.name = val; - } - - /// The table on which to create the index. - #[getter] - fn table(&self) -> Option { - let lock = self.0.lock(); - lock.table.clone() - } - - #[setter] - fn set_table(&self, val: Option>) -> pyo3::PyResult<()> { - let val = match val { - Some(x) => Some(PyTableName::try_from(x)?), - None => None, - }; - - let mut lock = self.0.lock(); - lock.table = val; - Ok(()) - } - - /// Whether this is a primary key constraint. - #[getter] - fn primary(&self) -> bool { - self.0.lock().options & OPT_PRIMARY > 0 - } - - #[setter] - fn set_primary(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= OPT_PRIMARY; - } else { - lock.options &= !OPT_PRIMARY; - } - } - - /// Whether this is a unique constraint. - #[getter] - fn unique(&self) -> bool { - self.0.lock().options & OPT_UNIQUE > 0 - } - - #[setter] - fn set_unique(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= OPT_UNIQUE; - } else { - lock.options &= !OPT_UNIQUE; - } - } - - /// Whether NULL values should be considered equal for uniqueness. - #[getter] - fn nulls_not_distinct(&self) -> bool { - self.0.lock().options & OPT_NULLS_NOT_DISTINCT > 0 - } - - #[setter] - fn set_nulls_not_distinct(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= OPT_NULLS_NOT_DISTINCT; - } else { - lock.options &= !OPT_NULLS_NOT_DISTINCT; - } - } - - /// Whether to use IF NOT EXISTS clause. - #[getter] - fn if_not_exists(&self) -> bool { - self.0.lock().options & OPT_IF_NOT_EXISTS > 0 - } - - #[setter] - fn set_if_not_exists(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= OPT_IF_NOT_EXISTS; - } else { - lock.options &= !OPT_IF_NOT_EXISTS; - } - } - - /// The columns that make up this index. - #[getter] - fn columns(&self) -> Vec { - let lock = self.0.lock(); - lock.columns.clone() - } - - #[setter] - fn set_columns(&self, val: Vec>) -> pyo3::PyResult<()> { - if val.is_empty() { - return Err(pyo3::exceptions::PyValueError::new_err( - "columns cannot be empty", - )); - } - - let mut cols = Vec::with_capacity(val.len()); - for c in val.into_iter() { - cols.push(PyIndexColumn::try_from(&c)?); - } - - let mut lock = self.0.lock(); - lock.columns = cols; - Ok(()) - } - - /// The type/algorithm for this index. - #[getter] - fn index_type(&self) -> Option { - let lock = self.0.lock(); - lock.index_type.clone() - } - - #[setter] - fn set_index_type(&self, val: Option) -> pyo3::PyResult<()> { - let mut lock = self.0.lock(); - lock.index_type = val; - Ok(()) - } - - /// Condition for partial indexing. - #[getter] - fn r#where(&self) -> Option { - let lock = self.0.lock(); - lock.r#where.clone() - } - - #[setter] - fn set_where(&self, val: Option>) -> pyo3::PyResult<()> { - let val = match val { - None => None, - Some(x) => Some(PyExpr::try_from(x)?), - }; - - let mut lock = self.0.lock(); - lock.r#where = val; - Ok(()) - } - - /// Additional columns to include in the index for covering queries - #[getter] - fn include(&self) -> Vec { - let lock = self.0.lock(); - lock.include.clone() - } - - #[setter] - fn set_include(&self, val: Vec) { - let mut lock = self.0.lock(); - lock.include = val; - } - - fn __copy__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult> { - let lock = self.0.lock(); - - pyo3::Py::new(py, (lock.clone().into(), PySchemaStatement)) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - - if lock.table.is_none() { - return crate::new_error!( - PyRuntimeError, - "To generate a CREATE INDEX statement, both `table` and `name` parameters are \ - necessary." - ); - } - - if lock.name.is_none() { - return crate::new_error!( - PyRuntimeError, - "To generate a CREATE INDEX statement, both `table` and `name` parameters are \ - necessary." - ); - } - - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_schema_statement!(backend, stmt) - } - - pub fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - let mut fmt = ReprFormatter::new_with_pyref(&slf) - .optional_quote("name", lock.name.as_ref()) - .optional_map("table", lock.table.as_ref(), |x| x.__repr__()) - .take(); - - fmt.vec("columns", true) - .display_iter(lock.columns.iter().map(|x| x.__repr__())) - .finish(&mut fmt); - - fmt.optional_boolean("if_not_exists", lock.options & OPT_IF_NOT_EXISTS > 0) - .optional_boolean("primary", lock.options & OPT_PRIMARY > 0) - .optional_boolean("unique", lock.options & OPT_UNIQUE > 0) - .optional_boolean( - "nulls_not_distinct", - lock.options & OPT_NULLS_NOT_DISTINCT > 0, - ) - .optional_quote("index_type", lock.index_type.as_ref()) - .optional_map("where", lock.r#where.as_ref(), |x| x.__repr__()); - - fmt.vec("include", true) - .display_iter(lock.include.iter()) - .finish(&mut fmt); - - fmt.finish() - } -} - -impl Clone for DropIndexState { - fn clone(&self) -> Self { - Self { - name: self.name.clone(), - table: self.table.clone(), - if_exists: self.if_exists, - } - } -} - -impl ToSeaQuery for DropIndexState { - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::IndexDropStatement { - let mut stmt = sea_query::IndexDropStatement::new(); - - stmt.name(&self.name); - stmt.table(self.table.clone()); - - if self.if_exists { - stmt.if_exists(); - } - stmt - } -} - -#[pyo3::pymethods] -impl PyDropIndex { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PySchemaStatement) { - (Self::uninit(), PySchemaStatement) - } - - #[pyo3(signature = (name, table, if_exists=false))] - fn __init__( - &self, - name: String, - table: RefBoundObject<'_>, - if_exists: bool, - ) -> pyo3::PyResult<()> { - let table = PyTableName::try_from(table)?; - - let state = DropIndexState { - name, - table, - if_exists, - }; - self.0.set(state); - Ok(()) - } - - /// The name of the index to drop. - #[getter] - fn name(&self) -> String { - let lock = self.0.lock(); - lock.name.clone() - } - - #[setter] - fn set_name(&self, val: String) { - let mut lock = self.0.lock(); - lock.name = val; - } - - /// The table from which to drop the index. - #[getter] - fn table(&self) -> PyTableName { - let lock = self.0.lock(); - lock.table.clone() - } - - #[setter] - fn set_table(&self, val: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let val = PyTableName::try_from(val)?; - - let mut lock = self.0.lock(); - lock.table = val; - Ok(()) - } - - /// Whether to use IF EXISTS clause to avoid errors. - #[getter] - fn if_exists(slf: pyo3::PyRef<'_, Self>) -> bool { - slf.0.lock().if_exists - } - - #[setter] - fn set_if_exists(slf: pyo3::PyRef<'_, Self>, val: bool) { - let mut lock = slf.0.lock(); - lock.if_exists = val; - } - - fn __copy__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult> { - let lock = self.0.lock(); - - pyo3::Py::new(py, (lock.clone().into(), PySchemaStatement)) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_schema_statement!(backend, stmt) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - ReprFormatter::new_with_pyref(&slf) - .quote("name", &lock.name) - .map("table", &lock.table, |x| x.__repr__()) - .optional_boolean("if_exists", lock.if_exists) - .finish() - } -} diff --git a/src/schema/mod.rs b/src/schema/mod.rs deleted file mode 100644 index 08fae68..0000000 --- a/src/schema/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub mod alter_table; -pub mod base; -pub mod index; -pub mod table; -pub mod table_operations; - -#[pyo3::pymodule(name = "schema")] -pub mod schema_module { - - #[pymodule_export] - use super::base::PySchemaStatement; - - #[pymodule_export] - use super::alter_table::PyAlterTable; - #[pymodule_export] - use super::alter_table::PyAlterTableAddColumnOption; - #[pymodule_export] - use super::alter_table::PyAlterTableAddForeignKeyOption; - #[pymodule_export] - use super::alter_table::PyAlterTableBaseOption; - #[pymodule_export] - use super::alter_table::PyAlterTableDropColumnOption; - #[pymodule_export] - use super::alter_table::PyAlterTableDropForeignKeyOption; - #[pymodule_export] - use super::alter_table::PyAlterTableModifyColumnOption; - #[pymodule_export] - use super::alter_table::PyAlterTableRenameColumnOption; - - #[pymodule_export] - use super::index::PyDropIndex; - #[pymodule_export] - use super::index::PyIndex; - #[pymodule_export] - use super::index::PyIndexColumn; - - #[pymodule_export] - use super::table::PyTable; - - #[pymodule_export] - use super::table_operations::PyDropTable; - #[pymodule_export] - use super::table_operations::PyRenameTable; - #[pymodule_export] - use super::table_operations::PyTruncateTable; -} diff --git a/src/schema/table.rs b/src/schema/table.rs deleted file mode 100644 index 021b22b..0000000 --- a/src/schema/table.rs +++ /dev/null @@ -1,510 +0,0 @@ -use super::base::PySchemaStatement; -use crate::common::table_ref::PyTableName; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, BoundObject, PyObject, RefBoundObject, ToSeaQuery}; - -use pyo3::types::PyTupleMethods; - -pub const OPT_IF_NOT_EXISTS: u8 = 1 << 0; -pub const OPT_TEMPORARY: u8 = 1 << 1; - -crate::implement_pyclass! { - /// Represents a complete database table definition. - /// - /// This class encapsulates all aspects of a table structure including: - /// - Column definitions with their types and constraints - /// - Indexes for query optimization - /// - Foreign key relationships for referential integrity - /// - Check constraints for data validation - /// - Table-level options like engine, collation, and character set - /// - /// Used to generate CREATE TABLE SQL statements with full schema specifications. - mutable [subclass, extends=PySchemaStatement] PyTable(TableState) as "Table" { - pub name: PyTableName, - - /// Always is `Vec` - pub columns: Vec, - - /// Always is `Vec` - pub indexes: Vec, - - /// Always is `Vec` - pub foreign_keys: Vec, - - /// Always is `Vec` - pub checks: Vec, - - pub options: u8, - pub comment: Option, - pub engine: Option, - pub collate: Option, - pub character_set: Option, - pub extra: Option, - } -} - -impl TableState { - fn clone_ref(&self, py: pyo3::Python) -> Self { - Self { - name: self.name.clone(), - columns: self.columns.iter().map(|x| x.clone_ref(py)).collect(), - indexes: self.indexes.iter().map(|x| x.clone_ref(py)).collect(), - foreign_keys: self.foreign_keys.iter().map(|x| x.clone_ref(py)).collect(), - checks: self.checks.iter().map(|x| x.clone_ref(py)).collect(), - options: self.options, - comment: self.comment.clone(), - engine: self.engine.clone(), - collate: self.collate.clone(), - character_set: self.character_set.clone(), - extra: self.extra.clone(), - } - } -} - -impl ToSeaQuery for TableState { - #[cfg_attr(feature = "optimize", optimize(speed))] - fn to_sea_query<'a>(&self, py: pyo3::Python<'a>) -> sea_query::TableCreateStatement { - let mut stmt = sea_query::TableCreateStatement::new(); - stmt.table(self.name.clone()); - - // Add columns - for column in self.columns.iter() { - let col = unsafe { column.cast_bound_unchecked::(py) }; - - let column_def: sea_query::ColumnDef = col.get().0.lock().to_sea_query(py); - stmt.col(column_def); - } - - // Add primary and unique indexes - for index in self.indexes.iter() { - let ix = unsafe { index.cast_bound_unchecked::(py) }; - - let lock = ix.get().0.lock(); - if lock.options & super::index::OPT_PRIMARY > 0 - || lock.options & super::index::OPT_UNIQUE > 0 - { - let mut index_stmt = lock.to_sea_query(py); - stmt.index(&mut index_stmt); - } - } - - // Add foreign keys - for foreign_key in self.foreign_keys.iter() { - let fk = unsafe { - foreign_key.cast_bound_unchecked::(py) - }; - - let mut fk_stmt = fk.get().0.lock().to_sea_query(py); - stmt.foreign_key(&mut fk_stmt); - } - - // Add check constraints - for check in self.checks.iter() { - let simple_expr = - unsafe { check.cast_bound_unchecked::(py) }; - - stmt.check(simple_expr.get().0.clone()); - } - - if self.options & OPT_IF_NOT_EXISTS > 0 { - stmt.if_not_exists(); - } - if self.options & OPT_TEMPORARY > 0 { - stmt.temporary(); - } - - if let Some(x) = &self.comment { - stmt.comment(x); - } - if let Some(x) = &self.engine { - stmt.engine(x); - } - if let Some(x) = &self.collate { - stmt.collate(x); - } - if let Some(x) = &self.character_set { - stmt.character_set(x); - } - if let Some(x) = &self.extra { - stmt.extra(x); - } - - stmt - } -} - -#[pyo3::pymethods] -impl PyTable { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PySchemaStatement) { - (Self::uninit(), PySchemaStatement) - } - - #[ - pyo3( - signature=( - name, - *args, - if_not_exists=false, - temporary=false, - comment=None, - engine=None, - collate=None, - character_set=None, - extra=None, - ) - ) - ] - fn __init__( - &self, - name: RefBoundObject<'_>, - args: BoundArgs<'_>, - if_not_exists: bool, - temporary: bool, - comment: Option, - engine: Option, - collate: Option, - character_set: Option, - extra: Option, - ) -> pyo3::PyResult<()> { - let name = PyTableName::try_from(name)?; - - let mut columns = Vec::new(); - let mut indexes = Vec::new(); - let mut foreign_keys = Vec::new(); - let mut checks = Vec::new(); - - for object in args.iter() { - unsafe { - // columns - if pyo3::ffi::PyObject_TypeCheck(object.as_ptr(), crate::typeref::COLUMN_TYPE) == 1 - { - columns.push(object.unbind()); - } - // indexes - else if pyo3::ffi::PyObject_TypeCheck(object.as_ptr(), crate::typeref::INDEX_TYPE) - == 1 - { - indexes.push(object.unbind()); - } - // foreign keys - else if pyo3::ffi::PyObject_TypeCheck( - object.as_ptr(), - crate::typeref::FOREIGN_KEY_TYPE, - ) == 1 - { - foreign_keys.push(object.unbind()); - } - // expressions (check constraints) - else if pyo3::ffi::Py_TYPE(object.as_ptr()) == crate::typeref::EXPR_TYPE { - checks.push(object.unbind()); - } else { - return crate::new_error!( - PyTypeError, - "expected Column, Index, ForeignKey, or Expr, got {}", - crate::internal::get_type_name(object.py(), object.as_ptr()) - ); - } - } - } - - let mut options = 0u8; - if if_not_exists { - options |= OPT_IF_NOT_EXISTS; - } - if temporary { - options |= OPT_TEMPORARY; - } - - let state = TableState { - name, - columns, - indexes, - foreign_keys, - checks, - options, - comment, - engine, - collate, - character_set, - extra, - }; - self.0.set(state); - Ok(()) - } - - /// The name of this table. - #[getter] - fn name(&self) -> PyTableName { - let lock = self.0.lock(); - lock.name.clone() - } - - /// Table columns. - #[getter] - fn columns(&self, py: pyo3::Python) -> Vec { - let lock = self.0.lock(); - lock.columns.iter().map(|x| x.clone_ref(py)).collect() - } - - #[setter] - fn set_columns(&self, val: Vec>) -> pyo3::PyResult<()> { - let mut columns = Vec::new(); - - for object in val.into_iter() { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(object.as_ptr(), crate::typeref::COLUMN_TYPE) == 0 - { - return crate::new_error!( - PyTypeError, - "expected iterable of Column, found {}", - crate::internal::get_type_name(object.py(), object.as_ptr()) - ); - } - } - - columns.push(object.unbind()); - } - - self.0.lock().columns = columns; - Ok(()) - } - - /// Table indexes. - #[getter] - fn indexes(&self, py: pyo3::Python) -> Vec { - let lock = self.0.lock(); - lock.indexes.iter().map(|x| x.clone_ref(py)).collect() - } - - #[setter] - fn set_indexes(&self, val: Vec>) -> pyo3::PyResult<()> { - let mut indexes = Vec::new(); - - for object in val.into_iter() { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(object.as_ptr(), crate::typeref::INDEX_TYPE) == 0 { - return crate::new_error!( - PyTypeError, - "expected iterable of Index, found {}", - crate::internal::get_type_name(object.py(), object.as_ptr()) - ); - } - } - - indexes.push(object.unbind()); - } - - self.0.lock().indexes = indexes; - Ok(()) - } - - /// Table foreign keys. - #[getter] - fn foreign_keys(&self, py: pyo3::Python) -> Vec { - let lock = self.0.lock(); - lock.foreign_keys.iter().map(|x| x.clone_ref(py)).collect() - } - - #[setter] - fn set_foreign_keys(&self, val: Vec>) -> pyo3::PyResult<()> { - let mut foreign_keys = Vec::new(); - - for object in val.into_iter() { - unsafe { - if pyo3::ffi::PyObject_TypeCheck(object.as_ptr(), crate::typeref::FOREIGN_KEY_TYPE) - == 0 - { - return crate::new_error!( - PyTypeError, - "expected iterable of ForeignKey, found {}", - crate::internal::get_type_name(object.py(), object.as_ptr()) - ); - } - } - - foreign_keys.push(object.unbind()); - } - - self.0.lock().foreign_keys = foreign_keys; - Ok(()) - } - - /// Table check constraints. - #[getter] - fn checks(&self, py: pyo3::Python) -> Vec { - let lock = self.0.lock(); - lock.checks.iter().map(|x| x.clone_ref(py)).collect() - } - - #[setter] - fn set_checks(&self, val: Vec>) -> pyo3::PyResult<()> { - let mut checks = Vec::new(); - - for object in val.into_iter() { - unsafe { - if pyo3::ffi::Py_TYPE(object.as_ptr()) != crate::typeref::EXPR_TYPE { - return crate::new_error!( - PyTypeError, - "expected iterable of Expr, found {}", - crate::internal::get_type_name(object.py(), object.as_ptr()) - ); - } - } - - checks.push(object.unbind()); - } - - self.0.lock().checks = checks; - Ok(()) - } - - /// Whether to use IF NOT EXISTS clause to avoid errors if table exists. - #[getter] - fn if_not_exists(&self) -> bool { - self.0.lock().options & OPT_IF_NOT_EXISTS > 0 - } - - /// Whether this is a temporary table that exists only for the session. - #[getter] - fn temporary(&self) -> bool { - self.0.lock().options & OPT_TEMPORARY > 0 - } - - /// Comment describing the purpose of this table. - #[getter] - fn comment(&self) -> Option { - self.0.lock().comment.clone() - } - - #[setter] - fn set_comment(&self, val: Option) { - self.0.lock().comment = val; - } - - /// Storage engine for the table (e.g., InnoDB, MyISAM for MySQL). - #[getter] - fn engine(&self) -> Option { - self.0.lock().engine.clone() - } - - #[setter] - fn set_engine(&self, val: Option) { - self.0.lock().engine = val; - } - - /// Collation for string comparisons and sorting in this table. - #[getter] - fn collate(&self) -> Option { - self.0.lock().collate.clone() - } - - #[setter] - fn set_collate(&self, val: Option) { - self.0.lock().collate = val; - } - - /// Character set encoding for text data in this table. - #[getter] - fn character_set(&self) -> Option { - self.0.lock().character_set.clone() - } - - #[setter] - fn set_character_set(&self, val: Option) { - self.0.lock().character_set = val; - } - - /// Additional table-specific options for the CREATE TABLE statement. - #[getter] - fn extra(&self) -> Option { - self.0.lock().extra.clone() - } - - #[setter] - fn set_extra(&self, val: Option) { - self.0.lock().extra = val; - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - - let mut sqls = vec![crate::build_schema_statement!(&backend, stmt)?]; - - for index in lock.indexes.iter() { - let ix = unsafe { index.cast_bound_unchecked::(py) }; - let ix_lock = ix.get().0.lock(); - - if ix_lock.options & super::index::OPT_PRIMARY > 0 - || ix_lock.options & super::index::OPT_UNIQUE > 0 - { - continue; - } - - // Index name and table is necessary here - if ix_lock.name.is_none() { - return Err(pyo3::exceptions::PyValueError::new_err( - "You should always set name for indexes that aren't primary or unique", - )); - } - - let mut index_stmt = ix_lock.to_sea_query(py); - index_stmt.table(lock.name.clone()); - - if lock.options & OPT_IF_NOT_EXISTS > 0 { - index_stmt.if_not_exists(); - } - - sqls.push(crate::build_schema_statement!(&backend, index_stmt)?); - } - - Ok(sqls.join(";\n")) - } - - #[getter] - fn __table_name__(&self) -> PyTableName { - self.0.lock().name.clone() - } - - fn __copy__<'a>(&self, py: pyo3::Python<'a>) -> pyo3::PyResult> { - let lock = self.0.lock(); - pyo3::Bound::new(py, (lock.clone_ref(py).into(), PySchemaStatement)) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - let mut fmt = ReprFormatter::new_with_pyref(&slf) - .map("name", &lock.name, |x| x.__repr__()) - .take(); - - fmt.vec("columns", true) - .display_iter(lock.columns.iter()) - .finish(&mut fmt); - - fmt.vec("indexes", true) - .display_iter(lock.indexes.iter()) - .finish(&mut fmt); - - fmt.vec("foreign_keys", true) - .display_iter(lock.foreign_keys.iter()) - .finish(&mut fmt); - - fmt.vec("checks", true) - .display_iter(lock.checks.iter()) - .finish(&mut fmt); - - fmt.optional_boolean("if_not_exists", lock.options & OPT_IF_NOT_EXISTS > 0) - .optional_boolean("temporary", lock.options & OPT_TEMPORARY > 0) - .optional_quote("comment", lock.comment.as_ref()) - .optional_quote("engine", lock.engine.as_ref()) - .optional_quote("collate", lock.collate.as_ref()) - .optional_quote("character_set", lock.character_set.as_ref()) - .finish() - } -} diff --git a/src/schema/table_operations.rs b/src/schema/table_operations.rs deleted file mode 100644 index 19ac707..0000000 --- a/src/schema/table_operations.rs +++ /dev/null @@ -1,364 +0,0 @@ -use super::base::PySchemaStatement; -use crate::common::table_ref::PyTableName; -use crate::internal::repr::ReprFormatter; -use crate::internal::{BoundArgs, BoundKwargs, RefBoundObject, ToSeaQuery}; - -pub const DROP_OPT_IF_EXISTS: u8 = 1 << 0; -pub const DROP_OPT_CASCADE: u8 = 1 << 1; -pub const DROP_OPT_RESTRICT: u8 = 1 << 2; - -crate::implement_pyclass! { - /// Represents a DROP TABLE SQL statement. - /// - /// Builds table deletion statements with support for: - /// - Conditional deletion (IF EXISTS) to avoid errors - /// - CASCADE to drop dependent objects - /// - RESTRICT to prevent deletion if dependencies exist - mutable [subclass, extends=PySchemaStatement] PyDropTable(DropTableState) as "DropTable" { - name: PyTableName, - options: u8, - } -} -crate::implement_pyclass! { - /// Represents a RENAME TABLE SQL statement. - /// - /// Changes the name of an existing table to a new name. Both names can be - /// schema-qualified if needed. - mutable [subclass, extends=PySchemaStatement] PyRenameTable(RenameTableState) as "RenameTable" { - from_name: PyTableName, - to_name: PyTableName, - } -} -crate::implement_pyclass! { - /// Represents a TRUNCATE TABLE SQL statement. - /// - /// Quickly removes all rows from a table, typically faster than DELETE - /// and with different transaction and trigger behavior depending on the - /// database system. - mutable [subclass, extends=PySchemaStatement] PyTruncateTable(TruncateTableState) as "TruncateTable" { - name: PyTableName, - } -} - -impl Clone for DropTableState { - fn clone(&self) -> Self { - Self { - name: self.name.clone(), - options: self.options, - } - } -} - -impl ToSeaQuery for DropTableState { - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::TableDropStatement { - let mut stmt = sea_query::TableDropStatement::new(); - stmt.table(self.name.clone()); - - if self.options & DROP_OPT_IF_EXISTS > 0 { - stmt.if_exists(); - } - if self.options & DROP_OPT_RESTRICT > 0 { - stmt.restrict(); - } - if self.options & DROP_OPT_CASCADE > 0 { - stmt.cascade(); - } - - stmt - } -} - -#[pyo3::pymethods] -impl PyDropTable { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PySchemaStatement) { - (Self::uninit(), PySchemaStatement) - } - - #[pyo3(signature = (name, *, if_exists=false, cascade=false, restrict=false))] - fn __init__( - &self, - name: RefBoundObject<'_>, - if_exists: bool, - cascade: bool, - restrict: bool, - ) -> pyo3::PyResult<()> { - let name = PyTableName::try_from(name)?; - - let mut options = 0u8; - if if_exists { - options |= DROP_OPT_IF_EXISTS; - } - if cascade { - options |= DROP_OPT_CASCADE; - } - if restrict { - options |= DROP_OPT_RESTRICT; - } - - let state = DropTableState { name, options }; - self.0.set(state); - Ok(()) - } - - /// The table name to drop. - #[getter] - fn name(&self) -> PyTableName { - let lock = self.0.lock(); - lock.name.clone() - } - - #[setter] - fn set_name(&self, val: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let val = PyTableName::try_from(val)?; - - let mut lock = self.0.lock(); - lock.name = val; - Ok(()) - } - - #[getter] - fn if_exists(&self) -> bool { - self.0.lock().options & DROP_OPT_IF_EXISTS > 0 - } - - #[setter] - fn set_if_exists(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= DROP_OPT_IF_EXISTS; - } else { - lock.options &= !DROP_OPT_IF_EXISTS; - } - } - - #[getter] - fn cascade(&self) -> bool { - self.0.lock().options & DROP_OPT_CASCADE > 0 - } - - #[setter] - fn set_cascade(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= DROP_OPT_CASCADE; - } else { - lock.options &= !DROP_OPT_CASCADE; - } - } - - #[getter] - fn restrict(&self) -> bool { - self.0.lock().options & DROP_OPT_RESTRICT > 0 - } - - #[setter] - fn set_restrict(&self, value: bool) { - let mut lock = self.0.lock(); - if value { - lock.options |= DROP_OPT_RESTRICT; - } else { - lock.options &= !DROP_OPT_RESTRICT; - } - } - - fn __copy__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult> { - let lock = self.0.lock(); - pyo3::Py::new(py, (lock.clone().into(), PySchemaStatement)) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_schema_statement!(backend, stmt) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - ReprFormatter::new_with_pyref(&slf) - .map("name", &lock.name, |x| x.__repr__()) - .optional_boolean("if_exists", lock.options & DROP_OPT_IF_EXISTS > 0) - .optional_boolean("cascade", lock.options & DROP_OPT_CASCADE > 0) - .optional_boolean("restrict", lock.options & DROP_OPT_RESTRICT > 0) - .finish() - } -} - -impl Clone for RenameTableState { - fn clone(&self) -> Self { - Self { - from_name: self.from_name.clone(), - to_name: self.to_name.clone(), - } - } -} - -impl ToSeaQuery for RenameTableState { - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::TableRenameStatement { - let mut stmt = sea_query::TableRenameStatement::new(); - stmt.table(self.from_name.clone(), self.to_name.clone()); - stmt - } -} - -#[pyo3::pymethods] -impl PyRenameTable { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PySchemaStatement) { - (Self::uninit(), PySchemaStatement) - } - - #[pyo3(signature = (from_name, to_name))] - fn __init__( - &self, - from_name: RefBoundObject<'_>, - to_name: RefBoundObject<'_>, - ) -> pyo3::PyResult<()> { - let from_name = PyTableName::try_from(from_name)?; - let to_name = PyTableName::try_from(to_name)?; - - let state = RenameTableState { from_name, to_name }; - self.0.set(state); - Ok(()) - } - - /// The current name of the table. - #[getter] - #[allow(clippy::wrong_self_convention)] - fn from_name(&self) -> PyTableName { - let lock = self.0.lock(); - lock.from_name.clone() - } - - #[setter] - fn set_from_name(&self, val: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let val = PyTableName::try_from(val)?; - - let mut lock = self.0.lock(); - lock.from_name = val; - Ok(()) - } - - /// The new name for the table. - #[getter] - fn to_name(&self) -> PyTableName { - let lock = self.0.lock(); - lock.to_name.clone() - } - - #[setter] - fn set_to_name(&self, val: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let val = PyTableName::try_from(val)?; - - let mut lock = self.0.lock(); - lock.to_name = val; - Ok(()) - } - - fn __copy__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult> { - let lock = self.0.lock(); - pyo3::Py::new(py, (lock.clone().into(), PySchemaStatement)) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_schema_statement!(backend, stmt) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - ReprFormatter::new_with_pyref(&slf) - .map("from_name", &lock.from_name, |x| x.__repr__()) - .map("to_name", &lock.to_name, |x| x.__repr__()) - .finish() - } -} - -impl Clone for TruncateTableState { - fn clone(&self) -> Self { - Self { - name: self.name.clone(), - } - } -} - -impl ToSeaQuery for TruncateTableState { - fn to_sea_query<'a>(&self, _py: pyo3::Python<'a>) -> sea_query::TableTruncateStatement { - let mut stmt = sea_query::TableTruncateStatement::new(); - stmt.table(self.name.clone()); - stmt - } -} - -#[pyo3::pymethods] -impl PyTruncateTable { - #[new] - #[allow(unused_variables)] - #[pyo3(signature=(*args, **kwds))] - fn __new__(args: BoundArgs<'_>, kwds: Option>) -> (Self, PySchemaStatement) { - (Self::uninit(), PySchemaStatement) - } - - #[pyo3(signature = (name))] - fn __init__(&self, name: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let name = PyTableName::try_from(name)?; - - let state = TruncateTableState { name }; - self.0.set(state); - Ok(()) - } - - /// The name of the table to truncate. - #[getter] - fn name(&self) -> PyTableName { - let lock = self.0.lock(); - lock.name.clone() - } - - #[setter] - fn set_name(&self, val: RefBoundObject<'_>) -> pyo3::PyResult<()> { - let val = PyTableName::try_from(val)?; - - let mut lock = self.0.lock(); - lock.name = val; - Ok(()) - } - - fn __copy__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult> { - let lock = self.0.lock(); - pyo3::Py::new(py, (lock.clone().into(), PySchemaStatement)) - } - - #[pyo3(signature = (backend, /))] - #[allow(clippy::wrong_self_convention)] - fn to_sql(&self, py: pyo3::Python<'_>, backend: String) -> pyo3::PyResult { - let lock = self.0.lock(); - let stmt = lock.to_sea_query(py); - drop(lock); - - crate::build_schema_statement!(backend, stmt) - } - - fn __repr__(slf: pyo3::PyRef<'_, Self>) -> String { - let lock = slf.0.lock(); - - ReprFormatter::new_with_pyref(&slf) - .map("name", &lock.name, |x| x.__repr__()) - .finish() - } -} diff --git a/src/sqlite.rs b/src/sqlite.rs deleted file mode 100644 index b5783a7..0000000 --- a/src/sqlite.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// SQLite-Only functions -/// -/// **Comming Soon ...** -#[pyo3::pymodule(name = "sqlite")] -pub mod sqlite_module {} diff --git a/src/sqltypes/abstracts.rs b/src/sqltypes/abstracts.rs deleted file mode 100644 index 1b9ba0a..0000000 --- a/src/sqltypes/abstracts.rs +++ /dev/null @@ -1,138 +0,0 @@ -/// All of native SQL types should implement this trait. -/// -/// This is useful for generic typing. -pub trait SQLTypeTrait { - /// Returns the related sea_query column type - fn to_sea_query_column_type(&self) -> ::sea_query::ColumnType; - - /// Returns SQL type name in string (uses PostgreSQL builder) - fn to_sql_type_name(&self) -> String { - let mut name = String::with_capacity(10); - let builder = sea_query::PostgresQueryBuilder; - sea_query::TableBuilder::prepare_column_type( - &builder, - &self.to_sea_query_column_type(), - &mut name, - ); - name - } - - /// Validates the `ptr` and makes sure that we can process it by using - /// `serialize` method. - /// - /// ### Safety - /// - The `ptr` should be borrowed. - unsafe fn validate( - &self, - py: ::pyo3::Python, - ptr: *mut ::pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()>; - - /// Converts PyObject into sea_query::Value - /// - /// ### Safety - /// - The `ptr` should be borrowed. - /// - The `ptr` should be validated by `validate` method. - unsafe fn serialize( - &self, - py: ::pyo3::Python, - ptr: *mut ::pyo3::ffi::PyObject, - ) -> pyo3::PyResult<::sea_query::Value>; - - /// Converts sea_query::Value into PyObject - /// - /// ### Safety - /// - The returned object is owned and you should call Py_DECREF if you do - /// not need it. - unsafe fn deserialize( - &self, - py: ::pyo3::Python, - value: &::sea_query::Value, - ) -> pyo3::PyResult<*mut ::pyo3::ffi::PyObject>; -} - -crate::implement_pyclass! { - /// Base class for all SQL column data types. - /// - /// This abstract base class represents SQL data types that can be used in - /// column definitions. Each subclass implements a specific SQL data type - /// with its particular characteristics, constraints, and backend-specific - /// representations. - #[derive(Debug, Clone, Copy)] - [subclass, generic] PySQLTypeAbstract as "SQLTypeAbstract"; -} - -#[pyo3::pymethods] -impl PySQLTypeAbstract { - /// Type name. e.g. `'INTEGER'`, `'STRING'` - /// - /// It also may be a property. This function must NOT raise any error. - #[getter] - fn __type_name__(&self) -> pyo3::PyResult<()> { - Err(pyo3::exceptions::PyNotImplementedError::new_err(())) - } -} - -#[macro_export] -macro_rules! implement_sqltype_pymethods { - ($name:ident) => { - #[pyo3::pymethods] - impl $name { - #[new] - fn __new__() -> (Self, PySQLTypeAbstract) { - (Self, PySQLTypeAbstract) - } - - /// Type name. e.g. `'INTEGER'`, `'STRING'` - /// - /// It also may be a property. This function must NOT raise any error. - #[getter] - fn __type_name__(&self) -> String { - self.to_sql_type_name() - } - - fn __repr__(&self) -> String { - let result = String::from(stringify!($name)); - result[2..].to_string() + "()" - } - } - }; - - ( - $name:ident, - init(|$param:ident: $param_type:ty| $init:expr), - $return_type:literal - $(, signature($($signature:tt)*))? - ) => { - #[pyo3::pymethods] - impl $name { - #[new] - $(#[pyo3(signature=($($signature)*))])? - fn __new__($param: $param_type) -> (Self, PySQLTypeAbstract) { - ($init, PySQLTypeAbstract) - } - - /// Type name. e.g. `'INTEGER'`, `'STRING'` - /// - /// It also may be a property. This function must NOT raise any error. - #[getter] - fn __type_name__(&self) -> String { - self.to_sql_type_name() - } - - #[getter] - fn $param(&self) -> $param_type { - self.0 - } - - fn __repr__(&self) -> String { - let result = format!( - concat!(stringify!($name), "(", stringify!($param), "={:?})"), - self.0 - ); - result[2..].to_string() - } - } - }; -} -pub(super) use implement_sqltype_pymethods; diff --git a/src/sqltypes/binary.rs b/src/sqltypes/binary.rs deleted file mode 100644 index 6c6e077..0000000 --- a/src/sqltypes/binary.rs +++ /dev/null @@ -1,352 +0,0 @@ -use crate::sqltypes::abstracts::{PySQLTypeAbstract, SQLTypeTrait}; - -crate::implement_pyclass! { - /// Binary large object column type (BLOB). - /// - /// Stores large binary data such as images, documents, audio files, or - /// any binary content. Size limits vary by database system. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyBlobType as "Blob"; -} -crate::implement_pyclass! { - /// Fixed-length binary data column type (BINARY). - /// - /// Stores binary data of a fixed length. Values shorter than the specified - /// length are padded. Useful for storing hashes, keys, or other binary - /// data with consistent length. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyBinaryType as "Binary" (pub u32); -} -crate::implement_pyclass! { - /// Variable-length binary data column type (VARBINARY). - /// - /// Stores binary data of variable length up to a specified maximum. - /// More storage-efficient than BINARY for binary data of varying lengths. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyVarBinaryType as "VarBinary" (pub Option); -} -crate::implement_pyclass! { - /// Fixed-length bit string column type (BIT). - /// - /// Stores a fixed number of bits. Useful for storing boolean flags efficiently - /// or binary data where individual bits have meaning. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyBitType as "Bit" (pub Option); -} -crate::implement_pyclass! { - /// Variable-length bit string column type (VARBIT). - /// - /// Stores a variable number of bits up to a specified maximum. More flexible - /// than fixed BIT type for bit strings of varying lengths. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyVarBitType as "VarBit" (pub u32); - -} - -#[inline] -unsafe fn _serialize_function( - object: *mut pyo3::ffi::PyObject, -) -> pyo3::PyResult { - let buffer = pyo3::ffi::PyBytes_AsString(object) as *const u8; - let size = pyo3::ffi::PyBytes_Size(object) as usize; - - debug_assert!(!buffer.is_null()); - - let val = std::slice::from_raw_parts(buffer, size); - - Ok(sea_query::Value::Bytes(Some(Box::new(val.to_vec())))) -} - -#[inline] -unsafe fn _deserialize_function( - py: pyo3::Python, - value: &[u8], -) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - let pyptr = pyo3::ffi::PyBytes_FromStringAndSize(std::ptr::null(), value.len() as isize); - - if pyptr.is_null() { - return Err(pyo3::PyErr::fetch(py)); - } - - let buffer = pyo3::ffi::PyBytes_AsString(pyptr) as *mut u8; - debug_assert!(!buffer.is_null()); - - let mutable = std::slice::from_raw_parts_mut(buffer, value.len()); - mutable.copy_from_slice(value); - - Ok(pyptr) -} - -impl SQLTypeTrait for PyBlobType { - #[inline(always)] - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Blob - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::PyBytes_CheckExact(ptr) != 1 { - crate::new_error!( - PyTypeError, - "expected bytes for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - _py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_function(ptr) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Bytes(Some(x)) => _deserialize_function(py, x), - sea_query::Value::Bytes(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected bytes for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyBinaryType { - #[inline(always)] - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Binary(self.0) - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::PyBytes_CheckExact(ptr) != 1 { - crate::new_error!( - PyTypeError, - "expected bytes for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - _py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_function(ptr) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Bytes(Some(x)) => _deserialize_function(py, x), - sea_query::Value::Bytes(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected bytes for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyVarBinaryType { - #[inline(always)] - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::VarBinary( - self.0 - .map_or(sea_query::StringLen::None, sea_query::StringLen::N), - ) - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::PyBytes_CheckExact(ptr) != 1 { - crate::new_error!( - PyTypeError, - "expected bytes for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - _py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_function(ptr) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Bytes(Some(x)) => _deserialize_function(py, x), - sea_query::Value::Bytes(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected bytes for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyBitType { - #[inline(always)] - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Bit(self.0) - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::PyBytes_CheckExact(ptr) != 1 { - crate::new_error!( - PyTypeError, - "expected bytes for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - _py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_function(ptr) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Bytes(Some(x)) => _deserialize_function(py, x), - sea_query::Value::Bytes(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected bytes for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyVarBitType { - #[inline(always)] - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::VarBit(self.0) - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::PyBytes_CheckExact(ptr) != 1 { - crate::new_error!( - PyTypeError, - "expected bytes for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - _py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_function(ptr) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Bytes(Some(x)) => _deserialize_function(py, x), - sea_query::Value::Bytes(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected bytes for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -super::abstracts::implement_sqltype_pymethods!(PyBlobType); -super::abstracts::implement_sqltype_pymethods!( - PyBinaryType, - init(|length: u32| Self(length)), - "int", - signature(length = 255) -); -super::abstracts::implement_sqltype_pymethods!( - PyVarBinaryType, - init(|length: Option| Self(length)), - "int", - signature(length = None) -); -super::abstracts::implement_sqltype_pymethods!( - PyBitType, - init(|length: Option| Self(length)), - "int", - signature(length = None) -); -super::abstracts::implement_sqltype_pymethods!( - PyVarBitType, - init(|length: u32| Self(length)), - "int", - signature(length = 255) -); diff --git a/src/sqltypes/datetimes.rs b/src/sqltypes/datetimes.rs deleted file mode 100644 index 596c4f9..0000000 --- a/src/sqltypes/datetimes.rs +++ /dev/null @@ -1,353 +0,0 @@ -use crate::sqltypes::abstracts::{PySQLTypeAbstract, SQLTypeTrait}; - -use chrono::TimeZone; -use pyo3::types::{PyAnyMethods, PyTzInfoAccess}; -use pyo3::IntoPyObject; - -crate::implement_pyclass! { - /// Date and time column type (DATETIME). - /// - /// Stores both date and time information without timezone awareness. - /// Suitable for recording timestamps, event times, or scheduling information - /// when timezone handling is managed at the application level. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyDateTimeType as "DateTime"; -} -crate::implement_pyclass! { - /// Timestamp column type (TIMESTAMP). - /// - /// Stores timestamp values, often with automatic update capabilities. - /// Behavior varies by database system. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyTimestampType as "Timestamp" (pub bool); -} -crate::implement_pyclass! { - /// Time-only column type (TIME). - /// - /// Stores time information without date component. Useful for storing - /// daily schedules, opening hours, or any time-based data that repeats - /// daily regardless of the specific date. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyTimeType as "Time"; -} -crate::implement_pyclass! { - /// Date-only column type (DATE). - /// - /// Stores date information without time component. Ideal for birth dates, - /// deadlines, or any date-based data where time precision is not needed. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyDateType as "Date"; - -} - -#[inline(always)] -unsafe fn _serialize_datetime( - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, -) -> pyo3::PyResult { - let val: pyo3::Bound<'_, pyo3::types::PyDateTime> = - pyo3::Bound::from_borrowed_ptr(py, ptr).cast_into()?; - - let tzinfo = val.get_tzinfo(); - - if tzinfo.is_none() { - let result = Box::new(val.extract()?); - Ok(sea_query::Value::ChronoDateTime(Some(result))) - } else { - let result = Box::new(val.extract()?); - Ok(sea_query::Value::ChronoDateTimeWithTimeZone(Some(result))) - } -} - -impl SQLTypeTrait for PyDateTimeType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::DateTime - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::Py_TYPE(ptr) != crate::typeref::STD_DATETIME_TYPE { - crate::new_error!( - PyTypeError, - "expected datetime.datetime for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_datetime(py, ptr) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::ChronoDateTime(Some(x)) => { - let pyobject = x.clone().into_pyobject(py)?; - Ok(pyobject.into_ptr()) - } - sea_query::Value::ChronoDateTimeWithTimeZone(Some(x)) => { - let pyobject = x.clone().into_pyobject(py)?; - Ok(pyobject.into_ptr()) - } - sea_query::Value::ChronoDateTimeUtc(Some(x)) => { - let pyobject = x.clone().into_pyobject(py)?; - Ok(pyobject.into_ptr()) - } - sea_query::Value::ChronoDateTimeLocal(Some(x)) => { - let pyobject = x.to_utc().into_pyobject(py)?; - Ok(pyobject.into_ptr()) - } - sea_query::Value::ChronoDateTime(None) - | sea_query::Value::ChronoDateTimeWithTimeZone(None) - | sea_query::Value::ChronoDateTimeUtc(None) - | sea_query::Value::ChronoDateTimeLocal(None) => Ok(pyo3::ffi::Py_None()), - - _ => crate::new_error!( - PyTypeError, - "expected datetime for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyTimestampType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - if self.0 { - sea_query::ColumnType::TimestampWithTimeZone - } else { - sea_query::ColumnType::Timestamp - } - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::Py_TYPE(ptr) != crate::typeref::STD_DATETIME_TYPE - && pyo3::ffi::PyLong_CheckExact(ptr) != 1 - && pyo3::ffi::PyFloat_CheckExact(ptr) != 1 - { - crate::new_error!( - PyTypeError, - "expected datetime.datetime/int/float for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - if pyo3::ffi::PyLong_CheckExact(ptr) == 1 { - let mut has_overflow: i32 = 0; - let num = pyo3::ffi::PyLong_AsLongLongAndOverflow(ptr, &mut has_overflow); - - if has_overflow == 1 { - return Err(pyo3::exceptions::PyOverflowError::new_err("out of range")); - } - - match chrono::Utc.timestamp_opt(num, 0) { - chrono::offset::LocalResult::Single(result) => { - return Ok(sea_query::Value::ChronoDateTimeUtc(Some(Box::new(result)))); - } - _ => { - return Err(pyo3::exceptions::PyOverflowError::new_err( - "timestamp is invalid", - )); - } - } - } - - if pyo3::ffi::PyFloat_CheckExact(ptr) == 1 { - let num = pyo3::ffi::PyFloat_AsDouble(ptr); - - match chrono::Utc - .timestamp_opt(num.trunc() as i64, (num.fract() * 1_000_000_000.0) as u32) - { - chrono::offset::LocalResult::Single(result) => { - return Ok(sea_query::Value::ChronoDateTimeUtc(Some(Box::new(result)))); - } - _ => { - return Err(pyo3::exceptions::PyOverflowError::new_err( - "timestamp is invalid", - )); - } - } - } - - _serialize_datetime(py, ptr) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::ChronoDateTime(Some(x)) => { - let pyobject = x.clone().into_pyobject(py)?; - Ok(pyobject.into_ptr()) - } - sea_query::Value::ChronoDateTimeWithTimeZone(Some(x)) => { - let pyobject = x.clone().into_pyobject(py)?; - Ok(pyobject.into_ptr()) - } - sea_query::Value::ChronoDateTimeUtc(Some(x)) => { - let pyobject = x.clone().into_pyobject(py)?; - Ok(pyobject.into_ptr()) - } - sea_query::Value::ChronoDateTime(None) - | sea_query::Value::ChronoDateTimeWithTimeZone(None) - | sea_query::Value::ChronoDateTimeUtc(None) => Ok(pyo3::ffi::Py_None()), - - _ => crate::new_error!( - PyTypeError, - "expected datetime for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyDateType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Date - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::Py_TYPE(ptr) != crate::typeref::STD_DATE_TYPE { - crate::new_error!( - PyTypeError, - "expected datetime.date for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - let val: pyo3::Bound<'_, pyo3::types::PyDate> = - pyo3::Bound::from_borrowed_ptr(py, ptr).cast_into()?; - - let result = Box::new(val.extract()?); - Ok(sea_query::Value::ChronoDate(Some(result))) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::ChronoDate(Some(x)) => { - let pyobject = x.clone().into_pyobject(py)?; - Ok(pyobject.into_ptr()) - } - sea_query::Value::ChronoDate(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected date for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyTimeType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Time - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::Py_TYPE(ptr) != crate::typeref::STD_TIME_TYPE { - crate::new_error!( - PyTypeError, - "expected datetime.time for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - let val: pyo3::Bound<'_, pyo3::types::PyTime> = - pyo3::Bound::from_borrowed_ptr(py, ptr).cast_into()?; - - let result = Box::new(val.extract()?); - Ok(sea_query::Value::ChronoTime(Some(result))) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::ChronoTime(Some(x)) => { - let pyobject = x.clone().into_pyobject(py)?; - Ok(pyobject.into_ptr()) - } - sea_query::Value::ChronoTime(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected time for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -super::abstracts::implement_sqltype_pymethods!(PyDateTimeType); -super::abstracts::implement_sqltype_pymethods!(PyDateType); -super::abstracts::implement_sqltype_pymethods!(PyTimeType); -super::abstracts::implement_sqltype_pymethods!( - PyTimestampType, - init(|timezone: bool| Self(timezone)), - "bool", - signature(timezone = false) -); diff --git a/src/sqltypes/json.rs b/src/sqltypes/json.rs deleted file mode 100644 index 981503b..0000000 --- a/src/sqltypes/json.rs +++ /dev/null @@ -1,213 +0,0 @@ -use crate::sqltypes::abstracts::{PySQLTypeAbstract, SQLTypeTrait}; - -use pyo3::types::PyAnyMethods; - -crate::implement_pyclass! { - /// JSON data column type (JSON). - /// - /// Stores JSON documents with validation and indexing capabilities. - /// Allows for flexible schema design and complex nested data structures - /// while maintaining some query capabilities. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyJSONType as "JSON"; -} -crate::implement_pyclass! { - /// Binary JSON column type (JSONB). - /// - /// Stores JSON documents in a binary format for improved performance. - /// Provides faster query and manipulation operations compared to text-based - /// JSON storage, with additional indexing capabilities. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyJSONBinaryType as "JSONBinary"; -} - -/// Import json module only once -#[inline(always)] -pub fn import_json_module( - py: pyo3::Python<'_>, -) -> pyo3::PyResult<&pyo3::Bound<'_, pyo3::types::PyModule>> { - static JSON_CLS: std::sync::OnceLock> = - std::sync::OnceLock::new(); - - let json = JSON_CLS.get_or_try_init(|| py.import("json").map(|x| x.unbind())); - json.map(|x| x.bind(py)) -} - -/// Serialize pyobject with Python `json` module -/// -/// Note: `ptr` should be borrowed -#[inline(always)] -pub fn _serialize_object_with_pyjson( - py: pyo3::Python<'_>, - ptr: *mut pyo3::ffi::PyObject, -) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - let json = import_json_module(py)?; - let dumps_func = json.getattr("dumps")?; - - unsafe { - let arg1 = pyo3::Bound::from_borrowed_ptr(py, ptr); - dumps_func.call1((arg1,)).map(|x| x.into_ptr()) - } -} - -/// Deserialize pyobject with Python `json` module -/// -/// Note: `ptr` should be borrowed -#[inline(always)] -pub fn _deserialize_object_with_pyjson( - py: pyo3::Python<'_>, - ptr: *mut pyo3::ffi::PyObject, -) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - let json = import_json_module(py)?; - let loads_func = json.getattr("loads")?; - - unsafe { - let arg1 = pyo3::Bound::from_borrowed_ptr(py, ptr); - loads_func.call1((arg1,)).map(|x| x.into_ptr()) - } -} - -/// Try to serialize pyobject to validate pyobject is JSON-serializable -#[inline] -#[cfg_attr(feature = "optimize", optimize(speed))] -pub fn _validate_json_object( - py: pyo3::Python<'_>, - ptr: *mut pyo3::ffi::PyObject, -) -> pyo3::PyResult<()> { - unsafe { - // Fast path - if (pyo3::ffi::PyLong_CheckExact(ptr) == 1) - || (pyo3::ffi::PyUnicode_CheckExact(ptr) == 1) - || (pyo3::ffi::PyFloat_CheckExact(ptr) == 1) - || (pyo3::ffi::Py_IsNone(ptr) == 1) - { - return Ok(()); - } - } - - _serialize_object_with_pyjson(py, ptr)?; - Ok(()) -} - -#[inline] -#[cfg_attr(feature = "optimize", optimize(speed))] -unsafe fn _serialize_function( - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, -) -> pyo3::PyResult { - let serialized = _serialize_object_with_pyjson(py, ptr)?; - - let mut size: pyo3::ffi::Py_ssize_t = 0; - let c_str = pyo3::ffi::PyUnicode_AsUTF8AndSize(serialized, &mut size); - - if c_str.is_null() || size < 0 { - pyo3::ffi::Py_DECREF(serialized); - Err(pyo3::PyErr::fetch(py)) - } else { - let val = std::ffi::CStr::from_ptr(c_str); - let val = serde_json::from_slice::(val.to_bytes()); - - pyo3::ffi::Py_DECREF(serialized); - - let val = - val.map_err(|x| pyo3::PyErr::new::(x.to_string()))?; - - Ok(sea_query::Value::Json(Some(Box::new(val)))) - } -} - -#[inline] -#[cfg_attr(feature = "optimize", optimize(speed))] -unsafe fn _deserialize_function( - py: pyo3::Python, - value: &serde_json::Value, -) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - let encoded = serde_json::to_vec(value) - .map_err(|x| pyo3::PyErr::new::(x.to_string()))?; - - let val = pyo3::types::PyString::intern(py, std::str::from_utf8_unchecked(&encoded)); - - let val = _deserialize_object_with_pyjson(py, val.as_ptr())?; - Ok(val) -} - -impl SQLTypeTrait for PyJSONType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Json - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - _validate_json_object(py, ptr) - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_function(py, ptr) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Json(Some(x)) => _deserialize_function(py, x), - sea_query::Value::Json(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected json for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyJSONBinaryType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::JsonBinary - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - _validate_json_object(py, ptr) - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_function(py, ptr) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Json(Some(x)) => _deserialize_function(py, x), - sea_query::Value::Json(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected json for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -super::abstracts::implement_sqltype_pymethods!(PyJSONType); -super::abstracts::implement_sqltype_pymethods!(PyJSONBinaryType); diff --git a/src/sqltypes/mod.rs b/src/sqltypes/mod.rs deleted file mode 100644 index 5cbd5dc..0000000 --- a/src/sqltypes/mod.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! All SQL types with their serialization and deserialization methods. -//! -//! With support for custom types for Python. - -mod abstracts; -mod binary; -mod datetimes; -mod json; -mod others; -mod primitives; -mod vector; - -pub use abstracts::*; -pub use binary::*; -pub use datetimes::*; -pub use json::*; -pub use others::*; -pub use primitives::*; -pub use vector::*; - -#[pyo3::pymodule(name = "sqltypes")] -pub mod sqltypes_module { - // NOTE: SQLTypes, PyExpr, PyFunc, PyTableName & PyColumnRef could never mark as subclass. - // these should be immutable and final types. - - // sqltypes::abstracts - #[pymodule_export] - use super::PySQLTypeAbstract; - - // sqltypes::binary - #[pymodule_export] - use super::PyBinaryType; - #[pymodule_export] - use super::PyBitType; - #[pymodule_export] - use super::PyBlobType; - #[pymodule_export] - use super::PyVarBinaryType; - #[pymodule_export] - use super::PyVarBitType; - - // sqltypes::datetimes - #[pymodule_export] - use super::PyDateTimeType; - #[pymodule_export] - use super::PyDateType; - #[pymodule_export] - use super::PyTimeType; - #[pymodule_export] - use super::PyTimestampType; - - // sqltypes::json - #[pymodule_export] - use super::PyJSONBinaryType; - #[pymodule_export] - use super::PyJSONType; - - // sqltypes::vector - #[pymodule_export] - use super::PyVectorType; - - // sqltypes::others - #[pymodule_export] - use super::PyArrayType; - #[pymodule_export] - use super::PyDecimalType; - #[pymodule_export] - use super::PyEnumType; - #[pymodule_export] - use super::PyINETType; - #[pymodule_export] - use super::PyMacAddressType; - #[pymodule_export] - use super::PyUUIDType; - - // sqltypes::primitives - #[pymodule_export] - use super::PyBigIntegerType; - #[pymodule_export] - use super::PyBigUnsignedType; - #[pymodule_export] - use super::PyBooleanType; - #[pymodule_export] - use super::PyCharType; - #[pymodule_export] - use super::PyDoubleType; - #[pymodule_export] - use super::PyFloatType; - #[pymodule_export] - use super::PyIntegerType; - #[pymodule_export] - use super::PySmallIntegerType; - #[pymodule_export] - use super::PySmallUnsignedType; - #[pymodule_export] - use super::PyStringType; - #[pymodule_export] - use super::PyTextType; - #[pymodule_export] - use super::PyTinyIntegerType; - #[pymodule_export] - use super::PyTinyUnsignedType; - #[pymodule_export] - use super::PyUnsignedType; -} diff --git a/src/sqltypes/others.rs b/src/sqltypes/others.rs deleted file mode 100644 index 684433f..0000000 --- a/src/sqltypes/others.rs +++ /dev/null @@ -1,491 +0,0 @@ -use pyo3::types::PyAnyMethods; -use pyo3::IntoPyObject; -use sea_query::IntoIden; -use std::str::FromStr; - -use crate::sqltypes::abstracts::{PySQLTypeAbstract, SQLTypeTrait}; - -crate::implement_pyclass! { - /// Exact numeric decimal column type (DECIMAL/NUMERIC). - /// - /// Stores exact numeric values with fixed precision and scale. Essential for - /// financial calculations, currency values, or any situation where exact - /// decimal representation is required without floating-point approximation. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyDecimalType as "Decimal" (pub Option<(u32, u32)>); -} -crate::implement_pyclass! { - /// UUID column type (UUID). - /// - /// Stores universally unique identifiers. Ideal for distributed systems, - /// primary keys, or any situation where globally unique identifiers are - /// needed without central coordination. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyUUIDType as "UUID"; -} -crate::implement_pyclass! { - /// Internet address column type (INET). - /// - /// Stores IPv4 or IPv6 addresses, with or without subnet specification. - /// More flexible than CIDR type, allowing both host addresses and network ranges. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyINETType as "INET"; -} -crate::implement_pyclass! { - /// MAC address column type (MACADDR). - /// - /// Stores MAC (Media Access Control) addresses for network devices. - /// Provides validation and formatting for 6-byte MAC addresses. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyMacAddressType as "MacAddress"; -} -crate::implement_pyclass! { - /// Enumeration column type (ENUM). - /// - /// Stores one value from a predefined set of allowed string values. - /// Provides type safety and storage efficiency for categorical data - /// with a fixed set of possible values. - #[derive(Debug, Clone)] - [extends=PySQLTypeAbstract] PyEnumType as "Enum" { - pub name: sea_query::DynIden, - pub variants: Vec, - } -} - -impl SQLTypeTrait for PyDecimalType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Decimal(self.0) - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::PyLong_CheckExact(ptr) == 1 { - let mut has_overflow: i32 = 0; - let _ = pyo3::ffi::PyLong_AsLongLongAndOverflow(ptr, &mut has_overflow); - - if has_overflow == 1 { - return Err(pyo3::exceptions::PyOverflowError::new_err("out of range")); - } - - return Ok(()); - } - - if pyo3::ffi::Py_TYPE(ptr) == crate::typeref::STD_DECIMAL_TYPE - || pyo3::ffi::PyFloat_CheckExact(ptr) == 1 - { - return Ok(()); - } - - if pyo3::ffi::PyUnicode_CheckExact(ptr) == 1 { - let val = super::primitives::_serialize_string(py, ptr).map_err(|x| { - pyo3::PyErr::new::(x.to_string()) - })?; - - rust_decimal::Decimal::from_str_exact(&val).or_else(|_| { - rust_decimal::Decimal::from_scientific(&val).map_err(|x| { - pyo3::PyErr::new::(x.to_string()) - }) - })?; - return Ok(()); - } - - crate::new_error!( - PyTypeError, - "expected decimal.Decimal/int/float/str for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - if pyo3::ffi::PyLong_CheckExact(ptr) == 1 { - let mut has_overflow: i32 = 0; - let num = pyo3::ffi::PyLong_AsLongLongAndOverflow(ptr, &mut has_overflow); - - if has_overflow == 1 { - return Err(pyo3::exceptions::PyOverflowError::new_err("out of range")); - } - - return Ok(sea_query::Value::Decimal(Some(Box::new( - rust_decimal::Decimal::new(num, 0), - )))); - } - - if pyo3::ffi::PyFloat_CheckExact(ptr) == 1 { - use rust_decimal::prelude::FromPrimitive; - let num = pyo3::ffi::PyFloat_AsDouble(ptr); - - return rust_decimal::Decimal::from_f64(num) - .ok_or_else(|| { - pyo3::PyErr::new::( - "This value cannot be represented as a decimal", - ) - }) - .map(|x| sea_query::Value::Decimal(Some(Box::new(x)))); - } - - if pyo3::ffi::PyUnicode_CheckExact(ptr) == 1 { - let val = super::primitives::_serialize_string(py, ptr).map_err(|x| { - pyo3::PyErr::new::(x.to_string()) - })?; - - return rust_decimal::Decimal::from_str_exact(&val) - .or_else(|_| { - rust_decimal::Decimal::from_scientific(&val).map_err(|x| { - pyo3::PyErr::new::(x.to_string()) - }) - }) - .map(|x| sea_query::Value::Decimal(Some(Box::new(x)))); - } - - if pyo3::ffi::Py_TYPE(ptr) == crate::typeref::STD_DECIMAL_TYPE { - let borrowed = pyo3::Borrowed::from_ptr(py, ptr); - let val = borrowed.to_string(); - - return rust_decimal::Decimal::from_str_radix(&val, 10) - .or_else(|_| { - rust_decimal::Decimal::from_scientific(&val).map_err(|x| { - pyo3::PyErr::new::(x.to_string()) - }) - }) - .map(|x| sea_query::Value::Decimal(Some(Box::new(x)))); - } - - crate::new_error!( - PyTypeError, - "expected decimal.Decimal/int/float/str for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - use pyo3::IntoPyObject; - - match value { - sea_query::Value::Decimal(Some(x)) => x.into_pyobject(py).map(|x| x.into_ptr()), - sea_query::Value::Decimal(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected decimal for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyUUIDType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Uuid - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::Py_TYPE(ptr) != crate::typeref::STD_UUID_TYPE { - crate::new_error!( - PyTypeError, - "expected uuid.UUID for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - let result = pyo3::Bound::from_borrowed_ptr(py, ptr) - .clone() - .extract::<::uuid::Uuid>()?; - - Ok(sea_query::Value::Uuid(Some(Box::new(result)))) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Uuid(Some(x)) => { - let result = x.clone().into_pyobject(py)?; - Ok(result.into_ptr()) - } - sea_query::Value::Uuid(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected uuid for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyINETType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Inet - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - let string = super::primitives::_serialize_string(py, ptr)?; - - ipnetwork::Ipv4Network::from_str(&string) - .map_err(|x| pyo3::exceptions::PyValueError::new_err(x.to_string()))?; - - Ok(()) - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - let string = super::primitives::_serialize_string(py, ptr)?; - - ipnetwork::Ipv4Network::from_str(&string) - .map(|x| sea_query::Value::IpNetwork(Some(Box::new(ipnetwork::IpNetwork::V4(x))))) - .map_err(|x| pyo3::exceptions::PyValueError::new_err(x.to_string())) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::IpNetwork(Some(x)) => { - super::primitives::_deserialize_string(py, &x.to_string()) - } - sea_query::Value::IpNetwork(None) => Ok(pyo3::ffi::Py_None()), - sea_query::Value::String(Some(x)) => super::primitives::_deserialize_string(py, x), - sea_query::Value::String(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected ipnetwork/str for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyMacAddressType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::MacAddr - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - let string = super::primitives::_serialize_string(py, ptr)?; - - mac_address::MacAddress::from_str(&string) - .map_err(|x| pyo3::exceptions::PyValueError::new_err(x.to_string()))?; - - Ok(()) - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - let string = super::primitives::_serialize_string(py, ptr)?; - - mac_address::MacAddress::from_str(&string) - .map(|x| sea_query::Value::MacAddress(Some(Box::new(x)))) - .map_err(|x| pyo3::exceptions::PyValueError::new_err(x.to_string())) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::MacAddress(Some(x)) => { - super::primitives::_deserialize_string(py, &x.to_string()) - } - sea_query::Value::MacAddress(None) => Ok(pyo3::ffi::Py_None()), - sea_query::Value::String(Some(x)) => super::primitives::_deserialize_string(py, x), - sea_query::Value::String(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected mac_address/str for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -const ENUM_TYPE_VALUE: &std::ffi::CStr = c"value"; - -impl SQLTypeTrait for PyEnumType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Enum { - name: self.name.clone(), - variants: self.variants.clone(), - } - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - // https://github.com/ijl/orjson/blob/master/src/util.rs#L55 - if (*pyo3::ffi::Py_TYPE(ptr)).ob_base.ob_base.ob_type == crate::typeref::STD_ENUM_TYPE { - let attribute = pyo3::ffi::PyObject_GetAttrString(ptr, ENUM_TYPE_VALUE.as_ptr()); - - if attribute.is_null() { - return Err(pyo3::PyErr::fetch(py)); - } - - if pyo3::ffi::PyUnicode_CheckExact(attribute) == 0 { - let type_error = crate::new_error!( - PyTypeError, - "Enum value wasn't str for {} serialization, was {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, attribute) - ); - - pyo3::ffi::Py_DECREF(attribute); - return type_error; - } - } else if pyo3::ffi::PyUnicode_CheckExact(ptr) == 0 { - return crate::new_error!( - PyTypeError, - "expected Enum/str for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ); - } - - Ok(()) - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - let string = { - if (*pyo3::ffi::Py_TYPE(ptr)).ob_base.ob_base.ob_type == crate::typeref::STD_ENUM_TYPE { - let attribute = pyo3::ffi::PyObject_GetAttrString(ptr, ENUM_TYPE_VALUE.as_ptr()); - - if attribute.is_null() { - return Err(pyo3::PyErr::fetch(py)); - } - - // SAFETY: Type of the attribute was checked in `validate` method - attribute - } else { - pyo3::ffi::Py_INCREF(ptr); - - // SAFETY: Type of the attribute was checked in `validate` method - ptr - } - }; - - let serialized = super::primitives::_serialize_string(py, string); - pyo3::ffi::Py_DECREF(string); - - serialized.map(|x| sea_query::Value::String(Some(Box::new(x)))) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::String(Some(x)) => super::primitives::_deserialize_string(py, x), - sea_query::Value::String(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected str for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -super::abstracts::implement_sqltype_pymethods!(PyUUIDType); -super::abstracts::implement_sqltype_pymethods!(PyINETType); -super::abstracts::implement_sqltype_pymethods!(PyMacAddressType); -super::abstracts::implement_sqltype_pymethods!( - PyDecimalType, - init(|context: Option<(u32, u32)>| Self(context)), - "tuple[int, int] | None", - signature(context = None) -); - -#[pyo3::pymethods] -impl PyEnumType { - #[new] - fn __new__(name: String, variants: Vec) -> (Self, PySQLTypeAbstract) { - let slf = Self { - name: sea_query::Alias::new(name).into_iden(), - variants: variants - .into_iter() - .map(|x| sea_query::Alias::new(x).into_iden()) - .collect(), - }; - - (slf, PySQLTypeAbstract) - } - - /// Type name. e.g. `'INTEGER'`, `'STRING'` - /// - /// It also may be a property. This function must NOT raise any error. - #[getter] - fn __type_name__(&self) -> String { - self.to_sql_type_name() - } - - #[getter] - fn name(&self) -> String { - self.name.to_string() - } - - #[getter] - fn variants(&self) -> Vec { - self.variants.iter().map(|x| x.to_string()).collect() - } - - fn __repr__(&self) -> String { - format!( - "EnumType(name={:?}, variants={:?})", - self.name.to_string(), - self.variants.iter().map(|x| x.to_string()) - ) - } -} diff --git a/src/sqltypes/primitives.rs b/src/sqltypes/primitives.rs deleted file mode 100644 index a4cb8ff..0000000 --- a/src/sqltypes/primitives.rs +++ /dev/null @@ -1,556 +0,0 @@ -use crate::sqltypes::abstracts::{PySQLTypeAbstract, SQLTypeTrait}; - -crate::implement_pyclass! { - /// Boolean column type (BOOLEAN). - /// - /// Stores true/false values. The standard way to store boolean data, - /// though implementation varies by database (some use TINYINT(1) or - /// similar representations). - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyBooleanType as "Boolean"; -} -crate::implement_pyclass! { - /// Very small integer column type (TINYINT). - /// - /// Typically stores integers in the range -128 to 127 (signed) or 0 to 255 - /// (unsigned). Useful for flags, small counters, or enumerated values. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyTinyIntegerType as "TinyInteger"; -} -crate::implement_pyclass! { - /// Small integer column type (SMALLINT). - /// - /// Typically stores integers in the range -32,768 to 32,767 (signed) or - /// 0 to 65,535 (unsigned). Good for moderate-sized counters or numeric codes. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PySmallIntegerType as "SmallInteger"; -} -crate::implement_pyclass! { - /// Standard integer column type (INTEGER/INT). - /// - /// The most common integer type, typically storing 32-bit integers in the - /// range -2,147,483,648 to 2,147,483,647 (signed). Suitable for most - /// numeric data including IDs, quantities, and counters. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyIntegerType as "Integer"; -} -crate::implement_pyclass! { - /// Large integer column type (BIGINT). - /// - /// Stores 64-bit integers for very large numeric values. Essential for - /// high-volume systems, timestamps, large counters, or when integer - /// overflow is a concern. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyBigIntegerType as "BigInteger"; -} -crate::implement_pyclass! { - /// Unsigned tiny integer column type. - /// - /// Stores small positive integers only, typically 0 to 255. Useful for - /// small counters, percentages, or enumerated values that are always positive. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyTinyUnsignedType as "TinyUnsigned"; -} -crate::implement_pyclass! { - /// Unsigned small integer column type. - /// - /// Stores moderate positive integers only, typically 0 to 65,535. Good for - /// larger counters or numeric codes that are always positive. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PySmallUnsignedType as "SmallUnsigned"; -} -crate::implement_pyclass! { - /// Unsigned integer column type. - /// - /// Stores positive integers only, typically 0 to 4,294,967,295. Doubles the - /// positive range compared to signed integers, useful for IDs and counters - /// that will never be negative. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyUnsignedType as "Unsigned"; -} -crate::implement_pyclass! { - /// Unsigned big integer column type. - /// - /// Stores very large positive integers only. Provides the maximum positive - /// integer range for high-volume systems or when very large positive - /// values are required. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyBigUnsignedType as "BigUnsigned"; -} -crate::implement_pyclass! { - /// Single-precision floating point column type (FLOAT). - /// - /// Stores approximate numeric values with single precision. Suitable for - /// scientific calculations, measurements, or any numeric data where some - /// precision loss is acceptable in exchange for storage efficiency. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyFloatType as "Float"; -} -crate::implement_pyclass! { - /// Double-precision floating point column type (DOUBLE). - /// - /// Stores approximate numeric values with double precision. Provides higher - /// precision than FLOAT for scientific calculations or when more accuracy - /// is required in floating-point operations. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyDoubleType as "Double"; -} -crate::implement_pyclass! { - /// Large text column type (TEXT). - /// - /// Represents a large text field capable of storing long strings without - /// a predefined length limit. Suitable for storing articles, comments, - /// descriptions, or any text content that may be very long. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyTextType as "Text"; -} -crate::implement_pyclass! { - /// Fixed-length character string column type (CHAR). - /// - /// Represents a fixed-length character string. Values shorter than the - /// specified length are padded with spaces. Suitable for storing data - /// with consistent, known lengths like country codes or status flags. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyCharType as "Char" (pub Option); -} -crate::implement_pyclass! { - /// Variable-length character string column type (VARCHAR). - /// - /// Represents a variable-length character string with a maximum length limit. - /// This is the most common string type for storing text data of varying lengths - /// like names, descriptions, or user input. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyStringType as "String" (pub Option); -} - -impl SQLTypeTrait for PyBooleanType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Boolean - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::PyBool_Check(ptr) != 1 { - crate::new_error!( - PyTypeError, - "expected bool for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - _py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - if pyo3::ffi::Py_True() == ptr { - Ok(sea_query::Value::Bool(Some(true))) - } else { - Ok(sea_query::Value::Bool(Some(false))) - } - } - - unsafe fn deserialize( - &self, - _py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Bool(Some(x)) if *x => Ok(pyo3::ffi::Py_True()), - sea_query::Value::Bool(Some(x)) if !*x => Ok(pyo3::ffi::Py_False()), - sea_query::Value::Bool(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected bool for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -macro_rules! implement_numeric_SQLTypeTrait { - ( - $name:ident, - $type:ty, - $python_type_name:literal, - $column_type:ident, - $value_type:ident, - $convertfunction:ident, - $($checkfunction:ident,)+ - ) => { - impl SQLTypeTrait for $name { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::$column_type - } - - unsafe fn validate(&self, py: pyo3::Python, ptr: *mut pyo3::ffi::PyObject) -> pyo3::PyResult<()> { - self.serialize(py, ptr)?; - Ok(()) - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - $( - if pyo3::ffi::$checkfunction(ptr) == 1 { - let val = pyo3::ffi::$convertfunction(ptr); - - if !pyo3::ffi::PyErr_Occurred().is_null() { - return Err(pyo3::PyErr::fetch(py)); - } - - return TryInto::<$type>::try_into(val) - .map_err(|_| pyo3::exceptions::PyOverflowError::new_err("out of range")) - .map(|x| sea_query::Value::$value_type(Some(x))); - } - )+ - - crate::new_error!( - PyTypeError, - concat!("expected ", $python_type_name, " for {} serialization, got {}"), - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - use pyo3::IntoPyObject; - - match value { - sea_query::Value::BigInt(Some(x)) => Ok((*x).into_pyobject(py).unwrap().into_ptr()), - sea_query::Value::Int(Some(x)) => Ok((*x).into_pyobject(py).unwrap().into_ptr()), - sea_query::Value::SmallInt(Some(x)) => Ok((*x).into_pyobject(py).unwrap().into_ptr()), - sea_query::Value::TinyInt(Some(x)) => Ok((*x).into_pyobject(py).unwrap().into_ptr()), - sea_query::Value::BigUnsigned(Some(x)) => Ok((*x).into_pyobject(py).unwrap().into_ptr()), - sea_query::Value::Unsigned(Some(x)) => Ok((*x).into_pyobject(py).unwrap().into_ptr()), - sea_query::Value::SmallUnsigned(Some(x)) => Ok((*x).into_pyobject(py).unwrap().into_ptr()), - sea_query::Value::TinyUnsigned(Some(x)) => Ok((*x).into_pyobject(py).unwrap().into_ptr()), - sea_query::Value::Double(Some(x)) => Ok((*x).into_pyobject(py).unwrap().into_ptr()), - sea_query::Value::Float(Some(x)) => Ok((*x as f64).into_pyobject(py).unwrap().into_ptr()), - sea_query::Value::BigInt(None) - | sea_query::Value::Int(None) - | sea_query::Value::SmallInt(None) - | sea_query::Value::TinyInt(None) - | sea_query::Value::BigUnsigned(None) - | sea_query::Value::Unsigned(None) - | sea_query::Value::SmallUnsigned(None) - | sea_query::Value::TinyUnsigned(None) => Ok(py.None().into_ptr()), - _ => crate::new_error!( - PyTypeError, - "expected int for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } - } - }; -} - -implement_numeric_SQLTypeTrait!( - PyBigIntegerType, - i64, - "int", - BigInteger, - BigInt, - PyLong_AsLongLong, - PyLong_CheckExact, -); -implement_numeric_SQLTypeTrait!( - PyIntegerType, - i32, - "int", - Integer, - Int, - PyLong_AsLongLong, - PyLong_CheckExact, -); -implement_numeric_SQLTypeTrait!( - PySmallIntegerType, - i16, - "int", - SmallInteger, - SmallInt, - PyLong_AsLongLong, - PyLong_CheckExact, -); -implement_numeric_SQLTypeTrait!( - PyTinyIntegerType, - i8, - "int", - TinyInteger, - TinyInt, - PyLong_AsLongLong, - PyLong_CheckExact, -); -implement_numeric_SQLTypeTrait!( - PyBigUnsignedType, - u64, - "int", - BigUnsigned, - BigUnsigned, - PyLong_AsUnsignedLongLong, - PyLong_CheckExact, -); -implement_numeric_SQLTypeTrait!( - PyUnsignedType, - u32, - "int", - Unsigned, - Unsigned, - PyLong_AsUnsignedLong, - PyLong_CheckExact, -); -implement_numeric_SQLTypeTrait!( - PySmallUnsignedType, - u16, - "int", - SmallUnsigned, - SmallUnsigned, - PyLong_AsUnsignedLong, - PyLong_CheckExact, -); -implement_numeric_SQLTypeTrait!( - PyTinyUnsignedType, - u8, - "int", - TinyUnsigned, - TinyUnsigned, - PyLong_AsUnsignedLong, - PyLong_CheckExact, -); -implement_numeric_SQLTypeTrait!( - PyFloatType, - // We're using f64 instead of f32 'cause we don't have f32::try_from(f64) - f64, - "float", - Float, - Double, - PyFloat_AsDouble, - PyFloat_CheckExact, - PyLong_CheckExact, -); -implement_numeric_SQLTypeTrait!( - PyDoubleType, - f64, - "float", - Float, - Double, - PyFloat_AsDouble, - PyFloat_CheckExact, - PyLong_CheckExact, -); - -#[inline] -pub(super) unsafe fn _serialize_string( - py: pyo3::Python, - object: *mut pyo3::ffi::PyObject, -) -> pyo3::PyResult { - let mut size: pyo3::ffi::Py_ssize_t = 0; - let c_str = pyo3::ffi::PyUnicode_AsUTF8AndSize(object, &mut size); - - if c_str.is_null() || size < 0 { - Err(pyo3::PyErr::fetch(py)) - } else { - let val = std::ffi::CStr::from_ptr(c_str); - Ok(std::str::from_utf8_unchecked(val.to_bytes()).into()) - } -} - -#[inline] -pub(super) unsafe fn _deserialize_string( - py: pyo3::Python, - value: &str, -) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - let val = pyo3::types::PyString::intern(py, std::str::from_utf8_unchecked(value.as_bytes())); - Ok(val.into_ptr()) -} - -impl SQLTypeTrait for PyTextType { - #[inline(always)] - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Text - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::PyUnicode_CheckExact(ptr) == 0 { - crate::new_error!( - PyTypeError, - "expected str for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - object: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_string(py, object).map(|x| sea_query::Value::String(Some(Box::new(x)))) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::String(Some(x)) => _deserialize_string(py, x), - sea_query::Value::String(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected str for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyCharType { - #[inline(always)] - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Char(self.0) - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::PyUnicode_CheckExact(ptr) == 0 { - crate::new_error!( - PyTypeError, - "expected str for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - object: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_string(py, object).map(|x| sea_query::Value::String(Some(Box::new(x)))) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::String(Some(x)) => _deserialize_string(py, x), - sea_query::Value::String(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected str for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyStringType { - #[inline(always)] - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::String( - self.0 - .map_or(sea_query::StringLen::None, sea_query::StringLen::N), - ) - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - if pyo3::ffi::PyUnicode_CheckExact(ptr) == 0 { - crate::new_error!( - PyTypeError, - "expected str for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } else { - Ok(()) - } - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - object: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - _serialize_string(py, object).map(|x| sea_query::Value::String(Some(Box::new(x)))) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::String(Some(x)) => _deserialize_string(py, x), - sea_query::Value::String(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected str for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -super::abstracts::implement_sqltype_pymethods!(PyBooleanType); -super::abstracts::implement_sqltype_pymethods!(PyBigIntegerType); -super::abstracts::implement_sqltype_pymethods!(PyIntegerType); -super::abstracts::implement_sqltype_pymethods!(PySmallIntegerType); -super::abstracts::implement_sqltype_pymethods!(PyTinyIntegerType); -super::abstracts::implement_sqltype_pymethods!(PyBigUnsignedType); -super::abstracts::implement_sqltype_pymethods!(PyUnsignedType); -super::abstracts::implement_sqltype_pymethods!(PySmallUnsignedType); -super::abstracts::implement_sqltype_pymethods!(PyTinyUnsignedType); -super::abstracts::implement_sqltype_pymethods!(PyFloatType); -super::abstracts::implement_sqltype_pymethods!(PyDoubleType); -super::abstracts::implement_sqltype_pymethods!(PyTextType); -super::abstracts::implement_sqltype_pymethods!( - PyCharType, - init(|length: Option| Self(length)), - "int | None", - signature(length = None) -); -super::abstracts::implement_sqltype_pymethods!( - PyStringType, - init(|length: Option| Self(length)), - "int | None", - signature(length = None) -); diff --git a/src/sqltypes/vector.rs b/src/sqltypes/vector.rs deleted file mode 100644 index 7a1081c..0000000 --- a/src/sqltypes/vector.rs +++ /dev/null @@ -1,337 +0,0 @@ -use pyo3::types::{PyListMethods, PyTupleMethods}; -use pyo3::IntoPyObject; - -use crate::internal::type_engine::TypeEngine; -use crate::internal::RefBoundObject; -use crate::sqltypes::abstracts::{PySQLTypeAbstract, SQLTypeTrait}; - -crate::implement_pyclass! { - /// Vector column type for storing mathematical vectors. - /// - /// Specialized type for storing vector data, often used in machine learning, - /// similarity search, or mathematical applications. - #[derive(Debug, Clone, Copy)] - [extends=PySQLTypeAbstract] PyVectorType as "Vector" (pub Option); -} -crate::implement_pyclass! { - /// Array column type for storing arrays of elements. - /// - /// Represents a column that stores arrays of a specified element type. - /// Useful in databases that support native array types (like PostgreSQL) - /// for storing lists of values in a single column. - #[derive(Clone)] - [extends=PySQLTypeAbstract] PyArrayType as "Array" (pub TypeEngine); -} - -#[inline] -#[cfg_attr(feature = "optimize", optimize(speed))] -unsafe fn _validate_iterable_ptr( - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - sql_type_name: &str, - mut condition: F, -) -> pyo3::PyResult<()> -where - F: FnMut(*mut pyo3::ffi::PyObject) -> bool, -{ - // We won't check all of elements, we only check some of them to improve performance `O(log(n))`. - - if pyo3::ffi::PyList_CheckExact(ptr) == 1 { - let mut size = pyo3::ffi::PyList_Size(ptr); - if size == 0 { - return Ok(()); - } - - while size > 0 { - // item is a owned pointer - let item = pyo3::ffi::PyList_GetItem(ptr, size - 1); - if item.is_null() { - return Err(pyo3::PyErr::fetch(py)); - } - - if !condition(item) { - return Err(pyo3::exceptions::PyTypeError::new_err( - "invalid type found in the list", - )); - } - - size /= 2; - } - - return Ok(()); - } - - if pyo3::ffi::PyTuple_CheckExact(ptr) == 1 { - let mut size = pyo3::ffi::PyTuple_Size(ptr); - if size == 0 { - return Ok(()); - } - - while size > 0 { - // item is a borrowed pointer - let item = pyo3::ffi::PyTuple_GetItem(ptr, size - 1); - if item.is_null() { - return Err(pyo3::PyErr::fetch(py)); - } - - if !condition(item) { - return Err(pyo3::exceptions::PyTypeError::new_err( - "invalid type found in the tuple", - )); - } - - size /= 2; - } - - return Ok(()); - } - - crate::new_error!( - PyTypeError, - "expected list/tuple for {} serialization, got {}", - sql_type_name, - crate::internal::get_type_name(py, ptr) - ) -} - -impl SQLTypeTrait for PyVectorType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Vector(self.0) - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - _validate_iterable_ptr(py, ptr, &self.to_sql_type_name(), |item| { - pyo3::ffi::PyFloat_CheckExact(item) == 1 || pyo3::ffi::PyLong_CheckExact(item) == 1 - }) - } - - #[cfg_attr(feature = "optimize", optimize(speed))] - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - if pyo3::ffi::PyList_CheckExact(ptr) == 1 { - let bound = pyo3::Bound::from_borrowed_ptr(py, ptr); - let list = bound.cast_unchecked::(); - - let mut values: Vec = Vec::with_capacity(list.len()); - - for item in list.iter() { - if pyo3::ffi::PyFloat_CheckExact(item.as_ptr()) == 0 - && pyo3::ffi::PyLong_CheckExact(item.as_ptr()) == 0 - { - return crate::new_error!( - PyTypeError, - "expected list of floats for {} serialization, found {} in the list", - self.to_sql_type_name(), - crate::internal::get_type_name(py, item.as_ptr()) - ); - } - - values.push(pyo3::ffi::PyFloat_AsDouble(item.as_ptr()) as f32); - } - - return Ok(sea_query::Value::Vector(Some(Box::new(values.into())))); - } - - if pyo3::ffi::PyTuple_CheckExact(ptr) == 1 { - let bound = pyo3::Bound::from_borrowed_ptr(py, ptr); - let list = bound.cast_unchecked::(); - - let mut values: Vec = Vec::with_capacity(list.len()); - - for item in list.iter() { - if pyo3::ffi::PyFloat_CheckExact(item.as_ptr()) == 0 - && pyo3::ffi::PyLong_CheckExact(item.as_ptr()) == 0 - { - return crate::new_error!( - PyTypeError, - "expected tuple of floats for {} serialization, found {} in the tuple", - self.to_sql_type_name(), - crate::internal::get_type_name(py, item.as_ptr()) - ); - } - - values.push(pyo3::ffi::PyFloat_AsDouble(item.as_ptr()) as f32); - } - - return Ok(sea_query::Value::Vector(Some(Box::new(values.into())))); - } - - crate::new_error!( - PyTypeError, - "expected list[float]/tuple[float] for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } - - #[cfg_attr(feature = "optimize", optimize(speed))] - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Vector(Some(x)) => { - let list = x.as_slice().into_pyobject(py)?; - Ok(list.into_ptr()) - } - sea_query::Value::Vector(None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected vector for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -impl SQLTypeTrait for PyArrayType { - fn to_sea_query_column_type(&self) -> sea_query::ColumnType { - sea_query::ColumnType::Array(std::sync::Arc::new(self.0.to_sea_query_column_type())) - } - - unsafe fn validate( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult<()> { - _validate_iterable_ptr(py, ptr, &self.to_sql_type_name(), |item| { - self.0.validate(py, item).is_ok() - }) - } - - unsafe fn serialize( - &self, - py: pyo3::Python, - ptr: *mut pyo3::ffi::PyObject, - ) -> pyo3::PyResult { - if pyo3::ffi::PyList_CheckExact(ptr) == 1 { - let bound = pyo3::Bound::from_borrowed_ptr(py, ptr); - let list = bound.cast_unchecked::(); - - let mut values: Vec = Vec::with_capacity(list.len()); - - for item in list.iter() { - let val = self.0.serialize(py, item.as_ptr())?; - values.push(val); - } - - return Ok(sea_query::Value::Array( - // array type is not important - sea_query::ArrayType::BigInt, - Some(Box::new(values)), - )); - } - - if pyo3::ffi::PyTuple_CheckExact(ptr) == 1 { - let bound = pyo3::Bound::from_borrowed_ptr(py, ptr); - let list = bound.cast_unchecked::(); - - let mut values: Vec = Vec::with_capacity(list.len()); - - for item in list.iter() { - let val = self.0.serialize(py, item.as_ptr())?; - values.push(val); - } - - return Ok(sea_query::Value::Array( - // array type is not important - sea_query::ArrayType::BigInt, - Some(Box::new(values)), - )); - } - - crate::new_error!( - PyTypeError, - "expected list/tuple for {} serialization, got {}", - self.to_sql_type_name(), - crate::internal::get_type_name(py, ptr) - ) - } - - unsafe fn deserialize( - &self, - py: pyo3::Python, - value: &sea_query::Value, - ) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { - match value { - sea_query::Value::Array(_, Some(array)) => { - let pylist = pyo3::ffi::PyList_New(array.len() as isize); - - if pylist.is_null() { - return Err(pyo3::PyErr::fetch(py)); - } - - for (index, item) in array.iter().enumerate() { - let result = self.0.deserialize(py, item); - - match result { - Ok(x) => { - if pyo3::ffi::PyList_SetItem(pylist, index as isize, x) == 0 { - pyo3::ffi::Py_DECREF(x); - pyo3::ffi::Py_DECREF(pylist); - return Err(pyo3::PyErr::fetch(py)); - } - } - Err(e) => { - pyo3::ffi::Py_DECREF(pylist); - return Err(e); - } - } - } - - Ok(pylist) - } - sea_query::Value::Array(_, None) => Ok(pyo3::ffi::Py_None()), - _ => crate::new_error!( - PyTypeError, - "expected array for {} deserialization, got {:?}", - self.to_sql_type_name(), - value - ), - } - } -} - -super::abstracts::implement_sqltype_pymethods!( - PyVectorType, - init(|length: Option| Self(length)), - "int | None", - signature(length = None) -); - -#[pyo3::pymethods] -impl PyArrayType { - #[new] - fn __new__(element: RefBoundObject<'_>) -> pyo3::PyResult<(Self, PySQLTypeAbstract)> { - let type_engine = TypeEngine::new(element)?; - - Ok((Self(type_engine), PySQLTypeAbstract)) - } - - /// Type name. e.g. `'INTEGER'`, `'STRING'` - /// - /// It also may be a property. This function must NOT raise any error. - #[getter] - fn __type_name__(&self) -> String { - self.to_sql_type_name() - } - - #[getter] - fn element<'py>(&self, py: pyo3::Python<'py>) -> pyo3::Bound<'py, pyo3::PyAny> { - let ptr = self.0 .1.as_ptr(); - unsafe { pyo3::Bound::from_borrowed_ptr(py, ptr) } - } - - fn __repr__(&self) -> String { - format!("ArrayType(element={})", self.0) - } -} diff --git a/src/typeref.rs b/src/typeref.rs deleted file mode 100644 index 218b5ab..0000000 --- a/src/typeref.rs +++ /dev/null @@ -1,172 +0,0 @@ -// Column types -pub(crate) static mut BLOB_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut BINARY_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut VAR_BINARY_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut BIT_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut VAR_BIT_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut DATETIME_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut TIMESTAMP_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut TIME_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut DATE_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut JSON_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut JSON_BINARY_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut DECIMAL_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut UUID_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut INET_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut MAC_ADDRESS_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut BOOLEAN_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut TINY_INTEGER_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut SMALL_INTEGER_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = - std::ptr::null_mut(); -pub(crate) static mut INTEGER_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut BIG_INTEGER_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut TINY_UNSIGNED_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = - std::ptr::null_mut(); -pub(crate) static mut SMALL_UNSIGNED_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = - std::ptr::null_mut(); -pub(crate) static mut UNSIGNED_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut BIG_UNSIGNED_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut FLOAT_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut DOUBLE_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut TEXT_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut CHAR_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut STRING_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut VECTOR_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut ARRAY_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut ENUM_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); - -pub(crate) static mut VALUE_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut COLUMN_REF_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut TABLE_NAME_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut EXPR_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut FUNC_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut INDEX_COLUMN_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut INDEX_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut FOREIGN_KEY_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut TABLE_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut ON_CONFLICT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut WINDOW_STATEMENT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut SELECT_LABEL_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut SELECT_STATEMENT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut DELETE_STATEMENT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut UPDATE_STATEMENT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut INSERT_STATEMENT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut CASE_STATEMENT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut WITH_CLAUSE_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut ALTER_TABLE_BASE_OPTION_TYPE: *mut pyo3::ffi::PyTypeObject = - std::ptr::null_mut(); - -// Python standard libraries types -pub(crate) static mut STD_DECIMAL_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut STD_UUID_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut STD_DATETIME_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut STD_DATE_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut STD_TIME_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); -pub(crate) static mut STD_ENUM_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); - -unsafe fn get_type_object_for( - py: pyo3::Python, -) -> *mut pyo3::ffi::PyTypeObject { - T::type_object_raw(py) -} - -unsafe fn look_up_type_object( - module_name: &std::ffi::CStr, - member_name: &std::ffi::CStr, -) -> *mut pyo3::ffi::PyTypeObject { - let module = pyo3::ffi::PyImport_ImportModule(module_name.as_ptr()); - let module_dict = pyo3::ffi::PyObject_GenericGetDict(module, std::ptr::null_mut()); - let ptr = pyo3::ffi::PyMapping_GetItemString(module_dict, member_name.as_ptr()) - .cast::(); - - pyo3::ffi::Py_DECREF(module_dict); - pyo3::ffi::Py_DECREF(module); - ptr -} - -macro_rules! multiple_get_type_object_for { - ($py:expr, $($type:ty => $name:ident,)*) => { - $($name = get_type_object_for::<$type>($py);)* - }; -} - -#[cold] -#[cfg_attr(feature = "optimize", optimize(size))] -fn _initialize_typeref(py: pyo3::Python) { - unsafe { - multiple_get_type_object_for!( - py, - crate::sqltypes::PyBlobType => BLOB_COLUMN_TYPE, - crate::sqltypes::PyBinaryType => BINARY_COLUMN_TYPE, - crate::sqltypes::PyVarBinaryType => VAR_BINARY_COLUMN_TYPE, - crate::sqltypes::PyBitType => BIT_COLUMN_TYPE, - crate::sqltypes::PyVarBitType => VAR_BIT_COLUMN_TYPE, - crate::sqltypes::PyDateTimeType => DATETIME_COLUMN_TYPE, - crate::sqltypes::PyTimestampType => TIMESTAMP_COLUMN_TYPE, - crate::sqltypes::PyTimeType => TIME_COLUMN_TYPE, - crate::sqltypes::PyDateType => DATE_COLUMN_TYPE, - crate::sqltypes::PyJSONType => JSON_COLUMN_TYPE, - crate::sqltypes::PyJSONBinaryType => JSON_BINARY_COLUMN_TYPE, - crate::sqltypes::PyDecimalType => DECIMAL_COLUMN_TYPE, - crate::sqltypes::PyUUIDType => UUID_COLUMN_TYPE, - crate::sqltypes::PyINETType => INET_COLUMN_TYPE, - crate::sqltypes::PyMacAddressType => MAC_ADDRESS_COLUMN_TYPE, - crate::sqltypes::PyBooleanType => BOOLEAN_COLUMN_TYPE, - crate::sqltypes::PyTinyIntegerType => TINY_INTEGER_COLUMN_TYPE, - crate::sqltypes::PySmallIntegerType => SMALL_INTEGER_COLUMN_TYPE, - crate::sqltypes::PyIntegerType => INTEGER_COLUMN_TYPE, - crate::sqltypes::PyBigIntegerType => BIG_INTEGER_COLUMN_TYPE, - crate::sqltypes::PyTinyUnsignedType => TINY_UNSIGNED_COLUMN_TYPE, - crate::sqltypes::PySmallUnsignedType => SMALL_UNSIGNED_COLUMN_TYPE, - crate::sqltypes::PyUnsignedType => UNSIGNED_COLUMN_TYPE, - crate::sqltypes::PyBigUnsignedType => BIG_UNSIGNED_COLUMN_TYPE, - crate::sqltypes::PyFloatType => FLOAT_COLUMN_TYPE, - crate::sqltypes::PyDoubleType => DOUBLE_COLUMN_TYPE, - crate::sqltypes::PyTextType => TEXT_COLUMN_TYPE, - crate::sqltypes::PyCharType => CHAR_COLUMN_TYPE, - crate::sqltypes::PyStringType => STRING_COLUMN_TYPE, - crate::sqltypes::PyVectorType => VECTOR_COLUMN_TYPE, - crate::sqltypes::PyArrayType => ARRAY_COLUMN_TYPE, - crate::sqltypes::PyEnumType => ENUM_COLUMN_TYPE, - crate::common::value::PyValue => VALUE_TYPE, - crate::common::table_ref::PyTableName => TABLE_NAME_TYPE, - crate::common::column_ref::PyColumnRef => COLUMN_REF_TYPE, - crate::common::expression::PyExpr => EXPR_TYPE, - crate::common::expression::PyFunc => FUNC_TYPE, - crate::common::column::PyColumn => COLUMN_TYPE, - crate::common::foreign_key::PyForeignKey => FOREIGN_KEY_TYPE, - crate::schema::index::PyIndexColumn => INDEX_COLUMN_TYPE, - crate::schema::index::PyIndex => INDEX_TYPE, - crate::schema::alter_table::PyAlterTableBaseOption => ALTER_TABLE_BASE_OPTION_TYPE, - crate::schema::table::PyTable => TABLE_TYPE, - crate::query::on_conflict::PyOnConflict => ON_CONFLICT_TYPE, - crate::query::window::PyWindowStatement => WINDOW_STATEMENT_TYPE, - crate::query::select::PySelectLabel => SELECT_LABEL_TYPE, - crate::query::select::PySelectStatement => SELECT_STATEMENT_TYPE, - crate::query::delete::PyDeleteStatement => DELETE_STATEMENT_TYPE, - crate::query::update::PyUpdateStatement => UPDATE_STATEMENT_TYPE, - crate::query::insert::PyInsertStatement => INSERT_STATEMENT_TYPE, - crate::query::case::PyCaseStatement => CASE_STATEMENT_TYPE, - crate::query::with::PyWithClause => WITH_CLAUSE_TYPE, - ); - - STD_DECIMAL_TYPE = look_up_type_object(c"decimal", c"Decimal"); - STD_UUID_TYPE = look_up_type_object(c"uuid", c"UUID"); - STD_ENUM_TYPE = look_up_type_object(c"enum", c"EnumMeta"); - - pyo3::ffi::PyDateTime_IMPORT(); - let datetime_capsule = pyo3::ffi::PyCapsule_Import(c"datetime.datetime_CAPI".as_ptr(), 1) - .cast::(); - - STD_DATETIME_TYPE = (*datetime_capsule).DateTimeType; - STD_DATE_TYPE = (*datetime_capsule).DateType; - STD_TIME_TYPE = (*datetime_capsule).TimeType; - } -} - -pub fn initialize_typeref(py: pyo3::Python) { - static INIT: std::sync::Once = std::sync::Once::new(); - - INIT.call_once(|| _initialize_typeref(py)); -} From 75f39b8fc1447bbf3998ecb39e80a5c334c28d9d Mon Sep 17 00:00:00 2001 From: awolverp Date: Sun, 21 Jun 2026 17:38:44 +0330 Subject: [PATCH 2/5] Complete data-types --- Cargo.lock | 293 +++++++ Cargo.toml | 15 +- rapidquery/__init__.py | 2 + rapidquery/_lib/__init__.pyi | 6 + rapidquery/_lib/_lib/__init__.pyi | 0 rapidquery/_lib/_lib/data_type.pyi | 125 +++ src/ast/data_type/mod.rs | 57 ++ src/ast/data_type/name.rs | 201 +++++ src/ast/data_type/serializers.rs | 1159 ++++++++++++++++++++++++++++ src/ast/mod.rs | 7 + src/dialect/mod.rs | 59 ++ src/internal/alias.rs | 15 + src/internal/mod.rs | 3 + src/internal/onceinit.rs | 158 ++++ src/internal/utils.rs | 19 + src/lib.rs | 19 +- src/macro_rules.rs | 133 ++++ src/typeref.rs | 94 +++ uv.lock | 732 +++--------------- 19 files changed, 2474 insertions(+), 623 deletions(-) create mode 100644 rapidquery/_lib/__init__.pyi create mode 100644 rapidquery/_lib/_lib/__init__.pyi create mode 100644 rapidquery/_lib/_lib/data_type.pyi create mode 100644 src/ast/data_type/mod.rs create mode 100644 src/ast/data_type/name.rs create mode 100644 src/ast/data_type/serializers.rs create mode 100644 src/ast/mod.rs create mode 100644 src/dialect/mod.rs create mode 100644 src/internal/alias.rs create mode 100644 src/internal/mod.rs create mode 100644 src/internal/onceinit.rs create mode 100644 src/internal/utils.rs create mode 100644 src/macro_rules.rs create mode 100644 src/typeref.rs diff --git a/Cargo.lock b/Cargo.lock index 962c5a3..91432a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ar_archive_writer" version = "0.5.2" @@ -11,6 +20,12 @@ dependencies = [ "object", ] +[[package]] +name = "arcstr" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d" + [[package]] name = "autocfg" version = "1.5.0" @@ -23,6 +38,12 @@ version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "cc" version = "1.2.64" @@ -39,18 +60,106 @@ 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.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" + +[[package]] +name = "js-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.175" @@ -73,12 +182,53 @@ version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "memchr" version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.37.3" @@ -117,6 +267,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -148,6 +304,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c" dependencies = [ + "chrono", "libc", "once_cell", "portable-atomic", @@ -212,6 +369,10 @@ dependencies = [ name = "rapidquery" version = "0.1.0" dependencies = [ + "arcstr", + "chrono", + "ipnetwork", + "mac_address", "parking_lot", "pyo3", "sqlparser", @@ -246,6 +407,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "scopeguard" version = "1.2.0" @@ -258,6 +425,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" @@ -322,12 +495,132 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +[[package]] +name = "wasm-bindgen" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index 1ba3a0c..9b4122b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,24 +16,29 @@ crate-type = ["cdylib", "rlib"] [profile.dev] lto = true panic = "unwind" -strip = true +strip = false [profile.release] codegen-units = 1 debug = false incremental = false lto = true -panic = "unwind" -strip = true +panic = "abort" +strip = false [dependencies] -pyo3 = { version = "0.29", default-features = false, features = ["macros"] } +pyo3 = { version = "0.29", default-features = false, features = ["macros", "experimental-inspect", "chrono"] } parking_lot = { version = "0.12", default-features = false } sqlparser = { version = "0.62", default-features = false, features = ["recursive", "recursive-protection", "visitor"] } +arcstr = { version = "1.2" } +chrono = { version = "0.4", default-features = false, features = ["clock"] } +ipnetwork = { version = "0.20", default-features = false } +mac_address = { version = "1.1", default-features = false } [features] -default = ["extension-module"] +default = ["extension-module", "experimental-inspect"] extension-module = ["pyo3/extension-module"] +experimental-inspect = ["pyo3/experimental-inspect"] [lints.clippy] dbg_macro = "warn" diff --git a/rapidquery/__init__.py b/rapidquery/__init__.py index 1c44260..dc5bdfc 100644 --- a/rapidquery/__init__.py +++ b/rapidquery/__init__.py @@ -4,3 +4,5 @@ Build complex SQL queries effortlessly and efficiently, with a library that prioritizes both performance and ease of use. """ + +from . import _lib as _lib diff --git a/rapidquery/_lib/__init__.pyi b/rapidquery/_lib/__init__.pyi new file mode 100644 index 0000000..e121e34 --- /dev/null +++ b/rapidquery/_lib/__init__.pyi @@ -0,0 +1,6 @@ +""" +RapidQuery core which is written in Rust. +""" + +from _typeshed import Incomplete +def __getattr__(name: str) -> Incomplete: ... diff --git a/rapidquery/_lib/_lib/__init__.pyi b/rapidquery/_lib/_lib/__init__.pyi new file mode 100644 index 0000000..e69de29 diff --git a/rapidquery/_lib/_lib/data_type.pyi b/rapidquery/_lib/_lib/data_type.pyi new file mode 100644 index 0000000..19595c4 --- /dev/null +++ b/rapidquery/_lib/_lib/data_type.pyi @@ -0,0 +1,125 @@ +from typing import Any, Callable, final + +@final +class ArraySerializer: + """ + Serializes a Python `list`/`tuple` by serializing each element with `element` + and wrapping in `ARRAY[...]`. + """ + def __new__(cls, /, element: 'ArraySerializer' |'BitSerializer' |'BooleanSerializer' |'BytesSerializer' |'DateSerializer' |'DatetimeSerializer' |'FloatSerializer' |'InetSerializer' |'IntegerSerializer' |'JSONSerializer' |'MacAddressSerializer' |'StringSerializer' |'TimeSerializer' |'UnspecifiedSerializer' |'UuidSerializer' |Callable[[Any, str], str]) -> ArraySerializer: ... + +@final +class BitSerializer: + """ + Serializes a bit string given as a `str` of `0`/`1` (e.g. `"1010"`) into a + `B'1010'` literal. + """ + def __new__(cls, /) -> BitSerializer: ... + +@final +class BooleanSerializer: + def __new__(cls, /) -> BooleanSerializer: ... + +@final +class BytesSerializer: + """ + Serializes a `uuid.UUID` (or 36-char `str`) as a quoted literal. + """ + def __new__(cls, /) -> BytesSerializer: ... + +@final +class DataType: + def __new__(cls, /, name: str, serializer: 'ArraySerializer' |'BitSerializer' |'BooleanSerializer' |'BytesSerializer' |'DateSerializer' |'DatetimeSerializer' |'FloatSerializer' |'InetSerializer' |'IntegerSerializer' |'JSONSerializer' |'MacAddressSerializer' |'StringSerializer' |'TimeSerializer' |'UnspecifiedSerializer' |'UuidSerializer' |Callable[[Any, str], str] |None = None) -> DataType: ... + @property + def name(self, /) -> str: ... + @property + def serializer(self, /) -> Any: ... + +@final +class DateSerializer: + """ + `datetime.date` -> `'YYYY-MM-DD'`. + """ + def __new__(cls, /) -> DateSerializer: ... + +@final +class DatetimeSerializer: + """ + `datetime.datetime` -> `'YYYY-MM-DD HH:MM:SS[.ffffff][+HH:MM]'`. + + `int`/`float` are treated as a UTC epoch and accepted too. + """ + def __new__(cls, /) -> DatetimeSerializer: ... + +@final +class FloatSerializer: + """ + Serializes Python `float`, `int`, `decimal.Decimal`, or numeric `str` as a + bare numeric literal. + + `Decimal`/`str`/`int` go through their exact text form (no precision loss); + only a real `float` is formatted from the f64. Non-finite floats are rejected. + """ + def __new__(cls, /) -> FloatSerializer: ... + +@final +class InetSerializer: + """ + `str` (or `ipaddress.*`) -> quoted literal. Validated as IPv4/IPv6 network. + """ + def __new__(cls, /) -> InetSerializer: ... + +@final +class IntegerSerializer: + """ + Serializes any Python `int` as a bare decimal literal. + + Uses `str(int)` so arbitrary precision (beyond i64/u64) is preserved - one + serializer genuinely covers TINYINT through BIGINT and the unsigned variants. + """ + def __new__(cls, /) -> IntegerSerializer: ... + +@final +class JSONSerializer: + """ + Serializes any JSON-encodable Python object via `json.dumps`, then wraps the + result as a quoted string literal. + """ + def __new__(cls, /) -> JSONSerializer: ... + +@final +class MacAddressSerializer: + """ + `str` -> quoted literal. Validated as a MAC address. + """ + def __new__(cls, /) -> MacAddressSerializer: ... + +@final +class StringSerializer: + """ + Serializes a Python `str` (or `enum.Enum` whose value is a str) as a quoted, + escaped string literal: `"LIKE"` -> `'LIKE'`. + """ + def __new__(cls, /) -> StringSerializer: ... + +@final +class TimeSerializer: + """ + `datetime.time` -> `'HH:MM:SS[.ffffff]'`. + """ + def __new__(cls, /) -> TimeSerializer: ... + +@final +class UnspecifiedSerializer: + """ + When you don't know about data type, you can use this. Uses values' `__str__` method + and return it as a serialized SQL value. + """ + def __new__(cls, /) -> UnspecifiedSerializer: ... + +@final +class UuidSerializer: + """ + Serializes a `uuid.UUID` (or 36-char `str`) as a quoted literal. + """ + def __new__(cls, /) -> UuidSerializer: ... diff --git a/src/ast/data_type/mod.rs b/src/ast/data_type/mod.rs new file mode 100644 index 0000000..fc700e4 --- /dev/null +++ b/src/ast/data_type/mod.rs @@ -0,0 +1,57 @@ +use std::sync::Arc; + +mod name; +pub use name::DataTypeName; + +pub mod serializers; +pub use serializers::DataTypeSerializer; + +use crate::internal::alias; + +implement_pyclass! { + #[derive(Debug, Clone)] + [skip_from_py_object] PyDataType as "DataType" { + pub name: DataTypeName, + pub serializer: Arc, + } +} + +#[pyo3::pymethods] +impl PyDataType { + #[new] + #[pyo3(signature = (name, serializer=None) )] + fn __new__(name: String, serializer: Option) -> Self { + let name = DataTypeName::from(name.as_str()); + + let serializer = match serializer { + Some(x) => Arc::new(x), + None => Arc::new(DataTypeSerializer::infer_from_name(&name)), + }; + + Self { name, serializer } + } + + #[getter] + fn name(&self) -> String { + self.name.to_string() + } + + #[getter] + fn serializer<'a>(&self, py: pyo3::Python<'a>) -> pyo3::PyResult> { + self.serializer.to_pyobject(py) + } +} + +#[pyo3::pymodule(name = "data_type")] +pub mod data_type_module { + #[pymodule_export] + pub use super::PyDataType; + + #[pymodule_export] + pub use super::serializers::{ + PyArraySerializer, PyBitSerializer, PyBooleanSerializer, PyBytesSerializer, + PyDateSerializer, PyDatetimeSerializer, PyFloatSerializer, PyInetSerializer, + PyIntegerSerializer, PyJSONSerializer, PyMacAddressSerializer, PyStringSerializer, + PyTimeSerializer, PyUnspecifiedSerializer, PyUuidSerializer, + }; +} diff --git a/src/ast/data_type/name.rs b/src/ast/data_type/name.rs new file mode 100644 index 0000000..1bb20d0 --- /dev/null +++ b/src/ast/data_type/name.rs @@ -0,0 +1,201 @@ +/// SQL data types +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub struct DataTypeName(arcstr::ArcStr); + +// Create all no-argument data types once. This reduces memory footprint, thanks to `arcstr::ArcStr`. +macro_rules! simple_data_types { + ( + $( + $(#[$docs:meta])* + ($upcase:ident, $realname:literal, $($possiblenames:literal,)*); + )+ + ) => { + $( + $(#[$docs])* + pub(super) const $upcase: DataTypeName = DataTypeName(arcstr::literal!($realname)); + )+ + + impl From<&str> for DataTypeName { + fn from(value: &str) -> Self { + match value { + $( + $realname $(| $possiblenames)* => $upcase, + )+ + _ => DataTypeName(arcstr::ArcStr::from(value)), + } + } + } + }; +} + +simple_data_types! { + /// Uuid type. + (UUID_DATA_TYPE, "UUID", "uuid",); + /// [MySQL] blob with up to 2**8 bytes. + /// + /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html + (TINY_BLOB_DATA_TYPE, "TINYBLOB", "tinyblob",); + /// [MySQL] blob with up to 2**24 bytes. + /// + /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html + (MEDIUM_BLOB_DATA_TYPE, "MEDIUMBLOB", "mediumblob",); + /// [MySQL] blob with up to 2**32 bytes. + /// + /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html + (LONG_BLOB_DATA_TYPE, "LONGBLOB", "longblob",); + /// Unsigned tiny integer, e.g. UTINYINT + (U_TINY_INT_DATA_TYPE, "UTINYINT", "utinyint",); + /// Unsigned tiny integer, e.g. UTINYINT + (U_SMALL_INT_DATA_TYPE, "USMALLINT", "usmallint",); + /// integer, e.g. INTEGER + (INTEGER_DATA_TYPE, "INTEGER", "integer",); + /// Integer type in [BigQuery], [ClickHouse]. + /// + /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#integer_types + /// [ClickHouse]: https://clickhouse.com/docs/en/sql-reference/data-types/int-uint + (INT64_DATA_TYPE, "INT64", "int64",); + /// 128-bit integer type, e.g. HUGEINT. + (HUGE_INT_DATA_TYPE, "HUGEINT", "hugeint",); + /// Unsigned 128-bit integer type, e.g. UHUGEINT. + (U_HUGE_INT_DATA_TYPE, "UHUGEINT", "uhugeint",); + /// Unsigned big integer, e.g. UBIGINT. + (U_BIG_INT_DATA_TYPE, "UBIGINT", "ubigint",); + /// Signed integer as used in [MySQL CAST] target types, without `INTEGER` suffix. + /// + /// [MySQL CAST]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html + (SIGNED_DATA_TYPE, "SIGNED", "signed",); + /// Signed integer as used in [MySQL CAST] target types, with `INTEGER` suffix. + /// + /// [MySQL CAST]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html + (SIGNED_INTEGER_DATA_TYPE, "SIGNED INTEGER", "signed integer",); + /// Unsigned integer as used in [MySQL CAST] target types, without `INTEGER` suffix. + /// + /// [MySQL CAST]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html + (UNSIGNED_DATA_TYPE, "UNSIGNED", "unsigned",); + /// Unsigned integer as used in [MySQL CAST] target types, with `INTEGER` suffix. + /// + /// [MySQL CAST]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html + (UNSIGNED_INTEGER_DATA_TYPE, "UNSIGNED INTEGER", "unsigned integer",); + /// Float4 is an alias for Real in [PostgreSQL]. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html + (FLOAT4_DATA_TYPE, "FLOAT4", "float4",); + /// Floating point in [BigQuery] and [ClickHouse]. + /// + /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#floating_point_types + /// [ClickHouse]: https://clickhouse.com/docs/en/sql-reference/data-types/float + (FLOAT64_DATA_TYPE, "Float64", "float64",); + /// Floating point, e.g. REAL. + (REAL_DATA_TYPE, "REAL", "real",); + /// [MySQL] unsigned real, e.g. REAL UNSIGNED. + /// + /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html + (REAL_UNSIGNED_DATA_TYPE, "REAL UNSIGNED", "real unsigned",); + /// Float8 is an alias for Double in [PostgreSQL]. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html + (FLOAT8_DATA_TYPE, "FLOAT8", "float8",); + /// Double Precision, see [SQL Standard], [PostgreSQL]. + /// + /// [SQL Standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#approximate-numeric-type + /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-numeric.html + (DOUBLE_PRECISION_DATA_TYPE, "DOUBLE PRECISION", "double precision",); + /// [MySQL] unsigned double precision, e.g. DOUBLE PRECISION UNSIGNED. + /// + /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html + (DOUBLE_PRECISION_UNSIGNED_DATA_TYPE, "DOUBLE PRECISION UNSIGNED", "double precision unsigned",); + /// Bool is an alias for Boolean, see [PostgreSQL]. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html + (BOOL_DATA_TYPE, "BOOL", "bool",); + /// Boolean type. + (BOOLEAN_DATA_TYPE, "BOOLEAN", "boolean",); + /// Date type. + (DATE_DATA_TYPE, "DATE", "date",); + /// Time type. + (TIME_DATA_TYPE, "TIME", "time",); + /// Time with time zone, see [PostgreSQL]. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-datetime.html + (TIME_WITH_TIME_ZONE_DATA_TYPE, "TIME WITH TIME ZONE", "time with time zone",); + /// Datetime type, see [MySQL]. + /// + /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/datetime.html + (DATETIME_DATA_TYPE, "DATETIME", "datetime",); + /// Timestamp type, see [SQL Standard]. + /// + /// [SQL Standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type + (TIMESTAMP_DATA_TYPE, "TIMESTAMP", "timestamp",); + /// Timestamp without time zone, see [PostgreSQL]. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-datetime.html + (TIMESTAMP_NTZ_DATA_TYPE, "TIMESTAMP WITHOUT TIME ZONE", "timestamp without time zone", "TIMESTAMP_NTZ", "timestamp_ntz",); + /// Timestamp with time zone, see [PostgreSQL]. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-datetime.html + (TIMESTAMP_WITH_TZ_DATA_TYPE, "TIMESTAMP WITH TIME ZONE", "timestamp with time zone", "TIMESTAMPTZ", "timestamptz",); + /// Regclass used in [PostgreSQL] serial. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html + (REGCLASS_DATA_TYPE, "REGCLASS", "regclass",); + /// Text type. + (TEXT_DATA_TYPE, "TEXT", "text",); + /// [MySQL] text with up to 2**8 bytes. + /// + /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html + (TINY_TEXT_DATA_TYPE, "TINYTEXT", "tinytext",); + /// [MySQL] text with up to 2**24 bytes. + /// + /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html + (MEDIUM_TEXT_DATA_TYPE, "MEDIUMTEXT", "mediumtext",); + /// [MySQL] text with up to 2**32 bytes. + /// + /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html + (LONG_TEXT_DATA_TYPE, "LONGTEXT", "longtext",); + /// Bytea type, see [PostgreSQL]. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-bit.html + (BYTEA_DATA_TYPE, "BYTEA", "bytea",); + /// JSON type. + (JSON_DATA_TYPE, "JSON", "json",); + /// Binary JSON type. + (JSONB_DATA_TYPE, "JSONB", "jsonb",); + /// Interval type. + (INTERVAL_DATA_TYPE, "INTERVAL", "interval",); + /// No type specified — only used with SQLiteDialect, e.g. `CREATE TABLE t1 (a)`. + (UNSPECIFIED_DATA_TYPE, "UNSPECIFIED", "unspecified",); + /// Trigger data type, returned by functions associated with triggers, see [PostgreSQL]. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/current/plpgsql-trigger.html + (TRIGGER_DATA_TYPE, "TRIGGER", "trigger",); + /// Any data type, used in BigQuery UDF definitions for templated parameters, see [BigQuery]. + /// + /// [BigQuery]: https://cloud.google.com/bigquery/docs/user-defined-functions#templated-sql-udf-parameters + (ANY_TYPE_DATA_TYPE, "ANY TYPE", "any type", "anytype", "ANYTYPE",); + /// PostgreSQL text search vector, see [PostgreSQL]. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-textsearch.html + (TS_VECTOR_DATA_TYPE, "TSVECTOR", "tsvector",); + /// PostgreSQL text search query, see [PostgreSQL]. + /// + /// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-textsearch.html + (TS_QUERY_DATA_TYPE, "TSQUERY", "tsquery",); +} + +impl AsRef for DataTypeName { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Into for DataTypeName { + fn into(self) -> String { + self.0.to_string() + } +} + +impl std::fmt::Display for DataTypeName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} diff --git a/src/ast/data_type/serializers.rs b/src/ast/data_type/serializers.rs new file mode 100644 index 0000000..28d0b1d --- /dev/null +++ b/src/ast/data_type/serializers.rs @@ -0,0 +1,1159 @@ +use pyo3::types::PyAnyMethods; +use pyo3::IntoPyObjectExt; + +use crate::internal::alias; +use std::sync::Arc; + +/// A value serializer: `PyObject -> String` in SQL-literal form. +pub trait Serialize { + /// Convert a *borrowed* Python object into a SQL literal string. + /// + /// ### Safety + /// - `ptr` must be a valid borrowed pointer. + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + _dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult; +} + +/// SQL data-type value serializers. +/// +/// One variant per *distinct serialization behaviour* - many SQL data types +/// collapse onto the same serializer. +#[derive(Debug)] +pub enum DataTypeSerializer { + /// Every signed/unsigned integer width. + /// + /// Supports: + /// - `TinyInt` + /// - `TinyIntUnsigned` + /// - `UTinyInt` + /// - `Int2` + /// - `Int2Unsigned` + /// - `SmallInt` + /// - `SmallIntUnsigned` + /// - `USmallInt` + /// - `MediumInt` + /// - `MediumIntUnsigned` + /// - `Int` + /// - `Int4` + /// - `Int8` + /// - `Integer` + /// - `IntUnsigned` + /// - `Int4Unsigned` + /// - `BigInt` + /// - `BigIntUnsigned` + /// - `Int8Unsigned` + /// - `UInt8` + /// - `UInt16` + /// - `UInt32` + /// - `UInt64` + /// - `UInt128` + /// - `UInt256` + /// - `Int128` + /// - `Int256` + /// - `UBigInt` + /// - `UInt` + Integer(PyIntegerSerializer), + /// Every float **and** decimal/numeric type (FLOAT, DOUBLE, DECIMAL, …). + /// + /// Supports: + /// - `Numeric` + /// - `Decimal` + /// - `DecimalUnsigned` + /// - `BigNumeric` + /// - `BigDecimal` + /// - `Dec` + /// - `DecUnsigned` + /// - `Float` + /// - `FloatUnsigned` + /// - `Real` + /// - `RealUnsigned` + /// - `Float4` + /// - `Float8` + /// - `Double` + /// - `DoubleUnsigned` + /// - `DoublePrecision` + /// - `DoublePrecisionUnsigned` + Float(PyFloatSerializer), + /// BOOLEAN / BOOL. + /// + /// Supports: + /// - `Bool` + /// - `Boolean` + Boolean(PyBooleanSerializer), + /// Any character/text type (CHAR, VARCHAR, TEXT, CLOB, ENUM, SET, …). + /// + /// Supports: + /// - `Character` + /// - `Char` + /// - `CharacterVarying` + /// - `CharVarying` + /// - `Varchar` + /// - `Nvarchar` + /// - `CharacterLargeObject` + /// - `CharLargeObject` + /// - `Clob` + /// - `String` + /// - `FixedString` + /// - `Regclass` + /// - `Enum` + /// - `Set` + String(PyStringSerializer), + /// UUID. + /// + /// Supports: + /// - `UUID` + Uuid(PyUuidSerializer), + /// Binary blobs (BINARY, VARBINARY, BLOB, BYTEA, BYTES, …). + /// + /// Supports: + /// - `Binary` + /// - `Varbinary` + /// - `Blob` + /// - `LongBlob` + /// - `Bytes` + /// - `Bytea` + Bytes(PyBytesSerializer), + /// Bit strings (BIT, BIT VARYING, VARBIT). + /// + /// Supports: + /// - `Bit` + /// - `BitVarying` + /// - `VarBit` + Bit(PyBitSerializer), + /// JSON / JSONB. + /// + /// Supports: + /// - `JSON` + /// - `JSONB` + JSON(PyJSONSerializer), + /// DATE. + /// + /// Supports: + /// - `Date` + /// - `Date32` + Date(PyDateSerializer), + /// TIME. + /// + /// Supports: + /// - `Time` + Time(PyTimeSerializer), + /// DATETIME / TIMESTAMP / DATETIME64. + /// + /// Supports: + /// - `Datetime` + /// - `Datetime64` + /// - `Timestamp` + /// - `TimestampNtz` + /// - `TimestampWithTz` + Datetime(PyDatetimeSerializer), + /// INET. + /// + /// Supports: + /// - `Inet` + Inet(PyInetSerializer), + /// MACADDR. + /// + /// Supports: + /// - `MacAddr` + MacAddress(PyMacAddressSerializer), + /// ARRAY of a single element type. + /// + /// Supports: + /// - `Array` + Array(PyArraySerializer), + /// Custom serializer + Custom(alias::PyObject), + /// Unspecified serializer. + /// + /// This is also useful when user doesn't specify the serializer, + /// and also we couldn't detect the type from datatype. + Unspecified(PyUnspecifiedSerializer), +} + +impl DataTypeSerializer { + pub fn infer_from_name(value: &super::name::DataTypeName) -> Self { + use super::name::*; + + if value == &BOOLEAN_DATA_TYPE || value == &BOOL_DATA_TYPE { + return Self::Boolean(PyBooleanSerializer); + } + + if [ + &U_TINY_INT_DATA_TYPE, + &U_SMALL_INT_DATA_TYPE, + &INT64_DATA_TYPE, + &HUGE_INT_DATA_TYPE, + &U_HUGE_INT_DATA_TYPE, + &U_BIG_INT_DATA_TYPE, + &SIGNED_DATA_TYPE, + &SIGNED_INTEGER_DATA_TYPE, + &UNSIGNED_DATA_TYPE, + &UNSIGNED_INTEGER_DATA_TYPE, + &INTEGER_DATA_TYPE, + ] + .contains(&value) + { + return Self::Integer(PyIntegerSerializer); + } + + if [ + &FLOAT4_DATA_TYPE, + &FLOAT64_DATA_TYPE, + &REAL_DATA_TYPE, + &REAL_UNSIGNED_DATA_TYPE, + &FLOAT8_DATA_TYPE, + &DOUBLE_PRECISION_DATA_TYPE, + &DOUBLE_PRECISION_UNSIGNED_DATA_TYPE, + ] + .contains(&value) + { + return Self::Float(PyFloatSerializer); + } + + if value == &DATE_DATA_TYPE { + return Self::Date(PyDateSerializer); + } + + if [ + ®CLASS_DATA_TYPE, + &TEXT_DATA_TYPE, + &TINY_TEXT_DATA_TYPE, + &MEDIUM_TEXT_DATA_TYPE, + &LONG_TEXT_DATA_TYPE, + ] + .contains(&value) + { + return Self::String(PyStringSerializer); + } + + if value == &UUID_DATA_TYPE { + return Self::Uuid(PyUuidSerializer); + } + + if [ + &BYTEA_DATA_TYPE, + &TINY_BLOB_DATA_TYPE, + &MEDIUM_BLOB_DATA_TYPE, + &LONG_BLOB_DATA_TYPE, + ] + .contains(&value) + { + return Self::Bytes(PyBytesSerializer); + } + + if value == &JSON_DATA_TYPE || value == &JSONB_DATA_TYPE { + return Self::JSON(PyJSONSerializer); + } + + if value == &TIME_DATA_TYPE || value == &TIME_WITH_TIME_ZONE_DATA_TYPE { + return Self::Time(PyTimeSerializer); + } + + if [ + &DATETIME_DATA_TYPE, + &TIMESTAMP_DATA_TYPE, + &TIMESTAMP_NTZ_DATA_TYPE, + &TIMESTAMP_WITH_TZ_DATA_TYPE, + ] + .contains(&value) + { + return Self::Datetime(PyDatetimeSerializer); + } + + // INTERVAL, TRIGGER, ANY TYPE, TSVECTOR, TSQUERY, UNSPECIFIED + // have no sensible scalar serializer; fall through to Unspecified. + Self::Unspecified(PyUnspecifiedSerializer) + } + + pub fn to_pyobject<'py>( + &self, + py: pyo3::Python<'py>, + ) -> pyo3::PyResult> { + match self { + Self::Integer(s) => s.into_bound_py_any(py), + Self::Float(s) => s.into_bound_py_any(py), + Self::Boolean(s) => s.into_bound_py_any(py), + Self::String(s) => s.into_bound_py_any(py), + Self::Uuid(s) => s.into_bound_py_any(py), + Self::Bytes(s) => s.into_bound_py_any(py), + Self::Bit(s) => s.into_bound_py_any(py), + Self::JSON(s) => s.into_bound_py_any(py), + Self::Date(s) => s.into_bound_py_any(py), + Self::Time(s) => s.into_bound_py_any(py), + Self::Datetime(s) => s.into_bound_py_any(py), + Self::Inet(s) => s.into_bound_py_any(py), + Self::MacAddress(s) => s.into_bound_py_any(py), + Self::Array(s) => s.clone().into_bound_py_any(py), + Self::Custom(s) => Ok(s.bind(py).clone()), + Self::Unspecified(s) => s.into_bound_py_any(py), + } + } +} + +impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for DataTypeSerializer { + type Error = pyo3::PyErr; + + #[cfg(feature = "experimental-inspect")] + const INPUT_TYPE: pyo3::inspect::PyStaticExpr = type_hint_union!( + type_hint_identifier!("'ArraySerializer'"), + type_hint_identifier!("'BitSerializer'"), + type_hint_identifier!("'BooleanSerializer'"), + type_hint_identifier!("'BytesSerializer'"), + type_hint_identifier!("'DateSerializer'"), + type_hint_identifier!("'DatetimeSerializer'"), + type_hint_identifier!("'FloatSerializer'"), + type_hint_identifier!("'InetSerializer'"), + type_hint_identifier!("'IntegerSerializer'"), + type_hint_identifier!("'JSONSerializer'"), + type_hint_identifier!("'MacAddressSerializer'"), + type_hint_identifier!("'StringSerializer'"), + type_hint_identifier!("'TimeSerializer'"), + type_hint_identifier!("'UnspecifiedSerializer'"), + type_hint_identifier!("'UuidSerializer'"), + type_hint_subscript!( + type_hint_identifier!("typing", "Callable"), + pyo3::inspect::PyStaticExpr::List { + elts: &[ + type_hint_identifier!("typing", "Any"), + type_hint_identifier!("str") + ] + }, + type_hint_identifier!("str") + ) + ); + + fn extract( + obj: pyo3::prelude::Borrowed<'a, 'py, pyo3::prelude::PyAny>, + ) -> Result { + unsafe { + let ptr = obj.as_ptr(); + let type_ptr = pyo3::ffi::Py_TYPE(ptr); + + if type_ptr == crate::typeref::INTEGER_SERIALIZER_TYPE { + return Ok(Self::Integer(PyIntegerSerializer)); + } + if type_ptr == crate::typeref::FLOAT_SERIALIZER_TYPE { + return Ok(Self::Float(PyFloatSerializer)); + } + if type_ptr == crate::typeref::BOOLEAN_SERIALIZER_TYPE { + return Ok(Self::Boolean(PyBooleanSerializer)); + } + if type_ptr == crate::typeref::STRING_SERIALIZER_TYPE { + return Ok(Self::String(PyStringSerializer)); + } + if type_ptr == crate::typeref::UUID_SERIALIZER_TYPE { + return Ok(Self::Uuid(PyUuidSerializer)); + } + if type_ptr == crate::typeref::BYTES_SERIALIZER_TYPE { + return Ok(Self::Bytes(PyBytesSerializer)); + } + if type_ptr == crate::typeref::BIT_SERIALIZER_TYPE { + return Ok(Self::Bit(PyBitSerializer)); + } + if type_ptr == crate::typeref::JSON_SERIALIZER_TYPE { + return Ok(Self::JSON(PyJSONSerializer)); + } + if type_ptr == crate::typeref::DATE_SERIALIZER_TYPE { + return Ok(Self::Date(PyDateSerializer)); + } + if type_ptr == crate::typeref::TIME_SERIALIZER_TYPE { + return Ok(Self::Time(PyTimeSerializer)); + } + if type_ptr == crate::typeref::DATETIME_SERIALIZER_TYPE { + return Ok(Self::Datetime(PyDatetimeSerializer)); + } + if type_ptr == crate::typeref::INET_SERIALIZER_TYPE { + return Ok(Self::Inet(PyInetSerializer)); + } + if type_ptr == crate::typeref::MAC_ADDRESS_SERIALIZER_TYPE { + return Ok(Self::MacAddress(PyMacAddressSerializer)); + } + if type_ptr == crate::typeref::ARRAY_SERIALIZER_TYPE { + let array_ref = obj.cast_unchecked::(); + let element = array_ref.get().element.clone(); + + return Ok(Self::Array(PyArraySerializer { element })); + } + if type_ptr == crate::typeref::UNSPECIFIED_SERIALIZER_TYPE { + return Ok(Self::Unspecified(PyUnspecifiedSerializer)); + } + + // Fall through: treat as a custom callable serializer. + let callable = obj.extract::()?; + Ok(Self::Custom(callable)) + } + } +} + +impl Serialize for DataTypeSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + match self { + Self::Integer(s) => s.serialize(py, ptr, dialect), + Self::Float(s) => s.serialize(py, ptr, dialect), + Self::Boolean(s) => s.serialize(py, ptr, dialect), + Self::String(s) => s.serialize(py, ptr, dialect), + Self::Uuid(s) => s.serialize(py, ptr, dialect), + Self::Bytes(s) => s.serialize(py, ptr, dialect), + Self::Bit(s) => s.serialize(py, ptr, dialect), + Self::JSON(s) => s.serialize(py, ptr, dialect), + Self::Date(s) => s.serialize(py, ptr, dialect), + Self::Time(s) => s.serialize(py, ptr, dialect), + Self::Datetime(s) => s.serialize(py, ptr, dialect), + Self::Inet(s) => s.serialize(py, ptr, dialect), + Self::MacAddress(s) => s.serialize(py, ptr, dialect), + Self::Array(s) => s.serialize(py, ptr, dialect), + Self::Custom(s) => { + let object_arg = pyo3::Bound::from_borrowed_ptr(py, ptr); + let dialect_arg = dialect.name(); + let output = s.call1(py, (object_arg, dialect_arg))?; + _serialize_string(py, output.as_ptr()) + } + Self::Unspecified(s) => s.serialize(py, ptr, dialect), + } + } +} + +implement_pyclass! { + /// Serializes any Python `int` as a bare decimal literal. + /// + /// Uses `str(int)` so arbitrary precision (beyond i64/u64) is preserved - one + /// serializer genuinely covers TINYINT through BIGINT and the unsigned variants. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyIntegerSerializer as "IntegerSerializer"; +} + +#[pyo3::pymethods] +impl PyIntegerSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyIntegerSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + _dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + if pyo3::ffi::PyLong_CheckExact(ptr) == 1 { + // bool is a subclass of int in Python, but PyLong_CheckExact rejects + // it, so True/False never sneaks through here. + py_to_string(py, ptr) + } else { + Err(new_py_error!( + PyTypeError, + "expected int for integer serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )) + } + } +} + +implement_pyclass! { + /// Serializes Python `float`, `int`, `decimal.Decimal`, or numeric `str` as a + /// bare numeric literal. + /// + /// `Decimal`/`str`/`int` go through their exact text form (no precision loss); + /// only a real `float` is formatted from the f64. Non-finite floats are rejected. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyFloatSerializer as "FloatSerializer"; +} + +#[pyo3::pymethods] +impl PyFloatSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyFloatSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + _dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + if pyo3::ffi::PyFloat_CheckExact(ptr) == 1 { + let v = pyo3::ffi::PyFloat_AsDouble(ptr); + if !v.is_finite() { + return Err(new_py_error!( + PyValueError, + "cannot serialize non-finite float {} as a numeric literal", + v + )); + } + + // {:?} on f64 round-trips and avoids the "1" vs "1.0" ambiguity. + return Ok(format!("{v:?}")); + } + + // int / Decimal / str: their textual form is already a valid literal. + if pyo3::ffi::PyLong_CheckExact(ptr) == 1 + || pyo3::ffi::PyUnicode_CheckExact(ptr) == 1 + || pyo3::ffi::Py_TYPE(ptr) == crate::typeref::STD_DECIMAL_TYPE + { + let text = py_to_string(py, ptr)?; + // Reject obvious garbage from str inputs cheaply: a literal must + // start with a digit, sign, or dot. + if pyo3::ffi::PyUnicode_CheckExact(ptr) == 1 + && !text + .trim() + .chars() + .next() + .is_some_and(|c| c.is_ascii_digit() || c == '-' || c == '+' || c == '.') + { + return Err(new_py_error!( + PyValueError, + "string {:?} is not a valid numeric literal", + text + )); + } + return Ok(text.trim().to_string()); + } + + Err(new_py_error!( + PyTypeError, + "expected float/int/Decimal/str for numeric serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )) + } +} + +implement_pyclass! { + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyBooleanSerializer as "BooleanSerializer"; +} + +#[pyo3::pymethods] +impl PyBooleanSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyBooleanSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + if pyo3::ffi::PyBool_Check(ptr) == 1 { + let value = pyo3::ffi::Py_True() == ptr; + Ok(dialect.bool_format().format(value).to_string()) + } else { + Err(new_py_error!( + PyTypeError, + "expected bool for boolean serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )) + } + } +} + +implement_pyclass! { + /// Serializes a Python `str` (or `enum.Enum` whose value is a str) as a quoted, + /// escaped string literal: `"LIKE"` -> `'LIKE'`. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyStringSerializer as "StringSerializer"; +} + +#[pyo3::pymethods] +impl PyStringSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyStringSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + if pyo3::ffi::PyUnicode_CheckExact(ptr) == 1 { + let raw = _serialize_string(py, ptr)?; + return Ok(dialect.escape_and_quote_string(&raw)); + } + + // Accept enum members whose `.value` is a string (mirrors PyEnumType). + if pyo3::ffi::Py_TYPE(ptr) == crate::typeref::STD_ENUM_TYPE { + let value = pyo3::ffi::PyObject_GetAttrString(ptr, c"value".as_ptr()); + if value.is_null() { + return Err(pyo3::PyErr::fetch(py)); + } + + let is_str = pyo3::ffi::PyUnicode_CheckExact(ptr) == 1; + let raw = if is_str { + _serialize_string(py, value) + } else { + Ok(String::new()) + }; + + pyo3::ffi::Py_DECREF(value); + if is_str { + return Ok(dialect.escape_and_quote_string(&raw?)); + } + } + + Err(new_py_error!( + PyTypeError, + "expected str/Enum for string serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )) + } +} + +implement_pyclass! { + /// Serializes a `uuid.UUID` (or 36-char `str`) as a quoted literal. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyUuidSerializer as "UuidSerializer"; +} + +#[pyo3::pymethods] +impl PyUuidSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyUuidSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + if pyo3::ffi::Py_TYPE(ptr) == crate::typeref::STD_UUID_TYPE + || pyo3::ffi::PyUnicode_CheckExact(ptr) == 1 + { + let text = py_to_string(py, ptr)?; + Ok(dialect.escape_and_quote_string(&text)) + } else { + Err(new_py_error!( + PyTypeError, + "expected uuid.UUID/str for UUID serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )) + } + } +} + +implement_pyclass! { + /// Serializes a `uuid.UUID` (or 36-char `str`) as a quoted literal. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyBytesSerializer as "BytesSerializer"; +} + +#[pyo3::pymethods] +impl PyBytesSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyBytesSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + if pyo3::ffi::PyBytes_CheckExact(ptr) == 1 { + Ok(dialect.encode_bytes(read_bytes(ptr))) + } else { + Err(new_py_error!( + PyTypeError, + "expected bytes for binary serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )) + } + } +} + +implement_pyclass! { + /// Serializes a bit string given as a `str` of `0`/`1` (e.g. `"1010"`) into a + /// `B'1010'` literal. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyBitSerializer as "BitSerializer"; +} + +#[pyo3::pymethods] +impl PyBitSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyBitSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + if pyo3::ffi::PyUnicode_CheckExact(ptr) == 1 { + let raw = _serialize_string(py, ptr)?; + + if raw.is_empty() || !raw.bytes().all(|b| b == b'0' || b == b'1') { + return Err(new_py_error!( + PyValueError, + "expected a string of '0'/'1' for bit serialization, got {:?}", + raw + )); + } + Ok(dialect.encode_bits(&raw)) + } else { + Err(new_py_error!( + PyTypeError, + "expected str of bits for bit serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )) + } + } +} + +implement_pyclass! { + /// Serializes any JSON-encodable Python object via `json.dumps`, then wraps the + /// result as a quoted string literal. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyJSONSerializer as "JSONSerializer"; +} + +#[pyo3::pymethods] +impl PyJSONSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyJSONSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + // Reuse the existing json-dumps helper from sqltypes::json. + let dumped = _serialize_object_with_pyjson(py, ptr)?; + let text = _serialize_string(py, dumped); + pyo3::ffi::Py_DECREF(dumped); + + let literal = dialect.escape_and_quote_string(&text?); + Ok(literal) + } +} + +implement_pyclass! { + /// `datetime.date` -> `'YYYY-MM-DD'`. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyDateSerializer as "DateSerializer"; +} + +#[pyo3::pymethods] +impl PyDateSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyDateSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + if pyo3::ffi::Py_TYPE(ptr) == crate::typeref::STD_DATE_TYPE { + let val: pyo3::Bound<'_, pyo3::types::PyDate> = + pyo3::Bound::from_borrowed_ptr(py, ptr).cast_into()?; + + let date: chrono::NaiveDate = val.extract()?; + let mut result = date.format("%Y-%m-%d").to_string(); + + dialect.quote_string(&mut result); + Ok(result) + } else { + Err(new_py_error!( + PyTypeError, + "expected datetime.date for date serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )) + } + } +} + +implement_pyclass! { + /// `datetime.time` -> `'HH:MM:SS[.ffffff]'`. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyTimeSerializer as "TimeSerializer"; +} + +#[pyo3::pymethods] +impl PyTimeSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyTimeSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + if pyo3::ffi::Py_TYPE(ptr) == crate::typeref::STD_TIME_TYPE { + let val: pyo3::Bound<'_, pyo3::types::PyTime> = + pyo3::Bound::from_borrowed_ptr(py, ptr).cast_into()?; + + let date: chrono::NaiveTime = val.extract()?; + let mut result = date.format("%H:%M:%S%.f").to_string(); + + dialect.quote_string(&mut result); + Ok(result) + } else { + Err(new_py_error!( + PyTypeError, + "expected datetime.time for time serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )) + } + } +} + +implement_pyclass! { + /// `datetime.datetime` -> `'YYYY-MM-DD HH:MM:SS[.ffffff][+HH:MM]'`. + /// + /// `int`/`float` are treated as a UTC epoch and accepted too. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyDatetimeSerializer as "DatetimeSerializer"; +} + +#[pyo3::pymethods] +impl PyDatetimeSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyDatetimeSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + use chrono::TimeZone; + use pyo3::types::PyTzInfoAccess; + + if pyo3::ffi::Py_TYPE(ptr) == crate::typeref::STD_DATETIME_TYPE { + // isoformat(sep=' ') -> the SQL-friendly space-separated form. + let val: pyo3::Bound<'_, pyo3::types::PyDateTime> = + pyo3::Bound::from_borrowed_ptr(py, ptr).cast_into()?; + + let tzinfo = val.get_tzinfo(); + + if tzinfo.is_none() { + let datetime: chrono::DateTime = val.extract()?; + let mut result = datetime.format("%Y-%m-%d %H:%M:%S%.f%:z").to_string(); + + dialect.quote_string(&mut result); + return Ok(result); + } else { + let datetime: chrono::NaiveDateTime = val.extract()?; + let mut result = datetime.format("%Y-%m-%d %H:%M:%S%.f").to_string(); + + dialect.quote_string(&mut result); + return Ok(result); + } + } + + if pyo3::ffi::PyLong_CheckExact(ptr) == 1 { + let mut has_overflow: i32 = 0; + let num = pyo3::ffi::PyLong_AsLongLongAndOverflow(ptr, &mut has_overflow); + + if has_overflow == 1 { + return Err(pyo3::exceptions::PyOverflowError::new_err("out of range")); + } + + if let chrono::offset::LocalResult::Single(datetime) = chrono::Utc.timestamp_opt(num, 0) + { + let mut result = datetime.format("%Y-%m-%d %H:%M:%S%.f%:z").to_string(); + dialect.quote_string(&mut result); + return Ok(result); + } + + return Err(new_py_error!( + PyOverflowError, + "timestamp is invalid for datetime serialization: {}", + num + )); + } + + if pyo3::ffi::PyFloat_CheckExact(ptr) == 1 { + let num = pyo3::ffi::PyFloat_AsDouble(ptr); + + if let chrono::offset::LocalResult::Single(datetime) = chrono::Utc + .timestamp_opt(num.trunc() as i64, (num.fract() * 1_000_000_000.0) as u32) + { + let mut result = datetime.format("%Y-%m-%d %H:%M:%S%.f%:z").to_string(); + dialect.quote_string(&mut result); + return Ok(result); + } + + return Err(new_py_error!( + PyOverflowError, + "timestamp is invalid for datetime serialization: {}", + num + )); + } + + Err(new_py_error!( + PyTypeError, + "expected datetime.datetime/int/float for datetime serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )) + } +} + +implement_pyclass! { + /// `str` (or `ipaddress.*`) -> quoted literal. Validated as IPv4/IPv6 network. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyInetSerializer as "InetSerializer"; +} + +#[pyo3::pymethods] +impl PyInetSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyInetSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + use std::str::FromStr; + + let text = py_to_string(py, ptr)?; + let mut net = ipnetwork::IpNetwork::from_str(&text) + .map(|net| net.to_string()) + .map_err(|e| new_py_error!(PyValueError, e.to_string()))?; + + dialect.quote_string(&mut net); + return Ok(net); + } +} + +implement_pyclass! { + /// `str` -> quoted literal. Validated as a MAC address. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyMacAddressSerializer as "MacAddressSerializer"; +} + +#[pyo3::pymethods] +impl PyMacAddressSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyMacAddressSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + use std::str::FromStr; + let text = py_to_string(py, ptr)?; + + let mut addr = mac_address::MacAddress::from_str(&text) + .map(|mac| mac.to_string()) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; + + dialect.quote_string(&mut addr); + return Ok(addr); + } +} + +implement_pyclass! { + /// Serializes a Python `list`/`tuple` by serializing each element with `element` + /// and wrapping in `ARRAY[...]`. + #[derive(Debug, Clone)] + [skip_from_py_object] PyArraySerializer as "ArraySerializer" { + pub element: Arc, + } +} + +#[pyo3::pymethods] +impl PyArraySerializer { + #[new] + fn __new__(element: DataTypeSerializer) -> Self { + Self { + element: Arc::new(element), + } + } +} + +impl Serialize for PyArraySerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + let (len, getter): ( + isize, + unsafe fn(*mut pyo3::ffi::PyObject, isize) -> *mut pyo3::ffi::PyObject, + ) = if pyo3::ffi::PyList_CheckExact(ptr) == 1 { + (pyo3::ffi::PyList_Size(ptr), |p, i| { + pyo3::ffi::PyList_GetItem(p, i) + }) + } else if pyo3::ffi::PyTuple_CheckExact(ptr) == 1 { + (pyo3::ffi::PyTuple_Size(ptr), |p, i| { + pyo3::ffi::PyTuple_GetItem(p, i) + }) + } else { + return Err(new_py_error!( + PyTypeError, + "expected list/tuple for array serialization, got {}", + crate::internal::utils::get_type_name(py, ptr) + )); + }; + + let mut parts: Vec = Vec::with_capacity(len.max(0) as usize); + for i in 0..len { + let item = getter(ptr, i); // borrowed + if item.is_null() { + return Err(pyo3::PyErr::fetch(py)); + } + parts.push(self.element.serialize(py, item, dialect)?); + } + + Ok(format!("ARRAY[{}]", parts.join(", "))) + } +} + +implement_pyclass! { + /// When you don't know about data type, you can use this. Uses values' `__str__` method + /// and return it as a serialized SQL value. + #[derive(Debug, Clone, Copy, Default)] + [skip_from_py_object] PyUnspecifiedSerializer as "UnspecifiedSerializer"; +} + +#[pyo3::pymethods] +impl PyUnspecifiedSerializer { + #[new] + fn __new__() -> Self { + Self + } +} + +impl Serialize for PyUnspecifiedSerializer { + unsafe fn serialize( + &self, + py: pyo3::Python, + ptr: *mut pyo3::ffi::PyObject, + _dialect: &dyn crate::dialect::Dialect, + ) -> pyo3::PyResult { + py_to_string(py, ptr) + } +} + +/// Converts Python strings into [`String`], in the fastest way. +#[inline] +unsafe fn _serialize_string( + py: pyo3::Python, + object: *mut pyo3::ffi::PyObject, +) -> pyo3::PyResult { + let mut size: pyo3::ffi::Py_ssize_t = 0; + let c_str = pyo3::ffi::PyUnicode_AsUTF8AndSize(object, &mut size); + + if c_str.is_null() || size < 0 { + Err(pyo3::PyErr::fetch(py)) + } else { + let val = std::ffi::CStr::from_ptr(c_str); + Ok(std::str::from_utf8_unchecked(val.to_bytes()).into()) + } +} + +/// `str(obj)` then read it as UTF-8. Used for arbitrary-precision ints, +/// `decimal.Decimal`, `uuid.UUID`, etc. where Python already has the right text. +#[inline] +unsafe fn py_to_string(py: pyo3::Python, ptr: *mut pyo3::ffi::PyObject) -> pyo3::PyResult { + let s = pyo3::ffi::PyObject_Str(ptr); + if s.is_null() { + return Err(pyo3::PyErr::fetch(py)); + } + let out = _serialize_string(py, s); + pyo3::ffi::Py_DECREF(s); + out +} + +/// Borrow the bytes of a Python `bytes` object. +#[inline] +unsafe fn read_bytes<'a>(ptr: *mut pyo3::ffi::PyObject) -> &'a [u8] { + let buf = pyo3::ffi::PyBytes_AsString(ptr) as *const u8; + let len = pyo3::ffi::PyBytes_Size(ptr) as usize; + debug_assert!(!buf.is_null()); + std::slice::from_raw_parts(buf, len) +} + +/// Import json module only once +#[inline(always)] +pub fn import_json_module( + py: pyo3::Python<'_>, +) -> pyo3::PyResult<&pyo3::Bound<'_, pyo3::types::PyModule>> { + static JSON_CLS: std::sync::OnceLock> = + std::sync::OnceLock::new(); + + let json = JSON_CLS.get_or_try_init(|| py.import("json").map(|x| x.unbind())); + json.map(|x| x.bind(py)) +} + +/// Serialize pyobject with Python `json` module +/// +/// # Safety +/// - `ptr` should be borrowed and a valid pointer +#[inline(always)] +pub unsafe fn _serialize_object_with_pyjson( + py: pyo3::Python<'_>, + ptr: *mut pyo3::ffi::PyObject, +) -> pyo3::PyResult<*mut pyo3::ffi::PyObject> { + let json = import_json_module(py)?; + let dumps_func = json.getattr("dumps")?; + + unsafe { + let arg1 = pyo3::Bound::from_borrowed_ptr(py, ptr); + dumps_func.call1((arg1,)).map(|x| x.into_ptr()) + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs new file mode 100644 index 0000000..169f40a --- /dev/null +++ b/src/ast/mod.rs @@ -0,0 +1,7 @@ +pub mod data_type; + +#[pyo3::pymodule(name = "ast")] +pub mod ast_module { + #[pymodule_export] + pub use super::data_type::data_type_module; +} diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs new file mode 100644 index 0000000..393b9d4 --- /dev/null +++ b/src/dialect/mod.rs @@ -0,0 +1,59 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum BoolFormat { + /// `TRUE` / `FALSE`. + Keyword, + /// `1` / `0` (MySQL, SQLite). + Int, +} + +impl BoolFormat { + pub fn format(self, value: bool) -> &'static str { + match (self, value) { + (Self::Keyword, true) => "TRUE", + (Self::Keyword, false) => "FALSE", + (Self::Int, true) => "1", + (Self::Int, false) => "0", + } + } +} + +pub trait Dialect { + // we cannot use constant variables here for boolean format + // and identifier quotes. + // + // https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility + + /// Dialect name + fn name(&self) -> &'static str; + + /// How a boolean should be rendered. + fn bool_format(&self) -> BoolFormat { + BoolFormat::Int + } + + /// The start/end characters used to quote identifiers. + fn identifier_quotes(&self) -> Option<(char, char)> { + None + } + + /// Escapes string literals. + fn escape_string(&self, string: &str) -> String; + + /// (zero-copy) Quotes string literals. + /// + /// You should call [`Dialect::escape`] before this method. + fn quote_string(&self, string: &mut String); + + /// Calls [`Self::escape_string`] and [`Self::quote_string`] in order. + fn escape_and_quote_string(&self, string: &str) -> String { + let mut result = self.escape_string(string); + self.quote_string(&mut result); + result + } + + /// Render a byte slice as a literal in the configured form. + fn encode_bytes(&self, bytes: &[u8]) -> String; + + /// Render a byte slice as a literal in the configured form. + fn encode_bits(&self, bits: &str) -> String; +} diff --git a/src/internal/alias.rs b/src/internal/alias.rs new file mode 100644 index 0000000..3694619 --- /dev/null +++ b/src/internal/alias.rs @@ -0,0 +1,15 @@ +//! There are type aliases that are used across the library + +/// Type alias for `pyo3::Py` +pub type PyObject = pyo3::Py; + +/// Type alias for `pyo3::Bound<'a, pyo3::PyAny>` +pub type BoundObject<'a> = pyo3::Bound<'a, pyo3::PyAny>; + +/// Type alias for `&'a pyo3::Bound<'a, pyo3::types::PyTuple>`. +/// Use it directly as `*args` argument type. +pub type ArgsType<'a> = &'a pyo3::Bound<'a, pyo3::types::PyTuple>; + +/// Type alias for `&'a pyo3::Bound<'a, pyo3::types::PyDict>`. +/// Use it directly as `**kwds` argument type. +pub type KwdsType<'a> = &'a pyo3::Bound<'a, pyo3::types::PyDict>; diff --git a/src/internal/mod.rs b/src/internal/mod.rs new file mode 100644 index 0000000..b5d798a --- /dev/null +++ b/src/internal/mod.rs @@ -0,0 +1,3 @@ +pub mod alias; +pub mod onceinit; +pub mod utils; diff --git a/src/internal/onceinit.rs b/src/internal/onceinit.rs new file mode 100644 index 0000000..a365c2b --- /dev/null +++ b/src/internal/onceinit.rs @@ -0,0 +1,158 @@ +//! According to PyO3 updates, we can write `__init__` methods inside the Rust, which allows developers +//! to use classes as subclass in Python. +//! +//! All of classes must implement `__new__` and `__init__` methods. +//! - In `__new__` methods, we should allocate memory for the type; +//! - And in `__init__` methods, we should initialize and constrcut the type, according to parameters. +//! +//! There are types that help us to create these methods completely thread-safe. + +use std::cell; +use std::mem; +use std::sync::atomic; +use std::sync::Arc; + +const UNINIT: u8 = 0; +const RUNNING: u8 = 1; +const INIT: u8 = 2; + +pub struct OnceInitInner { + /// Tracks the lifecycle of the inner value: + /// `UNINIT` → `RUNNING` (mid-write) → `INIT` (ready). + state: atomic::AtomicU8, + /// Heap-allocated storage that is uninitialized until [`set`](OnceInit::set) completes. + /// Wrapped in a [`std::sync::Mutex`] so that post-init access is safe across threads. + value: cell::UnsafeCell>, +} + +/// A thread-safe, write-once container for PyO3 `__new__` / `__init__` two-phase construction. +/// +/// PyO3 splits Python object creation into two steps: +/// - `__new__` allocates the Rust-side storage (calls [`OnceInit::uninit`]), +/// - `__init__` fills it in exactly once (calls [`OnceInit::set`]). +/// +/// After initialisation the inner value is accessible through a [`std::sync::MutexGuard`] +/// via [`OnceInit::lock`], which is safe to call from multiple threads simultaneously. +#[repr(transparent)] +pub struct OnceInit(Arc>); + +impl OnceInit { + /// Creates a new, **uninitialized** [`OnceInit`]. + /// + /// Intended to be called from the PyO3 `__new__` handler to allocate the + /// object slot before Python passes arguments to `__init__`. + /// + /// The returned value must not be accessed via [`lock`](Self::lock) + /// until [`set`](Self::set) has been called. + #[inline] + pub fn uninit() -> Self { + OnceInitInner { + state: atomic::AtomicU8::new(UNINIT), + value: cell::UnsafeCell::new(mem::MaybeUninit::uninit()), + } + .into() + } + + /// Creates a new **initialized** [`OnceInit`]. + #[inline] + pub fn new(val: T) -> Self { + OnceInitInner { + state: atomic::AtomicU8::new(INIT), + value: cell::UnsafeCell::new(mem::MaybeUninit::new(val)), + } + .into() + } + + #[inline] + pub fn is_initialized(&self) -> bool { + self.0.state.load(atomic::Ordering::Acquire) == INIT + } + + /// Initializes the container with `val`, transitioning state from `UNINIT` to `INIT`. + /// + /// Intended to be called from the PyO3 `__init__` handler once the Python-side + /// arguments have been validated and the Rust value can be constructed. + /// + /// # Panics + /// + /// Panics if `set` has already been called on this instance. + #[inline] + pub fn set(&self, val: T) { + if self + .0 + .state + .compare_exchange( + UNINIT, + RUNNING, + atomic::Ordering::Acquire, + atomic::Ordering::Relaxed, + ) + .is_err() + { + already_init_panic(); + } + // SAFETY: we own the RUNNING token — no other thread can write value. + unsafe { (*self.0.value.get()).write(val) }; + self.0.state.store(INIT, atomic::Ordering::Release); + } + + /// Returns an immutable reference to initialized value. + /// + /// # Panics + /// + /// Panics if called before [`set`](Self::set) has completed. + #[inline] + pub fn get(&self) -> &T { + if std::hint::likely(self.0.state.load(atomic::Ordering::Acquire) == INIT) { + // SAFETY: state == INIT guarantees `value` was fully written and is valid. + unsafe { (*self.0.value.get()).assume_init_ref() } + } else { + not_init_panic() + } + } +} + +impl Clone for OnceInit { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } +} + +impl From> for OnceInit { + fn from(value: OnceInitInner) -> Self { + Self(Arc::new(value)) + } +} + +// SAFETY: Mutex is Send+Sync when T: Send; we uphold the init invariant ourselves. +unsafe impl Send for OnceInit {} +unsafe impl Sync for OnceInit {} + +impl Drop for OnceInit { + /// Drops the inner value if and only if [`set`](OnceInit::set) was called. + /// + /// Checks the state flag without any atomic synchronisation since `drop` + /// requires `&mut self`, guaranteeing exclusive access. + fn drop(&mut self) { + if unsafe { *self.0.state.as_ptr() == INIT } { + // SAFETY: state == INIT means value was written and not yet dropped. + unsafe { (*self.0.value.get()).assume_init_drop() } + } + } +} + +/// Marked `#[cold]` and `#[inline(never)]` so it is compiled as a separate, +/// rarely-executed stub and does not bloat the hot path of [`lock`](OnceInit::lock). +#[cold] +#[inline(never)] +fn not_init_panic() -> ! { + panic!("Object not initialized (__init__ not called)") +} + +/// Marked `#[cold]` and `#[inline(never)]` so it is compiled as a separate, +/// rarely-executed stub and does not bloat the hot path of [`set`](OnceInit::set). +#[cold] +#[inline(never)] +fn already_init_panic() -> ! { + panic!("Object already initialized") +} diff --git a/src/internal/utils.rs b/src/internal/utils.rs new file mode 100644 index 0000000..e41a0d7 --- /dev/null +++ b/src/internal/utils.rs @@ -0,0 +1,19 @@ +/// Returns the type name of a [`pyo3::ffi::PyObject`]. +/// +/// Returns `""` on failure. +#[inline] +pub unsafe fn get_type_name<'a>(py: pyo3::Python<'a>, obj: *mut pyo3::ffi::PyObject) -> String { + use pyo3::types::{PyStringMethods, PyTypeMethods}; + + let type_ = pyo3::ffi::Py_TYPE(obj); + + if type_.is_null() { + String::from("") + } else { + let obj = pyo3::types::PyType::from_borrowed_type_ptr(py, type_); + + obj.fully_qualified_name() + .map(|x| x.to_string_lossy().into_owned()) + .unwrap_or_else(|_| String::from("")) + } +} diff --git a/src/lib.rs b/src/lib.rs index ea027cb..998c3a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,28 @@ -#![allow(unused_unsafe)] -#![allow(clippy::macro_metavars_in_unsafe)] -#![allow(clippy::too_many_arguments)] -#![allow(clippy::not_unsafe_ptr_arg_deref)] #![warn(clippy::print_stdout)] #![warn(clippy::print_stderr)] #![warn(clippy::dbg_macro)] +#![feature(likely_unlikely)] +#![feature(optimize_attribute)] +#![feature(once_cell_try)] + +#[macro_use] +pub mod macro_rules; + +pub mod ast; +pub mod dialect; +pub mod internal; +pub mod typeref; /// RapidQuery core which is written in Rust. #[pyo3::pymodule(gil_used = false)] mod _lib { + #[pymodule_export] + pub use super::ast::ast_module; + #[pymodule_init] #[cold] fn init(_m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { + // sqlparser::ast::DataType::Tiny Ok(()) } } diff --git a/src/macro_rules.rs b/src/macro_rules.rs new file mode 100644 index 0000000..edd41bc --- /dev/null +++ b/src/macro_rules.rs @@ -0,0 +1,133 @@ +/// Implements a `#[pyclass]` with pre-defined pyclass arguments. +/// +/// # Example +/// +/// ```ignore +/// implement_pyclass! { +/// [] MyClass as "MyClass" { field: type } +/// } +/// ``` +#[macro_export] +macro_rules! implement_pyclass { + ( + $(#[$outer:meta])* + [$($pyclass_args:tt)*] $struct_name:ident as $python_name:literal $($rest:tt)* + ) => { + #[pyo3::pyclass( + module = "rapidquery._lib", + name = $python_name, + immutable_type, + frozen, + $($pyclass_args)* + )] + $(#[$outer])* + pub struct $struct_name $($rest)* + }; +} + +/// Creates a new [`PyErr`] of the given exception type. +#[macro_export] +macro_rules! new_py_error { + ($name:ident, $msg:expr $(,)?) => { + ::pyo3::exceptions::$name::new_err($msg) + }; + ($name:ident, $fmt:expr, $($args:tt)*) => { + ::pyo3::exceptions::$name::new_err( + format!($fmt, $($args)*) + ) + }; +} + +/// Converts `String` to enum, and return PyErr if couldn't match. +#[macro_export] +macro_rules! literal_to_enum { + ( + $string:expr, + $($literal:literal => $result:expr,)+ + ) => { + match $string.as_str() { + $($literal => Ok($result),)+ + _ => Err( + new_py_error!( + PyValueError, + String::from(concat!("expected", $($literal,)+)) + &format!(", got {}", $string), + ) + ) + } + } +} + +/// Builds a type hint from a module name and a member name in the module +/// +/// ``` +/// use pyo3::type_hint_identifier; +/// use pyo3::inspect::PyStaticExpr; +/// +/// const T: PyStaticExpr = type_hint_identifier!("datetime", "date"); +/// assert_eq!(T.to_string(), "datetime.date"); +/// +/// const T2: PyStaticExpr = type_hint_identifier!("builtins", "int"); +/// assert_eq!(T2.to_string(), "int"); +/// ``` +#[macro_export] +#[cfg(feature = "experimental-inspect")] +macro_rules! type_hint_identifier { + ($name:expr) => { + pyo3::inspect::PyStaticExpr::Name { id: $name } + }; + ($module:expr, $name:expr) => { + pyo3::inspect::PyStaticExpr::Attribute { + value: &pyo3::inspect::PyStaticExpr::Name { id: $module }, + attr: $name, + } + }; +} + +/// Builds the union of multiple type hints +/// +/// ``` +/// use pyo3::{type_hint_identifier, type_hint_union}; +/// use pyo3::inspect::PyStaticExpr; +/// +/// const T: PyStaticExpr = type_hint_union!(type_hint_identifier!("builtins", "int"), type_hint_identifier!("builtins", "float")); +/// assert_eq!(T.to_string(), "int | float"); +/// ``` +#[macro_export] +#[cfg(feature = "experimental-inspect")] +macro_rules! type_hint_union { + ($e:expr) => { $e }; + ($l:expr , $($r:expr),+) => { pyo3::inspect::PyStaticExpr::BinOp { + left: &$l, + op: pyo3::inspect::PyStaticOperator::BitOr, + right: &type_hint_union!($($r),+), + } }; +} + +/// Builds a subscribed type hint +/// +/// ``` +/// use pyo3::{type_hint_identifier, type_hint_subscript}; +/// use pyo3::inspect::PyStaticExpr; +/// +/// const T: PyStaticExpr = type_hint_subscript!(type_hint_identifier!("collections.abc", "Sequence"), type_hint_identifier!("builtins", "float")); +/// assert_eq!(T.to_string(), "collections.abc.Sequence[float]"); +/// +/// const T2: PyStaticExpr = type_hint_subscript!(type_hint_identifier!("builtins", "dict"), type_hint_identifier!("builtins", "str"), type_hint_identifier!("builtins", "float")); +/// assert_eq!(T2.to_string(), "dict[str, float]"); +/// ``` +#[macro_export] +#[cfg(feature = "experimental-inspect")] +macro_rules! type_hint_subscript { + ($l:expr, $r:expr) => { + pyo3::inspect::PyStaticExpr::Subscript { + value: &$l, + slice: &$r + } + }; + ($l:expr, $($r:expr),*) => { + pyo3::inspect::PyStaticExpr::Subscript { + value: &$l, + slice: &pyo3::inspect::PyStaticExpr::Tuple { elts: &[$($r),*] } + } + }; +} diff --git a/src/typeref.rs b/src/typeref.rs new file mode 100644 index 0000000..6c0f63e --- /dev/null +++ b/src/typeref.rs @@ -0,0 +1,94 @@ +pub(crate) static mut INTEGER_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut FLOAT_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut BOOLEAN_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut STRING_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut UUID_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut BYTES_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut BIT_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut JSON_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut DATE_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut TIME_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut DATETIME_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut INET_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut MAC_ADDRESS_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = + std::ptr::null_mut(); +pub(crate) static mut ARRAY_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut UNSPECIFIED_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = + std::ptr::null_mut(); + +// Python standard libraries types +pub(crate) static mut STD_DECIMAL_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut STD_UUID_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut STD_DATETIME_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut STD_DATE_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut STD_TIME_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut STD_ENUM_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); + +unsafe fn get_type_object_for( + py: pyo3::Python, +) -> *mut pyo3::ffi::PyTypeObject { + T::type_object_raw(py) +} + +unsafe fn look_up_type_object( + module_name: &std::ffi::CStr, + member_name: &std::ffi::CStr, +) -> *mut pyo3::ffi::PyTypeObject { + let module = pyo3::ffi::PyImport_ImportModule(module_name.as_ptr()); + let module_dict = pyo3::ffi::PyObject_GenericGetDict(module, std::ptr::null_mut()); + let ptr = pyo3::ffi::PyMapping_GetItemString(module_dict, member_name.as_ptr()) + .cast::(); + + pyo3::ffi::Py_DECREF(module_dict); + pyo3::ffi::Py_DECREF(module); + ptr +} + +macro_rules! multiple_get_type_object_for { + ($py:expr, $($type:ty => $name:ident,)*) => { + $($name = get_type_object_for::<$type>($py);)* + }; +} + +#[cold] +#[optimize(size)] +fn _initialize_typeref(py: pyo3::Python) { + unsafe { + multiple_get_type_object_for!( + py, + crate::ast::data_type::serializers::PyIntegerSerializer => INTEGER_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyFloatSerializer => FLOAT_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyBooleanSerializer => BOOLEAN_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyStringSerializer => STRING_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyUuidSerializer => UUID_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyBytesSerializer => BYTES_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyBitSerializer => BIT_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyJSONSerializer => JSON_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyDateSerializer => DATE_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyTimeSerializer => TIME_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyDatetimeSerializer => DATETIME_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyInetSerializer => INET_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyMacAddressSerializer => MAC_ADDRESS_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyArraySerializer => ARRAY_SERIALIZER_TYPE, + crate::ast::data_type::serializers::PyUnspecifiedSerializer => UNSPECIFIED_SERIALIZER_TYPE, + ); + + STD_DECIMAL_TYPE = look_up_type_object(c"decimal", c"Decimal"); + STD_UUID_TYPE = look_up_type_object(c"uuid", c"UUID"); + STD_ENUM_TYPE = look_up_type_object(c"enum", c"EnumMeta"); + + pyo3::ffi::PyDateTime_IMPORT(); + let datetime_capsule = pyo3::ffi::PyCapsule_Import(c"datetime.datetime_CAPI".as_ptr(), 1) + .cast::(); + + STD_DATETIME_TYPE = (*datetime_capsule).DateTimeType; + STD_DATE_TYPE = (*datetime_capsule).DateType; + STD_TIME_TYPE = (*datetime_capsule).TimeType; + } +} + +pub fn initialize_typeref(py: pyo3::Python) { + static INIT: std::sync::Once = std::sync::Once::new(); + + INIT.call_once(|| _initialize_typeref(py)); +} diff --git a/uv.lock b/uv.lock index de45cac..6d46030 100644 --- a/uv.lock +++ b/uv.lock @@ -10,589 +10,177 @@ resolution-markers = [ [[package]] name = "colorama" version = "0.4.6" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -sdist = { url = "https://mirror-pypi.runflare.com/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "exceptiongroup" version = "1.3.1" -source = { registry = "https://mirror-pypi.runflare.com/simple" } +source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", version = "4.13.2", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version < '3.9'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, ] -sdist = { url = "https://mirror-pypi.runflare.com/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219" } +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598" }, -] - -[[package]] -name = "greenlet" -version = "3.1.1" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://mirror-pypi.runflare.com/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467" } -wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563" }, - { url = "https://mirror-pypi.runflare.com/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83" }, - { url = "https://mirror-pypi.runflare.com/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0" }, - { url = "https://mirror-pypi.runflare.com/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc" }, - { url = "https://mirror-pypi.runflare.com/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617" }, - { url = "https://mirror-pypi.runflare.com/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7" }, - { url = "https://mirror-pypi.runflare.com/packages/e0/1d/a305dce121838d0278cee39d5bb268c657f10a5363ae4b726848f833f1bb/greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6" }, - { url = "https://mirror-pypi.runflare.com/packages/96/28/d62835fb33fb5652f2e98d34c44ad1a0feacc8b1d3f1aecab035f51f267d/greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80" }, - { url = "https://mirror-pypi.runflare.com/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70" }, - { url = "https://mirror-pypi.runflare.com/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159" }, - { url = "https://mirror-pypi.runflare.com/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e" }, - { url = "https://mirror-pypi.runflare.com/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383" }, - { url = "https://mirror-pypi.runflare.com/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a" }, - { url = "https://mirror-pypi.runflare.com/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511" }, - { url = "https://mirror-pypi.runflare.com/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395" }, - { url = "https://mirror-pypi.runflare.com/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39" }, - { url = "https://mirror-pypi.runflare.com/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d" }, - { url = "https://mirror-pypi.runflare.com/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79" }, - { url = "https://mirror-pypi.runflare.com/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa" }, - { url = "https://mirror-pypi.runflare.com/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36" }, - { url = "https://mirror-pypi.runflare.com/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9" }, - { url = "https://mirror-pypi.runflare.com/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0" }, - { url = "https://mirror-pypi.runflare.com/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942" }, - { url = "https://mirror-pypi.runflare.com/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01" }, - { url = "https://mirror-pypi.runflare.com/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1" }, - { url = "https://mirror-pypi.runflare.com/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff" }, - { url = "https://mirror-pypi.runflare.com/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a" }, - { url = "https://mirror-pypi.runflare.com/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4" }, - { url = "https://mirror-pypi.runflare.com/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e" }, - { url = "https://mirror-pypi.runflare.com/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1" }, - { url = "https://mirror-pypi.runflare.com/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c" }, - { url = "https://mirror-pypi.runflare.com/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761" }, - { url = "https://mirror-pypi.runflare.com/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011" }, - { url = "https://mirror-pypi.runflare.com/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13" }, - { url = "https://mirror-pypi.runflare.com/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b" }, - { url = "https://mirror-pypi.runflare.com/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822" }, - { url = "https://mirror-pypi.runflare.com/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01" }, - { url = "https://mirror-pypi.runflare.com/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6" }, - { url = "https://mirror-pypi.runflare.com/packages/97/83/bdf5f69fcf304065ec7cf8fc7c08248479cfed9bcca02bf0001c07e000aa/greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9" }, - { url = "https://mirror-pypi.runflare.com/packages/31/4a/2d4443adcb38e1e90e50c653a26b2be39998ea78ca1a4cf414dfdeb2e98b/greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111" }, - { url = "https://mirror-pypi.runflare.com/packages/5a/c9/b5d9ac1b932aa772dd1eb90a8a2b30dbd7ad5569dcb7fdac543810d206b4/greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81" }, - { url = "https://mirror-pypi.runflare.com/packages/a7/25/de419a2b22fa6e18ce3b2a5adb01d33ec7b2784530f76fa36ba43d8f0fac/greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8" }, - { url = "https://mirror-pypi.runflare.com/packages/d8/88/0ce16c0afb2d71d85562a7bcd9b092fec80a7767ab5b5f7e1bbbca8200f8/greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1" }, - { url = "https://mirror-pypi.runflare.com/packages/5a/10/39a417ad0afb0b7e5b150f1582cdeb9416f41f2e1df76018434dfac4a6cc/greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd" }, - { url = "https://mirror-pypi.runflare.com/packages/9f/f5/e9b151ddd2ed0508b7a47bef7857e46218dbc3fd10e564617a3865abfaac/greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7" }, - { url = "https://mirror-pypi.runflare.com/packages/86/97/2c86989ca4e0f089fbcdc9229c972a01ef53abdafd5ae89e0f3dcdcd4adb/greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef" }, - { url = "https://mirror-pypi.runflare.com/packages/d3/50/7b7a3e10ed82c760c1fd8d3167a7c95508e9fdfc0b0604f05ed1a9a9efdc/greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d" }, - { url = "https://mirror-pypi.runflare.com/packages/8c/82/8051e82af6d6b5150aacb6789a657a8afd48f0a44d8e91cb72aaaf28553a/greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3" }, - { url = "https://mirror-pypi.runflare.com/packages/f9/74/f66de2785880293780eebd18a2958aeea7cbe7814af1ccef634f4701f846/greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42" }, - { url = "https://mirror-pypi.runflare.com/packages/68/23/acd9ca6bc412b02b8aa755e47b16aafbe642dde0ad2f929f836e57a7949c/greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f" }, - { url = "https://mirror-pypi.runflare.com/packages/03/d3/1006543621f16689f6dc75f6bcf06e3c23e044c26fe391c16c253623313e/greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145" }, - { url = "https://mirror-pypi.runflare.com/packages/2f/c1/ad71ce1b5f61f900593377b3f77b39408bce5dc96754790311b49869e146/greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c" }, - { url = "https://mirror-pypi.runflare.com/packages/f7/ff/183226685b478544d61d74804445589e069d00deb8ddef042699733950c7/greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e" }, - { url = "https://mirror-pypi.runflare.com/packages/c0/8b/9b3b85a89c22f55f315908b94cd75ab5fed5973f7393bbef000ca8b2c5c1/greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e" }, - { url = "https://mirror-pypi.runflare.com/packages/b8/1c/248fadcecd1790b0ba793ff81fa2375c9ad6442f4c748bf2cc2e6563346a/greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c" }, - { url = "https://mirror-pypi.runflare.com/packages/ae/02/e7d0aef2354a38709b764df50b2b83608f0621493e47f47694eb80922822/greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22" }, -] - -[[package]] -name = "greenlet" -version = "3.2.5" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -sdist = { url = "https://mirror-pypi.runflare.com/packages/b0/f5/3e9eafb4030588337b2a2ae4df46212956854e9069c07b53aa3caabafd47/greenlet-3.2.5.tar.gz", hash = "sha256:c816554eb33e7ecf9ba4defcb1fd8c994e59be6b4110da15480b3e7447ea4286" } -wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/ff/d6/b3db928fc329b1b19ba32ffe143d2305f3aaafc583f5e1074c74ec445189/greenlet-3.2.5-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:34cc7cf8ab6f4b85298b01e13e881265ee7b3c1daf6bc10a2944abc15d4f87c3" }, - { url = "https://mirror-pypi.runflare.com/packages/b3/ff/ab0ad4ff3d9e1faa266de4f6c79763b33fccd9265995f2940192494cc0ec/greenlet-3.2.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c11fe0cfb0ce33132f0b5d27eeadd1954976a82e5e9b60909ec2c4b884a55382" }, - { url = "https://mirror-pypi.runflare.com/packages/da/dd/7b3ac77099a1671af8077ecedb12c9a1be1310e4c35bb69fd34c18ab6093/greenlet-3.2.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a145f4b1c4ed7a2c94561b7f18b4beec3d3fb6f0580db22f7ed1d544e0620b34" }, - { url = "https://mirror-pypi.runflare.com/packages/0f/36/84630e9ff1dfc8b7690957c0f77834a84eabdbd9c4977c3a2d0cbd5325c2/greenlet-3.2.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1d01bdd67db3e5711e6246e451d7a0f75fae7bbf40adde129296a7f9aa7cc9" }, - { url = "https://mirror-pypi.runflare.com/packages/12/c4/6a2ee6c676dea7a05a3c3c1291fbc8ea44f26456b0accc891471293825af/greenlet-3.2.5-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd593db7ee1fa8a513a48a404f8cc4126998a48025e3f5cbbc68d51be0a6bf66" }, - { url = "https://mirror-pypi.runflare.com/packages/01/c0/75e75c2c993aa850292561ec80f5c263e3924e5843aa95a38716df69304c/greenlet-3.2.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ac8db07bced2c39b987bba13a3195f8157b0cfbce54488f86919321444a1cc3c" }, - { url = "https://mirror-pypi.runflare.com/packages/ee/03/e38ebf9024a0873fe8f60f5b7bc36bfb3be5e13efe4d798240f2d1f0fb73/greenlet-3.2.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4544ab2cfd5912e42458b13516429e029f87d8bbcdc8d5506db772941ae12493" }, - { url = "https://mirror-pypi.runflare.com/packages/d8/7b/c6e1192c795c0c12871e199237909a6bd35757d92c8472c7c019959b8637/greenlet-3.2.5-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:acabf468466d18017e2ae5fbf1a5a88b86b48983e550e1ae1437b69a83d9f4ac" }, - { url = "https://mirror-pypi.runflare.com/packages/3e/b6/9887b559f3e1952d23052ec352e9977e808a2246c7cb8282a38337221e88/greenlet-3.2.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:472841de62d60f2cafd60edd4fd4dd7253eb70e6eaf14b8990dcaf177f4af957" }, - { url = "https://mirror-pypi.runflare.com/packages/8a/be/e3e48b63bbc27d660fa1d98aecb64906b90a12e686a436169c1330ef34b2/greenlet-3.2.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d951e7d628a6e8b68af469f0fe4f100ef64c4054abeb9cdafbfaa30a920c950" }, - { url = "https://mirror-pypi.runflare.com/packages/4c/ac/e731ed62576e91e533b36d0d97325adc2786674ab9e48ed8a6a24f4ef4e9/greenlet-3.2.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8317d732e2ae0935d9ed2af2ea876fa714cf6f3b887a31ca150b54329b0a6e9" }, - { url = "https://mirror-pypi.runflare.com/packages/70/64/99e5cdceb494bd4c1341c45b93f322601d2c8a5e1e4d1c7a2d24c5ed0570/greenlet-3.2.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce8aed6fdd5e07d3cbb988cbdc188266a4eb9e1a52db9ef5c6526e59962d3933" }, - { url = "https://mirror-pypi.runflare.com/packages/ee/e9/968e11f388c2b8792d3b8b40a57984c894a3b4745dae3662dce722653bc5/greenlet-3.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:60c06b502d56d5451f60ca665691da29f79ed95e247bcf8ce5024d7bbe64acb9" }, - { url = "https://mirror-pypi.runflare.com/packages/cb/2c/b5f2c4c68d753dce08218dc5a6b21d82238fdfdc44309032f6fe24d285e6/greenlet-3.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d2a78e6f1bf3f1672df91e212a2f8314e1e7c922f065d14cbad4bc815059467" }, - { url = "https://mirror-pypi.runflare.com/packages/ad/32/022b21523eee713e7550162d5ca6aed23f913cc2c6232b154b9fd9badc07/greenlet-3.2.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2acb30e77042f747ca81f0a10cc153296567e92e666c5e1b117f4595afd43352" }, - { url = "https://mirror-pypi.runflare.com/packages/90/c5/8a3b0ed3cc34d8b988a44349437dfa0941f9c23ac108175f7b4ccea97111/greenlet-3.2.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:393c03c26c865f17f31d8db2f09603fadbe0581ad85a5d5908b131549fc38217" }, - { url = "https://mirror-pypi.runflare.com/packages/b1/2c/2627bea183554695016af6cae93d7474fa90f61e5a6601a84ae7841cb720/greenlet-3.2.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:04e6a202cde56043fd355fefd1552c4caa5c087528121871d950eb4f1b51fa99" }, - { url = "https://mirror-pypi.runflare.com/packages/2f/1b/75a5aeff487a26ba427a3837da6372f1fe6f2a9c6b2898e28ac99d491c11/greenlet-3.2.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:45fcea7b697b91290b36eafc12fff479aca6ba6500d98ef6f34d5634c7119cbe" }, - { url = "https://mirror-pypi.runflare.com/packages/53/91/9b5dfb4f3c88f8247c7a8f4c3759f0740bfa6bb0c59a9f6bf938e913df56/greenlet-3.2.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96e2bb8a56b7e1aed1dbfbbe0050cb2ecca99c7c91892fd1771e3afab63b3e3" }, - { url = "https://mirror-pypi.runflare.com/packages/b4/8d/d0b086410512d9859c84e9242a9b341de9f5566011ddf3a3f6886b842b61/greenlet-3.2.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d7456e67b0be653dfe643bb37d9566cd30939c80f858e2ce6d2d54951f75b14a" }, - { url = "https://mirror-pypi.runflare.com/packages/ef/37/59fe12fe456e84ced6ba71781e28cde52a3124d1dd2077bc1727021f49fd/greenlet-3.2.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5ceb29d1f74c7280befbbfa27b9bf91ba4a07a1a00b2179a5d953fc219b16c42" }, - { url = "https://mirror-pypi.runflare.com/packages/dd/95/d5d332fb73affaf7a1fbe80e49c2c7eae4f17c645af24a3b3fa25736d6f0/greenlet-3.2.5-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f2cc88b50b9006b324c1b9f5f3552f9d4564c78af57cdfb4c7baf4f0aa089146" }, - { url = "https://mirror-pypi.runflare.com/packages/6c/77/89458e20db5a4f1c64f9a0191561227e76d809941ca2d7529006d17d3450/greenlet-3.2.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e66872daffa360b2537170b73ad530f14fa31785b1bc78080125d92edf0a6def" }, - { url = "https://mirror-pypi.runflare.com/packages/90/f8/9962175d2f2eaa629a7fd7545abacc8c4deda3baa4e52c1526d2eb5f5546/greenlet-3.2.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c5445ddb7b586d870dad32ca9fc47c287d6022a528d194efdb8912093c5303ad" }, - { url = "https://mirror-pypi.runflare.com/packages/f5/d7/826d0e080f0a7ad5ec47c8d143bbd3ca0887657bb806595fe2434d12938a/greenlet-3.2.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:752c896a8c976548faafe8a306d446c6a4c68d4fd24699b84d4393bd9ac69a8e" }, - { url = "https://mirror-pypi.runflare.com/packages/41/cc/33bd4c2f816be8c8e16f71740c4130adf3a66a3dd2ba29de72b9d8dd1096/greenlet-3.2.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499b809e7738c8af0ff9ac9d5dd821cb93f4293065a9237543217f0b252f950a" }, - { url = "https://mirror-pypi.runflare.com/packages/48/79/f3891dcfc59097474a53cc3c624f2f2465e431ab493bda043b8c873fb20a/greenlet-3.2.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2c7429f6e9cea7cbf2637d86d3db12806ba970f7f972fcab39d6b54b4457cbaf" }, - { url = "https://mirror-pypi.runflare.com/packages/ca/47/212b47e6d2d7a04c4083db1af2fdd291bc8fe99b7e3571bfa560b65fc361/greenlet-3.2.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a5e4b25e855800fba17713020c5c33e0a4b7a1829027719344f0c7c8870092a2" }, - { url = "https://mirror-pypi.runflare.com/packages/f6/9d/4e9b941be05f8da7ba804c6413761d2c11cca05994cbf0a015bd729419f0/greenlet-3.2.5-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7123b29e6bad2f3f89681be4ef316480fca798ebe8d22fbaced9cc3775007a4f" }, - { url = "https://mirror-pypi.runflare.com/packages/23/cb/a73625c9a35138330014ecf3740c0d62e0c2b5e7279bb7f2586b1b199fac/greenlet-3.2.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6e8fe0c72603201a86b2e038daf9b6c8570715f8779566419cff543b6ace88de" }, - { url = "https://mirror-pypi.runflare.com/packages/83/49/6d1531109507bce7dfb23acf57a87013627ed3ac058851176e443a6a9134/greenlet-3.2.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:050703a60603db0e817364d69e048c70af299040c13a7e67792b9e62d4571196" }, - { url = "https://mirror-pypi.runflare.com/packages/f7/38/f958ee90fab93529b30cc1e4a59b27c1112b640570043a84af84da3b3b98/greenlet-3.2.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6712bfd520530eb67331813f7112d3ee18e206f48b3d026d8a96cd2d2ad20251" }, - { url = "https://mirror-pypi.runflare.com/packages/51/c1/a603906e79716d61f08afedaf8aed62017661457aef233d62d6e57ecd511/greenlet-3.2.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bc06a78fa3ffbe2a75f1ebc7e040eacf6fa1050a9432953ab111fbbbf0d03c1" }, - { url = "https://mirror-pypi.runflare.com/packages/3f/8f/f880ff4587d236b4d06893fb34da6b299aa0d00f6c8259673f80e1b6d63c/greenlet-3.2.5-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:dbe0e81e24982bb45907ca20152b31c2e3300ca352fdc4acbd4956e4a2cbc195" }, - { url = "https://mirror-pypi.runflare.com/packages/3c/50/f6c78b8420187fdfe97fcf2e6d1dd243a7742d272c32fd4d4b1095474b37/greenlet-3.2.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:15871afc0d78ec87d15d8412b337f287fc69f8f669346e391585824970931c48" }, - { url = "https://mirror-pypi.runflare.com/packages/26/d6/3277f92e1961e6e9f41d9f173ea74b5c1f7065072637669f761626f26cc0/greenlet-3.2.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5bf0d7d62e356ef2e87e55e46a4e930ac165f9372760fb983b5631bb479e9d3a" }, - { url = "https://mirror-pypi.runflare.com/packages/2a/6a/4f79d2e7b5ef3723fc5ffea0d6cb22627e5f95e0f19c973fa12bf1cf7891/greenlet-3.2.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6dff6433742073e5b6ad40953a78a0e8cddcb3f6869e5ea635d29a810ca5e7d0" }, - { url = "https://mirror-pypi.runflare.com/packages/4d/59/7aadf33f23c65dbf4db27e7f5b60c414797a61e954352ae4a86c5c8b0553/greenlet-3.2.5-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdd67619cefe1cc9fcab57c8853d2bb36eca9f166c0058cc0d428d471f7c785c" }, - { url = "https://mirror-pypi.runflare.com/packages/1d/46/b3422959f830de28a4eea447414e6bd7b980d755892f66ab52ad805da1c4/greenlet-3.2.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3828b309dfb1f117fe54867512a8265d8d4f00f8de6908eef9b885f4d8789062" }, - { url = "https://mirror-pypi.runflare.com/packages/54/4a/3d1c9728f093415637cf3696909fa10852632e33e68238fb8ca60eb90de1/greenlet-3.2.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:67725ae9fea62c95cf1aa230f1b8d4dc38f7cd14f6103d1df8a5a95657eb8e54" }, -] - -[[package]] -name = "greenlet" -version = "3.3.2" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] -sdist = { url = "https://mirror-pypi.runflare.com/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2" } -wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/38/3f/9859f655d11901e7b2996c6e3d33e0caa9a1d4572c3bc61ed0faa64b2f4c/greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d" }, - { url = "https://mirror-pypi.runflare.com/packages/fb/07/cb284a8b5c6498dbd7cba35d31380bb123d7dceaa7907f606c8ff5993cbf/greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13" }, - { url = "https://mirror-pypi.runflare.com/packages/ed/45/67922992b3a152f726163b19f890a85129a992f39607a2a53155de3448b8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e" }, - { url = "https://mirror-pypi.runflare.com/packages/ad/55/9f1ebb5a825215fadcc0f7d5073f6e79e3007e3282b14b22d6aba7ca6cb8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f" }, - { url = "https://mirror-pypi.runflare.com/packages/24/b4/21f5455773d37f94b866eb3cf5caed88d6cea6dd2c6e1f9c34f463cba3ec/greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef" }, - { url = "https://mirror-pypi.runflare.com/packages/00/68/91f061a926abead128fe1a87f0b453ccf07368666bd59ffa46016627a930/greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca" }, - { url = "https://mirror-pypi.runflare.com/packages/ac/78/f93e840cbaef8becaf6adafbaf1319682a6c2d8c1c20224267a5c6c8c891/greenlet-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:5d0e35379f93a6d0222de929a25ab47b5eb35b5ef4721c2b9cbcc4036129ff1f" }, - { url = "https://mirror-pypi.runflare.com/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86" }, - { url = "https://mirror-pypi.runflare.com/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f" }, - { url = "https://mirror-pypi.runflare.com/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55" }, - { url = "https://mirror-pypi.runflare.com/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358" }, - { url = "https://mirror-pypi.runflare.com/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99" }, - { url = "https://mirror-pypi.runflare.com/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be" }, - { url = "https://mirror-pypi.runflare.com/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5" }, - { url = "https://mirror-pypi.runflare.com/packages/e1/a1/65bbc059a43a7e2143ec4fc1f9e3f673e04f9c7b371a494a101422ac4fd5/greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd" }, - { url = "https://mirror-pypi.runflare.com/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd" }, - { url = "https://mirror-pypi.runflare.com/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd" }, - { url = "https://mirror-pypi.runflare.com/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac" }, - { url = "https://mirror-pypi.runflare.com/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070" }, - { url = "https://mirror-pypi.runflare.com/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79" }, - { url = "https://mirror-pypi.runflare.com/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395" }, - { url = "https://mirror-pypi.runflare.com/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f" }, - { url = "https://mirror-pypi.runflare.com/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643" }, - { url = "https://mirror-pypi.runflare.com/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4" }, - { url = "https://mirror-pypi.runflare.com/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986" }, - { url = "https://mirror-pypi.runflare.com/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92" }, - { url = "https://mirror-pypi.runflare.com/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab" }, - { url = "https://mirror-pypi.runflare.com/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a" }, - { url = "https://mirror-pypi.runflare.com/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b" }, - { url = "https://mirror-pypi.runflare.com/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124" }, - { url = "https://mirror-pypi.runflare.com/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327" }, - { url = "https://mirror-pypi.runflare.com/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab" }, - { url = "https://mirror-pypi.runflare.com/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082" }, - { url = "https://mirror-pypi.runflare.com/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9" }, - { url = "https://mirror-pypi.runflare.com/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506" }, - { url = "https://mirror-pypi.runflare.com/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce" }, - { url = "https://mirror-pypi.runflare.com/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5" }, - { url = "https://mirror-pypi.runflare.com/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492" }, - { url = "https://mirror-pypi.runflare.com/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71" }, - { url = "https://mirror-pypi.runflare.com/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54" }, - { url = "https://mirror-pypi.runflare.com/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4" }, - { url = "https://mirror-pypi.runflare.com/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff" }, - { url = "https://mirror-pypi.runflare.com/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4" }, - { url = "https://mirror-pypi.runflare.com/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727" }, - { url = "https://mirror-pypi.runflare.com/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e" }, - { url = "https://mirror-pypi.runflare.com/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a" }, + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" -source = { registry = "https://mirror-pypi.runflare.com/simple" } +source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.9.*'", "python_full_version < '3.9'", ] -sdist = { url = "https://mirror-pypi.runflare.com/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] name = "iniconfig" version = "2.3.0" -source = { registry = "https://mirror-pypi.runflare.com/simple" } +source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.10'", ] -sdist = { url = "https://mirror-pypi.runflare.com/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" }, -] - -[[package]] -name = "librt" -version = "0.8.1" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -sdist = { url = "https://mirror-pypi.runflare.com/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73" } -wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/7c/5f/63f5fa395c7a8a93558c0904ba8f1c8d1b997ca6a3de61bc7659970d66bf/librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc" }, - { url = "https://mirror-pypi.runflare.com/packages/ff/e0/0472cf37267b5920eff2f292ccfaede1886288ce35b7f3203d8de00abfe6/librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7" }, - { url = "https://mirror-pypi.runflare.com/packages/c8/be/8bd1359fdcd27ab897cd5963294fa4a7c83b20a8564678e4fd12157e56a5/librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6" }, - { url = "https://mirror-pypi.runflare.com/packages/e2/fe/163e33fdd091d0c2b102f8a60cc0a61fd730ad44e32617cd161e7cd67a01/librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0" }, - { url = "https://mirror-pypi.runflare.com/packages/01/99/f85130582f05dcf0c8902f3d629270231d2f4afdfc567f8305a952ac7f14/librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b" }, - { url = "https://mirror-pypi.runflare.com/packages/6f/54/cb5e4d03659e043a26c74e08206412ac9a3742f0477d96f9761a55313b5f/librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6" }, - { url = "https://mirror-pypi.runflare.com/packages/b1/81/a3a01e4240579c30f3487f6fed01eb4bc8ef0616da5b4ebac27ca19775f3/librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71" }, - { url = "https://mirror-pypi.runflare.com/packages/08/b0/fc2d54b4b1c6fb81e77288ff31ff25a2c1e62eaef4424a984f228839717b/librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7" }, - { url = "https://mirror-pypi.runflare.com/packages/96/96/85daa73ffbd87e1fb287d7af6553ada66bf25a2a6b0de4764344a05469f6/librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05" }, - { url = "https://mirror-pypi.runflare.com/packages/12/9c/c3aa7a2360383f4bf4f04d98195f2739a579128720c603f4807f006a4225/librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891" }, - { url = "https://mirror-pypi.runflare.com/packages/61/19/d350ea89e5274665185dabc4bbb9c3536c3411f862881d316c8b8e00eb66/librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7" }, - { url = "https://mirror-pypi.runflare.com/packages/4f/d6/45d587d3d41c112e9543a0093d883eb57a24a03e41561c127818aa2a6bcc/librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2" }, - { url = "https://mirror-pypi.runflare.com/packages/1d/01/0e748af5e4fee180cf7cd12bd12b0513ad23b045dccb2a83191bde82d168/librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd" }, - { url = "https://mirror-pypi.runflare.com/packages/9d/4d/7184806efda571887c798d573ca4134c80ac8642dcdd32f12c31b939c595/librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965" }, - { url = "https://mirror-pypi.runflare.com/packages/ae/88/c3c52d2a5d5101f28d3dc89298444626e7874aa904eed498464c2af17627/librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da" }, - { url = "https://mirror-pypi.runflare.com/packages/d6/5d/6fb0a25b6a8906e85b2c3b87bee1d6ed31510be7605b06772f9374ca5cb3/librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0" }, - { url = "https://mirror-pypi.runflare.com/packages/b2/a6/8006ae81227105476a45691f5831499e4d936b1c049b0c1feb17c11b02d1/librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e" }, - { url = "https://mirror-pypi.runflare.com/packages/ee/19/60e07886ad16670aae57ef44dada41912c90906a6fe9f2b9abac21374748/librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3" }, - { url = "https://mirror-pypi.runflare.com/packages/9c/cf/f666c89d0e861d05600438213feeb818c7514d3315bae3648b1fc145d2b6/librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac" }, - { url = "https://mirror-pypi.runflare.com/packages/8f/ef/f1bea01e40b4a879364c031476c82a0dc69ce068daad67ab96302fed2d45/librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596" }, - { url = "https://mirror-pypi.runflare.com/packages/9b/80/cdab544370cc6bc1b72ea369525f547a59e6938ef6863a11ab3cd24759af/librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99" }, - { url = "https://mirror-pypi.runflare.com/packages/9d/9c/48d6ed8dac595654f15eceab2035131c136d1ae9a1e3548e777bb6dbb95d/librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe" }, - { url = "https://mirror-pypi.runflare.com/packages/16/01/35b68b1db517f27a01be4467593292eb5315def8900afad29fabf56304ba/librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb" }, - { url = "https://mirror-pypi.runflare.com/packages/71/02/796fe8f02822235966693f257bf2c79f40e11337337a657a8cfebba5febc/librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b" }, - { url = "https://mirror-pypi.runflare.com/packages/28/ad/232e13d61f879a42a4e7117d65e4984bb28371a34bb6fb9ca54ec2c8f54e/librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9" }, - { url = "https://mirror-pypi.runflare.com/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a" }, - { url = "https://mirror-pypi.runflare.com/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9" }, - { url = "https://mirror-pypi.runflare.com/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb" }, - { url = "https://mirror-pypi.runflare.com/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d" }, - { url = "https://mirror-pypi.runflare.com/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7" }, - { url = "https://mirror-pypi.runflare.com/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440" }, - { url = "https://mirror-pypi.runflare.com/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9" }, - { url = "https://mirror-pypi.runflare.com/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972" }, - { url = "https://mirror-pypi.runflare.com/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921" }, - { url = "https://mirror-pypi.runflare.com/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0" }, - { url = "https://mirror-pypi.runflare.com/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a" }, - { url = "https://mirror-pypi.runflare.com/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444" }, - { url = "https://mirror-pypi.runflare.com/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d" }, - { url = "https://mirror-pypi.runflare.com/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35" }, - { url = "https://mirror-pypi.runflare.com/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583" }, - { url = "https://mirror-pypi.runflare.com/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c" }, - { url = "https://mirror-pypi.runflare.com/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04" }, - { url = "https://mirror-pypi.runflare.com/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363" }, - { url = "https://mirror-pypi.runflare.com/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0" }, - { url = "https://mirror-pypi.runflare.com/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012" }, - { url = "https://mirror-pypi.runflare.com/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb" }, - { url = "https://mirror-pypi.runflare.com/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b" }, - { url = "https://mirror-pypi.runflare.com/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d" }, - { url = "https://mirror-pypi.runflare.com/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a" }, - { url = "https://mirror-pypi.runflare.com/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79" }, - { url = "https://mirror-pypi.runflare.com/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0" }, - { url = "https://mirror-pypi.runflare.com/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f" }, - { url = "https://mirror-pypi.runflare.com/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c" }, - { url = "https://mirror-pypi.runflare.com/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc" }, - { url = "https://mirror-pypi.runflare.com/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c" }, - { url = "https://mirror-pypi.runflare.com/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3" }, - { url = "https://mirror-pypi.runflare.com/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14" }, - { url = "https://mirror-pypi.runflare.com/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7" }, - { url = "https://mirror-pypi.runflare.com/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6" }, - { url = "https://mirror-pypi.runflare.com/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071" }, - { url = "https://mirror-pypi.runflare.com/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78" }, - { url = "https://mirror-pypi.runflare.com/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023" }, - { url = "https://mirror-pypi.runflare.com/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730" }, - { url = "https://mirror-pypi.runflare.com/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3" }, - { url = "https://mirror-pypi.runflare.com/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1" }, - { url = "https://mirror-pypi.runflare.com/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee" }, - { url = "https://mirror-pypi.runflare.com/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7" }, - { url = "https://mirror-pypi.runflare.com/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040" }, - { url = "https://mirror-pypi.runflare.com/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e" }, - { url = "https://mirror-pypi.runflare.com/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732" }, - { url = "https://mirror-pypi.runflare.com/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624" }, - { url = "https://mirror-pypi.runflare.com/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4" }, - { url = "https://mirror-pypi.runflare.com/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382" }, - { url = "https://mirror-pypi.runflare.com/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994" }, - { url = "https://mirror-pypi.runflare.com/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a" }, - { url = "https://mirror-pypi.runflare.com/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4" }, - { url = "https://mirror-pypi.runflare.com/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61" }, - { url = "https://mirror-pypi.runflare.com/packages/01/1f/c7d8b66a3ca3ca3ed8ded4b32c96ee58a45920ebbbaa934355c74adcc33e/librt-0.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3dff3d3ca8db20e783b1bc7de49c0a2ab0b8387f31236d6a026597d07fcd68ac" }, - { url = "https://mirror-pypi.runflare.com/packages/56/be/ee9ba1730052313d08457f19beaa1b878619978863fba09b40aed5b5c123/librt-0.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08eec3a1fc435f0d09c87b6bf1ec798986a3544f446b864e4099633a56fcd9ed" }, - { url = "https://mirror-pypi.runflare.com/packages/81/27/b7309298b96f7690cec3ceee38004c1a7f60fcd96d952d3ac344a1e3e8b3/librt-0.8.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3f0a41487fd5fad7e760b9e8a90e251e27c2816fbc2cff36a22a0e6bcbbd9dd" }, - { url = "https://mirror-pypi.runflare.com/packages/10/48/160a5aacdcb21824b10a52378c39e88c46a29bb31efdaf3910dd1f9b670e/librt-0.8.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bacdb58d9939d95cc557b4dbaa86527c9db2ac1ed76a18bc8d26f6dc8647d851" }, - { url = "https://mirror-pypi.runflare.com/packages/ee/65/33dd1d8caabb7c6805d87d095b143417dc96b0277c06ffa0508361422c82/librt-0.8.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d7ab1f01aa753188605b09a51faa44a3327400b00b8cce424c71910fc0a128" }, - { url = "https://mirror-pypi.runflare.com/packages/09/d4/353805aa6181c7950a2462bd6e855366eeca21a501f375228d72a51547df/librt-0.8.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4998009e7cb9e896569f4be7004f09d0ed70d386fa99d42b6d363f6d200501ac" }, - { url = "https://mirror-pypi.runflare.com/packages/06/08/725b3f304d61eba56c713c251fb833a06d84bf93381caad5152366f5d2bb/librt-0.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2cc68eeeef5e906839c7bb0815748b5b0a974ec27125beefc0f942715785b551" }, - { url = "https://mirror-pypi.runflare.com/packages/0e/55/e8cdf04145872b3b97cb9b68287b22d1c08348227063f305aec11a3e6ce7/librt-0.8.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0bf69d79a23f4f40b8673a947a234baeeb133b5078b483b7297c5916539cf5d5" }, - { url = "https://mirror-pypi.runflare.com/packages/8f/d8/23b1c6592d2422dd6829c672f45b1f1c257f219926b0d216fedb572d0184/librt-0.8.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:22b46eabd76c1986ee7d231b0765ad387d7673bbd996aa0d0d054b38ac65d8f6" }, - { url = "https://mirror-pypi.runflare.com/packages/c9/92/2b44fd3cc3313f44e43bdbb41343735b568fa675fa351642b408ee48d418/librt-0.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:237796479f4d0637d6b9cbcb926ff424a97735e68ade6facf402df4ec93375ed" }, - { url = "https://mirror-pypi.runflare.com/packages/00/23/92313ecdab80e142d8ea10e8dfa6297694359dbaacc9e81679bdc8cbceb6/librt-0.8.1-cp39-cp39-win32.whl", hash = "sha256:4beb04b8c66c6ae62f8c1e0b2f097c1ebad9295c929a8d5286c05eae7c2fc7dc" }, - { url = "https://mirror-pypi.runflare.com/packages/68/36/18f6e768afad6b55a690d38427c53251b69b7ba8795512730fd2508b31a9/librt-0.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:64548cde61b692dc0dc379f4b5f59a2f582c2ebe7890d09c1ae3b9e66fa015b7" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] name = "maturin" -version = "1.12.2" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -dependencies = [ - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://mirror-pypi.runflare.com/packages/ae/13/aeff8a21835ed0e40c329c286750fcdcdcbf231f1a5cb327378666c5def6/maturin-1.12.2.tar.gz", hash = "sha256:d6253079f53dbb692395a13abddc0f2d3d96af32f8c0b32e2912849713c55794" } -wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/8a/9d/4811e1fcaa346a0b9fad6aee0ac0eec9eb376a24fe27c66d5d4fe975586e/maturin-1.12.2-py3-none-linux_armv6l.whl", hash = "sha256:0ed31b6a392928ad23645a470edc4f3814b952a416e41f8e5daac42d7bfbabc6" }, - { url = "https://mirror-pypi.runflare.com/packages/69/db/74d582af74c32bbda12e4d7e153b389884409a1c5cd31edc9d3194d515f7/maturin-1.12.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f1c2e4ee43bf286b052091a3b2356a157978985837c7aed42354deb2947a4006" }, - { url = "https://mirror-pypi.runflare.com/packages/9d/6f/71be226c6780387f032c0b4ab791c390c7162ed62f93a11e600f9266dafd/maturin-1.12.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:04c9c4f9c9f904f007cbfcd4640c406e53f19d04c220f5940d1537edb914d325" }, - { url = "https://mirror-pypi.runflare.com/packages/6a/cc/989dce6140227277b4184aab248d07fe67fa11f95411ccf90e272542287d/maturin-1.12.2-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:4bdc486b9ab80d8b50143ecc9a1924b890866fe95be150dd9a59fa22a6b37238" }, - { url = "https://mirror-pypi.runflare.com/packages/a1/e8/02bb64f7150013d8af3ca622944e22f550beb312b6d5cf8760dc2896cce8/maturin-1.12.2-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:134e895578258a693ba1d55b166c2ba96e9f51067e106b8a74d422432653d45b" }, - { url = "https://mirror-pypi.runflare.com/packages/84/81/b603a74bef68fabd402d1e54f43560213ea69c3c01467610d0256eea013b/maturin-1.12.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:39665d622dcc950ab17b9569e8cab84a4d64eea6a18b540a8b49e00c0f7dda02" }, - { url = "https://mirror-pypi.runflare.com/packages/70/a5/387c7bced34f7fd8d08d399c6b1ac3d94d7ca50c9f87db9e1bc0dd8c8d08/maturin-1.12.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:ca3b20bcc3aff115c9eaf97340e78bff58829ea1efa16764940dd0d858dcf6af" }, - { url = "https://mirror-pypi.runflare.com/packages/6d/30/d5ae812c54a70d5d3a5b67b073e92d1d14d36675242e2d00e6a175fa6117/maturin-1.12.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:d1617989b4a5dc543fea6d23c28b2f07fadb2c726ff00fe959538ee71a301384" }, - { url = "https://mirror-pypi.runflare.com/packages/84/f4/7baac2fa5324ccdc3f888ff5f6a793f3eb5a7805d89bc17a8bacbe9fc566/maturin-1.12.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6af778e7ee048612e55a1255488db7678741bea2ba881e66a19712f59f2534cb" }, - { url = "https://mirror-pypi.runflare.com/packages/6f/ed/5680efbb1becb4f47da3ada8ea4eb6844d2fd91ae558287e1dd0871cb603/maturin-1.12.2-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:72aad9efe09a6392de9930f2bea80bfcc36fd98e18caa621f512571179c02d41" }, - { url = "https://mirror-pypi.runflare.com/packages/86/20/7e27e07dd2270b707dd0124256cd46bef7c8832476b0aefa2ecd74835365/maturin-1.12.2-py3-none-win32.whl", hash = "sha256:9763d277e143409cf0ce309eb1a493fc4e1e75777364d67ccac39a161b51b5b0" }, - { url = "https://mirror-pypi.runflare.com/packages/3b/6e/9cc0e19c9a336fbc1b9664c1a7955caa6d8fd510c0047ace9be66a33704a/maturin-1.12.2-py3-none-win_amd64.whl", hash = "sha256:c06d218931985035d7ab4d0211ba96027e1bc7e4b01a87c8c4e30a57790403ec" }, - { url = "https://mirror-pypi.runflare.com/packages/2e/67/07ea2c991ca1a55c6b08cd821710736276af7a3e160e1f869ea5c41c78c3/maturin-1.12.2-py3-none-win_arm64.whl", hash = "sha256:a882cc80c241b1e2c27bd1acd713b09e9ac9266a3159cc1e34e8c7b77f049bba" }, -] - -[[package]] -name = "mypy" version = "1.14.1" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] +source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mypy-extensions", marker = "python_full_version < '3.9'" }, - { name = "tomli", marker = "python_full_version < '3.9'" }, - { name = "typing-extensions", version = "4.13.2", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://mirror-pypi.runflare.com/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6" } -wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb" }, - { url = "https://mirror-pypi.runflare.com/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0" }, - { url = "https://mirror-pypi.runflare.com/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d" }, - { url = "https://mirror-pypi.runflare.com/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b" }, - { url = "https://mirror-pypi.runflare.com/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427" }, - { url = "https://mirror-pypi.runflare.com/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f" }, - { url = "https://mirror-pypi.runflare.com/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c" }, - { url = "https://mirror-pypi.runflare.com/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1" }, - { url = "https://mirror-pypi.runflare.com/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8" }, - { url = "https://mirror-pypi.runflare.com/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f" }, - { url = "https://mirror-pypi.runflare.com/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1" }, - { url = "https://mirror-pypi.runflare.com/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae" }, - { url = "https://mirror-pypi.runflare.com/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14" }, - { url = "https://mirror-pypi.runflare.com/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9" }, - { url = "https://mirror-pypi.runflare.com/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11" }, - { url = "https://mirror-pypi.runflare.com/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e" }, - { url = "https://mirror-pypi.runflare.com/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89" }, - { url = "https://mirror-pypi.runflare.com/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b" }, - { url = "https://mirror-pypi.runflare.com/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255" }, - { url = "https://mirror-pypi.runflare.com/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34" }, - { url = "https://mirror-pypi.runflare.com/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a" }, - { url = "https://mirror-pypi.runflare.com/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9" }, - { url = "https://mirror-pypi.runflare.com/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd" }, - { url = "https://mirror-pypi.runflare.com/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107" }, - { url = "https://mirror-pypi.runflare.com/packages/39/02/1817328c1372be57c16148ce7d2bfcfa4a796bedaed897381b1aad9b267c/mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31" }, - { url = "https://mirror-pypi.runflare.com/packages/b9/07/99db9a95ece5e58eee1dd87ca456a7e7b5ced6798fd78182c59c35a7587b/mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6" }, - { url = "https://mirror-pypi.runflare.com/packages/9a/eb/85ea6086227b84bce79b3baf7f465b4732e0785830726ce4a51528173b71/mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319" }, - { url = "https://mirror-pypi.runflare.com/packages/4b/bb/f01bebf76811475d66359c259eabe40766d2f8ac8b8250d4e224bb6df379/mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac" }, - { url = "https://mirror-pypi.runflare.com/packages/2f/c9/84837ff891edcb6dcc3c27d85ea52aab0c4a34740ff5f0ccc0eb87c56139/mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b" }, - { url = "https://mirror-pypi.runflare.com/packages/84/5f/901e18464e6a13f8949b4909535be3fa7f823291b8ab4e4b36cfe57d6769/mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837" }, - { url = "https://mirror-pypi.runflare.com/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35" }, - { url = "https://mirror-pypi.runflare.com/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc" }, - { url = "https://mirror-pypi.runflare.com/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9" }, - { url = "https://mirror-pypi.runflare.com/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb" }, - { url = "https://mirror-pypi.runflare.com/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60" }, - { url = "https://mirror-pypi.runflare.com/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c" }, - { url = "https://mirror-pypi.runflare.com/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1" }, -] - -[[package]] -name = "mypy" -version = "1.19.1" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "librt", marker = "python_full_version >= '3.9' and platform_python_implementation != 'PyPy'" }, - { name = "mypy-extensions", marker = "python_full_version >= '3.9'" }, - { name = "pathspec", marker = "python_full_version >= '3.9'" }, - { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version >= '3.9'" }, -] -sdist = { url = "https://mirror-pypi.runflare.com/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba" } -wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec" }, - { url = "https://mirror-pypi.runflare.com/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b" }, - { url = "https://mirror-pypi.runflare.com/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6" }, - { url = "https://mirror-pypi.runflare.com/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74" }, - { url = "https://mirror-pypi.runflare.com/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1" }, - { url = "https://mirror-pypi.runflare.com/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac" }, - { url = "https://mirror-pypi.runflare.com/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288" }, - { url = "https://mirror-pypi.runflare.com/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab" }, - { url = "https://mirror-pypi.runflare.com/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6" }, - { url = "https://mirror-pypi.runflare.com/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331" }, - { url = "https://mirror-pypi.runflare.com/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925" }, - { url = "https://mirror-pypi.runflare.com/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042" }, - { url = "https://mirror-pypi.runflare.com/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1" }, - { url = "https://mirror-pypi.runflare.com/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e" }, - { url = "https://mirror-pypi.runflare.com/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2" }, - { url = "https://mirror-pypi.runflare.com/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8" }, - { url = "https://mirror-pypi.runflare.com/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a" }, - { url = "https://mirror-pypi.runflare.com/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13" }, - { url = "https://mirror-pypi.runflare.com/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250" }, - { url = "https://mirror-pypi.runflare.com/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b" }, - { url = "https://mirror-pypi.runflare.com/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e" }, - { url = "https://mirror-pypi.runflare.com/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef" }, - { url = "https://mirror-pypi.runflare.com/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75" }, - { url = "https://mirror-pypi.runflare.com/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd" }, - { url = "https://mirror-pypi.runflare.com/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1" }, - { url = "https://mirror-pypi.runflare.com/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718" }, - { url = "https://mirror-pypi.runflare.com/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b" }, - { url = "https://mirror-pypi.runflare.com/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045" }, - { url = "https://mirror-pypi.runflare.com/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957" }, - { url = "https://mirror-pypi.runflare.com/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f" }, - { url = "https://mirror-pypi.runflare.com/packages/b5/f7/88436084550ca9af5e610fa45286be04c3b63374df3e021c762fe8c4369f/mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3" }, - { url = "https://mirror-pypi.runflare.com/packages/ca/a5/43dfad311a734b48a752790571fd9e12d61893849a01bff346a54011957f/mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a" }, - { url = "https://mirror-pypi.runflare.com/packages/88/f0/efbfa391395cce2f2771f937e0620cfd185ec88f2b9cd88711028a768e96/mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67" }, - { url = "https://mirror-pypi.runflare.com/packages/25/05/58b3ba28f5aed10479e899a12d2120d582ba9fa6288851b20bf1c32cbb4f/mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e" }, - { url = "https://mirror-pypi.runflare.com/packages/c5/a0/c006ccaff50b31e542ae69b92fe7e2f55d99fba3a55e01067dd564325f85/mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376" }, - { url = "https://mirror-pypi.runflare.com/packages/b2/ff/8bdb051cd710f01b880472241bd36b3f817a8e1c5d5540d0b761675b6de2/mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24" }, - { url = "https://mirror-pypi.runflare.com/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -sdist = { url = "https://mirror-pypi.runflare.com/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/b3/addd877f871fb1860d46d3a4f206ecb10b946c85846805e6367631926fd3/maturin-1.14.1.tar.gz", hash = "sha256:9d6577a62cd08e0ceba7a0db06fb098e0c9b1b3429bad747a4f3a18215a1b3df", size = 369637, upload-time = "2026-06-19T05:19:49.774Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/97c5a5bd9c71653a066c0976a484eaaae50b9369557838a4176b7b0bdaa5/maturin-1.14.1-py3-none-linux_armv6l.whl", hash = "sha256:522292398945442cdafa9daeb2271b2340fbde57027b818f923f88eab04174f8", size = 10207496, upload-time = "2026-06-19T05:19:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/fe/83/294bca639b0e052f1e2f65199b3db258780c7d4e31408b934c9c974a1379/maturin-1.14.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ffe5ad71f21d1e6603c4dd75f7fee34adf5ed5ebcebb692886549888ebb329ed", size = 19680113, upload-time = "2026-06-19T05:19:13.43Z" }, + { url = "https://files.pythonhosted.org/packages/43/b6/79c881410a3b1c187f7eb3d407aecae646c6a4433d630d72200359015e83/maturin-1.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f3306078070c1508fd715b9116070cbcaff5959024272a9f1e6f5cb29768b86c", size = 10169205, upload-time = "2026-06-19T05:19:16.615Z" }, + { url = "https://files.pythonhosted.org/packages/93/9d/44b6f26dcb7f7a04c5501ac2dbb6ca1490150682baa525ca5860504f9eab/maturin-1.14.1-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:cd457cd88961156e26379e1155bd287cc0ec1c8b2f1582b0660fb31b87c8842d", size = 10188098, upload-time = "2026-06-19T05:19:19.736Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bd/9c0d5d6983905ce2c9edaa073a7e89355a9cf7f396988e05d32f1c37785d/maturin-1.14.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:dfc54ae32e6fcb18302193ab9a30b0b25eefffba994ae13238974805533ef75e", size = 10627576, upload-time = "2026-06-19T05:19:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/e5/33/b096412bd6a7cb399652b260666f901adf88a687181a6dbd6a3f89f0a94e/maturin-1.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a131d912b5267e640bc96d70f4914e10590aed64082ec9abacba7cea52004224", size = 10085181, upload-time = "2026-06-19T05:19:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/56/8d/08c3bf469c38a23c9e6c877e338193001eb604d010fedc08341974e38528/maturin-1.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:be18fc568fb76884c0205456336892a75105ec398e6b667cd777c6268bd06d69", size = 10026363, upload-time = "2026-06-19T05:19:28.904Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a4/c4d1a92839f8745ab4aab988a7db884a79d6d710bd3b286fcf9316dece1a/maturin-1.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:994a0c8ba3ad8a92b3a9ee1b02645d200d610216b15cff5102b0fe65e8e08666", size = 13321347, upload-time = "2026-06-19T05:19:32.411Z" }, + { url = "https://files.pythonhosted.org/packages/b3/fa/170f04624d03fd07d2a8b1b67de83a127af93aef9eaa425839553347297b/maturin-1.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be80866363e605d137991b491a741a84cde9ae350183c4c85f49690ca9aaaa65", size = 10877609, upload-time = "2026-06-19T05:19:35.448Z" }, + { url = "https://files.pythonhosted.org/packages/61/ad/1ae2e1d0ded282bf2c55ac13f0811d87deb425e200ae64a15785675dede9/maturin-1.14.1-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:5282dffd4b539d2be245f4e5b1a5ab6bc1033b58f4a4872f5833f9d43c954aa4", size = 10417316, upload-time = "2026-06-19T05:19:38.28Z" }, + { url = "https://files.pythonhosted.org/packages/fb/27/bf677183920718da49cd7982d6a3ffc440aad8919329f571d189f81b7bdf/maturin-1.14.1-py3-none-win32.whl", hash = "sha256:1a04de0a20188f95c721b5702eed18140bdcccb28c386797093eca3f62f4d4e0", size = 8931293, upload-time = "2026-06-19T05:19:41.183Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/585adeb9167b08d3cdff0032a938b0e72655c92003df4f52c3f696a1bcc2/maturin-1.14.1-py3-none-win_amd64.whl", hash = "sha256:3c9f94640ecc4895e94abaf834a0684430032c865b2748a36c12461fd9252fdd", size = 10314067, upload-time = "2026-06-19T05:19:44.389Z" }, + { url = "https://files.pythonhosted.org/packages/51/d4/dac8c0720ae246be1700afb6fbdbbea20fe35b13f6570b2f70faa005df77/maturin-1.14.1-py3-none-win_arm64.whl", hash = "sha256:15cea8fcb3ba47dd636f50092bb34baea8b04ac777392f23e6bf8a9a61efb894", size = 9718943, upload-time = "2026-06-19T05:19:47.49Z" }, ] [[package]] name = "packaging" -version = "26.0" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -sdist = { url = "https://mirror-pypi.runflare.com/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4" } +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529" }, -] - -[[package]] -name = "pathspec" -version = "1.0.4" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -sdist = { url = "https://mirror-pypi.runflare.com/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645" } -wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] name = "pluggy" version = "1.5.0" -source = { registry = "https://mirror-pypi.runflare.com/simple" } +source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] -sdist = { url = "https://mirror-pypi.runflare.com/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" }, + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] name = "pluggy" version = "1.6.0" -source = { registry = "https://mirror-pypi.runflare.com/simple" } +source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.10'", "python_full_version == '3.9.*'", ] -sdist = { url = "https://mirror-pypi.runflare.com/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "pygments" -version = "2.19.2" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -sdist = { url = "https://mirror-pypi.runflare.com/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" } -wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" }, -] - -[[package]] -name = "pypika" -version = "0.51.1" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -dependencies = [ - { name = "typing-extensions", version = "4.13.2", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version < '3.9'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, -] -sdist = { url = "https://mirror-pypi.runflare.com/packages/f8/78/cbaebba88e05e2dcda13ca203131b38d3640219f20ebb49676d26714861b/pypika-0.51.1.tar.gz", hash = "sha256:c30c7c1048fbf056fd3920c5a2b88b0c29dd190a9b2bee971fd17e4abe4d0ebe" } +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/57/83/c77dfeed04022e8930b08eedca2b6e5efed256ab3321396fde90066efb65/pypika-0.51.1-py2.py3-none-any.whl", hash = "sha256:77985b4d7ce71b9905255bf12468cf598349e98837c037541cfc240e528aec46" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pytest" version = "8.3.5" -source = { registry = "https://mirror-pypi.runflare.com/simple" } +source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.9'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version < '3.9'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "packaging", marker = "python_full_version < '3.9'" }, - { name = "pluggy", version = "1.5.0", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "tomli", marker = "python_full_version < '3.9'" }, ] -sdist = { url = "https://mirror-pypi.runflare.com/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820" }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, ] [[package]] name = "pytest" version = "8.4.2" -source = { registry = "https://mirror-pypi.runflare.com/simple" } +source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.9.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version == '3.9.*' and sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version == '3.9.*'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "packaging", marker = "python_full_version == '3.9.*'" }, - { name = "pluggy", version = "1.6.0", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "pygments", marker = "python_full_version == '3.9.*'" }, { name = "tomli", marker = "python_full_version == '3.9.*'" }, ] -sdist = { url = "https://mirror-pypi.runflare.com/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79" }, + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] [[package]] name = "pytest" -version = "9.0.2" -source = { registry = "https://mirror-pypi.runflare.com/simple" } +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.10'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pluggy", version = "1.6.0", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pygments", marker = "python_full_version >= '3.10'" }, { name = "tomli", marker = "python_full_version == '3.10.*'" }, ] -sdist = { url = "https://mirror-pypi.runflare.com/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] @@ -602,13 +190,9 @@ source = { editable = "." } [package.dev-dependencies] dev = [ { name = "maturin" }, - { name = "mypy", version = "1.14.1", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version < '3.9'" }, - { name = "mypy", version = "1.19.1", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "pypika" }, - { name = "pytest", version = "8.3.5", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "sqlalchemy" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] [package.metadata] @@ -616,164 +200,84 @@ dev = [ [package.metadata.requires-dev] dev = [ { name = "maturin", specifier = ">=1.9.4" }, - { name = "mypy", specifier = ">=1.14.1" }, - { name = "pypika", specifier = ">=0.51.1" }, { name = "pytest", specifier = ">=8.3.5" }, - { name = "sqlalchemy", specifier = ">=2.0.47" }, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.47" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -dependencies = [ - { name = "greenlet", version = "3.1.1", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "(python_full_version < '3.9' and platform_machine == 'AMD64') or (python_full_version < '3.9' and platform_machine == 'WIN32') or (python_full_version < '3.9' and platform_machine == 'aarch64') or (python_full_version < '3.9' and platform_machine == 'amd64') or (python_full_version < '3.9' and platform_machine == 'ppc64le') or (python_full_version < '3.9' and platform_machine == 'win32') or (python_full_version < '3.9' and platform_machine == 'x86_64')" }, - { name = "greenlet", version = "3.2.5", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "(python_full_version == '3.9.*' and platform_machine == 'AMD64') or (python_full_version == '3.9.*' and platform_machine == 'WIN32') or (python_full_version == '3.9.*' and platform_machine == 'aarch64') or (python_full_version == '3.9.*' and platform_machine == 'amd64') or (python_full_version == '3.9.*' and platform_machine == 'ppc64le') or (python_full_version == '3.9.*' and platform_machine == 'win32') or (python_full_version == '3.9.*' and platform_machine == 'x86_64')" }, - { name = "greenlet", version = "3.3.2", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "(python_full_version >= '3.10' and platform_machine == 'AMD64') or (python_full_version >= '3.10' and platform_machine == 'WIN32') or (python_full_version >= '3.10' and platform_machine == 'aarch64') or (python_full_version >= '3.10' and platform_machine == 'amd64') or (python_full_version >= '3.10' and platform_machine == 'ppc64le') or (python_full_version >= '3.10' and platform_machine == 'win32') or (python_full_version >= '3.10' and platform_machine == 'x86_64')" }, - { name = "typing-extensions", version = "4.13.2", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version < '3.9'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://mirror-pypi.runflare.com/simple" }, marker = "python_full_version >= '3.9'" }, -] -sdist = { url = "https://mirror-pypi.runflare.com/packages/cd/4b/1e00561093fe2cd8eef09d406da003c8a118ff02d6548498c1ae677d68d9/sqlalchemy-2.0.47.tar.gz", hash = "sha256:e3e7feb57b267fe897e492b9721ae46d5c7de6f9e8dee58aacf105dc4e154f3d" } -wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/ec/75/17db77c57129c223c7d98518ad1e1faa24ee350c22a44b55390d8463c28c/sqlalchemy-2.0.47-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33a917ede39406ddb93c3e642b5bc480be7c5fd0f3d0d6ae1036d466fb963f1a" }, - { url = "https://mirror-pypi.runflare.com/packages/b0/d6/3658f7e5c376de774c009f2bb9c0ddf88a35b89c5bfb15ee7174a17b1a5f/sqlalchemy-2.0.47-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:561d027c829b01e040bdade6b6f5b429249d056ef95d7bdcb9211539ecc82803" }, - { url = "https://mirror-pypi.runflare.com/packages/4e/38/f4b94f85d1c26cb9ee0e57449754de816c326f9586b9a8c5247eb49146de/sqlalchemy-2.0.47-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fa5072a37e68c565363c009b7afa5b199b488c87940ec02719860093a08f34ca" }, - { url = "https://mirror-pypi.runflare.com/packages/94/f2/36714f1de01e135a2bf142b662e416e5338ab63c47878e31051338c66e2d/sqlalchemy-2.0.47-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e7ed17dd4312a298b6024bfd1baf51654bc49e3f03c798005babf0c7922d6a7" }, - { url = "https://mirror-pypi.runflare.com/packages/ab/94/fcd978e7625cd1c97d9f1d7363e18e37d24314e572acd7c091e3a4210106/sqlalchemy-2.0.47-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6992e353fcb0593eb42d95ad84b3e58fe40b5e37fd332b9ccba28f4b2f36d1fc" }, - { url = "https://mirror-pypi.runflare.com/packages/23/29/c633202b9900ab65f0162f59df737b57f30010f44d892b186810c9ed58b7/sqlalchemy-2.0.47-cp310-cp310-win32.whl", hash = "sha256:05a6d58ed99ebd01303c92d29a0c9cbf70f637b3ddd155f5172c5a7239940998" }, - { url = "https://mirror-pypi.runflare.com/packages/00/39/54acf13913932b8508058d47a169e6fcde9adaa4cbfa16cbf30da1f6a482/sqlalchemy-2.0.47-cp310-cp310-win_amd64.whl", hash = "sha256:4a7aa4a584cc97e268c11e700dea0b763874eaebb435e75e7d0ffee5d90f5030" }, - { url = "https://mirror-pypi.runflare.com/packages/94/13/886338d3e8ab5ddcfe84d54302c749b1793e16c4bba63d7004e3f7baa8ec/sqlalchemy-2.0.47-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a1dbf0913879c443617d6b64403cf2801c941651db8c60e96d204ed9388d6b0" }, - { url = "https://mirror-pypi.runflare.com/packages/b6/bb/a897f6a66c9986aa9f27f5cf8550637d8a5ea368fd7fb42f6dac3105b4dc/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:775effbb97ea3b00c4dd3aeaf3ba8acba6e3e2b4b41d17d67a27e696843dbc95" }, - { url = "https://mirror-pypi.runflare.com/packages/59/fb/69bfae022b681507565ab0d34f0c80aa1e9f954a5a7cbfb0ed054966ac8d/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56cc834a3ffac34270cc2a41875e0f40e97aa651f4f3ca1cfbbf421c044cb62b" }, - { url = "https://mirror-pypi.runflare.com/packages/04/f3/0eba329f7c182d53205a228c4fd24651b95489b431ea2bd830887b4c13c4/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49b5e0c7244262f39e767c018e4fdb5e5dbc23cd54c5ddac8eea8f0ba32ef890" }, - { url = "https://mirror-pypi.runflare.com/packages/5c/06/654edc084b3b46ac79e04200d7c46467ae80c759c4ee41c897f9272b036f/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cd822a3f1f6f77b5b841a30c1a07a07f7dee3385f17e638e1722de9ab683be" }, - { url = "https://mirror-pypi.runflare.com/packages/78/33/c18c8f63b61981219d3aa12321bb7ccee605034d195e868ed94f9727b27c/sqlalchemy-2.0.47-cp311-cp311-win32.whl", hash = "sha256:9847a19548cd283a65e1ce0afd54016598d55ff72682d6fd3e493af6fc044064" }, - { url = "https://mirror-pypi.runflare.com/packages/f5/c6/a59e3f9796fff844e16afbd821db9abfd6e12698db9441a231a96193a100/sqlalchemy-2.0.47-cp311-cp311-win_amd64.whl", hash = "sha256:722abf1c82aeca46a1a0803711244a48a298279eeaec9e02f7bfee9e064182e5" }, - { url = "https://mirror-pypi.runflare.com/packages/80/88/74eb470223ff88ea6572a132c0b8de8c1d8ed7b843d3b44a8a3c77f31d39/sqlalchemy-2.0.47-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fa91b19d6b9821c04cc8f7aa2476429cc8887b9687c762815aa629f5c0edec1" }, - { url = "https://mirror-pypi.runflare.com/packages/ef/ba/1447d3d558971b036cb93b557595cb5dcdfe728f1c7ac4dec16505ef5756/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c5bbbd14eff577c8c79cbfe39a0771eecd20f430f3678533476f0087138f356" }, - { url = "https://mirror-pypi.runflare.com/packages/8a/07/b47472d2ffd0776826f17ccf0b4d01b224c99fbd1904aeb103dffbb4b1cc/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a6c555da8d4280a3c4c78c5b7a3f990cee2b2884e5f934f87a226191682ff7" }, - { url = "https://mirror-pypi.runflare.com/packages/bb/c6/95fa32b79b57769da3e16f054cf658d90940317b5ca0ec20eac84aa19c4f/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ed48a1701d24dff3bb49a5bce94d6bc84cbe33d98af2aa2d3cdcce3dea1709ec" }, - { url = "https://mirror-pypi.runflare.com/packages/bb/c8/3d07e7c73928dc59a0bed40961ca4e313e797bce650b088e8d5fdd3ad939/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f3178c920ad98158f0b6309382194df04b14808fa6052ae07099fdde29d5602" }, - { url = "https://mirror-pypi.runflare.com/packages/6b/d2/ed32b1611c1e19fdb028eee1adc5a9aa138c2952d09ae11f1670170f80ae/sqlalchemy-2.0.47-cp312-cp312-win32.whl", hash = "sha256:b9c11ac9934dd59ece9619fe42780a08abe2faab7b0543bb00d5eabea4f421b9" }, - { url = "https://mirror-pypi.runflare.com/packages/fd/52/9de590356a4dd8e9ef5a881dbba64b2bbc4cbc71bf02bc68e775fb9b1899/sqlalchemy-2.0.47-cp312-cp312-win_amd64.whl", hash = "sha256:db43b72cf8274a99e089755c9c1e0b947159b71adbc2c83c3de2e38d5d607acb" }, - { url = "https://mirror-pypi.runflare.com/packages/4a/e5/0af64ce7d8f60ec5328c10084e2f449e7912a9b8bdbefdcfb44454a25f49/sqlalchemy-2.0.47-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:456a135b790da5d3c6b53d0ef71ac7b7d280b7f41eb0c438986352bf03ca7143" }, - { url = "https://mirror-pypi.runflare.com/packages/63/79/746b8d15f6940e2ac469ce22d7aa5b1124b1ab820bad9b046eb3000c88a6/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09a2f7698e44b3135433387da5d8846cf7cc7c10e5425af7c05fee609df978b6" }, - { url = "https://mirror-pypi.runflare.com/packages/91/b1/bd793ddb34345d1ed43b13ab2d88c95d7d4eb2e28f5b5a99128b9cc2bca2/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bbc72e6a177c78d724f9106aaddc0d26a2ada89c6332b5935414eccf04cbd5" }, - { url = "https://mirror-pypi.runflare.com/packages/97/84/7213def33f94e5ca6f5718d259bc9f29de0363134648425aa218d4356b23/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:75460456b043b78b6006e41bdf5b86747ee42eafaf7fffa3b24a6e9a456a2092" }, - { url = "https://mirror-pypi.runflare.com/packages/ef/06/456810204f4dc29b5f025b1b0a03b4bd6b600ebf3c1040aebd90a257fa33/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d9adaa616c3bc7d80f9ded57cd84b51d6617cad6a5456621d858c9f23aaee01" }, - { url = "https://mirror-pypi.runflare.com/packages/fb/20/df3920a4b2217dbd7390a5bd277c1902e0393f42baaf49f49b3c935e7328/sqlalchemy-2.0.47-cp313-cp313-win32.whl", hash = "sha256:76e09f974382a496a5ed985db9343628b1cb1ac911f27342e4cc46a8bac10476" }, - { url = "https://mirror-pypi.runflare.com/packages/46/06/7873ddf69918efbfabd7211829f4bd8019739d0a719253112d305d3ba51d/sqlalchemy-2.0.47-cp313-cp313-win_amd64.whl", hash = "sha256:0664089b0bf6724a0bfb49a0cf4d4da24868a0a5c8e937cd7db356d5dcdf2c66" }, - { url = "https://mirror-pypi.runflare.com/packages/54/fa/61ad9731370c90ac7ea5bf8f5eaa12c48bb4beec41c0fa0360becf4ac10d/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed0c967c701ae13da98eb220f9ddab3044ab63504c1ba24ad6a59b26826ad003" }, - { url = "https://mirror-pypi.runflare.com/packages/33/d5/221fac96f0529391fe374875633804c866f2b21a9c6d3a6ca57d9c12cfd7/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3537943a61fd25b241e976426a0c6814434b93cf9b09d39e8e78f3c9eb9a487" }, - { url = "https://mirror-pypi.runflare.com/packages/ec/55/8247d53998c3673e4a8d1958eba75c6f5cc3b39082029d400bb1f2a911ae/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:57f7e336a64a0dba686c66392d46b9bc7af2c57d55ce6dc1697b4ef32b043ceb" }, - { url = "https://mirror-pypi.runflare.com/packages/6b/b5/c1f0eea1bac6790845f71420a7fe2f2a0566203aa57543117d4af3b77d1c/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dff735a621858680217cb5142b779bad40ef7322ddbb7c12062190db6879772e" }, - { url = "https://mirror-pypi.runflare.com/packages/c5/ed/2f43f92474ea0c43c204657dc47d9d002cd738b96ca2af8e6d29a9b5e42d/sqlalchemy-2.0.47-cp313-cp313t-win32.whl", hash = "sha256:3893dc096bb3cca9608ea3487372ffcea3ae9b162f40e4d3c51dd49db1d1b2dc" }, - { url = "https://mirror-pypi.runflare.com/packages/cc/a9/8b73f9f1695b6e92f7aaf1711135a1e3bbeb78bca9eded35cb79180d3c6d/sqlalchemy-2.0.47-cp313-cp313t-win_amd64.whl", hash = "sha256:b5103427466f4b3e61f04833ae01f9a914b1280a2a8bcde3a9d7ab11f3755b42" }, - { url = "https://mirror-pypi.runflare.com/packages/c1/30/98243209aae58ed80e090ea988d5182244ca7ab3ff59e6d850c3dfc7651e/sqlalchemy-2.0.47-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b03010a5a5dfe71676bc83f2473ebe082478e32d77e6f082c8fe15a31c3b42a6" }, - { url = "https://mirror-pypi.runflare.com/packages/ab/62/12ca6ea92055fe486d6558a2a4efe93e194ff597463849c01f88e5adb99d/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e3371aa9024520883a415a09cc20c33cfd3eeccf9e0f4f4c367f940b9cbd44" }, - { url = "https://mirror-pypi.runflare.com/packages/97/88/7dfbdeaa8d42b1584e65d6cc713e9d33b6fa563e0d546d5cb87e545bb0e5/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9449f747e50d518c6e1b40cc379e48bfc796453c47b15e627ea901c201e48a6" }, - { url = "https://mirror-pypi.runflare.com/packages/d0/b7/75e1c1970616a9dd64a8a6fd788248da2ddaf81c95f4875f2a1e8aee4128/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:21410f60d5cac1d6bfe360e05bd91b179be4fa0aa6eea6be46054971d277608f" }, - { url = "https://mirror-pypi.runflare.com/packages/31/ac/eec1a13b891df9a8bc203334caf6e6aac60b02f61b018ef3b4124b8c4120/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:819841dd5bb4324c284c09e2874cf96fe6338bfb57a64548d9b81a4e39c9871f" }, - { url = "https://mirror-pypi.runflare.com/packages/c9/b0/661b0245b06421058610da39f8ceb34abcc90b49f90f256380968d761dbe/sqlalchemy-2.0.47-cp314-cp314-win32.whl", hash = "sha256:e255ee44821a7ef45649c43064cf94e74f81f61b4df70547304b97a351e9b7db" }, - { url = "https://mirror-pypi.runflare.com/packages/aa/ef/1035a90d899e61810791c052004958be622a2cf3eb3df71c3fe20778c5d0/sqlalchemy-2.0.47-cp314-cp314-win_amd64.whl", hash = "sha256:209467ff73ea1518fe1a5aaed9ba75bb9e33b2666e2553af9ccd13387bf192cb" }, - { url = "https://mirror-pypi.runflare.com/packages/76/bb/17a1dd09cbba91258218ceb582225f14b5364d2683f9f5a274f72f2d764f/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e78fd9186946afaa287f8a1fe147ead06e5d566b08c0afcb601226e9c7322a64" }, - { url = "https://mirror-pypi.runflare.com/packages/66/8f/1a03d24c40cc321ef2f2231f05420d140bb06a84f7047eaa7eaa21d230ba/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5740e2f31b5987ed9619d6912ae5b750c03637f2078850da3002934c9532f172" }, - { url = "https://mirror-pypi.runflare.com/packages/fd/53/d56a213055d6b038a5384f0db5ece7343334aca230ff3f0fa1561106f22c/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb9ac00d03de93acb210e8ec7243fefe3e012515bf5fd2f0898c8dff38bc77a4" }, - { url = "https://mirror-pypi.runflare.com/packages/ff/19/c235d81b9cfdd6130bf63143b7bade0dc4afa46c4b634d5d6b2a96bea233/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c72a0b9eb2672d70d112cb149fbaf172d466bc691014c496aaac594f1988e706" }, - { url = "https://mirror-pypi.runflare.com/packages/0e/db/cafdeca5ecdaa3bb0811ba5449501da677ce0d83be8d05c5822da72d2e86/sqlalchemy-2.0.47-cp314-cp314t-win32.whl", hash = "sha256:c200db1128d72a71dc3c31c24b42eb9fd85b2b3e5a3c9ba1e751c11ac31250ff" }, - { url = "https://mirror-pypi.runflare.com/packages/fc/5e/ff41a010e9e0f76418b02ad352060a4341bb15f0af66cedc924ab376c7c6/sqlalchemy-2.0.47-cp314-cp314t-win_amd64.whl", hash = "sha256:669837759b84e575407355dcff912835892058aea9b80bd1cb76d6a151cf37f7" }, - { url = "https://mirror-pypi.runflare.com/packages/ed/44/52c490a81d747e10c6c3427d2169b551ae9a8c5059c0d792418fead6921a/sqlalchemy-2.0.47-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fe3f8519a52ca5032015780de3fc4e6ab42c6e0bcf9d807143a3d17b3350d546" }, - { url = "https://mirror-pypi.runflare.com/packages/05/1a/431489c8991f5a06d9b55f0999df221488a2725c2cccb661d21ac7dc315b/sqlalchemy-2.0.47-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6acfc1f95ed0369e0c4100d98870c9c4bfd56818ddc825357a0a979d5973195" }, - { url = "https://mirror-pypi.runflare.com/packages/cb/b6/e13f0136b21e98d3385554712c9a0a85fb0ebab2502f784b587474bd3fd4/sqlalchemy-2.0.47-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f530b939eca775f6f77fa359a3e7039209a96958c1aa28c1b796f600e0fee7cd" }, - { url = "https://mirror-pypi.runflare.com/packages/e6/f1/77dcffeeb622c4cd9083a4ddc314d5cd9b7caca4ef88ee487e3b79f7bcd0/sqlalchemy-2.0.47-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0a6fdf665268dfe0ba52fb2d8d62deee96b297d460e2797bdd52d2d1941dd8cd" }, - { url = "https://mirror-pypi.runflare.com/packages/6f/f3/0c7222af667c8c8307d4ed365e5eb3ead2f1375afe03c8992c500e56dd21/sqlalchemy-2.0.47-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:25b3c189dab94dedb6db9d4e06476ce955182e7f45412b096ae9033519e33ce8" }, - { url = "https://mirror-pypi.runflare.com/packages/ae/d4/b813da53629aadb97101d091bcae54b29735d82e74f85d1a8988fd788ba3/sqlalchemy-2.0.47-cp38-cp38-win32.whl", hash = "sha256:a8f991cac31b673aff1648cafb8b10022719e5a632bbadaa9c5d41511bd507a5" }, - { url = "https://mirror-pypi.runflare.com/packages/ba/1d/e992dbafa8a2189021683416d84cc325e81a2b29abc5c11586002f3c58eb/sqlalchemy-2.0.47-cp38-cp38-win_amd64.whl", hash = "sha256:52be08b31f70bed2ed05c5c4b8237cf361a8581f32a5e89f9dfc295f795db10f" }, - { url = "https://mirror-pypi.runflare.com/packages/e3/3f/2a9d99e811ada3d82bb13beaed2a0b1294de931ebb5590de18b8bce59a5b/sqlalchemy-2.0.47-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d7477190e90852c00bf654b70ae21e5b85b5ac4d09094cf82e84eb3abdb6c5a7" }, - { url = "https://mirror-pypi.runflare.com/packages/39/8f/dd79902a3e0e0a8b8d75727cdad2f2ac0d299ced73cd815f3b716d853079/sqlalchemy-2.0.47-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb0b3e4946bf05d68673a1857db1a16bd58207c83ebc4ed5732a6e60029bac2d" }, - { url = "https://mirror-pypi.runflare.com/packages/52/e1/f3524df09fecfa4b122b3d1318f1d242e61ed9b04715498dabe76be58980/sqlalchemy-2.0.47-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bc111a5b98881b7e1ab108921f2fcc09fa06abbd98f4f0ed6cb2c23e70cdd23" }, - { url = "https://mirror-pypi.runflare.com/packages/54/cf/e3a13f04a775e87a967386d2b00caafd36fe7b6ac8f477cfd29c2270ad0f/sqlalchemy-2.0.47-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:97bf49caf4e405c18f3b9f695751c5bf14a9d8899c6e54eaeb49dda0d4fa009d" }, - { url = "https://mirror-pypi.runflare.com/packages/b4/4d/83d84cc4b4ad459d88e0d6b20614d84a26704ca42e41cba07d97fd8ffca8/sqlalchemy-2.0.47-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d2fa029051d3db77ad79a55c3ddf005bd7c038a111af0db9b56158857702aef6" }, - { url = "https://mirror-pypi.runflare.com/packages/b3/b7/19a24d27718fa06225828871be5e4308904fd9965fe38015288ce1016f0a/sqlalchemy-2.0.47-cp39-cp39-win32.whl", hash = "sha256:6e547682d508d141de942484b9976cbee91b7a50739d4ee25b3d0a62dd71a954" }, - { url = "https://mirror-pypi.runflare.com/packages/7d/6e/b6317aa4af77f5506a70dda83e39a2ac4043e4cf735737778024f3c56805/sqlalchemy-2.0.47-cp39-cp39-win_amd64.whl", hash = "sha256:bb833131169444c87160aa95fcdd22ae86d0fa4ef174d36b3dfb9be363b4e574" }, - { url = "https://mirror-pypi.runflare.com/packages/15/9f/7c378406b592fcf1fc157248607b495a40e3202ba4a6f1372a2ba6447717/sqlalchemy-2.0.47-py3-none-any.whl", hash = "sha256:e2647043599297a1ef10e720cf310846b7f31b6c841fee093d2b09d81215eb93" }, ] [[package]] name = "tomli" -version = "2.4.0" -source = { registry = "https://mirror-pypi.runflare.com/simple" } -sdist = { url = "https://mirror-pypi.runflare.com/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c" } +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867" }, - { url = "https://mirror-pypi.runflare.com/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9" }, - { url = "https://mirror-pypi.runflare.com/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95" }, - { url = "https://mirror-pypi.runflare.com/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76" }, - { url = "https://mirror-pypi.runflare.com/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d" }, - { url = "https://mirror-pypi.runflare.com/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576" }, - { url = "https://mirror-pypi.runflare.com/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a" }, - { url = "https://mirror-pypi.runflare.com/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa" }, - { url = "https://mirror-pypi.runflare.com/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614" }, - { url = "https://mirror-pypi.runflare.com/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1" }, - { url = "https://mirror-pypi.runflare.com/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8" }, - { url = "https://mirror-pypi.runflare.com/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a" }, - { url = "https://mirror-pypi.runflare.com/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1" }, - { url = "https://mirror-pypi.runflare.com/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b" }, - { url = "https://mirror-pypi.runflare.com/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51" }, - { url = "https://mirror-pypi.runflare.com/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729" }, - { url = "https://mirror-pypi.runflare.com/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da" }, - { url = "https://mirror-pypi.runflare.com/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3" }, - { url = "https://mirror-pypi.runflare.com/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0" }, - { url = "https://mirror-pypi.runflare.com/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e" }, - { url = "https://mirror-pypi.runflare.com/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4" }, - { url = "https://mirror-pypi.runflare.com/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e" }, - { url = "https://mirror-pypi.runflare.com/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c" }, - { url = "https://mirror-pypi.runflare.com/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f" }, - { url = "https://mirror-pypi.runflare.com/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86" }, - { url = "https://mirror-pypi.runflare.com/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87" }, - { url = "https://mirror-pypi.runflare.com/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132" }, - { url = "https://mirror-pypi.runflare.com/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6" }, - { url = "https://mirror-pypi.runflare.com/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc" }, - { url = "https://mirror-pypi.runflare.com/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66" }, - { url = "https://mirror-pypi.runflare.com/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d" }, - { url = "https://mirror-pypi.runflare.com/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702" }, - { url = "https://mirror-pypi.runflare.com/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8" }, - { url = "https://mirror-pypi.runflare.com/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776" }, - { url = "https://mirror-pypi.runflare.com/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475" }, - { url = "https://mirror-pypi.runflare.com/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2" }, - { url = "https://mirror-pypi.runflare.com/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9" }, - { url = "https://mirror-pypi.runflare.com/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0" }, - { url = "https://mirror-pypi.runflare.com/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df" }, - { url = "https://mirror-pypi.runflare.com/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d" }, - { url = "https://mirror-pypi.runflare.com/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f" }, - { url = "https://mirror-pypi.runflare.com/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b" }, - { url = "https://mirror-pypi.runflare.com/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087" }, - { url = "https://mirror-pypi.runflare.com/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd" }, - { url = "https://mirror-pypi.runflare.com/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4" }, - { url = "https://mirror-pypi.runflare.com/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a" }, + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] [[package]] name = "typing-extensions" version = "4.13.2" -source = { registry = "https://mirror-pypi.runflare.com/simple" } +source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] -sdist = { url = "https://mirror-pypi.runflare.com/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c" }, + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, ] [[package]] name = "typing-extensions" version = "4.15.0" -source = { registry = "https://mirror-pypi.runflare.com/simple" } +source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.10'", "python_full_version == '3.9.*'", ] -sdist = { url = "https://mirror-pypi.runflare.com/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://mirror-pypi.runflare.com/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] From c0505244ef06c4be909dcb82bf65a88ced579be0 Mon Sep 17 00:00:00 2001 From: awolverp Date: Thu, 25 Jun 2026 23:37:56 +0330 Subject: [PATCH 3/5] Write serializers and refactor datatypes --- src/ast/data_type/mod.rs | 1 + src/ast/data_type/name.rs | 306 +++++++++++++++---------------- src/ast/data_type/serializers.rs | 107 ++--------- src/lib.rs | 2 +- 4 files changed, 166 insertions(+), 250 deletions(-) diff --git a/src/ast/data_type/mod.rs b/src/ast/data_type/mod.rs index fc700e4..80266c6 100644 --- a/src/ast/data_type/mod.rs +++ b/src/ast/data_type/mod.rs @@ -9,6 +9,7 @@ pub use serializers::DataTypeSerializer; use crate::internal::alias; implement_pyclass! { + /// SQL data type. #[derive(Debug, Clone)] [skip_from_py_object] PyDataType as "DataType" { pub name: DataTypeName, diff --git a/src/ast/data_type/name.rs b/src/ast/data_type/name.rs index 1bb20d0..ab4ec00 100644 --- a/src/ast/data_type/name.rs +++ b/src/ast/data_type/name.rs @@ -1,27 +1,53 @@ +/// Specifies data type groups +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub enum DataTypeGroup { + STRING, + UUID, + BYTES, + BIT, + INTEGER, + FLOAT, + BOOLEAN, + DATE, + TIME, + DATETIME, + JSON, + JSONB, + INET, + MACADDRESS, + CUSTOM, +} + /// SQL data types #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -pub struct DataTypeName(arcstr::ArcStr); +pub struct DataTypeName(arcstr::ArcStr, DataTypeGroup); // Create all no-argument data types once. This reduces memory footprint, thanks to `arcstr::ArcStr`. macro_rules! simple_data_types { ( $( $(#[$docs:meta])* - ($upcase:ident, $realname:literal, $($possiblenames:literal,)*); + ($upcase:ident => ($realname:literal, $group:ident), $possible_name:literal, $($possible_names:literal),*); )+ ) => { $( $(#[$docs])* - pub(super) const $upcase: DataTypeName = DataTypeName(arcstr::literal!($realname)); + pub(super) const $upcase: DataTypeName = DataTypeName(arcstr::literal!($realname), DataTypeGroup::$group); )+ impl From<&str> for DataTypeName { fn from(value: &str) -> Self { - match value { - $( - $realname $(| $possiblenames)* => $upcase, - )+ - _ => DataTypeName(arcstr::ArcStr::from(value)), + if value.find('(').is_some() { + // For data types that has gotten parameters, we cannot use constant variables + DataTypeName(arcstr::ArcStr::from(value), DataTypeGroup::CUSTOM) + } else { + let lowercase = value.to_ascii_lowercase(); + match lowercase.as_str() { + $( + $possible_name $(| $possible_names)* => $upcase, + )+ + _ => DataTypeName(arcstr::ArcStr::from(value), DataTypeGroup::CUSTOM), + } } } } @@ -29,157 +55,119 @@ macro_rules! simple_data_types { } simple_data_types! { - /// Uuid type. - (UUID_DATA_TYPE, "UUID", "uuid",); - /// [MySQL] blob with up to 2**8 bytes. - /// - /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html - (TINY_BLOB_DATA_TYPE, "TINYBLOB", "tinyblob",); - /// [MySQL] blob with up to 2**24 bytes. - /// - /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html - (MEDIUM_BLOB_DATA_TYPE, "MEDIUMBLOB", "mediumblob",); - /// [MySQL] blob with up to 2**32 bytes. - /// - /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html - (LONG_BLOB_DATA_TYPE, "LONGBLOB", "longblob",); - /// Unsigned tiny integer, e.g. UTINYINT - (U_TINY_INT_DATA_TYPE, "UTINYINT", "utinyint",); - /// Unsigned tiny integer, e.g. UTINYINT - (U_SMALL_INT_DATA_TYPE, "USMALLINT", "usmallint",); - /// integer, e.g. INTEGER - (INTEGER_DATA_TYPE, "INTEGER", "integer",); - /// Integer type in [BigQuery], [ClickHouse]. - /// - /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#integer_types - /// [ClickHouse]: https://clickhouse.com/docs/en/sql-reference/data-types/int-uint - (INT64_DATA_TYPE, "INT64", "int64",); - /// 128-bit integer type, e.g. HUGEINT. - (HUGE_INT_DATA_TYPE, "HUGEINT", "hugeint",); - /// Unsigned 128-bit integer type, e.g. UHUGEINT. - (U_HUGE_INT_DATA_TYPE, "UHUGEINT", "uhugeint",); - /// Unsigned big integer, e.g. UBIGINT. - (U_BIG_INT_DATA_TYPE, "UBIGINT", "ubigint",); - /// Signed integer as used in [MySQL CAST] target types, without `INTEGER` suffix. - /// - /// [MySQL CAST]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html - (SIGNED_DATA_TYPE, "SIGNED", "signed",); - /// Signed integer as used in [MySQL CAST] target types, with `INTEGER` suffix. - /// - /// [MySQL CAST]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html - (SIGNED_INTEGER_DATA_TYPE, "SIGNED INTEGER", "signed integer",); - /// Unsigned integer as used in [MySQL CAST] target types, without `INTEGER` suffix. - /// - /// [MySQL CAST]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html - (UNSIGNED_DATA_TYPE, "UNSIGNED", "unsigned",); - /// Unsigned integer as used in [MySQL CAST] target types, with `INTEGER` suffix. - /// - /// [MySQL CAST]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html - (UNSIGNED_INTEGER_DATA_TYPE, "UNSIGNED INTEGER", "unsigned integer",); - /// Float4 is an alias for Real in [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html - (FLOAT4_DATA_TYPE, "FLOAT4", "float4",); - /// Floating point in [BigQuery] and [ClickHouse]. - /// - /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#floating_point_types - /// [ClickHouse]: https://clickhouse.com/docs/en/sql-reference/data-types/float - (FLOAT64_DATA_TYPE, "Float64", "float64",); - /// Floating point, e.g. REAL. - (REAL_DATA_TYPE, "REAL", "real",); - /// [MySQL] unsigned real, e.g. REAL UNSIGNED. - /// - /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html - (REAL_UNSIGNED_DATA_TYPE, "REAL UNSIGNED", "real unsigned",); - /// Float8 is an alias for Double in [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html - (FLOAT8_DATA_TYPE, "FLOAT8", "float8",); - /// Double Precision, see [SQL Standard], [PostgreSQL]. - /// - /// [SQL Standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#approximate-numeric-type - /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-numeric.html - (DOUBLE_PRECISION_DATA_TYPE, "DOUBLE PRECISION", "double precision",); - /// [MySQL] unsigned double precision, e.g. DOUBLE PRECISION UNSIGNED. - /// - /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html - (DOUBLE_PRECISION_UNSIGNED_DATA_TYPE, "DOUBLE PRECISION UNSIGNED", "double precision unsigned",); - /// Bool is an alias for Boolean, see [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html - (BOOL_DATA_TYPE, "BOOL", "bool",); - /// Boolean type. - (BOOLEAN_DATA_TYPE, "BOOLEAN", "boolean",); - /// Date type. - (DATE_DATA_TYPE, "DATE", "date",); - /// Time type. - (TIME_DATA_TYPE, "TIME", "time",); - /// Time with time zone, see [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-datetime.html - (TIME_WITH_TIME_ZONE_DATA_TYPE, "TIME WITH TIME ZONE", "time with time zone",); - /// Datetime type, see [MySQL]. - /// - /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/datetime.html - (DATETIME_DATA_TYPE, "DATETIME", "datetime",); - /// Timestamp type, see [SQL Standard]. - /// - /// [SQL Standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#datetime-type - (TIMESTAMP_DATA_TYPE, "TIMESTAMP", "timestamp",); - /// Timestamp without time zone, see [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-datetime.html - (TIMESTAMP_NTZ_DATA_TYPE, "TIMESTAMP WITHOUT TIME ZONE", "timestamp without time zone", "TIMESTAMP_NTZ", "timestamp_ntz",); - /// Timestamp with time zone, see [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-datetime.html - (TIMESTAMP_WITH_TZ_DATA_TYPE, "TIMESTAMP WITH TIME ZONE", "timestamp with time zone", "TIMESTAMPTZ", "timestamptz",); - /// Regclass used in [PostgreSQL] serial. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype.html - (REGCLASS_DATA_TYPE, "REGCLASS", "regclass",); - /// Text type. - (TEXT_DATA_TYPE, "TEXT", "text",); - /// [MySQL] text with up to 2**8 bytes. - /// - /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html - (TINY_TEXT_DATA_TYPE, "TINYTEXT", "tinytext",); - /// [MySQL] text with up to 2**24 bytes. - /// - /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html - (MEDIUM_TEXT_DATA_TYPE, "MEDIUMTEXT", "mediumtext",); - /// [MySQL] text with up to 2**32 bytes. - /// - /// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/blob.html - (LONG_TEXT_DATA_TYPE, "LONGTEXT", "longtext",); - /// Bytea type, see [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/current/datatype-bit.html - (BYTEA_DATA_TYPE, "BYTEA", "bytea",); - /// JSON type. - (JSON_DATA_TYPE, "JSON", "json",); - /// Binary JSON type. - (JSONB_DATA_TYPE, "JSONB", "jsonb",); - /// Interval type. - (INTERVAL_DATA_TYPE, "INTERVAL", "interval",); - /// No type specified — only used with SQLiteDialect, e.g. `CREATE TABLE t1 (a)`. - (UNSPECIFIED_DATA_TYPE, "UNSPECIFIED", "unspecified",); - /// Trigger data type, returned by functions associated with triggers, see [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/current/plpgsql-trigger.html - (TRIGGER_DATA_TYPE, "TRIGGER", "trigger",); - /// Any data type, used in BigQuery UDF definitions for templated parameters, see [BigQuery]. - /// - /// [BigQuery]: https://cloud.google.com/bigquery/docs/user-defined-functions#templated-sql-udf-parameters - (ANY_TYPE_DATA_TYPE, "ANY TYPE", "any type", "anytype", "ANYTYPE",); - /// PostgreSQL text search vector, see [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-textsearch.html - (TS_VECTOR_DATA_TYPE, "TSVECTOR", "tsvector",); - /// PostgreSQL text search query, see [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-textsearch.html - (TS_QUERY_DATA_TYPE, "TSQUERY", "tsquery",); + // --- STRING group --- + (CHARACTER_DATA_TYPE => ("CHARACTER", STRING), "character",); + (CHAR_DATA_TYPE => ("CHAR", STRING), "char",); + (CHARACTER_VARYING_DATA_TYPE => ("CHARACTER VARYING", STRING), "characater varying",); + (CHAR_VARYING_DATA_TYPE => ("CHAR VARYING", STRING), "char varying",); + (VARCHAR_DATA_TYPE => ("VARCHAR", STRING), "varchar",); + (NVARCHAR_DATA_TYPE => ("NVARCHAR", STRING), "nvarchar",); + (CHARACTER_LARGE_OBJECT_DATA_TYPE => ("CHARACTER LARGE OBJECT", STRING), "character large object",); + (CHAR_LARGE_OBJECT_DATA_TYPE => ("CHAR LARGE OBJECT", STRING), "char large object",); + (CLOB_DATA_TYPE => ("CLOB", STRING), "clob",); + (REGCLASS_DATA_TYPE => ("REGCLASS", STRING), "regclass",); + (TEXT_DATA_TYPE => ("TEXT", STRING), "text",); + (TINYTEXT_DATA_TYPE => ("TINYTEXT", STRING), "tinytext",); + (MEDIUMTEXT_DATA_TYPE => ("MEDIUMTEXT", STRING), "mediumtext",); + (LONGTEXT_DATA_TYPE => ("LONGTEXT", STRING), "longtext",); + (STRING_DATA_TYPE => ("STRING", STRING), "string",); + + // --- UUID group --- + (UUID_DATA_TYPE => ("UUID", UUID), "uuid",); + + // --- BINARY group --- + (BINARY_DATA_TYPE => ("BINARY", BYTES), "binary",); + (VARBINARY_DATA_TYPE => ("VARBINARY", BYTES), "varbinary",); + (BLOB_DATA_TYPE => ("BLOB", BYTES), "blob",); + (TINYBLOB_DATA_TYPE => ("TINYBLOB", BYTES), "tinyblob",); + (MEDIUMBLOB_DATA_TYPE => ("MEDIUMBLOB", BYTES), "mediumblob",); + (LONGBLOB_DATA_TYPE => ("LONGBLOB", BYTES), "longblob",); + (BYTES_DATA_TYPE => ("BYTES", BYTES), "bytes",); + (BYTEA_DATA_TYPE => ("BYTEA", BYTES), "bytea",); + + // --- BIT group --- + (BIT_DATA_TYPE => ("BIT", BIT), "bit",); + (BIT_VARYING_DATA_TYPE => ("BIT VARYING", BIT), "bit varying",); + (VARBIT_DATA_TYPE => ("VARBIT", BIT), "varbit",); + + // --- FLOAT group --- + (NUMERIC_DATA_TYPE => ("NUMERIC", FLOAT), "numeric",); + (DECIMAL_DATA_TYPE => ("DECIMAL", FLOAT), "decimal",); + (BIGNUMERIC_DATA_TYPE => ("BIGNUMERIC", FLOAT), "bignumeric",); + (BIGDECIMAL_DATA_TYPE => ("BIGDECIMAL", FLOAT), "bigdecimal",); + (DEC_DATA_TYPE => ("DEC", FLOAT), "dec",); + (FLOAT_DATA_TYPE => ("FLOAT", FLOAT), "float",); + (FLOAT4_DATA_TYPE => ("FLOAT4", FLOAT), "float4",); + (FLOAT8_DATA_TYPE => ("FLOAT8", FLOAT), "float8",); + (FLOAT64_DATA_TYPE => ("FLOAT64", FLOAT), "float64",); + (REAL_DATA_TYPE => ("REAL", FLOAT), "real",); + (REAL_UNSIGNED_DATA_TYPE => ("REAL UNSIGNED", FLOAT), "real unsigned",); + (DOUBLE_DATA_TYPE => ("DOUBLE", FLOAT), "double",); + (DOUBLE_UNSIGNED_DATA_TYPE => ("DOUBLE UNSIGNED", FLOAT), "double unsigned",); + (DOUBLE_PRECISION_DATA_TYPE => ("DOUBLE PRECISION", FLOAT), "double precision",); + (DOUBLE_PRECISION_UNSIGNED_DATA_TYPE => ("DOUBLE PRECISION UNSIGNED", FLOAT), "double precision unsigned",); + + // --- INTEGER --- + (TINYINT_DATA_TYPE => ("TINYINT", INTEGER), "tinyint",); + (TINYINT_UNSIGNED_DATA_TYPE => ("TINYINT UNSIGNED", INTEGER), "tinyint unsigned", "utinyint"); + (INT2_DATA_TYPE => ("INT2", INTEGER), "int2",); + (INT2_UNSIGNED_DATA_TYPE => ("INT2 UNSIGNED", INTEGER), "int2 unsigned",); + (SMALLINT_DATA_TYPE => ("SMALLINT", INTEGER), "smallint",); + (SMALLINT_UNSIGNED_DATA_TYPE => ("SMALLINT UNSIGNED", INTEGER), "smallint unsigned", "usmallint"); + (MEDIUMINT_DATA_TYPE => ("MEDIUMINT", INTEGER), "mediumint",); + (MEDIUMINT_UNSIGNED_DATA_TYPE => ("MEDIUMINT UNSIGNED", INTEGER), "mediumint unsigned",); + (INT_DATA_TYPE => ("INT", INTEGER), "int",); + (INT4_DATA_TYPE => ("INT4", INTEGER), "int4",); + (INT8_DATA_TYPE => ("INT8", INTEGER), "int8",); + (INT64_DATA_TYPE => ("INT64", INTEGER), "int64",); + (INTEGER_DATA_TYPE => ("INTEGER", INTEGER), "integer",); + (UNSIGNED_INTEGER_DATA_TYPE => ("UNSIGNED INTEGER", INTEGER), "unsigned integer", "integer unsigned", "int unsigned"); + (INT4_UNSIGNED_DATA_TYPE => ("INT4 UNSIGNED", INTEGER), "int4 unsigned",); + (HUGEINT_DATA_TYPE => ("HUGEINT", INTEGER), "hugeint",); + (UHUGEINT_DATA_TYPE => ("UHUGEINT", INTEGER), "uhugeint",); + (BIGINT_DATA_TYPE => ("BIGINT", INTEGER), "bigint",); + (BIGINT_UNSIGNED_DATA_TYPE => ("BIGINT UNSIGNED", INTEGER), "bigint unsigned", "unsigned bitint", "ubigint"); + (INT8_UNSIGNED_DATA_TYPE => ("INT8 UNSIGNED", INTEGER), "int8 unsigned",); + (SIGNED_DATA_TYPE => ("SIGNED", INTEGER), "signed",); + (SIGNED_INTEGER_DATA_TYPE => ("SIGNED INTEGER", INTEGER), "signed integer",); + (UNSIGNED_DATA_TYPE => ("UNSIGNED", INTEGER), "unsigned",); + + // --- BOOLEAN group --- + (BOOLEAN_DATA_TYPE => ("BOOLEAN", INTEGER), "boolean", "bool"); + + // --- DATE group --- + (DATE_DATA_TYPE => ("DATE", DATE), "date",); + + // --- TIME group --- + (TIME_DATA_TYPE => ("TIME", TIME), "time", "time without timezone"); + (TIME_WITH_TIMEZONE_DATA_TYPE => ("TIME WITH TIMEZONE", TIME), "time with timezone",); + + // --- DATETIME group --- + (DATETIME_DATA_TYPE => ("DATETIME", DATETIME), "datetime",); + (TIMESTAMP_DATA_TYPE => ("TIMESTAMP", DATETIME), "timestamp", "timestamp without timezone"); + (TIMESTAMP_WITH_TIMEZONE_DATA_TYPE => ("TIMESTAMP WITH TIMEZONE", DATETIME), "timestamp with timezone", "timestamptz"); + (TIMESTAMPNTZ_DATA_TYPE => ("TIMESTAMPNTZ", DATETIME), "timestampntz",); + + // --- JSON group --- + (JSON_DATA_TYPE => ("JSON", JSON), "json",); + (JSONB_DATA_TYPE => ("JSONB", JSONB), "jsonb",); + + // --- INET group --- + (INET_DATA_TYPE => ("INET", INET), "inet",); + + // --- MACADDR group --- + (MACADDRESS_DATA_TYPE => ("MACADDRESS", MACADDRESS), "macaddress", "macaddr"); + + // --- CUSTOM group --- + (ANY_DATA_TYPE => ("ANY", CUSTOM), "any",); + (TRIGGER_DATA_TYPE => ("TRIGGER", CUSTOM), "trigger",); + (UNSPECIFIED_DATA_TYPE => ("UNSPECIFIED", CUSTOM), "unspecified",); +} + +impl DataTypeName { + pub fn group(&self) -> DataTypeGroup { + self.1 + } } impl AsRef for DataTypeName { diff --git a/src/ast/data_type/serializers.rs b/src/ast/data_type/serializers.rs index 28d0b1d..4c3fb1b 100644 --- a/src/ast/data_type/serializers.rs +++ b/src/ast/data_type/serializers.rs @@ -176,97 +176,24 @@ pub enum DataTypeSerializer { impl DataTypeSerializer { pub fn infer_from_name(value: &super::name::DataTypeName) -> Self { - use super::name::*; - - if value == &BOOLEAN_DATA_TYPE || value == &BOOL_DATA_TYPE { - return Self::Boolean(PyBooleanSerializer); - } - - if [ - &U_TINY_INT_DATA_TYPE, - &U_SMALL_INT_DATA_TYPE, - &INT64_DATA_TYPE, - &HUGE_INT_DATA_TYPE, - &U_HUGE_INT_DATA_TYPE, - &U_BIG_INT_DATA_TYPE, - &SIGNED_DATA_TYPE, - &SIGNED_INTEGER_DATA_TYPE, - &UNSIGNED_DATA_TYPE, - &UNSIGNED_INTEGER_DATA_TYPE, - &INTEGER_DATA_TYPE, - ] - .contains(&value) - { - return Self::Integer(PyIntegerSerializer); - } - - if [ - &FLOAT4_DATA_TYPE, - &FLOAT64_DATA_TYPE, - &REAL_DATA_TYPE, - &REAL_UNSIGNED_DATA_TYPE, - &FLOAT8_DATA_TYPE, - &DOUBLE_PRECISION_DATA_TYPE, - &DOUBLE_PRECISION_UNSIGNED_DATA_TYPE, - ] - .contains(&value) - { - return Self::Float(PyFloatSerializer); + use super::name::DataTypeGroup; + + match value.group() { + DataTypeGroup::STRING => Self::String(PyStringSerializer), + DataTypeGroup::UUID => Self::Uuid(PyUuidSerializer), + DataTypeGroup::BYTES => Self::Bytes(PyBytesSerializer), + DataTypeGroup::BIT => Self::Bit(PyBitSerializer), + DataTypeGroup::INTEGER => Self::Integer(PyIntegerSerializer), + DataTypeGroup::FLOAT => Self::Float(PyFloatSerializer), + DataTypeGroup::BOOLEAN => Self::Boolean(PyBooleanSerializer), + DataTypeGroup::DATE => Self::Date(PyDateSerializer), + DataTypeGroup::TIME => Self::Time(PyTimeSerializer), + DataTypeGroup::DATETIME => Self::Datetime(PyDatetimeSerializer), + DataTypeGroup::JSON | DataTypeGroup::JSONB => Self::JSON(PyJSONSerializer), + DataTypeGroup::INET => Self::Inet(PyInetSerializer), + DataTypeGroup::MACADDRESS => Self::MacAddress(PyMacAddressSerializer), + DataTypeGroup::CUSTOM => Self::Unspecified(PyUnspecifiedSerializer), } - - if value == &DATE_DATA_TYPE { - return Self::Date(PyDateSerializer); - } - - if [ - ®CLASS_DATA_TYPE, - &TEXT_DATA_TYPE, - &TINY_TEXT_DATA_TYPE, - &MEDIUM_TEXT_DATA_TYPE, - &LONG_TEXT_DATA_TYPE, - ] - .contains(&value) - { - return Self::String(PyStringSerializer); - } - - if value == &UUID_DATA_TYPE { - return Self::Uuid(PyUuidSerializer); - } - - if [ - &BYTEA_DATA_TYPE, - &TINY_BLOB_DATA_TYPE, - &MEDIUM_BLOB_DATA_TYPE, - &LONG_BLOB_DATA_TYPE, - ] - .contains(&value) - { - return Self::Bytes(PyBytesSerializer); - } - - if value == &JSON_DATA_TYPE || value == &JSONB_DATA_TYPE { - return Self::JSON(PyJSONSerializer); - } - - if value == &TIME_DATA_TYPE || value == &TIME_WITH_TIME_ZONE_DATA_TYPE { - return Self::Time(PyTimeSerializer); - } - - if [ - &DATETIME_DATA_TYPE, - &TIMESTAMP_DATA_TYPE, - &TIMESTAMP_NTZ_DATA_TYPE, - &TIMESTAMP_WITH_TZ_DATA_TYPE, - ] - .contains(&value) - { - return Self::Datetime(PyDatetimeSerializer); - } - - // INTERVAL, TRIGGER, ANY TYPE, TSVECTOR, TSQUERY, UNSPECIFIED - // have no sensible scalar serializer; fall through to Unspecified. - Self::Unspecified(PyUnspecifiedSerializer) } pub fn to_pyobject<'py>( diff --git a/src/lib.rs b/src/lib.rs index 998c3a1..7fbbc48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ mod _lib { #[pymodule_init] #[cold] fn init(_m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { - // sqlparser::ast::DataType::Tiny + // sqlparser::ast::DataType::USmallInt Ok(()) } } From 9579e4641bbb72452dda2bd06210442c6dd70b94 Mon Sep 17 00:00:00 2001 From: awolverp Date: Sat, 27 Jun 2026 16:08:28 +0330 Subject: [PATCH 4/5] Write 13 dialects, and complete `data_type`s (not tested) --- rapidquery/_lib/{_lib => ast}/__init__.pyi | 0 rapidquery/_lib/{_lib => ast}/data_type.pyi | 10 +- rapidquery/_lib/dialect.pyi | 132 ++++++++ src/ast/data_type/mod.rs | 25 +- src/ast/data_type/serializers.rs | 28 +- src/dialect/bigquery.rs | 74 +++++ src/dialect/databricks.rs | 69 ++++ src/dialect/duckdb.rs | 52 +++ src/dialect/hive.rs | 68 ++++ src/dialect/mod.rs | 332 +++++++++++++++++++- src/dialect/mssql.rs | 64 ++++ src/dialect/mysql.rs | 63 ++++ src/dialect/oracle.rs | 54 ++++ src/dialect/postgresql.rs | 76 +++++ src/dialect/redshift.rs | 58 ++++ src/dialect/snowflake.rs | 66 ++++ src/dialect/spark.rs | 68 ++++ src/dialect/sqlite.rs | 53 ++++ src/dialect/teradata.rs | 56 ++++ src/internal/utils.rs | 31 ++ src/lib.rs | 12 +- src/typeref.rs | 31 ++ 22 files changed, 1399 insertions(+), 23 deletions(-) rename rapidquery/_lib/{_lib => ast}/__init__.pyi (100%) rename rapidquery/_lib/{_lib => ast}/data_type.pyi (72%) create mode 100644 rapidquery/_lib/dialect.pyi create mode 100644 src/dialect/bigquery.rs create mode 100644 src/dialect/databricks.rs create mode 100644 src/dialect/duckdb.rs create mode 100644 src/dialect/hive.rs create mode 100644 src/dialect/mssql.rs create mode 100644 src/dialect/mysql.rs create mode 100644 src/dialect/oracle.rs create mode 100644 src/dialect/postgresql.rs create mode 100644 src/dialect/redshift.rs create mode 100644 src/dialect/snowflake.rs create mode 100644 src/dialect/spark.rs create mode 100644 src/dialect/sqlite.rs create mode 100644 src/dialect/teradata.rs diff --git a/rapidquery/_lib/_lib/__init__.pyi b/rapidquery/_lib/ast/__init__.pyi similarity index 100% rename from rapidquery/_lib/_lib/__init__.pyi rename to rapidquery/_lib/ast/__init__.pyi diff --git a/rapidquery/_lib/_lib/data_type.pyi b/rapidquery/_lib/ast/data_type.pyi similarity index 72% rename from rapidquery/_lib/_lib/data_type.pyi rename to rapidquery/_lib/ast/data_type.pyi index 19595c4..04824be 100644 --- a/rapidquery/_lib/_lib/data_type.pyi +++ b/rapidquery/_lib/ast/data_type.pyi @@ -1,3 +1,4 @@ +from rapidquery._lib.dialect import BigQueryDialect, DatabricksDialect, DuckDbDialect, HiveDialect, MsSqlDialect, MySqlDialect, OracleDialect, PostgreSqlDialect, RedshiftDialect, SnowflakeDialect, SparkDialect, SqliteDialect, TeradataDialect from typing import Any, Callable, final @final @@ -6,7 +7,7 @@ class ArraySerializer: Serializes a Python `list`/`tuple` by serializing each element with `element` and wrapping in `ARRAY[...]`. """ - def __new__(cls, /, element: 'ArraySerializer' |'BitSerializer' |'BooleanSerializer' |'BytesSerializer' |'DateSerializer' |'DatetimeSerializer' |'FloatSerializer' |'InetSerializer' |'IntegerSerializer' |'JSONSerializer' |'MacAddressSerializer' |'StringSerializer' |'TimeSerializer' |'UnspecifiedSerializer' |'UuidSerializer' |Callable[[Any, str], str]) -> ArraySerializer: ... + def __new__(cls, /, element: 'ArraySerializer' |'BitSerializer' |'BooleanSerializer' |'BytesSerializer' |'DateSerializer' |'DatetimeSerializer' |'FloatSerializer' |'InetSerializer' |'IntegerSerializer' |'JSONSerializer' |'MacAddressSerializer' |'StringSerializer' |'TimeSerializer' |'UnspecifiedSerializer' |'UuidSerializer' |Callable[[BigQueryDialect |DatabricksDialect |DuckDbDialect |HiveDialect |MsSqlDialect |MySqlDialect |OracleDialect |PostgreSqlDialect |RedshiftDialect |SnowflakeDialect |SparkDialect |SqliteDialect |TeradataDialect, str], str]) -> ArraySerializer: ... @final class BitSerializer: @@ -29,9 +30,14 @@ class BytesSerializer: @final class DataType: - def __new__(cls, /, name: str, serializer: 'ArraySerializer' |'BitSerializer' |'BooleanSerializer' |'BytesSerializer' |'DateSerializer' |'DatetimeSerializer' |'FloatSerializer' |'InetSerializer' |'IntegerSerializer' |'JSONSerializer' |'MacAddressSerializer' |'StringSerializer' |'TimeSerializer' |'UnspecifiedSerializer' |'UuidSerializer' |Callable[[Any, str], str] |None = None) -> DataType: ... + """ + SQL data type. + """ + def __new__(cls, /, name: str, serializer: 'ArraySerializer' |'BitSerializer' |'BooleanSerializer' |'BytesSerializer' |'DateSerializer' |'DatetimeSerializer' |'FloatSerializer' |'InetSerializer' |'IntegerSerializer' |'JSONSerializer' |'MacAddressSerializer' |'StringSerializer' |'TimeSerializer' |'UnspecifiedSerializer' |'UuidSerializer' |Callable[[BigQueryDialect |DatabricksDialect |DuckDbDialect |HiveDialect |MsSqlDialect |MySqlDialect |OracleDialect |PostgreSqlDialect |RedshiftDialect |SnowflakeDialect |SparkDialect |SqliteDialect |TeradataDialect, str], str] |None = None) -> DataType: ... + def __repr__(self, /) -> str: ... @property def name(self, /) -> str: ... + def serialize(self, /, dialect: BigQueryDialect |DatabricksDialect |DuckDbDialect |HiveDialect |MsSqlDialect |MySqlDialect |OracleDialect |PostgreSqlDialect |RedshiftDialect |SnowflakeDialect |SparkDialect |SqliteDialect |TeradataDialect |type[BigQueryDialect] |type[DatabricksDialect] |type[DuckDbDialect] |type[HiveDialect] |type[MsSqlDialect] |type[MySqlDialect] |type[OracleDialect] |type[PostgreSqlDialect] |type[RedshiftDialect] |type[SnowflakeDialect] |type[SparkDialect] |type[SqliteDialect] |type[TeradataDialect], value: Any) -> str: ... @property def serializer(self, /) -> Any: ... diff --git a/rapidquery/_lib/dialect.pyi b/rapidquery/_lib/dialect.pyi new file mode 100644 index 0000000..bff5f00 --- /dev/null +++ b/rapidquery/_lib/dialect.pyi @@ -0,0 +1,132 @@ +from collections.abc import Sequence +from typing import final + +@final +class BigQueryDialect: + def __new__(cls, /) -> BigQueryDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class DatabricksDialect: + def __new__(cls, /) -> DatabricksDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class DuckDbDialect: + def __new__(cls, /) -> DuckDbDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class HiveDialect: + def __new__(cls, /) -> HiveDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class MsSqlDialect: + def __new__(cls, /) -> MsSqlDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class MySqlDialect: + def __new__(cls, /) -> MySqlDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class OracleDialect: + def __new__(cls, /) -> OracleDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class PostgreSqlDialect: + def __new__(cls, /) -> PostgreSqlDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class RedshiftDialect: + def __new__(cls, /) -> RedshiftDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class SnowflakeDialect: + def __new__(cls, /) -> SnowflakeDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class SparkDialect: + def __new__(cls, /) -> SparkDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class SqliteDialect: + def __new__(cls, /) -> SqliteDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... + +@final +class TeradataDialect: + def __new__(cls, /) -> TeradataDialect: ... + def encode_bits(self, /, bits: str) -> str: ... + def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... + def escape_string(self, /, s: str) -> str: ... + def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... + def get_name(self, /) -> str: ... + def quote_string(self, /, s: str) -> str: ... diff --git a/src/ast/data_type/mod.rs b/src/ast/data_type/mod.rs index 80266c6..d4ee393 100644 --- a/src/ast/data_type/mod.rs +++ b/src/ast/data_type/mod.rs @@ -4,8 +4,9 @@ mod name; pub use name::DataTypeName; pub mod serializers; -pub use serializers::DataTypeSerializer; +pub use serializers::{DataTypeSerializerUnion, Serialize}; +use crate::dialect::DialectUnion; use crate::internal::alias; implement_pyclass! { @@ -13,7 +14,7 @@ implement_pyclass! { #[derive(Debug, Clone)] [skip_from_py_object] PyDataType as "DataType" { pub name: DataTypeName, - pub serializer: Arc, + pub serializer: Arc, } } @@ -21,12 +22,12 @@ implement_pyclass! { impl PyDataType { #[new] #[pyo3(signature = (name, serializer=None) )] - fn __new__(name: String, serializer: Option) -> Self { + fn __new__(name: String, serializer: Option) -> Self { let name = DataTypeName::from(name.as_str()); let serializer = match serializer { Some(x) => Arc::new(x), - None => Arc::new(DataTypeSerializer::infer_from_name(&name)), + None => Arc::new(DataTypeSerializerUnion::infer_from_name(&name)), }; Self { name, serializer } @@ -41,6 +42,22 @@ impl PyDataType { fn serializer<'a>(&self, py: pyo3::Python<'a>) -> pyo3::PyResult> { self.serializer.to_pyobject(py) } + + fn serialize( + &self, + py: pyo3::Python, + dialect: DialectUnion, + value: alias::PyObject, + ) -> pyo3::PyResult { + unsafe { self.serializer.serialize(py, value.as_ptr(), &dialect) } + } + + fn __repr__(&self) -> String { + format!( + "DataType(name={}, serializer={:?})", + self.name, self.serializer, + ) + } } #[pyo3::pymodule(name = "data_type")] diff --git a/src/ast/data_type/serializers.rs b/src/ast/data_type/serializers.rs index 4c3fb1b..2dd437d 100644 --- a/src/ast/data_type/serializers.rs +++ b/src/ast/data_type/serializers.rs @@ -23,7 +23,7 @@ pub trait Serialize { /// One variant per *distinct serialization behaviour* - many SQL data types /// collapse onto the same serializer. #[derive(Debug)] -pub enum DataTypeSerializer { +pub enum DataTypeSerializerUnion { /// Every signed/unsigned integer width. /// /// Supports: @@ -174,7 +174,7 @@ pub enum DataTypeSerializer { Unspecified(PyUnspecifiedSerializer), } -impl DataTypeSerializer { +impl DataTypeSerializerUnion { pub fn infer_from_name(value: &super::name::DataTypeName) -> Self { use super::name::DataTypeGroup; @@ -221,7 +221,7 @@ impl DataTypeSerializer { } } -impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for DataTypeSerializer { +impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for DataTypeSerializerUnion { type Error = pyo3::PyErr; #[cfg(feature = "experimental-inspect")] @@ -245,7 +245,21 @@ impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for DataTypeSerializer { type_hint_identifier!("typing", "Callable"), pyo3::inspect::PyStaticExpr::List { elts: &[ - type_hint_identifier!("typing", "Any"), + type_hint_union!( + type_hint_identifier!("rapidquery._lib.dialect", "BigQueryDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "DatabricksDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "DuckDbDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "HiveDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "MsSqlDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "MySqlDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "OracleDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "PostgreSqlDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "RedshiftDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "SnowflakeDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "SparkDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "SqliteDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "TeradataDialect") + ), type_hint_identifier!("str") ] }, @@ -316,7 +330,7 @@ impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for DataTypeSerializer { } } -impl Serialize for DataTypeSerializer { +impl Serialize for DataTypeSerializerUnion { unsafe fn serialize( &self, py: pyo3::Python, @@ -937,14 +951,14 @@ implement_pyclass! { /// and wrapping in `ARRAY[...]`. #[derive(Debug, Clone)] [skip_from_py_object] PyArraySerializer as "ArraySerializer" { - pub element: Arc, + pub element: Arc, } } #[pyo3::pymethods] impl PyArraySerializer { #[new] - fn __new__(element: DataTypeSerializer) -> Self { + fn __new__(element: DataTypeSerializerUnion) -> Self { Self { element: Arc::new(element), } diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs new file mode 100644 index 0000000..f391edf --- /dev/null +++ b/src/dialect/bigquery.rs @@ -0,0 +1,74 @@ +// • Strings: single-quoted; escape with backslash sequences. +// • Identifiers: back-tick quoted. +// • Bytes: B'\xNN…' (byte literal with \x escapes). +// • Bits: No native bit-string literal; cast from INT64. +// We emit CAST(0b AS BYTES) for best portability. +// • Bool: TRUE / FALSE keywords. +// +// Ref: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PyBigQueryDialect as "BigQueryDialect"; +} + +impl super::Dialect for PyBigQueryDialect { + fn name(&self) -> &'static str { + "BigQuery" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Keyword + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('`', '`')) + } + + fn escape_string(&self, s: &str) -> String { + let mut out = String::with_capacity(s.len() + s.len() / 4); + for ch in s.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '\'' => out.push_str("\\'"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\0' => out.push_str("\\0"), + // ASCII control characters + c if (c as u32) < 32 || c as u32 == 127 => { + out.push_str(&format!("\\x{:02X}", c as u32)); + } + c => out.push(c), + } + } + out + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + // BigQuery byte literal: B'\xNN\xNN…' + let mut out = String::with_capacity(4 + bytes.len() * 4); + out.push_str("B'"); + for b in bytes { + out.push_str(&format!("\\x{:02X}", b)); + } + out.push('\''); + out + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + // BigQuery has no native bit literal; represent as INT64 binary literal. + format!("CAST(0b{} AS INT64)", bits) + } +} + +super::implement_dialect_pymethods!(PyBigQueryDialect); diff --git a/src/dialect/databricks.rs b/src/dialect/databricks.rs new file mode 100644 index 0000000..de1b721 --- /dev/null +++ b/src/dialect/databricks.rs @@ -0,0 +1,69 @@ +// • Strings: single-quoted; backslash-escape sequences. +// • Identifiers: back-tick quoted. +// • Bytes: X'DEADBEEF' hex literal (BINARY type). +// • Bits: No native bit literal; we use CAST(… AS BINARY) from INT. +// • Bool: TRUE / FALSE keywords. +// +// Ref: https://docs.databricks.com/en/sql/language-manual/sql-ref-literals.html + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PyDatabricksDialect as "DatabricksDialect"; +} + +impl super::Dialect for PyDatabricksDialect { + fn name(&self) -> &'static str { + "Databricks" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Keyword + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('`', '`')) + } + + fn escape_string(&self, s: &str) -> String { + let mut out = String::with_capacity(s.len() + s.len() / 4); + for ch in s.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '\'' => out.push_str("\\'"), + '"' => out.push_str("\\\""), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\0' => out.push_str("\\0"), + c if (c as u32) < 32 || c as u32 == 127 => { + out.push_str(&format!("\\u{:04X}", c as u32)); + } + c => out.push(c), + } + } + out + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + format!("X'{}'", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + // Databricks supports CAST with integer literals + format!( + "CAST({}L AS BINARY)", + i64::from_str_radix(bits, 2).unwrap_or(0) + ) + } +} + +super::implement_dialect_pymethods!(PyDatabricksDialect); diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs new file mode 100644 index 0000000..ee5bdd2 --- /dev/null +++ b/src/dialect/duckdb.rs @@ -0,0 +1,52 @@ +// • Strings: single-quoted; `''` for embedded single-quote. +// • Identifiers: double-quote quoted. +// • Bytes: '\xNN…'::BLOB or X'DEADBEEF' hex literal. +// • Bits: 0b::INTEGER or BIT literal via BITSTRING. +// • Bool: TRUE / FALSE keywords. +// +// Ref: https://duckdb.org/docs/sql/data_types/literal_types + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PyDuckDbDialect as "DuckDbDialect"; +} + +impl super::Dialect for PyDuckDbDialect { + fn name(&self) -> &'static str { + "DuckDB" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Keyword + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('"', '"')) + } + + fn escape_string(&self, s: &str) -> String { + // DuckDB uses '' for quote escaping (ANSI-compatible). + // Backslash is NOT a special escape character in default mode. + super::escape_single_quote(s) + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + format!("X'{}'", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + // DuckDB supports bitstring literals with B prefix + format!("B'{}'", bits) + } +} + +super::implement_dialect_pymethods!(PyDuckDbDialect); diff --git a/src/dialect/hive.rs b/src/dialect/hive.rs new file mode 100644 index 0000000..c9a9e65 --- /dev/null +++ b/src/dialect/hive.rs @@ -0,0 +1,68 @@ +// • Strings: single-quoted; backslash-escape sequences (Java conventions). +// • Identifiers: back-tick quoted. +// • Bytes: No native byte literal; use CAST with hex string. +// • Bits: No native bit literal; use CAST with integer. +// • Bool: TRUE / FALSE keywords. +// +// Ref: https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PyHiveDialect as "HiveDialect"; +} + +impl super::Dialect for PyHiveDialect { + fn name(&self) -> &'static str { + "Hive" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Keyword + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('`', '`')) + } + + fn escape_string(&self, s: &str) -> String { + let mut out = String::with_capacity(s.len() + s.len() / 4); + for ch in s.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '\'' => out.push_str("\\'"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\0' => out.push_str("\\0"), + c if (c as u32) < 32 || c as u32 == 127 => { + out.push_str(&format!("\\u{:04X}", c as u32)); + } + c => out.push(c), + } + } + out + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + // Hive: BINARY from hex string via unhex() + format!("UNHEX('{}')", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + format!( + "CAST({} AS BIGINT)", + i64::from_str_radix(bits, 2).unwrap_or(0) + ) + } +} + +super::implement_dialect_pymethods!(PyHiveDialect); diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 393b9d4..47ad382 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1,8 +1,8 @@ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum BoolFormat { - /// `TRUE` / `FALSE`. + /// `TRUE` / `FALSE` Keyword, - /// `1` / `0` (MySQL, SQLite). + /// `1` / `0` Int, } @@ -18,11 +18,6 @@ impl BoolFormat { } pub trait Dialect { - // we cannot use constant variables here for boolean format - // and identifier quotes. - // - // https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility - /// Dialect name fn name(&self) -> &'static str; @@ -55,5 +50,328 @@ pub trait Dialect { fn encode_bytes(&self, bytes: &[u8]) -> String; /// Render a byte slice as a literal in the configured form. + /// + /// The `bits` is always validated. fn encode_bits(&self, bits: &str) -> String; } + +/// Converts a byte slice to an uppercase hex string. +#[inline] +pub(super) fn bytes_to_hex(bytes: &[u8]) -> String { + use std::fmt::Write; + let mut out = String::with_capacity(bytes.len() * 2); + for b in bytes { + write!(out, "{:02X}", b).unwrap(); + } + out +} + +/// Standard single-quote escape: `'` → `''`. +/// Used by most SQL dialects. +#[inline] +pub(super) fn escape_single_quote(s: &str) -> String { + s.replace('\'', "''") +} + +/// Wraps a (pre-escaped) string in single quotes. +#[inline] +pub(super) fn wrap_single_quote(s: &mut String) { + s.insert(0, '\''); + s.push('\''); +} + +#[macro_export] +macro_rules! implement_dialect_pymethods { + ($name:ident) => { + #[pyo3::pymethods] + impl $name { + #[new] + fn __new__() -> Self { + Self + } + + fn get_name(&self) -> &'static str { + super::Dialect::name(self) + } + + fn get_identifier_quotes(&self) -> Option<(char, char)> { + super::Dialect::identifier_quotes(self) + } + + fn escape_string(&self, s: String) -> String { + super::Dialect::escape_string(self, &s) + } + + fn quote_string(&self, mut s: String) -> String { + super::Dialect::quote_string(self, &mut s); + s + } + + fn encode_bytes(&self, bytes: Vec) -> String { + super::Dialect::encode_bytes(self, &bytes) + } + + fn encode_bits(&self, bits: &str) -> pyo3::PyResult { + if !bits.chars().all(|c| c == '0' || c == '1') { + return Err(new_py_error!(PyValueError, "invalid input: {}", bits)); + } + + Ok(super::Dialect::encode_bits(self, bits)) + } + } + }; +} +pub(super) use implement_dialect_pymethods; + +mod bigquery; +pub use bigquery::PyBigQueryDialect; + +mod databricks; +pub use databricks::PyDatabricksDialect; + +mod duckdb; +pub use duckdb::PyDuckDbDialect; + +mod hive; +pub use hive::PyHiveDialect; + +mod mssql; +pub use mssql::PyMsSqlDialect; + +mod mysql; +pub use mysql::PyMySqlDialect; + +mod oracle; +pub use oracle::PyOracleDialect; + +mod postgresql; +pub use postgresql::PyPostgreSqlDialect; + +mod redshift; +pub use redshift::PyRedshiftDialect; + +mod snowflake; +pub use snowflake::PySnowflakeDialect; + +mod spark; +pub use spark::PySparkDialect; + +mod sqlite; +pub use sqlite::PySqliteDialect; + +mod teradata; +pub use teradata::PyTeradataDialect; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum DialectUnion { + BigQuery(PyBigQueryDialect), + Databricks(PyDatabricksDialect), + DuckDb(PyDuckDbDialect), + Hive(PyHiveDialect), + MsSql(PyMsSqlDialect), + MySql(PyMySqlDialect), + Oracle(PyOracleDialect), + PostgreSql(PyPostgreSqlDialect), + Redshift(PyRedshiftDialect), + Snowflake(PySnowflakeDialect), + Spark(PySparkDialect), + Sqlite(PySqliteDialect), + Teradata(PyTeradataDialect), +} + +impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for DialectUnion { + type Error = pyo3::PyErr; + + #[cfg(feature = "experimental-inspect")] + const INPUT_TYPE: pyo3::inspect::PyStaticExpr = type_hint_union!( + // as object + type_hint_identifier!("rapidquery._lib.dialect", "BigQueryDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "DatabricksDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "DuckDbDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "HiveDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "MsSqlDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "MySqlDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "OracleDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "PostgreSqlDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "RedshiftDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "SnowflakeDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "SparkDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "SqliteDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "TeradataDialect"), + // as type + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "BigQueryDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "DatabricksDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "DuckDbDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "HiveDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "MsSqlDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "MySqlDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "OracleDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "PostgreSqlDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "RedshiftDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "SnowflakeDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "SparkDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "SqliteDialect") + ), + type_hint_subscript!( + type_hint_identifier!("type"), + type_hint_identifier!("rapidquery._lib.dialect", "TeradataDialect") + ) + ); + + fn extract( + obj: pyo3::prelude::Borrowed<'a, 'py, pyo3::prelude::PyAny>, + ) -> Result { + unsafe { + let ptr = obj.as_ptr(); + + let type_ptr = { + if pyo3::ffi::PyType_CheckExact(ptr) == 1 { + ptr as *mut pyo3::ffi::PyTypeObject + } else { + pyo3::ffi::Py_TYPE(ptr) + } + }; + + if type_ptr == crate::typeref::BIGQUERY_DIALECT_TYPE { + return Ok(Self::BigQuery(PyBigQueryDialect)); + } + if type_ptr == crate::typeref::DATABRICKS_DIALECT_TYPE { + return Ok(Self::Databricks(PyDatabricksDialect)); + } + if type_ptr == crate::typeref::DUCKDB_DIALECT_TYPE { + return Ok(Self::DuckDb(PyDuckDbDialect)); + } + if type_ptr == crate::typeref::HIVE_DIALECT_TYPE { + return Ok(Self::Hive(PyHiveDialect)); + } + if type_ptr == crate::typeref::MS_SQL_DIALECT_TYPE { + return Ok(Self::MsSql(PyMsSqlDialect)); + } + if type_ptr == crate::typeref::MYSQL_DIALECT_TYPE { + return Ok(Self::MySql(PyMySqlDialect)); + } + if type_ptr == crate::typeref::ORACLE_DIALECT_TYPE { + return Ok(Self::Oracle(PyOracleDialect)); + } + if type_ptr == crate::typeref::POSTGRESQL_DIALECT_TYPE { + return Ok(Self::PostgreSql(PyPostgreSqlDialect)); + } + if type_ptr == crate::typeref::REDSHIFT_DIALECT_TYPE { + return Ok(Self::Redshift(PyRedshiftDialect)); + } + if type_ptr == crate::typeref::SNOWFLAKE_DIALECT_TYPE { + return Ok(Self::Snowflake(PySnowflakeDialect)); + } + if type_ptr == crate::typeref::SPARK_DIALECT_TYPE { + return Ok(Self::Spark(PySparkDialect)); + } + if type_ptr == crate::typeref::SQLITE_DIALECT_TYPE { + return Ok(Self::Sqlite(PySqliteDialect)); + } + if type_ptr == crate::typeref::TERADATA_DIALECT_TYPE { + return Ok(Self::Teradata(PyTeradataDialect)); + } + + Err(new_py_error!( + PyTypeError, + "Unknown dialect: {}", + crate::internal::utils::get_type_name(obj.py(), ptr) + )) + } + } +} + +macro_rules! impl_for_all_fields { + ($self:expr, $dialect:ident => $expr:expr) => { + match $self { + Self::BigQuery($dialect) => $expr, + Self::Databricks($dialect) => $expr, + Self::DuckDb($dialect) => $expr, + Self::Hive($dialect) => $expr, + Self::MsSql($dialect) => $expr, + Self::MySql($dialect) => $expr, + Self::Oracle($dialect) => $expr, + Self::PostgreSql($dialect) => $expr, + Self::Redshift($dialect) => $expr, + Self::Snowflake($dialect) => $expr, + Self::Spark($dialect) => $expr, + Self::Sqlite($dialect) => $expr, + Self::Teradata($dialect) => $expr, + } + }; +} + +impl Dialect for DialectUnion { + fn name(&self) -> &'static str { + impl_for_all_fields!(self, s => s.name()) + } + + fn bool_format(&self) -> BoolFormat { + impl_for_all_fields!(self, s => s.bool_format()) + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + impl_for_all_fields!(self, s => s.identifier_quotes()) + } + + fn escape_string(&self, string: &str) -> String { + impl_for_all_fields!(self, s => s.escape_string(string)) + } + + fn quote_string(&self, string: &mut String) { + impl_for_all_fields!(self, s => s.quote_string(string)) + } + + fn encode_bits(&self, bits: &str) -> String { + impl_for_all_fields!(self, s => s.encode_bits(bits)) + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + impl_for_all_fields!(self, s => s.encode_bytes(bytes)) + } +} + +#[pyo3::pymodule(name = "dialect")] +pub mod dialect_module { + #[pymodule_export] + pub use super::{ + PyBigQueryDialect, PyDatabricksDialect, PyDuckDbDialect, PyHiveDialect, PyMsSqlDialect, + PyMySqlDialect, PyOracleDialect, PyPostgreSqlDialect, PyRedshiftDialect, + PySnowflakeDialect, PySparkDialect, PySqliteDialect, PyTeradataDialect, + }; +} diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs new file mode 100644 index 0000000..be92b9d --- /dev/null +++ b/src/dialect/mssql.rs @@ -0,0 +1,64 @@ +// • Strings: single-quoted; `''` for embedded single-quote. +// Unicode strings prefixed with N'…'. +// • Identifiers: square-bracket quoted: [name]. +// • Bytes: 0xDEADBEEF (no quotes). +// • Bits: Cast integer to BIT; MSSQL BIT is boolean-like (0/1). +// • Bool: 1 / 0 (BIT type; no TRUE/FALSE keyword). +// +// Ref: https://learn.microsoft.com/en-us/sql/t-sql/data-types/constants-transact-sql + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PyMsSqlDialect as "MsSqlDialect"; +} + +impl super::Dialect for PyMsSqlDialect { + fn name(&self) -> &'static str { + "MsSQL" + } + + fn bool_format(&self) -> super::BoolFormat { + // MSSQL BIT type uses 1/0 + super::BoolFormat::Int + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('[', ']')) + } + + fn escape_string(&self, s: &str) -> String { + // T-SQL: only single-quote needs doubling. + // NUL character (U+0000) is not allowed in T-SQL string literals. + // We remove it to stay safe. + super::escape_single_quote(s).replace('\0', "") + } + + fn quote_string(&self, s: &mut String) { + // Prefix with N for Unicode safety (NVARCHAR) + let mut out = String::with_capacity(s.len() + 3); + out.push_str("N'"); + out.push_str(s); + out.push('\''); + *s = out; + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + // T-SQL binary literal: 0xDEADBEEF + format!("0x{}", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + // MSSQL BIT is a 1-bit integer; for multi-bit we cast to integer + format!( + "CAST({} AS BIGINT)", + i64::from_str_radix(bits, 2).unwrap_or(0) + ) + } +} + +super::implement_dialect_pymethods!(PyMsSqlDialect); diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs new file mode 100644 index 0000000..eadfcdf --- /dev/null +++ b/src/dialect/mysql.rs @@ -0,0 +1,63 @@ +// • Strings: single-quoted; backslash-escape sequences. +// • Identifiers: back-tick quoted. +// • Bytes: X'DEADBEEF' or 0xDEADBEEF. +// • Bits: b'10110101' bit literal. +// • Bool: 1 / 0 (TINYINT; no native BOOLEAN literal). +// +// Ref: https://dev.mysql.com/doc/refman/8.0/en/string-literals.html +// https://dev.mysql.com/doc/refman/8.0/en/bit-value-literals.html + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PyMySqlDialect as "MySqlDialect"; +} + +impl super::Dialect for PyMySqlDialect { + fn name(&self) -> &'static str { + "MySQL" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Int + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('`', '`')) + } + + fn escape_string(&self, s: &str) -> String { + let mut out = String::with_capacity(s.len() + s.len() / 4); + for ch in s.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '\'' => out.push_str("\\'"), + '"' => out.push_str("\\\""), + '\0' => out.push_str("\\0"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\x1a' => out.push_str("\\Z"), // Ctrl+Z / SUB (Windows EOF) + c => out.push(c), + } + } + out + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + format!("X'{}'", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + format!("b'{}'", bits) + } +} + +super::implement_dialect_pymethods!(PyMySqlDialect); diff --git a/src/dialect/oracle.rs b/src/dialect/oracle.rs new file mode 100644 index 0000000..602d6e2 --- /dev/null +++ b/src/dialect/oracle.rs @@ -0,0 +1,54 @@ +// Ref: https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Literals.html +// +// • Strings: single-quoted; `''` for embedded single-quote. +// Alternatively q'[…]' alternative quoting. +// • Identifiers: double-quote quoted. +// • Bytes: HEXTORAW('DEADBEEF'). +// • Bits: No native bit literal; use integer value. +// • Bool: Oracle < 23c has no BOOLEAN in SQL; 23c onwards has TRUE/FALSE. +// We default to 1/0 for broadest compatibility. + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PyOracleDialect as "OracleDialect"; +} + +impl super::Dialect for PyOracleDialect { + fn name(&self) -> &'static str { + "Oracle" + } + + fn bool_format(&self) -> super::BoolFormat { + // TRUE/FALSE only available in Oracle 23c+; default to Int for safety + super::BoolFormat::Int + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('"', '"')) + } + + fn escape_string(&self, s: &str) -> String { + // Oracle: only single-quote doubling; no backslash escaping. + // NUL bytes are not supported in VARCHAR2 literals. + super::escape_single_quote(s).replace('\0', "") + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + format!("HEXTORAW('{}')", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + format!("{}", i64::from_str_radix(bits, 2).unwrap_or(0)) + } +} + +super::implement_dialect_pymethods!(PyOracleDialect); diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs new file mode 100644 index 0000000..3190391 --- /dev/null +++ b/src/dialect/postgresql.rs @@ -0,0 +1,76 @@ +// • Strings: single-quoted with `''` doubling. +// For control chars we use E'…' escape strings with \xNN. +// • Identifiers: double-quote quoted. +// • Bytes: '\xDEADBEEF'::bytea (hex format, PostgreSQL 9.0+). +// • Bits: B'10110101' bit string literal. +// • Bool: TRUE / FALSE keywords. +// +// Ref: https://www.postgresql.org/docs/current/sql-syntax-lexical.html +// https://www.postgresql.org/docs/current/datatype-binary.html +// https://www.postgresql.org/docs/current/datatype-bit.html + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PyPostgreSqlDialect as "PostgreSqlDialect"; +} + +impl super::Dialect for PyPostgreSqlDialect { + fn name(&self) -> &'static str { + "PostgreSQL" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Keyword + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('"', '"')) + } + + fn escape_string(&self, s: &str) -> String { + // Use PostgreSQL escape-string syntax (E'…') when control chars present. + // We always emit E'…' for safety; non-special chars pass through. + let mut out = String::with_capacity(s.len() + s.len() / 4); + + for ch in s.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '\'' => out.push_str("\\'"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\0' => out.push_str("\\x00"), + c if (c as u32) < 32 || c as u32 == 127 => { + out.push_str(&format!("\\x{:02X}", c as u32)); + } + c => out.push(c), + } + } + out + } + + fn quote_string(&self, s: &mut String) { + // Use E'…' escape string syntax so our \-sequences are interpreted. + let mut out = String::with_capacity(s.len() + 3); + out.push_str("E'"); + out.push_str(s); + out.push('\''); + *s = out; + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + // PostgreSQL hex-format bytea literal + format!("'\\x{}'::bytea", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + format!("B'{}'", bits) + } +} + +super::implement_dialect_pymethods!(PyPostgreSqlDialect); diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs new file mode 100644 index 0000000..6ed551d --- /dev/null +++ b/src/dialect/redshift.rs @@ -0,0 +1,58 @@ +// • Strings: single-quoted; `''` for embedded single-quote. +// Redshift is PostgreSQL-derived but does NOT support E'…' by default. +// Backslash in default mode is literal (not escape); so we double quotes only. +// • Identifiers: double-quote quoted. +// • Bytes: VARBYTE from hex via FROM_HEX or TO_VARBYTE. +// • Bits: No native bit literal; cast integer. +// • Bool: TRUE / FALSE keywords (BOOLEAN type). +// +// Ref: https://docs.aws.amazon.com/redshift/latest/dg/r_Constants.html +// https://docs.aws.amazon.com/redshift/latest/dg/r_VARBYTE_type.html + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PyRedshiftDialect as "RedshiftDialect"; +} + +impl super::Dialect for PyRedshiftDialect { + fn name(&self) -> &'static str { + "Redshift" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Keyword + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('"', '"')) + } + + fn escape_string(&self, s: &str) -> String { + // Redshift: backslash is NOT an escape character in standard mode. + // Only `'` needs doubling. + // NUL (U+0000) is not supported in VARCHAR literals. + super::escape_single_quote(s).replace('\0', "") + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + format!("TO_VARBYTE('{}', 'hex')", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + format!( + "CAST({} AS BIGINT)", + i64::from_str_radix(bits, 2).unwrap_or(0) + ) + } +} + +super::implement_dialect_pymethods!(PyRedshiftDialect); diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs new file mode 100644 index 0000000..34a4c08 --- /dev/null +++ b/src/dialect/snowflake.rs @@ -0,0 +1,66 @@ +// • Strings: single-quoted; backslash-escape sequences. +// • Identifiers: double-quote quoted. +// • Bytes: X'DEADBEEF' hex literal (BINARY type). +// • Bits: No native bit literal; use TO_BINARY with integer. +// • Bool: TRUE / FALSE keywords. +// +// Ref: https://docs.snowflake.com/en/sql-reference/data-types-text +// https://docs.snowflake.com/en/sql-reference/literals + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PySnowflakeDialect as "SnowflakeDialect"; +} + +impl super::Dialect for PySnowflakeDialect { + fn name(&self) -> &'static str { + "Snowflake" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Keyword + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('"', '"')) + } + + fn escape_string(&self, s: &str) -> String { + let mut out = String::with_capacity(s.len() + s.len() / 4); + for ch in s.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '\'' => out.push_str("\\'"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\0' => out.push_str("\\0"), + c if (c as u32) < 32 || c as u32 == 127 => { + out.push_str(&format!("\\x{:02X}", c as u32)); + } + c => out.push(c), + } + } + out + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + format!("X'{}'", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + let val = i64::from_str_radix(bits, 2).unwrap_or(0); + format!("TO_BINARY(LPAD(TO_HEX({}), 16, '0'), 'HEX')", val) + } +} + +super::implement_dialect_pymethods!(PySnowflakeDialect); diff --git a/src/dialect/spark.rs b/src/dialect/spark.rs new file mode 100644 index 0000000..62f335b --- /dev/null +++ b/src/dialect/spark.rs @@ -0,0 +1,68 @@ +// • Strings: single-quoted; backslash-escape sequences (Java/Scala conventions). +// • Identifiers: back-tick quoted. +// • Bytes: X'DEADBEEF' hex literal. +// • Bits: No native bit literal; cast integer. +// • Bool: TRUE / FALSE keywords. +// +// Ref: https://spark.apache.org/docs/latest/sql-ref-literals.html + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PySparkDialect as "SparkDialect"; +} + +impl super::Dialect for PySparkDialect { + fn name(&self) -> &'static str { + "Spark" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Keyword + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('`', '`')) + } + + fn escape_string(&self, s: &str) -> String { + let mut out = String::with_capacity(s.len() + s.len() / 4); + for ch in s.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '\'' => out.push_str("\\'"), + '"' => out.push_str("\\\""), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\0' => out.push_str("\\0"), + c if (c as u32) < 32 || c as u32 == 127 => { + out.push_str(&format!("\\u{:04X}", c as u32)); + } + c => out.push(c), + } + } + out + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + format!("X'{}'", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + format!( + "CAST({} AS BIGINT)", + i64::from_str_radix(bits, 2).unwrap_or(0) + ) + } +} + +super::implement_dialect_pymethods!(PySparkDialect); diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs new file mode 100644 index 0000000..60802ca --- /dev/null +++ b/src/dialect/sqlite.rs @@ -0,0 +1,53 @@ +// • Strings: single-quoted; `''` for embedded single-quote. +// SQLite does NOT interpret backslash escapes in string literals. +// • Identifiers: double-quote quoted (also supports back-tick and […]). +// • Bytes: X'DEADBEEF' BLOB hex literal. +// • Bits: No native bit literal; use integer value. +// • Bool: 1 / 0 (no native BOOLEAN type). +// +// Ref: https://www.sqlite.org/lang_expr.html + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PySqliteDialect as "SqliteDialect"; +} + +impl super::Dialect for PySqliteDialect { + fn name(&self) -> &'static str { + "SQLite" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Int + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('"', '"')) + } + + fn escape_string(&self, s: &str) -> String { + // SQLite does not recognise backslash escape sequences. + // NUL character (U+0000) terminates the string in the C library; + // we remove it to be safe. + super::escape_single_quote(s).replace('\0', "") + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + format!("X'{}'", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + format!("{}", i64::from_str_radix(bits, 2).unwrap_or(0)) + } +} + +super::implement_dialect_pymethods!(PySqliteDialect); diff --git a/src/dialect/teradata.rs b/src/dialect/teradata.rs new file mode 100644 index 0000000..d6a4766 --- /dev/null +++ b/src/dialect/teradata.rs @@ -0,0 +1,56 @@ +// • Strings: single-quoted; `''` for embedded single-quote. +// Teradata supports escape sequences only in named character sets; +// safest approach is quote-doubling only. +// • Identifiers: double-quote quoted. +// • Bytes: '…'XB byte literal (hex digits in single quotes with XB suffix). +// OR '…'XBV for VARBYTE. +// • Bits: '10110101'B bit literal. +// • Bool: TRUE / FALSE (Teradata 13.x+ with BYTEINT or BOOLEAN). +// +// Ref: https://docs.teradata.com/r/Teradata-VantageCloud-Lake/SQL-Fundamentals/SQL-Data-Types-and-Literals + +implement_pyclass! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + [skip_from_py_object] PyTeradataDialect as "TeradataDialect"; +} + +impl super::Dialect for PyTeradataDialect { + fn name(&self) -> &'static str { + "Teradata" + } + + fn bool_format(&self) -> super::BoolFormat { + super::BoolFormat::Keyword + } + + fn identifier_quotes(&self) -> Option<(char, char)> { + Some(('"', '"')) + } + + fn escape_string(&self, s: &str) -> String { + // Teradata: only single-quote doubling; no backslash interpretation. + // NUL bytes are not supported. + super::escape_single_quote(s).replace('\0', "") + } + + fn quote_string(&self, s: &mut String) { + super::wrap_single_quote(s); + } + + fn encode_bytes(&self, bytes: &[u8]) -> String { + // Teradata byte literal: ''XBV + format!("'{}'XBV", super::bytes_to_hex(bytes)) + } + + fn encode_bits(&self, bits: &str) -> String { + debug_assert!( + bits.chars().all(|c| c == '0' || c == '1'), + "encode_bits: invalid input: {bits}" + ); + + // Teradata bit literal: ''B + format!("'{}'B", bits) + } +} + +super::implement_dialect_pymethods!(PyTeradataDialect); diff --git a/src/internal/utils.rs b/src/internal/utils.rs index e41a0d7..92f5c96 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -1,3 +1,5 @@ +use pyo3::types::{PyAnyMethods, PyModule, PyModuleMethods}; + /// Returns the type name of a [`pyo3::ffi::PyObject`]. /// /// Returns `""` on failure. @@ -17,3 +19,32 @@ pub unsafe fn get_type_name<'a>(py: pyo3::Python<'a>, obj: *mut pyo3::ffi::PyObj .unwrap_or_else(|_| String::from("")) } } + +// --- https://github.com/PyO3/pyo3/issues/759#issuecomment-4041245073 --- + +fn _inner_init(module: &pyo3::Bound<'_, PyModule>, path: &str) -> pyo3::PyResult<()> { + let py = module.py(); + let sys_modules = py.import("sys")?.getattr("modules")?; + for submod_name in module.index()? { + let submod_name = submod_name.extract::()?; + let submod = module.getattr(&submod_name)?; + let modpath = format!("{path}.{submod_name}"); + if let Ok(submod) = submod.cast::() { + _inner_init(&submod, &modpath)?; + sys_modules.set_item(&modpath, submod)?; + } + } + Ok(()) +} + +pub trait PyModuleSubmoduleExt { + fn init_submodules(&self) -> pyo3::PyResult<()>; +} + +impl PyModuleSubmoduleExt for pyo3::Bound<'_, pyo3::types::PyModule> { + fn init_submodules(&self) -> pyo3::PyResult<()> { + let mod_path = self.name()?.extract::()?; + _inner_init(self, &mod_path)?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 7fbbc48..7207945 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,10 +19,16 @@ mod _lib { #[pymodule_export] pub use super::ast::ast_module; + #[pymodule_export] + pub use super::dialect::dialect_module; + #[pymodule_init] #[cold] - fn init(_m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { - // sqlparser::ast::DataType::USmallInt - Ok(()) + fn init(m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { + use crate::internal::utils::PyModuleSubmoduleExt; + + crate::typeref::initialize_typeref(m.py()); + + m.init_submodules() } } diff --git a/src/typeref.rs b/src/typeref.rs index 6c0f63e..88b6b10 100644 --- a/src/typeref.rs +++ b/src/typeref.rs @@ -1,3 +1,4 @@ +// Serializer types pub(crate) static mut INTEGER_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); pub(crate) static mut FLOAT_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); pub(crate) static mut BOOLEAN_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); @@ -16,6 +17,21 @@ pub(crate) static mut ARRAY_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std: pub(crate) static mut UNSPECIFIED_SERIALIZER_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +// Dialect types +pub(crate) static mut BIGQUERY_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut DATABRICKS_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut DUCKDB_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut HIVE_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut MS_SQL_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut MYSQL_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut ORACLE_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut POSTGRESQL_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut REDSHIFT_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut SNOWFLAKE_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut SPARK_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut SQLITE_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); +pub(crate) static mut TERADATA_DIALECT_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); + // Python standard libraries types pub(crate) static mut STD_DECIMAL_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); pub(crate) static mut STD_UUID_TYPE: *mut pyo3::ffi::PyTypeObject = std::ptr::null_mut(); @@ -56,6 +72,7 @@ fn _initialize_typeref(py: pyo3::Python) { unsafe { multiple_get_type_object_for!( py, + // serializers crate::ast::data_type::serializers::PyIntegerSerializer => INTEGER_SERIALIZER_TYPE, crate::ast::data_type::serializers::PyFloatSerializer => FLOAT_SERIALIZER_TYPE, crate::ast::data_type::serializers::PyBooleanSerializer => BOOLEAN_SERIALIZER_TYPE, @@ -71,6 +88,20 @@ fn _initialize_typeref(py: pyo3::Python) { crate::ast::data_type::serializers::PyMacAddressSerializer => MAC_ADDRESS_SERIALIZER_TYPE, crate::ast::data_type::serializers::PyArraySerializer => ARRAY_SERIALIZER_TYPE, crate::ast::data_type::serializers::PyUnspecifiedSerializer => UNSPECIFIED_SERIALIZER_TYPE, + // dialects + crate::dialect::PyBigQueryDialect => BIGQUERY_DIALECT_TYPE, + crate::dialect::PyDatabricksDialect => DATABRICKS_DIALECT_TYPE, + crate::dialect::PyDuckDbDialect => DUCKDB_DIALECT_TYPE, + crate::dialect::PyHiveDialect => HIVE_DIALECT_TYPE, + crate::dialect::PyMsSqlDialect => MS_SQL_DIALECT_TYPE, + crate::dialect::PyMySqlDialect => MYSQL_DIALECT_TYPE, + crate::dialect::PyOracleDialect => ORACLE_DIALECT_TYPE, + crate::dialect::PyPostgreSqlDialect => POSTGRESQL_DIALECT_TYPE, + crate::dialect::PyRedshiftDialect => REDSHIFT_DIALECT_TYPE, + crate::dialect::PySnowflakeDialect => SNOWFLAKE_DIALECT_TYPE, + crate::dialect::PySparkDialect => SPARK_DIALECT_TYPE, + crate::dialect::PySqliteDialect => SQLITE_DIALECT_TYPE, + crate::dialect::PyTeradataDialect => TERADATA_DIALECT_TYPE, ); STD_DECIMAL_TYPE = look_up_type_object(c"decimal", c"Decimal"); From 9cd5f8d8fa0147058e3535c1250ecb363640b1db Mon Sep 17 00:00:00 2001 From: awolverp Date: Sat, 27 Jun 2026 20:39:57 +0330 Subject: [PATCH 5/5] Add docstrings, rename dialect.SqliteDialect to dialect.SQLiteDialect --- rapidquery/_lib/ast/data_type.pyi | 43 +- rapidquery/_lib/dialect.pyi | 935 +++++++++++++++++++++++++++--- src/ast/data_type/mod.rs | 21 +- src/ast/data_type/serializers.rs | 2 +- src/dialect/bigquery.rs | 1 + src/dialect/databricks.rs | 3 + src/dialect/duckdb.rs | 1 + src/dialect/hive.rs | 1 + src/dialect/mod.rs | 60 +- src/dialect/mssql.rs | 1 + src/dialect/mysql.rs | 1 + src/dialect/oracle.rs | 1 + src/dialect/postgresql.rs | 1 + src/dialect/redshift.rs | 1 + src/dialect/snowflake.rs | 1 + src/dialect/spark.rs | 3 + src/dialect/sqlite.rs | 7 +- src/dialect/teradata.rs | 1 + src/typeref.rs | 2 +- tests/ast/test_data_type.py | 0 tests/mixin.py | 70 --- tests/test_common.py | 343 ----------- tests/test_dialect.py | 1 + tests/test_query.py | 427 -------------- tests/test_schema.py | 272 --------- tests/test_sqltypes.py | 262 --------- 26 files changed, 983 insertions(+), 1478 deletions(-) create mode 100644 tests/ast/test_data_type.py delete mode 100644 tests/mixin.py delete mode 100644 tests/test_common.py create mode 100644 tests/test_dialect.py delete mode 100644 tests/test_query.py delete mode 100644 tests/test_schema.py delete mode 100644 tests/test_sqltypes.py diff --git a/rapidquery/_lib/ast/data_type.pyi b/rapidquery/_lib/ast/data_type.pyi index 04824be..add69f2 100644 --- a/rapidquery/_lib/ast/data_type.pyi +++ b/rapidquery/_lib/ast/data_type.pyi @@ -1,4 +1,4 @@ -from rapidquery._lib.dialect import BigQueryDialect, DatabricksDialect, DuckDbDialect, HiveDialect, MsSqlDialect, MySqlDialect, OracleDialect, PostgreSqlDialect, RedshiftDialect, SnowflakeDialect, SparkDialect, SqliteDialect, TeradataDialect +from rapidquery._lib.dialect import BigQueryDialect, DatabricksDialect, DuckDbDialect, HiveDialect, MsSqlDialect, MySqlDialect, OracleDialect, PostgreSqlDialect, RedshiftDialect, SQLiteDialect, SnowflakeDialect, SparkDialect, TeradataDialect from typing import Any, Callable, final @final @@ -7,7 +7,7 @@ class ArraySerializer: Serializes a Python `list`/`tuple` by serializing each element with `element` and wrapping in `ARRAY[...]`. """ - def __new__(cls, /, element: 'ArraySerializer' |'BitSerializer' |'BooleanSerializer' |'BytesSerializer' |'DateSerializer' |'DatetimeSerializer' |'FloatSerializer' |'InetSerializer' |'IntegerSerializer' |'JSONSerializer' |'MacAddressSerializer' |'StringSerializer' |'TimeSerializer' |'UnspecifiedSerializer' |'UuidSerializer' |Callable[[BigQueryDialect |DatabricksDialect |DuckDbDialect |HiveDialect |MsSqlDialect |MySqlDialect |OracleDialect |PostgreSqlDialect |RedshiftDialect |SnowflakeDialect |SparkDialect |SqliteDialect |TeradataDialect, str], str]) -> ArraySerializer: ... + def __new__(cls, /, element: 'ArraySerializer' |'BitSerializer' |'BooleanSerializer' |'BytesSerializer' |'DateSerializer' |'DatetimeSerializer' |'FloatSerializer' |'InetSerializer' |'IntegerSerializer' |'JSONSerializer' |'MacAddressSerializer' |'StringSerializer' |'TimeSerializer' |'UnspecifiedSerializer' |'UuidSerializer' |Callable[[BigQueryDialect |DatabricksDialect |DuckDbDialect |HiveDialect |MsSqlDialect |MySqlDialect |OracleDialect |PostgreSqlDialect |RedshiftDialect |SnowflakeDialect |SparkDialect |SQLiteDialect |TeradataDialect, str], str]) -> ArraySerializer: ... @final class BitSerializer: @@ -31,15 +31,42 @@ class BytesSerializer: @final class DataType: """ - SQL data type. - """ - def __new__(cls, /, name: str, serializer: 'ArraySerializer' |'BitSerializer' |'BooleanSerializer' |'BytesSerializer' |'DateSerializer' |'DatetimeSerializer' |'FloatSerializer' |'InetSerializer' |'IntegerSerializer' |'JSONSerializer' |'MacAddressSerializer' |'StringSerializer' |'TimeSerializer' |'UnspecifiedSerializer' |'UuidSerializer' |Callable[[BigQueryDialect |DatabricksDialect |DuckDbDialect |HiveDialect |MsSqlDialect |MySqlDialect |OracleDialect |PostgreSqlDialect |RedshiftDialect |SnowflakeDialect |SparkDialect |SqliteDialect |TeradataDialect, str], str] |None = None) -> DataType: ... + Reperesents a SQL data type. It includes a name, and a serializer. + + The name of a SQL data type can be any string; it is not limited to a set of predefined values. + Additionally, the serializer for each data type is fully customizable. + """ + def __new__(cls, /, name: str, serializer: 'ArraySerializer' |'BitSerializer' |'BooleanSerializer' |'BytesSerializer' |'DateSerializer' |'DatetimeSerializer' |'FloatSerializer' |'InetSerializer' |'IntegerSerializer' |'JSONSerializer' |'MacAddressSerializer' |'StringSerializer' |'TimeSerializer' |'UnspecifiedSerializer' |'UuidSerializer' |Callable[[BigQueryDialect |DatabricksDialect |DuckDbDialect |HiveDialect |MsSqlDialect |MySqlDialect |OracleDialect |PostgreSqlDialect |RedshiftDialect |SnowflakeDialect |SparkDialect |SQLiteDialect |TeradataDialect, str], str] |None = None) -> DataType: + """ + Creates a new SQL data type. + + Args: + name: The name of a data type. it is not limited to a set of predefined values. + serializer: The `serializer` is used whenever a Python object needs to be converted + into a valid SQL value during query construction. This is commonly required + when setting default values, inserting data, or generating other SQL statements. + You can use one of the built-in serializers (we provide ready-to-use + serializers for most common data types), or you can create and register your + own custom serializer. This parameter is optional. However, it is strongly + recommended to always specify it explicitly. If you do not provide a serializer, + the `DataType` will attempt to automatically detect and assign one based on the + `name`. If no suitable serializer is found, it will fall back to `UnspecifiedSerializer`. + """ def __repr__(self, /) -> str: ... @property - def name(self, /) -> str: ... - def serialize(self, /, dialect: BigQueryDialect |DatabricksDialect |DuckDbDialect |HiveDialect |MsSqlDialect |MySqlDialect |OracleDialect |PostgreSqlDialect |RedshiftDialect |SnowflakeDialect |SparkDialect |SqliteDialect |TeradataDialect |type[BigQueryDialect] |type[DatabricksDialect] |type[DuckDbDialect] |type[HiveDialect] |type[MsSqlDialect] |type[MySqlDialect] |type[OracleDialect] |type[PostgreSqlDialect] |type[RedshiftDialect] |type[SnowflakeDialect] |type[SparkDialect] |type[SqliteDialect] |type[TeradataDialect], value: Any) -> str: ... + def name(self, /) -> str: + """ + Returns data type name. + """ + def serialize(self, /, dialect: BigQueryDialect |DatabricksDialect |DuckDbDialect |HiveDialect |MsSqlDialect |MySqlDialect |OracleDialect |PostgreSqlDialect |RedshiftDialect |SnowflakeDialect |SparkDialect |SQLiteDialect |TeradataDialect |type[BigQueryDialect] |type[DatabricksDialect] |type[DuckDbDialect] |type[HiveDialect] |type[MsSqlDialect] |type[MySqlDialect] |type[OracleDialect] |type[PostgreSqlDialect] |type[RedshiftDialect] |type[SnowflakeDialect] |type[SparkDialect] |type[SQLiteDialect] |type[TeradataDialect], value: Any) -> str: + """ + Uses configured serializer to serialize `value` to SQL, depend on the specified `dialect`. + """ @property - def serializer(self, /) -> Any: ... + def serializer(self, /) -> Any: + """ + Returns data type serializer. + """ @final class DateSerializer: diff --git a/rapidquery/_lib/dialect.pyi b/rapidquery/_lib/dialect.pyi index bff5f00..07bd358 100644 --- a/rapidquery/_lib/dialect.pyi +++ b/rapidquery/_lib/dialect.pyi @@ -3,130 +3,901 @@ from typing import final @final class BigQueryDialect: + """ + A dialect for [Google Bigquery](https://cloud.google.com/bigquery/) + """ def __new__(cls, /) -> BigQueryDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class DatabricksDialect: + """ + A dialect for [Databricks SQL](https://www.databricks.com/) + + See . + """ def __new__(cls, /) -> DatabricksDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class DuckDbDialect: + """ + A dialect for [DuckDB](https://duckdb.org/) + """ def __new__(cls, /) -> DuckDbDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class HiveDialect: + """ + A [`Dialect`] for [Hive](https://hive.apache.org/). + """ def __new__(cls, /) -> HiveDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class MsSqlDialect: + """ + A dialect for [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/) + """ def __new__(cls, /) -> MsSqlDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class MySqlDialect: + """ + A dialect for [MySQL](https://www.mysql.com/) + """ def __new__(cls, /) -> MySqlDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class OracleDialect: + """ + A [`Dialect`] for [Oracle Databases](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/index.html) + """ def __new__(cls, /) -> OracleDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class PostgreSqlDialect: + """ + A dialect for [PostgreSQL](https://www.postgresql.org/) + """ def __new__(cls, /) -> PostgreSqlDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class RedshiftDialect: + """ + A dialect for [RedShift](https://aws.amazon.com/redshift/) + """ def __new__(cls, /) -> RedshiftDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ + +@final +class SQLiteDialect: + """ + A dialect for [SQLite](https://www.sqlite.org) + """ + def __new__(cls, /) -> SQLiteDialect: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class SnowflakeDialect: + """ + A dialect for [Snowflake](https://www.snowflake.com/) + """ def __new__(cls, /) -> SnowflakeDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class SparkDialect: + """ + A [`Dialect`] for [Apache Spark SQL](https://spark.apache.org/docs/latest/sql-ref.html). + + See . + """ def __new__(cls, /) -> SparkDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... - -@final -class SqliteDialect: - def __new__(cls, /) -> SqliteDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ @final class TeradataDialect: + """ + A Dialect for [Teradata](https://docs.teradata.com/). + """ def __new__(cls, /) -> TeradataDialect: ... - def encode_bits(self, /, bits: str) -> str: ... - def encode_bytes(self, /, bytes: Sequence[int]) -> str: ... - def escape_string(self, /, s: str) -> str: ... - def get_identifier_quotes(self, /) -> tuple[str, str] |None: ... - def get_name(self, /) -> str: ... - def quote_string(self, /, s: str) -> str: ... + def encode_bits(self, /, bits: str) -> str: + """ + Render a byte slice as a literal in the configured form. + + Raises `ValueError` if `bits` includes something other than `0` and `1`. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bits("10101010001") == "1361" + ``` + """ + def encode_bytes(self, /, bytes: Sequence[int]) -> str: + """ + Render a byte slice as a literal in the configured form. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + ``` + """ + def escape_string(self, /, s: str) -> str: + """ + Escapes string literals. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + ``` + """ + def get_identifier_quotes(self, /) -> tuple[str, str] |None: + """ + Returns the start/end characters used to quote identifiers. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_identifier_quotes() == ('"', '"') + ``` + """ + def get_name(self, /) -> str: + """ + Returns dialect name + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.get_name() == "SQLite" + ``` + """ + def quote_string(self, /, s: str) -> str: + """ + Quotes string literals. this method DOES NOT escape characters. + + Example: + ```python + dialect = SQLiteDialect() + assert dialect.quote_string("string") == "'string'" + ``` + """ diff --git a/src/ast/data_type/mod.rs b/src/ast/data_type/mod.rs index d4ee393..53c28e3 100644 --- a/src/ast/data_type/mod.rs +++ b/src/ast/data_type/mod.rs @@ -10,7 +10,10 @@ use crate::dialect::DialectUnion; use crate::internal::alias; implement_pyclass! { - /// SQL data type. + /// Reperesents a SQL data type. It includes a name, and a serializer. + /// + /// The name of a SQL data type can be any string; it is not limited to a set of predefined values. + /// Additionally, the serializer for each data type is fully customizable. #[derive(Debug, Clone)] [skip_from_py_object] PyDataType as "DataType" { pub name: DataTypeName, @@ -20,6 +23,19 @@ implement_pyclass! { #[pyo3::pymethods] impl PyDataType { + /// Creates a new SQL data type. + /// + /// Args: + /// name: The name of a data type. it is not limited to a set of predefined values. + /// serializer: The `serializer` is used whenever a Python object needs to be converted + /// into a valid SQL value during query construction. This is commonly required + /// when setting default values, inserting data, or generating other SQL statements. + /// You can use one of the built-in serializers (we provide ready-to-use + /// serializers for most common data types), or you can create and register your + /// own custom serializer. This parameter is optional. However, it is strongly + /// recommended to always specify it explicitly. If you do not provide a serializer, + /// the `DataType` will attempt to automatically detect and assign one based on the + /// `name`. If no suitable serializer is found, it will fall back to `UnspecifiedSerializer`. #[new] #[pyo3(signature = (name, serializer=None) )] fn __new__(name: String, serializer: Option) -> Self { @@ -33,16 +49,19 @@ impl PyDataType { Self { name, serializer } } + /// Returns data type name. #[getter] fn name(&self) -> String { self.name.to_string() } + /// Returns data type serializer. #[getter] fn serializer<'a>(&self, py: pyo3::Python<'a>) -> pyo3::PyResult> { self.serializer.to_pyobject(py) } + /// Uses configured serializer to serialize `value` to SQL, depend on the specified `dialect`. fn serialize( &self, py: pyo3::Python, diff --git a/src/ast/data_type/serializers.rs b/src/ast/data_type/serializers.rs index 2dd437d..5140147 100644 --- a/src/ast/data_type/serializers.rs +++ b/src/ast/data_type/serializers.rs @@ -257,7 +257,7 @@ impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for DataTypeSerializerUnion { type_hint_identifier!("rapidquery._lib.dialect", "RedshiftDialect"), type_hint_identifier!("rapidquery._lib.dialect", "SnowflakeDialect"), type_hint_identifier!("rapidquery._lib.dialect", "SparkDialect"), - type_hint_identifier!("rapidquery._lib.dialect", "SqliteDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "SQLiteDialect"), type_hint_identifier!("rapidquery._lib.dialect", "TeradataDialect") ), type_hint_identifier!("str") diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs index f391edf..aa7195d 100644 --- a/src/dialect/bigquery.rs +++ b/src/dialect/bigquery.rs @@ -8,6 +8,7 @@ // Ref: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical implement_pyclass! { + /// A dialect for [Google Bigquery](https://cloud.google.com/bigquery/) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PyBigQueryDialect as "BigQueryDialect"; } diff --git a/src/dialect/databricks.rs b/src/dialect/databricks.rs index de1b721..ff24c5b 100644 --- a/src/dialect/databricks.rs +++ b/src/dialect/databricks.rs @@ -7,6 +7,9 @@ // Ref: https://docs.databricks.com/en/sql/language-manual/sql-ref-literals.html implement_pyclass! { + /// A dialect for [Databricks SQL](https://www.databricks.com/) + /// + /// See . #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PyDatabricksDialect as "DatabricksDialect"; } diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index ee5bdd2..470b7c6 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -7,6 +7,7 @@ // Ref: https://duckdb.org/docs/sql/data_types/literal_types implement_pyclass! { + /// A dialect for [DuckDB](https://duckdb.org/) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PyDuckDbDialect as "DuckDbDialect"; } diff --git a/src/dialect/hive.rs b/src/dialect/hive.rs index c9a9e65..930f5fd 100644 --- a/src/dialect/hive.rs +++ b/src/dialect/hive.rs @@ -7,6 +7,7 @@ // Ref: https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types implement_pyclass! { + /// A [`Dialect`] for [Hive](https://hive.apache.org/). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PyHiveDialect as "HiveDialect"; } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 47ad382..e02d4df 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -34,7 +34,7 @@ pub trait Dialect { /// Escapes string literals. fn escape_string(&self, string: &str) -> String; - /// (zero-copy) Quotes string literals. + /// Quotes string literals. /// /// You should call [`Dialect::escape`] before this method. fn quote_string(&self, string: &mut String); @@ -90,27 +90,71 @@ macro_rules! implement_dialect_pymethods { Self } + /// Returns dialect name + /// + /// Example: + /// ```python + /// dialect = SQLiteDialect() + /// assert dialect.get_name() == "SQLite" + /// ``` fn get_name(&self) -> &'static str { super::Dialect::name(self) } + /// Returns the start/end characters used to quote identifiers. + /// + /// Example: + /// ```python + /// dialect = SQLiteDialect() + /// assert dialect.get_identifier_quotes() == ('"', '"') + /// ``` fn get_identifier_quotes(&self) -> Option<(char, char)> { super::Dialect::identifier_quotes(self) } + /// Escapes string literals. + /// + /// Example: + /// ```python + /// dialect = SQLiteDialect() + /// assert dialect.escape_string("A'Wolver'P\0") == "A''Wolver''P" + /// ``` fn escape_string(&self, s: String) -> String { super::Dialect::escape_string(self, &s) } + /// Quotes string literals. this method DOES NOT escape characters. + /// + /// Example: + /// ```python + /// dialect = SQLiteDialect() + /// assert dialect.quote_string("string") == "'string'" + /// ``` fn quote_string(&self, mut s: String) -> String { super::Dialect::quote_string(self, &mut s); s } + /// Render a byte slice as a literal in the configured form. + /// + /// Example: + /// ```python + /// dialect = SQLiteDialect() + /// assert dialect.encode_bytes(b"bytes") == "X'6279746573'" + /// ``` fn encode_bytes(&self, bytes: Vec) -> String { super::Dialect::encode_bytes(self, &bytes) } + /// Render a byte slice as a literal in the configured form. + /// + /// Raises `ValueError` if `bits` includes something other than `0` and `1`. + /// + /// Example: + /// ```python + /// dialect = SQLiteDialect() + /// assert dialect.encode_bits("10101010001") == "1361" + /// ``` fn encode_bits(&self, bits: &str) -> pyo3::PyResult { if !bits.chars().all(|c| c == '0' || c == '1') { return Err(new_py_error!(PyValueError, "invalid input: {}", bits)); @@ -157,7 +201,7 @@ mod spark; pub use spark::PySparkDialect; mod sqlite; -pub use sqlite::PySqliteDialect; +pub use sqlite::PySQLiteDialect; mod teradata; pub use teradata::PyTeradataDialect; @@ -175,7 +219,7 @@ pub enum DialectUnion { Redshift(PyRedshiftDialect), Snowflake(PySnowflakeDialect), Spark(PySparkDialect), - Sqlite(PySqliteDialect), + Sqlite(PySQLiteDialect), Teradata(PyTeradataDialect), } @@ -196,7 +240,7 @@ impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for DialectUnion { type_hint_identifier!("rapidquery._lib.dialect", "RedshiftDialect"), type_hint_identifier!("rapidquery._lib.dialect", "SnowflakeDialect"), type_hint_identifier!("rapidquery._lib.dialect", "SparkDialect"), - type_hint_identifier!("rapidquery._lib.dialect", "SqliteDialect"), + type_hint_identifier!("rapidquery._lib.dialect", "SQLiteDialect"), type_hint_identifier!("rapidquery._lib.dialect", "TeradataDialect"), // as type type_hint_subscript!( @@ -245,7 +289,7 @@ impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for DialectUnion { ), type_hint_subscript!( type_hint_identifier!("type"), - type_hint_identifier!("rapidquery._lib.dialect", "SqliteDialect") + type_hint_identifier!("rapidquery._lib.dialect", "SQLiteDialect") ), type_hint_subscript!( type_hint_identifier!("type"), @@ -301,7 +345,7 @@ impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for DialectUnion { return Ok(Self::Spark(PySparkDialect)); } if type_ptr == crate::typeref::SQLITE_DIALECT_TYPE { - return Ok(Self::Sqlite(PySqliteDialect)); + return Ok(Self::Sqlite(PySQLiteDialect)); } if type_ptr == crate::typeref::TERADATA_DIALECT_TYPE { return Ok(Self::Teradata(PyTeradataDialect)); @@ -371,7 +415,7 @@ pub mod dialect_module { #[pymodule_export] pub use super::{ PyBigQueryDialect, PyDatabricksDialect, PyDuckDbDialect, PyHiveDialect, PyMsSqlDialect, - PyMySqlDialect, PyOracleDialect, PyPostgreSqlDialect, PyRedshiftDialect, - PySnowflakeDialect, PySparkDialect, PySqliteDialect, PyTeradataDialect, + PyMySqlDialect, PyOracleDialect, PyPostgreSqlDialect, PyRedshiftDialect, PySQLiteDialect, + PySnowflakeDialect, PySparkDialect, PyTeradataDialect, }; } diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index be92b9d..c94a8a8 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -8,6 +8,7 @@ // Ref: https://learn.microsoft.com/en-us/sql/t-sql/data-types/constants-transact-sql implement_pyclass! { +/// A dialect for [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PyMsSqlDialect as "MsSqlDialect"; } diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs index eadfcdf..ff557e8 100644 --- a/src/dialect/mysql.rs +++ b/src/dialect/mysql.rs @@ -8,6 +8,7 @@ // https://dev.mysql.com/doc/refman/8.0/en/bit-value-literals.html implement_pyclass! { + /// A dialect for [MySQL](https://www.mysql.com/) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PyMySqlDialect as "MySqlDialect"; } diff --git a/src/dialect/oracle.rs b/src/dialect/oracle.rs index 602d6e2..be4bede 100644 --- a/src/dialect/oracle.rs +++ b/src/dialect/oracle.rs @@ -9,6 +9,7 @@ // We default to 1/0 for broadest compatibility. implement_pyclass! { + /// A [`Dialect`] for [Oracle Databases](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/index.html) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PyOracleDialect as "OracleDialect"; } diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index 3190391..15c8873 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -10,6 +10,7 @@ // https://www.postgresql.org/docs/current/datatype-bit.html implement_pyclass! { + /// A dialect for [PostgreSQL](https://www.postgresql.org/) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PyPostgreSqlDialect as "PostgreSqlDialect"; } diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs index 6ed551d..d592650 100644 --- a/src/dialect/redshift.rs +++ b/src/dialect/redshift.rs @@ -10,6 +10,7 @@ // https://docs.aws.amazon.com/redshift/latest/dg/r_VARBYTE_type.html implement_pyclass! { + /// A dialect for [RedShift](https://aws.amazon.com/redshift/) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PyRedshiftDialect as "RedshiftDialect"; } diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 34a4c08..1127bd6 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -8,6 +8,7 @@ // https://docs.snowflake.com/en/sql-reference/literals implement_pyclass! { + /// A dialect for [Snowflake](https://www.snowflake.com/) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PySnowflakeDialect as "SnowflakeDialect"; } diff --git a/src/dialect/spark.rs b/src/dialect/spark.rs index 62f335b..9c01374 100644 --- a/src/dialect/spark.rs +++ b/src/dialect/spark.rs @@ -7,6 +7,9 @@ // Ref: https://spark.apache.org/docs/latest/sql-ref-literals.html implement_pyclass! { + /// A [`Dialect`] for [Apache Spark SQL](https://spark.apache.org/docs/latest/sql-ref.html). + /// + /// See . #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PySparkDialect as "SparkDialect"; } diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index 60802ca..7cea479 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -8,11 +8,12 @@ // Ref: https://www.sqlite.org/lang_expr.html implement_pyclass! { + /// A dialect for [SQLite](https://www.sqlite.org) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - [skip_from_py_object] PySqliteDialect as "SqliteDialect"; + [skip_from_py_object] PySQLiteDialect as "SQLiteDialect"; } -impl super::Dialect for PySqliteDialect { +impl super::Dialect for PySQLiteDialect { fn name(&self) -> &'static str { "SQLite" } @@ -50,4 +51,4 @@ impl super::Dialect for PySqliteDialect { } } -super::implement_dialect_pymethods!(PySqliteDialect); +super::implement_dialect_pymethods!(PySQLiteDialect); diff --git a/src/dialect/teradata.rs b/src/dialect/teradata.rs index d6a4766..709a3d6 100644 --- a/src/dialect/teradata.rs +++ b/src/dialect/teradata.rs @@ -10,6 +10,7 @@ // Ref: https://docs.teradata.com/r/Teradata-VantageCloud-Lake/SQL-Fundamentals/SQL-Data-Types-and-Literals implement_pyclass! { + /// A Dialect for [Teradata](https://docs.teradata.com/). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] [skip_from_py_object] PyTeradataDialect as "TeradataDialect"; } diff --git a/src/typeref.rs b/src/typeref.rs index 88b6b10..42903eb 100644 --- a/src/typeref.rs +++ b/src/typeref.rs @@ -100,7 +100,7 @@ fn _initialize_typeref(py: pyo3::Python) { crate::dialect::PyRedshiftDialect => REDSHIFT_DIALECT_TYPE, crate::dialect::PySnowflakeDialect => SNOWFLAKE_DIALECT_TYPE, crate::dialect::PySparkDialect => SPARK_DIALECT_TYPE, - crate::dialect::PySqliteDialect => SQLITE_DIALECT_TYPE, + crate::dialect::PySQLiteDialect => SQLITE_DIALECT_TYPE, crate::dialect::PyTeradataDialect => TERADATA_DIALECT_TYPE, ); diff --git a/tests/ast/test_data_type.py b/tests/ast/test_data_type.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/mixin.py b/tests/mixin.py deleted file mode 100644 index 6351bdc..0000000 --- a/tests/mixin.py +++ /dev/null @@ -1,70 +0,0 @@ -import typing - -import rapidquery as rq - - -class TableNameInitMixin: - table_name_instance: typing.Callable - - def test_table_name_init(self): - class Prop: - @property - def __table_name__(self): - return "world.public.users" - - class Var: - __table_name__ = "world.public.users" - - self.table_name_instance(rq.TableName("users", "public", "world")) - self.table_name_instance("world.public.users") - self.table_name_instance(Prop()) - self.table_name_instance(Var) - - -class ReturningMixin: - def get_statement(self): - raise NotImplementedError - - def test_returning(self): - stmt = self.get_statement().returning(rq.Returning("id")) - assert 'RETURNING "id"' in stmt.to_sql("postgres") - - stmt = self.get_statement().returning(rq.Returning.all()) - assert "RETURNING *" in stmt.to_sql("postgres") - - -class OrderByMixin: - def get_statement(self): - raise NotImplementedError - - def test_order_by(self): - stmt = self.get_statement().order_by(rq.Ordering("id", "DESC")) - assert 'ORDER BY "id" DESC' in stmt.to_sql("postgres") - - def test_clear_order_by(self): - stmt = self.get_statement().order_by(rq.Ordering("id")) - assert "ORDER BY" in stmt.to_sql("mysql") - - stmt = stmt.clear_order_by() - assert "ORDER BY" not in stmt.to_sql("mysql") - - -class WhereMixin: - def get_statement(self): - raise NotImplementedError - - def test_clear_where(self): - stmt = self.get_statement().where(rq.Expr.val(True)) - assert "WHERE" in stmt.to_sql("mysql") - - stmt = stmt.clear_where() - assert "WHERE" not in stmt.to_sql("mysql") - - def test_where(self): - stmt = ( - self.get_statement() - .where(rq.Expr.col("id") > 10) - .where(rq.Expr.col("name") < 20) - .to_sql("sqlite") - ) - assert 'WHERE "id" > 10 AND "name" < 20' in stmt diff --git a/tests/test_common.py b/tests/test_common.py deleted file mode 100644 index c1dce08..0000000 --- a/tests/test_common.py +++ /dev/null @@ -1,343 +0,0 @@ -import pytest - -import rapidquery as rq - - -class TestColumn: - def test_init(self): - rq.Column("id", rq.sqltypes.Integer()) - - with pytest.raises(TypeError): - rq.Column("id", rq.sqltypes.Integer) # type: ignore - - with pytest.raises(TypeError): - rq.Column("id", "INTEGER") # type: ignore - - rq.Column("id", rq.sqltypes.String(), default="") - rq.Column("id", rq.sqltypes.String(), default=None) - rq.Column("id", rq.sqltypes.String(), default=rq.Expr.val(232)) - rq.Column("id", rq.sqltypes.String(), default=None, generated=232) - - with pytest.raises(TypeError): - rq.Column("id", rq.sqltypes.String(), default=3243) - - col = rq.Column( - "id", - rq.sqltypes.Float(), - primary_key=True, - auto_increment=True, - nullable=False, - extra="HELLO", - comment="WOW", - default=4.3, - generated=98, - stored_generated=True, - ) - assert col.name == "id" - assert type(col.type) is rq.sqltypes.Float - assert col.primary_key - assert col.auto_increment - assert col.nullable is False - assert col.extra == "HELLO" - assert col.comment == "WOW" - assert type(col.default) is rq.Expr - assert type(col.generated) is rq.Expr - assert col.stored_generated - - def test_adapt(self): - col = rq.Column("name", rq.sqltypes.String()) - col.adapt("Ali") - col.adapt(None) - - with pytest.raises(TypeError): - col.adapt(4) # type: ignore - - def test_column_ref_property(self): - col = rq.Column("name", rq.sqltypes.String()) - rq.AlterTableDropColumnOption(col) - - def test_label(self): - col = rq.Column("name", rq.sqltypes.String()) - label = col.label("a") - assert type(label) is rq.SelectLabel - assert label.alias == "a" - assert label.window is None - - def test_to_expr(self): - col = rq.Column("name", rq.sqltypes.String()) - assert type(col.to_expr()) is rq.Expr - - -class TestColumnRef: - def test_new(self): - ref_1 = rq.ColumnRef("id") - assert ref_1.name == "id" - assert ref_1.table is None - assert ref_1.schema is None - - ref_2 = rq.ColumnRef("id", "characters", "public") - assert ref_2.name == "id" - assert ref_2.table == "characters" - assert ref_2.schema == "public" - - ref_3 = rq.ColumnRef.parse("public.characters.id") - assert ref_3.name == "id" - assert ref_3.table == "characters" - assert ref_3.schema == "public" - - assert ref_2 == ref_3 - assert ref_1 != ref_2 - - asterisk_1 = rq.ColumnRef("*") - assert asterisk_1.name == "*" - assert asterisk_1.table is None - assert asterisk_1.schema is None - - asterisk_2 = rq.ColumnRef("*", "characters") - assert asterisk_2.name == "*" - assert asterisk_2.table == "characters" - assert asterisk_2.schema is None - - asterisk_3 = rq.ColumnRef.parse("characters.*") - assert asterisk_3.name == "*" - assert asterisk_3.table == "characters" - assert asterisk_3.schema is None - - assert asterisk_2 == asterisk_3 - assert asterisk_1 != asterisk_2 - - def test_copy_with(self): - ref_1 = rq.ColumnRef("id") - assert ref_1.name == "id" - assert ref_1.table is None - assert ref_1.schema is None - - ref_2 = ref_1.copy_with(table="characters", schema="public") - assert ref_2.name == "id" - assert ref_2.table == "characters" - assert ref_2.schema == "public" - - ref_3 = ref_2.copy_with(name="*") - assert ref_3.name == "*" - assert ref_3.table == "characters" - assert ref_3.schema == "public" - - def test_try_from(self): - class Prop: - @property - def __column_ref__(self): - return "characters.id" - - class Var: - __column_ref__ = "characters.id" - - rq.Expr.col("characters.id") - rq.Expr.col(rq.ColumnRef("id", "characters")) - rq.Expr.col(Prop()) - rq.Expr.col(Var) - - def test_to_expr(self): - ref = rq.ColumnRef("id") - assert type(ref.to_expr()) is rq.Expr - - def test_label(self): - ref = rq.ColumnRef("id") - label = ref.label("a") - assert type(label) is rq.SelectLabel - assert label.alias == "a" - assert label.window is None - - -class SelectStatementChild(rq.SelectStatement): - pass - - -class TestExpr: - def test_new(self): - rq.Expr(rq.Expr.custom("WOW")) - rq.Expr(rq.Value(None)) - rq.Expr(rq.ColumnRef("id")) - rq.Expr(rq.Func("NOW")) - rq.Expr(rq.SelectStatement().columns("id")) - rq.Expr(SelectStatementChild().columns("id")) - rq.Expr(rq.CaseStatement().when(rq.Expr.col("aspect").in_([2, 4]), True).else_(False)) - - rq.Expr((rq.Expr.custom("TUPLE"), rq.Expr.custom("TUPLE"))) - - class Prop: - @property - def __expr__(self): - return rq.Expr.custom("PROPERTY") - - rq.Expr(Prop()) - - class ColumnRefProp: - @property - def __column_ref__(self): - return "characters.id" - - rq.Expr(ColumnRefProp()) - - rq.Expr(1) - rq.Expr("a") - rq.Expr(3.4) - rq.Expr(None) - rq.Expr(["a", "b"]) - - def test_val(self): - rq.Expr.val(1) - rq.Expr.val(1.4) - rq.Expr.val("a") - rq.Expr.val(None) - - with pytest.raises(TypeError): - rq.Expr.val("a", rq.sqltypes.Integer()) # type: ignore - - def test_exists(self): - with pytest.raises(TypeError): - rq.Expr.exists(1) # type: ignore - - stmt = rq.SelectStatement().columns("id") - assert "EXISTS" in rq.Expr.exists(stmt)._to_sql("postgres") - - stmt = SelectStatementChild().columns("id") - assert "EXISTS" in rq.Expr.exists(stmt)._to_sql("postgres") - - def test_all(self): - with pytest.raises(TypeError): - rq.Expr.all(1) # type: ignore - - stmt = rq.SelectStatement().columns("id") - assert "ALL" in rq.Expr.all(stmt)._to_sql("postgres") - - stmt = SelectStatementChild().columns("id") - assert "ALL" in rq.Expr.all(stmt)._to_sql("postgres") - - def test_any(self): - with pytest.raises(TypeError): - rq.Expr.any(1) # type: ignore - - stmt = rq.SelectStatement().columns("id") - assert "ANY" in rq.Expr.any(stmt)._to_sql("postgres") - - stmt = SelectStatementChild().columns("id") - assert "ANY" in rq.Expr.any(stmt)._to_sql("postgres") - - def test_some(self): - with pytest.raises(TypeError): - rq.Expr.some(1) # type: ignore - - stmt = rq.SelectStatement().columns("id") - assert "SOME" in rq.Expr.some(stmt)._to_sql("postgres") - - stmt = SelectStatementChild().columns("id") - assert "SOME" in rq.Expr.some(stmt)._to_sql("postgres") - - -class TestForeignKey: - def test_init(self): - rq.ForeignKey( - ["font_id"], - ["id"], - "fonts", - "fk_name", - on_delete="CASCADE", - on_update="NO ACTION", - ) - fk = rq.ForeignKey(["font_id"], ["fonts.id"]) - assert fk.to_table.name == "fonts" - - rq.ForeignKey(["font_id", "font_name"], ["fonts.id", "fonts.name"]) - - with pytest.raises(ValueError): - rq.ForeignKey(["id_1", "id_2"], ["t1.id", "t2.id"]) - - with pytest.raises(ValueError): - rq.ForeignKey(["1", "2"], ["1"]) - - with pytest.raises(ValueError): - rq.ForeignKey(["font_id"], ["fonts.id"], on_delete="AAA") # type: ignore - - with pytest.raises(ValueError): - rq.ForeignKey(["font_id"], ["fonts.id"], on_update="AAA") # type: ignore - - -class TestFunc: - def test_new(self): - rq.Func("CUSTOM", 1, 2) - - -class TestTableName: - def test_new(self): - t_1 = rq.TableName("users") - assert t_1.name == "users" - assert t_1.schema is None - assert t_1.database is None - assert t_1.alias is None - - t_2 = rq.TableName("users", "public", "world") - assert t_2.name == "users" - assert t_2.schema == "public" - assert t_2.database == "world" - assert t_2.alias is None - - t_3 = rq.TableName.parse("world.public.users") - assert t_3.name == "users" - assert t_3.schema == "public" - assert t_3.database == "world" - assert t_3.alias is None - - assert t_3 == t_2 - assert t_3 != t_1 - - def test_copy_with(self): - t_1 = rq.TableName("users") - assert t_1.name == "users" - assert t_1.schema is None - assert t_1.database is None - assert t_1.alias is None - - t_2 = t_1.copy_with(schema="public", database="world") - assert t_2.name == "users" - assert t_2.schema == "public" - assert t_2.database == "world" - assert t_2.alias is None - - t_3 = t_2.copy_with(database=None, alias="t_3") - assert t_3.name == "users" - assert t_3.schema == "public" - assert t_3.database is None - assert t_3.alias == "t_3" - - def test_try_from(self): - class Prop: - @property - def __table_name__(self): - return "world.public.users" - - class Var: - __table_name__ = "world.public.users" - - stmt = rq.AlterTable(rq.TableName("users", "public", "world")) - assert stmt.name == rq.TableName("users", "public", "world") - - stmt = rq.AlterTable("world.public.users") - assert stmt.name == rq.TableName("users", "public", "world") - - stmt = rq.AlterTable(Prop()) - assert stmt.name == rq.TableName("users", "public", "world") - - stmt = rq.AlterTable(Var) - assert stmt.name == rq.TableName("users", "public", "world") - - stmt = rq.AlterTable("world.public.users as t1") - assert stmt.name == rq.TableName("users", "public", "world", "t1") - - stmt = rq.AlterTable("world.public.users AS t1") - assert stmt.name == rq.TableName("users", "public", "world", "t1") - - stmt = rq.AlterTable("world.public.users As t1") - assert stmt.name == rq.TableName("users", "public", "world", "t1") - - stmt = rq.AlterTable("world.public.users As s t1") - assert stmt.name == rq.TableName("users", "public", "world", "s t1") diff --git a/tests/test_dialect.py b/tests/test_dialect.py new file mode 100644 index 0000000..976671b --- /dev/null +++ b/tests/test_dialect.py @@ -0,0 +1 @@ +from rapidquery._lib import dialect diff --git a/tests/test_query.py b/tests/test_query.py deleted file mode 100644 index 1b1564b..0000000 --- a/tests/test_query.py +++ /dev/null @@ -1,427 +0,0 @@ -import pytest - -import rapidquery as rq - -from .mixin import OrderByMixin, ReturningMixin, TableNameInitMixin, WhereMixin - - -class TestCaseStatement: - def test_init(self): - rq.CaseStatement() - - def test_when_else(self): - stmt = ( - rq.CaseStatement().when(rq.Expr.col("id") == 1, 1).when(rq.Expr.col("name") == "ali", 1) - ) - sql = rq.SelectStatement(rq.SelectLabel(stmt, "test")).to_sql("postgres") - assert '"id"' in sql - assert '"name"' in sql - assert '"test"' in sql - - stmt = ( - rq.CaseStatement().when(rq.Expr.col("id") == 1, 1).when(rq.Expr.col("name") == "ali", 1) - ) - sql = rq.SelectStatement(stmt.label("test")).to_sql("postgres") - assert '"id"' in sql - assert '"name"' in sql - assert '"test"' in sql - - with pytest.raises(TypeError): - rq.CaseStatement().when("Ali", 1) # type: ignore - - rq.CaseStatement().when(rq.Expr("Ali"), 1).else_(1) - - -class TestDeleteStatement(TableNameInitMixin, ReturningMixin, WhereMixin, OrderByMixin): - table_name_instance = rq.DeleteStatement - - def get_statement(self): - return rq.DeleteStatement("users") - - def test_from_table(self): - stmt = rq.DeleteStatement("users") - assert '"users"' in stmt.to_sql("postgres") - - stmt = stmt.from_table("fonts") - assert '"users"' not in stmt.to_sql("postgres") - assert '"fonts"' in stmt.to_sql("postgres") - - def test_limit(self): - stmt = rq.DeleteStatement("users").limit(20) - assert "LIMIT" in stmt.to_sql("postgres") - - -class TestInsertStatement(TableNameInitMixin, ReturningMixin): - table_name_instance = rq.InsertStatement - - def get_statement(self): - return rq.InsertStatement("users") - - def test_replace(self): - stmt = rq.InsertStatement("public.users").replace().to_sql("postgres") - assert "INSERT" not in stmt - assert "REPLACE" in stmt - - def test_columns(self): - stmt = rq.InsertStatement("users").columns("id", "name") - assert '"id"' in stmt.to_sql("postgres") - assert '"name"' in stmt.to_sql("postgres") - - stmt.columns("created_at") - assert '"created_at"' in stmt.to_sql("postgres") - assert '"id"' not in stmt.to_sql("postgres") - assert '"name"' not in stmt.to_sql("postgres") - - def test_values(self): - with pytest.raises(TypeError): - rq.InsertStatement("users").values(1, a=1) # type: ignore - - with pytest.raises(ValueError): - rq.InsertStatement("users").values(name="ali", id=1).values(name="ali") # type: ignore - - with pytest.raises(ValueError): - rq.InsertStatement("users").columns("id").values(1, 2) - - with pytest.raises(ValueError): - rq.InsertStatement("users").columns("id").values() - - stmt = rq.InsertStatement("users").columns("id").values("ali") - assert stmt.to_sql("postgres").count("ali") == 1 - - stmt = rq.InsertStatement("users").columns("id").values("ali").values("ali") - assert stmt.to_sql("postgres").count("ali") == 2 - - stmt = rq.InsertStatement("users").values(name="ali", id=1) - assert "name" in stmt.to_sql("postgres") - assert "id" in stmt.to_sql("postgres") - assert "ali" in stmt.to_sql("postgres") - assert "1" in stmt.to_sql("postgres") - - stmt = rq.InsertStatement("users").values(name="ali", id=1).values(name="ali", id=1) - assert stmt.to_sql("postgres").count("ali") == 2 - assert stmt.to_sql("postgres").count("1") == 2 - - def test_into(self): - stmt = ( - rq.InsertStatement("name_1") - .values(aspect=4.21, image="123") - .into("name_2") - .to_sql("sqlite") - ) - assert "name_2" in stmt - assert "name_1" not in stmt - - def test_or_default_values(self): - stmt = rq.InsertStatement("users").or_default_values() - assert stmt.to_sql("postgres").count("DEFAULT") == 1 - - stmt = rq.InsertStatement("users").or_default_values(4) - assert stmt.to_sql("postgres").count("DEFAULT") == 4 - - stmt = rq.InsertStatement("users").or_default_values(4).values(aspect=3.14) - assert stmt.to_sql("postgres").count("DEFAULT") == 0 - - def test_select_from(self): - valid_cols = rq.SelectStatement().columns("id", "name").from_table("fonts") - invalid_cols = rq.SelectStatement().columns("id").from_table("fonts") - - stmt = rq.InsertStatement("users").columns("id", "name") - - with pytest.raises(ValueError): - stmt.select_from(invalid_cols) - - stmt.select_from(valid_cols) - assert "SELECT" in stmt.to_sql("postgres") - - -class TestOnConflict: - def test_init(self): - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict(rq.OnConflict("id").do_update(image="ex")) - ) - assert 'ON CONFLICT ("id")' in stmt.to_sql("postgres") - assert "DO UPDATE" in stmt.to_sql("postgres") - - def test_action_where(self): - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict( - rq.OnConflict("id") - .do_update(image="ex") - .action_where(rq.Expr.col("aspect").is_null()) - ) - ) - assert "WHERE" in stmt.to_sql("postgres") - - def test_target_where(self): - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict( - rq.OnConflict("id") - .do_update(image="ex") - .target_where(rq.Expr.col("aspect").is_null()) - ) - ) - assert "WHERE" in stmt.to_sql("postgres") - - def test_do_update(self): - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict(rq.OnConflict("id").do_update("aspect", image=rq.Expr(1) + 2)) - ) - assert "DO UPDATE" in stmt.to_sql("postgres") - - def test_do_nothing(self): - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict(rq.OnConflict("id").do_nothing()) - ) - assert "DO NOTHING" in stmt.to_sql("postgres") - - stmt = ( - rq.InsertStatement("glyph") - .values(aspect=3.1415, image="abcd") - .on_conflict(rq.OnConflict("id").do_nothing("id")) - ) - assert "DO NOTHING" in stmt.to_sql("postgres") - - -class TestSelectStatement(WhereMixin, OrderByMixin): - table_name_instance = rq.SelectStatement - - def get_statement(self): - return rq.SelectStatement().columns("character").from_table("characters") - - def test_init(self): - stmt = ( - rq.SelectStatement() - .columns("character", "fonts.name") - .from_table("characters") - .join( - "fonts", - rq.Expr.col("characters.font_id") == rq.Expr.col("fonts.id"), - "LEFT", - ) - .where(rq.Expr.col("size_w").in_((3, 4))) - .where(rq.Expr.col("character").like("A%")) - .to_sql("postgres") - ) - assert 'LEFT JOIN "fonts" ON "characters"."font_id" = "fonts"."id"' in stmt - - stmt = rq.SelectStatement( - rq.SelectLabel(1), - rq.SelectLabel("hello"), - rq.Expr.col("font"), - ).to_sql("postgres") - assert "SELECT 1, 'hello', \"font\"" in stmt - - def test_columns(self): - stmt = ( - rq.SelectStatement() - .columns("character", "size_w", "size_h") - .from_table("characters") - .to_sql("mysql") - ) - assert "`character` AS `character`, `size_w` AS `size_w`, `size_h` AS `size_h`" in stmt - - def test_distinct(self): - stmt = ( - rq.SelectStatement() - .distinct() - .columns("character", "size_w", "size_h") - .from_table("characters") - .to_sql("mysql") - ) - assert "DISTINCT" in stmt - - def test_from_function(self): - stmt = ( - rq.SelectStatement(rq.Expr.asterisk()) - .from_function(rq.Func.random(), "func") - .to_sql("postgres") - ) - assert "FROM RANDOM()" in stmt - - def test_from_subquery(self): - rq.SelectStatement().from_subquery( - rq.SelectStatement().columns("image", "aspect").from_table("glyph"), - "subglyph", - ) - - stmt = rq.SelectStatement() - with pytest.raises(ValueError): - stmt.from_subquery(stmt, "a") - - -class TestUpdateStatement(WhereMixin, OrderByMixin): - table_name_instance = rq.UpdateStatement - - def get_statement(self): - return rq.UpdateStatement("users") - - def test_from_table(self): - stmt = rq.UpdateStatement("archive.users").from_table("public.users").to_sql("postgres") - assert "FROM" in stmt - assert '"public"."users"' in stmt - assert '"archive"."users"' in stmt - - def test_table(self): - stmt = ( - rq.UpdateStatement("name_1") - .values(aspect=1.23, image=123) - .table("name_2") - .to_sql("sqlite") - ) - assert "name_1" not in stmt - assert "name_2" in stmt - - def test_values(self): - stmt = ( - rq.UpdateStatement("glyph") - .values(aspect=1.23, image=123) - .values(font_id=20) - .to_sql("sqlite") - ) - assert "aspect" in stmt - assert "image" in stmt - assert "font_id" in stmt - - -class TestWindowStatement: - def test_basic_partition(self): - """Test a basic OVER clause with PARTITION BY.""" - window = rq.WindowStatement("department").order_by(rq.Ordering("salary", order="DESC")) - - # Using it in a SelectStatement context - stmt = rq.SelectStatement( - rq.Expr.custom("AVG(salary)").label("svg_sal", window) - ).from_table("employees") - - sql = stmt.to_sql("postgres") - assert 'PARTITION BY "department"' in sql - assert 'ORDER BY "salary" DESC' in sql - assert "OVER" in sql - - def test_frame_rows(self): - """Test WINDOW with ROWS framing.""" - window = ( - rq.WindowStatement() - .order_by(rq.Ordering("id")) - .frame("ROWS", rq.Frame.preceding(5), rq.Frame.current_row()) - ) - - stmt = rq.SelectStatement( - rq.Expr.custom("SUM(val)").label("running_sum", window) - ).from_table("data") - - sql = stmt.to_sql("postgres") - assert "ROWS BETWEEN 5PRECEDING AND CURRENT ROW" in sql - - def test_frame_unbounded(self): - """Test WINDOW with UNBOUNDED boundaries.""" - window = rq.WindowStatement().frame( - "RANGE", rq.Frame.unbounded_preceding(), rq.Frame.unbounded_following() - ) - - stmt = rq.SelectStatement( - rq.SelectLabel(rq.Expr.custom("COUNT(*)"), "total", window) - ).from_table("users") - - sql = stmt.to_sql("mysql") - assert "RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING" in sql - - -class TestWith: - def test_simple_cte(self): - """Test a basic CTE using WithClause.""" - cte_query = ( - rq.SelectStatement() - .columns("id", "name") - .from_table("users") - .where(rq.Expr.col("active").is_(True)) - ) - - with_query = ( - rq.WithClause() - .cte("active_users", cte_query) - .query(rq.SelectStatement().columns("*").from_table("active_users")) - ) - - sql = with_query.to_sql("postgres") - assert 'WITH "active_users"' in sql - assert 'SELECT "id" AS "id", "name" AS "name" FROM "users"' in sql - assert 'SELECT * FROM "active_users"' in sql - - def test_multiple_ctes(self): - """Test multiple CTEs in a single WITH clause.""" - q1 = rq.SelectStatement().columns("id").from_table("table_a") - q2 = rq.SelectStatement().columns("id").from_table("table_b") - - with_query = ( - rq.WithClause() - .cte("cte_a", q1) - .cte("cte_b", q2) - .query( - rq.SelectStatement() - .columns("cte_a.id") - .from_table("cte_a") - .join("cte_b", rq.Expr.col("cte_a.id") == rq.Expr.col("cte_b.id")) - ) - ) - - sql = with_query.to_sql("postgres") - assert 'WITH "cte_a"' in sql - assert '"cte_b"' in sql - assert 'JOIN "cte_b"' in sql - - def test_recursive(self): - """Test RECURSIVE WITH clause generation.""" - # A simplified recursive CTE for sequence generation - base_part = rq.SelectStatement(rq.Expr.val(1).label("n")) - recursive_part = ( - rq.SelectStatement((rq.Expr.col("n") + 1).label("n")) - .from_table("nums") - .where(rq.Expr.col("n") < 5) - ) - - # SeaQuery/RapidQuery typically uses Union to combine parts for recursion - union_query = base_part.union(recursive_part) - - with_query = ( - rq.WithClause() - .recursive() - .cte("nums", union_query, columns=["n"]) - .query(rq.SelectStatement().columns("*").from_table("nums")) - ) - - sql = with_query.to_sql("postgres") - assert "WITH RECURSIVE" in sql - assert '"nums" ("n") AS' in sql - - def test_materialized(self): - """Test the MATERIALIZED hint in CTEs (Postgres specific).""" - q = rq.SelectStatement().columns("id").from_table("very_large_table") - - # Materialized True - sql_mat = ( - rq.WithClause() - .cte("cached_data", q, materialized=True) - .query(rq.SelectStatement().from_table("cached_data")) - .to_sql("postgres") - ) - assert "MATERIALIZED" in sql_mat - - # Materialized False (NOT MATERIALIZED) - sql_not_mat = ( - rq.WithClause() - .cte("raw_data", q, materialized=False) - .query(rq.SelectStatement().from_table("raw_data")) - .to_sql("postgres") - ) - assert "NOT MATERIALIZED" in sql_not_mat diff --git a/tests/test_schema.py b/tests/test_schema.py deleted file mode 100644 index 814c6a2..0000000 --- a/tests/test_schema.py +++ /dev/null @@ -1,272 +0,0 @@ -import pytest - -import rapidquery as rq - -from .mixin import TableNameInitMixin - - -class TestAlterTable(TableNameInitMixin): - table_name_instance = rq.AlterTable - - options = [ - rq.AlterTableAddColumnOption(rq.Column("premium", rq.sqltypes.Boolean())), - rq.AlterTableAddForeignKeyOption(rq.ForeignKey(["id"], ["fonts.id"])), - rq.AlterTableDropColumnOption("id"), - rq.AlterTableDropForeignKeyOption("fk_12"), - rq.AlterTableModifyColumnOption(rq.Column("name", rq.sqltypes.Char(64), nullable=True)), - rq.AlterTableRenameColumnOption("id", "user_id"), - ] - - def test_init(self): - rq.AlterTable("users", self.options) - rq.AlterTable("users", []) - - with pytest.raises(TypeError): - rq.AlterTable("users", [1]) # type: ignore - - with pytest.raises(TypeError): - rq.AlterTable("users", ["a"]) # type: ignore - - def test_add_option(self): - stmt = rq.AlterTable("users", []) - - for op in self.options: - stmt.add_option(op) - - def test_properties(self): - stmt = rq.AlterTable("users", self.options) - - assert stmt.name == rq.TableName("users") - assert stmt.options == self.options - - stmt.name = "public.fonts" - assert stmt.name == rq.TableName("fonts", "public") - - stmt.options = [] - assert stmt.options == [] - - -class TestDropIndex(TableNameInitMixin): - def table_name_instance(self, tb): - return rq.DropIndex("idx_1", tb) - - def test_init(self): - stmt = rq.DropIndex("idx_name", "users", if_exists=True) - assert "IF EXISTS" in stmt.to_sql("postgres") - - def test_properties(self): - stmt = rq.DropIndex("idx_1", "fonts") - - assert stmt.name == "idx_1" - assert stmt.table == rq.TableName("fonts") - assert not stmt.if_exists - - stmt.name = "idx_2" - assert stmt.name == "idx_2" - - stmt.table = "users" - assert stmt.table == rq.TableName("users") - - stmt.if_exists = True - assert stmt.if_exists - - -class TestDropTable(TableNameInitMixin): - table_name_instance = rq.DropTable - - def test_init(self): - rq.DropTable("users", if_exists=True, cascade=True, restrict=True) - - def test_properties(self): - stmt = rq.DropTable("fonts") - - assert stmt.name == rq.TableName("fonts") - assert not stmt.if_exists - assert not stmt.cascade - assert not stmt.restrict - - stmt.name = "public.users" - assert stmt.name == rq.TableName("users", "public") - - stmt.if_exists = True - stmt.cascade = True - stmt.restrict = True - assert stmt.if_exists - assert stmt.cascade - assert stmt.restrict - - -class TestIndex(TableNameInitMixin): - def table_name_instance(self, tb): - return rq.Index(None, ["id"], tb) - - def test_init(self): - stmt = rq.Index("idx_glyph_aspect", ["aspect"], "glyph", if_not_exists=True).to_sql( - "sqlite" - ) - assert stmt == 'CREATE INDEX IF NOT EXISTS "idx_glyph_aspect" ON "glyph" ("aspect")' - - stmt = rq.Index("idx_glyph_aspect", [rq.IndexColumn("aspect", "ASC", 128)], "glyph").to_sql( - "mysql" - ) - assert stmt == "CREATE INDEX `idx_glyph_aspect` ON `glyph` (`aspect` (128) ASC)" - - stmt = rq.Index( - "idx_font_name_include_language", - ["name"], - "fonts", - include=["language"], - where=rq.Expr.col("aspect").in_([3, 4]), - ).to_sql("postgresql") - assert ( - stmt - == 'CREATE INDEX "idx_font_name_include_language" ON "fonts" ("name") INCLUDE ("language")' - ) - - stmt = rq.Index( - "idx_name", - ["font_id"], - "fonts", - primary=False, - nulls_not_distinct=True, - unique=True, - index_type="BTREE", - where=None, - include=["language"], - ).to_sql("postgres") - assert ( - stmt - == 'CREATE UNIQUE INDEX "idx_name" ON "fonts" USING BTREE ("font_id") INCLUDE ("language") NULLS NOT DISTINCT' - ) - - -class TestIndexColumn: - def test_new(self): - rq.IndexColumn("name") - rq.IndexColumn("name", "ASC") - rq.IndexColumn("name", prefix=12) - rq.IndexColumn("name", "DESC", prefix=1) - rq.IndexColumn("name", None, None) - - # lowercase orders - rq.IndexColumn("name", "asc") # type: ignore - rq.IndexColumn("name", "desc") # type: ignore - - with pytest.raises(Exception): - rq.IndexColumn("name", "invalid") # type: ignore - - val = rq.IndexColumn("name", "desc", prefix=1) # type: ignore - assert val.name == "name" - assert val.order == "DESC" - assert val.prefix == 1 - - -class TestRenameTable(TableNameInitMixin): - def table_name_instance(self, tb): - return rq.RenameTable(tb, tb) - - def test_properties(self): - stmt = rq.RenameTable("t1", "t2") - - assert stmt.from_name == rq.TableName("t1") - assert stmt.to_name == rq.TableName("t2") - - stmt.from_name = "pub.users" - stmt.to_name = "arc.users" - assert stmt.from_name == rq.TableName("users", "pub") - assert stmt.to_name == rq.TableName("users", "arc") - - -class TestTable: - def test_init(self): - rq.Table("users") - rq.Table("public.users") - rq.Table("db.public.users") - rq.Table(rq.TableName("users")) - rq.Table(rq.TableName("users", "public")) - rq.Table(rq.TableName("users", "public", "db")) - - table = rq.Table( - "pub.users", - rq.Column("id", rq.sqltypes.Integer(), primary_key=True, auto_increment=True), - rq.Column("username", rq.sqltypes.Integer(), unique_key=True, nullable=False), - rq.Column("font_id", rq.sqltypes.Integer(), unique_key=True, nullable=False), - rq.ForeignKey(["font_id"], ["fonts.id"], on_delete="CASCADE"), - rq.Index("idx_name", ["id", "font_id"]), - rq.Expr.col("username").like("A%"), - if_not_exists=True, - extra="WOW", - ) - assert table.name == rq.TableName("users", "pub") - assert len(table.columns) == 3 - assert len(table.foreign_keys) == 1 - assert len(table.indexes) == 1 - assert len(table.checks) == 1 - assert all(isinstance(x, rq.Column) for x in table.columns) - assert all(isinstance(x, rq.ForeignKey) for x in table.foreign_keys) - assert all(isinstance(x, rq.Index) for x in table.indexes) - assert all(isinstance(x, rq.Expr) for x in table.checks) - assert table.if_not_exists - assert not table.temporary - assert table.extra == "WOW" - - stmt = table.to_sql("postgres") - assert stmt.count(";") == 1 - assert "IF NOT EXISTS" in stmt - assert '"id"' in stmt - assert '"username"' in stmt - assert '"font_id"' in stmt - assert "WOW" in stmt - - def test_args(self): - with pytest.raises(TypeError): - rq.Table( - "pub.users", - rq.Column("id", rq.sqltypes.Integer(), primary_key=True, auto_increment=True), - complex(), # type: ignore - ) - - class Col(rq.Column): - pass - - class Fk(rq.ForeignKey): - pass - - class Idx(rq.Index): - pass - - table = rq.Table( - "pub.users", - Col("id", rq.sqltypes.Integer(), primary_key=True, auto_increment=True), - Fk(["font_id"], ["fonts.id"], on_delete="CASCADE"), - Idx("idx_name", ["id", "font_id"]), - ) - - assert table.name == rq.TableName("users", "pub") - assert len(table.columns) == 1 - assert len(table.foreign_keys) == 1 - assert len(table.indexes) == 1 - assert all(isinstance(x, Col) for x in table.columns) - assert all(isinstance(x, Fk) for x in table.foreign_keys) - assert all(isinstance(x, Idx) for x in table.indexes) - - def test_multiple_primary(self): - stmt = rq.Table( - "pub.users", - rq.Column("id", rq.sqltypes.Integer(), primary_key=True), - rq.Column("name", rq.sqltypes.Integer(), primary_key=True), - ) - assert stmt.to_sql("postgres").count("PRIMARY KEY") == 2 - - -class TestTruncateTable: - def table_name_instance(self, tb): - return rq.TruncateTable(tb) - - def test_properties(self): - stmt = rq.TruncateTable("t1") - - assert stmt.name == rq.TableName("t1") - - stmt.name = "pub.users" - assert stmt.name == rq.TableName("users", "pub") diff --git a/tests/test_sqltypes.py b/tests/test_sqltypes.py deleted file mode 100644 index 5538402..0000000 --- a/tests/test_sqltypes.py +++ /dev/null @@ -1,262 +0,0 @@ -import datetime -import decimal -import enum -import uuid -from collections import namedtuple - -import pytest - -import rapidquery as rq - -Case = namedtuple( - "Case", - ("data_type", "value", "exc"), -) - - -class Enum(enum.Enum): - FIELD = "field" - - -class StrEnum(str, enum.Enum): - FIELD = "field" - - -class IntEnum(int, enum.Enum): - FIELD = 1 - - -TEST_CASES = [ - # --- BlobType --- - Case(rq.sqltypes.Blob(), b"A", None), - Case(rq.sqltypes.Blob(), 4.5, TypeError), - Case(rq.sqltypes.Blob(), "Ali", TypeError), - # --- BinaryType --- - Case(rq.sqltypes.Binary(), b"A", None), - Case(rq.sqltypes.Binary(), 4.5, TypeError), - Case(rq.sqltypes.Binary(), "Ali", TypeError), - # --- VarBinaryType --- - Case(rq.sqltypes.VarBinary(), b"A", None), - Case(rq.sqltypes.VarBinary(), 4.5, TypeError), - Case(rq.sqltypes.VarBinary(), "Ali", TypeError), - # --- BitType --- - Case(rq.sqltypes.Bit(8), b"A", None), - Case(rq.sqltypes.Bit(8), 4.5, TypeError), - Case(rq.sqltypes.Bit(8), "Ali", TypeError), - # --- VarBitType --- - Case(rq.sqltypes.VarBit(8), b"A", None), - Case(rq.sqltypes.VarBit(8), 4.5, TypeError), - Case(rq.sqltypes.VarBit(8), "Ali", TypeError), - # --- DateTimeType --- - Case(rq.sqltypes.DateTime(), datetime.datetime.now(), None), - Case(rq.sqltypes.DateTime(), datetime.datetime.now(tz=datetime.timezone.utc), None), - Case(rq.sqltypes.DateTime(), datetime.datetime.now(tz=datetime.timezone.min), None), - Case(rq.sqltypes.DateTime(), datetime.datetime.now().date(), TypeError), - Case(rq.sqltypes.DateTime(), datetime.datetime.now().time(), TypeError), - Case(rq.sqltypes.DateTime(), 4, TypeError), - Case(rq.sqltypes.DateTime(), 4.5, TypeError), - Case(rq.sqltypes.DateTime(), "Ali", TypeError), - # --- TimestampType --- - Case(rq.sqltypes.Timestamp(), datetime.datetime.now(), None), - Case( - rq.sqltypes.Timestamp(True), - datetime.datetime.now(tz=datetime.timezone.utc), - None, - ), - Case(rq.sqltypes.Timestamp(), datetime.datetime.now(tz=datetime.timezone.min), None), - Case(rq.sqltypes.Timestamp(), datetime.datetime.now().date(), TypeError), - Case(rq.sqltypes.Timestamp(), datetime.datetime.now().time(), TypeError), - Case(rq.sqltypes.Timestamp(), datetime.datetime.now().timestamp(), None), - Case(rq.sqltypes.Timestamp(), int(datetime.datetime.now().timestamp()), None), - Case(rq.sqltypes.Timestamp(), "Ali", TypeError), - # --- TimeType --- - Case(rq.sqltypes.Time(), datetime.datetime.now(), TypeError), - Case(rq.sqltypes.Time(), datetime.datetime.now().date(), TypeError), - Case(rq.sqltypes.Time(), datetime.datetime.now().time(), None), - Case(rq.sqltypes.Time(), datetime.datetime.now().timestamp(), TypeError), - Case(rq.sqltypes.Time(), int(datetime.datetime.now().timestamp()), TypeError), - Case(rq.sqltypes.Time(), "Ali", TypeError), - # --- DateType --- - Case(rq.sqltypes.Date(), datetime.datetime.now(), TypeError), - Case(rq.sqltypes.Date(), datetime.datetime.now().date(), None), - Case(rq.sqltypes.Date(), datetime.datetime.now().time(), TypeError), - Case(rq.sqltypes.Date(), datetime.datetime.now().timestamp(), TypeError), - Case(rq.sqltypes.Date(), int(datetime.datetime.now().timestamp()), TypeError), - Case(rq.sqltypes.Date(), "Ali", TypeError), - # --- JSONType --- - Case(rq.sqltypes.JSON(), 4.5, None), - Case(rq.sqltypes.JSON(), 4456746532, None), - Case(rq.sqltypes.JSON(), "Ali", None), - Case(rq.sqltypes.JSON(), {"key": "value"}, None), - Case(rq.sqltypes.JSON(), [2, 3], None), - Case(rq.sqltypes.JSON(), b"bytes", TypeError), - Case(rq.sqltypes.JSON(), (1, 2), TypeError), - Case(rq.sqltypes.JSON(), datetime.datetime.now(), TypeError), - # --- JSONBinaryType --- - Case(rq.sqltypes.JSONBinary(), 4.5, None), - Case(rq.sqltypes.JSONBinary(), 4456746532, None), - Case(rq.sqltypes.JSONBinary(), "Ali", None), - Case(rq.sqltypes.JSONBinary(), {"key": "value"}, None), - Case(rq.sqltypes.JSONBinary(), [2, 3], None), - Case(rq.sqltypes.JSONBinary(), b"bytes", TypeError), - Case(rq.sqltypes.JSONBinary(), (1, 2), TypeError), - Case(rq.sqltypes.JSONBinary(), datetime.datetime.now(), TypeError), - # --- DecimalType --- - Case(rq.sqltypes.Decimal(None), 4.5, None), - Case(rq.sqltypes.Decimal((10, 4)), "5.6", None), - Case(rq.sqltypes.Decimal(), 4, None), - Case(rq.sqltypes.Decimal(), "9e-10", None), - Case(rq.sqltypes.Decimal(), decimal.Decimal("1.0"), None), - Case(rq.sqltypes.Decimal(), "Ali", ValueError), - # --- UUIDType --- - Case(rq.sqltypes.UUID(), uuid.uuid4(), None), - Case(rq.sqltypes.UUID(), uuid.uuid4().hex, TypeError), - Case(rq.sqltypes.UUID(), uuid.uuid4().int, TypeError), - # --- INETType --- - Case(rq.sqltypes.INET(), "1.2.3.4", None), - Case(rq.sqltypes.INET(), "1.2.3.4/23", None), - Case(rq.sqltypes.INET(), "127.0.0.1", None), - Case(rq.sqltypes.INET(), "127.0.0.1:8080", ValueError), - Case(rq.sqltypes.INET(), "EA:31:7D:14:D8:40", ValueError), - Case(rq.sqltypes.INET(), "invalid", ValueError), - Case(rq.sqltypes.INET(), uuid.uuid4().hex, ValueError), - Case(rq.sqltypes.INET(), 45, TypeError), - # --- MacAddressType --- - Case(rq.sqltypes.MacAddress(), "EA:31:7D:14:D8:40", None), - Case(rq.sqltypes.MacAddress(), "1.2.3.4", ValueError), - Case(rq.sqltypes.MacAddress(), "invalid", ValueError), - Case(rq.sqltypes.MacAddress(), uuid.uuid4().hex, ValueError), - Case(rq.sqltypes.MacAddress(), 45, TypeError), - # --- EnumType --- - Case(rq.sqltypes.Enum("USER", ("a", "b")), "string", None), - Case(rq.sqltypes.Enum("USER", ("a", "b")), Enum.FIELD, None), - Case(rq.sqltypes.Enum("USER", ("a", "b")), StrEnum.FIELD, None), - Case(rq.sqltypes.Enum("USER", ("a", "b")), IntEnum.FIELD, TypeError), - Case(rq.sqltypes.Enum("USER", ("a", "b")), Enum, TypeError), - Case(rq.sqltypes.Enum("USER", ("a", "b")), 4.5, TypeError), - Case(rq.sqltypes.Enum("USER", ("a", "b")), 45, TypeError), - # --- BigIntegerType --- - Case(rq.sqltypes.BigInteger(), 1638479230, None), - Case(rq.sqltypes.BigInteger(), -1638479230, None), - Case(rq.sqltypes.BigInteger(), 4.5, TypeError), - Case(rq.sqltypes.BigInteger(), "Ali", TypeError), - Case(rq.sqltypes.BigInteger(), 9223372036854775807, None), - Case(rq.sqltypes.BigInteger(), 9223372036854775807 + 1, OverflowError), - # --- IntegerType --- - Case(rq.sqltypes.Integer(), 37, None), - Case(rq.sqltypes.Integer(), -37, None), - Case(rq.sqltypes.Integer(), 4.5, TypeError), - Case(rq.sqltypes.Integer(), "Ali", TypeError), - Case(rq.sqltypes.Integer(), 2147483647, None), - Case(rq.sqltypes.Integer(), 2147483647 + 1, OverflowError), - # --- SmallIntegerType --- - Case(rq.sqltypes.SmallInteger(), 647, None), - Case(rq.sqltypes.SmallInteger(), -647, None), - Case(rq.sqltypes.SmallInteger(), 4.5, TypeError), - Case(rq.sqltypes.SmallInteger(), "Ali", TypeError), - Case(rq.sqltypes.SmallInteger(), 32767, None), - Case(rq.sqltypes.SmallInteger(), 32767 + 1, OverflowError), - # --- TinyIntegerType --- - Case(rq.sqltypes.TinyInteger(), 10, None), - Case(rq.sqltypes.TinyInteger(), -10, None), - Case(rq.sqltypes.TinyInteger(), 4.5, TypeError), - Case(rq.sqltypes.TinyInteger(), "Ali", TypeError), - Case(rq.sqltypes.TinyInteger(), 127, None), - Case(rq.sqltypes.TinyInteger(), 127 + 1, OverflowError), - # --- BigUnsignedType --- - Case(rq.sqltypes.BigUnsigned(), 89438302, None), - Case(rq.sqltypes.BigUnsigned(), -1, OverflowError), - Case(rq.sqltypes.BigUnsigned(), 4.5, TypeError), - Case(rq.sqltypes.BigUnsigned(), "Ali", TypeError), - Case(rq.sqltypes.BigUnsigned(), 18446744073709551615, None), - Case(rq.sqltypes.BigUnsigned(), 18446744073709551615 + 1, OverflowError), - # --- UnsignedType --- - Case(rq.sqltypes.Unsigned(), 89438302, None), - Case(rq.sqltypes.Unsigned(), -1, OverflowError), - Case(rq.sqltypes.Unsigned(), 4.5, TypeError), - Case(rq.sqltypes.Unsigned(), "Ali", TypeError), - Case(rq.sqltypes.Unsigned(), 4294967295, None), - Case(rq.sqltypes.Unsigned(), 4294967295 + 1, OverflowError), - # --- SmallUnsignedType --- - Case(rq.sqltypes.SmallUnsigned(), 978, None), - Case(rq.sqltypes.SmallUnsigned(), -1, OverflowError), - Case(rq.sqltypes.SmallUnsigned(), 4.5, TypeError), - Case(rq.sqltypes.SmallUnsigned(), "Ali", TypeError), - Case(rq.sqltypes.SmallUnsigned(), 65535, None), - Case(rq.sqltypes.SmallUnsigned(), 65535 + 1, OverflowError), - # --- TinyUnsignedType --- - Case(rq.sqltypes.TinyUnsigned(), 20, None), - Case(rq.sqltypes.TinyUnsigned(), -1, OverflowError), - Case(rq.sqltypes.TinyUnsigned(), 4.5, TypeError), - Case(rq.sqltypes.TinyUnsigned(), "Ali", TypeError), - Case(rq.sqltypes.TinyUnsigned(), 255, None), - Case(rq.sqltypes.TinyUnsigned(), 255 + 1, OverflowError), - # --- FloatType --- - Case(rq.sqltypes.Float(), 20.0, None), - Case(rq.sqltypes.Float(), 20, None), - Case(rq.sqltypes.Float(), 4.5, None), - Case(rq.sqltypes.Float(), "Ali", TypeError), - # --- DoubleType --- - Case(rq.sqltypes.Double(), 20.0, None), - Case(rq.sqltypes.Double(), 20, None), - Case(rq.sqltypes.Double(), 4.5, None), - Case(rq.sqltypes.Double(), "Ali", TypeError), - # --- TextType --- - Case(rq.sqltypes.Text(), "A", None), - Case(rq.sqltypes.Text(), "A" * 10000, None), - Case(rq.sqltypes.Text(), b"B", TypeError), - Case(rq.sqltypes.Text(), 1, TypeError), - Case(rq.sqltypes.Text(), 1.4, TypeError), - # --- StringType --- - Case(rq.sqltypes.String(), "A", None), - Case(rq.sqltypes.String(), "A" * 10000, None), - Case(rq.sqltypes.String(), b"B", TypeError), - Case(rq.sqltypes.String(), 1, TypeError), - Case(rq.sqltypes.String(), 1.4, TypeError), - # --- CharType --- - Case(rq.sqltypes.Char(), "A", None), - Case(rq.sqltypes.Char(), "A" * 10000, None), - Case(rq.sqltypes.Char(), b"B", TypeError), - Case(rq.sqltypes.Char(), 1, TypeError), - Case(rq.sqltypes.Char(), 1.4, TypeError), - # --- ArrayType --- - Case(rq.sqltypes.Array(rq.sqltypes.Integer()), [1, 2], None), - Case(rq.sqltypes.Array(rq.sqltypes.Integer()), [1, 2], None), - Case(rq.sqltypes.Array(rq.sqltypes.Integer()), (1, 2, 5), None), - Case(rq.sqltypes.Array(rq.sqltypes.Float()), [1.3, 2.7], None), - Case(rq.sqltypes.Array(rq.sqltypes.Integer()), [1, "A"], TypeError), - Case( - rq.sqltypes.Array(rq.sqltypes.Integer()), - [1, 2, 3, 4, 5, 6, 7, 8, "A", 10], - TypeError, - ), - Case(rq.sqltypes.Array(rq.sqltypes.Text()), ["B", "A"], TypeError), - # --- VectorType --- - Case(rq.sqltypes.Vector(6), [1.4, 2.1], None), - Case(rq.sqltypes.Vector(5), [1, 2], None), - Case(rq.sqltypes.Vector(4), (1, 2, 5.3), None), - Case(rq.sqltypes.Vector(3), [1.3, 2.7], None), - Case(rq.sqltypes.Vector(2), [1, "A"], TypeError), - Case(rq.sqltypes.Vector(1), [1, 2, 3, 4, 5, 6, 7, 8, "A", 10], TypeError), - Case(rq.sqltypes.Vector(), ["B", "A"], TypeError), -] - - -class TestSQLType: - @pytest.mark.parametrize("case", TEST_CASES) - def test_validate_serialize(self, case: Case): - try: - val = rq.Value(case.value, case.data_type) - rq.Expr(val) # Force Value to adapt - except Exception as e: - if case.exc is not None and type(e) is case.exc: - return - - raise e - - assert val.sql_type is case.data_type - assert val.value == case.value - - @pytest.mark.parametrize("case", TEST_CASES) - def test_type_name(self, case: Case): - assert case.data_type.__type_name__