Skip to content

Commit a435c42

Browse files
feat(completions): complete (materialized) views (#409)
1 parent f24ddf6 commit a435c42

File tree

6 files changed

+139
-25
lines changed

6 files changed

+139
-25
lines changed

.sqlx/query-2a964a12383b977bbbbd6fe7298dfce00358ecbe878952e8d4915c06cc5c9e0f.json renamed to .sqlx/query-66d92238c94b5f1c99fbf068a0b5cf4c296b594fe9e6cebbdc382acde73f4fb9.json

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

crates/pgt_completions/src/providers/columns.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub fn complete_columns<'a>(ctx: &CompletionContext<'a>, builder: &mut Completio
1717
label: col.name.clone(),
1818
score: CompletionScore::from(relevance.clone()),
1919
filter: CompletionFilter::from(relevance),
20-
description: format!("Table: {}.{}", col.schema_name, col.table_name),
20+
description: format!("{}.{}", col.schema_name, col.table_name),
2121
kind: CompletionItemKind::Column,
2222
completion_text: None,
2323
detail: col.type_name.as_ref().map(|t| t.to_string()),
@@ -92,7 +92,7 @@ mod tests {
9292
message: "correctly prefers the columns of present tables",
9393
query: format!(r#"select na{} from public.audio_books;"#, CURSOR_POS),
9494
label: "narrator",
95-
description: "Table: public.audio_books",
95+
description: "public.audio_books",
9696
},
9797
TestCase {
9898
message: "correctly handles nested queries",
@@ -110,13 +110,13 @@ mod tests {
110110
CURSOR_POS
111111
),
112112
label: "narrator_id",
113-
description: "Table: private.audio_books",
113+
description: "private.audio_books",
114114
},
115115
TestCase {
116116
message: "works without a schema",
117117
query: format!(r#"select na{} from users;"#, CURSOR_POS),
118118
label: "name",
119-
description: "Table: public.users",
119+
description: "public.users",
120120
},
121121
];
122122

@@ -186,10 +186,10 @@ mod tests {
186186
.collect();
187187

188188
let expected = vec![
189-
("name", "Table: public.users"),
190-
("narrator", "Table: public.audio_books"),
191-
("narrator_id", "Table: private.audio_books"),
192-
("id", "Table: public.audio_books"),
189+
("name", "public.users"),
190+
("narrator", "public.audio_books"),
191+
("narrator_id", "private.audio_books"),
192+
("id", "public.audio_books"),
193193
("name", "Schema: pg_catalog"),
194194
("nameconcatoid", "Schema: pg_catalog"),
195195
]
@@ -559,10 +559,7 @@ mod tests {
559559
)
560560
.as_str(),
561561
vec![
562-
CompletionAssertion::LabelAndDesc(
563-
"id".to_string(),
564-
"Table: public.two".to_string(),
565-
),
562+
CompletionAssertion::LabelAndDesc("id".to_string(), "public.two".to_string()),
566563
CompletionAssertion::Label("z".to_string()),
567564
],
568565
setup,

crates/pgt_completions/src/providers/tables.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,21 @@ pub fn complete_tables<'a>(ctx: &'a CompletionContext, builder: &mut CompletionB
1313
for table in available_tables {
1414
let relevance = CompletionRelevanceData::Table(table);
1515

16+
let detail: Option<String> = match table.table_kind {
17+
pgt_schema_cache::TableKind::Ordinary | pgt_schema_cache::TableKind::Partitioned => {
18+
None
19+
}
20+
pgt_schema_cache::TableKind::View => Some("View".into()),
21+
pgt_schema_cache::TableKind::MaterializedView => Some("MView".into()),
22+
};
23+
1624
let item = PossibleCompletionItem {
1725
label: table.name.clone(),
1826
score: CompletionScore::from(relevance.clone()),
1927
filter: CompletionFilter::from(relevance),
20-
description: format!("Schema: {}", table.schema),
28+
description: table.schema.to_string(),
2129
kind: CompletionItemKind::Table,
22-
detail: None,
30+
detail,
2331
completion_text: get_completion_text_with_schema_or_alias(
2432
ctx,
2533
&table.name,

crates/pgt_schema_cache/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ pub use policies::{Policy, PolicyCommand};
1919
pub use roles::*;
2020
pub use schema_cache::SchemaCache;
2121
pub use schemas::Schema;
22-
pub use tables::{ReplicaIdentity, Table};
22+
pub use tables::{ReplicaIdentity, Table, TableKind};
2323
pub use triggers::{Trigger, TriggerAffected, TriggerEvent};
2424
pub use types::{PostgresType, PostgresTypeAttribute};

crates/pgt_schema_cache/src/queries/tables.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ select
22
c.oid :: int8 as "id!",
33
nc.nspname as schema,
44
c.relname as name,
5+
c.relkind as table_kind,
56
c.relrowsecurity as rls_enabled,
67
c.relforcerowsecurity as rls_forced,
78
case
@@ -21,7 +22,7 @@ from
2122
pg_namespace nc
2223
join pg_class c on nc.oid = c.relnamespace
2324
where
24-
c.relkind in ('r', 'p')
25+
c.relkind in ('r', 'p', 'v', 'm')
2526
and not pg_is_other_temp_schema(nc.oid)
2627
and (
2728
pg_has_role(c.relowner, 'USAGE')

crates/pgt_schema_cache/src/tables.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,34 @@ impl From<String> for ReplicaIdentity {
2323
}
2424
}
2525

26+
#[derive(Debug, Clone, PartialEq, Eq, Default)]
27+
pub enum TableKind {
28+
#[default]
29+
Ordinary,
30+
View,
31+
MaterializedView,
32+
Partitioned,
33+
}
34+
35+
impl From<char> for TableKind {
36+
fn from(s: char) -> Self {
37+
match s {
38+
'r' => Self::Ordinary,
39+
'p' => Self::Partitioned,
40+
'v' => Self::View,
41+
'm' => Self::MaterializedView,
42+
_ => panic!("Invalid table kind"),
43+
}
44+
}
45+
}
46+
47+
impl From<i8> for TableKind {
48+
fn from(s: i8) -> Self {
49+
let c = char::from(u8::try_from(s).unwrap());
50+
c.into()
51+
}
52+
}
53+
2654
#[derive(Debug, Default, PartialEq, Eq)]
2755
pub struct Table {
2856
pub id: i64,
@@ -31,6 +59,7 @@ pub struct Table {
3159
pub rls_enabled: bool,
3260
pub rls_forced: bool,
3361
pub replica_identity: ReplicaIdentity,
62+
pub table_kind: TableKind,
3463
pub bytes: i64,
3564
pub size: String,
3665
pub live_rows_estimate: i64,
@@ -47,3 +76,76 @@ impl SchemaCacheItem for Table {
4776
.await
4877
}
4978
}
79+
80+
#[cfg(test)]
81+
mod tests {
82+
use crate::{SchemaCache, tables::TableKind};
83+
use pgt_test_utils::test_database::get_new_test_db;
84+
use sqlx::Executor;
85+
86+
#[tokio::test]
87+
async fn includes_views_in_query() {
88+
let test_db = get_new_test_db().await;
89+
90+
let setup = r#"
91+
create table public.base_table (
92+
id serial primary key,
93+
value text
94+
);
95+
96+
create view public.my_view as
97+
select * from public.base_table;
98+
"#;
99+
100+
test_db
101+
.execute(setup)
102+
.await
103+
.expect("Failed to setup test database");
104+
105+
let cache = SchemaCache::load(&test_db)
106+
.await
107+
.expect("Failed to load Schema Cache");
108+
109+
let view = cache
110+
.tables
111+
.iter()
112+
.find(|t| t.name == "my_view")
113+
.expect("View not found");
114+
115+
assert_eq!(view.table_kind, TableKind::View);
116+
assert_eq!(view.schema, "public");
117+
}
118+
119+
#[tokio::test]
120+
async fn includes_materialized_views_in_query() {
121+
let test_db = get_new_test_db().await;
122+
123+
let setup = r#"
124+
create table public.base_table (
125+
id serial primary key,
126+
value text
127+
);
128+
129+
create materialized view public.my_mat_view as
130+
select * from public.base_table;
131+
"#;
132+
133+
test_db
134+
.execute(setup)
135+
.await
136+
.expect("Failed to setup test database");
137+
138+
let cache = SchemaCache::load(&test_db)
139+
.await
140+
.expect("Failed to load Schema Cache");
141+
142+
let mat_view = cache
143+
.tables
144+
.iter()
145+
.find(|t| t.name == "my_mat_view")
146+
.expect("Materialized view not found");
147+
148+
assert_eq!(mat_view.table_kind, TableKind::MaterializedView);
149+
assert_eq!(mat_view.schema, "public");
150+
}
151+
}

0 commit comments

Comments
 (0)