Skip to content

Commit ef3ee18

Browse files
committed
store: Enforce existing GraphQL first/skip limits for SQL queries
1 parent 804a208 commit ef3ee18

File tree

3 files changed

+73
-11
lines changed

3 files changed

+73
-11
lines changed

store/postgres/src/deployment_store.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,10 @@ impl DeploymentStore {
292292
conn: &mut PgConnection,
293293
query: &str,
294294
) -> Result<Vec<SqlQueryObject>, QueryExecutionError> {
295-
let query = format!("select to_jsonb(sub.*) as data from ({}) as sub", query);
295+
let query = format!(
296+
"select to_jsonb(sub.*) as data from ({}) as sub limit {}",
297+
query, ENV_VARS.graphql.max_first
298+
);
296299
let query = diesel::sql_query(query);
297300

298301
let results = conn

store/postgres/src/sql/parser.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::{constants::SQL_DIALECT, validation::Validator};
22
use crate::relational::Layout;
33
use anyhow::{anyhow, Ok, Result};
4-
use graph::prelude::BlockNumber;
4+
use graph::{env::ENV_VARS, prelude::BlockNumber};
55
use std::sync::Arc;
66

77
pub struct Parser {
@@ -17,7 +17,10 @@ impl Parser {
1717
pub fn parse_and_validate(&self, sql: &str) -> Result<String> {
1818
let mut statements = sqlparser::parser::Parser::parse_sql(&SQL_DIALECT, sql)?;
1919

20-
let mut validator = Validator::new(&self.layout, self.block);
20+
let max_offset = ENV_VARS.graphql.max_skip;
21+
let max_limit = ENV_VARS.graphql.max_first;
22+
23+
let mut validator = Validator::new(&self.layout, self.block, max_limit, max_offset);
2124
validator.validate_statements(&mut statements)?;
2225

2326
let statement = statements

store/postgres/src/sql/validation.rs

+64-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use graph::prelude::BlockNumber;
22
use sqlparser::ast::{
3-
Expr, Ident, ObjectName, Query, SetExpr, Statement, TableAlias, TableFactor, VisitMut,
4-
VisitorMut,
3+
Expr, Ident, ObjectName, Offset, Query, SetExpr, Statement, TableAlias, TableFactor, Value,
4+
VisitMut, VisitorMut,
55
};
66
use sqlparser::parser::Parser;
77
use std::result::Result;
@@ -22,20 +22,30 @@ pub enum Error {
2222
NotSelectQuery,
2323
#[error("Unknown table {0}")]
2424
UnknownTable(String),
25+
#[error("Only constant numbers are supported for LIMIT and OFFSET.")]
26+
UnsupportedLimitOffset,
27+
#[error("The limit of {0} is greater than the maximum allowed limit of {1}.")]
28+
UnsupportedLimit(u32, u32),
29+
#[error("The offset of {0} is greater than the maximum allowed offset of {1}.")]
30+
UnsupportedOffset(u32, u32),
2531
}
2632

2733
pub struct Validator<'a> {
2834
layout: &'a Layout,
2935
ctes: HashSet<String>,
3036
block: BlockNumber,
37+
max_limit: u32,
38+
max_offset: u32,
3139
}
3240

3341
impl<'a> Validator<'a> {
34-
pub fn new(layout: &'a Layout, block: BlockNumber) -> Self {
42+
pub fn new(layout: &'a Layout, block: BlockNumber, max_limit: u32, max_offset: u32) -> Self {
3543
Self {
3644
layout,
3745
ctes: Default::default(),
3846
block,
47+
max_limit,
48+
max_offset,
3949
}
4050
}
4151

@@ -61,6 +71,45 @@ impl<'a> Validator<'a> {
6171

6272
Ok(())
6373
}
74+
75+
pub fn validate_limit_offset(&mut self, query: &mut Query) -> ControlFlow<Error> {
76+
let Query { limit, offset, .. } = query;
77+
78+
if let Some(limit) = limit {
79+
match limit {
80+
Expr::Value(Value::Number(s, _)) => match s.parse::<u32>() {
81+
Err(_) => return ControlFlow::Break(Error::UnsupportedLimitOffset),
82+
Ok(limit) => {
83+
if limit > self.max_limit {
84+
return ControlFlow::Break(Error::UnsupportedLimit(
85+
limit,
86+
self.max_limit,
87+
));
88+
}
89+
}
90+
},
91+
_ => return ControlFlow::Break(Error::UnsupportedLimitOffset),
92+
}
93+
}
94+
95+
if let Some(Offset { value, .. }) = offset {
96+
match value {
97+
Expr::Value(Value::Number(s, _)) => match s.parse::<u32>() {
98+
Err(_) => return ControlFlow::Break(Error::UnsupportedLimitOffset),
99+
Ok(offset) => {
100+
if offset > self.max_offset {
101+
return ControlFlow::Break(Error::UnsupportedOffset(
102+
offset,
103+
self.max_offset,
104+
));
105+
}
106+
}
107+
},
108+
_ => return ControlFlow::Break(Error::UnsupportedLimitOffset),
109+
}
110+
}
111+
ControlFlow::Continue(())
112+
}
64113
}
65114

66115
impl VisitorMut for Validator<'_> {
@@ -73,20 +122,27 @@ impl VisitorMut for Validator<'_> {
73122
}
74123
}
75124

76-
fn pre_visit_query(&mut self, _query: &mut Query) -> ControlFlow<Self::Break> {
125+
fn pre_visit_query(&mut self, query: &mut Query) -> ControlFlow<Self::Break> {
77126
// Add common table expressions to the set of known tables
78-
if let Some(ref with) = _query.with {
127+
if let Some(ref with) = query.with {
79128
self.ctes.extend(
80129
with.cte_tables
81130
.iter()
82131
.map(|cte| cte.alias.name.value.to_lowercase()),
83132
);
84133
}
85134

86-
match *_query.body {
87-
SetExpr::Update(_) | SetExpr::Insert(_) => ControlFlow::Break(Error::NotSelectQuery),
88-
_ => ControlFlow::Continue(()),
135+
match *query.body {
136+
SetExpr::Select(_) | SetExpr::Query(_) => { /* permitted */ }
137+
SetExpr::SetOperation { .. } => { /* permitted */ }
138+
SetExpr::Table(_) => { /* permitted */ }
139+
SetExpr::Values(_) => { /* permitted */ }
140+
SetExpr::Insert(_) | SetExpr::Update(_) => {
141+
return ControlFlow::Break(Error::NotSelectQuery)
142+
}
89143
}
144+
145+
self.validate_limit_offset(query)
90146
}
91147

92148
/// Invoked for any table function in the AST.

0 commit comments

Comments
 (0)