Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ members = [
"tools/xtask-llm-benchmark",
"crates/bindings-typescript/test-app/server",
"crates/bindings-typescript/test-react-router-app/server",
"crates/query-builder",
]
default-members = ["crates/cli", "crates/standalone", "crates/update"]
# cargo feature graph resolver. v3 is default in edition2024 but workspace
Expand Down Expand Up @@ -138,6 +139,7 @@ spacetimedb-vm = { path = "crates/vm", version = "=1.11.2" }
spacetimedb-fs-utils = { path = "crates/fs-utils", version = "=1.11.2" }
spacetimedb-snapshot = { path = "crates/snapshot", version = "=1.11.2" }
spacetimedb-subscription = { path = "crates/subscription", version = "=1.11.2" }
spacetimedb-query-builder = { path = "crates/query-builder", version = "=1.11.2" }

# Prevent `ahash` from pulling in `getrandom` by disabling default features.
# Modules use `getrandom02` and we need to prevent an incompatible version
Expand Down
1 change: 1 addition & 0 deletions crates/bindings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ spacetimedb-bindings-sys.workspace = true
spacetimedb-lib.workspace = true
spacetimedb-bindings-macro.workspace = true
spacetimedb-primitives.workspace = true
spacetimedb-query-builder.workspace = true

anyhow.workspace = true
bytemuck.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub mod rt;
pub mod table;

#[doc(hidden)]
pub mod query_builder;
pub use spacetimedb_query_builder as query_builder;

#[cfg(feature = "unstable")]
pub use client_visibility_filter::Filter;
Expand Down
2 changes: 1 addition & 1 deletion crates/bindings/src/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ impl<T: SpacetimeType + Serialize> ViewReturn for Option<T> {

impl<T: SpacetimeType + Serialize> ViewReturn for Query<T> {
fn to_writer(self, buf: &mut Vec<u8>) -> Result<(), EncodeError> {
bsatn::to_writer(buf, &ViewResultHeader::RawSql(self.sql))
bsatn::to_writer(buf, &ViewResultHeader::RawSql(self.sql().to_string()))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
source: crates/bindings/tests/deps.rs
expression: "cargo tree -p spacetimedb -e no-dev --color never --target wasm32-unknown-unknown -f {lib}"
---
total crates: 69
total crates: 70
spacetimedb
├── anyhow
├── bytemuck
Expand Down Expand Up @@ -128,4 +128,6 @@ spacetimedb
│ │ │ └── syn (*)
│ │ └── uuid
│ └── thiserror (*)
└── spacetimedb_primitives (*)
├── spacetimedb_primitives (*)
└── spacetimedb_query_builder
└── spacetimedb_lib (*)
6 changes: 3 additions & 3 deletions crates/bindings/tests/ui/views.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ error[E0277]: the trait bound `{integer}: RHS<Player, spacetimedb::Identity>` is
`u128` implements `RHS<T, u128>`
and $N others
note: required by a bound in `Col::<T, V>::eq`
--> src/query_builder/table.rs
--> $WORKSPACE/crates/query-builder/src/table.rs
|
| pub fn eq<R: RHS<T, V>>(self, rhs: R) -> BoolExpr<T> {
| ^^^^^^^^^ required by this bound in `Col::<T, V>::eq`
Expand All @@ -413,7 +413,7 @@ error[E0277]: the trait bound `u32: RHS<PlayerInfo, u8>` is not satisfied
but trait `RHS<PlayerInfo, u32>` is implemented for it
= help: for that trait implementation, expected `u32`, found `u8`
note: required by a bound in `Col::<T, V>::eq`
--> src/query_builder/table.rs
--> $WORKSPACE/crates/query-builder/src/table.rs
|
| pub fn eq<R: RHS<T, V>>(self, rhs: R) -> BoolExpr<T> {
| ^^^^^^^^^ required by this bound in `Col::<T, V>::eq`
Expand All @@ -429,7 +429,7 @@ error[E0308]: mismatched types
= note: expected struct `IxCol<Player, u32>`
found struct `IxCol<Player, spacetimedb::Identity>`
note: method defined here
--> src/query_builder/join.rs
--> $WORKSPACE/crates/query-builder/src/join.rs
|
| pub fn eq<R: HasIxCols>(self, rhs: IxCol<R, V>) -> IxJoinEq<T, R, V> {
| ^^
Expand Down
189 changes: 188 additions & 1 deletion crates/codegen/src/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::code_indenter::{CodeIndenter, Indenter};
use super::util::{collect_case, iter_reducers, print_lines, type_ref_name};
use super::Lang;
use crate::util::{
iter_procedures, iter_table_names_and_types, iter_tables, iter_types, iter_unique_cols, iter_views,
iter_indexes, iter_procedures, iter_table_names_and_types, iter_tables, iter_types, iter_unique_cols, iter_views,
print_auto_generated_file_comment, print_auto_generated_version_comment,
};
use crate::OutputFile;
Expand Down Expand Up @@ -66,6 +66,33 @@ impl __sdk::InModule for {type_name} {{
",
);

// Do not implement query col types for nested types.
// as querying is only supported on top-level table row types.
let name = type_ref_name(module, typ.ty);
let implemented = if let Some(table) = module
.tables()
.find(|t| type_ref_name(module, t.product_type_ref) == name)
{
implement_query_col_types_for_table_struct(module, out, table)
.expect("failed to implement query col types");
out.newline();
true
} else {
false
};

if !implemented {
if let Some(type_ref) = module
.views()
.map(|v| v.product_type_ref)
.find(|type_ref| type_ref_name(module, *type_ref) == name)
{
implement_query_col_types_for_struct(module, out, type_ref)
.expect("failed to implement query col types");
out.newline();
}
}

vec![OutputFile {
filename: type_module_name(&typ.name) + ".rs",
code: output.into_inner(),
Expand Down Expand Up @@ -286,6 +313,8 @@ pub(super) fn parse_table_update(
);
}

implement_query_table_accessor(table, out, &row_type).expect("failed to implement query table accessor");

// TODO: expose non-unique indices.

OutputFile {
Expand Down Expand Up @@ -608,6 +637,163 @@ impl {func_name} for super::RemoteProcedures {{
}
}

/// Implements `HasCols` for the given `AlgebraicTypeRef` struct type.
fn implement_query_col_types_for_struct(
module: &ModuleDef,
out: &mut impl Write,
type_ref: AlgebraicTypeRef,
) -> fmt::Result {
let struct_name = type_ref_name(module, type_ref);
let cols_struct = struct_name.clone() + "Cols";
let product_def = module.typespace_for_generate()[type_ref]
.as_product()
.expect("expected product type");

writeln!(
out,
"
/// Column accessor struct for the table `{struct_name}`.
///
/// Provides typed access to columns for query building.
pub struct {cols_struct} {{"
)?;

for element in &product_def.elements {
let field_name = &element.0;
let field_type = type_name(module, &element.1);
writeln!(
out,
" pub {field_name}: __sdk::__query_builder::Col<{struct_name}, {field_type}>,"
)?;
}

writeln!(out, "}}")?;

writeln!(
out,
"
impl __sdk::__query_builder::HasCols for {struct_name} {{
type Cols = {cols_struct};
fn cols(table_name: &'static str) -> Self::Cols {{
{cols_struct} {{"
)?;
for element in &product_def.elements {
let field_name = &element.0;
writeln!(
out,
" {field_name}: __sdk::__query_builder::Col::new(table_name, {field_name:?}),"
)?;
}

writeln!(
out,
r#"
}}
}}
}}"#
)
}

/// Implements `HasCols` and `HasIxCols` for the given table's row struct type.
fn implement_query_col_types_for_table_struct(
module: &ModuleDef,
out: &mut impl Write,
table: &TableDef,
) -> fmt::Result {
let type_ref = table.product_type_ref;
let struct_name = type_ref_name(module, type_ref);

implement_query_col_types_for_struct(module, out, type_ref)?;
let cols_ix = struct_name.clone() + "IxCols";
writeln!(
out,
"
/// Indexed column accessor struct for the table `{struct_name}`.
///
/// Provides typed access to indexed columns for query building.
pub struct {cols_ix} {{"
)?;
for index in iter_indexes(table) {
let cols = index.algorithm.columns();
if cols.len() != 1 {
continue;
}
let column = table
.columns
.iter()
.find(|col| col.col_id == cols.as_singleton().expect("singleton column"))
.unwrap();
let field_name = column.name.deref();
let field_type = type_name(module, &column.ty_for_generate);

writeln!(
out,
" pub {field_name}: __sdk::__query_builder::IxCol<{struct_name}, {field_type}>,",
)?;
}
writeln!(out, "}}")?;

writeln!(
out,
"
impl __sdk::__query_builder::HasIxCols for {struct_name} {{
type IxCols = {cols_ix};
fn ix_cols(table_name: &'static str) -> Self::IxCols {{
{cols_ix} {{"
)?;
for index in iter_indexes(table) {
let cols = index.algorithm.columns();
if cols.len() != 1 {
continue;
}
let column = table
.columns
.iter()
.find(|col| col.col_id == cols.as_singleton().expect("singleton column"))
.expect("singleton column");
let field_name = column.name.deref();

writeln!(
out,
" {field_name}: __sdk::__query_builder::IxCol::new(table_name, {field_name:?}),",
)?;
}
writeln!(
out,
r#"
}}
}}
}}"#
)
}

pub fn implement_query_table_accessor(table: &TableDef, out: &mut impl Write, struct_name: &String) -> fmt::Result {
// NEW: Generate query table accessor trait and implementation
let accessor_method = table.name.clone();
let query_accessor_trait = accessor_method.to_string() + "QueryTableAccess";

writeln!(
out,
"
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `{struct_name}`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait {query_accessor_trait} {{
#[allow(non_snake_case)]
/// Get a query builder for the table `{struct_name}`.
fn {accessor_method}(&self) -> __sdk::__query_builder::Table<{struct_name}>;
}}

impl {query_accessor_trait} for __sdk::QueryTableAccessor {{
fn {accessor_method}(&self) -> __sdk::__query_builder::Table<{struct_name}> {{
__sdk::__query_builder::Table::new({accessor_method:?})
}}
}}
"
)
}

pub fn write_type<W: Write>(module: &ModuleDef, out: &mut W, ty: &AlgebraicTypeUse) -> fmt::Result {
match ty {
AlgebraicTypeUse::Unit => write!(out, "()")?,
Expand Down Expand Up @@ -1304,6 +1490,7 @@ type SetReducerFlags = SetReducerFlags;
type DbUpdate = DbUpdate;
type AppliedDiff<'r> = AppliedDiff<'r>;
type SubscriptionHandle = SubscriptionHandle;
type QueryBuilder = __sdk::QueryBuilder;
"
);
out.delimited_block(
Expand Down
Loading