diff --git a/.github/workflows/check-pg_analytics-schema-upgrade.yml b/.github/workflows/check-pg_analytics-schema-upgrade.yml index 8860820c..bb3622b5 100644 --- a/.github/workflows/check-pg_analytics-schema-upgrade.yml +++ b/.github/workflows/check-pg_analytics-schema-upgrade.yml @@ -20,6 +20,7 @@ on: # Required to post a comment to the PR permissions: pull-requests: write + issues: write concurrency: group: check-pg_analytics-schema-upgrade-${{ github.head_ref || github.ref }} diff --git a/Cargo.lock b/Cargo.lock index 425ce14e..50a8a285 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,20 +147,20 @@ dependencies = [ [[package]] name = "arrow" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05048a8932648b63f21c37d88b552ccc8a65afb6dfe9fc9f30ce79174c2e7a85" +checksum = "4caf25cdc4a985f91df42ed9e9308e1adbcd341a31a72605c697033fcef163e3" dependencies = [ - "arrow-arith 52.2.0", - "arrow-array 52.2.0", - "arrow-buffer 52.2.0", - "arrow-cast 52.2.0", - "arrow-data 52.2.0", - "arrow-ord 52.2.0", - "arrow-row 52.2.0", - "arrow-schema 52.2.0", - "arrow-select 52.2.0", - "arrow-string 52.2.0", + "arrow-arith 53.2.0", + "arrow-array 53.2.0", + "arrow-buffer 53.2.0", + "arrow-cast 53.2.0", + "arrow-data 53.2.0", + "arrow-ord 53.2.0", + "arrow-row 53.2.0", + "arrow-schema 53.2.0", + "arrow-select 53.2.0", + "arrow-string 53.2.0", ] [[package]] @@ -180,14 +180,14 @@ dependencies = [ [[package]] name = "arrow-arith" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8a57966e43bfe9a3277984a14c24ec617ad874e4c0e1d2a1b083a39cfbf22c" +checksum = "91f2dfd1a7ec0aca967dfaa616096aec49779adc8eccec005e2f5e4111b1192a" dependencies = [ - "arrow-array 52.2.0", - "arrow-buffer 52.2.0", - "arrow-data 52.2.0", - "arrow-schema 52.2.0", + "arrow-array 53.2.0", + "arrow-buffer 53.2.0", + "arrow-data 53.2.0", + "arrow-schema 53.2.0", "chrono", "half 2.4.1", "num", @@ -212,16 +212,16 @@ dependencies = [ [[package]] name = "arrow-array" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f4a9468c882dc66862cef4e1fd8423d47e67972377d85d80e022786427768c" +checksum = "d39387ca628be747394890a6e47f138ceac1aa912eab64f02519fed24b637af8" dependencies = [ "ahash 0.8.11", - "arrow-buffer 52.2.0", - "arrow-data 52.2.0", - "arrow-schema 52.2.0", + "arrow-buffer 53.2.0", + "arrow-data 53.2.0", + "arrow-schema 53.2.0", "chrono", - "chrono-tz 0.9.0", + "chrono-tz 0.10.0", "half 2.4.1", "hashbrown 0.14.5", "num", @@ -240,9 +240,9 @@ dependencies = [ [[package]] name = "arrow-buffer" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c975484888fc95ec4a632cdc98be39c085b1bb518531b0c80c5d462063e5daa1" +checksum = "9e51e05228852ffe3eb391ce7178a0f97d2cf80cc6ef91d3c4a6b3cb688049ec" dependencies = [ "bytes", "half 2.4.1", @@ -265,28 +265,28 @@ dependencies = [ "chrono", "comfy-table", "half 2.4.1", - "lexical-core", + "lexical-core 0.8.5", "num", "ryu", ] [[package]] name = "arrow-cast" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da26719e76b81d8bc3faad1d4dbdc1bcc10d14704e63dc17fc9f3e7e1e567c8e" +checksum = "d09aea56ec9fa267f3f3f6cdab67d8a9974cbba90b3aa38c8fe9d0bb071bd8c1" dependencies = [ - "arrow-array 52.2.0", - "arrow-buffer 52.2.0", - "arrow-data 52.2.0", - "arrow-schema 52.2.0", - "arrow-select 52.2.0", + "arrow-array 53.2.0", + "arrow-buffer 53.2.0", + "arrow-data 53.2.0", + "arrow-schema 53.2.0", + "arrow-select 53.2.0", "atoi", "base64 0.22.1", "chrono", "comfy-table", "half 2.4.1", - "lexical-core", + "lexical-core 1.0.2", "num", "ryu", ] @@ -306,7 +306,7 @@ dependencies = [ "csv", "csv-core", "lazy_static", - "lexical-core", + "lexical-core 0.8.5", "regex", ] @@ -324,12 +324,12 @@ dependencies = [ [[package]] name = "arrow-data" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd9d6f18c65ef7a2573ab498c374d8ae364b4a4edf67105357491c031f716ca5" +checksum = "b98ae0af50890b494cebd7d6b04b35e896205c1d1df7b29a6272c5d0d0249ef5" dependencies = [ - "arrow-buffer 52.2.0", - "arrow-schema 52.2.0", + "arrow-buffer 53.2.0", + "arrow-schema 53.2.0", "half 2.4.1", "num", ] @@ -363,7 +363,7 @@ dependencies = [ "chrono", "half 2.4.1", "indexmap 2.6.0", - "lexical-core", + "lexical-core 0.8.5", "num", "serde", "serde_json", @@ -386,15 +386,15 @@ dependencies = [ [[package]] name = "arrow-ord" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42745f86b1ab99ef96d1c0bcf49180848a64fe2c7a7a0d945bc64fa2b21ba9bc" +checksum = "2883d7035e0b600fb4c30ce1e50e66e53d8656aa729f2bfa4b51d359cf3ded52" dependencies = [ - "arrow-array 52.2.0", - "arrow-buffer 52.2.0", - "arrow-data 52.2.0", - "arrow-schema 52.2.0", - "arrow-select 52.2.0", + "arrow-array 53.2.0", + "arrow-buffer 53.2.0", + "arrow-data 53.2.0", + "arrow-schema 53.2.0", + "arrow-select 53.2.0", "half 2.4.1", "num", ] @@ -416,15 +416,15 @@ dependencies = [ [[package]] name = "arrow-row" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd09a518c602a55bd406bcc291a967b284cfa7a63edfbf8b897ea4748aad23c" +checksum = "552907e8e587a6fde4f8843fd7a27a576a260f65dab6c065741ea79f633fc5be" dependencies = [ "ahash 0.8.11", - "arrow-array 52.2.0", - "arrow-buffer 52.2.0", - "arrow-data 52.2.0", - "arrow-schema 52.2.0", + "arrow-array 53.2.0", + "arrow-buffer 53.2.0", + "arrow-data 53.2.0", + "arrow-schema 53.2.0", "half 2.4.1", ] @@ -439,9 +439,9 @@ dependencies = [ [[package]] name = "arrow-schema" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e972cd1ff4a4ccd22f86d3e53e835c2ed92e0eea6a3e8eadb72b4f1ac802cf8" +checksum = "539ada65246b949bd99ffa0881a9a15a4a529448af1a07a9838dd78617dafab1" dependencies = [ "bitflags 2.6.0", ] @@ -462,15 +462,15 @@ dependencies = [ [[package]] name = "arrow-select" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600bae05d43483d216fb3494f8c32fdbefd8aa4e1de237e790dbb3d9f44690a3" +checksum = "6259e566b752da6dceab91766ed8b2e67bf6270eb9ad8a6e07a33c1bede2b125" dependencies = [ "ahash 0.8.11", - "arrow-array 52.2.0", - "arrow-buffer 52.2.0", - "arrow-data 52.2.0", - "arrow-schema 52.2.0", + "arrow-array 53.2.0", + "arrow-buffer 53.2.0", + "arrow-data 53.2.0", + "arrow-schema 53.2.0", "num", ] @@ -493,15 +493,15 @@ dependencies = [ [[package]] name = "arrow-string" -version = "52.2.0" +version = "53.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc1985b67cb45f6606a248ac2b4a288849f196bab8c657ea5589f47cdd55e6" +checksum = "f3179ccbd18ebf04277a095ba7321b93fd1f774f18816bd5f6b3ce2f594edb6c" dependencies = [ - "arrow-array 52.2.0", - "arrow-buffer 52.2.0", - "arrow-data 52.2.0", - "arrow-schema 52.2.0", - "arrow-select 52.2.0", + "arrow-array 53.2.0", + "arrow-buffer 53.2.0", + "arrow-data 53.2.0", + "arrow-schema 53.2.0", + "arrow-select 53.2.0", "memchr", "num", "regex", @@ -1535,12 +1535,12 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" dependencies = [ "chrono", - "chrono-tz-build 0.3.0", + "chrono-tz-build 0.4.0", "phf", ] @@ -1557,12 +1557,11 @@ dependencies = [ [[package]] name = "chrono-tz-build" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" dependencies = [ "parse-zoneinfo", - "phf", "phf_codegen", ] @@ -2279,16 +2278,36 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "duckdb" -version = "1.0.0" -source = "git+https://github.com/paradedb/duckdb-rs.git?rev=e532dd6#e532dd68291736dd102da2bb6041fd3fb273573f" +version = "1.1.1" +source = "git+https://github.com/paradedb/duckdb-rs.git?rev=c2f9e2010e326de21126e90dc24da47e0a962cb0#c2f9e2010e326de21126e90dc24da47e0a962cb0" +dependencies = [ + "arrow 53.2.0", + "calamine", + "cast", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink 0.9.1", + "libduckdb-sys 1.1.1 (git+https://github.com/paradedb/duckdb-rs.git?rev=c2f9e2010e326de21126e90dc24da47e0a962cb0)", + "memchr", + "num", + "num-integer", + "rust_decimal", + "smallvec", + "strum 0.25.0", +] + +[[package]] +name = "duckdb" +version = "1.1.1" +source = "git+https://github.com/paradedb/duckdb-rs.git?rev=e532dd6#c2f9e2010e326de21126e90dc24da47e0a962cb0" dependencies = [ - "arrow 52.2.0", + "arrow 53.2.0", "calamine", "cast", "fallible-iterator", "fallible-streaming-iterator", - "hashlink", - "libduckdb-sys", + "hashlink 0.9.1", + "libduckdb-sys 1.1.1 (git+https://github.com/paradedb/duckdb-rs.git?rev=e532dd6)", "memchr", "num", "num-integer", @@ -2863,6 +2882,15 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "heapless" version = "0.8.0" @@ -3313,11 +3341,24 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" dependencies = [ - "lexical-parse-float", - "lexical-parse-integer", - "lexical-util", - "lexical-write-float", - "lexical-write-integer", + "lexical-parse-float 0.8.5", + "lexical-parse-integer 0.8.6", + "lexical-util 0.8.5", + "lexical-write-float 0.8.5", + "lexical-write-integer 0.8.5", +] + +[[package]] +name = "lexical-core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431c65b318a590c1de6b8fd6e72798c92291d27762d94c9e6c37ed7a73d8458" +dependencies = [ + "lexical-parse-float 1.0.2", + "lexical-parse-integer 1.0.2", + "lexical-util 1.0.3", + "lexical-write-float 1.0.2", + "lexical-write-integer 1.0.2", ] [[package]] @@ -3326,8 +3367,19 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" dependencies = [ - "lexical-parse-integer", - "lexical-util", + "lexical-parse-integer 0.8.6", + "lexical-util 0.8.5", + "static_assertions", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb17a4bdb9b418051aa59d41d65b1c9be5affab314a872e5ad7f06231fb3b4e0" +dependencies = [ + "lexical-parse-integer 1.0.2", + "lexical-util 1.0.3", "static_assertions", ] @@ -3337,7 +3389,17 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" dependencies = [ - "lexical-util", + "lexical-util 0.8.5", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df98f4a4ab53bf8b175b363a34c7af608fe31f93cc1fb1bf07130622ca4ef61" +dependencies = [ + "lexical-util 1.0.3", "static_assertions", ] @@ -3350,14 +3412,34 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "lexical-util" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85314db53332e5c192b6bca611fb10c114a80d1b831ddac0af1e9be1b9232ca0" +dependencies = [ + "static_assertions", +] + [[package]] name = "lexical-write-float" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" dependencies = [ - "lexical-util", - "lexical-write-integer", + "lexical-util 0.8.5", + "lexical-write-integer 0.8.5", + "static_assertions", +] + +[[package]] +name = "lexical-write-float" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7c3ad4e37db81c1cbe7cf34610340adc09c322871972f74877a712abc6c809" +dependencies = [ + "lexical-util 1.0.3", + "lexical-write-integer 1.0.2", "static_assertions", ] @@ -3367,7 +3449,17 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" dependencies = [ - "lexical-util", + "lexical-util 0.8.5", + "static_assertions", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb89e9f6958b83258afa3deed90b5de9ef68eef090ad5086c791cd2345610162" +dependencies = [ + "lexical-util 1.0.3", "static_assertions", ] @@ -3379,8 +3471,23 @@ checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libduckdb-sys" -version = "1.0.0" -source = "git+https://github.com/paradedb/duckdb-rs.git?rev=e532dd6#e532dd68291736dd102da2bb6041fd3fb273573f" +version = "1.1.1" +source = "git+https://github.com/paradedb/duckdb-rs.git?rev=c2f9e2010e326de21126e90dc24da47e0a962cb0#c2f9e2010e326de21126e90dc24da47e0a962cb0" +dependencies = [ + "autocfg", + "cc", + "flate2", + "pkg-config", + "serde", + "serde_json", + "tar", + "vcpkg", +] + +[[package]] +name = "libduckdb-sys" +version = "1.1.1" +source = "git+https://github.com/paradedb/duckdb-rs.git?rev=e532dd6#c2f9e2010e326de21126e90dc24da47e0a962cb0" dependencies = [ "autocfg", "cc", @@ -3898,16 +4005,16 @@ dependencies = [ [[package]] name = "pg_analytics" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "async-std", "chrono", - "duckdb", + "duckdb 1.1.1 (git+https://github.com/paradedb/duckdb-rs.git?rev=c2f9e2010e326de21126e90dc24da47e0a962cb0)", "pgrx", "serde_json", "signal-hook", - "sqlparser 0.50.0", + "sqlparser 0.52.0", "strum 0.26.3", "supabase-wrappers", "thiserror", @@ -5112,9 +5219,9 @@ dependencies = [ [[package]] name = "sqlparser" -version = "0.50.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5b515a2bd5168426033e9efbfd05500114833916f1d5c268f938b4ee130ac" +checksum = "9a875d8cd437cc8a97e9aeaeea352ec9a19aea99c23e9effb17757291de80b08" dependencies = [ "log", ] @@ -5166,7 +5273,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashlink", + "hashlink 0.8.4", "hex", "indexmap 2.6.0", "log", @@ -5581,7 +5688,7 @@ dependencies = [ [[package]] name = "tests" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "async-std", @@ -5592,7 +5699,7 @@ dependencies = [ "chrono", "datafusion", "deltalake", - "duckdb", + "duckdb 1.1.1 (git+https://github.com/paradedb/duckdb-rs.git?rev=e532dd6)", "futures", "geojson", "pgrx", diff --git a/Cargo.toml b/Cargo.toml index 77bf7322..32a1ccd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pg_analytics" description = "Postgres for analytics, powered by DuckDB" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "PostgreSQL" @@ -24,11 +24,11 @@ chrono = "0.4.38" duckdb = { git = "https://github.com/paradedb/duckdb-rs.git", features = [ "bundled", "extensions-full", -], rev = "e532dd6" } +], rev = "c2f9e2010e326de21126e90dc24da47e0a962cb0" } pgrx = "0.12.7" serde_json = "1.0.128" signal-hook = "0.3.17" -sqlparser = "0.50.0" +sqlparser = "0.52.0" strum = { version = "0.26.3", features = ["derive"] } supabase-wrappers = { git = "https://github.com/paradedb/wrappers.git", default-features = false, rev = "8aef4a6" } thiserror = "1.0.63" diff --git a/README.md b/README.md index d74ea595..fd5570c6 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Today, a vast amount of non-operational data — events, metrics, historical sna - [x] Google Cloud Storage - [x] Azure Blob Storage - [x] Azure Data Lake Storage Gen2 -- [x] HuggingFace +- [x] Hugging Face (`.parquet`, `.csv`, `.jsonl`) - [x] HTTP server - [x] Local file system diff --git a/docs/object_stores/huggingface.mdx b/docs/object_stores/huggingface.mdx index e15291e0..4d15e713 100644 --- a/docs/object_stores/huggingface.mdx +++ b/docs/object_stores/huggingface.mdx @@ -4,7 +4,7 @@ title: Hugging Face ## Overview -This code block demonstrates how to query machine learning datasets from the Hugging Face Datasets library. The file path must start with `hf://`. +This code block demonstrates how to query machine learning datasets from the Hugging Face Datasets library. The file path must start with `hf://`. The supported file types are `.parquet`, `.csv` and `.jsonl`. ```sql -- CSV format is assumed diff --git a/sql/pg_analytics--0.2.3--0.2.4.sql b/sql/pg_analytics--0.2.3--0.2.4.sql new file mode 100644 index 00000000..09c56cb4 --- /dev/null +++ b/sql/pg_analytics--0.2.3--0.2.4.sql @@ -0,0 +1 @@ +\echo Use "ALTER EXTENSION pg_analytics UPDATE TO '0.2.4'" to load this file. \quit diff --git a/src/duckdb/connection.rs b/src/duckdb/connection.rs index b420916e..530b2412 100644 --- a/src/duckdb/connection.rs +++ b/src/duckdb/connection.rs @@ -66,6 +66,7 @@ pub fn get_global_connection() -> &'static UnsafeCell { INIT.call_once(|| { init_globals(); }); + #[allow(static_mut_refs)] unsafe { GLOBAL_CONNECTION .as_ref() @@ -77,6 +78,7 @@ fn get_global_statement() -> &'static UnsafeCell>> { INIT.call_once(|| { init_globals(); }); + #[allow(static_mut_refs)] unsafe { GLOBAL_STATEMENT .as_ref() @@ -88,7 +90,10 @@ fn get_global_arrow() -> &'static UnsafeCell>> { INIT.call_once(|| { init_globals(); }); - unsafe { GLOBAL_ARROW.as_ref().expect("Arrow not initialized") } + #[allow(static_mut_refs)] + unsafe { + GLOBAL_ARROW.as_ref().expect("Arrow not initialized") + } } pub fn create_csv_view( @@ -249,3 +254,21 @@ pub fn set_search_path(search_path: Vec) -> Result<()> { Ok(()) } + +pub fn execute_explain(query: &str) -> Result { + let conn = unsafe { &*get_global_connection().get() }; + let mut stmt = conn.prepare(query)?; + let rows = stmt.query_row([], |row| { + let mut r = vec![]; + + let mut col_index = 1; + while let Ok(value) = row.get::<_, String>(col_index) { + r.push(value); + col_index += 1; + } + + Ok(r) + })?; + + Ok(rows.join("")) +} diff --git a/src/duckdb/json.rs b/src/duckdb/json.rs index 0772cdd6..efb1ac39 100644 --- a/src/duckdb/json.rs +++ b/src/duckdb/json.rs @@ -98,10 +98,10 @@ fn extract_option( table_options: &HashMap, quote: bool, ) -> Option { - return table_options.get(option.as_ref()).map(|res| match quote { + table_options.get(option.as_ref()).map(|res| match quote { true => format!("{option} = '{res}'"), false => format!("{option} = {res}"), - }); + }) } #[cfg(test)] diff --git a/src/hooks/utility.rs b/src/hooks/utility.rs index b42ef984..4fa5f3f0 100644 --- a/src/hooks/utility.rs +++ b/src/hooks/utility.rs @@ -163,13 +163,7 @@ fn parse_query_from_utility_stmt(query_string: &core::ffi::CStr) -> Result Ok(statement.to_string()), + Statement::Explain { statement, .. } => Ok(statement.to_string()), _ => bail!("unexpected utility statement: {}", query_string), } } diff --git a/src/hooks/utility/explain.rs b/src/hooks/utility/explain.rs index bdfca82b..96e64634 100644 --- a/src/hooks/utility/explain.rs +++ b/src/hooks/utility/explain.rs @@ -15,13 +15,26 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use std::ffi::CString; +use std::ffi::{CStr, CString}; +use std::time::Instant; use anyhow::Result; use pgrx::{error, pg_sys}; use super::parse_query_from_utility_stmt; -use crate::hooks::query::{get_query_relations, is_duckdb_query}; +use crate::{ + duckdb::connection, + hooks::query::{get_query_relations, is_duckdb_query, set_search_path_by_pg}, +}; + +enum Style { + Postgres, + Duckdb, +} +struct ExplainState { + analyze: bool, + style: Style, +} pub fn explain_query( query_string: &core::ffi::CStr, @@ -37,9 +50,34 @@ pub fn explain_query( return Ok(true); } - if unsafe { !(*stmt).options.is_null() } { - error!("the EXPLAIN options provided are not supported for DuckDB pushdown queries."); - } + let state = parse_explain_options(unsafe { (*stmt).options }); + let query = parse_query_from_utility_stmt(query_string)?; + + let output = match state.style { + Style::Postgres => { + let mut output = format!("DuckDB Scan: {}\n", query); + if state.analyze { + let start_time = Instant::now(); + set_search_path_by_pg()?; + connection::execute(&query, [])?; + let duration = start_time.elapsed(); + output += &format!( + "Execution Time: {:.3} ms\n", + duration.as_micros() as f64 / 1_000.0 + ); + } + output + } + Style::Duckdb => { + set_search_path_by_pg()?; + let explain_query = if state.analyze { + format!("EXPLAIN ANALYZE {query}") + } else { + format!("EXPLAIN {query}") + }; + connection::execute_explain(&explain_query)? + } + }; unsafe { let tstate = pg_sys::begin_tup_output_tupdesc( @@ -47,15 +85,71 @@ pub fn explain_query( pg_sys::ExplainResultDesc(stmt), &pg_sys::TTSOpsVirtual, ); - let query = format!( - "DuckDB Scan: {}", - parse_query_from_utility_stmt(query_string)? - ); - let query_c_str = CString::new(query)?; - pg_sys::do_text_output_multiline(tstate, query_c_str.as_ptr()); + let output_cstr = CString::new(output)?; + + pg_sys::do_text_output_multiline(tstate, output_cstr.as_ptr()); pg_sys::end_tup_output(tstate); } Ok(false) } + +fn parse_explain_options(options: *const pg_sys::List) -> ExplainState { + let mut explain_state = ExplainState { + analyze: false, + style: Style::Postgres, + }; + + if options.is_null() { + return explain_state; + } + + unsafe { + let elements = (*options).elements; + + for i in 0..(*options).length as isize { + let opt = (*elements.offset(i)).ptr_value as *mut pg_sys::DefElem; + + let opt_name = match CStr::from_ptr((*opt).defname).to_str() { + Ok(opt) => opt, + Err(e) => { + error!("failed to parse EXPLAIN option name: {e}"); + } + }; + match opt_name { + "analyze" => { + explain_state.analyze = pg_sys::defGetBoolean(opt); + } + "style" => { + let style = match CStr::from_ptr(pg_sys::defGetString(opt)).to_str() { + Ok(style) => style, + + Err(e) => { + error!("failed to parse STYLE option: {e}"); + } + }; + + explain_state.style = match parse_explain_style(style) { + Some(s) => s, + None => { + error!("unrecognized STYLE option: {style}") + } + }; + } + _ => error!("unrecognized EXPLAIN option \"{opt_name}\""), + } + } + } + + explain_state +} + +fn parse_explain_style(style: &str) -> Option