Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c0ed76c
ok?
juleswritescode Sep 19, 2025
fd0d198
ok?
juleswritescode Sep 26, 2025
d4ece2e
testie testie
juleswritescode Sep 26, 2025
dae4b9a
ok so far
juleswritescode Sep 26, 2025
c35c596
Merge branch 'main' of https://github.com/supabase-community/postgres…
juleswritescode Sep 26, 2025
2823ca0
ack
juleswritescode Sep 26, 2025
a2ae8f0
OKJ
juleswritescode Sep 26, 2025
802a0d2
install em toolz
juleswritescode Sep 26, 2025
934a44b
oke then
juleswritescode Sep 26, 2025
e3b5e30
amazing!
juleswritescode Sep 27, 2025
79cb776
Merge branch 'main' of https://github.com/supabase-community/postgres…
juleswritescode Sep 27, 2025
b472454
remove unneeded
juleswritescode Sep 27, 2025
48ade40
better api
juleswritescode Sep 27, 2025
38741b4
almost there…
juleswritescode Sep 27, 2025
7e63009
ok?
juleswritescode Sep 27, 2025
4e0ef81
ack ack
juleswritescode Sep 27, 2025
5f1d273
comment
juleswritescode Sep 27, 2025
d503333
Update crates/pgt_text_size/src/text_range_replacement.rs
juleswritescode Sep 27, 2025
bb9bfba
ok
juleswritescode Sep 27, 2025
3eb2f61
simple
juleswritescode Sep 27, 2025
79109a0
Merge branch 'main' into feat/longer-type-replacements
juleswritescode Oct 3, 2025
52cd007
intermediary
juleswritescode Oct 3, 2025
00fe8ef
resolve conflicts
juleswritescode Oct 3, 2025
e669094
ok then…
juleswritescode Oct 3, 2025
0cf105e
its not dead
juleswritescode Oct 3, 2025
7c7549c
no more spaces
juleswritescode Oct 10, 2025
53e2429
ok
juleswritescode Oct 10, 2025
41405a6
we got em tests
juleswritescode Oct 10, 2025
8cfef05
Update crates/pgt_typecheck/tests/diagnostics.rs
juleswritescode Oct 10, 2025
f653ea5
Merge branch 'main' into feat/shorter-type-replacements
juleswritescode Oct 10, 2025
6ec8115
better
juleswritescode Oct 10, 2025
1ddbc5e
Merge branch 'feat/shorter-type-replacements' of https://github.com/s…
juleswritescode Oct 10, 2025
36345c6
ack
juleswritescode Oct 10, 2025
22b4e54
kewl
juleswritescode Oct 10, 2025
b2ec636
very good, very good
juleswritescode Oct 10, 2025
63e57d5
ok
juleswritescode Oct 10, 2025
a94f549
readied
juleswritescode Oct 10, 2025
4b7053d
ok
juleswritescode Oct 10, 2025
97c566a
fix tests
juleswritescode Oct 10, 2025
8af181b
fixie fixie messagie
juleswritescode Oct 10, 2025
d37bdc0
lockfile
juleswritescode Oct 10, 2025
b4d513b
accept snapshot
juleswritescode Oct 10, 2025
40e4c93
no tracing
juleswritescode Oct 10, 2025
caee75a
Merge branch 'main' into feat/improved-type-msgs
juleswritescode Oct 15, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
kewl
  • Loading branch information
juleswritescode committed Oct 10, 2025
commit 22b4e548e881b4c61c5d887c54a8168b33ab1103
54 changes: 50 additions & 4 deletions crates/pgt_typecheck/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ use std::io;

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 sqlx::postgres::{PgDatabaseError, PgSeverity};

use crate::{IdentifierType, TypedReplacement};

/// A specialized diagnostic for the typechecker.
///
/// Type diagnostics are always **errors**.
Expand Down Expand Up @@ -94,18 +96,50 @@ impl Advices for TypecheckAdvices {
}
}

/// Finds the original type at the given position in the adjusted text
fn find_type_at_position(
adjusted_position: TextSize,
type_info: &[(TextRange, IdentifierType)],
) -> Option<&IdentifierType> {
type_info
.iter()
.find(|(range, _)| range.contains(adjusted_position))
.map(|(_, type_)| type_)
}

/// Rewrites error messages to show the original type name instead of the replaced literal value
fn rewrite_error_message(original_message: &str, identifier_type: &IdentifierType) -> String {
// pattern: invalid input syntax for type X: "literal_value"
// we want to replace "literal_value" with the type name

if let Some(colon_pos) = original_message.rfind(": ") {
let before_value = &original_message[..colon_pos];

// build the type name, including schema if present
let type_name = if let Some(schema) = &identifier_type.schema {
format!("{}.{}", schema, identifier_type.name)
} else {
identifier_type.name.clone()
};

format!("{}: {}", before_value, type_name)
} else {
original_message.to_string()
}
}

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 adjusted = typed_replacement.replacement.to_original_position(TextSize::new(pos.try_into().unwrap()));

ts.root_node()
.named_descendant_for_byte_range(adjusted.into(), adjusted.into())
Expand All @@ -128,8 +162,20 @@ pub(crate) fn create_type_error(
PgSeverity::Log => Severity::Information,
};

// check if the error position corresponds to a replaced parameter
let message = if let Some(pos) = position {
let adjusted_pos = TextSize::new(pos.try_into().unwrap());
if let Some(original_type) = find_type_at_position(adjusted_pos, &typed_replacement.type_info) {
rewrite_error_message(pg_err.message(), original_type)
} 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 {
Expand Down
8 changes: 4 additions & 4 deletions crates/pgt_typecheck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{IdentifierType, TypedIdentifier, TypedReplacement};

#[derive(Debug)]
pub struct TypecheckParams<'a> {
Expand Down Expand Up @@ -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,
Expand All @@ -68,13 +68,13 @@ pub async fn check_sql(
conn.execute(&*search_path_query).await?;
}

let res = conn.prepare(replacement.text()).await;
let res = conn.prepare(typed_replacement.replacement.text()).await;

match res {
Ok(_) => Ok(None),
Err(sqlx::Error::Database(err)) => {
let pg_err = err.downcast_ref::<PgDatabaseError>();
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),
}
Expand Down
29 changes: 21 additions & 8 deletions crates/pgt_typecheck/src/typed_identifier.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use pgt_schema_cache::PostgresType;
use pgt_text_size::{TextRangeReplacement, TextRangeReplacementBuilder};
use pgt_text_size::{TextRange, TextRangeReplacement, TextRangeReplacementBuilder};
use pgt_treesitter::queries::{ParameterMatch, TreeSitterQueriesExecutor};

/// It is used to replace parameters within the SQL string.
Expand All @@ -21,13 +21,20 @@ pub struct IdentifierType {
pub is_array: bool,
}

/// Contains the text replacement along with metadata about which ranges correspond to which types.
#[derive(Debug)]
pub struct TypedReplacement {
pub replacement: TextRangeReplacement,
pub type_info: Vec<(TextRange, IdentifierType)>,
}

/// Applies the identifiers to the SQL string by replacing them with their default values.
pub fn apply_identifiers<'a>(
identifiers: Vec<TypedIdentifier>,
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::<ParameterMatch>();
Expand All @@ -46,20 +53,26 @@ pub fn apply_identifiers<'a>(
// Resolve the type based on whether we're accessing a field of a composite type
let type_ = resolve_type(identifier, position, &parts, schema_cache)?;

Some((m.get_byte_range(), type_, identifier.type_.is_array))
Some((m.get_byte_range(), type_, identifier.type_.clone()))
})
.collect();

let mut text_range_replacement_builder = TextRangeReplacementBuilder::new(sql);
let mut type_info = vec![];

for (range, type_, is_array) in replacements {
let default_value = get_formatted_default_value(type_, is_array);
for (range, type_, original_type) in replacements {
let default_value = get_formatted_default_value(type_, original_type.is_array);

text_range_replacement_builder
.replace_range(range.clone().try_into().unwrap(), &default_value);

type_info.push((range.try_into().unwrap(), original_type));
}

text_range_replacement_builder.build()
TypedReplacement {
replacement: text_range_replacement_builder.build(),
type_info,
}
}

/// Format the default value based on the type and whether it's an array
Expand Down Expand Up @@ -307,7 +320,7 @@ mod tests {
let replacement = super::apply_identifiers(identifiers, &schema_cache, &tree, input);

assert_eq!(
replacement.text(),
replacement.replacement.text(),
// the numeric parameters are filled with 0;
"select 0 + 0 + 0 + 0 + 0 + 'critical'"
);
Expand Down Expand Up @@ -357,7 +370,7 @@ mod tests {
let replacement = super::apply_identifiers(identifiers, &schema_cache, &tree, input);

assert_eq!(
replacement.text(),
replacement.replacement.text(),
r#"select id from auth.users where email_change_confirm_status = '00000000-0000-0000-0000-000000000000' and email = '';"#
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ expression: content
---
delete from public.contacts where id = ~~~uid~~~;

invalid input syntax for type integer: &quot;00000000-0000-0000-0000-000000000000&quot;
invalid input syntax for type integer: uuid
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ expression: content
---
delete from public.contacts where id = ~~~contact_name~~~;

invalid input syntax for type integer: &quot;&quot;
invalid input syntax for type integer: text