Skip to content

Commit e80551e

Browse files
committed
Call parameterized views from sql #3489, parsing
1 parent 8967ea7 commit e80551e

File tree

9 files changed

+141
-25
lines changed

9 files changed

+141
-25
lines changed

crates/expr/src/check.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use spacetimedb_sql_parser::{
1515
};
1616

1717
use super::{
18-
errors::{DuplicateName, TypingError, Unresolved, Unsupported},
18+
errors::{DuplicateName, FunctionCall, TypingError, Unresolved, Unsupported},
1919
expr::RelExpr,
2020
type_expr, type_proj, type_select,
2121
};
@@ -78,12 +78,8 @@ pub trait TypeChecker {
7878
delta: None,
7979
});
8080

81-
for SqlJoin {
82-
var: SqlIdent(name),
83-
alias: SqlIdent(alias),
84-
on,
85-
} in joins
86-
{
81+
for SqlJoin { from, on } in joins {
82+
let (SqlIdent(name), SqlIdent(alias)) = from.into_name_alias();
8783
// Check for duplicate aliases
8884
if vars.contains_key(&alias) {
8985
return Err(DuplicateName(alias.into_string()).into());
@@ -113,6 +109,8 @@ pub trait TypeChecker {
113109

114110
Ok(join)
115111
}
112+
// TODO: support function calls in FROM clause
113+
SqlFrom::FuncCall(_, _) => Err(FunctionCall.into()),
116114
}
117115
}
118116

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: 26 additions & 3 deletions
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 {
@@ -22,11 +23,26 @@ impl SqlFrom {
2223
}
2324
}
2425

26+
/// A source in a FROM clause, restricted to a single relvar or function call
27+
#[derive(Debug)]
28+
pub enum SqlFromSource {
29+
Expr(SqlIdent, SqlIdent),
30+
FuncCall(SqlFuncCall, SqlIdent),
31+
}
32+
33+
impl SqlFromSource {
34+
pub fn into_name_alias(self) -> (SqlIdent, SqlIdent) {
35+
match self {
36+
Self::Expr(name, alias) => (name, alias),
37+
Self::FuncCall(func, alias) => (func.name, alias),
38+
}
39+
}
40+
}
41+
2542
/// An inner join in a FROM clause
2643
#[derive(Debug)]
2744
pub struct SqlJoin {
28-
pub var: SqlIdent,
29-
pub alias: SqlIdent,
45+
pub from: SqlFromSource,
3046
pub on: Option<SqlExpr>,
3147
}
3248

@@ -247,3 +263,10 @@ impl Display for LogOp {
247263
}
248264
}
249265
}
266+
267+
/// A SQL function call
268+
#[derive(Debug)]
269+
pub struct SqlFuncCall {
270+
pub name: SqlIdent,
271+
pub args: Vec<SqlLiteral>,
272+
}

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: 7 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,12 @@ 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 call to table-valued function with empty params. Use `select * from table_function` syntax instead: {0}")]
84+
TableFunctionNoParams(String),
85+
#[error("Unsupported JOIN with table-valued function: {0}")]
86+
JoinTableFunction(String),
8087
}
8188

8289
impl SqlUnsupported {

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

Lines changed: 51 additions & 15 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,15 @@ 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) => Ok(SqlFrom::FuncCall(func_call, alias)),
4046
}
41-
Ok(SqlFrom::Join(name, alias, Self::parse_joins(joins)?))
4247
}
4348

4449
/// Parse a sequence of JOIN clauses
@@ -48,10 +53,11 @@ trait RelParser {
4853

4954
/// Parse a single JOIN clause
5055
fn parse_join(join: Join) -> SqlParseResult<SqlJoin> {
51-
let (var, alias) = Self::parse_relvar(join.relation)?;
56+
let from = Self::parse_relvar(join.relation)?;
57+
5258
match join.join_operator {
53-
JoinOperator::CrossJoin => Ok(SqlJoin { var, alias, on: None }),
54-
JoinOperator::Inner(JoinConstraint::None) => Ok(SqlJoin { var, alias, on: None }),
59+
JoinOperator::CrossJoin => Ok(SqlJoin { from, on: None }),
60+
JoinOperator::Inner(JoinConstraint::None) => Ok(SqlJoin { from, on: None }),
5561
JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp {
5662
left,
5763
op: BinaryOperator::Eq,
@@ -60,8 +66,7 @@ trait RelParser {
6066
&& matches!(*right, Expr::Identifier(..) | Expr::CompoundIdentifier(..)) =>
6167
{
6268
Ok(SqlJoin {
63-
var,
64-
alias,
69+
from,
6570
on: Some(parse_expr(
6671
Expr::BinaryOp {
6772
left,
@@ -76,32 +81,63 @@ trait RelParser {
7681
}
7782
}
7883

84+
/// Parse a function call
85+
fn parse_func_call(name: SqlIdent, args: Vec<FunctionArg>) -> SqlParseResult<SqlFuncCall> {
86+
if args.is_empty() {
87+
return Err(SqlUnsupported::TableFunctionNoParams(name.0.into()).into());
88+
}
89+
let args = args
90+
.into_iter()
91+
.map(|arg| match arg.clone() {
92+
FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => match parse_expr(expr, 0) {
93+
Ok(SqlExpr::Lit(lit)) => Ok(lit),
94+
_ => Err(SqlUnsupported::FuncArg(arg).into()),
95+
},
96+
_ => Err(SqlUnsupported::FuncArg(arg.clone()).into()),
97+
})
98+
.collect::<SqlParseResult<_>>()?;
99+
Ok(SqlFuncCall { name, args })
100+
}
101+
79102
/// Parse a table reference in a FROM clause
80-
fn parse_relvar(expr: TableFactor) -> SqlParseResult<(SqlIdent, SqlIdent)> {
103+
fn parse_relvar(expr: TableFactor) -> SqlParseResult<SqlFromSource> {
81104
match expr {
82105
// Relvar no alias
83106
TableFactor::Table {
84107
name,
85108
alias: None,
86-
args: None,
109+
args,
87110
with_hints,
88111
version: None,
89112
partitions,
90113
} if with_hints.is_empty() && partitions.is_empty() => {
91114
let name = parse_ident(name)?;
92115
let alias = name.clone();
93-
Ok((name, alias))
116+
117+
if let Some(args) = args {
118+
Ok(SqlFromSource::FuncCall(Self::parse_func_call(name, args)?, alias))
119+
} else {
120+
Ok(SqlFromSource::Expr(name, alias))
121+
}
94122
}
95123
// Relvar with alias
96124
TableFactor::Table {
97125
name,
98126
alias: Some(TableAlias { name: alias, columns }),
99-
args: None,
127+
args,
100128
with_hints,
101129
version: None,
102130
partitions,
103131
} if with_hints.is_empty() && partitions.is_empty() && columns.is_empty() => {
104-
Ok((parse_ident(name)?, alias.into()))
132+
let args = args.filter(|v| !v.is_empty());
133+
if let Some(args) = args {
134+
Ok(SqlFromSource::FuncCall(
135+
Self::parse_func_call(parse_ident(name)?, args)?,
136+
alias.into(),
137+
))
138+
} else {
139+
Ok(SqlFromSource::Expr(parse_ident(name)?, alias.into()))
140+
}
105141
}
106142
_ => Err(SqlUnsupported::From(expr).into()),
107143
}

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

Lines changed: 22 additions & 0 deletions
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,11 @@ 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 as s",
455+
"select * from sample(1, 'abc', true, 0xFF, 0.1)",
456+
"select * from sample(1, 'abc', true, 0xFF, 0.1) as s",
457+
"select * from t join sample(1) on t.id = sample.id",
458+
"select * from t join sample(1) as s on t.id = s.id",
445459
"insert into t values (1, 2)",
446460
"delete from t",
447461
"delete from t where a = 1",
@@ -463,6 +477,14 @@ mod tests {
463477
"select a from where b = 1",
464478
// Empty WHERE
465479
"select a from t where",
480+
// Function call params are not literals
481+
"select * from sample(a, b)",
482+
// Function call without params
483+
"select * from sample()",
484+
// Nested function call
485+
"select * from sample(sample(1))",
486+
// Function call in JOIN ON
487+
"select * from t join sample(1) on t.id = sample(1).id",
466488
// Empty GROUP BY
467489
"select a, count(*) from t group by",
468490
// Aggregate without alias

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

Lines changed: 22 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,14 @@ 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 params are not literals
175+
"select * from sample(a, b)",
176+
// Function call without params
177+
"select * from sample()",
178+
// Nested function call
179+
"select * from sample(sample(1))",
180+
// Function call in JOIN ON
181+
"select * from t join sample(1) on t.id = sample(1).id",
165182
] {
166183
assert!(parse_subscription(sql).is_err());
167184
}
@@ -178,6 +195,11 @@ mod tests {
178195
"select t.* from t join s on t.c = s.d",
179196
"select a.* from t as a join s as b on a.c = b.d",
180197
"select * from t where x = :sender",
198+
"select * from sample as s",
199+
"select * from sample(1, 'abc', true, 0xFF, 0.1)",
200+
"select * from sample(1, 'abc', true, 0xFF, 0.1) as s",
201+
"select * from t join sample(1) on t.id = sample.id",
202+
"select * from t join sample(1) as s on t.id = s.id",
181203
] {
182204
assert!(parse_subscription(sql).is_ok());
183205
}

0 commit comments

Comments
 (0)