Skip to content

Commit 92cef1a

Browse files
View resolution in sql (#3570)
# Description of Changes Not many changes were required for the query compiler to be able to resolve views. This is because the query engine can always assume a view is materialized and therefore has a backing table. So from the perspective of the query engine, a view is just another table with one small caveat: The physical table in the datastore has two internal metadata columns - `sender` and `arg_id`. These columns are not user facing and so should be hidden from name resolution/type checking. # API and ABI breaking changes None # Expected complexity level and risk 1.5 # Testing <!-- Describe any testing you've done, and any testing you'd like your reviewers to do, so that you're confident that all the changes work as expected! --> - [x] SQL type checking tests
1 parent ee19a56 commit 92cef1a

File tree

15 files changed

+387
-70
lines changed

15 files changed

+387
-70
lines changed

crates/core/src/sql/ast.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use spacetimedb_primitives::{ColId, TableId};
1212
use spacetimedb_sats::{AlgebraicType, AlgebraicValue};
1313
use spacetimedb_schema::def::error::RelationError;
1414
use spacetimedb_schema::relation::{ColExpr, FieldName};
15-
use spacetimedb_schema::schema::{ColumnSchema, TableSchema};
15+
use spacetimedb_schema::schema::{ColumnSchema, TableOrViewSchema, TableSchema};
1616
use spacetimedb_vm::errors::ErrorVm;
1717
use spacetimedb_vm::expr::{Expr, FieldExpr, FieldOp};
1818
use spacetimedb_vm::operator::{OpCmp, OpLogic, OpQuery};
@@ -503,12 +503,14 @@ impl<T: StateView> SchemaView for SchemaViewer<'_, T> {
503503
.map(|schema| schema.table_id)
504504
}
505505

506-
fn schema_for_table(&self, table_id: TableId) -> Option<Arc<TableSchema>> {
506+
fn schema_for_table(&self, table_id: TableId) -> Option<Arc<TableOrViewSchema>> {
507507
let AuthCtx { owner, caller } = self.auth;
508508
self.tx
509509
.get_schema(table_id)
510510
.filter(|schema| schema.table_access == StAccess::Public || caller == owner)
511-
.cloned()
511+
.map(Arc::clone)
512+
.map(TableOrViewSchema::from)
513+
.map(Arc::new)
512514
}
513515

514516
fn rls_rules_for_table(&self, table_id: TableId) -> anyhow::Result<Vec<Box<str>>> {

crates/core/src/vm.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,7 @@ pub(crate) mod tests {
686686
TableSchema::new(
687687
TableId::SENTINEL,
688688
table_name.into(),
689+
None,
689690
columns,
690691
vec![],
691692
vec![],

crates/datastore/src/locking_tx_datastore/datastore.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,7 @@ mod tests {
15761576
TableSchema::new(
15771577
TableId::SENTINEL,
15781578
"Foo".into(),
1579+
None,
15791580
cols.into(),
15801581
indices.into(),
15811582
constraints.into(),

crates/datastore/src/locking_tx_datastore/state_view.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ pub trait StateView {
123123
Ok(TableSchema::new(
124124
table_id,
125125
table_name,
126+
None,
126127
columns,
127128
indexes,
128129
constraints,

crates/expr/src/check.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::expr::{Expr, ProjectList, ProjectName, Relvar};
77
use spacetimedb_lib::identity::AuthCtx;
88
use spacetimedb_lib::AlgebraicType;
99
use spacetimedb_primitives::TableId;
10-
use spacetimedb_schema::schema::TableSchema;
10+
use spacetimedb_schema::schema::TableOrViewSchema;
1111
use spacetimedb_sql_parser::ast::BinOp;
1212
use spacetimedb_sql_parser::{
1313
ast::{sub::SqlSelect, SqlFrom, SqlIdent, SqlJoin},
@@ -26,19 +26,19 @@ pub type TypingResult<T> = core::result::Result<T, TypingError>;
2626
/// A view of the database schema
2727
pub trait SchemaView {
2828
fn table_id(&self, name: &str) -> Option<TableId>;
29-
fn schema_for_table(&self, table_id: TableId) -> Option<Arc<TableSchema>>;
29+
fn schema_for_table(&self, table_id: TableId) -> Option<Arc<TableOrViewSchema>>;
3030
fn rls_rules_for_table(&self, table_id: TableId) -> anyhow::Result<Vec<Box<str>>>;
3131

32-
fn schema(&self, name: &str) -> Option<Arc<TableSchema>> {
32+
fn schema(&self, name: &str) -> Option<Arc<TableOrViewSchema>> {
3333
self.table_id(name).and_then(|table_id| self.schema_for_table(table_id))
3434
}
3535
}
3636

3737
#[derive(Default)]
38-
pub struct Relvars(HashMap<Box<str>, Arc<TableSchema>>);
38+
pub struct Relvars(HashMap<Box<str>, Arc<TableOrViewSchema>>);
3939

4040
impl Deref for Relvars {
41-
type Target = HashMap<Box<str>, Arc<TableSchema>>;
41+
type Target = HashMap<Box<str>, Arc<TableOrViewSchema>>;
4242
fn deref(&self) -> &Self::Target {
4343
&self.0
4444
}
@@ -116,7 +116,7 @@ pub trait TypeChecker {
116116
}
117117
}
118118

119-
fn type_relvar(tx: &impl SchemaView, name: &str) -> TypingResult<Arc<TableSchema>> {
119+
fn type_relvar(tx: &impl SchemaView, name: &str) -> TypingResult<Arc<TableOrViewSchema>> {
120120
tx.schema(name)
121121
.ok_or_else(|| Unresolved::table(name))
122122
.map_err(TypingError::from)
@@ -180,7 +180,7 @@ pub mod test_utils {
180180
use spacetimedb_primitives::TableId;
181181
use spacetimedb_schema::{
182182
def::ModuleDef,
183-
schema::{Schema, TableSchema},
183+
schema::{Schema, TableOrViewSchema, TableSchema},
184184
};
185185
use std::sync::Arc;
186186

@@ -205,7 +205,7 @@ pub mod test_utils {
205205
}
206206
}
207207

208-
fn schema_for_table(&self, table_id: TableId) -> Option<Arc<TableSchema>> {
208+
fn schema_for_table(&self, table_id: TableId) -> Option<Arc<TableOrViewSchema>> {
209209
match table_id.idx() {
210210
0 => Some((TableId(0), "t")),
211211
1 => Some((TableId(1), "s")),
@@ -215,6 +215,8 @@ pub mod test_utils {
215215
self.0
216216
.table(name)
217217
.map(|def| Arc::new(TableSchema::from_module_def(&self.0, def, (), table_id)))
218+
.map(TableOrViewSchema::from)
219+
.map(Arc::new)
218220
})
219221
}
220222

crates/expr/src/errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ pub struct DuplicateName(pub String);
122122
#[error("`filter!` does not support column projections; Must return table rows")]
123123
pub struct FilterReturnType;
124124

125+
#[derive(Debug, Error)]
126+
#[error("`{view_name}` is a view; DML on views is not supported")]
127+
pub struct DmlOnView {
128+
pub view_name: Box<str>,
129+
}
130+
125131
#[derive(Error, Debug)]
126132
pub enum TypingError {
127133
#[error(transparent)]
@@ -137,6 +143,8 @@ pub enum TypingError {
137143
#[error(transparent)]
138144
ParseError(#[from] SqlParseError),
139145

146+
#[error(transparent)]
147+
DmlOnView(#[from] DmlOnView),
140148
#[error(transparent)]
141149
InvalidOp(#[from] InvalidOp),
142150
#[error(transparent)]

crates/expr/src/expr.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::sync::Arc;
22

33
use spacetimedb_lib::{query::Delta, AlgebraicType, AlgebraicValue};
44
use spacetimedb_primitives::TableId;
5-
use spacetimedb_schema::schema::TableSchema;
5+
use spacetimedb_schema::schema::TableOrViewSchema;
66
use spacetimedb_sql_parser::ast::{BinOp, LogOp};
77

88
/// A projection is the root of any relational expression.
@@ -42,10 +42,10 @@ impl ProjectName {
4242
}
4343
}
4444

45-
/// The [TableSchema] of the returned rows.
45+
/// The [`TableOrViewSchema`] of the returned rows.
4646
/// Note this expression returns rows from a relvar.
4747
/// Hence it this method should never return [None].
48-
pub fn return_table(&self) -> Option<&TableSchema> {
48+
pub fn return_table(&self) -> Option<&TableOrViewSchema> {
4949
match self {
5050
Self::None(input) => input.return_table(),
5151
Self::Some(input, alias) => input.find_table_schema(alias),
@@ -65,7 +65,7 @@ impl ProjectName {
6565
/// Iterate over the returned column names and types
6666
pub fn for_each_return_field(&self, mut f: impl FnMut(&str, &AlgebraicType)) {
6767
if let Some(schema) = self.return_table() {
68-
for schema in schema.columns() {
68+
for schema in schema.public_columns() {
6969
f(&schema.col_name, &schema.col_type);
7070
}
7171
}
@@ -148,9 +148,9 @@ pub enum AggType {
148148

149149
impl ProjectList {
150150
/// Does this expression project a single relvar?
151-
/// If so, we return it's [TableSchema].
151+
/// If so, we return it's [`TableOrViewSchema`].
152152
/// If not, it projects a list of columns, so we return [None].
153-
pub fn return_table(&self) -> Option<&TableSchema> {
153+
pub fn return_table(&self) -> Option<&TableOrViewSchema> {
154154
match self {
155155
Self::Name(project) => project.first().and_then(|expr| expr.return_table()),
156156
Self::Limit(input, _) => input.return_table(),
@@ -205,7 +205,7 @@ pub enum RelExpr {
205205
#[derive(Debug, Clone, PartialEq, Eq)]
206206
pub struct Relvar {
207207
/// The table schema of this relvar
208-
pub schema: Arc<TableSchema>,
208+
pub schema: Arc<TableOrViewSchema>,
209209
/// The name of this relvar
210210
pub alias: Box<str>,
211211
/// Does this relvar represent a delta table?
@@ -259,8 +259,8 @@ impl RelExpr {
259259
}
260260
}
261261

262-
/// Return the [TableSchema] for a relvar in the expression
263-
pub fn find_table_schema(&self, alias: &str) -> Option<&TableSchema> {
262+
/// Return the [`TableOrViewSchema`] for a relvar in the expression
263+
pub fn find_table_schema(&self, alias: &str) -> Option<&TableOrViewSchema> {
264264
match self {
265265
Self::RelVar(relvar) if relvar.alias.as_ref() == alias => Some(&relvar.schema),
266266
Self::Select(input, _) => input.find_table_schema(alias),
@@ -278,8 +278,8 @@ impl RelExpr {
278278
}
279279

280280
/// Does this expression return a single relvar?
281-
/// If so, return it's [TableSchema], otherwise return [None].
282-
pub fn return_table(&self) -> Option<&TableSchema> {
281+
/// If so, return it's [`TableOrViewSchema`], otherwise return [None].
282+
pub fn return_table(&self) -> Option<&TableOrViewSchema> {
283283
match self {
284284
Self::RelVar(Relvar { schema, .. }) => Some(schema),
285285
Self::Select(input, _) => input.return_table(),

crates/expr/src/rls.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ mod tests {
476476
use spacetimedb_primitives::TableId;
477477
use spacetimedb_schema::{
478478
def::ModuleDef,
479-
schema::{Schema, TableSchema},
479+
schema::{Schema, TableOrViewSchema, TableSchema},
480480
};
481481
use spacetimedb_sql_parser::ast::BinOp;
482482

@@ -499,7 +499,7 @@ mod tests {
499499
}
500500
}
501501

502-
fn schema_for_table(&self, table_id: TableId) -> Option<Arc<TableSchema>> {
502+
fn schema_for_table(&self, table_id: TableId) -> Option<Arc<TableOrViewSchema>> {
503503
match table_id.idx() {
504504
0 => Some((TableId(0), "users")),
505505
1 => Some((TableId(1), "admins")),
@@ -510,6 +510,8 @@ mod tests {
510510
self.0
511511
.table(name)
512512
.map(|def| Arc::new(TableSchema::from_module_def(&self.0, def, (), table_id)))
513+
.map(TableOrViewSchema::from)
514+
.map(Arc::new)
513515
})
514516
}
515517

0 commit comments

Comments
 (0)