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

Appearance settings

Commit 405370c

Browse filesBrowse files
committed
store: Handle CTE's in nested queries correctly
1 parent 71d502b commit 405370c
Copy full SHA for 405370c

File tree

Expand file treeCollapse file tree

2 files changed

+59
-9
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+59
-9
lines changed
Open diff view settings
Collapse file

‎store/postgres/src/sql/parser_tests.yaml‎

Copy file name to clipboardExpand all lines: store/postgres/src/sql/parser_tests.yaml
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,6 @@
125125
- name: aggregation tables with alias
126126
sql: select * from stats('hour') as sh
127127
ok: SELECT * FROM (SELECT "id", "timestamp", "sum" FROM "sgd0815"."stats_hour" WHERE block$ <= 2147483647) AS sh
128+
- name: nested query with CTE
129+
sql: select *, (with pg_user as (select 1) select 1) as one from pg_user
130+
err: Unknown table pg_user
Collapse file

‎store/postgres/src/sql/validation.rs‎

Copy file name to clipboardExpand all lines: store/postgres/src/sql/validation.rs
+56-9Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use graph::prelude::BlockNumber;
22
use graph::schema::AggregationInterval;
33
use sqlparser::ast::{
4-
Expr, FunctionArg, FunctionArgExpr, Ident, LimitClause, ObjectName, ObjectNamePart, Offset,
5-
Query, SetExpr, Statement, TableAlias, TableFactor, TableFunctionArgs, Value, ValueWithSpan,
6-
VisitMut, VisitorMut,
4+
Cte, Expr, FunctionArg, FunctionArgExpr, Ident, LimitClause, ObjectName, ObjectNamePart,
5+
Offset, Query, SetExpr, Statement, TableAlias, TableFactor, TableFunctionArgs, Value,
6+
ValueWithSpan, VisitMut, VisitorMut,
77
};
88
use sqlparser::parser::Parser;
99
use std::result::Result;
@@ -36,11 +36,56 @@ pub enum Error {
3636
UnsupportedOffset(u32, u32),
3737
#[error("Qualified table names are not supported: {0}")]
3838
NoQualifiedTables(String),
39+
#[error("Internal error: {0}")]
40+
InternalError(String),
41+
}
42+
43+
/// Helper to track CTEs introduced by the main query or subqueries. Every
44+
/// time we enter a query, we need to track a new set of CTEs which must be
45+
/// discarded once we are done with that query. Otherwise, we might allow
46+
/// access to forbidden tables with a query like `select *, (with pg_user as
47+
/// (select 1) select 1) as one from pg_user`
48+
#[derive(Default)]
49+
struct CteStack {
50+
stack: Vec<HashSet<String>>,
51+
}
52+
53+
impl CteStack {
54+
fn enter_query(&mut self) {
55+
self.stack.push(HashSet::new());
56+
}
57+
58+
fn exit_query(&mut self) {
59+
self.stack.pop();
60+
}
61+
62+
fn contains(&self, name: &str) -> bool {
63+
for entry in self.stack.iter().rev() {
64+
if entry.contains(&name.to_lowercase()) {
65+
return true;
66+
}
67+
}
68+
false
69+
}
70+
71+
fn clear(&mut self) {
72+
self.stack.clear();
73+
}
74+
75+
fn add_ctes(&mut self, ctes: &[Cte]) -> ControlFlow<Error> {
76+
let Some(entry) = self.stack.last_mut() else {
77+
return ControlFlow::Break(Error::InternalError("CTE stack is empty".into()));
78+
};
79+
for cte in ctes {
80+
entry.insert(cte.alias.name.value.to_lowercase());
81+
}
82+
ControlFlow::Continue(())
83+
}
3984
}
4085

4186
pub struct Validator<'a> {
4287
layout: &'a Layout,
43-
ctes: HashSet<String>,
88+
ctes: CteStack,
4489
block: BlockNumber,
4590
max_limit: u32,
4691
max_offset: u32,
@@ -156,12 +201,9 @@ impl VisitorMut for Validator<'_> {
156201

157202
fn pre_visit_query(&mut self, query: &mut Query) -> ControlFlow<Self::Break> {
158203
// Add common table expressions to the set of known tables
204+
self.ctes.enter_query();
159205
if let Some(ref with) = query.with {
160-
self.ctes.extend(
161-
with.cte_tables
162-
.iter()
163-
.map(|cte| cte.alias.name.value.to_lowercase()),
164-
);
206+
self.ctes.add_ctes(&with.cte_tables)?;
165207
}
166208

167209
match *query.body {
@@ -177,6 +219,11 @@ impl VisitorMut for Validator<'_> {
177219
self.validate_limit_offset(query)
178220
}
179221

222+
fn post_visit_query(&mut self, _query: &mut Query) -> ControlFlow<Self::Break> {
223+
self.ctes.exit_query();
224+
ControlFlow::Continue(())
225+
}
226+
180227
/// Invoked for any table function in the AST.
181228
/// See [TableFactor::Table.args](sqlparser::ast::TableFactor::Table::args) for more details identifying a table function
182229
fn post_visit_table_factor(

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.