Skip to content

Commit 9b92948

Browse files
authoredJan 14, 2025
migrate typechecker to new lsp (#169)
* migrate typechecker to new lsp * refactor: run async in parallel * lint-fix * fixes from review
1 parent 680689c commit 9b92948

File tree

17 files changed

+440
-150
lines changed

17 files changed

+440
-150
lines changed
 

‎Cargo.lock

+7-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/pg_completions/src/relevance.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl CompletionRelevance<'_> {
7070
Some(ct) => ct,
7171
};
7272

73-
let has_mentioned_tables = ctx.mentioned_relations.len() > 0;
73+
let has_mentioned_tables = !ctx.mentioned_relations.is_empty();
7474

7575
self.score += match self.data {
7676
CompletionRelevanceData::Table(_) => match clause_type {

‎crates/pg_completions/src/test_helper.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub(crate) async fn get_test_deps(
5151
.set_language(tree_sitter_sql::language())
5252
.expect("Error loading sql language");
5353

54-
let tree = parser.parse(&input.to_string(), None).unwrap();
54+
let tree = parser.parse(input.to_string(), None).unwrap();
5555

5656
(tree, schema_cache)
5757
}

‎crates/pg_diagnostics_categories/src/categories.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ define_categories! {
2626
"internalError/fs",
2727
"flags/invalid",
2828
"project",
29+
"typecheck",
2930
"internalError/panic",
3031
"syntax",
3132
"dummy",

‎crates/pg_test_utils/src/test_database.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ use uuid::Uuid;
44
// TODO: Work with proper config objects instead of a connection_string.
55
// With the current implementation, we can't parse the password from the connection string.
66
pub async fn get_new_test_db() -> PgPool {
7-
dotenv::dotenv()
8-
.ok()
9-
.expect("Unable to load .env file for tests");
7+
dotenv::dotenv().expect("Unable to load .env file for tests");
108

119
let connection_string = std::env::var("DATABASE_URL").expect("DATABASE_URL not set");
1210
let password = std::env::var("DB_PASSWORD").unwrap_or("postgres".into());

‎crates/pg_treesitter_queries/src/lib.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl<'a> TreeSitterQueriesExecutor<'a> {
2121

2222
#[allow(private_bounds)]
2323
pub fn add_query_results<Q: Query<'a>>(&mut self) {
24-
let mut results = Q::execute(self.root_node, &self.stmt);
24+
let mut results = Q::execute(self.root_node, self.stmt);
2525
self.results.append(&mut results);
2626
}
2727

@@ -104,9 +104,9 @@ where
104104
let mut parser = tree_sitter::Parser::new();
105105
parser.set_language(tree_sitter_sql::language()).unwrap();
106106

107-
let tree = parser.parse(&sql, None).unwrap();
107+
let tree = parser.parse(sql, None).unwrap();
108108

109-
let mut executor = TreeSitterQueriesExecutor::new(tree.root_node(), &sql);
109+
let mut executor = TreeSitterQueriesExecutor::new(tree.root_node(), sql);
110110

111111
executor.add_query_results::<RelationMatch>();
112112

@@ -152,7 +152,7 @@ on sq1.id = pt.id;
152152
let mut parser = tree_sitter::Parser::new();
153153
parser.set_language(tree_sitter_sql::language()).unwrap();
154154

155-
let tree = parser.parse(&sql, None).unwrap();
155+
let tree = parser.parse(sql, None).unwrap();
156156

157157
// trust me bro
158158
let range = {
@@ -172,7 +172,7 @@ on sq1.id = pt.id;
172172
cursor.node().range()
173173
};
174174

175-
let mut executor = TreeSitterQueriesExecutor::new(tree.root_node(), &sql);
175+
let mut executor = TreeSitterQueriesExecutor::new(tree.root_node(), sql);
176176

177177
executor.add_query_results::<RelationMatch>();
178178

‎crates/pg_treesitter_queries/src/queries/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub enum QueryResult<'a> {
77
Relation(RelationMatch<'a>),
88
}
99

10-
impl<'a> QueryResult<'a> {
10+
impl QueryResult<'_> {
1111
pub fn within_range(&self, range: &tree_sitter::Range) -> bool {
1212
match self {
1313
Self::Relation(rm) => {

‎crates/pg_treesitter_queries/src/queries/relations.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{Query, QueryResult};
55
use super::QueryTryFrom;
66

77
static TS_QUERY: LazyLock<tree_sitter::Query> = LazyLock::new(|| {
8-
static QUERY_STR: &'static str = r#"
8+
static QUERY_STR: &str = r#"
99
(relation
1010
(object_reference
1111
.
@@ -15,7 +15,7 @@ static TS_QUERY: LazyLock<tree_sitter::Query> = LazyLock::new(|| {
1515
)+
1616
)
1717
"#;
18-
tree_sitter::Query::new(tree_sitter_sql::language(), &QUERY_STR).expect("Invalid TS Query")
18+
tree_sitter::Query::new(tree_sitter_sql::language(), QUERY_STR).expect("Invalid TS Query")
1919
});
2020

2121
#[derive(Debug)]
@@ -24,7 +24,7 @@ pub struct RelationMatch<'a> {
2424
pub(crate) table: tree_sitter::Node<'a>,
2525
}
2626

27-
impl<'a> RelationMatch<'a> {
27+
impl RelationMatch<'_> {
2828
pub fn get_schema(&self, sql: &str) -> Option<String> {
2929
let str = self
3030
.schema
@@ -48,7 +48,7 @@ impl<'a> TryFrom<&'a QueryResult<'a>> for &'a RelationMatch<'a> {
4848

4949
fn try_from(q: &'a QueryResult<'a>) -> Result<Self, Self::Error> {
5050
match q {
51-
QueryResult::Relation(r) => Ok(&r),
51+
QueryResult::Relation(r) => Ok(r),
5252

5353
#[allow(unreachable_patterns)]
5454
_ => Err("Invalid QueryResult type".into()),

‎crates/pg_typecheck/Cargo.toml

+7-7
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ version = "0.0.0"
1212

1313

1414
[dependencies]
15-
pg_base_db.workspace = true
15+
insta = "1.31.0"
16+
pg_console.workspace = true
17+
pg_diagnostics.workspace = true
1618
pg_query_ext.workspace = true
1719
pg_schema_cache.workspace = true
18-
pg_syntax.workspace = true
20+
sqlx.workspace = true
1921
text-size.workspace = true
20-
21-
sqlx.workspace = true
22-
23-
async-std = "1.12.0"
24-
22+
tokio.workspace = true
23+
tree-sitter.workspace = true
24+
tree_sitter_sql.workspace = true
2525

2626
[dev-dependencies]
2727
pg_test_utils.workspace = true
+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
use std::io;
2+
3+
use pg_console::markup;
4+
use pg_diagnostics::{Advices, Diagnostic, LogCategory, MessageAndDescription, Severity, Visit};
5+
use sqlx::postgres::{PgDatabaseError, PgSeverity};
6+
use text_size::TextRange;
7+
8+
/// A specialized diagnostic for the typechecker.
9+
///
10+
/// Type diagnostics are always **errors**.
11+
#[derive(Clone, Debug, Diagnostic)]
12+
#[diagnostic(category = "typecheck")]
13+
pub struct TypecheckDiagnostic {
14+
#[location(span)]
15+
span: Option<TextRange>,
16+
#[description]
17+
#[message]
18+
message: MessageAndDescription,
19+
#[advice]
20+
advices: TypecheckAdvices,
21+
#[severity]
22+
severity: Severity,
23+
}
24+
25+
#[derive(Debug, Clone)]
26+
struct TypecheckAdvices {
27+
code: String,
28+
schema: Option<String>,
29+
table: Option<String>,
30+
column: Option<String>,
31+
data_type: Option<String>,
32+
constraint: Option<String>,
33+
line: Option<usize>,
34+
file: Option<String>,
35+
detail: Option<String>,
36+
routine: Option<String>,
37+
where_: Option<String>,
38+
hint: Option<String>,
39+
}
40+
41+
impl Advices for TypecheckAdvices {
42+
fn record(&self, visitor: &mut dyn Visit) -> io::Result<()> {
43+
// First, show the error code
44+
visitor.record_log(
45+
LogCategory::Error,
46+
&markup! { "Error Code: " <Emphasis>{&self.code}</Emphasis> },
47+
)?;
48+
49+
// Show detailed message if available
50+
if let Some(detail) = &self.detail {
51+
visitor.record_log(LogCategory::Info, &detail)?;
52+
}
53+
54+
// Show object location information
55+
if let (Some(schema), Some(table)) = (&self.schema, &self.table) {
56+
let mut location = format!("In table: {schema}.{table}");
57+
if let Some(column) = &self.column {
58+
location.push_str(&format!(", column: {column}"));
59+
}
60+
visitor.record_log(LogCategory::Info, &location)?;
61+
}
62+
63+
// Show constraint information
64+
if let Some(constraint) = &self.constraint {
65+
visitor.record_log(
66+
LogCategory::Info,
67+
&markup! { "Constraint: " <Emphasis>{constraint}</Emphasis> },
68+
)?;
69+
}
70+
71+
// Show data type information
72+
if let Some(data_type) = &self.data_type {
73+
visitor.record_log(
74+
LogCategory::Info,
75+
&markup! { "Data type: " <Emphasis>{data_type}</Emphasis> },
76+
)?;
77+
}
78+
79+
// Show context information
80+
if let Some(where_) = &self.where_ {
81+
visitor.record_log(LogCategory::Info, &markup! { "Context:\n"{where_}"" })?;
82+
}
83+
84+
// Show hint if available
85+
if let Some(hint) = &self.hint {
86+
visitor.record_log(LogCategory::Info, &markup! { "Hint: "{hint}"" })?;
87+
}
88+
89+
// Show source location if available
90+
if let (Some(file), Some(line)) = (&self.file, &self.line) {
91+
if let Some(routine) = &self.routine {
92+
visitor.record_log(
93+
LogCategory::Info,
94+
&markup! { "Source: "{file}":"{line}" in "{routine}"" },
95+
)?;
96+
} else {
97+
visitor.record_log(LogCategory::Info, &markup! { "Source: "{file}":"{line}"" })?;
98+
}
99+
}
100+
101+
Ok(())
102+
}
103+
}
104+
105+
pub(crate) fn create_type_error(
106+
pg_err: &PgDatabaseError,
107+
ts: Option<&tree_sitter::Tree>,
108+
) -> TypecheckDiagnostic {
109+
let position = pg_err.position().and_then(|pos| match pos {
110+
sqlx::postgres::PgErrorPosition::Original(pos) => Some(pos - 1),
111+
_ => None,
112+
});
113+
114+
let range = position.and_then(|pos| {
115+
ts.and_then(|tree| {
116+
tree.root_node()
117+
.named_descendant_for_byte_range(pos, pos)
118+
.map(|node| {
119+
TextRange::new(
120+
node.start_byte().try_into().unwrap(),
121+
node.end_byte().try_into().unwrap(),
122+
)
123+
})
124+
})
125+
});
126+
127+
let severity = match pg_err.severity() {
128+
PgSeverity::Panic => Severity::Error,
129+
PgSeverity::Fatal => Severity::Error,
130+
PgSeverity::Error => Severity::Error,
131+
PgSeverity::Warning => Severity::Warning,
132+
PgSeverity::Notice => Severity::Hint,
133+
PgSeverity::Debug => Severity::Hint,
134+
PgSeverity::Info => Severity::Information,
135+
PgSeverity::Log => Severity::Information,
136+
};
137+
138+
TypecheckDiagnostic {
139+
message: pg_err.to_string().into(),
140+
severity,
141+
span: range,
142+
advices: TypecheckAdvices {
143+
code: pg_err.code().to_string(),
144+
hint: pg_err.hint().and_then(|s| {
145+
if !s.is_empty() {
146+
Some(s.to_string())
147+
} else {
148+
None
149+
}
150+
}),
151+
schema: pg_err.schema().and_then(|s| {
152+
if !s.is_empty() {
153+
Some(s.to_string())
154+
} else {
155+
None
156+
}
157+
}),
158+
table: pg_err.table().and_then(|s| {
159+
if !s.is_empty() {
160+
Some(s.to_string())
161+
} else {
162+
None
163+
}
164+
}),
165+
detail: pg_err.detail().and_then(|s| {
166+
if !s.is_empty() {
167+
Some(s.to_string())
168+
} else {
169+
None
170+
}
171+
}),
172+
column: pg_err.column().and_then(|s| {
173+
if !s.is_empty() {
174+
Some(s.to_string())
175+
} else {
176+
None
177+
}
178+
}),
179+
data_type: pg_err.data_type().and_then(|s| {
180+
if !s.is_empty() {
181+
Some(s.to_string())
182+
} else {
183+
None
184+
}
185+
}),
186+
constraint: pg_err.constraint().and_then(|s| {
187+
if !s.is_empty() {
188+
Some(s.to_string())
189+
} else {
190+
None
191+
}
192+
}),
193+
line: pg_err.line(),
194+
file: pg_err.file().and_then(|s| {
195+
if !s.is_empty() {
196+
Some(s.to_string())
197+
} else {
198+
None
199+
}
200+
}),
201+
routine: pg_err.routine().and_then(|s| {
202+
if !s.is_empty() {
203+
Some(s.to_string())
204+
} else {
205+
None
206+
}
207+
}),
208+
where_: pg_err.r#where().and_then(|s| {
209+
if !s.is_empty() {
210+
Some(s.to_string())
211+
} else {
212+
None
213+
}
214+
}),
215+
},
216+
}
217+
}

‎crates/pg_typecheck/src/lib.rs

+23-82
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1+
mod diagnostics;
2+
3+
use diagnostics::create_type_error;
4+
pub use diagnostics::TypecheckDiagnostic;
15
use sqlx::postgres::PgDatabaseError;
26
pub use sqlx::postgres::PgSeverity;
37
use sqlx::Executor;
48
use sqlx::PgPool;
59
use text_size::TextRange;
6-
use text_size::TextSize;
710

8-
pub struct TypecheckerParams<'a> {
11+
#[derive(Debug)]
12+
pub struct TypecheckParams<'a> {
913
pub conn: &'a PgPool,
1014
pub sql: &'a str,
11-
pub enriched_ast: Option<&'a pg_syntax::AST>,
1215
pub ast: &'a pg_query_ext::NodeEnum,
16+
pub tree: Option<&'a tree_sitter::Tree>,
1317
}
1418

1519
#[derive(Debug, Clone)]
@@ -25,90 +29,27 @@ pub struct TypeError {
2529
pub constraint: Option<String>,
2630
}
2731

28-
pub async fn check_sql<'a>(params: TypecheckerParams<'a>) -> Vec<TypeError> {
29-
let mut errs = vec![];
30-
31-
// prpeared statements work only for select, insert, update, delete, and cte
32-
if match params.ast {
33-
pg_query_ext::NodeEnum::SelectStmt(_) => false,
34-
pg_query_ext::NodeEnum::InsertStmt(_) => false,
35-
pg_query_ext::NodeEnum::UpdateStmt(_) => false,
36-
pg_query_ext::NodeEnum::DeleteStmt(_) => false,
37-
pg_query_ext::NodeEnum::CommonTableExpr(_) => false,
38-
_ => true,
39-
} {
40-
return errs;
32+
pub async fn check_sql(params: TypecheckParams<'_>) -> Option<TypecheckDiagnostic> {
33+
// Check if the AST is not a supported statement type
34+
if !matches!(
35+
params.ast,
36+
pg_query_ext::NodeEnum::SelectStmt(_)
37+
| pg_query_ext::NodeEnum::InsertStmt(_)
38+
| pg_query_ext::NodeEnum::UpdateStmt(_)
39+
| pg_query_ext::NodeEnum::DeleteStmt(_)
40+
| pg_query_ext::NodeEnum::CommonTableExpr(_)
41+
) {
42+
return None;
4143
}
4244

4345
let res = params.conn.prepare(params.sql).await;
4446

45-
if res.is_err() {
46-
if let sqlx::Error::Database(err) = res.as_ref().unwrap_err() {
47+
match res {
48+
Ok(_) => None,
49+
Err(sqlx::Error::Database(err)) => {
4750
let pg_err = err.downcast_ref::<PgDatabaseError>();
48-
49-
let position = match pg_err.position() {
50-
Some(sqlx::postgres::PgErrorPosition::Original(pos)) => Some(pos - 1),
51-
_ => None,
52-
};
53-
54-
let range = match params.enriched_ast {
55-
Some(ast) => {
56-
if position.is_none() {
57-
None
58-
} else {
59-
ast.covering_node(TextRange::empty(
60-
TextSize::try_from(position.unwrap()).unwrap(),
61-
))
62-
.map(|node| node.range())
63-
}
64-
}
65-
None => None,
66-
};
67-
68-
errs.push(TypeError {
69-
message: pg_err.message().to_string(),
70-
code: pg_err.code().to_string(),
71-
severity: pg_err.severity(),
72-
position,
73-
range,
74-
table: pg_err.table().map(|s| s.to_string()),
75-
column: pg_err.column().map(|s| s.to_string()),
76-
data_type: pg_err.data_type().map(|s| s.to_string()),
77-
constraint: pg_err.constraint().map(|s| s.to_string()),
78-
});
51+
Some(create_type_error(pg_err, params.tree))
7952
}
80-
}
81-
82-
errs
83-
}
84-
85-
#[cfg(test)]
86-
mod tests {
87-
use async_std::task::block_on;
88-
use pg_test_utils::test_database::get_new_test_db;
89-
90-
use crate::{check_sql, TypecheckerParams};
91-
92-
#[test]
93-
fn test_basic_type() {
94-
let input = "select id, unknown from contact;";
95-
96-
let test_db = block_on(get_new_test_db());
97-
98-
let root = pg_query_ext::parse(input).unwrap();
99-
let ast = pg_syntax::parse_syntax(input, &root).ast;
100-
101-
let errs = block_on(check_sql(TypecheckerParams {
102-
conn: &test_db,
103-
sql: input,
104-
ast: &root,
105-
enriched_ast: Some(&ast),
106-
}));
107-
108-
assert_eq!(errs.len(), 1);
109-
110-
let e = &errs[0];
111-
112-
assert_eq!(&input[e.range.unwrap()], "contact");
53+
Err(_) => None,
11354
}
11455
}
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use pg_console::{
2+
fmt::{Formatter, HTML},
3+
markup,
4+
};
5+
use pg_diagnostics::PrintDiagnostic;
6+
use pg_test_utils::test_database::get_new_test_db;
7+
use pg_typecheck::{check_sql, TypecheckParams};
8+
use sqlx::Executor;
9+
10+
async fn test(name: &str, query: &str, setup: &str) {
11+
let test_db = get_new_test_db().await;
12+
13+
test_db
14+
.execute(setup)
15+
.await
16+
.expect("Failed to setup test database");
17+
18+
let mut parser = tree_sitter::Parser::new();
19+
parser
20+
.set_language(tree_sitter_sql::language())
21+
.expect("Error loading sql language");
22+
23+
let root = pg_query_ext::parse(query).unwrap();
24+
let tree = parser.parse(query, None);
25+
26+
let conn = &test_db;
27+
let result = check_sql(TypecheckParams {
28+
conn,
29+
sql: query,
30+
ast: &root,
31+
tree: tree.as_ref(),
32+
})
33+
.await;
34+
35+
let mut content = vec![];
36+
let mut writer = HTML::new(&mut content);
37+
38+
Formatter::new(&mut writer)
39+
.write_markup(markup! {
40+
{PrintDiagnostic::simple(&result.unwrap())}
41+
})
42+
.unwrap();
43+
44+
let content = String::from_utf8(content).unwrap();
45+
insta::with_settings!({
46+
prepend_module_to_snapshot => false,
47+
}, {
48+
insta::assert_snapshot!(name, content);
49+
});
50+
}
51+
52+
#[tokio::test]
53+
async fn invalid_column() {
54+
test(
55+
"invalid_column",
56+
"select id, unknown from contacts;",
57+
r#"
58+
create table public.contacts (
59+
id serial primary key,
60+
name varchar(255) not null,
61+
is_vegetarian bool default false,
62+
middle_name varchar(255)
63+
);
64+
"#,
65+
)
66+
.await;
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
source: crates/pg_typecheck/tests/diagnostics.rs
3+
expression: content
4+
snapshot_kind: text
5+
---
6+
typecheck ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
7+
8+
<strong><span style="color: Tomato;">✖</span></strong> <span style="color: Tomato;">column &quot;unknown&quot; does not exist</span>
9+
10+
<strong><span style="color: Tomato;">✖</span></strong> <span style="color: Tomato;">Error Code: </span><span style="color: Tomato;"><strong>42703</strong></span>
11+
12+
<strong><span style="color: lightgreen;">ℹ</span></strong> <span style="color: lightgreen;">Source: </span><span style="color: lightgreen;">parse_relation.c</span><span style="color: lightgreen;">:</span><span style="color: lightgreen;">3716</span><span style="color: lightgreen;"> in </span><span style="color: lightgreen;">errorMissingColumn</span>

‎crates/pg_workspace/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pg_fs = { workspace = true, features = ["serde"] }
2626
pg_query_ext = { workspace = true }
2727
pg_schema_cache = { workspace = true }
2828
pg_statement_splitter = { workspace = true }
29+
pg_typecheck = { workspace = true }
2930
rustc-hash = { workspace = true }
3031
serde = { workspace = true, features = ["derive"] }
3132
serde_json = { workspace = true, features = ["raw_value"] }

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

+91-42
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ use analyser::AnalyserVisitorBuilder;
44
use change::StatementChange;
55
use dashmap::{DashMap, DashSet};
66
use document::{Document, Statement};
7+
use futures::{stream, StreamExt};
78
use pg_analyse::{AnalyserOptions, AnalysisFilter};
89
use pg_analyser::{Analyser, AnalyserConfig, AnalyserContext};
910
use pg_diagnostics::{serde::Diagnostic as SDiagnostic, Diagnostic, DiagnosticExt, Severity};
1011
use pg_fs::{ConfigName, PgLspPath};
1112
use pg_query::PgQueryStore;
1213
use pg_schema_cache::SchemaCache;
14+
use pg_typecheck::TypecheckParams;
1315
use sqlx::PgPool;
1416
use std::sync::LazyLock;
1517
use tokio::runtime::Runtime;
@@ -336,50 +338,97 @@ impl Workspace for WorkspaceServer {
336338
filter,
337339
});
338340

339-
let diagnostics: Vec<SDiagnostic> = doc
340-
.iter_statements_with_range()
341-
.flat_map(|(stmt, r)| {
342-
let mut stmt_diagnostics = vec![];
343-
344-
stmt_diagnostics.extend(self.pg_query.get_diagnostics(&stmt));
345-
let ast = self.pg_query.get_ast(&stmt);
346-
if let Some(ast) = ast {
347-
stmt_diagnostics.extend(
348-
analyser
349-
.run(AnalyserContext { root: &ast })
350-
.into_iter()
351-
.map(SDiagnostic::new)
352-
.collect::<Vec<_>>(),
353-
);
341+
let mut diagnostics: Vec<SDiagnostic> = vec![];
342+
343+
// run diagnostics for each statement in parallel if its mostly i/o work
344+
if let Ok(connection) = self.connection.read() {
345+
if let Some(pool) = connection.get_pool() {
346+
let typecheck_params: Vec<_> = doc
347+
.iter_statements_with_text_and_range()
348+
.map(|(stmt, range, text)| {
349+
let ast = self.pg_query.get_ast(&stmt);
350+
let tree = self.tree_sitter.get_parse_tree(&stmt);
351+
(text.to_string(), ast, tree, *range)
352+
})
353+
.collect();
354+
355+
let pool_clone = pool.clone();
356+
let path_clone = params.path.clone();
357+
let async_results = run_async(async move {
358+
stream::iter(typecheck_params)
359+
.map(|(text, ast, tree, range)| {
360+
let pool = pool_clone.clone();
361+
let path = path_clone.clone();
362+
async move {
363+
if let Some(ast) = ast {
364+
pg_typecheck::check_sql(TypecheckParams {
365+
conn: &pool,
366+
sql: &text,
367+
ast: &ast,
368+
tree: tree.as_deref(),
369+
})
370+
.await
371+
.map(|d| {
372+
let r = d.location().span.map(|span| span + range.start());
373+
374+
d.with_file_path(path.as_path().display().to_string())
375+
.with_file_span(r.unwrap_or(range))
376+
})
377+
} else {
378+
None
379+
}
380+
}
381+
})
382+
.buffer_unordered(10)
383+
.collect::<Vec<_>>()
384+
.await
385+
})?;
386+
387+
for result in async_results.into_iter().flatten() {
388+
diagnostics.push(SDiagnostic::new(result));
354389
}
390+
}
391+
}
355392

356-
stmt_diagnostics
357-
.into_iter()
358-
.map(|d| {
359-
// We do now check if the severity of the diagnostics should be changed.
360-
// The configuration allows to change the severity of the diagnostics emitted by rules.
361-
let severity = d
362-
.category()
363-
.filter(|category| category.name().starts_with("lint/"))
364-
.map_or_else(
365-
|| d.severity(),
366-
|category| {
367-
settings
368-
.as_ref()
369-
.get_severity_from_rule_code(category)
370-
.unwrap_or(Severity::Warning)
371-
},
372-
);
373-
374-
SDiagnostic::new(
375-
d.with_file_path(params.path.as_path().display().to_string())
376-
.with_file_span(r)
377-
.with_severity(severity),
378-
)
379-
})
380-
.collect::<Vec<_>>()
381-
})
382-
.collect();
393+
diagnostics.extend(doc.iter_statements_with_range().flat_map(|(stmt, r)| {
394+
let mut stmt_diagnostics = self.pg_query.get_diagnostics(&stmt);
395+
396+
let ast = self.pg_query.get_ast(&stmt);
397+
398+
if let Some(ast) = ast {
399+
stmt_diagnostics.extend(
400+
analyser
401+
.run(AnalyserContext { root: &ast })
402+
.into_iter()
403+
.map(SDiagnostic::new)
404+
.collect::<Vec<_>>(),
405+
);
406+
}
407+
408+
stmt_diagnostics
409+
.into_iter()
410+
.map(|d| {
411+
let severity = d
412+
.category()
413+
.filter(|category| category.name().starts_with("lint/"))
414+
.map_or_else(
415+
|| d.severity(),
416+
|category| {
417+
settings
418+
.as_ref()
419+
.get_severity_from_rule_code(category)
420+
.unwrap_or(Severity::Warning)
421+
},
422+
);
423+
424+
SDiagnostic::new(
425+
d.with_file_path(params.path.as_path().display().to_string())
426+
.with_file_span(r)
427+
.with_severity(severity),
428+
)
429+
})
430+
.collect::<Vec<_>>()
431+
}));
383432

384433
let errors = diagnostics
385434
.iter()

‎docker-compose.yml

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
version: "3.8"
21
services:
32
db:
43
# postgres://postgres:postgres@127.0.0.1:5432/postgres

‎rustfmt.toml

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
newline_style = "Unix"
2+
edition = "2018"

0 commit comments

Comments
 (0)
Please sign in to comment.