Skip to content

Commit 8ee1de6

Browse files
Update SQL AST in accordance with the SQL spec (#1623)
Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
1 parent f7ae920 commit 8ee1de6

File tree

11 files changed

+1257
-8
lines changed

11 files changed

+1257
-8
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ members = [
2020
"crates/sdk",
2121
"crates/snapshot",
2222
"crates/sqltest",
23+
"crates/sql-parser",
2324
"crates/standalone",
2425
"crates/table",
2526
"crates/testing",
@@ -100,6 +101,7 @@ spacetimedb-primitives = { path = "crates/primitives", version = "0.12.0" }
100101
spacetimedb-sats = { path = "crates/sats", version = "0.12.0" }
101102
spacetimedb-schema = { path = "crates/schema", version = "0.12.0" }
102103
spacetimedb-standalone = { path = "crates/standalone", version = "0.12.0" }
104+
spacetimedb-sql-parser = { path = "crates/sql-parser", version = "0.12.0" }
103105
spacetimedb-table = { path = "crates/table", version = "0.12.0" }
104106
spacetimedb-vm = { path = "crates/vm", version = "0.12.0" }
105107
spacetimedb-fs-utils = { path = "crates/fs-utils", version = "0.12.0" }
@@ -247,14 +249,14 @@ wasmtime = { version = "15", default-features = false, features = ["cranelift",
247249
# and reconnecting, from the tracy client to the database.
248250
# TODO(George): Need to be able to remove "broadcast" in some build configurations.
249251
tracing-tracy = { version = "0.10.4", features = [
250-
"enable",
251-
"system-tracing",
252-
"context-switch-tracing",
253-
"sampling",
254-
"code-transfer",
255-
"broadcast",
256-
"ondemand",
257-
] }
252+
"enable",
253+
"system-tracing",
254+
"context-switch-tracing",
255+
"sampling",
256+
"code-transfer",
257+
"broadcast",
258+
"ondemand",
259+
] }
258260

259261
# Vendor the openssl we rely on, rather than depend on a
260262
# potentially very old system version.

crates/sql-parser/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "spacetimedb-sql-parser"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
license-file = "LICENSE"
7+
description = "The SpacetimeDB SQL AST and Parser"
8+
9+
[dependencies]
10+
derive_more.workspace = true
11+
sqlparser.workspace = true
12+
thiserror.workspace = true

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

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use sqlparser::ast::Ident;
2+
3+
pub mod sql;
4+
pub mod sub;
5+
6+
/// The FROM clause is either a [RelExpr] or a JOIN
7+
pub enum SqlFrom<Ast> {
8+
Expr(RelExpr<Ast>, Option<SqlIdent>),
9+
Join(RelExpr<Ast>, SqlIdent, Vec<SqlJoin<Ast>>),
10+
}
11+
12+
/// A RelExpr is an expression that produces a relation
13+
pub enum RelExpr<Ast> {
14+
Var(SqlIdent),
15+
Ast(Box<Ast>),
16+
}
17+
18+
/// An inner join in a FROM clause
19+
pub struct SqlJoin<Ast> {
20+
pub expr: RelExpr<Ast>,
21+
pub alias: SqlIdent,
22+
pub on: Option<SqlExpr>,
23+
}
24+
25+
/// A projection expression in a SELECT clause
26+
pub struct ProjectElem(pub SqlExpr, pub Option<SqlIdent>);
27+
28+
/// A SQL SELECT clause
29+
pub enum Project {
30+
/// SELECT *
31+
/// SELECT a.*
32+
Star(Option<SqlIdent>),
33+
/// SELECT a, b
34+
Exprs(Vec<ProjectElem>),
35+
}
36+
37+
/// A scalar SQL expression
38+
pub enum SqlExpr {
39+
/// A constant expression
40+
Lit(SqlLiteral),
41+
/// Unqualified column ref
42+
Var(SqlIdent),
43+
/// Qualified column ref
44+
Field(SqlIdent, SqlIdent),
45+
/// A binary infix expression
46+
Bin(Box<SqlExpr>, Box<SqlExpr>, BinOp),
47+
}
48+
49+
/// A SQL identifier or named reference
50+
#[derive(Clone)]
51+
pub struct SqlIdent {
52+
pub name: String,
53+
pub case_sensitive: bool,
54+
}
55+
56+
impl From<Ident> for SqlIdent {
57+
fn from(value: Ident) -> Self {
58+
match value {
59+
Ident {
60+
value: name,
61+
quote_style: None,
62+
} => SqlIdent {
63+
name,
64+
case_sensitive: false,
65+
},
66+
Ident {
67+
value: name,
68+
quote_style: Some(_),
69+
} => SqlIdent {
70+
name,
71+
case_sensitive: true,
72+
},
73+
}
74+
}
75+
}
76+
77+
/// A SQL constant expression
78+
pub enum SqlLiteral {
79+
/// A boolean constant
80+
Bool(bool),
81+
/// A hex value like 0xFF or x'FF'
82+
Hex(String),
83+
/// An integer or float value
84+
Num(String),
85+
/// A string value
86+
Str(String),
87+
}
88+
89+
/// Binary infix operators
90+
pub enum BinOp {
91+
Eq,
92+
Ne,
93+
Lt,
94+
Gt,
95+
Lte,
96+
Gte,
97+
And,
98+
Or,
99+
}

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use super::{Project, SqlExpr, SqlFrom, SqlIdent, SqlLiteral};
2+
3+
/// The AST for the SQL DML and query language
4+
pub enum SqlAst {
5+
/// SELECT ...
6+
Query(QueryAst),
7+
/// INSERT INTO ...
8+
Insert(SqlInsert),
9+
/// UPDATE ...
10+
Update(SqlUpdate),
11+
/// DELETE FROM ...
12+
Delete(SqlDelete),
13+
/// SET var TO ...
14+
Set(SqlSet),
15+
/// SHOW var
16+
Show(SqlShow),
17+
}
18+
19+
/// The AST for the SQL query language
20+
pub struct QueryAst {
21+
pub query: SqlSetOp,
22+
pub order: Vec<OrderByElem>,
23+
pub limit: Option<SqlLiteral>,
24+
}
25+
26+
/// Set operations in the SQL query language
27+
pub enum SqlSetOp {
28+
/// SELECT
29+
Select(SqlSelect),
30+
/// ORDER/LIMIT
31+
Query(Box<QueryAst>),
32+
/// UNION
33+
Union(Box<SqlSetOp>, Box<SqlSetOp>, bool),
34+
/// EXCEPT
35+
Minus(Box<SqlSetOp>, Box<SqlSetOp>, bool),
36+
}
37+
38+
/// A SELECT statement in the SQL query language
39+
pub struct SqlSelect {
40+
pub project: Project,
41+
pub distinct: bool,
42+
pub from: SqlFrom<QueryAst>,
43+
pub filter: Option<SqlExpr>,
44+
}
45+
46+
/// ORDER BY cols [ ASC | DESC ]
47+
pub struct OrderByElem(pub SqlExpr, pub bool);
48+
49+
/// INSERT INTO table cols VALUES literals
50+
pub struct SqlInsert {
51+
pub table: SqlIdent,
52+
pub fields: Vec<SqlIdent>,
53+
pub values: SqlValues,
54+
}
55+
56+
/// VALUES literals
57+
pub struct SqlValues(pub Vec<Vec<SqlLiteral>>);
58+
59+
/// UPDATE table SET cols [ WHERE predicate ]
60+
pub struct SqlUpdate {
61+
pub table: SqlIdent,
62+
pub assignments: Vec<SqlSet>,
63+
pub filter: Option<SqlExpr>,
64+
}
65+
66+
/// DELETE FROM table [ WHERE predicate ]
67+
pub struct SqlDelete(pub SqlIdent, pub Option<SqlExpr>);
68+
69+
/// SET var '=' literal
70+
pub struct SqlSet(pub SqlIdent, pub SqlLiteral);
71+
72+
/// SHOW var
73+
pub struct SqlShow(pub SqlIdent);

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use super::{Project, SqlExpr, SqlFrom};
2+
3+
/// The AST for the SQL subscription language
4+
pub enum SqlAst {
5+
Select(SqlSelect),
6+
/// UNION ALL
7+
Union(Box<SqlAst>, Box<SqlAst>),
8+
/// EXCEPT ALL
9+
Minus(Box<SqlAst>, Box<SqlAst>),
10+
}
11+
12+
/// A SELECT statement in the SQL subscription language
13+
pub struct SqlSelect {
14+
pub project: Project,
15+
pub from: SqlFrom<SqlAst>,
16+
pub filter: Option<SqlExpr>,
17+
}

crates/sql-parser/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod ast;
2+
pub mod parser;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use std::fmt::Display;
2+
3+
use sqlparser::{
4+
ast::{BinaryOperator, Expr, ObjectName, Query, Select, SelectItem, SetExpr, TableFactor, TableWithJoins, Value},
5+
parser::ParserError,
6+
};
7+
use thiserror::Error;
8+
9+
#[derive(Error, Debug)]
10+
pub enum SubscriptionUnsupported {
11+
#[error("Unsupported SELECT: {0}")]
12+
Select(Select),
13+
#[error("Unsupported: {0}")]
14+
Feature(String),
15+
#[error("Unsupported: Non-SELECT queries")]
16+
Dml,
17+
}
18+
19+
impl SubscriptionUnsupported {
20+
pub(crate) fn feature(expr: impl Display) -> Self {
21+
Self::Feature(format!("{expr}"))
22+
}
23+
}
24+
25+
#[derive(Error, Debug)]
26+
pub enum SqlUnsupported {
27+
#[error("Unsupported literal expression: {0}")]
28+
Literal(Value),
29+
#[error("Unsupported LIMIT expression: {0}")]
30+
Limit(Expr),
31+
#[error("Unsupported expression: {0}")]
32+
Expr(Expr),
33+
#[error("Unsupported binary operator: {0}")]
34+
BinOp(BinaryOperator),
35+
#[error("Unsupported projection: {0}")]
36+
Projection(SelectItem),
37+
#[error("Unsupported FROM expression: {0}")]
38+
From(TableFactor),
39+
#[error("Unsupported set operation: {0}")]
40+
SetOp(SetExpr),
41+
#[error("Unsupported INSERT expression: {0}")]
42+
Insert(Query),
43+
#[error("Unsupported INSERT value: {0}")]
44+
InsertValue(Expr),
45+
#[error("Unsupported table expression in DELETE: {0}")]
46+
DeleteTable(TableWithJoins),
47+
#[error("Unsupported column/variable assignment expression: {0}")]
48+
Assignment(Expr),
49+
#[error("Multi-part names are not supported: {0}")]
50+
MultiPartName(ObjectName),
51+
#[error("Unsupported: {0}")]
52+
Feature(String),
53+
#[error("Non-inner joins are not supported")]
54+
JoinType,
55+
#[error("Implicit joins are not supported")]
56+
ImplicitJoins,
57+
#[error("Mixed wildcard projections are not supported")]
58+
MixedWildcardProject,
59+
#[error("Multiple SQL statements are not supported")]
60+
MultiStatement,
61+
#[error("Multi-table DELETE is not supported")]
62+
MultiTableDelete,
63+
}
64+
65+
impl SqlUnsupported {
66+
pub(crate) fn feature(expr: impl Display) -> Self {
67+
Self::Feature(format!("{expr}"))
68+
}
69+
}
70+
71+
#[derive(Error, Debug)]
72+
pub enum SqlRequired {
73+
#[error("A FROM clause is required")]
74+
From,
75+
#[error("Aliases are required for JOIN")]
76+
JoinAlias,
77+
}
78+
79+
#[derive(Error, Debug)]
80+
pub enum SqlParseError {
81+
#[error(transparent)]
82+
SqlUnsupported(#[from] SqlUnsupported),
83+
#[error(transparent)]
84+
SubscriptionUnsupported(#[from] SubscriptionUnsupported),
85+
#[error(transparent)]
86+
SqlRequired(#[from] SqlRequired),
87+
#[error(transparent)]
88+
ParserError(#[from] ParserError),
89+
}

0 commit comments

Comments
 (0)