Skip to content

Commit 1c3da97

Browse files
committed
Call parameterized views from sql #3489
1 parent b5f3ce8 commit 1c3da97

File tree

9 files changed

+129
-19
lines changed

9 files changed

+129
-19
lines changed

crates/expr/src/check.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ use std::collections::HashMap;
22
use std::ops::{Deref, DerefMut};
33
use std::sync::Arc;
44

5+
use super::{
6+
errors::{DuplicateName, TypingError, Unresolved, Unsupported},
7+
expr::RelExpr,
8+
type_expr, type_proj, type_select,
9+
};
10+
use crate::errors::FunctionCall;
511
use crate::expr::LeftDeepJoin;
612
use crate::expr::{Expr, ProjectList, ProjectName, Relvar};
713
use spacetimedb_lib::identity::AuthCtx;
@@ -14,12 +20,6 @@ use spacetimedb_sql_parser::{
1420
parser::sub::parse_subscription,
1521
};
1622

17-
use super::{
18-
errors::{DuplicateName, TypingError, Unresolved, Unsupported},
19-
expr::RelExpr,
20-
type_expr, type_proj, type_select,
21-
};
22-
2323
/// The result of type checking and name resolution
2424
pub type TypingResult<T> = core::result::Result<T, TypingError>;
2525

@@ -113,6 +113,8 @@ pub trait TypeChecker {
113113

114114
Ok(join)
115115
}
116+
// TODO: support function calls in FROM clause
117+
SqlFrom::FuncCall(_, _) => Err(FunctionCall.into()),
116118
}
117119
}
118120

crates/expr/src/errors.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ pub struct DmlOnView {
128128
pub view_name: Box<str>,
129129
}
130130

131+
#[derive(Debug, Error)]
132+
#[error("Function calls are not supported")]
133+
pub struct FunctionCall;
134+
131135
#[derive(Error, Debug)]
132136
pub enum TypingError {
133137
#[error(transparent)]
@@ -157,4 +161,6 @@ pub enum TypingError {
157161
DuplicateName(#[from] DuplicateName),
158162
#[error(transparent)]
159163
FilterReturnType(#[from] FilterReturnType),
164+
#[error(transparent)]
165+
FunctionCall(#[from] FunctionCall),
160166
}

crates/sql-parser/src/ast/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ use sqlparser::ast::Ident;
66
pub mod sql;
77
pub mod sub;
88

9-
/// The FROM clause is either a relvar or a JOIN
9+
/// The FROM clause is either a relvar, a JOIN, or a function call
1010
#[derive(Debug)]
1111
pub enum SqlFrom {
1212
Expr(SqlIdent, SqlIdent),
1313
Join(SqlIdent, SqlIdent, Vec<SqlJoin>),
14+
FuncCall(SqlFuncCall, SqlIdent),
1415
}
1516

1617
impl SqlFrom {
@@ -247,3 +248,15 @@ impl Display for LogOp {
247248
}
248249
}
249250
}
251+
252+
/// A SQL function call
253+
#[derive(Debug)]
254+
pub struct SqlFuncCall {
255+
pub name: SqlIdent,
256+
pub args: Vec<SqlLiteral>,
257+
}
258+
259+
pub enum SqlFromSource {
260+
Expr(SqlIdent, SqlIdent),
261+
FuncCall(SqlFuncCall, SqlIdent),
262+
}

crates/sql-parser/src/ast/sql.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ impl SqlSelect {
7878
..self
7979
},
8080
SqlFrom::Join(..) => self,
81+
SqlFrom::FuncCall(..) => self,
8182
}
8283
}
8384

crates/sql-parser/src/ast/sub.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ impl SqlSelect {
2121
from: self.from,
2222
},
2323
SqlFrom::Join(..) => self,
24+
SqlFrom::FuncCall(..) => self,
2425
}
2526
}
2627

crates/sql-parser/src/parser/errors.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::fmt::Display;
22

3+
use sqlparser::ast::FunctionArg;
34
use sqlparser::{
45
ast::{
56
BinaryOperator, Expr, Function, ObjectName, Query, Select, SelectItem, SetExpr, TableFactor, TableWithJoins,
@@ -77,6 +78,10 @@ pub enum SqlUnsupported {
7778
Empty,
7879
#[error("Names must be qualified when using joins")]
7980
UnqualifiedNames,
81+
#[error("Unsupported function argument: {0}")]
82+
FuncArg(FunctionArg),
83+
#[error("Unsupported join on function call")]
84+
FunctionJoin,
8085
}
8186

8287
impl SqlUnsupported {

crates/sql-parser/src/parser/mod.rs

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use sqlparser::ast::{
66
};
77

88
use crate::ast::{
9-
BinOp, LogOp, Parameter, Project, ProjectElem, ProjectExpr, SqlExpr, SqlFrom, SqlIdent, SqlJoin, SqlLiteral,
9+
BinOp, LogOp, Parameter, Project, ProjectElem, ProjectExpr, SqlExpr, SqlFrom, SqlFromSource, SqlFuncCall, SqlIdent,
10+
SqlJoin, SqlLiteral,
1011
};
1112

1213
pub mod errors;
@@ -34,11 +35,20 @@ trait RelParser {
3435
return Err(SqlUnsupported::ImplicitJoins.into());
3536
}
3637
let TableWithJoins { relation, joins } = tables.swap_remove(0);
37-
let (name, alias) = Self::parse_relvar(relation)?;
38-
if joins.is_empty() {
39-
return Ok(SqlFrom::Expr(name, alias));
38+
match Self::parse_relvar(relation)? {
39+
SqlFromSource::Expr(name, alias) => {
40+
if joins.is_empty() {
41+
return Ok(SqlFrom::Expr(name, alias));
42+
}
43+
Ok(SqlFrom::Join(name, alias, Self::parse_joins(joins)?))
44+
}
45+
SqlFromSource::FuncCall(func_call, alias) => {
46+
if !joins.is_empty() {
47+
return Err(SqlUnsupported::FunctionJoin.into());
48+
}
49+
Ok(SqlFrom::FuncCall(func_call, alias))
50+
}
4051
}
41-
Ok(SqlFrom::Join(name, alias, Self::parse_joins(joins)?))
4252
}
4353

4454
/// Parse a sequence of JOIN clauses
@@ -48,7 +58,12 @@ trait RelParser {
4858

4959
/// Parse a single JOIN clause
5060
fn parse_join(join: Join) -> SqlParseResult<SqlJoin> {
51-
let (var, alias) = Self::parse_relvar(join.relation)?;
61+
let (var, alias) = if let SqlFromSource::Expr(var, alias) = Self::parse_relvar(join.relation)? {
62+
(var, alias)
63+
} else {
64+
return Err(SqlUnsupported::FunctionJoin.into());
65+
};
66+
5267
match join.join_operator {
5368
JoinOperator::CrossJoin => Ok(SqlJoin { var, alias, on: None }),
5469
JoinOperator::Inner(JoinConstraint::None) => Ok(SqlJoin { var, alias, on: None }),
@@ -76,32 +91,63 @@ trait RelParser {
7691
}
7792
}
7893

94+
fn parse_func_call(func_name: SqlIdent, args: Vec<FunctionArg>) -> SqlParseResult<SqlFuncCall> {
95+
let func_args = args
96+
.into_iter()
97+
.map(|arg| match arg.clone() {
98+
FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => match parse_expr(expr, 0) {
99+
Ok(SqlExpr::Lit(lit)) => Ok(lit),
100+
_ => Err(SqlUnsupported::FuncArg(arg).into()),
101+
},
102+
_ => Err(SqlUnsupported::FuncArg(arg.clone()).into()),
103+
})
104+
.collect::<SqlParseResult<_>>()?;
105+
Ok(SqlFuncCall {
106+
name: func_name,
107+
args: func_args,
108+
})
109+
}
110+
79111
/// Parse a table reference in a FROM clause
80-
fn parse_relvar(expr: TableFactor) -> SqlParseResult<(SqlIdent, SqlIdent)> {
112+
fn parse_relvar(expr: TableFactor) -> SqlParseResult<SqlFromSource> {
81113
match expr {
82114
// Relvar no alias
83115
TableFactor::Table {
84116
name,
85117
alias: None,
86-
args: None,
118+
args,
87119
with_hints,
88120
version: None,
89121
partitions,
90122
} if with_hints.is_empty() && partitions.is_empty() => {
91123
let name = parse_ident(name)?;
92124
let alias = name.clone();
93-
Ok((name, alias))
125+
if let Some(args) = args {
126+
Ok(SqlFromSource::FuncCall(
127+
Self::parse_func_call(name, args)?,
128+
alias.into(),
129+
))
130+
} else {
131+
Ok(SqlFromSource::Expr(name, alias))
132+
}
94133
}
95134
// Relvar with alias
96135
TableFactor::Table {
97136
name,
98137
alias: Some(TableAlias { name: alias, columns }),
99-
args: None,
138+
args,
100139
with_hints,
101140
version: None,
102141
partitions,
103142
} if with_hints.is_empty() && partitions.is_empty() && columns.is_empty() => {
104-
Ok((parse_ident(name)?, alias.into()))
143+
if let Some(args) = args {
144+
Ok(SqlFromSource::FuncCall(
145+
Self::parse_func_call(parse_ident(name)?, args)?,
146+
alias.into(),
147+
))
148+
} else {
149+
Ok(SqlFromSource::Expr(parse_ident(name)?, alias.into()))
150+
}
105151
}
106152
_ => Err(SqlUnsupported::From(expr).into()),
107153
}

crates/sql-parser/src/parser/sql.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,18 @@
6868
//! | SUM '(' columnExpr ')' AS ident
6969
//! ;
7070
//!
71+
//! paramExpr
72+
//! = literal
73+
//! ;
74+
//!
75+
//! functionCall
76+
//! = ident '(' [ paramExpr { ',' paramExpr } ] ')'
77+
//! ;
78+
//!
7179
//! relation
7280
//! = table
7381
//! | '(' query ')'
82+
//! | functionCall
7483
//! | relation [ [AS] ident ] { [INNER] JOIN relation [ [AS] ident ] ON predicate }
7584
//! ;
7685
//!
@@ -442,6 +451,9 @@ mod tests {
442451
"select a from t where x = :sender",
443452
"select count(*) as n from t",
444453
"select count(*) as n from t join s on t.id = s.id where s.x = 1",
454+
"select * from sample()",
455+
"select * from sample() as s",
456+
"select * from sample(1, 'abc', true, 0xFF, 0.1)",
445457
"insert into t values (1, 2)",
446458
"delete from t",
447459
"delete from t where a = 1",
@@ -450,7 +462,7 @@ mod tests {
450462
"update t set a = 1, b = 2 where c = 3",
451463
"update t set a = 1, b = 2 where x = :sender",
452464
] {
453-
assert!(parse_sql(sql).is_ok());
465+
assert!(parse_sql(sql).is_ok(), "{}", sql);
454466
}
455467
}
456468

@@ -465,6 +477,12 @@ mod tests {
465477
"select a from t where",
466478
// Empty GROUP BY
467479
"select a, count(*) from t group by",
480+
// Function call in JOIN
481+
"select * from t join sample() on t.id = sample().id",
482+
// Function call params are not literals
483+
"select * from sample(a, b)",
484+
// Nested function call
485+
"select * from sample(sample(1))",
468486
// Aggregate without alias
469487
"select count(*) from t",
470488
// Empty statement

crates/sql-parser/src/parser/sub.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,18 @@
1010
//! | ident '.' STAR
1111
//! ;
1212
//!
13+
//! paramExpr
14+
//! = literal
15+
//! ;
16+
//!
17+
//! functionCall
18+
//! = ident '(' [ paramExpr { ',' paramExpr } ] ')'
19+
//! ;
20+
//!
1321
//! relation
1422
//! = table
1523
//! | '(' query ')'
24+
//! | functionCall
1625
//! | relation [ [AS] ident ] { [INNER] JOIN relation [ [AS] ident ] ON predicate }
1726
//! ;
1827
//!
@@ -162,6 +171,12 @@ mod tests {
162171
"",
163172
"select distinct a from t",
164173
"select * from (select * from t) join (select * from s) on a = b",
174+
// Function call in JOIN
175+
"select * from t join sample() on t.id = sample().id",
176+
// Function call params are not literals
177+
"select * from sample(a, b)",
178+
// Nested function call
179+
"select * from sample(sample(1))",
165180
] {
166181
assert!(parse_subscription(sql).is_err());
167182
}
@@ -178,6 +193,9 @@ mod tests {
178193
"select t.* from t join s on t.c = s.d",
179194
"select a.* from t as a join s as b on a.c = b.d",
180195
"select * from t where x = :sender",
196+
"select * from sample()",
197+
"select * from sample() as s",
198+
"select * from sample(1, 'abc', true, 0xFF, 0.1)",
181199
] {
182200
assert!(parse_subscription(sql).is_ok());
183201
}

0 commit comments

Comments
 (0)