Skip to content

fix: sql fn params #366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

fix: sql fn params #366

wants to merge 11 commits into from

Conversation

psteinroe
Copy link
Collaborator

@psteinroe psteinroe commented Apr 22, 2025

the idea is to replace the fn params with a default value based on their type:

create or replace function users.select_no_ref (user_id int4) returns table (
    first_name text
) language sql security invoker as $$
select
    first_name
FROM 
    users_hidden.users
where 
    id = user_id;
$$;

will become

create or replace function users.select_no_ref (user_id int4) returns table (
    first_name text
) language sql security invoker as $$
select
    first_name
FROM 
    users_hidden.users
where 
    id = 0; -- <-- here
$$;

Todo

  • pass params to typechecker
  • implement apply_identifiers

fixes #353
fixes #352

@psteinroe psteinroe marked this pull request as ready for review May 7, 2025 08:07
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes SQL function parameter substitution by replacing function parameters with type‐based default values while refactoring SQL function signature extraction and integrating these changes into the typecheck flow.

  • Added utility methods to StatementId.
  • Refactored SQL function parsing to utilize SQLFunctionSignature and SQLFunctionArgs.
  • Updated document parsing, typecheck, and identifier application with improved default value resolution.

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/pgt_workspace/src/workspace/server/statement_identifier.rs Added helper methods (is_root, is_child, parent) for statement hierarchy.
crates/pgt_workspace/src/workspace/server/sql_function.rs Introduced SQLFunctionSignature and revamped function parameter extraction.
crates/pgt_workspace/src/workspace/server/parsed_document.rs Removed legacy SQLFunctionBodyStore and integrated new SQL function extraction.
crates/pgt_workspace/src/workspace/server/document.rs Added statement_content method to get statement SQL content.
crates/pgt_workspace/src/workspace/server.rs Updated typecheck integration with new schema cache and identifier handling.
crates/pgt_typecheck/src/typed_identifier.rs Enhanced apply_identifiers with default value formatting and support for type-based defaults.
crates/pgt_treesitter_queries Updated parameter extraction queries and tests for ParameterMatch.
Other files Minor adjustments to tests, diagnostics, and schema cache types to support the changes.
Comments suppressed due to low confidence (1)

crates/pgt_workspace/src/workspace/server.rs:376

  • [nitpick] The async block here could be refactored for improved readability and maintainability. A clearer structure may help future developers understand the schema cache and identifier propagation logic.
// sorry for the ugly code :(

Copy link
Collaborator

@juleswritescode juleswritescode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just details, the concept looks great!

Comment on lines +180 to +184
if parts.len() == 1 && parts[0].starts_with('$') {
let idx = parts[0][1..].parse::<usize>().ok()?;
let identifier = identifiers.get(idx - 1)?;
return Some((identifier, idx));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sehr nice

Comment on lines +335 to +338
assert_eq!(
sql_out,
"select 0 + 0 + 0 + 0 + 0 + NULL "
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert_eq!(
sql_out,
"select 0 + 0 + 0 + 0 + 0 + NULL "
);
// the numeric parameters are filled with 0; the enum does not have a default, so it's filled with `null`
assert_eq!(
sql_out,
"select 0 + 0 + 0 + 0 + 0 + NULL "
);

db: DashMap<StatementId, Option<Arc<SQLFunctionBody>>>,
#[derive(Debug, Clone)]
pub struct SQLFunctionSignature {
pub name: (Option<String>, String),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: i think it would be more readable if it had a name and a schema property

pub struct SQLFunctionBody {
pub range: TextRange,
pub body: String,
pub struct SQLFunctionArgs {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a single Arg right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes!

// If not cached, try to extract it from the AST
let fn_body = get_sql_fn(ast, content).map(Arc::new);
// Extract language from function options
let language = find_option_value(create_fn, "language")?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i really like that api

Comment on lines +85 to +86
// If the default value is longer than "NULL", use "NULL" instead
"NULL".to_string()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we doing this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because we need to throw away positions when the replacement is longer than its original, and NULL will be shorter than a few default values (e.g. timestamps or custom enums)


let mut result = sql.to_string();

let mut valid_positions = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what position is in this context.
I understand it this way: We'll need to adjust the statements ranges if a parameter's default value is longer than the substituted text.
If that's correct, how about we rename this to something like should_adjust_ranges (default false) ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sometimes (almost never), we get a position back for the type error. in that case, we show the diagnostics on that position instead of the entire statement. since we are replacing the vars in the text, the position we get back does not match the position in the original statement anymore if the replacements are not the same length. hence, if valid_positions is false, we throw away the position from the type error and always show the error on the entire length of the statement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants