diff --git a/Cargo.lock b/Cargo.lock index 9246093..6b8e6a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.6.21" @@ -52,18 +61,83 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which 4.4.2", +] + [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.54" @@ -122,6 +196,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -134,6 +214,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.14" @@ -150,6 +236,24 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "getrandom" version = "0.3.4" @@ -162,12 +266,43 @@ dependencies = [ "wasip2", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "insta" version = "1.46.1" @@ -186,30 +321,104 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -222,15 +431,60 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pg_query" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ca6fdb8f9d32182abf17328789f87f305dd8c8ce5bf48c5aa2b5cffc94e1c04" +dependencies = [ + "bindgen", + "cc", + "fs_extra", + "glob", + "itertools 0.10.5", + "prost", + "prost-build", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "pqb" version = "0.1.3" dependencies = [ "insta", + "pg_query", "serde_json", "uuid", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -240,6 +494,58 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.44" @@ -255,6 +561,54 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.3" @@ -264,7 +618,7 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.11.0", "windows-sys 0.61.2", ] @@ -275,6 +629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -310,6 +665,12 @@ dependencies = [ "zmij", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "similar" version = "2.7.0" @@ -342,10 +703,30 @@ dependencies = [ "fastrand", "getrandom", "once_cell", - "rustix", + "rustix 1.1.3", "windows-sys 0.61.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.22" @@ -373,6 +754,18 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "which" version = "8.0.0" @@ -380,7 +773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ "env_home", - "rustix", + "rustix 1.1.3", "winsafe", ] @@ -489,7 +882,7 @@ name = "x" version = "0.0.0" dependencies = [ "clap", - "which", + "which 8.0.0", ] [[package]] diff --git a/pqb/Cargo.toml b/pqb/Cargo.toml index 25e6647..dd36117 100644 --- a/pqb/Cargo.toml +++ b/pqb/Cargo.toml @@ -39,6 +39,7 @@ uuid = { version = "1", default-features = false, optional = true } [dev-dependencies] insta = { version = "1.46.1" } +pg_query = { version = "6.1.1" } [lints] workspace = true diff --git a/pqb/src/query/conflict.rs b/pqb/src/query/conflict.rs index fd50cc1..dd9223c 100644 --- a/pqb/src/query/conflict.rs +++ b/pqb/src/query/conflict.rs @@ -216,7 +216,13 @@ pub(crate) fn write_on_conflict(w: &mut W, on_conflict: &OnConflic if i > 0 { w.push_str(", "); } - write_expr(w, expr); + if matches!(expr, Expr::Column(_)) { + write_expr(w, expr); + } else { + w.push_str("("); + write_expr(w, expr); + w.push_str(")"); + } } w.push_str(")"); } diff --git a/pqb/src/value.rs b/pqb/src/value.rs index adf513c..bd0b6fc 100644 --- a/pqb/src/value.rs +++ b/pqb/src/value.rs @@ -23,7 +23,7 @@ use crate::writer::SqlWriter; /// SQL value variants. #[derive(Debug, Clone, PartialEq)] -#[expect(missing_docs)] +#[allow(missing_docs)] pub enum Value { Bool(Option), TinyInt(Option), diff --git a/pqb/tests/common.rs b/pqb/tests/common.rs new file mode 100644 index 0000000..5d29cf6 --- /dev/null +++ b/pqb/tests/common.rs @@ -0,0 +1,25 @@ +// Copyright 2025 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub trait ValidateSql { + fn validate(self) -> Self; +} + +impl ValidateSql for String { + #[track_caller] + fn validate(self) -> Self { + pg_query::parse(self.as_str()).unwrap(); + self + } +} diff --git a/pqb/tests/create_table.rs b/pqb/tests/create_table.rs index 6f8dd32..c9c3072 100644 --- a/pqb/tests/create_table.rs +++ b/pqb/tests/create_table.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod common; + use insta::assert_snapshot; use pqb::expr::Expr; use pqb::index::CreateIndex; @@ -19,6 +21,8 @@ use pqb::table::ColumnDef; use pqb::table::ColumnType; use pqb::table::CreateTable; +use crate::common::ValidateSql; + #[test] fn create_table_basic() { assert_snapshot!( @@ -28,7 +32,8 @@ fn create_table_basic() { .column(ColumnDef::new("email").text().not_null()) .column(ColumnDef::new("nickname").text().null()) .column(ColumnDef::new("created_at").timestamp_with_time_zone()) - .to_sql(), + .to_sql() + .validate(), @r#"CREATE TABLE "users" ( "id" bigint NOT NULL, "email" text NOT NULL, "nickname" text NULL, "created_at" timestamp with time zone )"# ); } @@ -42,7 +47,8 @@ fn create_table_if_not_exists_temporary() { .table("cache") .column(ColumnDef::new("key").text().not_null()) .column(ColumnDef::new("value").json_binary()) - .to_sql(), + .to_sql() + .validate(), @r#"CREATE TEMPORARY TABLE IF NOT EXISTS "cache" ( "key" text NOT NULL, "value" jsonb )"# ); } @@ -55,7 +61,8 @@ fn create_table_primary_key_index() { .column(ColumnDef::new("id").int()) .column(ColumnDef::new("name").text()) .primary_key(CreateIndex::new().column("id")) - .to_sql(), + .to_sql() + .validate(), @r#"CREATE TABLE "widgets" ( "id" integer, "name" text, PRIMARY KEY ("id") )"# ); } @@ -78,6 +85,7 @@ fn create_table_generated_column() { .generated_as_virtual(Expr::column("sum").div(Expr::value(2))), ) .to_sql(), + // .validate(), // TODO: Before PostgreSQL 18, STORED is the only supported kind and must be specified. @r#"CREATE TABLE "calc" ( "a" integer, "b" integer, "sum" integer GENERATED ALWAYS AS ("a" + "b") STORED, "avg" integer GENERATED ALWAYS AS ("sum" / 2) VIRTUAL )"# ); } @@ -116,7 +124,8 @@ fn create_table_all_column_types() { .column(ColumnDef::new("col_jsonb").json_binary()) .column(ColumnDef::new("col_uuid").uuid()) .column(ColumnDef::new("col_int_array").array_of(ColumnType::Int)) - .to_sql(), + .to_sql() + .validate(), @r#"CREATE TABLE "all_types" ( "col_char" char(4), "col_varchar" varchar(10), "col_text" text, "col_bytea" bytea, "col_smallint" smallint, "col_int" integer, "col_bigint" bigint, "col_float" real, "col_double" double precision, "col_numeric" numeric(10, 2), "col_smallserial" smallserial, "col_serial" serial, "col_bigserial" bigserial, "col_int4range" int4range, "col_int8range" int8range, "col_numrange" numrange, "col_tsrange" tsrange, "col_tstzrange" tstzrange, "col_daterange" daterange, "col_datetime" timestamp without time zone, "col_timestamp" timestamp, "col_timestamptz" timestamp with time zone, "col_time" time, "col_date" date, "col_bool" bool, "col_json" json, "col_jsonb" jsonb, "col_uuid" uuid, "col_int_array" integer[] )"# ); } diff --git a/pqb/tests/drop.rs b/pqb/tests/drop.rs index af1de1b..e0a487e 100644 --- a/pqb/tests/drop.rs +++ b/pqb/tests/drop.rs @@ -12,11 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod common; + use insta::assert_snapshot; use pqb::index::DropIndex; use pqb::schema::DropSchema; use pqb::table::DropTable; +use crate::common::ValidateSql; + #[test] fn drop_index_sql() { assert_snapshot!( @@ -25,7 +29,8 @@ fn drop_index_sql() { .if_exists() .concurrently() .cascade() - .to_sql(), + .to_sql() + .validate(), @r#"DROP INDEX CONCURRENTLY IF EXISTS "public"."idx_users_email" CASCADE"# ); } @@ -37,7 +42,8 @@ fn drop_table_sql() { .tables([("public", "users"), ("public", "accounts")]) .if_exists() .restrict() - .to_sql(), + .to_sql() + .validate(), @r#"DROP TABLE IF EXISTS "public"."users", "public"."accounts" RESTRICT"# ); } @@ -49,7 +55,8 @@ fn drop_schema_sql() { .schemas(["public", "analytics"]) .if_exists() .cascade() - .to_sql(), + .to_sql() + .validate(), @r#"DROP SCHEMA IF EXISTS "public", "analytics" CASCADE"# ); } diff --git a/pqb/tests/explain.rs b/pqb/tests/explain.rs index 9a6336d..6882355 100644 --- a/pqb/tests/explain.rs +++ b/pqb/tests/explain.rs @@ -12,10 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod common; + use insta::assert_snapshot; use pqb::query::Explain; use pqb::query::Select; +use crate::common::ValidateSql; + #[test] fn explain_postgres_select_with_options() { assert_snapshot!( @@ -33,7 +37,8 @@ fn explain_postgres_select_with_options() { .memory(true) .format_json() .statement(Select::new().column("character").from("character")) - .to_sql(), + .to_sql() + .validate(), @r#"EXPLAIN (ANALYZE, VERBOSE 0, COSTS, SETTINGS 0, GENERIC_PLAN, BUFFERS, SERIALIZE TEXT, WAL, TIMING 0, SUMMARY, MEMORY, FORMAT JSON) SELECT "character" FROM "character""# ); } @@ -44,7 +49,8 @@ fn explain_postgres_serialize_text() { Explain::new() .serialize_text() .statement(Select::new().column("character").from("character")) - .to_sql(), + .to_sql() + .validate(), @r#"EXPLAIN (SERIALIZE TEXT) SELECT "character" FROM "character""# ); } @@ -55,7 +61,8 @@ fn explain_postgres_serialize_binary() { Explain::new() .serialize_binary() .statement(Select::new().column("character").from("character")) - .to_sql(), + .to_sql() + .validate(), @r#"EXPLAIN (SERIALIZE BINARY) SELECT "character" FROM "character""# ); } @@ -66,7 +73,8 @@ fn explain_postgres_serialize_none() { Explain::new() .serialize_none() .statement(Select::new().column("character").from("character")) - .to_sql(), + .to_sql() + .validate(), @r#"EXPLAIN (SERIALIZE NONE) SELECT "character" FROM "character""# ); } diff --git a/pqb/tests/expr.rs b/pqb/tests/expr.rs index 4bd44c9..cd7daed 100644 --- a/pqb/tests/expr.rs +++ b/pqb/tests/expr.rs @@ -12,10 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod common; + use insta::assert_snapshot; use pqb::expr::Expr; use pqb::query::Select; +use crate::common::ValidateSql; + #[test] fn select_function() { assert_snapshot!( @@ -25,7 +29,8 @@ fn select_function() { Expr::value(10), Expr::value("[]"), ])) - .to_sql(), + .to_sql() + .validate(), @"SELECT int8range(1, 10, '[]')" ); } @@ -47,7 +52,8 @@ fn select_range_ops() { .and_where(left.clone().does_not_extend_right_of(right.clone())) .and_where(left.clone().does_not_extend_left_of(right.clone())) .and_where(left.adjacent_to(right)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT * FROM "ranges" WHERE "r1" @> "r2" AND "r1" <@ "r2" AND "r1" && "r2" AND "r1" << "r2" AND "r1" >> "r2" AND "r1" &< "r2" AND "r1" &> "r2" AND "r1" -|- "r2""# ); } diff --git a/pqb/tests/index.rs b/pqb/tests/index.rs index a026169..82deeba 100644 --- a/pqb/tests/index.rs +++ b/pqb/tests/index.rs @@ -12,10 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod common; + use insta::assert_snapshot; use pqb::expr::Expr; use pqb::index::CreateIndex; +use crate::common::ValidateSql; + #[test] fn create_index_gist_with_options() { assert_snapshot!( @@ -25,7 +29,8 @@ fn create_index_gist_with_options() { .gist() .with_option("fillfactor", 80) .with_option("buffering", "auto") - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX ON "spatial" USING gist ("geom") WITH ("fillfactor" = 80, "buffering" = 'auto')"# ); } @@ -38,7 +43,8 @@ fn create_index_brin_with_options() { .column("created_at") .brin() .with_options([("pages_per_range", Expr::value(32)), ("autosummarize", Expr::value(true))]) - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX ON "events" USING brin ("created_at") WITH ("pages_per_range" = 32, "autosummarize" = TRUE)"# ); } @@ -50,7 +56,8 @@ fn create_index_hash() { .table("tokens") .column("value") .hash() - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX ON "tokens" USING hash ("value")"# ); } @@ -62,7 +69,8 @@ fn create_index_named() { .name("idx_tokens_value") .table("tokens") .column("value") - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX "idx_tokens_value" ON "tokens" ("value")"# ); } @@ -76,7 +84,8 @@ fn create_index_if_not_exists_named() { .column("created_at") .brin() .if_not_exists() - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX IF NOT EXISTS "idx_events_created_at_brin" ON "events" USING brin ("created_at")"# ); } @@ -91,7 +100,8 @@ fn create_index_named_with_options() { .gist() .with_option("fillfactor", 90) .with_option("buffering", "auto") - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX "idx_spatial_geom_gist" ON "spatial" USING gist ("geom") WITH ("fillfactor" = 90, "buffering" = 'auto')"# ); } @@ -105,7 +115,8 @@ fn create_index_if_not_exists_custom_method() { .column("value") .if_not_exists() .using("hnsw") - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX IF NOT EXISTS "idx_tokens_value_hnsw" ON "tokens" USING hnsw ("value")"# ); } @@ -118,7 +129,8 @@ fn create_index_include_columns() { .table("orders") .column("customer_id") .include_columns(["id", "created_at"]) - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX "idx_orders_customer" ON "orders" ("customer_id") INCLUDE ("id", "created_at")"# ); } @@ -131,7 +143,8 @@ fn create_index_partial() { .table("sessions") .column("user_id") .index_where(Expr::column("expires_at").gt(Expr::current_timestamp())) - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX "idx_sessions_active" ON "sessions" ("user_id") WHERE "expires_at" > CURRENT_TIMESTAMP"# ); } @@ -142,7 +155,8 @@ fn create_index_expression() { CreateIndex::new() .table("users") .expr(Expr::function("lower", [Expr::column("email")])) - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX ON "users" (lower("email"))"# ); } @@ -154,7 +168,8 @@ fn create_index_expression_with_column() { .table("metrics") .expr(Expr::column("value").add(Expr::value(1))) .column("created_at") - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX ON "metrics" (("value" + 1), "created_at")"# ); } @@ -168,7 +183,8 @@ fn create_index_concurrently() { .column("customer_id") .concurrently() .if_not_exists() - .to_sql(), + .to_sql() + .validate(), @r#"CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_orders_customer" ON "orders" ("customer_id")"# ); } diff --git a/pqb/tests/insert.rs b/pqb/tests/insert.rs index 32a7706..c322fcb 100644 --- a/pqb/tests/insert.rs +++ b/pqb/tests/insert.rs @@ -12,24 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod common; + use insta::assert_snapshot; use pqb::expr::Expr; use pqb::func::FunctionCall; use pqb::query::Insert; use pqb::query::OnConflict; +use crate::common::ValidateSql; + #[test] fn insert_on_conflict_1() { - let query = Insert::new() - .into_table("glyph") - .columns(["aspect", "image"]) - .values([ - "04108048005887010020060000204E0180400400".into(), - 42.0321.into(), - ]) - .on_conflict(OnConflict::column("id").update_column("aspect")); assert_snapshot!( - query.to_sql(), + Insert::new() + .into_table("glyph") + .columns(["aspect", "image"]) + .values([ + "04108048005887010020060000204E0180400400".into(), + 42.0321.into(), + ]) + .on_conflict(OnConflict::column("id").update_column("aspect")) + .to_sql() + .validate(), @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('04108048005887010020060000204E0180400400', 42.0321) ON CONFLICT ("id") DO UPDATE SET "aspect" = "excluded"."aspect""# ); } @@ -48,7 +53,8 @@ fn insert_on_conflict_2() { OnConflict::columns(["id", "aspect"]) .update_columns(["aspect", "image"]) ) - .to_sql(), + .to_sql() + .validate(), @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('04108048005887010020060000204E0180400400', 42.0321) ON CONFLICT ("id", "aspect") DO UPDATE SET "aspect" = "excluded"."aspect", "image" = "excluded"."image""# ); } @@ -70,7 +76,8 @@ fn insert_on_conflict_3() { ("image", 42.0321.into()), ]) ) - .to_sql(), + .to_sql() + .validate(), @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('04108048005887010020060000204E0180400400', 42.0321) ON CONFLICT ("id", "aspect") DO UPDATE SET "aspect" = '04108048005887010020060000204E0180400400', "image" = 42.0321"# ); } @@ -88,7 +95,8 @@ fn insert_on_conflict_4() { .on_conflict( OnConflict::columns(["id", "aspect"]).value("image", Expr::value(1).add(2)) ) - .to_sql(), + .to_sql() + .validate(), @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('04108048005887010020060000204E0180400400', 42.0321) ON CONFLICT ("id", "aspect") DO UPDATE SET "image" = 1 + 2"# ); } @@ -108,7 +116,8 @@ fn insert_on_conflict_5() { .value("aspect", Expr::value("04108048005887010020060000204E0180400400")) .update_column("image") ) - .to_sql(), + .to_sql() + .validate(), @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('04108048005887010020060000204E0180400400', 42.0321) ON CONFLICT ("id", "aspect") DO UPDATE SET "aspect" = '04108048005887010020060000204E0180400400', "image" = "excluded"."image""# ); } @@ -128,7 +137,8 @@ fn insert_on_conflict_6() { .update_column("aspect") .value("image", Expr::value(1).add(2)) ) - .to_sql(), + .to_sql() + .validate(), @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('04108048005887010020060000204E0180400400', 42.0321) ON CONFLICT ("id", "aspect") DO UPDATE SET "aspect" = "excluded"."aspect", "image" = 1 + 2"# ); } @@ -144,7 +154,8 @@ fn insert_on_conflict_7() { 42.0321.into(), ]) .on_conflict(OnConflict::expr(Expr::column("id")).update_column("aspect")) - .to_sql(), + .to_sql() + .validate(), @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('04108048005887010020060000204E0180400400', 42.0321) ON CONFLICT ("id") DO UPDATE SET "aspect" = "excluded"."aspect""# ); } @@ -163,7 +174,8 @@ fn insert_on_conflict_8() { OnConflict::exprs([Expr::column("id"), Expr::column("aspect")]) .update_column("aspect") ) - .to_sql(), + .to_sql() + .validate(), @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('04108048005887010020060000204E0180400400', 42.0321) ON CONFLICT ("id", "aspect") DO UPDATE SET "aspect" = "excluded"."aspect""# ); } @@ -185,8 +197,9 @@ fn insert_on_conflict_9() { ]) .update_column("aspect") ) - .to_sql(), - @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('04108048005887010020060000204E0180400400', 42.0321) ON CONFLICT ("id", LOWER("tokens")) DO UPDATE SET "aspect" = "excluded"."aspect""# + .to_sql() + .validate(), + @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('04108048005887010020060000204E0180400400', 42.0321) ON CONFLICT ("id", (LOWER("tokens"))) DO UPDATE SET "aspect" = "excluded"."aspect""# ); } @@ -198,7 +211,8 @@ fn insert_on_conflict_10() { .columns(["id", "name"]) .values([15.into(), "CyberFont Sans Serif".into()]) .on_conflict(OnConflict::constraint("name_unique").do_nothing()) - .to_sql(), + .to_sql() + .validate(), @r#"INSERT INTO "font" ("id", "name") VALUES (15, 'CyberFont Sans Serif') ON CONFLICT ON CONSTRAINT "name_unique" DO NOTHING"# ); } @@ -214,8 +228,9 @@ fn insert_on_conflict_11() { OnConflict::exprs([Expr::column("name"), Expr::is_null(Expr::column("variant"))]) .do_nothing() ) - .to_sql(), - @r#"INSERT INTO "font" ("id", "name") VALUES (20, 'Monospaced terminal') ON CONFLICT ("name", "variant" IS NULL) DO NOTHING"# + .to_sql() + .validate(), + @r#"INSERT INTO "font" ("id", "name") VALUES (20, 'Monospaced terminal') ON CONFLICT ("name", ("variant" IS NULL)) DO NOTHING"# ); } @@ -227,7 +242,8 @@ fn insert_on_conflict_do_nothing() { .columns(["aspect", "image"]) .values(["abcd".into(), 42.0321.into()]) .on_conflict(OnConflict::columns(["id", "aspect"]).do_nothing()) - .to_sql(), + .to_sql() + .validate(), @r#"INSERT INTO "glyph" ("aspect", "image") VALUES ('abcd', 42.0321) ON CONFLICT ("id", "aspect") DO NOTHING"# ); } diff --git a/pqb/tests/select.rs b/pqb/tests/select.rs index e70b24f..ec68fae 100644 --- a/pqb/tests/select.rs +++ b/pqb/tests/select.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod common; + use insta::assert_compact_debug_snapshot; use insta::assert_snapshot; use pqb::expr::Expr; @@ -19,15 +21,18 @@ use pqb::query::Order; use pqb::query::Select; use pqb::types::Asterisk; +use crate::common::ValidateSql; + #[test] fn select_0() { - assert_snapshot!(Select::new().expr(Expr::value(1)).to_sql(), @"SELECT 1"); + assert_snapshot!(Select::new().expr(Expr::value(1)).to_sql().validate(), @"SELECT 1"); assert_snapshot!( Select::new() .expr(Expr::column("n")) .from("tbl") .and_where(Expr::column("region").eq(Expr::value("CN"))) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "n" FROM "tbl" WHERE "region" = 'CN'"# ); } @@ -40,7 +45,8 @@ fn select_1() { .from("character") .limit(10) .offset(100) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character", "size_w", "size_h" FROM "character" LIMIT 10 OFFSET 100"# ); } @@ -52,7 +58,8 @@ fn select_2() { .columns(["character", "size_w", "size_h"]) .from("character") .and_where(Expr::column("size_w").eq(3)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character", "size_w", "size_h" FROM "character" WHERE "size_w" = 3"# ); } @@ -65,7 +72,8 @@ fn select_3() { .from("character") .and_where(Expr::column("size_w").eq(3)) .and_where(Expr::column("size_h").eq(4)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character", "size_w", "size_h" FROM "character" WHERE "size_w" = 3 AND "size_h" = 4"# ); } @@ -79,7 +87,8 @@ fn select_4() { Select::new().columns(["image", "aspect"]).from("glyph"), "subglyph", ) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "aspect" FROM (SELECT "image", "aspect" FROM "glyph") AS "subglyph""# ); } @@ -91,7 +100,8 @@ fn select_5() { .column(("glyph", "image")) .from("glyph") .and_where(Expr::column(("glyph", "aspect")).is_in([3, 4])) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "glyph"."image" FROM "glyph" WHERE "glyph"."aspect" IN (3, 4)"# ); } @@ -105,7 +115,8 @@ fn select_6() { .from("glyph") .group_by_columns(["aspect"]) .and_having(Expr::column("aspect").gt(2)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "aspect", MAX("image") FROM "glyph" GROUP BY "aspect" HAVING "aspect" > 2"# ); } @@ -117,7 +128,8 @@ fn select_7() { .column("aspect") .from("glyph") .and_where(Expr::column("aspect").if_null(0).gt(2)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "aspect" FROM "glyph" WHERE COALESCE("aspect", 0) > 2"# ); } @@ -132,7 +144,8 @@ fn select_8() { "font", Expr::column(("character", "font_id")).eq(Expr::column(("font", "id"))), ) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" LEFT JOIN "font" ON "character"."font_id" = "font"."id""# ); } @@ -151,7 +164,8 @@ fn select_9() { "glyph", Expr::column(("character", "character")).eq(Expr::column(("glyph", "image"))), ) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" LEFT JOIN "font" ON "character"."font_id" = "font"."id" INNER JOIN "glyph" ON "character"."character" = "glyph"."image""# ); } @@ -168,7 +182,8 @@ fn select_10() { .eq(Expr::column(("font", "id"))) .and(Expr::column(("character", "font_id")).eq(Expr::column(("font", "id")))), ) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" LEFT JOIN "font" ON "character"."font_id" = "font"."id" AND "character"."font_id" = "font"."id""# ); } @@ -184,7 +199,8 @@ fn select_11() { Order::column("image").desc(), Order::column(("glyph", "aspect")).asc() ]) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "aspect" FROM "glyph" WHERE COALESCE("aspect", 0) > 2 ORDER BY "image" DESC, "glyph"."aspect" ASC"# ); } @@ -197,7 +213,8 @@ fn select_12() { .from("glyph") .and_where(Expr::column("aspect").if_null(0).gt(2)) .order_by([Order::column("id"), Order::column("aspect").desc()]) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "aspect" FROM "glyph" WHERE COALESCE("aspect", 0) > 2 ORDER BY "id" ASC, "aspect" DESC"# ); } @@ -213,7 +230,8 @@ fn select_13() { Order::column(("glyph", "id")), Order::column(("glyph", "aspect")).desc(), ]) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "aspect" FROM "glyph" WHERE COALESCE("aspect", 0) > 2 ORDER BY "glyph"."id" ASC, "glyph"."aspect" DESC"# ); } @@ -227,7 +245,8 @@ fn select_14() { .from("glyph") .group_by_columns([("glyph", "id"), ("glyph", "aspect")]) .and_having(Expr::column("aspect").gt(2)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id", "aspect", MAX("image") FROM "glyph" GROUP BY "glyph"."id", "glyph"."aspect" HAVING "aspect" > 2"# ); } @@ -239,7 +258,8 @@ fn select_15() { .column("character") .from("character") .and_where(Expr::column("font_id").is_null()) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" WHERE "font_id" IS NULL"# ); } @@ -252,7 +272,8 @@ fn select_16() { .from("character") .and_where(Expr::column("font_id").is_null()) .and_where(Expr::column("character").is_not_null()) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" WHERE "font_id" IS NULL AND "character" IS NOT NULL"# ); } @@ -264,7 +285,8 @@ fn select_17() { .column(("glyph", "image")) .from("glyph") .and_where(Expr::column(("glyph", "aspect")).between(3, 5)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "glyph"."image" FROM "glyph" WHERE "glyph"."aspect" BETWEEN 3 AND 5"# ); } @@ -277,7 +299,8 @@ fn select_18() { .from("glyph") .and_where(Expr::column("aspect").between(3, 5)) .and_where(Expr::column("aspect").not_between(8, 10)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "aspect" FROM "glyph" WHERE ("aspect" BETWEEN 3 AND 5) AND ("aspect" NOT BETWEEN 8 AND 10)"# ); } @@ -289,7 +312,8 @@ fn select_19() { .column("character") .from("character") .and_where(Expr::column("character").eq("A")) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" WHERE "character" = 'A'"# ); } @@ -301,7 +325,8 @@ fn select_20() { .column("character") .from("character") .and_where(Expr::column("character").like("A")) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" WHERE "character" LIKE 'A'"# ); } @@ -318,7 +343,8 @@ fn select_21() { .or(Expr::column("character").like("%B")) .or(Expr::column("character").like("%C%")), ) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" WHERE "character" LIKE 'A%' OR "character" LIKE '%B' OR "character" LIKE '%C%'"# ); } @@ -339,7 +365,8 @@ fn select_22() { .like("F") .or(Expr::column("character").like("G")), ) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" WHERE ("character" LIKE 'C' OR ("character" LIKE 'D' AND "character" LIKE 'E')) AND ("character" LIKE 'F' OR "character" LIKE 'G')"# ); } @@ -351,7 +378,8 @@ fn select_23() { Select::new() .column("character") .from("character") - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character""# ); } @@ -365,7 +393,8 @@ fn select_24() { .column("character") .from("character") .and_where(Expr::column("font_id").eq(5)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" WHERE "font_id" = 5"# ); } @@ -377,7 +406,8 @@ fn select_25() { .column("character") .from("character") .and_where(Expr::column("size_w").mul(2).eq(Expr::column("size_h").div(2))) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" WHERE "size_w" * 2 = "size_h" / 2"# ); } @@ -389,7 +419,8 @@ fn select_26() { .column("character") .from("character") .and_where(Expr::column("size_w").add(1).mul(2).eq(Expr::column("size_h").div(2).sub(1))) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" FROM "character" WHERE ("size_w" + 1) * 2 = ("size_h" / 2) - 1"# ); } @@ -403,7 +434,8 @@ fn select_27() { .and_where(Expr::column("size_w").eq(3)) .and_where(Expr::column("size_h").eq(4)) .and_where(Expr::column("size_h").eq(5)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character", "size_w", "size_h" FROM "character" WHERE "size_w" = 3 AND "size_h" = 4 AND "size_h" = 5"# ); } @@ -421,7 +453,8 @@ fn select_28() { .or(Expr::column("size_h").eq(4)) .or(Expr::column("size_h").eq(5)), ) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character", "size_w", "size_h" FROM "character" WHERE "size_w" = 3 OR "size_h" = 4 OR "size_h" = 5"# ); } @@ -435,7 +468,8 @@ fn select_30() { .and_where( Expr::column("size_w").mul(2).add(Expr::column("size_h").div(3)).eq(4) ) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character", "size_w", "size_h" FROM "character" WHERE ("size_w" * 2) + ("size_h" / 3) = 4"# ); } @@ -445,7 +479,8 @@ fn select_31() { assert_snapshot!( Select::new() .expr((1..10_i32).fold(Expr::value(0), |expr, i| expr.add(i))) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9"# ); } @@ -456,7 +491,8 @@ fn select_32() { Select::new() .expr_as(Expr::column("character"), "C") .from("character") - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character" AS "C" FROM "character""# ); } @@ -470,7 +506,8 @@ fn select_33a() { .and_where(Expr::column("aspect").in_subquery( Select::new().expr(Expr::custom("3 + 2 * 2")), )) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "image" FROM "glyph" WHERE "aspect" IN (SELECT 3 + 2 * 2)"# ); } @@ -484,7 +521,8 @@ fn select_33b() { .and_where(Expr::column("aspect").in_subquery( Select::new().expr(Expr::column("ignore")), )) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "image" FROM "glyph" WHERE "aspect" IN (SELECT "ignore")"# ); } @@ -504,7 +542,8 @@ fn select_34() { .or(Expr::column("aspect").gt(12).and(Expr::column("aspect").lt(18))) .or(Expr::column("aspect").gt(32)), ) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "aspect", MAX("image") FROM "glyph" GROUP BY "aspect" HAVING "aspect" > 2 OR "aspect" < 8 OR ("aspect" > 12 AND "aspect" < 18) OR "aspect" > 32"# ); } @@ -516,7 +555,8 @@ fn select_35() { .column("id") .from("glyph") .and_where(Expr::column("aspect").is_null()) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE "aspect" IS NULL"# ); } @@ -528,7 +568,8 @@ fn select_36() { .column("id") .from("glyph") .and_where(Expr::column("aspect").is_null()) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE "aspect" IS NULL"# ); } @@ -576,7 +617,7 @@ fn select_38() { .to_values() .into_parts(); assert_snapshot!( - statement, + statement.validate(), @r#"SELECT "id" FROM "glyph" WHERE "aspect" IS NULL OR "aspect" IS NOT NULL"# ); assert!(values.is_empty()); @@ -595,7 +636,7 @@ fn select_39() { .to_values() .into_parts(); assert_snapshot!( - statement, + statement.validate(), @r#"SELECT "id" FROM "glyph" WHERE "aspect" IS NULL AND "aspect" IS NOT NULL"# ); assert!(values.is_empty()); @@ -612,7 +653,8 @@ fn select_40() { .is_null() .or(Expr::column("aspect").is_not_null().and(Expr::column("aspect").lt(8))) ) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE "aspect" IS NULL OR ("aspect" IS NOT NULL AND "aspect" < 8)"# ); } @@ -626,7 +668,8 @@ fn select_41() { .from("glyph") .group_by_columns(["aspect"]) .and_having(Expr::column("aspect").gt(2)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "aspect", MAX("image") FROM "glyph" GROUP BY "aspect" HAVING "aspect" > 2"# ); } @@ -639,7 +682,8 @@ fn select_42() { .from("glyph") .and_where(Expr::column("aspect").lt(8)) .and_where(Expr::column("aspect").is_not_null()) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE "aspect" < 8 AND "aspect" IS NOT NULL"# ); } @@ -651,7 +695,8 @@ fn select_43() { .column("id") .from("glyph") .and_where(Expr::custom("TRUE")) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE TRUE"# ); } @@ -663,7 +708,8 @@ fn select_44() { .column("id") .from("glyph") .and_where(Expr::column("aspect").lt(8).not()) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE NOT "aspect" < 8"# ); } @@ -675,7 +721,8 @@ fn select_45() { .column("id") .from("glyph") .and_where(Expr::column("aspect").lt(8).or(Expr::column("aspect").is_not_null()).not()) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE NOT ("aspect" < 8 OR "aspect" IS NOT NULL)"# ); } @@ -687,7 +734,8 @@ fn select_46() { .column("id") .from("glyph") .and_where(Expr::column("aspect").lt(8).not()) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE NOT "aspect" < 8"# ); } @@ -699,7 +747,8 @@ fn select_47() { .column("id") .from("glyph") .and_where(Expr::column("aspect").lt(8).and(Expr::column("aspect").is_not_null()).not()) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE NOT ("aspect" < 8 AND "aspect" IS NOT NULL)"# ); } @@ -711,7 +760,8 @@ fn select_48() { .column("id") .from("glyph") .and_where(Expr::tuple([Expr::column("aspect"), Expr::value(100)]).lt(Expr::tuple([Expr::value(8), Expr::value(100)]))) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE ("aspect", 100) < (8, 100)"# ); } @@ -723,7 +773,8 @@ fn select_48a() { .column("id") .from("glyph") .and_where(Expr::tuple([Expr::column("aspect"), Expr::value("100")]).in_tuples([[Expr::value(8), Expr::value("100")]])) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "glyph" WHERE ("aspect", '100') IN ((8, '100'))"# ); } @@ -734,7 +785,8 @@ fn select_49() { Select::new() .expr(Expr::asterisk()) .from("character") - .to_sql(), + .to_sql() + .validate(), @r#"SELECT * FROM "character""# ); } @@ -747,7 +799,8 @@ fn select_50() { .column(("font", "name")) .from("character") .inner_join("font", Expr::column(("character", "font_id")).eq(Expr::column(("font", "id")))) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "character".*, "font"."name" FROM "character" INNER JOIN "font" ON "character"."font_id" = "font"."id""# ); } @@ -763,7 +816,8 @@ fn select_51() { Order::column("image").desc().nulls_first(), Order::column(("glyph", "aspect")).asc().nulls_last() ]) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "aspect" FROM "glyph" WHERE COALESCE("aspect", 0) > 2 ORDER BY "image" DESC NULLS FIRST, "glyph"."aspect" ASC NULLS LAST"# ); } diff --git a/pqb/tests/types.rs b/pqb/tests/types.rs index 457d3fa..aa1c1a3 100644 --- a/pqb/tests/types.rs +++ b/pqb/tests/types.rs @@ -12,11 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod common; + use insta::assert_snapshot; use pqb::query::Select; use pqb::types::Asterisk; use pqb::types::Iden; +use crate::common::ValidateSql; + #[test] fn iden_escape_detection() { assert!(Iden::new("alpha_1").is_escaped()); @@ -29,17 +33,18 @@ fn iden_escape_detection() { #[test] fn iden_rendering() { assert_snapshot!( - Select::new().column(Iden::new("simple")).to_sql(), + Select::new().column(Iden::new("simple")).to_sql().validate(), @r#"SELECT "simple""# ); assert_snapshot!( - Select::new().column(Iden::new("has space")).to_sql(), + Select::new().column(Iden::new("has space")).to_sql().validate(), @r#"SELECT "has space""# ); assert_snapshot!( Select::new() .column(Iden::new(r#"has"quote"#)) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "has""quote""# ); } @@ -47,28 +52,31 @@ fn iden_rendering() { #[test] fn qualified_names_rendering() { assert_snapshot!( - Select::new().column("id").from(("audit", "events")).to_sql(), + Select::new().column("id").from(("audit", "events")).to_sql().validate(), @r#"SELECT "id" FROM "audit"."events""# ); assert_snapshot!( Select::new() .column("id") .from(("analytics", "audit", "events")) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "id" FROM "analytics"."audit"."events""# ); assert_snapshot!( Select::new() .column(("audit", "events", "id")) .from(("audit", "events")) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "audit"."events"."id" FROM "audit"."events""# ); assert_snapshot!( Select::new() .column(("analytics", "audit", "events", Asterisk)) .from(("analytics", "audit", "events")) - .to_sql(), + .to_sql() + .validate(), @r#"SELECT "analytics"."audit"."events".* FROM "analytics"."audit"."events""# ); }