diff --git a/Cargo.lock b/Cargo.lock index 5dc3c559e..8d488c4af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,17 +302,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "auto_impl" version = "1.2.0" @@ -845,7 +834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", - "half 2.6.0", + "half", ] [[package]] @@ -859,17 +848,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "bitflags 1.3.2", - "textwrap", - "unicode-width", -] - [[package]] name = "clap" version = "4.5.23" @@ -1006,32 +984,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "criterion" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" -dependencies = [ - "atty", - "cast", - "clap 2.34.0", - "criterion-plot 0.4.5", - "csv", - "itertools 0.10.5", - "lazy_static", - "num-traits", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_cbor", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - [[package]] name = "criterion" version = "0.5.1" @@ -1041,8 +993,8 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.23", - "criterion-plot 0.5.0", + "clap", + "criterion-plot", "is-terminal", "itertools 0.10.5", "num-traits", @@ -1058,16 +1010,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "criterion-plot" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" -dependencies = [ - "cast", - "itertools 0.10.5", -] - [[package]] name = "criterion-plot" version = "0.5.0" @@ -1150,27 +1092,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "csv" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" -dependencies = [ - "memchr", -] - [[package]] name = "dashmap" version = "5.5.3" @@ -1782,12 +1703,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "half" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" - [[package]] name = "half" version = "2.6.0" @@ -1850,15 +1765,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.9" @@ -2823,7 +2729,7 @@ name = "pgt_completions" version = "0.0.0" dependencies = [ "async-std", - "criterion 0.5.1", + "criterion", "fuzzy-matcher", "pgt_schema_cache", "pgt_test_utils", @@ -3099,7 +3005,7 @@ dependencies = [ name = "pgt_statement_splitter" version = "0.0.0" dependencies = [ - "criterion 0.3.6", + "criterion", "ntest", "pgt_diagnostics", "pgt_lexer", @@ -3134,7 +3040,7 @@ name = "pgt_test_utils" version = "0.0.0" dependencies = [ "anyhow", - "clap 4.5.23", + "clap", "dotenv", "pgt_treesitter_grammar", "sqlx", @@ -3173,7 +3079,7 @@ dependencies = [ name = "pgt_treesitter" version = "0.0.0" dependencies = [ - "clap 4.5.23", + "clap", "pgt_schema_cache", "pgt_test_utils", "pgt_text_size", @@ -3202,9 +3108,11 @@ dependencies = [ name = "pgt_typecheck" version = "0.0.0" dependencies = [ + "criterion", "globset", "insta", "itertools 0.14.0", + "once_cell", "pgt_console", "pgt_diagnostics", "pgt_query", @@ -3213,6 +3121,7 @@ dependencies = [ "pgt_text_size", "pgt_treesitter", "pgt_treesitter_grammar", + "regex", "sqlx", "tokio", "tree-sitter", @@ -4003,16 +3912,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half 1.8.3", - "serde", -] - [[package]] name = "serde_core" version = "1.0.225" @@ -4627,15 +4526,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 485ac000b..e0491ea3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ biome_js_syntax = "0.5.7" biome_rowan = "0.5.7" biome_string_case = "0.5.8" bpaf = { version = "0.9.15", features = ["derive"] } +criterion = "0.5" crossbeam = "0.8.4" enumflags2 = "0.7.11" ignore = "0.4.23" diff --git a/crates/pgt_completions/Cargo.toml b/crates/pgt_completions/Cargo.toml index c20b7d897..9915f522c 100644 --- a/crates/pgt_completions/Cargo.toml +++ b/crates/pgt_completions/Cargo.toml @@ -31,7 +31,7 @@ sqlx.workspace = true tokio = { version = "1.41.1", features = ["full"] } [dev-dependencies] -criterion = "0.5.1" +criterion.workspace = true pgt_test_utils.workspace = true [lib] diff --git a/crates/pgt_statement_splitter/Cargo.toml b/crates/pgt_statement_splitter/Cargo.toml index 45a42ebc6..38c78bf68 100644 --- a/crates/pgt_statement_splitter/Cargo.toml +++ b/crates/pgt_statement_splitter/Cargo.toml @@ -19,8 +19,8 @@ pgt_text_size.workspace = true regex.workspace = true [dev-dependencies] -criterion = "0.3" -ntest = "0.9.3" +criterion.workspace = true +ntest = "0.9.3" [[bench]] harness = false diff --git a/crates/pgt_typecheck/Cargo.toml b/crates/pgt_typecheck/Cargo.toml index 1c326a675..29101293c 100644 --- a/crates/pgt_typecheck/Cargo.toml +++ b/crates/pgt_typecheck/Cargo.toml @@ -14,6 +14,7 @@ version = "0.0.0" [dependencies] globset = "0.4.16" itertools = { version = "0.14.0" } +once_cell = "1.20.2" pgt_console.workspace = true pgt_diagnostics.workspace = true pgt_query.workspace = true @@ -21,14 +22,20 @@ pgt_schema_cache.workspace = true pgt_text_size.workspace = true pgt_treesitter.workspace = true pgt_treesitter_grammar.workspace = true +regex = "1.11.1" sqlx.workspace = true tokio.workspace = true tree-sitter.workspace = true uuid = { version = "1.18.1", features = ["v4"] } [dev-dependencies] +criterion.workspace = true insta.workspace = true pgt_test_utils.workspace = true +[[bench]] +harness = false +name = "error_rewriting" + [lib] doctest = false diff --git a/crates/pgt_typecheck/benches/error_rewriting.rs b/crates/pgt_typecheck/benches/error_rewriting.rs new file mode 100644 index 000000000..590d665b9 --- /dev/null +++ b/crates/pgt_typecheck/benches/error_rewriting.rs @@ -0,0 +1,55 @@ +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use pgt_typecheck::IdentifierReplacement; +use pgt_typecheck::diagnostics::rewrite_error_message; + +fn benchmark_error_rewriting(c: &mut Criterion) { + let replacement = IdentifierReplacement { + original_name: "user_id".to_string(), + original_range: 0..7, + default_value: "'00000000-0000-0000-0000-000000000000'".to_string(), + type_name: "uuid".to_string(), + }; + + // test case 1: matching the first pattern (most common case) + c.bench_function("rewrite_invalid_input_syntax", |b| { + b.iter(|| { + rewrite_error_message( + black_box(r#"invalid input syntax for type integer: "00000000-0000-0000-0000-000000000000""#), + black_box(&replacement), + ) + }) + }); + + // test case 2: matching the operator pattern + c.bench_function("rewrite_operator_does_not_exist", |b| { + b.iter(|| { + rewrite_error_message( + black_box("operator does not exist: integer + text"), + black_box(&replacement), + ) + }) + }); + + // test case 3: no pattern matches (fallback) + c.bench_function("rewrite_fallback", |b| { + b.iter(|| { + rewrite_error_message( + black_box("some other error message that doesn't match any pattern"), + black_box(&replacement), + ) + }) + }); + + // test case 4: longer error message with first pattern + c.bench_function("rewrite_long_message", |b| { + b.iter(|| { + rewrite_error_message( + black_box(r#"invalid input syntax for type timestamp: "00000000-0000-0000-0000-000000000000" at character 45"#), + black_box(&replacement), + ) + }) + }); +} + +criterion_group!(benches, benchmark_error_rewriting); +criterion_main!(benches); diff --git a/crates/pgt_typecheck/src/diagnostics.rs b/crates/pgt_typecheck/src/diagnostics.rs index 994bf8e19..cc802d799 100644 --- a/crates/pgt_typecheck/src/diagnostics.rs +++ b/crates/pgt_typecheck/src/diagnostics.rs @@ -1,10 +1,14 @@ use std::io; +use once_cell::sync::Lazy; use pgt_console::markup; use pgt_diagnostics::{Advices, Diagnostic, LogCategory, MessageAndDescription, Severity, Visit}; -use pgt_text_size::{TextRange, TextRangeReplacement, TextSize}; +use pgt_text_size::{TextRange, TextSize}; +use regex::Regex; use sqlx::postgres::{PgDatabaseError, PgSeverity}; +use crate::typed_identifier::{IdentifierReplacement, TypedReplacement}; + /// A specialized diagnostic for the typechecker. /// /// Type diagnostics are always **errors**. @@ -94,21 +98,78 @@ impl Advices for TypecheckAdvices { } } +/// Pattern and rewrite rule for error messages +struct ErrorRewriteRule { + pattern: Regex, + rewrite: fn(®ex::Captures, &IdentifierReplacement) -> String, +} + +static ERROR_REWRITE_RULES: Lazy> = Lazy::new(|| { + vec![ + ErrorRewriteRule { + pattern: Regex::new(r#"invalid input syntax for type ([\w\s]+): "([^"]*)""#).unwrap(), + rewrite: |caps, replacement| { + let expected_type = &caps[1]; + format!( + "`{}` is of type {}, not {}", + replacement.original_name, replacement.type_name, expected_type + ) + }, + }, + ErrorRewriteRule { + pattern: Regex::new(r#"operator does not exist: (.+)"#).unwrap(), + rewrite: |caps, replacement| { + let operator_expr = &caps[1]; + format!( + "operator does not exist: {} (parameter `{}` is of type {})", + operator_expr, replacement.original_name, replacement.type_name + ) + }, + }, + ] +}); + +/// Rewrites Postgres error messages to be more user-friendly +pub fn rewrite_error_message( + pg_error_message: &str, + replacement: &IdentifierReplacement, +) -> String { + for rule in ERROR_REWRITE_RULES.iter() { + if let Some(caps) = rule.pattern.captures(pg_error_message) { + return (rule.rewrite)(&caps, replacement); + } + } + + // if we don't have a matching error-rewrite-rule, + // we'll fallback to replacing default values with their types, + // e.g. `""` is replaced with `text`. + let unquoted_default = replacement.default_value.trim_matches('\''); + pg_error_message + .replace(&format!("\"{}\"", unquoted_default), &replacement.type_name) + .replace(&format!("'{}'", unquoted_default), &replacement.type_name) +} + pub(crate) fn create_type_error( pg_err: &PgDatabaseError, ts: &tree_sitter::Tree, - txt_replacement: TextRangeReplacement, + typed_replacement: TypedReplacement, ) -> TypecheckDiagnostic { let position = pg_err.position().and_then(|pos| match pos { sqlx::postgres::PgErrorPosition::Original(pos) => Some(pos - 1), _ => None, }); - let range = position.and_then(|pos| { - let adjusted = txt_replacement.to_original_position(TextSize::new(pos.try_into().unwrap())); + let original_position = position.map(|p| { + let pos = TextSize::new(p.try_into().unwrap()); + typed_replacement + .text_replacement() + .to_original_position(pos) + }); + + let range = original_position.and_then(|pos| { ts.root_node() - .named_descendant_for_byte_range(adjusted.into(), adjusted.into()) + .named_descendant_for_byte_range(pos.into(), pos.into()) .map(|node| { TextRange::new( node.start_byte().try_into().unwrap(), @@ -128,8 +189,18 @@ pub(crate) fn create_type_error( PgSeverity::Log => Severity::Information, }; + let message = if let Some(pos) = original_position { + if let Some(replacement) = typed_replacement.find_type_at_position(pos) { + rewrite_error_message(pg_err.message(), replacement) + } else { + pg_err.to_string() + } + } else { + pg_err.to_string() + }; + TypecheckDiagnostic { - message: pg_err.to_string().into(), + message: message.into(), severity, span: range, advices: TypecheckAdvices { diff --git a/crates/pgt_typecheck/src/lib.rs b/crates/pgt_typecheck/src/lib.rs index ecb4f987b..93c2eb55d 100644 --- a/crates/pgt_typecheck/src/lib.rs +++ b/crates/pgt_typecheck/src/lib.rs @@ -1,5 +1,5 @@ -mod diagnostics; -mod typed_identifier; +pub mod diagnostics; +pub mod typed_identifier; pub use diagnostics::TypecheckDiagnostic; use diagnostics::create_type_error; @@ -10,7 +10,7 @@ use sqlx::postgres::PgDatabaseError; pub use sqlx::postgres::PgSeverity; use sqlx::{Executor, PgPool}; use typed_identifier::apply_identifiers; -pub use typed_identifier::{IdentifierType, TypedIdentifier}; +pub use typed_identifier::{IdentifierReplacement, IdentifierType, TypedIdentifier}; #[derive(Debug)] pub struct TypecheckParams<'a> { @@ -48,7 +48,7 @@ pub async fn check_sql( // each typecheck operation. conn.close_on_drop(); - let replacement = apply_identifiers( + let typed_replacement = apply_identifiers( params.identifiers, params.schema_cache, params.tree, @@ -68,13 +68,19 @@ pub async fn check_sql( conn.execute(&*search_path_query).await?; } - let res = conn.prepare(replacement.text()).await; + let res = conn + .prepare(typed_replacement.text_replacement().text()) + .await; match res { Ok(_) => Ok(None), Err(sqlx::Error::Database(err)) => { let pg_err = err.downcast_ref::(); - Ok(Some(create_type_error(pg_err, params.tree, replacement))) + Ok(Some(create_type_error( + pg_err, + params.tree, + typed_replacement, + ))) } Err(err) => Err(err), } diff --git a/crates/pgt_typecheck/src/typed_identifier.rs b/crates/pgt_typecheck/src/typed_identifier.rs index b51774e50..24e2dcb1b 100644 --- a/crates/pgt_typecheck/src/typed_identifier.rs +++ b/crates/pgt_typecheck/src/typed_identifier.rs @@ -1,5 +1,5 @@ use pgt_schema_cache::PostgresType; -use pgt_text_size::{TextRangeReplacement, TextRangeReplacementBuilder}; +use pgt_text_size::{TextRange, TextRangeReplacement, TextRangeReplacementBuilder, TextSize}; use pgt_treesitter::queries::{ParameterMatch, TreeSitterQueriesExecutor}; /// It is used to replace parameters within the SQL string. @@ -21,19 +21,67 @@ pub struct IdentifierType { pub is_array: bool, } +#[derive(Debug)] +pub struct IdentifierReplacement { + pub original_name: String, + pub original_range: std::ops::Range, + /// The default value with which the identifier was replaced, e.g. `''` for a TEXT param. + pub default_value: String, + pub type_name: String, +} + +/// Contains the text replacement along with metadata about which ranges correspond to which types. +#[derive(Debug)] +pub struct TypedReplacement { + text_replacement: TextRangeReplacement, + identifier_replacements: Vec, +} + +impl TypedReplacement { + pub fn new(sql: &str, replacements: Vec) -> Self { + let mut text_range_replacement_builder = TextRangeReplacementBuilder::new(sql); + + for replacement in &replacements { + let text_range: TextRange = replacement.original_range.clone().try_into().unwrap(); + text_range_replacement_builder.replace_range(text_range, &replacement.default_value); + } + + Self { + identifier_replacements: replacements, + text_replacement: text_range_replacement_builder.build(), + } + } + + /// Finds the original type at the given position in the adjusted text + pub(crate) fn find_type_at_position( + &self, + original_position: TextSize, + ) -> Option<&IdentifierReplacement> { + self.identifier_replacements.iter().find(|replacement| { + replacement + .original_range + .contains(&original_position.into()) + }) + } + + pub(crate) fn text_replacement(&self) -> &TextRangeReplacement { + &self.text_replacement + } +} + /// Applies the identifiers to the SQL string by replacing them with their default values. pub fn apply_identifiers<'a>( identifiers: Vec, schema_cache: &'a pgt_schema_cache::SchemaCache, cst: &'a tree_sitter::Tree, sql: &'a str, -) -> TextRangeReplacement { +) -> TypedReplacement { let mut executor = TreeSitterQueriesExecutor::new(cst.root_node(), sql); executor.add_query_results::(); // Collect all replacements first to avoid modifying the string while iterating - let replacements: Vec<_> = executor + let replacements: Vec = executor .get_iter(None) .filter_map(|q| { let m: &ParameterMatch = q.try_into().ok()?; @@ -44,22 +92,23 @@ pub fn apply_identifiers<'a>( let (identifier, position) = find_matching_identifier(&parts, &identifiers)?; // Resolve the type based on whether we're accessing a field of a composite type - let type_ = resolve_type(identifier, position, &parts, schema_cache)?; + let postgres_type = resolve_type(identifier, position, &parts, schema_cache)?; - Some((m.get_byte_range(), type_, identifier.type_.is_array)) - }) - .collect(); - - let mut text_range_replacement_builder = TextRangeReplacementBuilder::new(sql); + let default_value = + get_formatted_default_value(postgres_type, identifier.type_.is_array); - for (range, type_, is_array) in replacements { - let default_value = get_formatted_default_value(type_, is_array); + let replacement = IdentifierReplacement { + default_value, + original_name: identifier.name.clone().unwrap_or("".into()), + original_range: m.get_byte_range(), + type_name: identifier.type_.name.clone(), + }; - text_range_replacement_builder - .replace_range(range.clone().try_into().unwrap(), &default_value); - } + Some(replacement) + }) + .collect(); - text_range_replacement_builder.build() + TypedReplacement::new(sql, replacements) } /// Format the default value based on the type and whether it's an array @@ -307,7 +356,7 @@ mod tests { let replacement = super::apply_identifiers(identifiers, &schema_cache, &tree, input); assert_eq!( - replacement.text(), + replacement.text_replacement.text(), // the numeric parameters are filled with 0; "select 0 + 0 + 0 + 0 + 0 + 'critical'" ); @@ -357,7 +406,7 @@ mod tests { let replacement = super::apply_identifiers(identifiers, &schema_cache, &tree, input); assert_eq!( - replacement.text(), + replacement.text_replacement.text(), r#"select id from auth.users where email_change_confirm_status = '00000000-0000-0000-0000-000000000000' and email = '';"# ); } diff --git a/crates/pgt_typecheck/tests/diagnostics.rs b/crates/pgt_typecheck/tests/diagnostics.rs index 7b4964750..b80db1d92 100644 --- a/crates/pgt_typecheck/tests/diagnostics.rs +++ b/crates/pgt_typecheck/tests/diagnostics.rs @@ -177,3 +177,81 @@ async fn invalid_type_in_function(test_db: PgPool) { .test() .await; } + +#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] +async fn operator_does_not_exist(test_db: PgPool) { + // tests the pattern: operator does not exist: X + Y + // use boolean type which doesn't have + operator with text + let setup = r#" + create table public.products ( + id serial primary key, + is_active boolean not null, + product_name text not null + ); + "#; + + TestSetup { + name: "operator_does_not_exist", + setup: Some(setup), + query: r#"select is_active + product_name from public.products;"#, + test_db: &test_db, + typed_identifiers: vec![TypedIdentifier { + path: "calculate_total".to_string(), + name: Some("product_name".to_string()), + type_: IdentifierType { + schema: None, + name: "text".to_string(), + is_array: false, + }, + }], + } + .test() + .await; +} + +#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] +async fn function_parameter_type_with_multiple_words(test_db: PgPool) { + // the type "timestamp with time zone" has multiple words + let setup = r#" + create table public.products ( + id serial primary key, + released timestamp with time zone not null + ); + "#; + + TestSetup { + name: "testing_type_with_multiple_words", + setup: Some(setup), + query: r#"select * from public.products where released = pid;"#, + test_db: &test_db, + typed_identifiers: vec![TypedIdentifier { + path: "delete_product".to_string(), + name: Some("pid".to_string()), + type_: IdentifierType { + schema: None, + name: "uuid".to_string(), + is_array: false, + }, + }], + } + .test() + .await; + + TestSetup { + name: "testing_operator_type_with_multiple_words", + setup: None, + query: r#"delete from public.products where released > pid;"#, + test_db: &test_db, + typed_identifiers: vec![TypedIdentifier { + path: "delete_product".to_string(), + name: Some("pid".to_string()), + type_: IdentifierType { + schema: None, + name: "numeric".to_string(), + is_array: false, + }, + }], + } + .test() + .await; +} diff --git a/crates/pgt_typecheck/tests/snapshots/invalid_type_in_function_longer_default.snap b/crates/pgt_typecheck/tests/snapshots/invalid_type_in_function_longer_default.snap index c1494513b..91bf9322c 100644 --- a/crates/pgt_typecheck/tests/snapshots/invalid_type_in_function_longer_default.snap +++ b/crates/pgt_typecheck/tests/snapshots/invalid_type_in_function_longer_default.snap @@ -4,4 +4,4 @@ expression: content --- delete from public.contacts where id = ~~~uid~~~; -invalid input syntax for type integer: "00000000-0000-0000-0000-000000000000" +`uid` is of type uuid, not integer diff --git a/crates/pgt_typecheck/tests/snapshots/invalid_type_in_function_shorter_default.snap b/crates/pgt_typecheck/tests/snapshots/invalid_type_in_function_shorter_default.snap index 8be816b2f..791aaee21 100644 --- a/crates/pgt_typecheck/tests/snapshots/invalid_type_in_function_shorter_default.snap +++ b/crates/pgt_typecheck/tests/snapshots/invalid_type_in_function_shorter_default.snap @@ -4,4 +4,4 @@ expression: content --- delete from public.contacts where id = ~~~contact_name~~~; -invalid input syntax for type integer: "" +`contact_name` is of type text, not integer diff --git a/crates/pgt_typecheck/tests/snapshots/operator_does_not_exist.snap b/crates/pgt_typecheck/tests/snapshots/operator_does_not_exist.snap new file mode 100644 index 000000000..41ef8f98b --- /dev/null +++ b/crates/pgt_typecheck/tests/snapshots/operator_does_not_exist.snap @@ -0,0 +1,7 @@ +--- +source: crates/pgt_typecheck/tests/diagnostics.rs +expression: content +--- +select ~~~is_active + product_name~~~ from public.products; + +operator does not exist: boolean + unknown diff --git a/crates/pgt_typecheck/tests/snapshots/testing_operator_type_with_multiple_words.snap b/crates/pgt_typecheck/tests/snapshots/testing_operator_type_with_multiple_words.snap new file mode 100644 index 000000000..3732af603 --- /dev/null +++ b/crates/pgt_typecheck/tests/snapshots/testing_operator_type_with_multiple_words.snap @@ -0,0 +1,7 @@ +--- +source: crates/pgt_typecheck/tests/diagnostics.rs +expression: content +--- +delete from public.products where ~~~released > pid~~~; + +operator does not exist: timestamp with time zone > integer diff --git a/crates/pgt_typecheck/tests/snapshots/testing_type_with_multiple_words.snap b/crates/pgt_typecheck/tests/snapshots/testing_type_with_multiple_words.snap new file mode 100644 index 000000000..a7b35a773 --- /dev/null +++ b/crates/pgt_typecheck/tests/snapshots/testing_type_with_multiple_words.snap @@ -0,0 +1,7 @@ +--- +source: crates/pgt_typecheck/tests/diagnostics.rs +expression: content +--- +select * from public.products where released = ~~~pid~~~; + +`pid` is of type uuid, not timestamp with time zone