Skip to content

Commit e74204d

Browse files
Merge branch 'update-grammar-rules-1' of github.com:supabase-community/postgres-language-server into test/update-insert-copy-grammar
2 parents 786a6fb + dc47661 commit e74204d

7 files changed

Lines changed: 243 additions & 52 deletions

File tree

crates/pgls_completions/src/providers/keywords.rs

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
6363
SqlKeyword::new("boolean"),
6464
SqlKeyword::new("brin"),
6565
SqlKeyword::new("btree"),
66+
SqlKeyword::new("buffer_usage_limit"),
67+
SqlKeyword::new("buffers"),
6668
SqlKeyword::new("by"),
6769
SqlKeyword::new("bytea"),
6870
SqlKeyword::new("cache"),
@@ -77,7 +79,6 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
7779
SqlKeyword::new("check"),
7880
SqlKeyword::new("collate"),
7981
SqlKeyword::new("column"),
80-
SqlKeyword::new("columns"),
8182
SqlKeyword::new("comment").starts_statement(),
8283
SqlKeyword::new("commit").starts_statement(),
8384
SqlKeyword::new("committed"),
@@ -89,6 +90,7 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
8990
SqlKeyword::new("constraints"),
9091
SqlKeyword::new("copy").starts_statement(),
9192
SqlKeyword::new("cost"),
93+
SqlKeyword::new("costs"),
9294
SqlKeyword::new("create").starts_statement(),
9395
SqlKeyword::new("cross"),
9496
SqlKeyword::new("csv"),
@@ -109,7 +111,6 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
109111
SqlKeyword::new("delete").starts_statement(),
110112
SqlKeyword::new("delimiter"),
111113
SqlKeyword::new("desc"),
112-
SqlKeyword::new("disable_page_skipping"),
113114
SqlKeyword::new("distinct"),
114115
SqlKeyword::new("do").starts_statement(),
115116
SqlKeyword::new("double"),
@@ -149,6 +150,7 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
149150
SqlKeyword::new("function"),
150151
SqlKeyword::new("functions"),
151152
SqlKeyword::new("generated"),
153+
SqlKeyword::new("generic_plan"),
152154
SqlKeyword::new("gin"),
153155
SqlKeyword::new("gist"),
154156
SqlKeyword::new("grant").starts_statement(),
@@ -159,12 +161,12 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
159161
SqlKeyword::new("having"),
160162
SqlKeyword::new("header"),
161163
SqlKeyword::new("if"),
164+
SqlKeyword::new("ignore"),
162165
SqlKeyword::new("immediate"),
163166
SqlKeyword::new("immutable"),
164167
SqlKeyword::new("in").require_prefix(),
165168
SqlKeyword::new("increment"),
166169
SqlKeyword::new("index"),
167-
SqlKeyword::new("index_cleanup"),
168170
SqlKeyword::new("inet"),
169171
SqlKeyword::new("inherit"),
170172
SqlKeyword::new("initially"),
@@ -194,14 +196,15 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
194196
SqlKeyword::new("limit"),
195197
SqlKeyword::new("list"),
196198
SqlKeyword::new("local"),
197-
SqlKeyword::new("location"),
199+
SqlKeyword::new("log_verbosity"),
198200
SqlKeyword::new("logged"),
199201
SqlKeyword::new("main"),
200202
SqlKeyword::new("maintain"),
201203
SqlKeyword::new("match"),
202204
SqlKeyword::new("matched"),
203205
SqlKeyword::new("materialized"),
204206
SqlKeyword::new("maxvalue"),
207+
SqlKeyword::new("memory"),
205208
SqlKeyword::new("merge").starts_statement(),
206209
SqlKeyword::new("minvalue"),
207210
SqlKeyword::new("money"),
@@ -213,7 +216,6 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
213216
SqlKeyword::new("none"),
214217
SqlKeyword::new("not").require_prefix(),
215218
SqlKeyword::new("nothing"),
216-
SqlKeyword::new("nowait"),
217219
SqlKeyword::new("null").require_prefix(),
218220
SqlKeyword::new("nulls"),
219221
SqlKeyword::new("numeric"),
@@ -224,6 +226,7 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
224226
SqlKeyword::new("oids"),
225227
SqlKeyword::new("old"),
226228
SqlKeyword::new("on"),
229+
SqlKeyword::new("on_error"),
227230
SqlKeyword::new("only").require_prefix(),
228231
SqlKeyword::new("option"),
229232
SqlKeyword::new("or").require_prefix(),
@@ -238,19 +241,16 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
238241
SqlKeyword::new("owner"),
239242
SqlKeyword::new("parallel"),
240243
SqlKeyword::new("partition"),
241-
SqlKeyword::new("partitioned"),
242244
SqlKeyword::new("password"),
243245
SqlKeyword::new("permissive"),
244246
SqlKeyword::new("plain"),
245247
SqlKeyword::new("policy"),
246-
SqlKeyword::new("precedes"),
247248
SqlKeyword::new("preceding"),
248249
SqlKeyword::new("precision"),
249250
SqlKeyword::new("primary"),
250251
SqlKeyword::new("privileges"),
251252
SqlKeyword::new("procedure"),
252253
SqlKeyword::new("procedures"),
253-
SqlKeyword::new("process_toast"),
254254
SqlKeyword::new("program"),
255255
SqlKeyword::new("public"),
256256
SqlKeyword::new("quote"),
@@ -264,10 +264,10 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
264264
SqlKeyword::new("regnamespace"),
265265
SqlKeyword::new("regproc"),
266266
SqlKeyword::new("regtype"),
267+
SqlKeyword::new("reject_limit"),
267268
SqlKeyword::new("rename"),
268269
SqlKeyword::new("repeatable"),
269270
SqlKeyword::new("replace"),
270-
SqlKeyword::new("replication"),
271271
SqlKeyword::new("reset").starts_statement(),
272272
SqlKeyword::new("restart"),
273273
SqlKeyword::new("restrict"),
@@ -277,7 +277,6 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
277277
SqlKeyword::new("returning"),
278278
SqlKeyword::new("returns"),
279279
SqlKeyword::new("revoke").starts_statement(),
280-
SqlKeyword::new("rewrite"),
281280
SqlKeyword::new("right"),
282281
SqlKeyword::new("role"),
283282
SqlKeyword::new("rollback").starts_statement(),
@@ -296,7 +295,10 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
296295
SqlKeyword::new("session_user"),
297296
SqlKeyword::new("set").starts_statement(),
298297
SqlKeyword::new("setof"),
298+
SqlKeyword::new("settings"),
299+
SqlKeyword::new("share"),
299300
SqlKeyword::new("show").starts_statement(),
301+
SqlKeyword::new("silent"),
300302
SqlKeyword::new("similar").require_prefix(),
301303
SqlKeyword::new("skip_locked"),
302304
SqlKeyword::new("smallint"),
@@ -309,9 +311,12 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
309311
SqlKeyword::new("statement"),
310312
SqlKeyword::new("statistics"),
311313
SqlKeyword::new("stdin"),
314+
SqlKeyword::new("stdout"),
315+
SqlKeyword::new("stop"),
312316
SqlKeyword::new("storage"),
313317
SqlKeyword::new("stored"),
314318
SqlKeyword::new("strict"),
319+
SqlKeyword::new("summary"),
315320
SqlKeyword::new("support"),
316321
SqlKeyword::new("system"),
317322
SqlKeyword::new("table"),
@@ -325,6 +330,7 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
325330
SqlKeyword::new("time"),
326331
SqlKeyword::new("timestamp"),
327332
SqlKeyword::new("timestamptz"),
333+
SqlKeyword::new("timing"),
328334
SqlKeyword::new("to"),
329335
SqlKeyword::new("transaction"),
330336
SqlKeyword::new("trigger"),
@@ -353,13 +359,15 @@ pub static ALL_KEYWORDS: &[SqlKeyword] = &[
353359
SqlKeyword::new("version"),
354360
SqlKeyword::new("view"),
355361
SqlKeyword::new("volatile"),
362+
SqlKeyword::new("wal"),
356363
SqlKeyword::new("when"),
357364
SqlKeyword::new("where"),
358365
SqlKeyword::new("window"),
359366
SqlKeyword::new("with").starts_statement(),
360367
SqlKeyword::new("without"),
361368
SqlKeyword::new("write"),
362369
SqlKeyword::new("xml"),
370+
SqlKeyword::new("yaml"),
363371
SqlKeyword::new("zone"),
364372
];
365373

@@ -409,17 +417,92 @@ pub fn complete_keywords<'a>(
409417

410418
#[cfg(test)]
411419
mod tests {
420+
use std::collections::HashSet;
421+
412422
use pgls_test_utils::QueryWithCursorPosition;
423+
use regex::Regex;
413424
use sqlx::PgPool;
425+
use tree_sitter::Language;
414426

415427
use crate::{
416428
CompletionItemKind,
429+
providers::keywords::ALL_KEYWORDS,
417430
test_helper::{
418431
CompletionAssertion, TestCompletionsCase, TestCompletionsSuite,
419432
assert_complete_results, assert_no_complete_results,
420433
},
421434
};
422435

436+
#[test]
437+
fn has_all_keywords_from_treesitter_grammar() {
438+
let expected_keywords = keywords_from_treesitter_grammar();
439+
let actual_keywords = all_keywords_set();
440+
441+
let mut missing_keywords = expected_keywords
442+
.difference(&actual_keywords)
443+
.cloned()
444+
.collect::<Vec<_>>();
445+
missing_keywords.sort_unstable();
446+
447+
assert!(
448+
missing_keywords.is_empty(),
449+
"Found {} keyword(s) from tree-sitter grammar that are missing from ALL_KEYWORDS.\n\
450+
Add missing entries to ALL_KEYWORDS in crates/pgls_completions/src/providers/keywords.rs.\n\
451+
Missing keywords:\n{}",
452+
missing_keywords.len(),
453+
missing_keywords.join("\n")
454+
);
455+
}
456+
457+
#[test]
458+
fn has_no_extra_keywords_outside_treesitter_grammar() {
459+
let expected_keywords = keywords_from_treesitter_grammar();
460+
let actual_keywords = all_keywords_set();
461+
462+
let mut extra_keywords = actual_keywords
463+
.difference(&expected_keywords)
464+
.cloned()
465+
.collect::<Vec<_>>();
466+
extra_keywords.sort_unstable();
467+
468+
assert!(
469+
extra_keywords.is_empty(),
470+
"Found {} keyword(s) in ALL_KEYWORDS that are not tree-sitter grammar keywords.\n\
471+
Remove these from ALL_KEYWORDS or add matching keyword_* symbols to grammar.js.\n\
472+
Extra keywords:\n{}",
473+
extra_keywords.len(),
474+
extra_keywords.join("\n")
475+
);
476+
}
477+
478+
fn all_keywords_set() -> HashSet<String> {
479+
ALL_KEYWORDS
480+
.iter()
481+
.map(|keyword| keyword.name.to_string())
482+
.collect::<HashSet<_>>()
483+
}
484+
485+
fn keywords_from_treesitter_grammar() -> HashSet<String> {
486+
let language: Language = pgls_treesitter_grammar::LANGUAGE.into();
487+
// Tree-sitter generates auxiliary symbol names like `keyword_int_token2` for keyword
488+
// rules that use `choice(...)`. Strip the suffix to compare canonical keyword names.
489+
let generated_token_suffix =
490+
Regex::new(r"_token\d+$").expect("valid regex for generated token suffix");
491+
let mut keywords = HashSet::new();
492+
493+
for id in 0..language.node_kind_count() {
494+
let Some(kind) = language.node_kind_for_id(id as u16) else {
495+
continue;
496+
};
497+
if let Some(keyword) = kind.strip_prefix("keyword_") {
498+
let keyword = generated_token_suffix.replace(keyword, "").to_string();
499+
keywords.insert(keyword);
500+
}
501+
}
502+
503+
keywords
504+
}
505+
423506
#[sqlx::test]
424507
async fn completes_stmt_start_keywords(pool: PgPool) {
425508
let setup = r#"

crates/pgls_treesitter/src/context/ancestors.rs

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ pub struct Scope {
33
pub ancestors: AncestorTracker,
44
}
55

6-
static SCOPE_BOUNDARIES: &[&str] = &["statement", "ERROR", "program"];
6+
static SCOPE_BOUNDARIES: &[&str] = &[
7+
"statement",
8+
"ERROR",
9+
"program",
10+
"block",
11+
"transaction",
12+
"psql_meta_command",
13+
"copy_data_stream",
14+
];
715

816
#[derive(Debug)]
917
pub struct ScopeTracker {
@@ -22,7 +30,7 @@ impl ScopeTracker {
2230

2331
self.scopes
2432
.last_mut()
25-
.unwrap_or_else(|| panic!("Unhandled node kind: {}", node.kind()))
33+
.unwrap_or_else(|| panic!("No top-level grammar-rule found. Please create an issue with the entire Postgres file, noting cursor/hover position."))
2634
.ancestors
2735
.register(node, position);
2836
}
@@ -105,3 +113,104 @@ impl AncestorTracker {
105113
true
106114
}
107115
}
116+
117+
#[cfg(test)]
118+
mod tests {
119+
use crate::context::{TreeSitterContextParams, TreesitterContext};
120+
121+
fn get_tree(input: &str) -> tree_sitter::Tree {
122+
let mut parser = tree_sitter::Parser::new();
123+
parser
124+
.set_language(&pgls_treesitter_grammar::LANGUAGE.into())
125+
.expect("Couldn't set language");
126+
127+
parser.parse(input, None).expect("Unable to parse tree")
128+
}
129+
130+
fn assert_no_panic_for_all_positions(sql: &str) {
131+
let tree = get_tree(sql);
132+
for pos in 0..sql.len() {
133+
let params = TreeSitterContextParams {
134+
position: (pos as u32).into(),
135+
text: sql,
136+
tree: &tree,
137+
};
138+
let _ = TreesitterContext::new(params);
139+
}
140+
}
141+
142+
#[test]
143+
fn scope_boundary_block() {
144+
assert_no_panic_for_all_positions("BEGIN; SELECT 1; END;");
145+
}
146+
147+
#[test]
148+
fn scope_boundary_transaction() {
149+
assert_no_panic_for_all_positions("BEGIN TRANSACTION; SELECT 1; COMMIT;");
150+
assert_no_panic_for_all_positions("BEGIN; INSERT INTO t VALUES (1); ROLLBACK;");
151+
}
152+
153+
#[test]
154+
fn scope_boundary_psql_meta_command() {
155+
assert_no_panic_for_all_positions("\\dt\n\\d users");
156+
}
157+
158+
#[test]
159+
fn scope_boundary_copy_data_stream() {
160+
assert_no_panic_for_all_positions("COPY t FROM STDIN;\n1\tAlice\n\\.\n");
161+
}
162+
163+
#[test]
164+
fn scope_boundary_comment() {
165+
assert_no_panic_for_all_positions("-- a comment\nSELECT 1;");
166+
}
167+
168+
#[test]
169+
fn issue_704_regression() {
170+
let statements = vec![
171+
r#"
172+
CREATE OR REPLACE FUNCTION my_schema.my_function1(
173+
pi_1 character varying,
174+
pi_2 character varying,
175+
pi_3 jsonb,
176+
OUT po_1 integer,
177+
OUT po_2 integer,
178+
OUT result integer
179+
)
180+
RETURNS record
181+
LANGUAGE plpgsql
182+
AS $function$
183+
"#
184+
.trim(),
185+
186+
r#"
187+
CREATE OR REPLACE FUNCTION my_schema.my_function2(
188+
pi_1 character varying,
189+
pi_2 character varying,
190+
pi_3 jsonb,
191+
OUT po_1 integer,
192+
OUT po_2 integer,
193+
OUT result integer
194+
)
195+
RETURNS record
196+
LANGUAGE plpgsql
197+
AS $function$
198+
DECLARE
199+
BEGIN
200+
-- Function logic goes here
201+
-- For example, you can perform some operations using the input parameters and set the output parameters accordingly
202+
203+
-- Example logic (replace with actual implementation):
204+
po_1 := length(pi_1); -- Set po_1 to the length of pi_1
205+
po_2 := length(pi_2); -- Set po_2 to the length of pi_2
206+
result := po_1 + po_2; -- Set result to the sum of po_1 and po_2
207+
END;
208+
$function$;
209+
"#.trim(),
210+
];
211+
212+
for stmt in statements {
213+
assert_no_panic_for_all_positions(stmt);
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)