Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
158 changes: 157 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,17 @@ 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);
if let Some(table) = module
.tables()
Comment thread
Shubham8287 marked this conversation as resolved.
.find(|t| type_ref_name(module, t.product_type_ref) == name)
{
implement_query_col_types(module, out, table).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 +297,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 +621,147 @@ impl {func_name} for super::RemoteProcedures {{
}
}

fn implement_query_col_types(module: &ModuleDef, out: &mut impl Write, table: &TableDef) -> fmt::Result {
Comment thread
Shubham8287 marked this conversation as resolved.
Outdated
let struct_name = type_ref_name(module, table.product_type_ref);
let cols_struct = struct_name.clone() + "Cols";
let product_def = module.typespace_for_generate()[table.product_type_ref]
.as_product()
.unwrap();

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}: __query_builder::Col<{struct_name}, {field_type}>,"
)?;
}

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

writeln!(
out,
"
impl __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}: __query_builder::Col::new(table_name, {field_name:?}),"
)?;
}

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

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}: __query_builder::IxCol<{struct_name}, {field_type}>,",
)?;
}
writeln!(out, "}}")?;

writeln!(
out,
"
impl __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}: __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) -> __query_builder::Table<{struct_name}>;
}}

impl {query_accessor_trait} for __sdk::QueryTableAccessor {{
fn {accessor_method}(&self) -> __query_builder::Table<{struct_name}> {{
__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 @@ -719,6 +873,7 @@ const SPACETIMEDB_IMPORTS: &[&str] = &[
"\t__lib,",
"\t__sats,",
"\t__ws,",
"\t__query_builder,",
"};",
];

Expand Down Expand Up @@ -1304,6 +1459,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
Loading