Skip to content

Commit 0265881

Browse files
authored
fix: ignore false positive (#490)
1 parent 15666eb commit 0265881

File tree

5 files changed

+105
-2
lines changed

5 files changed

+105
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/pgt_plpgsql_check/src/lib.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,39 @@ mod tests {
271271
Ok((diagnostics, span_texts))
272272
}
273273

274+
#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")]
275+
async fn test_plpgsql_check_composite_types(test_db: PgPool) {
276+
let setup = r#"
277+
create extension if not exists plpgsql_check;
278+
279+
create table if not exists _fetch_cycle_continuation_data (
280+
next_id bigint,
281+
next_state jsonb null default '{}'::jsonb
282+
constraint abstract_no_data check(false) no inherit
283+
);
284+
"#;
285+
286+
let create_fn_sql = r#"
287+
create or replace function continue_fetch_cycle_prototype (
288+
) returns _fetch_cycle_continuation_data language plpgsql as $prototype$
289+
declare
290+
result _fetch_cycle_continuation_data := null;
291+
begin
292+
result.next_id := 0;
293+
result.next_state := '{}'::jsonb;
294+
295+
return result;
296+
end;
297+
$prototype$;
298+
"#;
299+
300+
let (diagnostics, span_texts) = run_plpgsql_check_test(&test_db, setup, create_fn_sql)
301+
.await
302+
.expect("Failed to run plpgsql_check test");
303+
304+
assert_eq!(diagnostics.len(), 0);
305+
}
306+
274307
#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")]
275308
async fn test_plpgsql_check_if_expr(test_db: PgPool) {
276309
let setup = r#"

crates/pgt_workspace/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pgt_text_size.workspace = true
3737
pgt_tokenizer = { workspace = true }
3838
pgt_typecheck = { workspace = true }
3939
pgt_workspace_macros = { workspace = true }
40+
regex = { workspace = true }
4041
rustc-hash = { workspace = true }
4142
schemars = { workspace = true, optional = true }
4243
serde = { workspace = true, features = ["derive"] }

crates/pgt_workspace/src/workspace/server.tests.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,59 @@ async fn test_dedupe_diagnostics(test_db: PgPool) {
279279
);
280280
}
281281

282+
#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")]
283+
async fn test_plpgsql_assign_composite_types(test_db: PgPool) {
284+
let conf = PartialConfiguration::init();
285+
286+
let workspace = get_test_workspace(Some(conf)).expect("Unable to create test workspace");
287+
288+
let path = PgTPath::new("test.sql");
289+
290+
let setup_sql = r"
291+
create table if not exists _fetch_cycle_continuation_data (
292+
next_id bigint,
293+
next_state jsonb null default '{}'::jsonb
294+
constraint abstract_no_data check(false) no inherit
295+
);
296+
";
297+
test_db.execute(setup_sql).await.expect("setup sql failed");
298+
299+
let content = r#"
300+
create or replace function continue_fetch_cycle_prototype ()
301+
returns _fetch_cycle_continuation_data language plpgsql as $prototype$
302+
declare
303+
result _fetch_cycle_continuation_data := null;
304+
begin
305+
result.next_id := 0;
306+
result.next_state := '{}'::jsonb
307+
308+
return result;
309+
end;
310+
$prototype$
311+
"#;
312+
313+
workspace
314+
.open_file(OpenFileParams {
315+
path: path.clone(),
316+
content: content.into(),
317+
version: 1,
318+
})
319+
.expect("Unable to open test file");
320+
321+
let diagnostics = workspace
322+
.pull_diagnostics(crate::workspace::PullDiagnosticsParams {
323+
path: path.clone(),
324+
categories: RuleCategories::all(),
325+
max_diagnostics: 100,
326+
only: vec![],
327+
skip: vec![],
328+
})
329+
.expect("Unable to pull diagnostics")
330+
.diagnostics;
331+
332+
assert_eq!(diagnostics.len(), 0, "Expected no diagnostic");
333+
}
334+
282335
#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")]
283336
async fn test_positional_params(test_db: PgPool) {
284337
let mut conf = PartialConfiguration::init();

crates/pgt_workspace/src/workspace/server/pg_query.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use std::collections::HashMap;
22
use std::num::NonZeroUsize;
3-
use std::sync::{Arc, Mutex};
3+
use std::sync::{Arc, LazyLock, Mutex};
44

55
use lru::LruCache;
66
use pgt_query_ext::diagnostics::*;
77
use pgt_text_size::TextRange;
88
use pgt_tokenizer::tokenize;
9+
use regex::Regex;
910

1011
use super::statement_identifier::StatementId;
1112

@@ -82,13 +83,27 @@ impl PgQueryStore {
8283
let range = TextRange::new(start.try_into().unwrap(), end.try_into().unwrap());
8384

8485
let r = pgt_query::parse_plpgsql(statement.content())
85-
.map_err(|err| SyntaxDiagnostic::new(err.to_string(), Some(range)));
86+
.or_else(|e| match &e {
87+
// ignore `is not a known variable` for composite types because libpg_query reports a false positive.
88+
// https://github.com/pganalyze/libpg_query/issues/159
89+
pgt_query::Error::Parse(err) if is_composite_type_error(err) => Ok(()),
90+
_ => Err(e),
91+
})
92+
.map_err(|e| SyntaxDiagnostic::new(e.to_string(), Some(range)));
93+
8694
cache.put(statement.clone(), r.clone());
8795

8896
Some(r)
8997
}
9098
}
9199

100+
static COMPOSITE_TYPE_ERROR_RE: LazyLock<Regex> =
101+
LazyLock::new(|| Regex::new(r#"\\?"([^"\\]+\.[^"\\]+)\\?" is not a known variable"#).unwrap());
102+
103+
fn is_composite_type_error(err: &str) -> bool {
104+
COMPOSITE_TYPE_ERROR_RE.is_match(err)
105+
}
106+
92107
/// Converts named parameters in a SQL query string to positional parameters.
93108
///
94109
/// This function scans the input SQL string for named parameters (e.g., `@param`, `:param`, `:'param'`)

0 commit comments

Comments
 (0)