Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8869683

Browse files
feat: provide additional search_path patterns for typechecking (#484)
1 parent 7cca8f4 commit 8869683

File tree

11 files changed

+278
-2
lines changed

11 files changed

+278
-2
lines changed

‎Cargo.lock

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

‎crates/pgt_configuration/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod diagnostics;
99
pub mod files;
1010
pub mod generated;
1111
pub mod migrations;
12+
pub mod typecheck;
1213
pub mod vcs;
1314

1415
pub use crate::diagnostics::ConfigurationDiagnostic;
@@ -33,6 +34,9 @@ use migrations::{
3334
MigrationsConfiguration, PartialMigrationsConfiguration, partial_migrations_configuration,
3435
};
3536
use serde::{Deserialize, Serialize};
37+
pub use typecheck::{
38+
PartialTypecheckConfiguration, TypecheckConfiguration, partial_typecheck_configuration,
39+
};
3640
use vcs::VcsClientKind;
3741

3842
pub const VERSION: &str = match option_env!("PGT_VERSION") {
@@ -77,6 +81,10 @@ pub struct Configuration {
7781
#[partial(type, bpaf(external(partial_linter_configuration), optional))]
7882
pub linter: LinterConfiguration,
7983

84+
/// The configuration for type checking
85+
#[partial(type, bpaf(external(partial_typecheck_configuration), optional))]
86+
pub typecheck: TypecheckConfiguration,
87+
8088
/// The configuration of the database connection
8189
#[partial(
8290
type,
@@ -110,6 +118,9 @@ impl PartialConfiguration {
110118
}),
111119
..Default::default()
112120
}),
121+
typecheck: Some(PartialTypecheckConfiguration {
122+
..Default::default()
123+
}),
113124
db: Some(PartialDatabaseConfiguration {
114125
host: Some("127.0.0.1".to_string()),
115126
port: Some(5432),
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use biome_deserialize::StringSet;
2+
use biome_deserialize_macros::{Merge, Partial};
3+
use bpaf::Bpaf;
4+
use serde::{Deserialize, Serialize};
5+
6+
/// The configuration for type checking.
7+
#[derive(Clone, Debug, Deserialize, Eq, Partial, PartialEq, Serialize)]
8+
#[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))]
9+
#[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))]
10+
#[partial(serde(rename_all = "camelCase", default, deny_unknown_fields))]
11+
pub struct TypecheckConfiguration {
12+
/// Default search path schemas for type checking.
13+
/// Can be a list of schema names or glob patterns like ["public", "app_*"].
14+
/// If not specified, defaults to ["public"].
15+
#[partial(bpaf(long("search_path")))]
16+
pub search_path: StringSet,
17+
}
18+
19+
impl Default for TypecheckConfiguration {
20+
fn default() -> Self {
21+
Self {
22+
search_path: ["public".to_string()].into_iter().collect(),
23+
}
24+
}
25+
}

‎crates/pgt_typecheck/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ version = "0.0.0"
1212

1313

1414
[dependencies]
15+
globset = "0.4.16"
16+
itertools = { version = "0.14.0" }
1517
pgt_console.workspace = true
1618
pgt_diagnostics.workspace = true
1719
pgt_query.workspace = true

‎crates/pgt_typecheck/src/lib.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ mod typed_identifier;
33

44
pub use diagnostics::TypecheckDiagnostic;
55
use diagnostics::create_type_error;
6+
use globset::Glob;
7+
use itertools::Itertools;
8+
use pgt_schema_cache::SchemaCache;
69
use sqlx::postgres::PgDatabaseError;
710
pub use sqlx::postgres::PgSeverity;
811
use sqlx::{Executor, PgPool};
@@ -17,6 +20,9 @@ pub struct TypecheckParams<'a> {
1720
pub tree: &'a tree_sitter::Tree,
1821
pub schema_cache: &'a pgt_schema_cache::SchemaCache,
1922
pub identifiers: Vec<TypedIdentifier>,
23+
/// Set of glob patterns that will be matched against the schemas in the database.
24+
/// Each matching schema will be added to the search_path for the typecheck.
25+
pub search_path_patterns: Vec<String>,
2026
}
2127

2228
pub async fn check_sql(
@@ -49,6 +55,19 @@ pub async fn check_sql(
4955
params.sql,
5056
);
5157

58+
let mut search_path_schemas =
59+
get_schemas_in_search_path(params.schema_cache, params.search_path_patterns);
60+
61+
if !search_path_schemas.is_empty() {
62+
// Always include public if we have any schemas in search path
63+
if !search_path_schemas.contains(&"public") {
64+
search_path_schemas.push("public");
65+
}
66+
67+
let search_path_query = format!("SET search_path TO {};", search_path_schemas.join(", "));
68+
conn.execute(&*search_path_query).await?;
69+
}
70+
5271
let res = conn.prepare(&prepared).await;
5372

5473
match res {
@@ -64,3 +83,33 @@ pub async fn check_sql(
6483
Err(err) => Err(err),
6584
}
6685
}
86+
87+
fn get_schemas_in_search_path(schema_cache: &SchemaCache, glob_patterns: Vec<String>) -> Vec<&str> {
88+
// iterate over glob_patterns on the outside to keep the order
89+
glob_patterns
90+
.iter()
91+
.filter_map(|pattern| {
92+
if let Ok(glob) = Glob::new(pattern) {
93+
let matcher = glob.compile_matcher();
94+
95+
Some(
96+
schema_cache
97+
.schemas
98+
.iter()
99+
.filter_map(|s| {
100+
if matcher.is_match(s.name.as_str()) {
101+
Some(s.name.as_str())
102+
} else {
103+
None
104+
}
105+
})
106+
.collect::<Vec<&str>>(),
107+
)
108+
} else {
109+
None
110+
}
111+
})
112+
.flatten()
113+
.unique()
114+
.collect()
115+
}

‎crates/pgt_typecheck/tests/diagnostics.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ async fn test(name: &str, query: &str, setup: Option<&str>, test_db: &PgPool) {
3636
ast: &root,
3737
tree: &tree,
3838
schema_cache: &schema_cache,
39+
search_path_patterns: vec![],
3940
identifiers: vec![],
4041
})
4142
.await;

‎crates/pgt_workspace/src/settings.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use tracing::trace;
1212

1313
use ignore::gitignore::{Gitignore, GitignoreBuilder};
1414
use pgt_configuration::{
15-
ConfigurationDiagnostic, LinterConfiguration, PartialConfiguration,
15+
ConfigurationDiagnostic, LinterConfiguration, PartialConfiguration,TypecheckConfiguration,
1616
database::PartialDatabaseConfiguration,
1717
diagnostics::InvalidIgnorePattern,
1818
files::FilesConfiguration,
@@ -210,6 +210,9 @@ pub struct Settings {
210210
/// Linter settings applied to all files in the workspace
211211
pub linter: LinterSettings,
212212

213+
/// Type checking settings for the workspace
214+
pub typecheck: TypecheckSettings,
215+
213216
/// Migrations settings
214217
pub migrations: Option<MigrationSettings>,
215218
}
@@ -245,6 +248,11 @@ impl Settings {
245248
to_linter_settings(working_directory.clone(), LinterConfiguration::from(linter))?;
246249
}
247250

251+
// typecheck part
252+
if let Some(typecheck) = configuration.typecheck {
253+
self.typecheck = to_typecheck_settings(TypecheckConfiguration::from(typecheck));
254+
}
255+
248256
// Migrations settings
249257
if let Some(migrations) = configuration.migrations {
250258
self.migrations = to_migration_settings(
@@ -294,6 +302,12 @@ fn to_linter_settings(
294302
})
295303
}
296304

305+
fn to_typecheck_settings(conf: TypecheckConfiguration) -> TypecheckSettings {
306+
TypecheckSettings {
307+
search_path: conf.search_path.into_iter().collect(),
308+
}
309+
}
310+
297311
fn to_file_settings(
298312
working_directory: Option<PathBuf>,
299313
config: Option<FilesConfiguration>,
@@ -401,6 +415,21 @@ impl Default for LinterSettings {
401415
}
402416
}
403417

418+
/// Type checking settings for the entire workspace
419+
#[derive(Debug)]
420+
pub struct TypecheckSettings {
421+
/// Default search path schemas for type checking
422+
pub search_path: Vec<String>,
423+
}
424+
425+
impl Default for TypecheckSettings {
426+
fn default() -> Self {
427+
Self {
428+
search_path: vec!["public".to_string()],
429+
}
430+
}
431+
}
432+
404433
/// Database settings for the entire workspace
405434
#[derive(Debug)]
406435
pub struct DatabaseSettings {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ impl Workspace for WorkspaceServer {
455455
let path_clone = params.path.clone();
456456
let schema_cache = self.schema_cache.load(pool.clone())?;
457457
let input = doc.iter(TypecheckDiagnosticsMapper).collect::<Vec<_>>();
458+
let search_path_patterns = settings.typecheck.search_path.clone();
458459

459460
// Combined async context for both typecheck and plpgsql_check
460461
let async_results = run_async(async move {
@@ -463,6 +464,8 @@ impl Workspace for WorkspaceServer {
463464
let pool = pool.clone();
464465
let path = path_clone.clone();
465466
let schema_cache = Arc::clone(&schema_cache);
467+
let search_path_patterns = search_path_patterns.clone();
468+
466469
async move {
467470
let mut diagnostics = Vec::new();
468471

@@ -474,6 +477,7 @@ impl Workspace for WorkspaceServer {
474477
ast: &ast,
475478
tree: &cst,
476479
schema_cache: schema_cache.as_ref(),
480+
search_path_patterns,
477481
identifiers: sign
478482
.map(|s| {
479483
s.args

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

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use std::sync::Arc;
33
use biome_deserialize::{Merge, StringSet};
44
use pgt_analyse::RuleCategories;
55
use pgt_configuration::{
6-
PartialConfiguration, database::PartialDatabaseConfiguration, files::PartialFilesConfiguration,
6+
PartialConfiguration, PartialTypecheckConfiguration, database::PartialDatabaseConfiguration,
7+
files::PartialFilesConfiguration,
78
};
89
use pgt_diagnostics::Diagnostic;
910
use pgt_fs::PgTPath;
@@ -331,3 +332,113 @@ async fn test_positional_params(test_db: PgPool) {
331332

332333
assert_eq!(diagnostics.len(), 0, "Expected no diagnostic");
333334
}
335+
336+
#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")]
337+
async fn test_search_path_configuration(test_db: PgPool) {
338+
// Setup test schemas and functions
339+
let setup_sql = r#"
340+
create schema if not exists private;
341+
342+
create or replace function private.get_user_id() returns integer as $$
343+
select 1;
344+
$$ language sql;
345+
"#;
346+
test_db.execute(setup_sql).await.expect("setup sql failed");
347+
348+
let path_glob = PgTPath::new("test_glob.sql");
349+
let file_content = r#"
350+
select get_user_id(); -- on private schema
351+
"#;
352+
353+
// first check that the we get a valid typecheck
354+
let mut glob_conf = PartialConfiguration::init();
355+
glob_conf.merge_with(PartialConfiguration {
356+
db: Some(PartialDatabaseConfiguration {
357+
database: Some(
358+
test_db
359+
.connect_options()
360+
.get_database()
361+
.unwrap()
362+
.to_string(),
363+
),
364+
..Default::default()
365+
}),
366+
..Default::default()
367+
});
368+
369+
// without glob
370+
{
371+
let workspace =
372+
get_test_workspace(Some(glob_conf.clone())).expect("Unable to create test workspace");
373+
374+
workspace
375+
.open_file(OpenFileParams {
376+
path: path_glob.clone(),
377+
content: file_content.into(),
378+
version: 1,
379+
})
380+
.expect("Unable to open test file");
381+
382+
let diagnostics_glob = workspace
383+
.pull_diagnostics(crate::workspace::PullDiagnosticsParams {
384+
path: path_glob.clone(),
385+
categories: RuleCategories::all(),
386+
max_diagnostics: 100,
387+
only: vec![],
388+
skip: vec![],
389+
})
390+
.expect("Unable to pull diagnostics")
391+
.diagnostics;
392+
393+
assert_eq!(
394+
diagnostics_glob.len(),
395+
1,
396+
"get_user_id() should not be found in search_path"
397+
);
398+
399+
// yep, type error!
400+
assert_eq!(
401+
diagnostics_glob[0].category().map(|c| c.name()),
402+
Some("typecheck")
403+
);
404+
}
405+
406+
// adding the glob
407+
glob_conf.merge_with(PartialConfiguration {
408+
typecheck: Some(PartialTypecheckConfiguration {
409+
// Adding glob pattern to match the "private" schema
410+
search_path: Some(StringSet::from_iter(vec!["pr*".to_string()])),
411+
}),
412+
..Default::default()
413+
}); // checking with the pattern should yield no diagnostics
414+
415+
{
416+
let workspace =
417+
get_test_workspace(Some(glob_conf.clone())).expect("Unable to create test workspace");
418+
419+
workspace
420+
.open_file(OpenFileParams {
421+
path: path_glob.clone(),
422+
content: file_content.into(),
423+
version: 1,
424+
})
425+
.expect("Unable to open test file");
426+
427+
let diagnostics_glob = workspace
428+
.pull_diagnostics(crate::workspace::PullDiagnosticsParams {
429+
path: path_glob.clone(),
430+
categories: RuleCategories::all(),
431+
max_diagnostics: 100,
432+
only: vec![],
433+
skip: vec![],
434+
})
435+
.expect("Unable to pull diagnostics")
436+
.diagnostics;
437+
438+
assert_eq!(
439+
diagnostics_glob.len(),
440+
0,
441+
"Glob pattern should put private schema in search path"
442+
);
443+
}
444+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /