Skip to content

Commit b443733

Browse files
committed
initial
1 parent 2df5b3c commit b443733

6 files changed

Lines changed: 205 additions & 7 deletions

File tree

crates/pgls_configuration/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ impl PartialConfiguration {
176176
}),
177177
plpgsql_check: Some(PartialPlPgSqlCheckConfiguration {
178178
enabled: Some(true),
179+
..Default::default()
179180
}),
180181
db: Some(PartialDatabaseConfiguration {
181182
connection_string: None,

crates/pgls_configuration/src/plpgsql_check.rs

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use bpaf::Bpaf;
22
use pgls_configuration_macros::{Merge, Partial};
33
use serde::{Deserialize, Serialize};
44

5-
/// The configuration for type checking.
5+
/// The configuration for plpgsql_check.
66
#[derive(Clone, Debug, Deserialize, Eq, Partial, PartialEq, Serialize)]
77
#[partial(derive(Bpaf, Clone, Eq, PartialEq, Merge))]
88
#[partial(cfg_attr(feature = "schema", derive(schemars::JsonSchema)))]
@@ -11,10 +11,71 @@ pub struct PlPgSqlCheckConfiguration {
1111
/// if `false`, it disables the feature and pglpgsql_check won't be executed. `true` by default
1212
#[partial(bpaf(hide))]
1313
pub enabled: bool,
14+
15+
/// Stop processing at the first error. `true` by default.
16+
#[partial(bpaf(hide))]
17+
pub fatal_errors: bool,
18+
19+
/// Show warnings about attribute count mismatches, variable overlaps, unused variables, and
20+
/// unwanted casting. `true` by default.
21+
#[partial(bpaf(hide))]
22+
pub other_warnings: bool,
23+
24+
/// Show warnings regarding missing RETURN statements, shadowed variables, dead code, and
25+
/// unused parameters. `true` by default.
26+
#[partial(bpaf(hide))]
27+
pub extra_warnings: bool,
28+
29+
/// Flag performance issues like declared types with modifiers and implicit casts that may
30+
/// prevent index usage. `false` by default.
31+
#[partial(bpaf(hide))]
32+
pub performance_warnings: bool,
33+
34+
/// Identify potential SQL injection vulnerabilities in dynamic statements. `false` by default.
35+
#[partial(bpaf(hide))]
36+
pub security_warnings: bool,
37+
38+
/// Detect deprecated patterns like explicit cursor name assignments in refcursor variables.
39+
/// `false` by default.
40+
#[partial(bpaf(hide))]
41+
pub compatibility_warnings: bool,
42+
43+
/// Disable all warnings, overriding individual warning parameters. `false` by default.
44+
#[partial(bpaf(hide))]
45+
pub without_warnings: bool,
46+
47+
/// Enable all warnings, overriding individual warning parameters. `false` by default.
48+
#[partial(bpaf(hide))]
49+
pub all_warnings: bool,
50+
51+
/// Activate in-comment options embedded in function source code. `true` by default.
52+
#[partial(bpaf(hide))]
53+
pub use_incomment_options: bool,
54+
55+
/// Raise warnings when in-comment options are utilized. `false` by default.
56+
#[partial(bpaf(hide))]
57+
pub incomment_options_usage_warning: bool,
58+
59+
/// Permit variables holding constant values to be used like constants. `true` by default.
60+
#[partial(bpaf(hide))]
61+
pub constant_tracing: bool,
1462
}
1563

1664
impl Default for PlPgSqlCheckConfiguration {
1765
fn default() -> Self {
18-
Self { enabled: true }
66+
Self {
67+
enabled: true,
68+
fatal_errors: true,
69+
other_warnings: true,
70+
extra_warnings: true,
71+
performance_warnings: false,
72+
security_warnings: false,
73+
compatibility_warnings: false,
74+
without_warnings: false,
75+
all_warnings: false,
76+
use_incomment_options: true,
77+
incomment_options_usage_warning: false,
78+
constant_tracing: true,
79+
}
1980
}
2081
}

crates/pgls_plpgsql_check/src/lib.rs

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,47 @@ use serde::Deserialize;
88
pub use sqlx::postgres::PgSeverity;
99
use sqlx::{Acquire, PgPool, Postgres, Transaction};
1010

11+
/// Settings that control which plpgsql_check_function() arguments are passed.
12+
#[derive(Clone, Debug)]
13+
pub struct PlPgSqlCheckOptions {
14+
pub fatal_errors: bool,
15+
pub other_warnings: bool,
16+
pub extra_warnings: bool,
17+
pub performance_warnings: bool,
18+
pub security_warnings: bool,
19+
pub compatibility_warnings: bool,
20+
pub without_warnings: bool,
21+
pub all_warnings: bool,
22+
pub use_incomment_options: bool,
23+
pub incomment_options_usage_warning: bool,
24+
pub constant_tracing: bool,
25+
}
26+
27+
impl Default for PlPgSqlCheckOptions {
28+
fn default() -> Self {
29+
Self {
30+
fatal_errors: true,
31+
other_warnings: true,
32+
extra_warnings: true,
33+
performance_warnings: false,
34+
security_warnings: false,
35+
compatibility_warnings: false,
36+
without_warnings: false,
37+
all_warnings: false,
38+
use_incomment_options: true,
39+
incomment_options_usage_warning: false,
40+
constant_tracing: true,
41+
}
42+
}
43+
}
44+
1145
#[derive(Debug)]
1246
pub struct PlPgSqlCheckParams<'a> {
1347
pub conn: &'a PgPool,
1448
pub sql: &'a str,
1549
pub ast: &'a pgls_query::NodeEnum,
1650
pub schema_cache: &'a pgls_schema_cache::SchemaCache,
51+
pub options: &'a PlPgSqlCheckOptions,
1752
}
1853

1954
#[derive(Debug, Deserialize)]
@@ -129,6 +164,52 @@ fn build_function_identifier(
129164
}
130165
}
131166

167+
/// Build extra named parameters for plpgsql_check_function().
168+
/// Only includes parameters that differ from plpgsql_check defaults.
169+
fn build_extra_params(options: &PlPgSqlCheckOptions) -> String {
170+
let mut params = Vec::new();
171+
172+
if !options.fatal_errors {
173+
params.push("fatal_errors := false".to_string());
174+
}
175+
if !options.other_warnings {
176+
params.push("other_warnings := false".to_string());
177+
}
178+
if !options.extra_warnings {
179+
params.push("extra_warnings := false".to_string());
180+
}
181+
if options.performance_warnings {
182+
params.push("performance_warnings := true".to_string());
183+
}
184+
if options.security_warnings {
185+
params.push("security_warnings := true".to_string());
186+
}
187+
if options.compatibility_warnings {
188+
params.push("compatibility_warnings := true".to_string());
189+
}
190+
if options.without_warnings {
191+
params.push("without_warnings := true".to_string());
192+
}
193+
if options.all_warnings {
194+
params.push("all_warnings := true".to_string());
195+
}
196+
if !options.use_incomment_options {
197+
params.push("use_incomment_options := false".to_string());
198+
}
199+
if options.incomment_options_usage_warning {
200+
params.push("incomment_options_usage_warning := true".to_string());
201+
}
202+
if !options.constant_tracing {
203+
params.push("constant_tracing := false".to_string());
204+
}
205+
206+
if params.is_empty() {
207+
String::new()
208+
} else {
209+
format!(", {}", params.join(", "))
210+
}
211+
}
212+
132213
pub async fn check_plpgsql(
133214
params: PlPgSqlCheckParams<'_>,
134215
) -> Result<Vec<PlPgSqlCheckDiagnostic>, sqlx::Error> {
@@ -175,6 +256,7 @@ pub async fn check_plpgsql(
175256
sqlx::query(&sql_with_replace).execute(&mut *tx).await?;
176257

177258
// run plpgsql_check and collect results with their relations
259+
let extra = build_extra_params(params.options);
178260
let results_with_relations: Vec<(String, Option<String>)> = if is_trigger {
179261
let mut results = Vec::new();
180262

@@ -185,7 +267,7 @@ pub async fn check_plpgsql(
185267
let relation = format!("{}.{}", trigger.table_schema, trigger.table_name);
186268

187269
let result: Option<String> = sqlx::query_scalar(&format!(
188-
"select plpgsql_check_function('{fn_identifier}', '{relation}', format := 'json')"
270+
"select plpgsql_check_function('{fn_identifier}', '{relation}', format := 'json'{extra})"
189271
))
190272
.fetch_optional(&mut *tx)
191273
.await?
@@ -200,7 +282,7 @@ pub async fn check_plpgsql(
200282
results
201283
} else {
202284
let result: Option<String> = sqlx::query_scalar(&format!(
203-
"select plpgsql_check_function('{fn_identifier}', format := 'json')"
285+
"select plpgsql_check_function('{fn_identifier}', format := 'json'{extra})"
204286
))
205287
.fetch_optional(&mut *tx)
206288
.await?
@@ -248,11 +330,13 @@ mod tests {
248330
.ok_or("Failed to parse SQL root")?;
249331
let schema_cache = pgls_schema_cache::SchemaCache::load(test_db).await?;
250332

333+
let options = super::PlPgSqlCheckOptions::default();
251334
let diagnostics = super::check_plpgsql(super::PlPgSqlCheckParams {
252335
conn: test_db,
253336
sql: create_fn_sql,
254337
ast: &ast,
255338
schema_cache: &schema_cache,
339+
options: &options,
256340
})
257341
.await?;
258342

crates/pgls_workspace/src/settings.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,17 @@ fn to_typecheck_settings(conf: TypecheckConfiguration) -> TypecheckSettings {
399399
fn to_plpgsql_check_settings(conf: PlPgSqlCheckConfiguration) -> PlPgSqlCheckSettings {
400400
PlPgSqlCheckSettings {
401401
enabled: conf.enabled,
402+
fatal_errors: conf.fatal_errors,
403+
other_warnings: conf.other_warnings,
404+
extra_warnings: conf.extra_warnings,
405+
performance_warnings: conf.performance_warnings,
406+
security_warnings: conf.security_warnings,
407+
compatibility_warnings: conf.compatibility_warnings,
408+
without_warnings: conf.without_warnings,
409+
all_warnings: conf.all_warnings,
410+
use_incomment_options: conf.use_incomment_options,
411+
incomment_options_usage_warning: conf.incomment_options_usage_warning,
412+
constant_tracing: conf.constant_tracing,
402413
}
403414
}
404415

@@ -613,16 +624,39 @@ impl Default for PglinterSettings {
613624
}
614625
}
615626

616-
/// Type checking settings for the entire workspace
627+
/// plpgsql_check settings for the entire workspace
617628
#[derive(Debug)]
618629
pub struct PlPgSqlCheckSettings {
619-
/// Enabled by default
620630
pub enabled: bool,
631+
pub fatal_errors: bool,
632+
pub other_warnings: bool,
633+
pub extra_warnings: bool,
634+
pub performance_warnings: bool,
635+
pub security_warnings: bool,
636+
pub compatibility_warnings: bool,
637+
pub without_warnings: bool,
638+
pub all_warnings: bool,
639+
pub use_incomment_options: bool,
640+
pub incomment_options_usage_warning: bool,
641+
pub constant_tracing: bool,
621642
}
622643

623644
impl Default for PlPgSqlCheckSettings {
624645
fn default() -> Self {
625-
Self { enabled: true }
646+
Self {
647+
enabled: true,
648+
fatal_errors: true,
649+
other_warnings: true,
650+
extra_warnings: true,
651+
performance_warnings: false,
652+
security_warnings: false,
653+
compatibility_warnings: false,
654+
without_warnings: false,
655+
all_warnings: false,
656+
use_incomment_options: true,
657+
incomment_options_usage_warning: false,
658+
constant_tracing: true,
659+
}
626660
}
627661
}
628662

crates/pgls_workspace/src/workspace/server.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,21 @@ impl Workspace for WorkspaceServer {
558558
{
559559
let typecheck_enabled = settings.typecheck.enabled;
560560
let plpgsql_check_enabled = settings.plpgsql_check.enabled;
561+
let plpgsql_check_options = pgls_plpgsql_check::PlPgSqlCheckOptions {
562+
fatal_errors: settings.plpgsql_check.fatal_errors,
563+
other_warnings: settings.plpgsql_check.other_warnings,
564+
extra_warnings: settings.plpgsql_check.extra_warnings,
565+
performance_warnings: settings.plpgsql_check.performance_warnings,
566+
security_warnings: settings.plpgsql_check.security_warnings,
567+
compatibility_warnings: settings.plpgsql_check.compatibility_warnings,
568+
without_warnings: settings.plpgsql_check.without_warnings,
569+
all_warnings: settings.plpgsql_check.all_warnings,
570+
use_incomment_options: settings.plpgsql_check.use_incomment_options,
571+
incomment_options_usage_warning: settings
572+
.plpgsql_check
573+
.incomment_options_usage_warning,
574+
constant_tracing: settings.plpgsql_check.constant_tracing,
575+
};
561576
if (typecheck_enabled || plpgsql_check_enabled)
562577
&& let Some(pool) = self.get_current_connection()
563578
{
@@ -574,6 +589,7 @@ impl Workspace for WorkspaceServer {
574589
let path = path_clone.clone();
575590
let schema_cache = Arc::clone(&schema_cache);
576591
let search_path_patterns = search_path_patterns.clone();
592+
let plpgsql_check_options = plpgsql_check_options.clone();
577593

578594
async move {
579595
let mut diagnostics = Vec::new();
@@ -632,6 +648,7 @@ impl Workspace for WorkspaceServer {
632648
sql: id.content(),
633649
ast: &ast,
634650
schema_cache: schema_cache.as_ref(),
651+
options: &plpgsql_check_options,
635652
},
636653
)
637654
.await

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ async fn test_disable_plpgsql_check(test_db: PgPool) {
461461
configuration: PartialConfiguration {
462462
plpgsql_check: Some(PartialPlPgSqlCheckConfiguration {
463463
enabled: Some(false),
464+
..Default::default()
464465
}),
465466
..Default::default()
466467
},

0 commit comments

Comments
 (0)