From e33075914ec4aa847f116a18de30450543148f34 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 10:19:57 -0700 Subject: [PATCH 01/16] [bfops/package-on-gh-runner]: CI - Move the Package job to ubuntu-latest --- .github/workflows/package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index dd3d2dee121..202a67547d6 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: include: - - { name: x86_64 Linux, target: x86_64-unknown-linux-gnu, runner: bare-metal, container: 'debian:bookworm' } + - { name: x86_64 Linux, target: x86_64-unknown-linux-gnu, runner: ubuntu-latest } - { name: aarch64 Linux, target: aarch64-unknown-linux-gnu, runner: arm-runner } # Disabled because musl builds weren't working and we didn't want to investigate. See https://github.com/clockworklabs/SpacetimeDB/pull/2964. # - { name: x86_64 Linux musl, target: x86_64-unknown-linux-musl, runner: bare-metal, container: alpine } From 60f729836103d1f8cba40007ea616c697882bda4 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:46:31 -0700 Subject: [PATCH 02/16] [bfops/fix-nuget-smoketests]: Rebase PR --- smoketests/tests/quickstart.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/smoketests/tests/quickstart.py b/smoketests/tests/quickstart.py index fdcf3b7a4e5..0d729f2f2d2 100644 --- a/smoketests/tests/quickstart.py +++ b/smoketests/tests/quickstart.py @@ -301,6 +301,19 @@ def sdk_setup(self, path: Path): source_dir=(STDB_DIR / "crates/bindings-csharp/BSATN.Runtime").absolute(), build_subdir="bin/Release" ) + # This one is only needed because the regression-tests subdir uses it + override_nuget_package( + project_dir=STDB_DIR/"sdks/csharp", + package="SpacetimeDB.Runtime", + source_dir=(STDB_DIR / "crates/bindings-csharp/Runtime").absolute(), + build_subdir="bin/Release" + ) + override_nuget_package( + project_dir=path, + package="SpacetimeDB.BSATN.Runtime", + source_dir=(STDB_DIR / "crates/bindings-csharp/BSATN.Runtime").absolute(), + build_subdir="bin/Release" + ) override_nuget_package( project_dir=path, package="SpacetimeDB.ClientSDK", @@ -316,6 +329,12 @@ def server_postprocess(self, server_path: Path): source_dir=(STDB_DIR / "crates/bindings-csharp/Runtime").absolute(), build_subdir="bin/Release" ) + override_nuget_package( + project_dir=server_path, + package="SpacetimeDB.BSATN.Runtime", + source_dir=(STDB_DIR / "crates/bindings-csharp/BSATN.Runtime").absolute(), + build_subdir="bin/Release" + ) def test_quickstart(self): """Run the C# quickstart guides for server and client.""" From 747b2404cf3304b5ae29bae1698af4900ff54cf1 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:48:00 -0700 Subject: [PATCH 03/16] [bfops/bump-versions]: Bump versions to 1.7.0 --- Cargo.lock | 306 +++++++++--------- Cargo.toml | 66 ++-- LICENSE.txt | 4 +- .../BSATN.Codegen/BSATN.Codegen.csproj | 2 +- .../BSATN.Runtime/BSATN.Runtime.csproj | 2 +- crates/bindings-csharp/Codegen/Codegen.csproj | 2 +- crates/bindings-csharp/Runtime/Runtime.csproj | 2 +- .../src/module_bindings/index.ts | 2 +- crates/bindings-typescript/package.json | 2 +- .../project/typescript/package._json | 2 +- .../basic-c-sharp/server/StdbModule.csproj | 2 +- .../templates/basic-rust/client/Cargo.toml | 2 +- .../templates/basic-rust/server/Cargo.toml | 2 +- .../server-csharp/StdbModule.csproj | 2 +- licenses/BSL.txt | 4 +- sdks/csharp/SpacetimeDB.ClientSDK.csproj | 6 +- .../quickstart-chat/server/StdbModule.csproj | 2 +- .../regression-tests/server/StdbModule.csproj | 2 +- sdks/csharp/package.json | 2 +- 19 files changed, 207 insertions(+), 207 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed929d1e017..c2d03fb6b56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,7 +443,7 @@ name = "benchmarks-module" version = "0.1.0" dependencies = [ "anyhow", - "spacetimedb 1.6.0", + "spacetimedb 1.7.0", ] [[package]] @@ -1072,7 +1072,7 @@ dependencies = [ [[package]] name = "connect_disconnect_client" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "spacetimedb-sdk", @@ -3420,7 +3420,7 @@ name = "keynote-benchmarks" version = "0.1.0" dependencies = [ "log", - "spacetimedb 1.6.0", + "spacetimedb 1.7.0", ] [[package]] @@ -3800,7 +3800,7 @@ version = "0.0.0" dependencies = [ "anyhow", "log", - "spacetimedb 1.6.0", + "spacetimedb 1.7.0", ] [[package]] @@ -4893,7 +4893,7 @@ name = "perf-test-module" version = "0.1.0" dependencies = [ "log", - "spacetimedb 1.6.0", + "spacetimedb 1.7.0", ] [[package]] @@ -5445,7 +5445,7 @@ name = "quickstart-chat-module" version = "0.1.0" dependencies = [ "log", - "spacetimedb 1.6.0", + "spacetimedb 1.7.0", ] [[package]] @@ -6535,12 +6535,12 @@ dependencies = [ "anyhow", "log", "paste", - "spacetimedb 1.6.0", + "spacetimedb 1.7.0", ] [[package]] name = "sdk-unreal-test-harness" -version = "1.6.0" +version = "1.7.0" dependencies = [ "serial_test", "spacetimedb-testing", @@ -6974,61 +6974,61 @@ name = "spacetime-module" version = "0.1.0" dependencies = [ "log", - "spacetimedb 1.6.0", + "spacetimedb 1.7.0", ] [[package]] name = "spacetimedb" version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8679cf54a7a653e6bc612bef28c219f98b9274308d8aae1e86046ed685db48b1" dependencies = [ "bytemuck", "derive_more 0.99.20", "getrandom 0.2.16", - "insta", "log", "rand 0.8.5", "scoped-tls", - "serde_json", "spacetimedb-bindings-macro 1.6.0", "spacetimedb-bindings-sys 1.6.0", "spacetimedb-lib 1.6.0", "spacetimedb-primitives 1.6.0", - "trybuild", ] [[package]] name = "spacetimedb" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8679cf54a7a653e6bc612bef28c219f98b9274308d8aae1e86046ed685db48b1" +version = "1.7.0" dependencies = [ "bytemuck", "derive_more 0.99.20", "getrandom 0.2.16", + "insta", "log", "rand 0.8.5", "scoped-tls", - "spacetimedb-bindings-macro 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "spacetimedb-bindings-sys 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "spacetimedb-lib 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "spacetimedb-primitives 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json", + "spacetimedb-bindings-macro 1.7.0", + "spacetimedb-bindings-sys 1.7.0", + "spacetimedb-lib 1.7.0", + "spacetimedb-primitives 1.7.0", + "trybuild", ] [[package]] name = "spacetimedb-auth" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "serde", "serde_json", "serde_with", "spacetimedb-jsonwebtoken", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", ] [[package]] name = "spacetimedb-bench" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "anymap", @@ -7056,11 +7056,11 @@ dependencies = [ "spacetimedb-data-structures", "spacetimedb-datastore", "spacetimedb-execution", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-paths", - "spacetimedb-primitives 1.6.0", + "spacetimedb-primitives 1.7.0", "spacetimedb-query", - "spacetimedb-sats 1.6.0", + "spacetimedb-sats 1.7.0", "spacetimedb-schema", "spacetimedb-standalone", "spacetimedb-table", @@ -7076,6 +7076,8 @@ dependencies = [ [[package]] name = "spacetimedb-bindings-macro" version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a930242493f5c875ab96903eb40fb6a90c4d3ae99597fd51da569ff22769a03" dependencies = [ "heck 0.4.1", "humantime", @@ -7087,37 +7089,35 @@ dependencies = [ [[package]] name = "spacetimedb-bindings-macro" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a930242493f5c875ab96903eb40fb6a90c4d3ae99597fd51da569ff22769a03" +version = "1.7.0" dependencies = [ "heck 0.4.1", "humantime", "proc-macro2", "quote", - "spacetimedb-primitives 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spacetimedb-primitives 1.7.0", "syn 2.0.107", ] [[package]] name = "spacetimedb-bindings-sys" version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e565dfcdd2dc3f58e0178052ec1c8ce710b013482d4fa50b511ba786a2c3bc68" dependencies = [ "spacetimedb-primitives 1.6.0", ] [[package]] name = "spacetimedb-bindings-sys" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e565dfcdd2dc3f58e0178052ec1c8ce710b013482d4fa50b511ba786a2c3bc68" +version = "1.7.0" dependencies = [ - "spacetimedb-primitives 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spacetimedb-primitives 1.7.0", ] [[package]] name = "spacetimedb-cli" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "base64 0.21.7", @@ -7160,9 +7160,9 @@ dependencies = [ "spacetimedb-data-structures", "spacetimedb-fs-utils", "spacetimedb-jsonwebtoken", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-paths", - "spacetimedb-primitives 1.6.0", + "spacetimedb-primitives 1.7.0", "spacetimedb-schema", "syntect", "tabled", @@ -7186,7 +7186,7 @@ dependencies = [ [[package]] name = "spacetimedb-client-api" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "async-stream", @@ -7223,7 +7223,7 @@ dependencies = [ "spacetimedb-data-structures", "spacetimedb-datastore", "spacetimedb-jsonwebtoken", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-paths", "spacetimedb-schema", "tempfile", @@ -7240,7 +7240,7 @@ dependencies = [ [[package]] name = "spacetimedb-client-api-messages" -version = "1.6.0" +version = "1.7.0" dependencies = [ "bytes", "bytestring", @@ -7254,16 +7254,16 @@ dependencies = [ "serde_json", "serde_with", "smallvec", - "spacetimedb-lib 1.6.0", - "spacetimedb-primitives 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb-lib 1.7.0", + "spacetimedb-primitives 1.7.0", + "spacetimedb-sats 1.7.0", "strum", "thiserror 1.0.69", ] [[package]] name = "spacetimedb-codegen" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -7272,15 +7272,15 @@ dependencies = [ "itertools 0.12.1", "regex", "spacetimedb-data-structures", - "spacetimedb-lib 1.6.0", - "spacetimedb-primitives 1.6.0", + "spacetimedb-lib 1.7.0", + "spacetimedb-primitives 1.7.0", "spacetimedb-schema", "spacetimedb-testing", ] [[package]] name = "spacetimedb-commitlog" -version = "1.6.0" +version = "1.7.0" dependencies = [ "async-stream", "bitflags 2.10.0", @@ -7300,8 +7300,8 @@ dependencies = [ "spacetimedb-commitlog", "spacetimedb-fs-utils", "spacetimedb-paths", - "spacetimedb-primitives 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb-primitives 1.7.0", + "spacetimedb-sats 1.7.0", "tempfile", "thiserror 1.0.69", "tokio", @@ -7312,7 +7312,7 @@ dependencies = [ [[package]] name = "spacetimedb-core" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "arrayvec", @@ -7392,14 +7392,14 @@ dependencies = [ "spacetimedb-fs-utils", "spacetimedb-jsonwebtoken", "spacetimedb-jwks", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-memory-usage", "spacetimedb-metrics", "spacetimedb-paths", "spacetimedb-physical-plan", - "spacetimedb-primitives 1.6.0", + "spacetimedb-primitives 1.7.0", "spacetimedb-query", - "spacetimedb-sats 1.6.0", + "spacetimedb-sats 1.7.0", "spacetimedb-schema", "spacetimedb-snapshot", "spacetimedb-subscription", @@ -7434,7 +7434,7 @@ dependencies = [ [[package]] name = "spacetimedb-data-structures" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ahash 0.8.12", "crossbeam-queue", @@ -7448,7 +7448,7 @@ dependencies = [ [[package]] name = "spacetimedb-datastore" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "bytes", @@ -7468,11 +7468,11 @@ dependencies = [ "spacetimedb-data-structures", "spacetimedb-durability", "spacetimedb-execution", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-metrics", "spacetimedb-paths", - "spacetimedb-primitives 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb-primitives 1.7.0", + "spacetimedb-sats 1.7.0", "spacetimedb-schema", "spacetimedb-snapshot", "spacetimedb-table", @@ -7483,14 +7483,14 @@ dependencies = [ [[package]] name = "spacetimedb-durability" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "itertools 0.12.1", "log", "spacetimedb-commitlog", "spacetimedb-paths", - "spacetimedb-sats 1.6.0", + "spacetimedb-sats 1.7.0", "thiserror 1.0.69", "tokio", "tracing", @@ -7498,32 +7498,32 @@ dependencies = [ [[package]] name = "spacetimedb-execution" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "itertools 0.12.1", "spacetimedb-expr", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-physical-plan", - "spacetimedb-primitives 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb-primitives 1.7.0", + "spacetimedb-sats 1.7.0", "spacetimedb-sql-parser", "spacetimedb-table", ] [[package]] name = "spacetimedb-expr" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "bigdecimal", "derive_more 0.99.20", "ethnum", "pretty_assertions", - "spacetimedb 1.6.0", - "spacetimedb-lib 1.6.0", - "spacetimedb-primitives 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb 1.7.0", + "spacetimedb-lib 1.7.0", + "spacetimedb-primitives 1.7.0", + "spacetimedb-sats 1.7.0", "spacetimedb-schema", "spacetimedb-sql-parser", "thiserror 1.0.69", @@ -7531,7 +7531,7 @@ dependencies = [ [[package]] name = "spacetimedb-fs-utils" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "hex", @@ -7574,26 +7574,18 @@ dependencies = [ [[package]] name = "spacetimedb-lib" version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba57c8f1983bb144ee7e1ff28aa5882ca437f67c14a14daad4bf61d31f7040d" dependencies = [ "anyhow", "bitflags 2.10.0", "blake3", - "bytes", "chrono", "derive_more 0.99.20", "enum-as-inner", - "enum-map", "hex", - "insta", "itertools 0.12.1", - "proptest", - "proptest-derive", - "ron", - "serde", - "serde_json", "spacetimedb-bindings-macro 1.6.0", - "spacetimedb-memory-usage", - "spacetimedb-metrics", "spacetimedb-primitives 1.6.0", "spacetimedb-sats 1.6.0", "thiserror 1.0.69", @@ -7601,27 +7593,35 @@ dependencies = [ [[package]] name = "spacetimedb-lib" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba57c8f1983bb144ee7e1ff28aa5882ca437f67c14a14daad4bf61d31f7040d" +version = "1.7.0" dependencies = [ "anyhow", "bitflags 2.10.0", "blake3", + "bytes", "chrono", "derive_more 0.99.20", "enum-as-inner", + "enum-map", "hex", + "insta", "itertools 0.12.1", - "spacetimedb-bindings-macro 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "spacetimedb-primitives 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "spacetimedb-sats 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proptest", + "proptest-derive", + "ron", + "serde", + "serde_json", + "spacetimedb-bindings-macro 1.7.0", + "spacetimedb-memory-usage", + "spacetimedb-metrics", + "spacetimedb-primitives 1.7.0", + "spacetimedb-sats 1.7.0", "thiserror 1.0.69", ] [[package]] name = "spacetimedb-memory-usage" -version = "1.6.0" +version = "1.7.0" dependencies = [ "decorum", "ethnum", @@ -7631,7 +7631,7 @@ dependencies = [ [[package]] name = "spacetimedb-metrics" -version = "1.6.0" +version = "1.7.0" dependencies = [ "arrayvec", "itertools 0.12.1", @@ -7641,7 +7641,7 @@ dependencies = [ [[package]] name = "spacetimedb-paths" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "chrono", @@ -7657,7 +7657,7 @@ dependencies = [ [[package]] name = "spacetimedb-pg" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "async-trait", @@ -7668,22 +7668,22 @@ dependencies = [ "pgwire", "spacetimedb-client-api", "spacetimedb-client-api-messages", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "thiserror 1.0.69", "tokio", ] [[package]] name = "spacetimedb-physical-plan" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "derive_more 0.99.20", "either", "pretty_assertions", "spacetimedb-expr", - "spacetimedb-lib 1.6.0", - "spacetimedb-primitives 1.6.0", + "spacetimedb-lib 1.7.0", + "spacetimedb-primitives 1.7.0", "spacetimedb-schema", "spacetimedb-sql-parser", "spacetimedb-table", @@ -7692,31 +7692,31 @@ dependencies = [ [[package]] name = "spacetimedb-primitives" version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dcd64c6970ca59e7b71e51952cf1a71bae2652b1eb736c19a7e528f3874894a" dependencies = [ "bitflags 2.10.0", "either", - "enum-as-inner", "itertools 0.12.1", "nohash-hasher", - "proptest", - "spacetimedb-memory-usage", ] [[package]] name = "spacetimedb-primitives" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dcd64c6970ca59e7b71e51952cf1a71bae2652b1eb736c19a7e528f3874894a" +version = "1.7.0" dependencies = [ "bitflags 2.10.0", "either", + "enum-as-inner", "itertools 0.12.1", "nohash-hasher", + "proptest", + "spacetimedb-memory-usage", ] [[package]] name = "spacetimedb-query" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "itertools 0.12.1", @@ -7724,9 +7724,9 @@ dependencies = [ "spacetimedb-client-api-messages", "spacetimedb-execution", "spacetimedb-expr", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-physical-plan", - "spacetimedb-primitives 1.6.0", + "spacetimedb-primitives 1.7.0", "spacetimedb-sql-parser", "spacetimedb-table", ] @@ -7734,15 +7734,14 @@ dependencies = [ [[package]] name = "spacetimedb-sats" version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae61d8f88bda21f56c143bc9b4ddc20100ff08ae4fed0b9444509f7c3ca4339" dependencies = [ - "ahash 0.8.12", "anyhow", "arrayvec", "bitflags 2.10.0", - "blake3", "bytemuck", "bytes", - "bytestring", "chrono", "decorum", "derive_more 0.99.20", @@ -7750,32 +7749,26 @@ dependencies = [ "ethnum", "hex", "itertools 0.12.1", - "proptest", - "proptest-derive", - "rand 0.9.2", "second-stack", - "serde", - "serde_json", "sha3", "smallvec", "spacetimedb-bindings-macro 1.6.0", - "spacetimedb-memory-usage", - "spacetimedb-metrics", "spacetimedb-primitives 1.6.0", "thiserror 1.0.69", ] [[package]] name = "spacetimedb-sats" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae61d8f88bda21f56c143bc9b4ddc20100ff08ae4fed0b9444509f7c3ca4339" +version = "1.7.0" dependencies = [ + "ahash 0.8.12", "anyhow", "arrayvec", "bitflags 2.10.0", + "blake3", "bytemuck", "bytes", + "bytestring", "chrono", "decorum", "derive_more 0.99.20", @@ -7783,17 +7776,24 @@ dependencies = [ "ethnum", "hex", "itertools 0.12.1", + "proptest", + "proptest-derive", + "rand 0.9.2", "second-stack", + "serde", + "serde_json", "sha3", "smallvec", - "spacetimedb-bindings-macro 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "spacetimedb-primitives 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spacetimedb-bindings-macro 1.7.0", + "spacetimedb-memory-usage", + "spacetimedb-metrics", + "spacetimedb-primitives 1.7.0", "thiserror 1.0.69", ] [[package]] name = "spacetimedb-schema" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "derive_more 0.99.20", @@ -7810,9 +7810,9 @@ dependencies = [ "serial_test", "smallvec", "spacetimedb-data-structures", - "spacetimedb-lib 1.6.0", - "spacetimedb-primitives 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb-lib 1.7.0", + "spacetimedb-primitives 1.7.0", + "spacetimedb-sats 1.7.0", "spacetimedb-sql-parser", "spacetimedb-testing", "termcolor", @@ -7823,7 +7823,7 @@ dependencies = [ [[package]] name = "spacetimedb-sdk" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anymap", "base64 0.21.7", @@ -7842,9 +7842,9 @@ dependencies = [ "rand 0.9.2", "spacetimedb-client-api-messages", "spacetimedb-data-structures", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-metrics", - "spacetimedb-sats 1.6.0", + "spacetimedb-sats 1.7.0", "spacetimedb-testing", "thiserror 1.0.69", "tokio", @@ -7853,7 +7853,7 @@ dependencies = [ [[package]] name = "spacetimedb-snapshot" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "blake3", @@ -7870,10 +7870,10 @@ dependencies = [ "spacetimedb-datastore", "spacetimedb-durability", "spacetimedb-fs-utils", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-paths", - "spacetimedb-primitives 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb-primitives 1.7.0", + "spacetimedb-sats 1.7.0", "spacetimedb-schema", "spacetimedb-table", "tempfile", @@ -7886,17 +7886,17 @@ dependencies = [ [[package]] name = "spacetimedb-sql-parser" -version = "1.6.0" +version = "1.7.0" dependencies = [ "derive_more 0.99.20", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "sqlparser", "thiserror 1.0.69", ] [[package]] name = "spacetimedb-standalone" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "async-trait", @@ -7920,7 +7920,7 @@ dependencies = [ "spacetimedb-client-api-messages", "spacetimedb-core", "spacetimedb-datastore", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-paths", "spacetimedb-pg", "spacetimedb-schema", @@ -7937,20 +7937,20 @@ dependencies = [ [[package]] name = "spacetimedb-subscription" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "spacetimedb-execution", "spacetimedb-expr", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-physical-plan", - "spacetimedb-primitives 1.6.0", + "spacetimedb-primitives 1.7.0", "spacetimedb-query", ] [[package]] name = "spacetimedb-table" -version = "1.6.0" +version = "1.7.0" dependencies = [ "ahash 0.8.12", "blake3", @@ -7966,17 +7966,17 @@ dependencies = [ "rand 0.9.2", "smallvec", "spacetimedb-data-structures", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-memory-usage", - "spacetimedb-primitives 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb-primitives 1.7.0", + "spacetimedb-sats 1.7.0", "spacetimedb-schema", "thiserror 1.0.69", ] [[package]] name = "spacetimedb-testing" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "clap 4.5.50", @@ -7992,7 +7992,7 @@ dependencies = [ "spacetimedb-client-api", "spacetimedb-core", "spacetimedb-data-structures", - "spacetimedb-lib 1.6.0", + "spacetimedb-lib 1.7.0", "spacetimedb-paths", "spacetimedb-schema", "spacetimedb-standalone", @@ -8003,7 +8003,7 @@ dependencies = [ [[package]] name = "spacetimedb-update" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "bytes", @@ -8028,7 +8028,7 @@ dependencies = [ [[package]] name = "spacetimedb-vm" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "arrayvec", @@ -8038,9 +8038,9 @@ dependencies = [ "smallvec", "spacetimedb-data-structures", "spacetimedb-execution", - "spacetimedb-lib 1.6.0", - "spacetimedb-primitives 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb-lib 1.7.0", + "spacetimedb-primitives 1.7.0", + "spacetimedb-sats 1.7.0", "spacetimedb-schema", "spacetimedb-table", "tempfile", @@ -8119,7 +8119,7 @@ dependencies = [ [[package]] name = "sqltest" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "async-trait", @@ -8138,8 +8138,8 @@ dependencies = [ "rust_decimal", "spacetimedb-core", "spacetimedb-datastore", - "spacetimedb-lib 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb-lib 1.7.0", + "spacetimedb-sats 1.7.0", "spacetimedb-vm", "sqllogictest", "sqllogictest-engines", @@ -8513,7 +8513,7 @@ dependencies = [ [[package]] name = "test-client" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "env_logger 0.10.2", @@ -8525,7 +8525,7 @@ dependencies = [ [[package]] name = "test-counter" -version = "1.6.0" +version = "1.7.0" dependencies = [ "anyhow", "spacetimedb-data-structures", @@ -9244,7 +9244,7 @@ version = "0.1.0" dependencies = [ "anyhow", "log", - "spacetimedb 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spacetimedb 1.6.0", ] [[package]] @@ -9253,7 +9253,7 @@ version = "0.1.0" dependencies = [ "anyhow", "log", - "spacetimedb 1.6.0", + "spacetimedb 1.7.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5bb1d180aed..d1dc2344c4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,44 +89,44 @@ inherits = "release" debug = true [workspace.package] -version = "1.6.0" +version = "1.7.0" edition = "2021" # update rust-toolchain.toml too! rust-version = "1.90.0" [workspace.dependencies] -spacetimedb = { path = "crates/bindings", version = "=1.6.0" } -spacetimedb-auth = { path = "crates/auth", version = "=1.6.0" } -spacetimedb-bindings-macro = { path = "crates/bindings-macro", version = "=1.6.0" } -spacetimedb-bindings-sys = { path = "crates/bindings-sys", version = "=1.6.0" } -spacetimedb-cli = { path = "crates/cli", version = "=1.6.0" } -spacetimedb-client-api = { path = "crates/client-api", version = "=1.6.0" } -spacetimedb-client-api-messages = { path = "crates/client-api-messages", version = "=1.6.0" } -spacetimedb-codegen = { path = "crates/codegen", version = "=1.6.0" } -spacetimedb-commitlog = { path = "crates/commitlog", version = "=1.6.0" } -spacetimedb-core = { path = "crates/core", version = "=1.6.0" } -spacetimedb-data-structures = { path = "crates/data-structures", version = "=1.6.0" } -spacetimedb-datastore = { path = "crates/datastore", version = "=1.6.0" } -spacetimedb-durability = { path = "crates/durability", version = "=1.6.0" } -spacetimedb-execution = { path = "crates/execution", version = "=1.6.0" } -spacetimedb-expr = { path = "crates/expr", version = "=1.6.0" } -spacetimedb-lib = { path = "crates/lib", default-features = false, version = "=1.6.0" } -spacetimedb-memory-usage = { path = "crates/memory-usage", version = "=1.6.0", default-features = false } -spacetimedb-metrics = { path = "crates/metrics", version = "=1.6.0" } -spacetimedb-paths = { path = "crates/paths", version = "=1.6.0" } -spacetimedb-pg = { path = "crates/pg", version = "=1.6.0" } -spacetimedb-physical-plan = { path = "crates/physical-plan", version = "=1.6.0" } -spacetimedb-primitives = { path = "crates/primitives", version = "=1.6.0" } -spacetimedb-query = { path = "crates/query", version = "=1.6.0" } -spacetimedb-sats = { path = "crates/sats", version = "=1.6.0" } -spacetimedb-schema = { path = "crates/schema", version = "=1.6.0" } -spacetimedb-standalone = { path = "crates/standalone", version = "=1.6.0" } -spacetimedb-sql-parser = { path = "crates/sql-parser", version = "=1.6.0" } -spacetimedb-table = { path = "crates/table", version = "=1.6.0" } -spacetimedb-vm = { path = "crates/vm", version = "=1.6.0" } -spacetimedb-fs-utils = { path = "crates/fs-utils", version = "=1.6.0" } -spacetimedb-snapshot = { path = "crates/snapshot", version = "=1.6.0" } -spacetimedb-subscription = { path = "crates/subscription", version = "=1.6.0" } +spacetimedb = { path = "crates/bindings", version = "=1.7.0" } +spacetimedb-auth = { path = "crates/auth", version = "=1.7.0" } +spacetimedb-bindings-macro = { path = "crates/bindings-macro", version = "=1.7.0" } +spacetimedb-bindings-sys = { path = "crates/bindings-sys", version = "=1.7.0" } +spacetimedb-cli = { path = "crates/cli", version = "=1.7.0" } +spacetimedb-client-api = { path = "crates/client-api", version = "=1.7.0" } +spacetimedb-client-api-messages = { path = "crates/client-api-messages", version = "=1.7.0" } +spacetimedb-codegen = { path = "crates/codegen", version = "=1.7.0" } +spacetimedb-commitlog = { path = "crates/commitlog", version = "=1.7.0" } +spacetimedb-core = { path = "crates/core", version = "=1.7.0" } +spacetimedb-data-structures = { path = "crates/data-structures", version = "=1.7.0" } +spacetimedb-datastore = { path = "crates/datastore", version = "=1.7.0" } +spacetimedb-durability = { path = "crates/durability", version = "=1.7.0" } +spacetimedb-execution = { path = "crates/execution", version = "=1.7.0" } +spacetimedb-expr = { path = "crates/expr", version = "=1.7.0" } +spacetimedb-lib = { path = "crates/lib", default-features = false, version = "=1.7.0" } +spacetimedb-memory-usage = { path = "crates/memory-usage", version = "=1.7.0", default-features = false } +spacetimedb-metrics = { path = "crates/metrics", version = "=1.7.0" } +spacetimedb-paths = { path = "crates/paths", version = "=1.7.0" } +spacetimedb-pg = { path = "crates/pg", version = "=1.7.0" } +spacetimedb-physical-plan = { path = "crates/physical-plan", version = "=1.7.0" } +spacetimedb-primitives = { path = "crates/primitives", version = "=1.7.0" } +spacetimedb-query = { path = "crates/query", version = "=1.7.0" } +spacetimedb-sats = { path = "crates/sats", version = "=1.7.0" } +spacetimedb-schema = { path = "crates/schema", version = "=1.7.0" } +spacetimedb-standalone = { path = "crates/standalone", version = "=1.7.0" } +spacetimedb-sql-parser = { path = "crates/sql-parser", version = "=1.7.0" } +spacetimedb-table = { path = "crates/table", version = "=1.7.0" } +spacetimedb-vm = { path = "crates/vm", version = "=1.7.0" } +spacetimedb-fs-utils = { path = "crates/fs-utils", version = "=1.7.0" } +spacetimedb-snapshot = { path = "crates/snapshot", version = "=1.7.0" } +spacetimedb-subscription = { path = "crates/subscription", version = "=1.7.0" } # Prevent `ahash` from pulling in `getrandom` by disabling default features. # Modules use `getrandom02` and we need to prevent an incompatible version diff --git a/LICENSE.txt b/LICENSE.txt index 8471788dae9..40f9d2c9477 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -5,7 +5,7 @@ Business Source License 1.1 Parameters Licensor: Clockwork Laboratories, Inc. -Licensed Work: SpacetimeDB 1.6.0 +Licensed Work: SpacetimeDB 1.7.0 The Licensed Work is (c) 2023 Clockwork Laboratories, Inc. @@ -21,7 +21,7 @@ Additional Use Grant: You may make use of the Licensed Work provided your Licensed Work by creating tables whose schemas are controlled by such third parties. -Change Date: 2030-10-15 +Change Date: 2030-10-31 Change License: GNU Affero General Public License v3.0 with a linking exception diff --git a/crates/bindings-csharp/BSATN.Codegen/BSATN.Codegen.csproj b/crates/bindings-csharp/BSATN.Codegen/BSATN.Codegen.csproj index d7c8b7aeeec..5784a4da6be 100644 --- a/crates/bindings-csharp/BSATN.Codegen/BSATN.Codegen.csproj +++ b/crates/bindings-csharp/BSATN.Codegen/BSATN.Codegen.csproj @@ -2,7 +2,7 @@ SpacetimeDB.BSATN.Codegen - 1.6.0 + 1.7.0 SpacetimeDB BSATN Codegen The SpacetimeDB BSATN Codegen implements the Roslyn incremental generators for BSATN serialization/deserialization in C#. diff --git a/crates/bindings-csharp/BSATN.Runtime/BSATN.Runtime.csproj b/crates/bindings-csharp/BSATN.Runtime/BSATN.Runtime.csproj index c4c991b73c3..97f12a6bfef 100644 --- a/crates/bindings-csharp/BSATN.Runtime/BSATN.Runtime.csproj +++ b/crates/bindings-csharp/BSATN.Runtime/BSATN.Runtime.csproj @@ -2,7 +2,7 @@ SpacetimeDB.BSATN.Runtime - 1.6.0 + 1.7.0 SpacetimeDB BSATN Runtime The SpacetimeDB BSATN Runtime implements APIs for BSATN serialization/deserialization in C#. true diff --git a/crates/bindings-csharp/Codegen/Codegen.csproj b/crates/bindings-csharp/Codegen/Codegen.csproj index 33a6772f134..aedc4de1c9b 100644 --- a/crates/bindings-csharp/Codegen/Codegen.csproj +++ b/crates/bindings-csharp/Codegen/Codegen.csproj @@ -2,7 +2,7 @@ SpacetimeDB.Codegen - 1.6.0 + 1.7.0 SpacetimeDB Module Codegen The SpacetimeDB Codegen implements the Roslyn incremental generators for writing SpacetimeDB modules in C#. diff --git a/crates/bindings-csharp/Runtime/Runtime.csproj b/crates/bindings-csharp/Runtime/Runtime.csproj index 00a5bc43391..5e68bff2729 100644 --- a/crates/bindings-csharp/Runtime/Runtime.csproj +++ b/crates/bindings-csharp/Runtime/Runtime.csproj @@ -2,7 +2,7 @@ SpacetimeDB.Runtime - 1.6.0 + 1.7.0 SpacetimeDB Module Runtime The SpacetimeDB Runtime implements the database runtime bindings for writing SpacetimeDB modules in C#. diff --git a/crates/bindings-typescript/examples/quickstart-chat/src/module_bindings/index.ts b/crates/bindings-typescript/examples/quickstart-chat/src/module_bindings/index.ts index f30b1c1253b..89a6af89230 100644 --- a/crates/bindings-typescript/examples/quickstart-chat/src/module_bindings/index.ts +++ b/crates/bindings-typescript/examples/quickstart-chat/src/module_bindings/index.ts @@ -89,7 +89,7 @@ const REMOTE_MODULE = { }, }, versionInfo: { - cliVersion: '1.6.0', + cliVersion: '1.7.0', }, // Constructors which are used by the DbConnectionImpl to // extract type information from the generated RemoteModule. diff --git a/crates/bindings-typescript/package.json b/crates/bindings-typescript/package.json index b1dd6daf6a9..74217fac8e8 100644 --- a/crates/bindings-typescript/package.json +++ b/crates/bindings-typescript/package.json @@ -1,6 +1,6 @@ { "name": "spacetimedb", - "version": "1.6.2", + "version": "1.7.0", "description": "API and ABI bindings for the SpacetimeDB TypeScript module library", "homepage": "https://github.com/clockworklabs/SpacetimeDB#readme", "bugs": { diff --git a/crates/cli/src/subcommands/project/typescript/package._json b/crates/cli/src/subcommands/project/typescript/package._json index 47be347a920..9af76d05094 100644 --- a/crates/cli/src/subcommands/project/typescript/package._json +++ b/crates/cli/src/subcommands/project/typescript/package._json @@ -10,6 +10,6 @@ "author": "", "license": "ISC", "dependencies": { - "spacetimedb": "1.6.*" + "spacetimedb": "1.7.*" } } \ No newline at end of file diff --git a/crates/cli/templates/basic-c-sharp/server/StdbModule.csproj b/crates/cli/templates/basic-c-sharp/server/StdbModule.csproj index 4a504b099c8..6121a5ae7f9 100644 --- a/crates/cli/templates/basic-c-sharp/server/StdbModule.csproj +++ b/crates/cli/templates/basic-c-sharp/server/StdbModule.csproj @@ -8,7 +8,7 @@ - + diff --git a/crates/cli/templates/basic-rust/client/Cargo.toml b/crates/cli/templates/basic-rust/client/Cargo.toml index 35168a60fd8..56befed5f8a 100644 --- a/crates/cli/templates/basic-rust/client/Cargo.toml +++ b/crates/cli/templates/basic-rust/client/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -spacetimedb-sdk = "1.6.*" +spacetimedb-sdk = "1.7.*" diff --git a/crates/cli/templates/basic-rust/server/Cargo.toml b/crates/cli/templates/basic-rust/server/Cargo.toml index dfbc3a9823e..417cc88be6c 100644 --- a/crates/cli/templates/basic-rust/server/Cargo.toml +++ b/crates/cli/templates/basic-rust/server/Cargo.toml @@ -9,5 +9,5 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -spacetimedb = "1.6.*" +spacetimedb = "1.7.*" log = "0.4" diff --git a/demo/Blackholio/server-csharp/StdbModule.csproj b/demo/Blackholio/server-csharp/StdbModule.csproj index 60c71d74247..eea7fcc05cb 100644 --- a/demo/Blackholio/server-csharp/StdbModule.csproj +++ b/demo/Blackholio/server-csharp/StdbModule.csproj @@ -13,7 +13,7 @@ - + diff --git a/licenses/BSL.txt b/licenses/BSL.txt index 8058a776b49..758ef4e8948 100644 --- a/licenses/BSL.txt +++ b/licenses/BSL.txt @@ -5,7 +5,7 @@ Business Source License 1.1 Parameters Licensor: Clockwork Laboratories, Inc. -Licensed Work: SpacetimeDB 1.6.0 +Licensed Work: SpacetimeDB 1.7.0 The Licensed Work is (c) 2023 Clockwork Laboratories, Inc. @@ -21,7 +21,7 @@ Additional Use Grant: You may make use of the Licensed Work provided your Licensed Work by creating tables whose schemas are controlled by such third parties. -Change Date: 2030-10-15 +Change Date: 2030-10-31 Change License: GNU Affero General Public License v3.0 with a linking exception diff --git a/sdks/csharp/SpacetimeDB.ClientSDK.csproj b/sdks/csharp/SpacetimeDB.ClientSDK.csproj index b7f81755e20..f1f24620b2f 100644 --- a/sdks/csharp/SpacetimeDB.ClientSDK.csproj +++ b/sdks/csharp/SpacetimeDB.ClientSDK.csproj @@ -16,8 +16,8 @@ logo.png README.md https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk - 1.6.0 - 1.6.0 + 1.7.0 + 1.7.0 $(DefaultItemExcludes);*~/** packages @@ -25,7 +25,7 @@ - + diff --git a/sdks/csharp/examples~/quickstart-chat/server/StdbModule.csproj b/sdks/csharp/examples~/quickstart-chat/server/StdbModule.csproj index 12710df0560..beac8e96f64 100644 --- a/sdks/csharp/examples~/quickstart-chat/server/StdbModule.csproj +++ b/sdks/csharp/examples~/quickstart-chat/server/StdbModule.csproj @@ -14,7 +14,7 @@ - + diff --git a/sdks/csharp/examples~/regression-tests/server/StdbModule.csproj b/sdks/csharp/examples~/regression-tests/server/StdbModule.csproj index 4a504b099c8..6121a5ae7f9 100644 --- a/sdks/csharp/examples~/regression-tests/server/StdbModule.csproj +++ b/sdks/csharp/examples~/regression-tests/server/StdbModule.csproj @@ -8,7 +8,7 @@ - + diff --git a/sdks/csharp/package.json b/sdks/csharp/package.json index 3219e255131..68dfccff873 100644 --- a/sdks/csharp/package.json +++ b/sdks/csharp/package.json @@ -1,7 +1,7 @@ { "name": "com.clockworklabs.spacetimedbsdk", "displayName": "SpacetimeDB SDK", - "version": "1.6.0", + "version": "1.7.0", "description": "The SpacetimeDB Client SDK is a software development kit (SDK) designed to interact with and manipulate SpacetimeDB modules..", "keywords": [], "author": { From 38165f062cc191188021024744028addb8f976ef Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:29 -0700 Subject: [PATCH 04/16] [release/v1.7.0]: Revert "Index a view's metadata columns (#3540)" This reverts commit 3863f209d9c58335fdb980209988992007a81a3b. --- crates/schema/src/schema.rs | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/crates/schema/src/schema.rs b/crates/schema/src/schema.rs index 86c748237c8..65a8813be72 100644 --- a/crates/schema/src/schema.rs +++ b/crates/schema/src/schema.rs @@ -603,19 +603,19 @@ impl TableSchema { /// fn my_anonymous_view(ctx: &AnonymousViewContext, x: u32, y: u32) -> Vec { ... } /// ``` /// - /// The above views are materialized with the following schema: + /// The above views are materialized with the following schemas: /// /// my_view: /// - /// | sender | arg_id | a | b | - /// |----------------|--------|-----|-----| - /// | (some = 0x...) | u64 | u32 | u32 | + /// | sender | arg_id | a | b | + /// |----------|--------|-----|-----| + /// | Identity | 1 | u32 | u32 | /// /// my_anonymous_view: /// - /// | sender | arg_id | a | b | - /// |-------------|--------|-----|-----| - /// | (none = ()) | u64 | u32 | u32 | + /// | arg_id | a | b | + /// |--------|-----|-----| + /// | 1 | u32 | u32 | /// /// Note, `arg_id` is a foreign key into `st_view_arg`. pub fn from_view_def(module_def: &ModuleDef, view_def: &ViewDef) -> Self { @@ -631,20 +631,17 @@ impl TableSchema { let n = return_columns.len() + 2; let mut columns = Vec::with_capacity(n); - let sender_col_name = "sender"; - let arg_id_col_name = "arg_id"; - columns.push(ColumnSchema { table_id: TableId::SENTINEL, col_pos: ColId(0), - col_name: sender_col_name.into(), + col_name: "sender".into(), col_type: AlgebraicType::option(AlgebraicType::identity()), }); columns.push(ColumnSchema { table_id: TableId::SENTINEL, col_pos: ColId(1), - col_name: arg_id_col_name.into(), + col_name: "arg_id".into(), col_type: AlgebraicType::U64, }); @@ -657,15 +654,6 @@ impl TableSchema { .map(|(col_pos, schema)| ColumnSchema { col_pos, ..schema }), ); - let index_name = format!("{}_{}_{}_idx_btree", name, sender_col_name, arg_id_col_name); - - let indexes = vec![IndexSchema { - index_id: IndexId::SENTINEL, - table_id: TableId::SENTINEL, - index_name: index_name.into_boxed_str(), - index_algorithm: IndexAlgorithm::BTree(col_list![0, 1].into()), - }]; - let table_access = if *is_public { StAccess::Public } else { @@ -676,7 +664,7 @@ impl TableSchema { TableId::SENTINEL, (*name).clone().into(), columns, - indexes, + vec![], vec![], vec![], StTableType::User, From 904be75e3b258a2e5168574d7ba205041fb86860 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:29 -0700 Subject: [PATCH 05/16] [release/v1.7.0]: Revert "Add system table for tracking view subscribers (#3482)" This reverts commit 247655bb8775e8c99be3af131f4e586ccaba3819. --- .../locking_tx_datastore/committed_state.rs | 8 +- .../src/locking_tx_datastore/datastore.rs | 30 +------ crates/datastore/src/system_tables.rs | 88 +------------------ crates/schema/src/schema.rs | 80 +++++++++++------ 4 files changed, 60 insertions(+), 146 deletions(-) diff --git a/crates/datastore/src/locking_tx_datastore/committed_state.rs b/crates/datastore/src/locking_tx_datastore/committed_state.rs index 24df23ae55f..0187aa41648 100644 --- a/crates/datastore/src/locking_tx_datastore/committed_state.rs +++ b/crates/datastore/src/locking_tx_datastore/committed_state.rs @@ -17,15 +17,15 @@ use crate::{ ST_CONSTRAINT_ID, ST_CONSTRAINT_IDX, ST_CONSTRAINT_NAME, ST_INDEX_ID, ST_INDEX_IDX, ST_INDEX_NAME, ST_MODULE_ID, ST_MODULE_IDX, ST_ROW_LEVEL_SECURITY_ID, ST_ROW_LEVEL_SECURITY_IDX, ST_SCHEDULED_ID, ST_SCHEDULED_IDX, ST_SEQUENCE_ID, ST_SEQUENCE_IDX, ST_SEQUENCE_NAME, ST_TABLE_ID, ST_TABLE_IDX, ST_VAR_ID, - ST_VAR_IDX, ST_VIEW_ARG_ID, ST_VIEW_ARG_IDX, + ST_VAR_IDX, }, traits::TxData, }; use crate::{ locking_tx_datastore::mut_tx::ReadSet, system_tables::{ - ST_CONNECTION_CREDENTIALS_ID, ST_CONNECTION_CREDENTIALS_IDX, ST_VIEW_CLIENT_ID, ST_VIEW_CLIENT_IDX, - ST_VIEW_COLUMN_ID, ST_VIEW_COLUMN_IDX, ST_VIEW_ID, ST_VIEW_IDX, ST_VIEW_PARAM_ID, ST_VIEW_PARAM_IDX, + ST_CONNECTION_CREDENTIALS_ID, ST_CONNECTION_CREDENTIALS_IDX, ST_VIEW_COLUMN_ID, ST_VIEW_COLUMN_IDX, ST_VIEW_ID, + ST_VIEW_IDX, ST_VIEW_PARAM_ID, ST_VIEW_PARAM_IDX, }, }; use anyhow::anyhow; @@ -304,8 +304,6 @@ impl CommittedState { self.create_table(ST_VIEW_ID, schemas[ST_VIEW_IDX].clone()); self.create_table(ST_VIEW_PARAM_ID, schemas[ST_VIEW_PARAM_IDX].clone()); self.create_table(ST_VIEW_COLUMN_ID, schemas[ST_VIEW_COLUMN_IDX].clone()); - self.create_table(ST_VIEW_CLIENT_ID, schemas[ST_VIEW_CLIENT_IDX].clone()); - self.create_table(ST_VIEW_ARG_ID, schemas[ST_VIEW_ARG_IDX].clone()); // Insert the sequences into `st_sequences` let (st_sequences, blob_store, pool) = diff --git a/crates/datastore/src/locking_tx_datastore/datastore.rs b/crates/datastore/src/locking_tx_datastore/datastore.rs index 570eb2d0023..8393f9d9719 100644 --- a/crates/datastore/src/locking_tx_datastore/datastore.rs +++ b/crates/datastore/src/locking_tx_datastore/datastore.rs @@ -1252,13 +1252,12 @@ mod tests { use crate::system_tables::{ system_tables, StColumnRow, StConnectionCredentialsFields, StConstraintData, StConstraintFields, StConstraintRow, StIndexAlgorithm, StIndexFields, StIndexRow, StRowLevelSecurityFields, StScheduledFields, - StSequenceFields, StSequenceRow, StTableRow, StVarFields, StViewArgFields, StViewFields, ST_CLIENT_NAME, - ST_COLUMN_ID, ST_COLUMN_NAME, ST_CONNECTION_CREDENTIALS_ID, ST_CONNECTION_CREDENTIALS_NAME, ST_CONSTRAINT_ID, + StSequenceFields, StSequenceRow, StTableRow, StVarFields, StViewFields, ST_CLIENT_NAME, ST_COLUMN_ID, + ST_COLUMN_NAME, ST_CONNECTION_CREDENTIALS_ID, ST_CONNECTION_CREDENTIALS_NAME, ST_CONSTRAINT_ID, ST_CONSTRAINT_NAME, ST_INDEX_ID, ST_INDEX_NAME, ST_MODULE_NAME, ST_RESERVED_SEQUENCE_RANGE, ST_ROW_LEVEL_SECURITY_ID, ST_ROW_LEVEL_SECURITY_NAME, ST_SCHEDULED_ID, ST_SCHEDULED_NAME, ST_SEQUENCE_ID, - ST_SEQUENCE_NAME, ST_TABLE_NAME, ST_VAR_ID, ST_VAR_NAME, ST_VIEW_ARG_ID, ST_VIEW_ARG_NAME, ST_VIEW_CLIENT_ID, - ST_VIEW_CLIENT_NAME, ST_VIEW_COLUMN_ID, ST_VIEW_COLUMN_NAME, ST_VIEW_ID, ST_VIEW_NAME, ST_VIEW_PARAM_ID, - ST_VIEW_PARAM_NAME, + ST_SEQUENCE_NAME, ST_TABLE_NAME, ST_VAR_ID, ST_VAR_NAME, ST_VIEW_COLUMN_ID, ST_VIEW_COLUMN_NAME, ST_VIEW_ID, + ST_VIEW_NAME, ST_VIEW_PARAM_ID, ST_VIEW_PARAM_NAME, }; use crate::traits::{IsolationLevel, MutTx}; use crate::Result; @@ -1714,8 +1713,6 @@ mod tests { TableRow { id: ST_VIEW_ID.into(), name: ST_VIEW_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: Some(StViewFields::ViewId.into()) }, TableRow { id: ST_VIEW_PARAM_ID.into(), name: ST_VIEW_PARAM_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: None }, TableRow { id: ST_VIEW_COLUMN_ID.into(), name: ST_VIEW_COLUMN_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: None }, - TableRow { id: ST_VIEW_CLIENT_ID.into(), name: ST_VIEW_CLIENT_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: None }, - TableRow { id: ST_VIEW_ARG_ID.into(), name: ST_VIEW_ARG_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: Some(StViewArgFields::Id.into()) }, ])); #[rustfmt::skip] @@ -1791,14 +1788,6 @@ mod tests { ColRow { table: ST_VIEW_COLUMN_ID.into(), pos: 1, name: "col_pos", ty: ColId::get_type() }, ColRow { table: ST_VIEW_COLUMN_ID.into(), pos: 2, name: "col_name", ty: AlgebraicType::String }, ColRow { table: ST_VIEW_COLUMN_ID.into(), pos: 3, name: "col_type", ty: AlgebraicType::bytes() }, - - ColRow { table: ST_VIEW_CLIENT_ID.into(), pos: 0, name: "view_id", ty: ViewId::get_type() }, - ColRow { table: ST_VIEW_CLIENT_ID.into(), pos: 1, name: "arg_id", ty: AlgebraicType::U64 }, - ColRow { table: ST_VIEW_CLIENT_ID.into(), pos: 2, name: "identity", ty: AlgebraicType::U256 }, - ColRow { table: ST_VIEW_CLIENT_ID.into(), pos: 3, name: "connection_id", ty: AlgebraicType::U128 }, - - ColRow { table: ST_VIEW_ARG_ID.into(), pos: 0, name: "id", ty: AlgebraicType::U64 }, - ColRow { table: ST_VIEW_ARG_ID.into(), pos: 1, name: "bytes", ty: AlgebraicType::bytes() }, ])); #[rustfmt::skip] assert_eq!(query.scan_st_indexes()?, map_array([ @@ -1819,10 +1808,6 @@ mod tests { IndexRow { id: 15, table: ST_VIEW_ID.into(), col: col(1), name: "st_view_view_name_idx_btree", }, IndexRow { id: 16, table: ST_VIEW_PARAM_ID.into(), col: col_list![0, 1], name: "st_view_param_view_id_param_pos_idx_btree", }, IndexRow { id: 17, table: ST_VIEW_COLUMN_ID.into(), col: col_list![0, 1], name: "st_view_column_view_id_col_pos_idx_btree", }, - IndexRow { id: 18, table: ST_VIEW_CLIENT_ID.into(), col: col_list![0, 1], name: "st_view_client_view_id_arg_id_idx_btree", }, - IndexRow { id: 19, table: ST_VIEW_CLIENT_ID.into(), col: col_list![2, 3], name: "st_view_client_identity_connection_id_idx_btree", }, - IndexRow { id: 20, table: ST_VIEW_ARG_ID.into(), col: col(0), name: "st_view_arg_id_idx_btree", }, - IndexRow { id: 21, table: ST_VIEW_ARG_ID.into(), col: col(1), name: "st_view_arg_bytes_idx_btree", }, ])); let start = ST_RESERVED_SEQUENCE_RANGE as i128 + 1; #[rustfmt::skip] @@ -1834,7 +1819,6 @@ mod tests { SequenceRow { id: 3, table: ST_CONSTRAINT_ID.into(), col_pos: 0, name: "st_constraint_constraint_id_seq", start }, SequenceRow { id: 4, table: ST_SCHEDULED_ID.into(), col_pos: 0, name: "st_scheduled_schedule_id_seq", start }, SequenceRow { id: 6, table: ST_VIEW_ID.into(), col_pos: 0, name: "st_view_view_id_seq", start }, - SequenceRow { id: 7, table: ST_VIEW_ARG_ID.into(), col_pos: 0, name: "st_view_arg_id_seq", start }, ], |row| StSequenceRow { allocated: start - 1, @@ -1859,8 +1843,6 @@ mod tests { ConstraintRow { constraint_id: 14, table_id: ST_VIEW_ID.into(), unique_columns: col(1), constraint_name: "st_view_view_name_key", }, ConstraintRow { constraint_id: 15, table_id: ST_VIEW_PARAM_ID.into(), unique_columns: col_list![0, 1], constraint_name: "st_view_param_view_id_param_pos_key", }, ConstraintRow { constraint_id: 16, table_id: ST_VIEW_COLUMN_ID.into(), unique_columns: col_list![0, 1], constraint_name: "st_view_column_view_id_col_pos_key", }, - ConstraintRow { constraint_id: 17, table_id: ST_VIEW_ARG_ID.into(), unique_columns: col(0), constraint_name: "st_view_arg_id_key", }, - ConstraintRow { constraint_id: 18, table_id: ST_VIEW_ARG_ID.into(), unique_columns: col(1), constraint_name: "st_view_arg_bytes_key", }, ])); // Verify we get back the tables correctly with the proper ids... @@ -2281,10 +2263,6 @@ mod tests { IndexRow { id: 15, table: ST_VIEW_ID.into(), col: col(1), name: "st_view_view_name_idx_btree", }, IndexRow { id: 16, table: ST_VIEW_PARAM_ID.into(), col: col_list![0, 1], name: "st_view_param_view_id_param_pos_idx_btree", }, IndexRow { id: 17, table: ST_VIEW_COLUMN_ID.into(), col: col_list![0, 1], name: "st_view_column_view_id_col_pos_idx_btree", }, - IndexRow { id: 18, table: ST_VIEW_CLIENT_ID.into(), col: col_list![0, 1], name: "st_view_client_view_id_arg_id_idx_btree", }, - IndexRow { id: 19, table: ST_VIEW_CLIENT_ID.into(), col: col_list![2, 3], name: "st_view_client_identity_connection_id_idx_btree", }, - IndexRow { id: 20, table: ST_VIEW_ARG_ID.into(), col: col(0), name: "st_view_arg_id_idx_btree", }, - IndexRow { id: 21, table: ST_VIEW_ARG_ID.into(), col: col(1), name: "st_view_arg_bytes_idx_btree", }, IndexRow { id: seq_start, table: FIRST_NON_SYSTEM_ID, col: col(0), name: "Foo_id_idx_btree", }, IndexRow { id: seq_start + 1, table: FIRST_NON_SYSTEM_ID, col: col(1), name: "Foo_name_idx_btree", }, IndexRow { id: seq_start + 2, table: FIRST_NON_SYSTEM_ID, col: col(2), name: "Foo_age_idx_btree", }, diff --git a/crates/datastore/src/system_tables.rs b/crates/datastore/src/system_tables.rs index 503ef4d9576..7f33051469e 100644 --- a/crates/datastore/src/system_tables.rs +++ b/crates/datastore/src/system_tables.rs @@ -71,10 +71,6 @@ pub const ST_VIEW_ID: TableId = TableId(12); pub const ST_VIEW_PARAM_ID: TableId = TableId(13); /// The static ID of the table that tracks view columns pub const ST_VIEW_COLUMN_ID: TableId = TableId(14); -/// The static ID of the table that tracks the clients subscribed to each view -pub const ST_VIEW_CLIENT_ID: TableId = TableId(15); -/// The static ID of the table that tracks view arguments -pub const ST_VIEW_ARG_ID: TableId = TableId(16); pub(crate) const ST_CONNECTION_CREDENTIALS_NAME: &str = "st_connection_credentials"; pub const ST_TABLE_NAME: &str = "st_table"; @@ -90,8 +86,6 @@ pub(crate) const ST_ROW_LEVEL_SECURITY_NAME: &str = "st_row_level_security"; pub(crate) const ST_VIEW_NAME: &str = "st_view"; pub(crate) const ST_VIEW_PARAM_NAME: &str = "st_view_param"; pub(crate) const ST_VIEW_COLUMN_NAME: &str = "st_view_column"; -pub(crate) const ST_VIEW_CLIENT_NAME: &str = "st_view_client"; -pub(crate) const ST_VIEW_ARG_NAME: &str = "st_view_arg"; /// Reserved range of sequence values used for system tables. /// /// Ids for user-created tables will start at `ST_RESERVED_SEQUENCE_RANGE`. @@ -121,7 +115,7 @@ pub enum SystemTable { st_row_level_security, } -pub fn system_tables() -> [TableSchema; 16] { +pub fn system_tables() -> [TableSchema; 14] { [ // The order should match the `id` of the system table, that start with [ST_TABLE_IDX]. st_table_schema(), @@ -138,8 +132,6 @@ pub fn system_tables() -> [TableSchema; 16] { st_view_schema(), st_view_param_schema(), st_view_column_schema(), - st_view_client_schema(), - st_view_arg_schema(), ] } @@ -182,8 +174,6 @@ pub(crate) const ST_CONNECTION_CREDENTIALS_IDX: usize = 10; pub(crate) const ST_VIEW_IDX: usize = 11; pub(crate) const ST_VIEW_PARAM_IDX: usize = 12; pub(crate) const ST_VIEW_COLUMN_IDX: usize = 13; -pub(crate) const ST_VIEW_CLIENT_IDX: usize = 14; -pub(crate) const ST_VIEW_ARG_IDX: usize = 15; macro_rules! st_fields_enum { ($(#[$attr:meta])* enum $ty_name:ident { $($name:expr, $var:ident = $discr:expr,)* }) => { @@ -250,18 +240,6 @@ st_fields_enum!(enum StViewColumnFields { "col_type", ColType = 3, }); // WARNING: For a stable schema, don't change the field names and discriminants. -st_fields_enum!(enum StViewClientFields { - "view_id", ViewId = 0, - "arg_id", ArgId = 1, - "identity", Identity = 2, - "connection_id", ConnectionId = 3, -}); -// WARNING: For a stable schema, don't change the field names and discriminants. -st_fields_enum!(enum StViewArgFields { - "id", Id = 0, - "bytes", Bytes = 1, -}); -// WARNING: For a stable schema, don't change the field names and discriminants. st_fields_enum!(enum StViewParamFields { "view_id", ViewId = 0, "param_pos", ParamPos = 1, @@ -398,25 +376,6 @@ fn system_module_def() -> ModuleDef { .with_unique_constraint(st_view_param_unique_cols) .with_index_no_accessor_name(btree(st_view_param_unique_cols)); - let st_view_client_type = builder.add_type::(); - builder - .build_table( - ST_VIEW_CLIENT_NAME, - *st_view_client_type.as_ref().expect("should be ref"), - ) - .with_type(TableType::System) - .with_index_no_accessor_name(btree([StViewClientFields::ViewId, StViewClientFields::ArgId])) - .with_index_no_accessor_name(btree([StViewClientFields::Identity, StViewClientFields::ConnectionId])); - - let st_view_arg_type = builder.add_type::(); - builder - .build_table(ST_VIEW_ARG_NAME, *st_view_arg_type.as_ref().expect("should be ref")) - .with_type(TableType::System) - .with_auto_inc_primary_key(StViewArgFields::Id) - .with_index_no_accessor_name(btree(StViewArgFields::Id)) - .with_unique_constraint(StViewArgFields::Bytes) - .with_index_no_accessor_name(btree(StViewArgFields::Bytes)); - let st_index_type = builder.add_type::(); builder .build_table(ST_INDEX_NAME, *st_index_type.as_ref().expect("should be ref")) @@ -516,8 +475,6 @@ fn system_module_def() -> ModuleDef { validate_system_table::(&result, ST_VIEW_NAME); validate_system_table::(&result, ST_VIEW_PARAM_NAME); validate_system_table::(&result, ST_VIEW_COLUMN_NAME); - validate_system_table::(&result, ST_VIEW_CLIENT_NAME); - validate_system_table::(&result, ST_VIEW_ARG_NAME); result } @@ -557,8 +514,6 @@ lazy_static::lazy_static! { m.insert("st_view_view_name_key", ConstraintId(14)); m.insert("st_view_param_view_id_param_pos_key", ConstraintId(15)); m.insert("st_view_column_view_id_col_pos_key", ConstraintId(16)); - m.insert("st_view_arg_id_key", ConstraintId(17)); - m.insert("st_view_arg_bytes_key", ConstraintId(18)); m }; } @@ -585,10 +540,6 @@ lazy_static::lazy_static! { m.insert("st_view_view_name_idx_btree", IndexId(15)); m.insert("st_view_param_view_id_param_pos_idx_btree", IndexId(16)); m.insert("st_view_column_view_id_col_pos_idx_btree", IndexId(17)); - m.insert("st_view_client_view_id_arg_id_idx_btree", IndexId(18)); - m.insert("st_view_client_identity_connection_id_idx_btree", IndexId(19)); - m.insert("st_view_arg_id_idx_btree", IndexId(20)); - m.insert("st_view_arg_bytes_idx_btree", IndexId(21)); m }; } @@ -604,7 +555,6 @@ lazy_static::lazy_static! { m.insert("st_scheduled_schedule_id_seq", SequenceId(4)); m.insert("st_sequence_sequence_id_seq", SequenceId(5)); m.insert("st_view_view_id_seq", SequenceId(6)); - m.insert("st_view_arg_id_seq", SequenceId(7)); m }; } @@ -717,14 +667,6 @@ pub fn st_view_column_schema() -> TableSchema { st_schema(ST_VIEW_COLUMN_NAME, ST_VIEW_COLUMN_ID) } -pub fn st_view_client_schema() -> TableSchema { - st_schema(ST_VIEW_CLIENT_NAME, ST_VIEW_CLIENT_ID) -} - -pub fn st_view_arg_schema() -> TableSchema { - st_schema(ST_VIEW_ARG_NAME, ST_VIEW_ARG_ID) -} - /// If `table_id` refers to a known system table, return its schema. /// /// Used when restoring from a snapshot; system tables are reinstantiated with this schema, @@ -747,8 +689,6 @@ pub(crate) fn system_table_schema(table_id: TableId) -> Option { ST_VIEW_ID => Some(st_view_schema()), ST_VIEW_PARAM_ID => Some(st_view_param_schema()), ST_VIEW_COLUMN_ID => Some(st_view_column_schema()), - ST_VIEW_CLIENT_ID => Some(st_view_client_schema()), - ST_VIEW_ARG_ID => Some(st_view_arg_schema()), _ => None, } } @@ -923,32 +863,6 @@ pub struct StViewParamRow { pub param_type: AlgebraicTypeViaBytes, } -/// System table [ST_VIEW_CLIENT_NAME] -/// -/// | view_id | arg_id | identity | connection_id | -/// |---------|--------|----------|---------------| -/// | 1 | 2 | 0x... | 0x... | -#[derive(Debug, Clone, Eq, PartialEq, SpacetimeType)] -#[sats(crate = spacetimedb_lib)] -pub struct StViewClientRow { - pub view_id: ViewId, - pub arg_id: u64, - pub identity: IdentityViaU256, - pub connection_id: ConnectionIdViaU128, -} - -/// System table [ST_VIEW_ARG_NAME] -/// -/// | id | bytes | -/// |----|---------| -/// | 1 | | -#[derive(Debug, Clone, Eq, PartialEq, SpacetimeType)] -#[sats(crate = spacetimedb_lib)] -pub struct StViewArgRow { - pub id: u64, - pub bytes: Box<[u8]>, -} - /// System Table [ST_INDEX_NAME] /// /// | index_id | table_id | index_name | index_algorithm | diff --git a/crates/schema/src/schema.rs b/crates/schema/src/schema.rs index 65a8813be72..c05104431b6 100644 --- a/crates/schema/src/schema.rs +++ b/crates/schema/src/schema.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use crate::def::{ ColumnDef, ConstraintData, ConstraintDef, IndexAlgorithm, IndexDef, ModuleDef, ModuleDefLookup, ScheduleDef, - SequenceDef, TableDef, UniqueConstraintData, ViewColumnDef, ViewDef, + SequenceDef, TableDef, UniqueConstraintData, ViewColumnDef, ViewDef, ViewParamDef, }; use crate::identifier::Identifier; @@ -607,51 +607,63 @@ impl TableSchema { /// /// my_view: /// - /// | sender | arg_id | a | b | - /// |----------|--------|-----|-----| - /// | Identity | 1 | u32 | u32 | + /// | sender | x | y | a | b | + /// |----------|-----|-----|-----|-----| + /// | Identity | u32 | u32 | u32 | u32 | /// /// my_anonymous_view: /// - /// | arg_id | a | b | - /// |--------|-----|-----| - /// | 1 | u32 | u32 | - /// - /// Note, `arg_id` is a foreign key into `st_view_arg`. + /// | x | y | a | b | + /// |-----|-----|-----|-----| + /// | u32 | u32 | u32 | u32 | pub fn from_view_def(module_def: &ModuleDef, view_def: &ViewDef) -> Self { module_def.expect_contains(view_def); let ViewDef { name, + is_anonymous, is_public, + params: _, + params_for_generate: _, + return_type: _, + return_type_for_generate: _, return_columns, - .. + param_columns, } = view_def; - let n = return_columns.len() + 2; + let num_args = param_columns.len(); + let num_cols = return_columns.len(); + let n = num_args + num_cols + if *is_anonymous { 0 } else { 1 }; + let mut columns = Vec::with_capacity(n); - columns.push(ColumnSchema { - table_id: TableId::SENTINEL, - col_pos: ColId(0), - col_name: "sender".into(), - col_type: AlgebraicType::option(AlgebraicType::identity()), - }); + if !is_anonymous { + columns.push(ColumnSchema { + table_id: TableId::SENTINEL, + col_pos: ColId(0), + col_name: "sender".into(), + col_type: AlgebraicType::identity(), + }); + } - columns.push(ColumnSchema { - table_id: TableId::SENTINEL, - col_pos: ColId(1), - col_name: "arg_id".into(), - col_type: AlgebraicType::U64, - }); + let n = columns.len(); + + let param_iter = param_columns + .iter() + .map(|def| ColumnSchema::from_view_param_def(module_def, def)); + + let column_iter = return_columns + .iter() + .map(|def| ColumnSchema::from_view_column_def(module_def, def)); columns.extend( - return_columns - .iter() - .map(|def| ColumnSchema::from_view_column_def(module_def, def)) + param_iter + .chain(column_iter) .enumerate() - .map(|(i, schema)| (ColId::from(i + 2), schema)) - .map(|(col_pos, schema)| ColumnSchema { col_pos, ..schema }), + .map(|(i, schema)| ColumnSchema { + col_pos: (n + i).into(), + ..schema + }), ); let table_access = if *is_public { @@ -879,6 +891,18 @@ impl ColumnSchema { col_type, } } + + fn from_view_param_def(module_def: &ModuleDef, def: &ViewParamDef) -> Self { + let col_type = WithTypespace::new(module_def.typespace(), &def.ty) + .resolve_refs() + .expect("validated module should have all types resolve"); + ColumnSchema { + table_id: TableId::SENTINEL, + col_pos: def.col_id, + col_name: (*def.name).into(), + col_type, + } + } } impl Schema for ColumnSchema { From 9203a2b3f5e6813cca5d1ddefdd21d19c786a673 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:29 -0700 Subject: [PATCH 06/16] [release/v1.7.0]: Revert "WASM host execution for procedures (#3498)" This reverts commit 3f1de9e09651bc412de3cb9daf49cc553ebb81e8. --- crates/bindings-sys/src/lib.rs | 27 +- crates/bindings/src/lib.rs | 20 +- crates/client-api-messages/src/websocket.rs | 91 ------ crates/client-api/src/routes/database.rs | 265 ++++++------------ crates/core/src/client/client_connection.rs | 26 +- crates/core/src/client/message_handlers.rs | 23 +- crates/core/src/client/messages.rs | 106 +------ crates/core/src/host/host_controller.rs | 9 +- crates/core/src/host/instance_env.rs | 4 +- crates/core/src/host/mod.rs | 8 +- crates/core/src/host/module_host.rs | 161 +---------- crates/core/src/host/v8/mod.rs | 11 +- crates/core/src/host/wasm_common.rs | 10 +- .../src/host/wasm_common/module_host_actor.rs | 163 +---------- .../src/host/wasmtime/wasm_instance_env.rs | 110 ++------ .../core/src/host/wasmtime/wasmtime_module.rs | 203 +------------- .../subscription/module_subscription_actor.rs | 20 +- crates/core/src/util/jobs.rs | 3 - crates/datastore/src/execution_context.rs | 1 - .../tests/standalone_integration_test.rs | 44 --- sdks/rust/src/db_connection.rs | 3 +- .../src/module_bindings/mod.rs | 2 +- .../test-client/src/module_bindings/mod.rs | 2 +- 23 files changed, 178 insertions(+), 1134 deletions(-) diff --git a/crates/bindings-sys/src/lib.rs b/crates/bindings-sys/src/lib.rs index 0e1c786eb39..764b73387c6 100644 --- a/crates/bindings-sys/src/lib.rs +++ b/crates/bindings-sys/src/lib.rs @@ -645,26 +645,6 @@ pub mod raw { pub fn get_jwt(connection_id_ptr: *const u8, bytes_source_id: *mut BytesSource) -> u16; } - #[link(wasm_import_module = "spacetime_10.3")] - extern "C" { - /// Suspends execution of this WASM instance until approximately `wake_at_micros_since_unix_epoch`. - /// - /// Returns immediately if `wake_at_micros_since_unix_epoch` is in the past. - /// - /// Upon resuming, returns the current timestamp as microseconds since the Unix epoch. - /// - /// Not particularly useful, except for testing SpacetimeDB internals related to suspending procedure execution. - /// # Traps - /// - /// Traps if: - /// - /// - The calling WASM instance is holding open a transaction. - /// - The calling WASM instance is not executing a procedure. - // TODO(procedure-sleep-until): remove this - #[cfg(feature = "unstable")] - pub fn procedure_sleep_until(wake_at_micros_since_unix_epoch: i64) -> i64; - } - /// What strategy does the database index use? /// /// See also: @@ -1242,10 +1222,7 @@ impl Drop for RowIter { pub mod procedure { //! Side-effecting or asynchronous operations which only procedures are allowed to perform. #[inline] - #[cfg(feature = "unstable")] - pub fn sleep_until(wake_at_timestamp: i64) -> i64 { - // Safety: Just calling an `extern "C"` function. - // Nothing weird happening here. - unsafe { super::raw::procedure_sleep_until(wake_at_timestamp) } + pub fn sleep_until(_wake_at_timestamp: i64) -> i64 { + todo!("Add `procedure_sleep_until` host function") } } diff --git a/crates/bindings/src/lib.rs b/crates/bindings/src/lib.rs index 03f210f177f..7e63f2ecc05 100644 --- a/crates/bindings/src/lib.rs +++ b/crates/bindings/src/lib.rs @@ -720,9 +720,24 @@ pub use spacetimedb_bindings_macro::reducer; /// Procedures are allowed to perform certain operations which take time. /// During the execution of these operations, the procedure's execution will be suspended, /// allowing other database operations to run in parallel. +/// The simplest (and least useful) of these operators is [`ProcedureContext::sleep_until`]. /// /// Procedures must not hold open a transaction while performing a blocking operation. -// TODO(procedure-http): add example with an HTTP request. +/// +/// ```no_run +/// # use std::time::Duration; +/// # use spacetimedb::{procedure, ProcedureContext}; +/// #[procedure] +/// fn sleep_one_second(ctx: &mut ProcedureContext) { +/// let prev_time = ctx.timestamp; +/// let target = prev_time + Duration::from_secs(1); +/// ctx.sleep_until(target); +/// let new_time = ctx.timestamp; +/// let actual_delta = new_time.duration_since(prev_time).unwrap(); +/// log::info!("Slept from {prev_time} to {new_time}, a total of {actual_delta:?}"); +/// } +/// ``` +// TODO(procedure-http): replace this example with an HTTP request. // TODO(procedure-transaction): document obtaining and using a transaction within a procedure. /// /// # Scheduled procedures @@ -1073,8 +1088,7 @@ impl ProcedureContext { /// log::info!("Slept from {prev_time} to {new_time}, a total of {actual_delta:?}"); /// # } /// ``` - // TODO(procedure-sleep-until): remove this method - #[cfg(feature = "unstable")] + // TODO(procedure-async): mark this method `async`. pub fn sleep_until(&mut self, timestamp: Timestamp) { let new_time = sys::procedure::sleep_until(timestamp.to_micros_since_unix_epoch()); let new_time = Timestamp::from_micros_since_unix_epoch(new_time); diff --git a/crates/client-api-messages/src/websocket.rs b/crates/client-api-messages/src/websocket.rs index 4620f508809..cf24e6e5e87 100644 --- a/crates/client-api-messages/src/websocket.rs +++ b/crates/client-api-messages/src/websocket.rs @@ -105,8 +105,6 @@ pub enum ClientMessage { /// Remove a subscription to a SQL query that was added with SubscribeSingle. Unsubscribe(Unsubscribe), UnsubscribeMulti(UnsubscribeMulti), - /// Request a procedure run. - CallProcedure(CallProcedure), } impl ClientMessage { @@ -129,17 +127,6 @@ impl ClientMessage { ClientMessage::Subscribe(x) => ClientMessage::Subscribe(x), ClientMessage::SubscribeMulti(x) => ClientMessage::SubscribeMulti(x), ClientMessage::UnsubscribeMulti(x) => ClientMessage::UnsubscribeMulti(x), - ClientMessage::CallProcedure(CallProcedure { - procedure, - args, - request_id, - flags, - }) => ClientMessage::CallProcedure(CallProcedure { - procedure, - args: f(args), - request_id, - flags, - }), } } } @@ -305,40 +292,6 @@ pub struct OneOffQuery { pub query_string: Box, } -#[derive(SpacetimeType)] -#[sats(crate = spacetimedb_lib)] -/// Request a procedure run. -/// -/// Parametric over the argument type to enable [`ClientMessage::map_args`]. -pub struct CallProcedure { - /// The name of the procedure to call. - pub procedure: Box, - /// The arguments to the procedure. - /// - /// In the wire format, this will be a [`Bytes`], BSATN or JSON encoded according to the reducer's argument schema - /// and the enclosing message format. - pub args: Args, - /// An identifier for a client request. - /// - /// The server will include the same ID in the response [`ProcedureResult`]. - pub request_id: u32, - /// Reserved space for future extensions. - pub flags: CallProcedureFlags, -} - -#[derive(Clone, Copy, Default, PartialEq, Eq)] -pub enum CallProcedureFlags { - #[default] - Default, -} - -impl_st!([] CallProcedureFlags, AlgebraicType::U8); -impl_serialize!([] CallProcedureFlags, (self, ser) => ser.serialize_u8(*self as u8)); -impl_deserialize!([] CallProcedureFlags, de => match de.deserialize_u8()? { - 0 => Ok(Self::Default), - x => Err(D::Error::custom(format_args!("invalid call procedure flag {x}"))), -}); - /// The tag recognized by the host and SDKs to mean no compression of a [`ServerMessage`]. pub const SERVER_MSG_COMPRESSION_TAG_NONE: u8 = 0; @@ -373,8 +326,6 @@ pub enum ServerMessage { SubscribeMultiApplied(SubscribeMultiApplied), /// Sent in response to an `UnsubscribeMulti` message. This contains the matching rows. UnsubscribeMultiApplied(UnsubscribeMultiApplied), - /// Sent in response to a [`CallProcedure`] message. This contains the return value. - ProcedureResult(ProcedureResult), } /// The matching rows of a subscription query. @@ -754,48 +705,6 @@ pub struct OneOffTable { pub rows: F::List, } -/// The result of running a procedure, -/// including the return value of the procedure on success. -/// -/// Sent in response to a [`CallProcedure`] message. -#[derive(SpacetimeType, Debug)] -#[sats(crate = spacetimedb_lib)] -pub struct ProcedureResult { - /// The status of the procedure run. - /// - /// Contains the return value if successful, or the error message if not. - pub status: ProcedureStatus, - /// The time when the reducer started. - /// - /// Note that [`Timestamp`] serializes as `i64` nanoseconds since the Unix epoch. - pub timestamp: Timestamp, - /// The time the procedure took to run. - pub total_host_execution_duration: TimeDuration, - /// The same same client-provided identifier as in the original [`ProcedureCall`] request. - /// - /// Clients use this to correlate the response with the original request. - pub request_id: u32, -} - -/// The status of a procedure call, -/// including the return value on success. -#[derive(SpacetimeType, Debug)] -#[sats(crate = spacetimedb_lib)] -pub enum ProcedureStatus { - /// The procedure ran and returned the enclosed value. - /// - /// All user error handling happens within here; - /// the returned value may be a `Result` or `Option`, - /// or any other type to which the user may ascribe arbitrary meaning. - Returned(F::Single), - /// The reducer was interrupted due to insufficient energy/funds. - /// - /// The procedure may have performed some observable side effects before being interrupted. - OutOfEnergy, - /// The call failed in the host, e.g. due to a type error or unknown procedure name. - InternalError(String), -} - /// Used whenever different formats need to coexist. #[derive(Debug, Clone)] pub enum FormatSwitch { diff --git a/crates/client-api/src/routes/database.rs b/crates/client-api/src/routes/database.rs index 6fecde08bbd..7be200d5c26 100644 --- a/crates/client-api/src/routes/database.rs +++ b/crates/client-api/src/routes/database.rs @@ -8,7 +8,7 @@ use crate::auth::{ }; use crate::routes::subscribe::generate_random_connection_id; pub use crate::util::{ByteStringBody, NameOrIdentity}; -use crate::{log_and_500, ControlStateDelegate, DatabaseDef, Host, NodeDelegate}; +use crate::{log_and_500, ControlStateDelegate, DatabaseDef, NodeDelegate}; use axum::body::{Body, Bytes}; use axum::extract::{Path, Query, State}; use axum::response::{ErrorResponse, IntoResponse}; @@ -20,10 +20,10 @@ use http::StatusCode; use serde::Deserialize; use spacetimedb::database_logger::DatabaseLogger; use spacetimedb::host::module_host::ClientConnectedError; +use spacetimedb::host::ReducerCallError; +use spacetimedb::host::ReducerOutcome; use spacetimedb::host::UpdateDatabaseResult; use spacetimedb::host::{FunctionArgs, MigratePlanResult}; -use spacetimedb::host::{ModuleHost, ReducerOutcome}; -use spacetimedb::host::{ProcedureCallError, ReducerCallError}; use spacetimedb::identity::Identity; use spacetimedb::messages::control_db::{Database, HostType}; use spacetimedb_client_api_messages::name::{ @@ -31,7 +31,7 @@ use spacetimedb_client_api_messages::name::{ }; use spacetimedb_lib::db::raw_def::v9::RawModuleDefV9; use spacetimedb_lib::identity::AuthCtx; -use spacetimedb_lib::{sats, AlgebraicValue, ProductValue, Timestamp}; +use spacetimedb_lib::{sats, ProductValue, Timestamp}; use spacetimedb_schema::auto_migrate::{ MigrationPolicy as SchemaMigrationPolicy, MigrationToken, PrettyPrintStyle as AutoMigratePrettyPrintStyle, }; @@ -56,23 +56,62 @@ pub async fn call( TypedHeader(content_type): TypedHeader, ByteStringBody(body): ByteStringBody, ) -> axum::response::Result { - assert_content_type_json(content_type)?; - + if content_type != headers::ContentType::json() { + return Err(axum::extract::rejection::MissingJsonContentType::default().into()); + } let caller_identity = auth.claims.identity; let args = FunctionArgs::Json(body); + let db_identity = name_or_identity.resolve(&worker_ctx).await?; + let database = worker_ctx_find_database(&worker_ctx, &db_identity) + .await? + .ok_or_else(|| { + log::error!("Could not find database: {}", db_identity.to_hex()); + NO_SUCH_DATABASE + })?; + let identity = database.owner_identity; + + let leader = worker_ctx + .leader(database.id) + .await + .map_err(log_and_500)? + .ok_or(StatusCode::NOT_FOUND)?; + let module = leader.module().await.map_err(log_and_500)?; + // HTTP callers always need a connection ID to provide to connect/disconnect, // so generate one. let connection_id = generate_random_connection_id(); - let (module, Database { owner_identity, .. }) = find_module_and_database(&worker_ctx, name_or_identity).await?; - - module - .call_identity_connected(auth.into(), connection_id) - .await - .map_err(client_connected_error_to_response)?; + match module.call_identity_connected(auth.into(), connection_id).await { + // If `call_identity_connected` returns `Err(Rejected)`, then the `client_connected` reducer errored, + // meaning the connection was refused. Return 403 forbidden. + Err(ClientConnectedError::Rejected(msg)) => return Err((StatusCode::FORBIDDEN, msg).into()), + // If `call_identity_connected` returns `Err(OutOfEnergy)`, + // then, well, the database is out of energy. + // Return 503 service unavailable. + Err(err @ ClientConnectedError::OutOfEnergy) => { + return Err((StatusCode::SERVICE_UNAVAILABLE, err.to_string()).into()) + } + // If `call_identity_connected` returns `Err(ReducerCall)`, + // something went wrong while invoking the `client_connected` reducer. + // I (pgoldman 2025-03-27) am not really sure how this would happen, + // but we returned 404 not found in this case prior to my editing this code, + // so I guess let's keep doing that. + Err(ClientConnectedError::ReducerCall(e)) => { + return Err((StatusCode::NOT_FOUND, format!("{:#}", anyhow::anyhow!(e))).into()) + } + // If `call_identity_connected` returns `Err(DBError)`, + // then the module didn't define `client_connected`, + // but something went wrong when we tried to insert into `st_client`. + // That's weird and scary, so return 500 internal error. + Err(e @ ClientConnectedError::DBError(_)) => { + return Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into()) + } + // If `call_identity_connected` returns `Ok`, then we can actually call the reducer we want. + Ok(()) => (), + } let result = match module .call_reducer(caller_identity, Some(connection_id), None, None, None, &reducer, args) .await @@ -100,14 +139,17 @@ pub async fn call( } }; - module - .call_identity_disconnected(caller_identity, connection_id) - .await - .map_err(client_disconnected_error_to_response)?; + if let Err(e) = module.call_identity_disconnected(caller_identity, connection_id).await { + // If `call_identity_disconnected` errors, something is very wrong: + // it means we tried to delete the `st_client` row but failed. + // Note that `call_identity_disconnected` swallows errors from the `client_disconnected` reducer. + // Slap a 500 on it and pray. + return Err((StatusCode::INTERNAL_SERVER_ERROR, format!("{:#}", anyhow::anyhow!(e))).into()); + } match result { Ok(result) => { - let (status, body) = reducer_outcome_response(&owner_identity, &reducer, result.outcome); + let (status, body) = reducer_outcome_response(&identity, &reducer, result.outcome); Ok(( status, TypedHeader(SpacetimeEnergyUsed(result.energy_used)), @@ -119,15 +161,7 @@ pub async fn call( } } -fn assert_content_type_json(content_type: headers::ContentType) -> axum::response::Result<()> { - if content_type != headers::ContentType::json() { - Err(axum::extract::rejection::MissingJsonContentType::default().into()) - } else { - Ok(()) - } -} - -fn reducer_outcome_response(owner_identity: &Identity, reducer: &str, outcome: ReducerOutcome) -> (StatusCode, String) { +fn reducer_outcome_response(identity: &Identity, reducer: &str, outcome: ReducerOutcome) -> (StatusCode, String) { match outcome { ReducerOutcome::Committed => (StatusCode::OK, "".to_owned()), ReducerOutcome::Failed(errmsg) => { @@ -135,7 +169,7 @@ fn reducer_outcome_response(owner_identity: &Identity, reducer: &str, outcome: R (StatusCode::from_u16(530).unwrap(), errmsg) } ReducerOutcome::BudgetExceeded => { - log::warn!("Node's energy budget exceeded for identity: {owner_identity} while executing {reducer}"); + log::warn!("Node's energy budget exceeded for identity: {identity} while executing {reducer}"); ( StatusCode::PAYMENT_REQUIRED, "Module energy budget exhausted.".to_owned(), @@ -144,69 +178,6 @@ fn reducer_outcome_response(owner_identity: &Identity, reducer: &str, outcome: R } } -fn client_connected_error_to_response(err: ClientConnectedError) -> ErrorResponse { - match err { - // If `call_identity_connected` returns `Err(Rejected)`, then the `client_connected` reducer errored, - // meaning the connection was refused. Return 403 forbidden. - ClientConnectedError::Rejected(msg) => (StatusCode::FORBIDDEN, msg).into(), - // If `call_identity_connected` returns `Err(OutOfEnergy)`, - // then, well, the database is out of energy. - // Return 503 service unavailable. - ClientConnectedError::OutOfEnergy => (StatusCode::SERVICE_UNAVAILABLE, err.to_string()).into(), - // If `call_identity_connected` returns `Err(ReducerCall)`, - // something went wrong while invoking the `client_connected` reducer. - // I (pgoldman 2025-03-27) am not really sure how this would happen, - // but we returned 404 not found in this case prior to my editing this code, - // so I guess let's keep doing that. - ClientConnectedError::ReducerCall(e) => (StatusCode::NOT_FOUND, format!("{:#}", anyhow::anyhow!(e))).into(), - // If `call_identity_connected` returns `Err(DBError)`, - // then the module didn't define `client_connected`, - // but something went wrong when we tried to insert into `st_client`. - // That's weird and scary, so return 500 internal error. - ClientConnectedError::DBError(_) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into(), - } -} - -/// If `call_identity_disconnected` errors, something is very wrong: -/// it means we tried to delete the `st_client` row but failed. -/// -/// Note that `call_identity_disconnected` swallows errors from the `client_disconnected` reducer. -/// Slap a 500 on it and pray. -fn client_disconnected_error_to_response(err: ReducerCallError) -> ErrorResponse { - (StatusCode::INTERNAL_SERVER_ERROR, format!("{:#}", anyhow::anyhow!(err))).into() -} - -async fn find_leader_and_database( - worker_ctx: &S, - name_or_identity: NameOrIdentity, -) -> axum::response::Result<(Host, Database)> { - let db_identity = name_or_identity.resolve(worker_ctx).await?; - let database = worker_ctx_find_database(worker_ctx, &db_identity) - .await? - .ok_or_else(|| { - log::error!("Could not find database: {}", db_identity.to_hex()); - NO_SUCH_DATABASE - })?; - - let leader = worker_ctx - .leader(database.id) - .await - .map_err(log_and_500)? - .ok_or(StatusCode::NOT_FOUND)?; - - Ok((leader, database)) -} - -async fn find_module_and_database( - worker_ctx: &S, - name_or_identity: NameOrIdentity, -) -> axum::response::Result<(ModuleHost, Database)> { - let (leader, database) = find_leader_and_database(worker_ctx, name_or_identity).await?; - let module = leader.module().await.map_err(log_and_500)?; - - Ok((module, database)) -} - #[derive(Debug, derive_more::From)] pub enum DBCallErr { HandlerError(ErrorResponse), @@ -214,93 +185,6 @@ pub enum DBCallErr { InstanceNotScheduled, } -#[derive(Deserialize)] -pub struct ProcedureParams { - name_or_identity: NameOrIdentity, - procedure: String, -} - -async fn procedure( - State(worker_ctx): State, - Extension(auth): Extension, - Path(ProcedureParams { - name_or_identity, - procedure, - }): Path, - TypedHeader(content_type): TypedHeader, - ByteStringBody(body): ByteStringBody, -) -> axum::response::Result { - assert_content_type_json(content_type)?; - - let caller_identity = auth.claims.identity; - - let args = FunctionArgs::Json(body); - - let (module, _) = find_module_and_database(&worker_ctx, name_or_identity).await?; - - // HTTP callers always need a connection ID to provide to connect/disconnect, - // so generate one. - let connection_id = generate_random_connection_id(); - - // Call the database's `client_connected` reducer, if any. - // If it fails or rejects the connection, bail. - module - .call_identity_connected(auth.into(), connection_id) - .await - .map_err(client_connected_error_to_response)?; - - let result = match module - .call_procedure(caller_identity, Some(connection_id), None, &procedure, args) - .await - { - Ok(res) => Ok(res), - Err(e) => { - let status_code = match e { - ProcedureCallError::Args(_) => { - log::debug!("Attempt to call reducer with invalid arguments"); - StatusCode::BAD_REQUEST - } - ProcedureCallError::NoSuchModule(_) => StatusCode::NOT_FOUND, - ProcedureCallError::NoSuchProcedure => { - log::debug!("Attempt to call non-existent procedure {procedure}"); - StatusCode::NOT_FOUND - } - ProcedureCallError::OutOfEnergy => StatusCode::PAYMENT_REQUIRED, - ProcedureCallError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, - }; - log::error!("Error while invoking procedure {e:#}"); - Err((status_code, format!("{:#}", anyhow::anyhow!(e)))) - } - }; - - module - .call_identity_disconnected(caller_identity, connection_id) - .await - .map_err(client_disconnected_error_to_response)?; - - match result { - Ok(result) => { - // Procedures don't assign a special meaning to error returns, unlike reducers, - // as there's no transaction for them to automatically abort. - // Instead, we just pass on their return value with the OK status so long as we successfully invoked the procedure. - let (status, body) = procedure_outcome_response(result.return_val); - Ok(( - status, - TypedHeader(SpacetimeExecutionDurationMicros(result.execution_duration)), - body, - )) - } - Err(e) => Err((e.0, e.1).into()), - } -} - -fn procedure_outcome_response(return_val: AlgebraicValue) -> (StatusCode, axum::response::Response) { - ( - StatusCode::OK, - axum::Json(sats::serde::SerdeWrapper(return_val)).into_response(), - ) -} - #[derive(Deserialize)] pub struct SchemaParams { name_or_identity: NameOrIdentity, @@ -325,7 +209,17 @@ pub async fn schema( where S: ControlStateDelegate + NodeDelegate, { - let (module, _) = find_module_and_database(&worker_ctx, name_or_identity).await?; + let db_identity = name_or_identity.resolve(&worker_ctx).await?; + let database = worker_ctx_find_database(&worker_ctx, &db_identity) + .await? + .ok_or(NO_SUCH_DATABASE)?; + + let leader = worker_ctx + .leader(database.id) + .await + .map_err(log_and_500)? + .ok_or(StatusCode::NOT_FOUND)?; + let module = leader.module().await.map_err(log_and_500)?; let module_def = &module.info.module_def; let response_json = match version { @@ -513,11 +407,20 @@ where // Anyone is authorized to execute SQL queries. The SQL engine will determine // which queries this identity is allowed to execute against the database. - let (host, database) = find_leader_and_database(&worker_ctx, name_or_identity).await?; + let db_identity = name_or_identity.resolve(&worker_ctx).await?; + let database = worker_ctx_find_database(&worker_ctx, &db_identity) + .await? + .ok_or(NO_SUCH_DATABASE)?; let auth = AuthCtx::new(database.owner_identity, caller_identity); log::debug!("auth: {auth:?}"); + let host = worker_ctx + .leader(database.id) + .await + .map_err(log_and_500)? + .ok_or(StatusCode::NOT_FOUND)?; + host.exec_sql(auth, database, confirmed, sql).await } @@ -1037,8 +940,6 @@ pub struct DatabaseRoutes { pub subscribe_get: MethodRouter, /// POST: /database/:name_or_identity/call/:reducer pub call_reducer_post: MethodRouter, - /// POST: /database/:name_or_identity/procedure/:reducer - pub call_procedure_post: MethodRouter, /// GET: /database/:name_or_identity/schema pub schema_get: MethodRouter, /// GET: /database/:name_or_identity/logs @@ -1068,7 +969,6 @@ where identity_get: get(get_identity::), subscribe_get: get(handle_websocket::), call_reducer_post: post(call::), - call_procedure_post: post(procedure::), schema_get: get(schema::), logs_get: get(logs::), sql_post: post(sql::), @@ -1093,7 +993,6 @@ where .route("/identity", self.identity_get) .route("/subscribe", self.subscribe_get) .route("/call/:reducer", self.call_reducer_post) - .route("/procedure/:procedure", self.call_procedure_post) .route("/schema", self.schema_get) .route("/logs", self.logs_get) .route("/sql", self.sql_post) diff --git a/crates/core/src/client/client_connection.rs b/crates/core/src/client/client_connection.rs index 7f3b3b24003..ccc63497bc2 100644 --- a/crates/core/src/client/client_connection.rs +++ b/crates/core/src/client/client_connection.rs @@ -7,14 +7,13 @@ use std::sync::Arc; use std::task::{Context, Poll}; use std::time::{Instant, SystemTime}; -use super::messages::{OneOffQueryResponseMessage, ProcedureResultMessage, SerializableMessage}; +use super::messages::{OneOffQueryResponseMessage, SerializableMessage}; use super::{message_handlers, ClientActorId, MessageHandleError}; use crate::db::relational_db::RelationalDB; use crate::error::DBError; use crate::host::module_host::ClientConnectedError; use crate::host::{FunctionArgs, ModuleHost, NoSuchModule, ReducerCallError, ReducerCallResult}; use crate::messages::websocket::Subscribe; -use crate::subscription::module_subscription_manager::BroadcastError; use crate::util::asyncify; use crate::util::prometheus_handle::IntGaugeExt; use crate::worker_metrics::WORKER_METRICS; @@ -835,29 +834,6 @@ impl ClientConnection { .await } - pub async fn call_procedure( - &self, - procedure: &str, - args: FunctionArgs, - request_id: RequestId, - timer: Instant, - ) -> Result<(), BroadcastError> { - let res = self - .module() - .call_procedure( - self.id.identity, - Some(self.id.connection_id), - Some(timer), - procedure, - args, - ) - .await; - - self.module() - .subscriptions() - .send_procedure_message(self.sender(), ProcedureResultMessage::from_result(&res, request_id)) - } - pub async fn subscribe_single( &self, subscription: SubscribeSingle, diff --git a/crates/core/src/client/message_handlers.rs b/crates/core/src/client/message_handlers.rs index 313b87d0281..90829afa9e5 100644 --- a/crates/core/src/client/message_handlers.rs +++ b/crates/core/src/client/message_handlers.rs @@ -6,7 +6,6 @@ use crate::host::{FunctionArgs, ReducerId}; use crate::identity::Identity; use crate::messages::websocket::{CallReducer, ClientMessage, OneOffQuery}; use crate::worker_metrics::WORKER_METRICS; -use spacetimedb_client_api_messages::websocket::CallProcedure; use spacetimedb_datastore::execution_context::WorkloadType; use spacetimedb_lib::de::serde::DeserializeWrapper; use spacetimedb_lib::identity::RequestId; @@ -130,27 +129,9 @@ pub async fn handle(client: &ClientConnection, message: DataMessage, timer: Inst .observe(timer.elapsed().as_secs_f64()); res.map_err(|err| (None, None, err)) } - ClientMessage::CallProcedure(CallProcedure { - ref procedure, - args, - request_id, - flags: _, - }) => { - let res = client.call_procedure(procedure, args, request_id, timer).await; - WORKER_METRICS - .request_round_trip - .with_label_values(&WorkloadType::Procedure, &database_identity, procedure) - .observe(timer.elapsed().as_secs_f64()); - if let Err(e) = res { - log::warn!("Procedure call failed: {e:#}"); - } - // `ClientConnection::call_procedure` handles sending the error message to the client if the call fails, - // so we don't need to return an `Err` here. - Ok(()) - } }; - res.map_err(|(reducer_name, reducer_id, err)| MessageExecutionError { - reducer: reducer_name.cloned(), + res.map_err(|(reducer, reducer_id, err)| MessageExecutionError { + reducer: reducer.cloned(), reducer_id, caller_identity: client.id.identity, caller_connection_id: Some(client.id.connection_id), diff --git a/crates/core/src/client/messages.rs b/crates/core/src/client/messages.rs index eb2ace727e5..67f3b90397b 100644 --- a/crates/core/src/client/messages.rs +++ b/crates/core/src/client/messages.rs @@ -1,6 +1,6 @@ use super::{ClientConfig, DataMessage, Protocol}; -use crate::host::module_host::{EventStatus, ModuleEvent, ProcedureCallError}; -use crate::host::{ArgsTuple, ProcedureCallResult}; +use crate::host::module_host::{EventStatus, ModuleEvent}; +use crate::host::ArgsTuple; use crate::messages::websocket as ws; use crate::subscription::websocket_building::{brotli_compress, decide_compression, gzip_compress}; use bytes::{BufMut, Bytes, BytesMut}; @@ -13,7 +13,7 @@ use spacetimedb_client_api_messages::websocket::{ use spacetimedb_datastore::execution_context::WorkloadType; use spacetimedb_lib::identity::RequestId; use spacetimedb_lib::ser::serde::SerializeWrapper; -use spacetimedb_lib::{AlgebraicValue, ConnectionId, TimeDuration, Timestamp}; +use spacetimedb_lib::{ConnectionId, TimeDuration}; use spacetimedb_primitives::TableId; use spacetimedb_sats::bsatn; use std::sync::Arc; @@ -167,7 +167,6 @@ pub enum SerializableMessage { Subscribe(SubscriptionUpdateMessage), Subscription(SubscriptionMessage), TxUpdate(TransactionUpdateMessage), - ProcedureResult(ProcedureResultMessage), } impl SerializableMessage { @@ -178,7 +177,7 @@ impl SerializableMessage { Self::Subscribe(msg) => Some(msg.num_rows()), Self::Subscription(msg) => Some(msg.num_rows()), Self::TxUpdate(msg) => Some(msg.num_rows()), - Self::Identity(_) | Self::ProcedureResult(_) => None, + Self::Identity(_) => None, } } @@ -195,7 +194,6 @@ impl SerializableMessage { }, Self::TxUpdate(_) => Some(WorkloadType::Update), Self::Identity(_) => None, - Self::ProcedureResult(_) => Some(WorkloadType::Procedure), } } } @@ -210,7 +208,6 @@ impl ToProtocol for SerializableMessage { SerializableMessage::Subscribe(msg) => msg.to_protocol(protocol), SerializableMessage::TxUpdate(msg) => msg.to_protocol(protocol), SerializableMessage::Subscription(msg) => msg.to_protocol(protocol), - SerializableMessage::ProcedureResult(msg) => msg.to_protocol(protocol), } } } @@ -587,98 +584,3 @@ fn convert(msg: OneOffQueryResponseMessage) -> ws::Server total_host_execution_duration: msg.total_host_execution_duration, }) } - -/// Result of a procedure run. -#[derive(Debug)] -pub enum ProcedureStatus { - /// The procedure ran to completion and returned this value. - Returned(AlgebraicValue), - /// The procedure was terminated due to running out of energy. - OutOfEnergy, - /// The procedure failed to run to completion. This string describes the failure. - InternalError(String), -} - -/// Will be sent to the caller of a procedure after that procedure finishes running. -#[derive(Debug)] -pub struct ProcedureResultMessage { - status: ProcedureStatus, - timestamp: Timestamp, - total_host_execution_duration: TimeDuration, - request_id: u32, -} - -impl ProcedureResultMessage { - pub fn from_result(res: &Result, request_id: RequestId) -> Self { - let (status, timestamp, execution_duration) = match res { - Ok(ProcedureCallResult { - return_val, - execution_duration, - start_timestamp, - }) => ( - ProcedureStatus::Returned(return_val.clone()), - *start_timestamp, - TimeDuration::from(*execution_duration), - ), - Err(err) => ( - match err { - ProcedureCallError::OutOfEnergy => ProcedureStatus::OutOfEnergy, - _ => ProcedureStatus::InternalError(format!("{err}")), - }, - Timestamp::UNIX_EPOCH, - TimeDuration::ZERO, - ), - }; - - ProcedureResultMessage { - status, - timestamp, - total_host_execution_duration: execution_duration, - request_id, - } - } -} - -impl ToProtocol for ProcedureResultMessage { - type Encoded = SwitchedServerMessage; - - fn to_protocol(self, protocol: Protocol) -> Self::Encoded { - fn convert( - msg: ProcedureResultMessage, - serialize_value: impl Fn(AlgebraicValue) -> F::Single, - ) -> ws::ServerMessage { - let ProcedureResultMessage { - status, - timestamp, - total_host_execution_duration, - request_id, - } = msg; - let status = match status { - ProcedureStatus::InternalError(msg) => ws::ProcedureStatus::InternalError(msg), - ProcedureStatus::OutOfEnergy => ws::ProcedureStatus::OutOfEnergy, - ProcedureStatus::Returned(val) => ws::ProcedureStatus::Returned(serialize_value(val)), - }; - ws::ServerMessage::ProcedureResult(ws::ProcedureResult { - status, - timestamp, - total_host_execution_duration, - request_id, - }) - } - - // Note that procedure returns are sent only to the caller, not broadcast to all subscribers, - // so we don't have to bother with memoizing the serialization the way we do for reducer args. - match protocol { - Protocol::Binary => FormatSwitch::Bsatn(convert(self, |val| { - bsatn::to_vec(&val) - .expect("Procedure return value failed to serialize to BSATN") - .into() - })), - Protocol::Text => FormatSwitch::Json(convert(self, |val| { - serde_json::to_string(&SerializeWrapper(val)) - .expect("Procedure return value failed to serialize to JSON") - .into() - })), - } - } -} diff --git a/crates/core/src/host/host_controller.rs b/crates/core/src/host/host_controller.rs index f81e5d6b018..cc266b30351 100644 --- a/crates/core/src/host/host_controller.rs +++ b/crates/core/src/host/host_controller.rs @@ -29,7 +29,7 @@ use spacetimedb_datastore::db_metrics::data_size::DATA_SIZE_METRICS; use spacetimedb_datastore::db_metrics::DB_METRICS; use spacetimedb_datastore::traits::Program; use spacetimedb_durability::{self as durability}; -use spacetimedb_lib::{hash_bytes, AlgebraicValue, Identity, Timestamp}; +use spacetimedb_lib::{hash_bytes, Identity}; use spacetimedb_paths::server::{ReplicaDir, ServerDataDir}; use spacetimedb_paths::FromPathUnchecked; use spacetimedb_sats::hash::Hash; @@ -170,13 +170,6 @@ impl From<&EventStatus> for ReducerOutcome { } } -#[derive(Clone, Debug)] -pub struct ProcedureCallResult { - pub return_val: AlgebraicValue, - pub execution_duration: Duration, - pub start_timestamp: Timestamp, -} - impl HostController { pub fn new( data_dir: Arc, diff --git a/crates/core/src/host/instance_env.rs b/crates/core/src/host/instance_env.rs index 76b7c6d8882..b94f700b9a8 100644 --- a/crates/core/src/host/instance_env.rs +++ b/crates/core/src/host/instance_env.rs @@ -182,8 +182,8 @@ impl InstanceEnv { &self.replica_ctx.database.database_identity } - /// Signal to this `InstanceEnv` that a reducer or procedure call is beginning. - pub fn start_funcall(&mut self, ts: Timestamp) { + /// Signal to this `InstanceEnv` that a reducer call is beginning. + pub fn start_reducer(&mut self, ts: Timestamp) { self.start_time = ts; self.view_id = None; } diff --git a/crates/core/src/host/mod.rs b/crates/core/src/host/mod.rs index 8cbcab7b926..d49ae054b27 100644 --- a/crates/core/src/host/mod.rs +++ b/crates/core/src/host/mod.rs @@ -24,10 +24,10 @@ mod wasm_common; pub use disk_storage::DiskStorage; pub use host_controller::{ - extract_schema, ExternalDurability, ExternalStorage, HostController, MigratePlanResult, ProcedureCallResult, - ProgramStorage, ReducerCallResult, ReducerOutcome, + extract_schema, ExternalDurability, ExternalStorage, HostController, MigratePlanResult, ProgramStorage, + ReducerCallResult, ReducerOutcome, }; -pub use module_host::{ModuleHost, NoSuchModule, ProcedureCallError, ReducerCallError, UpdateDatabaseResult}; +pub use module_host::{ModuleHost, NoSuchModule, ReducerCallError, UpdateDatabaseResult}; pub use scheduler::Scheduler; /// Encoded arguments to a database function. @@ -167,6 +167,4 @@ pub enum AbiCall { GetJwt, VolatileNonatomicScheduleImmediate, - - ProcedureSleepUntil, } diff --git a/crates/core/src/host/module_host.rs b/crates/core/src/host/module_host.rs index 1fde862906f..ea00f8a7330 100644 --- a/crates/core/src/host/module_host.rs +++ b/crates/core/src/host/module_host.rs @@ -1,6 +1,5 @@ use super::{ - ArgsTuple, FunctionArgs, InvalidProcedureArguments, InvalidReducerArguments, ProcedureCallResult, - ReducerCallResult, ReducerId, ReducerOutcome, Scheduler, + ArgsTuple, FunctionArgs, InvalidReducerArguments, ReducerCallResult, ReducerId, ReducerOutcome, Scheduler, }; use crate::client::messages::{OneOffQueryResponseMessage, SerializableMessage}; use crate::client::{ClientActorId, ClientConnectionSender}; @@ -47,17 +46,16 @@ use spacetimedb_lib::identity::{AuthCtx, RequestId}; use spacetimedb_lib::metrics::ExecutionMetrics; use spacetimedb_lib::ConnectionId; use spacetimedb_lib::Timestamp; -use spacetimedb_primitives::{ProcedureId, TableId}; +use spacetimedb_primitives::TableId; use spacetimedb_query::compile_subscription; use spacetimedb_sats::ProductValue; use spacetimedb_schema::auto_migrate::{AutoMigrateError, MigrationPolicy}; use spacetimedb_schema::def::deserialize::ArgsSeed; -use spacetimedb_schema::def::{ModuleDef, ProcedureDef, ReducerDef, TableDef, ViewDef}; +use spacetimedb_schema::def::{ModuleDef, ReducerDef, TableDef, ViewDef}; use spacetimedb_schema::schema::{Schema, TableSchema}; use spacetimedb_vm::relation::RelValue; use std::collections::VecDeque; use std::fmt; -use std::future::Future; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; @@ -402,13 +400,6 @@ impl Instance { Instance::Js(inst) => inst.call_reducer(tx, params), } } - - async fn call_procedure(&mut self, params: CallProcedureParams) -> Result { - match self { - Instance::Wasm(inst) => inst.call_procedure(params).await, - Instance::Js(inst) => inst.call_procedure(params).await, - } - } } /// Creates the table for `table_def` in `stdb`. @@ -528,15 +519,6 @@ pub struct CallReducerParams { pub args: ArgsTuple, } -pub struct CallProcedureParams { - pub timestamp: Timestamp, - pub caller_identity: Identity, - pub caller_connection_id: ConnectionId, - pub timer: Option, - pub procedure_id: ProcedureId, - pub args: ArgsTuple, -} - /// Holds a [`Module`] and a set of [`Instance`]s from it, /// and allocates the [`Instance`]s to be used for function calls. /// @@ -690,20 +672,6 @@ pub enum ReducerCallError { LifecycleReducer(Lifecycle), } -#[derive(thiserror::Error, Debug)] -pub enum ProcedureCallError { - #[error(transparent)] - Args(#[from] InvalidProcedureArguments), - #[error(transparent)] - NoSuchModule(#[from] NoSuchModule), - #[error("No such procedure")] - NoSuchProcedure, - #[error("Procedure terminated due to insufficient budget")] - OutOfEnergy, - #[error("The module instance encountered a fatal error: {0}")] - InternalError(String), -} - #[derive(thiserror::Error, Debug)] pub enum InitDatabaseError { #[error(transparent)] @@ -826,37 +794,6 @@ impl ModuleHost { }) } - async fn call_async_with_instance(&self, label: &str, f: Fun) -> Result - where - Fun: (FnOnce(Instance) -> Fut) + Send + 'static, - Fut: Future + Send + 'static, - R: Send + 'static, - { - self.guard_closed()?; - let timer_guard = self.start_call_timer(label); - - scopeguard::defer_on_unwind!({ - log::warn!("procedure {label} panicked"); - (self.on_panic)(); - }); - - // TODO: should we be calling and/or `await`-ing `get_instance` within the below `run_job`? - // Unclear how much overhead this call can have. - let instance = self.instance_manager.lock().await.get_instance().await; - - let (res, instance) = self - .executor - .run_job(async move { - drop(timer_guard); - f(instance).await - }) - .await; - - self.instance_manager.lock().await.return_instance(instance); - - Ok(res) - } - /// Run a function on the JobThread for this module which has access to the module instance. async fn call(&self, label: &str, f: F) -> Result where @@ -1274,8 +1211,13 @@ impl ModuleHost { .await; let log_message = match &res { - Err(ReducerCallError::NoSuchReducer) => Some(no_such_function_log_message("reducer", reducer_name)), - Err(ReducerCallError::Args(_)) => Some(args_error_log_message("reducer", reducer_name)), + Err(ReducerCallError::NoSuchReducer) => Some(format!( + "External attempt to call nonexistent reducer \"{reducer_name}\" failed. Have you run `spacetime generate` recently?" + )), + Err(ReducerCallError::Args(_)) => Some(format!( + "External attempt to call reducer \"{reducer_name}\" failed, invalid arguments.\n\ + This is likely due to a mismatched client schema, have you run `spacetime generate` recently?", + )), _ => None, }; if let Some(log_message) = log_message { @@ -1285,74 +1227,6 @@ impl ModuleHost { res } - pub async fn call_procedure( - &self, - caller_identity: Identity, - caller_connection_id: Option, - timer: Option, - procedure_name: &str, - args: FunctionArgs, - ) -> Result { - let res = async { - let (procedure_id, procedure_def) = self - .info - .module_def - .procedure_full(procedure_name) - .ok_or(ProcedureCallError::NoSuchProcedure)?; - self.call_procedure_inner( - caller_identity, - caller_connection_id, - timer, - procedure_id, - procedure_def, - args, - ) - .await - } - .await; - - let log_message = match &res { - Err(ProcedureCallError::NoSuchProcedure) => Some(no_such_function_log_message("procedure", procedure_name)), - Err(ProcedureCallError::Args(_)) => Some(args_error_log_message("procedure", procedure_name)), - _ => None, - }; - - if let Some(log_message) = log_message { - self.inject_logs(LogLevel::Error, procedure_name, &log_message) - } - - res - } - - async fn call_procedure_inner( - &self, - caller_identity: Identity, - caller_connection_id: Option, - timer: Option, - procedure_id: ProcedureId, - procedure_def: &ProcedureDef, - args: FunctionArgs, - ) -> Result { - let procedure_seed = ArgsSeed(self.info.module_def.typespace().with_type(procedure_def)); - let args = args.into_tuple(procedure_seed).map_err(InvalidProcedureArguments)?; - let caller_connection_id = caller_connection_id.unwrap_or(ConnectionId::ZERO); - - self.call_async_with_instance(&procedure_def.name, async move |mut inst| { - let res = inst - .call_procedure(CallProcedureParams { - timestamp: Timestamp::now(), - caller_identity, - caller_connection_id, - timer, - procedure_id, - args, - }) - .await; - (res, inst) - }) - .await? - } - // Scheduled reducers require a different function here to call their reducer // because their reducer arguments are stored in the database and need to be fetched // within the same transaction as the reducer call. @@ -1438,11 +1312,11 @@ impl ModuleHost { self.module.scheduler().closed().await; } - pub fn inject_logs(&self, log_level: LogLevel, function_name: &str, message: &str) { + pub fn inject_logs(&self, log_level: LogLevel, reducer_name: &str, message: &str) { self.replica_ctx().logger.write( log_level, &Record { - function: Some(function_name), + function: Some(reducer_name), ..Record::injected(message) }, &(), @@ -1618,14 +1492,3 @@ impl WeakModuleHost { }) } } - -fn no_such_function_log_message(function_kind: &str, function_name: &str) -> String { - format!("External attempt to call nonexistent {function_kind} \"{function_name}\" failed. Have you run `spacetime generate` recently?") -} - -fn args_error_log_message(function_kind: &str, function_name: &str) -> String { - format!( - "External attempt to call {function_kind} \"{function_name}\" failed, invalid arguments.\n\ - This is likely due to a mismatched client schema, have you run `spacetime generate` recently?" - ) -} diff --git a/crates/core/src/host/v8/mod.rs b/crates/core/src/host/v8/mod.rs index 73b1ad9d76a..b0e31fd31f1 100644 --- a/crates/core/src/host/v8/mod.rs +++ b/crates/core/src/host/v8/mod.rs @@ -7,7 +7,7 @@ use self::ser::serialize_to_js; use self::string::{str_from_ident, IntoJsString}; use self::syscall::{call_call_reducer, call_describe_module, call_reducer_fun, resolve_sys_module, FnRet}; use super::module_common::{build_common_module_from_raw, run_describer, ModuleCommon}; -use super::module_host::{CallProcedureParams, CallReducerParams, Module, ModuleInfo, ModuleRuntime}; +use super::module_host::{CallReducerParams, Module, ModuleInfo, ModuleRuntime}; use super::UpdateDatabaseResult; use crate::host::instance_env::{ChunkPool, InstanceEnv}; use crate::host::module_host::Instance; @@ -200,7 +200,7 @@ impl JsInstanceEnv { fn start_reducer(&mut self, name: &str, ts: Timestamp) { self.reducer_start = Instant::now(); name.clone_into(&mut self.reducer_name); - self.instance_env.start_funcall(ts); + self.instance_env.start_reducer(ts); } /// Returns the name of the most recent reducer to be run in this environment. @@ -295,13 +295,6 @@ impl JsInstance { response } - - pub async fn call_procedure( - &mut self, - _params: CallProcedureParams, - ) -> Result { - todo!("JS/TS module procedure support") - } } /// A request for the worker in [`spawn_instance_worker`]. diff --git a/crates/core/src/host/wasm_common.rs b/crates/core/src/host/wasm_common.rs index 0fbdec0d17b..b44976e53c3 100644 --- a/crates/core/src/host/wasm_common.rs +++ b/crates/core/src/host/wasm_common.rs @@ -14,8 +14,6 @@ use spacetimedb_table::table::UniqueConstraintViolation; pub const CALL_REDUCER_DUNDER: &str = "__call_reducer__"; -pub const CALL_PROCEDURE_DUNDER: &str = "__call_procedure__"; - pub const DESCRIBE_MODULE_DUNDER: &str = "__describe_module__"; /// functions with this prefix run prior to __setup__, initializing global variables and the like @@ -386,8 +384,8 @@ pub struct AbiRuntimeError { } macro_rules! abi_funcs { - ($link_sync:ident, $link_async:ident) => { - $link_sync! { + ($mac:ident) => { + $mac! { "spacetime_10.0"::table_id_from_name, "spacetime_10.0"::datastore_table_row_count, "spacetime_10.0"::datastore_table_scan_bsatn, @@ -415,10 +413,6 @@ macro_rules! abi_funcs { "spacetime_10.2"::get_jwt, } - - $link_async! { - "spacetime_10.3"::procedure_sleep_until, - } }; } pub(crate) use abi_funcs; diff --git a/crates/core/src/host/wasm_common/module_host_actor.rs b/crates/core/src/host/wasm_common/module_host_actor.rs index 6bf94421f2a..2cdf70335cd 100644 --- a/crates/core/src/host/wasm_common/module_host_actor.rs +++ b/crates/core/src/host/wasm_common/module_host_actor.rs @@ -1,10 +1,6 @@ -use bytes::Bytes; use prometheus::{Histogram, IntCounter, IntGauge}; use spacetimedb_lib::db::raw_def::v9::Lifecycle; -use spacetimedb_lib::de::DeserializeSeed; -use spacetimedb_primitives::ProcedureId; use spacetimedb_schema::auto_migrate::{MigratePlan, MigrationPolicy, MigrationPolicyError}; -use std::future::Future; use std::sync::Arc; use std::time::Duration; use tracing::span::EnteredSpan; @@ -16,12 +12,9 @@ use crate::energy::{EnergyMonitor, ReducerBudget, ReducerFingerprint}; use crate::host::instance_env::InstanceEnv; use crate::host::module_common::{build_common_module_from_raw, ModuleCommon}; use crate::host::module_host::{ - CallProcedureParams, CallReducerParams, DatabaseUpdate, EventStatus, ModuleEvent, ModuleFunctionCall, ModuleInfo, -}; -use crate::host::{ - ArgsTuple, ProcedureCallError, ProcedureCallResult, ReducerCallResult, ReducerId, ReducerOutcome, Scheduler, - UpdateDatabaseResult, + CallReducerParams, DatabaseUpdate, EventStatus, ModuleEvent, ModuleFunctionCall, ModuleInfo, }; +use crate::host::{ArgsTuple, ReducerCallResult, ReducerId, ReducerOutcome, Scheduler, UpdateDatabaseResult}; use crate::identity::Identity; use crate::messages::control_db::HostType; use crate::module_host_context::ModuleCreationContextLimited; @@ -55,7 +48,6 @@ pub trait WasmInstancePre: Send + Sync + 'static { fn instantiate(&self, env: InstanceEnv, func_names: &FuncNames) -> Result; } -#[async_trait::async_trait] pub trait WasmInstance: Send + Sync + 'static { fn extract_descriptions(&mut self) -> Result, DescribeError>; @@ -64,8 +56,6 @@ pub trait WasmInstance: Send + Sync + 'static { fn call_reducer(&mut self, op: ReducerOp<'_>, budget: ReducerBudget) -> ExecuteResult; fn log_traceback(func_type: &str, func: &str, trap: &anyhow::Error); - - async fn call_procedure(&mut self, op: ProcedureOp, budget: ReducerBudget) -> ProcedureExecuteResult; } pub struct EnergyStats { @@ -74,11 +64,6 @@ pub struct EnergyStats { } impl EnergyStats { - pub const ZERO: Self = Self { - budget: ReducerBudget::ZERO, - remaining: ReducerBudget::ZERO, - }; - /// Returns the used energy amount. fn used(&self) -> ReducerBudget { (self.budget.get() - self.remaining.get()).into() @@ -90,17 +75,6 @@ pub struct ExecutionTimings { pub wasm_instance_env_call_times: CallTimes, } -impl ExecutionTimings { - /// Not a `const` because there doesn't seem to be any way to `const` construct an `enum_map::EnumMap`, - /// which `CallTimes` uses. - pub fn zero() -> Self { - Self { - total_duration: Duration::ZERO, - wasm_instance_env_call_times: CallTimes::new(), - } - } -} - /// The result that `__call_reducer__` produces during normal non-trap execution. pub type ReducerResult = Result<(), Box>; @@ -111,15 +85,6 @@ pub struct ExecuteResult { pub call_result: anyhow::Result, } -pub struct ProcedureExecuteResult { - #[allow(unused)] - pub energy: EnergyStats, - #[allow(unused)] - pub timings: ExecutionTimings, - pub memory_allocation: usize, - pub call_result: anyhow::Result, -} - pub struct WasmModuleHostActor { module: T::InstancePre, common: ModuleCommon, @@ -273,24 +238,6 @@ impl WasmModuleInstance { pub fn call_reducer(&mut self, tx: Option, params: CallReducerParams) -> ReducerCallResult { crate::callgrind_flag::invoke_allowing_callgrind(|| self.call_reducer_with_tx(tx, params)) } - - pub async fn call_procedure( - &mut self, - params: CallProcedureParams, - ) -> Result { - let res = self - .common - .call_procedure( - params, - |ty, fun, err| T::log_traceback(ty, fun, err), - |op, budget| self.instance.call_procedure(op, budget), - ) - .await; - if res.is_err() { - self.trapped = true; - } - res - } } impl WasmModuleInstance { @@ -393,101 +340,6 @@ impl InstanceCommon { } } - async fn call_procedure>( - &mut self, - params: CallProcedureParams, - log_traceback: impl FnOnce(&str, &str, &anyhow::Error), - vm_call_procedure: impl FnOnce(ProcedureOp, ReducerBudget) -> F, - ) -> Result { - let CallProcedureParams { - timestamp, - caller_identity, - caller_connection_id, - timer, - procedure_id, - args, - } = params; - - // We've already validated by this point that the procedure exists, - // so it's fine to use the panicking `procedure_by_id`. - let procedure_def = self.info.module_def.procedure_by_id(procedure_id); - let procedure_name: &str = &procedure_def.name; - - // TODO(observability): Add tracing spans, energy, metrics? - // These will require further thinking once we implement procedure suspend/resume, - // and so are not worth doing yet. - - let op = ProcedureOp { - id: procedure_id, - name: procedure_name.into(), - caller_identity, - caller_connection_id, - timestamp, - arg_bytes: args.get_bsatn().clone(), - }; - - let energy_fingerprint = ReducerFingerprint { - module_hash: self.info.module_hash, - module_identity: self.info.owner_identity, - caller_identity, - reducer_name: &procedure_def.name, - }; - - // TODO(procedure-energy): replace with call to separate function `procedure_budget`. - let budget = self.energy_monitor.reducer_budget(&energy_fingerprint); - - let result = vm_call_procedure(op, budget).await; - - let ProcedureExecuteResult { - memory_allocation, - call_result, - // TODO(procedure-energy): Do something with timing and energy. - .. - } = result; - - // TODO(shub): deduplicate with reducer and view logic. - if self.allocated_memory != memory_allocation { - self.metric_wasm_memory_bytes.set(memory_allocation as i64); - self.allocated_memory = memory_allocation; - } - - match call_result { - Err(err) => { - log_traceback("procedure", &procedure_def.name, &err); - - WORKER_METRICS - .wasm_instance_errors - .with_label_values( - &caller_identity, - &self.info.module_hash, - &caller_connection_id, - procedure_name, - ) - .inc(); - - // TODO(procedure-energy): - // if energy.remaining.get() == 0 { - // return Err(ProcedureCallError::OutOfEnergy); - // } else - { - Err(ProcedureCallError::InternalError(format!("{err}"))) - } - } - Ok(return_val) => { - let return_type = &procedure_def.return_type; - let seed = spacetimedb_sats::WithTypespace::new(self.info.module_def.typespace(), return_type); - let return_val = seed - .deserialize(bsatn::Deserializer::new(&mut &return_val[..])) - .map_err(|err| ProcedureCallError::InternalError(format!("{err}")))?; - Ok(ProcedureCallResult { - return_val, - execution_duration: timer.map(|timer| timer.elapsed()).unwrap_or_default(), - start_timestamp: timestamp, - }) - } - } - } - /// Execute a reducer. /// /// If `Some` [`MutTxId`] is supplied, the reducer is called within the @@ -837,14 +689,3 @@ impl From> for execution_context::ReducerContext { } } } - -/// Describes a procedure call in a cheaply shareable way. -#[derive(Clone, Debug)] -pub struct ProcedureOp { - pub id: ProcedureId, - pub name: Box, - pub caller_identity: Identity, - pub caller_connection_id: ConnectionId, - pub timestamp: Timestamp, - pub arg_bytes: Bytes, -} diff --git a/crates/core/src/host/wasmtime/wasm_instance_env.rs b/crates/core/src/host/wasmtime/wasm_instance_env.rs index 7aac129306d..03df1cac2d8 100644 --- a/crates/core/src/host/wasmtime/wasm_instance_env.rs +++ b/crates/core/src/host/wasmtime/wasm_instance_env.rs @@ -11,7 +11,6 @@ use anyhow::Context as _; use spacetimedb_data_structures::map::IntMap; use spacetimedb_lib::{ConnectionId, Timestamp}; use spacetimedb_primitives::{errno, ColId}; -use std::future::Future; use std::num::NonZeroU32; use std::time::Instant; use wasmtime::{AsContext, Caller, StoreContextMut}; @@ -103,8 +102,8 @@ pub(super) struct WasmInstanceEnv { /// Track time spent in module-defined spans. timing_spans: TimingSpanSet, - /// The point in time the last, or current, reducer or procedure call started at. - funcall_start: Instant, + /// The point in time the last reducer call started at. + reducer_start: Instant, /// Track time spent in all wasm instance env calls (aka syscall time). /// @@ -112,8 +111,8 @@ pub(super) struct WasmInstanceEnv { /// to this tracker. call_times: CallTimes, - /// The name of the last, including current, reducer or procedure to be executed by this environment. - funcall_name: String, + /// The last, including current, reducer to be executed by this environment. + reducer_name: String, /// A pool of unused allocated chunks that can be reused. // TODO(Centril): consider using this pool for `console_timer_start` and `bytes_sink_write`. @@ -130,7 +129,7 @@ type RtResult = anyhow::Result; impl WasmInstanceEnv { /// Create a new `WasmEnstanceEnv` from the given `InstanceEnv`. pub fn new(instance_env: InstanceEnv) -> Self { - let funcall_start = Instant::now(); + let reducer_start = Instant::now(); Self { instance_env, mem: None, @@ -139,9 +138,9 @@ impl WasmInstanceEnv { standard_bytes_sink: None, iters: Default::default(), timing_spans: Default::default(), - funcall_start, + reducer_start, call_times: CallTimes::new(), - funcall_name: String::from(""), + reducer_name: String::from(""), chunk_pool: <_>::default(), } } @@ -220,54 +219,48 @@ impl WasmInstanceEnv { self.standard_bytes_sink.take().unwrap_or_default() } - /// Signal to this `WasmInstanceEnv` that a reducer or procedure call is beginning. + /// Signal to this `WasmInstanceEnv` that a reducer call is beginning. /// - /// Returns the handle used by reducers and procedures to read from `args` - /// as well as the handle used to write the reducer error message or procedure return value. - pub fn start_funcall(&mut self, name: &str, args: bytes::Bytes, ts: Timestamp) -> (BytesSourceId, u32) { - // Create the output sink. - // Reducers which fail will write their error message here. - // Procedures will write their result here. + /// Returns the handle used by reducers to read from `args` + /// as well as the handle used to write the error message, if any. + pub fn start_reducer(&mut self, name: &str, args: bytes::Bytes, ts: Timestamp) -> (BytesSourceId, u32) { let errors = self.setup_standard_bytes_sink(); let args = self.create_bytes_source(args).unwrap(); - self.funcall_start = Instant::now(); - name.clone_into(&mut self.funcall_name); - self.instance_env.start_funcall(ts); + self.reducer_start = Instant::now(); + name.clone_into(&mut self.reducer_name); + self.instance_env.start_reducer(ts); (args, errors) } - /// Returns the name of the most recent reducer or procedure to be run in this environment. - pub fn funcall_name(&self) -> &str { - &self.funcall_name + /// Returns the name of the most recent reducer to be run in this environment. + pub fn reducer_name(&self) -> &str { + &self.reducer_name } - /// Returns the name of the most recent reducer or procedure to be run in this environment, - /// or `None` if no reducer or procedure is actively being invoked. + /// Returns the name of the most recent reducer to be run in this environment, + /// or `None` if no reducer is actively being invoked. fn log_record_function(&self) -> Option<&str> { - let function = self.funcall_name(); + let function = self.reducer_name(); (!function.is_empty()).then_some(function) } - /// Returns the start time of the most recent reducer or procedure to be run in this environment. - pub fn funcall_start(&self) -> Instant { - self.funcall_start + /// Returns the name of the most recent reducer to be run in this environment. + pub fn reducer_start(&self) -> Instant { + self.reducer_start } - /// Signal to this `WasmInstanceEnv` that a reducer or procedure call is over. - /// - /// Returns time measurements which can be recorded as metrics, - /// and the errors written by the WASM code to the standard error sink. - /// - /// This resets the call times and clears the arguments source and error sink. - pub fn finish_funcall(&mut self) -> (ExecutionTimings, Vec) { + /// Signal to this `WasmInstanceEnv` that a reducer call is over. + /// This resets all of the state associated to a single reducer call, + /// and returns instrumentation records. + pub fn finish_reducer(&mut self) -> (ExecutionTimings, Vec) { // For the moment, // we only explicitly clear the source/sink buffers and the "syscall" times. // TODO: should we be clearing `iters` and/or `timing_spans`? - let total_duration = self.funcall_start.elapsed(); + let total_duration = self.reducer_start.elapsed(); // Taking the call times record also resets timings to 0s for the next call. let wasm_instance_env_call_times = self.call_times.take(); @@ -1374,53 +1367,6 @@ impl WasmInstanceEnv { Ok(()) }) } - - /// Suspends execution of this WASM instance until approximately `wake_at_micros_since_unix_epoch`. - /// - /// Returns immediately if `wake_at_micros_since_unix_epoch` is in the past. - /// - /// Upon resuming, returns the current timestamp as microseconds since the Unix epoch. - /// - /// Not particularly useful, except for testing SpacetimeDB internals related to suspending procedure execution. - /// - /// In our public module-facing interfaces, this function is marked as unstable. - /// - /// # Traps - /// - /// Traps if: - /// - /// - The calling WASM instance is holding open a transaction. - /// - The calling WASM instance is not executing a procedure. - // TODO(procedure-sleep-until): remove this - pub fn procedure_sleep_until<'caller>( - mut caller: Caller<'caller, Self>, - (wake_at_micros_since_unix_epoch,): (i64,), - ) -> Box + Send + 'caller> { - Box::new(async move { - use std::time::SystemTime; - let span_start = span::CallSpanStart::new(AbiCall::ProcedureSleepUntil); - - let get_current_time = || Timestamp::now().to_micros_since_unix_epoch(); - - if wake_at_micros_since_unix_epoch < 0 { - return get_current_time(); - } - - let wake_at = Timestamp::from_micros_since_unix_epoch(wake_at_micros_since_unix_epoch); - let Ok(duration) = SystemTime::from(wake_at).duration_since(SystemTime::now()) else { - return get_current_time(); - }; - - tokio::time::sleep(duration).await; - - let res = get_current_time(); - - let span = span_start.end(); - span::record_span(&mut caller.data_mut().call_times, span); - - res - }) - } } impl BacktraceProvider for wasmtime::StoreContext<'_, T> { diff --git a/crates/core/src/host/wasmtime/wasmtime_module.rs b/crates/core/src/host/wasmtime/wasmtime_module.rs index 7cae069db93..eaf5b682e29 100644 --- a/crates/core/src/host/wasmtime/wasmtime_module.rs +++ b/crates/core/src/host/wasmtime/wasmtime_module.rs @@ -9,7 +9,6 @@ use crate::host::wasm_common::module_host_actor::{DescribeError, InitializationE use crate::host::wasm_common::*; use crate::util::string_from_utf8_lossy_owned; use futures_util::FutureExt; -use spacetimedb_lib::{ConnectionId, Identity}; use spacetimedb_primitives::errno::HOST_CALL_FAILURE; use wasmtime::{ AsContext, AsContextMut, ExternType, Instance, InstancePre, Linker, Store, TypedFunc, WasmBacktrace, WasmParams, @@ -40,7 +39,7 @@ impl WasmtimeModule { WasmtimeModule { module } } - pub const IMPLEMENTED_ABI: abi::VersionTuple = abi::VersionTuple::new(10, 3); + pub const IMPLEMENTED_ABI: abi::VersionTuple = abi::VersionTuple::new(10, 2); pub(super) fn link_imports(linker: &mut Linker) -> anyhow::Result<()> { const { assert!(WasmtimeModule::IMPLEMENTED_ABI.major == spacetimedb_lib::MODULE_ABI_MAJOR_VERSION) }; @@ -50,13 +49,7 @@ impl WasmtimeModule { linker$(.func_wrap($module, stringify!($func), WasmInstanceEnv::$func)?)*; } } - macro_rules! link_async_functions { - ($($module:literal :: $func:ident,)*) => { - #[allow(deprecated)] - linker$(.func_wrap_async($module, stringify!($func), WasmInstanceEnv::$func)?)*; - } - } - abi_funcs!(link_functions, link_async_functions); + abi_funcs!(link_functions); Ok(()) } } @@ -133,11 +126,9 @@ impl module_host_actor::WasmInstancePre for WasmtimeModule { store.epoch_deadline_callback(|store| { let env = store.data(); let database = env.instance_env().replica_ctx.database_identity; - let funcall = env.funcall_name(); - let dur = env.funcall_start().elapsed(); - // TODO(procedure-timing): This measurement is not super meaningful for procedures, - // which may (will) suspend execution and therefore may not have been continuously running since `env.funcall_start`. - tracing::warn!(funcall, ?database, "Wasm has been running for {dur:?}"); + let reducer = env.reducer_name(); + let dur = env.reducer_start().elapsed(); + tracing::warn!(reducer, ?database, "Wasm has been running for {dur:?}"); Ok(wasmtime::UpdateDeadline::Continue(EPOCH_TICKS_PER_SECOND)) }); @@ -171,78 +162,22 @@ impl module_host_actor::WasmInstancePre for WasmtimeModule { .get_typed_func(&mut store, CALL_REDUCER_DUNDER) .expect("no call_reducer"); - let call_procedure = get_call_procedure(&mut store, &instance); - Ok(WasmtimeInstance { store, instance, call_reducer, - call_procedure, }) } } -/// Look up the `instance`'s export named by [`CALL_PROCEDURE_DUNDER`]. -/// -/// Return `None` if the `instance` has no such export. -/// Modules from before the introduction of procedures will not have a `__call_procedure__` export, -/// which is fine because they also won't define any procedures. -/// -/// Panics if the `instance` has an export at the expected name, -/// but it is not a function or is a function of an inappropriate type. -/// For new modules, this will be caught during publish. -/// Old modules from before the introduction of procedures might have an export at that name, -/// but it follows the double-underscore pattern of reserved names, -/// so we're fine to break those modules. -fn get_call_procedure(store: &mut Store, instance: &Instance) -> Option { - // Wasmtime uses `anyhow` for error reporting, vexing library users the world over. - // This means we can't distinguish between the failure modes of `Instance::get_typed_func`. - // Instead, we type out the body of that method ourselves, - // but with error handling appropriate to our needs. - let export = instance.get_export(store.as_context_mut(), CALL_PROCEDURE_DUNDER)?; - - Some( - export - .into_func() - .unwrap_or_else(|| panic!("{CALL_PROCEDURE_DUNDER} export is not a function")) - .typed(store) - .unwrap_or_else(|err| panic!("{CALL_PROCEDURE_DUNDER} export is a function with incorrect type: {err}")), - ) -} - -type CallReducerType = TypedFunc< - ( - // Reducer ID, - u32, - // Sender `Identity` - u64, - u64, - u64, - u64, - // Sender `ConnectionId`, or 0 for none. - u64, - u64, - // Start timestamp. - u64, - // Args byte source. - u32, - // Errors byte sink. - u32, - ), - // Errno. - i32, ->; -// `__call_procedure__` takes the same arguments as `__call_reducer__`. -type CallProcedureType = CallReducerType; +type CallReducerType = TypedFunc<(u32, u64, u64, u64, u64, u64, u64, u64, u32, u32), i32>; pub struct WasmtimeInstance { store: Store, instance: Instance, call_reducer: CallReducerType, - call_procedure: Option, } -#[async_trait::async_trait] impl module_host_actor::WasmInstance for WasmtimeInstance { fn extract_descriptions(&mut self) -> Result, DescribeError> { let describer_func_name = DESCRIBE_MODULE_DUNDER; @@ -271,17 +206,18 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { #[tracing::instrument(level = "trace", skip_all)] fn call_reducer(&mut self, op: ReducerOp<'_>, budget: ReducerBudget) -> module_host_actor::ExecuteResult { let store = &mut self.store; - - prepare_store_for_call(store, budget); + // Set the fuel budget in WASM. + set_store_fuel(store, budget.into()); + store.set_epoch_deadline(EPOCH_TICKS_PER_SECOND); // Prepare sender identity and connection ID, as LITTLE-ENDIAN byte arrays. - let [sender_0, sender_1, sender_2, sender_3] = prepare_identity_for_call(*op.caller_identity); - let [conn_id_0, conn_id_1] = prepare_connection_id_for_call(*op.caller_connection_id); + let [sender_0, sender_1, sender_2, sender_3] = bytemuck::must_cast(op.caller_identity.to_byte_array()); + let [conn_id_0, conn_id_1] = bytemuck::must_cast(op.caller_connection_id.as_le_byte_array()); // Prepare arguments to the reducer + the error sink & start timings. let args_bytes = op.args.get_bsatn().clone(); - let (args_source, errors_sink) = store.data_mut().start_funcall(op.name, args_bytes, op.timestamp); + let (args_source, errors_sink) = store.data_mut().start_reducer(op.name, args_bytes, op.timestamp); let call_result = call_sync_typed_func( &self.call_reducer, @@ -303,7 +239,7 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { // Signal that this reducer call is finished. This gets us the timings // associated to our reducer call, and clears all of the instance state // associated to the call. - let (timings, error) = store.data_mut().finish_funcall(); + let (timings, error) = store.data_mut().finish_reducer(); let call_result = call_result.map(|code| handle_error_sink_code(code, error)); @@ -324,77 +260,6 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { fn log_traceback(func_type: &str, func: &str, trap: &anyhow::Error) { log_traceback(func_type, func, trap) } - - #[tracing::instrument(level = "trace", skip_all)] - async fn call_procedure( - &mut self, - op: module_host_actor::ProcedureOp, - budget: ReducerBudget, - ) -> module_host_actor::ProcedureExecuteResult { - let store = &mut self.store; - prepare_store_for_call(store, budget); - - // Prepare sender identity and connection ID, as LITTLE-ENDIAN byte arrays. - let [sender_0, sender_1, sender_2, sender_3] = prepare_identity_for_call(op.caller_identity); - let [conn_id_0, conn_id_1] = prepare_connection_id_for_call(op.caller_connection_id); - - // Prepare arguments to the reducer + the error sink & start timings. - let (args_source, result_sink) = store.data_mut().start_funcall(&op.name, op.arg_bytes, op.timestamp); - - let Some(call_procedure) = self.call_procedure.as_ref() else { - return module_host_actor::ProcedureExecuteResult { - energy: module_host_actor::EnergyStats::ZERO, - timings: module_host_actor::ExecutionTimings::zero(), - memory_allocation: get_memory_size(store), - call_result: Err(anyhow::anyhow!( - "Module defines procedure {} but does not export `{}`", - op.name, - CALL_PROCEDURE_DUNDER, - )), - }; - }; - let call_result = call_procedure - .call_async( - &mut *store, - ( - op.id.0, - sender_0, - sender_1, - sender_2, - sender_3, - conn_id_0, - conn_id_1, - op.timestamp.to_micros_since_unix_epoch() as u64, - args_source.0, - result_sink, - ), - ) - .await; - - // Close the timing span for this procedure and get the BSATN bytes of its result. - let (timings, result_bytes) = store.data_mut().finish_funcall(); - - let call_result = call_result.and_then(|code| { - (code == 0).then_some(result_bytes.into()).ok_or_else(|| { - anyhow::anyhow!( - "{CALL_PROCEDURE_DUNDER} returned unexpected code {code}. Procedures should return code 0 or trap." - ) - }) - }); - - let remaining_fuel = get_store_fuel(store); - let remaining = ReducerBudget::from(remaining_fuel); - - let energy = module_host_actor::EnergyStats { budget, remaining }; - let memory_allocation = get_memory_size(store); - - module_host_actor::ProcedureExecuteResult { - energy, - timings, - memory_allocation, - call_result, - } - } } fn set_store_fuel(store: &mut impl AsContextMut, fuel: WasmtimeFuel) { @@ -405,48 +270,6 @@ fn get_store_fuel(store: &impl AsContext) -> WasmtimeFuel { WasmtimeFuel(store.as_context().get_fuel().unwrap()) } -fn prepare_store_for_call(store: &mut Store, budget: ReducerBudget) { - // note that ReducerBudget being a u64 is load-bearing here - although we convert budget right back into - // EnergyQuanta at the end of this function, from_energy_quanta clamps it to a u64 range. - // otherwise, we'd return something like `used: i128::MAX - u64::MAX`, which is inaccurate. - set_store_fuel(store, budget.into()); - - // We enable epoch interruption only to log on long-running WASM functions. - // Our epoch interrupt callback logs and then immediately resumes execution. - store.set_epoch_deadline(EPOCH_TICKS_PER_SECOND); -} - -/// Convert `caller_identity` to the format used by `__call_reducer__` and `__call_procedure__`, -/// i.e. an array of 4 `u64`s. -/// -/// Callers should destructure this like: -/// ```ignore -/// # let identity = Identity::ZERO; -/// let [sender_0, sender_1, sender_2, sender_3] = prepare_identity_for_call(identity); -/// ``` -fn prepare_identity_for_call(caller_identity: Identity) -> [u64; 4] { - // Encode this as a LITTLE-ENDIAN byte array - bytemuck::must_cast(caller_identity.to_byte_array()) -} - -/// Convert `caller_connection_id` to the format used by `__call_reducer` and `__call_procedure__`, -/// i.e. an array of 2 `u64`s. -/// -/// Callers should destructure this like: -/// ```ignore -/// # let connection_id = ConnectionId::ZERO; -/// let [conn_id_0, conn_id_1] = prepare_connection_id_for_call(connection_id); -/// ``` -/// -fn prepare_connection_id_for_call(caller_connection_id: ConnectionId) -> [u64; 2] { - // Encode this as a LITTLE-ENDIAN byte array - bytemuck::must_cast(caller_connection_id.as_le_byte_array()) -} - -fn get_memory_size(store: &Store) -> usize { - store.data().get_mem().memory.data_size(store) -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/core/src/subscription/module_subscription_actor.rs b/crates/core/src/subscription/module_subscription_actor.rs index 1c1f492b285..41eb62e60ba 100644 --- a/crates/core/src/subscription/module_subscription_actor.rs +++ b/crates/core/src/subscription/module_subscription_actor.rs @@ -7,8 +7,8 @@ use super::query::compile_query_with_hashes; use super::tx::DeltaTx; use super::{collect_table_update, TableUpdateType}; use crate::client::messages::{ - ProcedureResultMessage, SerializableMessage, SubscriptionData, SubscriptionError, SubscriptionMessage, - SubscriptionResult, SubscriptionRows, SubscriptionUpdateMessage, TransactionUpdateMessage, + SerializableMessage, SubscriptionData, SubscriptionError, SubscriptionMessage, SubscriptionResult, + SubscriptionRows, SubscriptionUpdateMessage, TransactionUpdateMessage, }; use crate::client::{ClientActorId, ClientConnectionSender, Protocol}; use crate::db::relational_db::{MutTx, RelationalDB, Tx}; @@ -647,22 +647,6 @@ impl ModuleSubscriptions { .send_client_message(recipient, Some(tx_offset), message) } - /// Like [`Self::send_client_message`], - /// but doesn't require a `TxId` because procedures don't hold a transaction open. - pub fn send_procedure_message( - &self, - recipient: Arc, - message: ProcedureResultMessage, - ) -> Result<(), BroadcastError> { - self.broadcast_queue.send_client_message( - recipient, - // TODO(procedure-tx): We'll need some mechanism for procedures to report their last-referenced TxOffset, - // and to pass it here. - // This is currently moot, as procedures have no way to open a transaction yet. - None, message, - ) - } - #[tracing::instrument(level = "trace", skip_all)] pub fn add_multi_subscription( &self, diff --git a/crates/core/src/util/jobs.rs b/crates/core/src/util/jobs.rs index 059e2486278..d97acd652b7 100644 --- a/crates/core/src/util/jobs.rs +++ b/crates/core/src/util/jobs.rs @@ -71,9 +71,6 @@ impl CoreInfo { // However, `max_blocking_threads` will panic if passed 0, so we set a limit of 1 // and use `on_thread_start` to log an error when spawning a blocking task. .max_blocking_threads(1) - // Enable the timer system so that `procedure_sleep_until` can work. - // TODO(procedure-sleep): Remove this. - .enable_time() .on_thread_start({ use std::sync::atomic::{AtomicBool, Ordering}; let already_spawned_worker = AtomicBool::new(false); diff --git a/crates/datastore/src/execution_context.rs b/crates/datastore/src/execution_context.rs index ef72cbbdf25..f4fbcaf84ae 100644 --- a/crates/datastore/src/execution_context.rs +++ b/crates/datastore/src/execution_context.rs @@ -131,7 +131,6 @@ pub enum WorkloadType { Unsubscribe, Update, Internal, - Procedure, } impl Default for WorkloadType { diff --git a/crates/testing/tests/standalone_integration_test.rs b/crates/testing/tests/standalone_integration_test.rs index 7f19bd185ce..2df76d7d628 100644 --- a/crates/testing/tests/standalone_integration_test.rs +++ b/crates/testing/tests/standalone_integration_test.rs @@ -130,50 +130,6 @@ fn test_calling_a_reducer_with_private_table() { ); } -fn test_calling_a_procedure_in_module(module_name: &'static str) { - init(); - - CompiledModule::compile(module_name, CompilationMode::Debug).with_module_async( - DEFAULT_CONFIG, - |module| async move { - let json = r#" -{ - "CallProcedure": { - "procedure": "sleep_one_second", - "args": "[]", - "request_id": 0, - "flags": 0 - } -}"# - .to_string(); - module.send(json).await.unwrap(); - - // It sleeps one second, but we'll wait two just to be safe. - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - - let logs = read_logs(&module).await; - let logs = logs - .into_iter() - // Filter out log lines from the `repeating_test` reducer, - // which runs frequently enough to appear in our logs after we've slept a second. - .filter(|line| !line.starts_with("Timestamp: Timestamp { __timestamp_micros_since_unix_epoch__: ")) - .collect::>(); - let [log_sleep] = &logs[..] else { - panic!("Expected a single log message but found {logs:#?}"); - }; - - assert!(log_sleep.starts_with("Slept from ")); - assert!(log_sleep.contains("a total of")); - }, - ) -} - -#[test] -#[serial] -fn test_calling_a_procedure() { - test_calling_a_procedure_in_module("module-test"); -} - /// Invoke the `module-test` module, /// use `caller` to invoke its `test` reducer, /// and assert that its logs look right. diff --git a/sdks/rust/src/db_connection.rs b/sdks/rust/src/db_connection.rs index b7f507f263b..aa5c3f6fb27 100644 --- a/sdks/rust/src/db_connection.rs +++ b/sdks/rust/src/db_connection.rs @@ -1184,8 +1184,7 @@ async fn parse_loop( error: e.error.to_string(), }, ws::ServerMessage::SubscribeApplied(_) => unreachable!("Rust client SDK never sends `SubscribeSingle`, but received a `SubscribeApplied` from the host... huh?"), - ws::ServerMessage::UnsubscribeApplied(_) => unreachable!("Rust client SDK never sends `UnsubscribeSingle`, but received a `UnsubscribeApplied` from the host... huh?"), - ws::ServerMessage::ProcedureResult(_) => todo!("Rust client SDK procedure support"), + ws::ServerMessage::UnsubscribeApplied(_) => unreachable!("Rust client SDK never sends `UnsubscribeSingle`, but received a `UnsubscribeApplied` from the host... huh?") }) .expect("Failed to send ParsedMessage to main thread"); } diff --git a/sdks/rust/tests/connect_disconnect_client/src/module_bindings/mod.rs b/sdks/rust/tests/connect_disconnect_client/src/module_bindings/mod.rs index 4c763beb353..77cb1bb77fd 100644 --- a/sdks/rust/tests/connect_disconnect_client/src/module_bindings/mod.rs +++ b/sdks/rust/tests/connect_disconnect_client/src/module_bindings/mod.rs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.6.0 (commit a952ba57372dfbee37da9d079a3f86704ca35611). +// This was generated using spacetimedb cli version 1.6.0 (commit 11eb6b1cc9098d6b3727cef255b0c6b3dbf1df97). #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; diff --git a/sdks/rust/tests/test-client/src/module_bindings/mod.rs b/sdks/rust/tests/test-client/src/module_bindings/mod.rs index 234638a8f28..f7eb3a169d9 100644 --- a/sdks/rust/tests/test-client/src/module_bindings/mod.rs +++ b/sdks/rust/tests/test-client/src/module_bindings/mod.rs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.6.0 (commit a952ba57372dfbee37da9d079a3f86704ca35611). +// This was generated using spacetimedb cli version 1.6.0 (commit 11eb6b1cc9098d6b3727cef255b0c6b3dbf1df97). #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; From cc384f53d04558c20060e41c539ebcf4cbb7beaa Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:29 -0700 Subject: [PATCH 07/16] [release/v1.7.0]: Revert "Compute read sets for MutTxId via InstanceEnv (#3520)" This reverts commit f2cf5d19fe08bceafed77998ff6beb4d80866300. --- crates/core/src/db/relational_db.rs | 12 +-- crates/core/src/host/instance_env.rs | 25 +---- .../locking_tx_datastore/committed_state.rs | 84 ++-------------- .../src/locking_tx_datastore/datastore.rs | 1 - .../src/locking_tx_datastore/mut_tx.rs | 99 +------------------ 5 files changed, 19 insertions(+), 202 deletions(-) diff --git a/crates/core/src/db/relational_db.rs b/crates/core/src/db/relational_db.rs index 4b7a533c008..f8423af7227 100644 --- a/crates/core/src/db/relational_db.rs +++ b/crates/core/src/db/relational_db.rs @@ -57,7 +57,7 @@ use std::collections::HashSet; use std::fmt; use std::fs::File; use std::io; -use std::ops::{Bound, RangeBounds}; +use std::ops::RangeBounds; use std::path::Path; use std::sync::Arc; use tokio::sync::watch; @@ -1353,15 +1353,7 @@ impl RelationalDB { prefix_elems: ColId, rstart: &[u8], rend: &[u8], - ) -> Result< - ( - TableId, - Bound, - Bound, - impl Iterator>, - ), - DBError, - > { + ) -> Result<(TableId, impl Iterator>), DBError> { Ok(tx.index_scan_range(index_id, prefix, prefix_elems, rstart, rend)?) } diff --git a/crates/core/src/host/instance_env.rs b/crates/core/src/host/instance_env.rs index b94f700b9a8..6825f1a50de 100644 --- a/crates/core/src/host/instance_env.rs +++ b/crates/core/src/host/instance_env.rs @@ -11,7 +11,7 @@ use smallvec::SmallVec; use spacetimedb_datastore::locking_tx_datastore::state_view::StateView; use spacetimedb_datastore::locking_tx_datastore::MutTxId; use spacetimedb_lib::{ConnectionId, Identity, Timestamp}; -use spacetimedb_primitives::{ColId, ColList, IndexId, TableId, ViewId}; +use spacetimedb_primitives::{ColId, ColList, IndexId, TableId}; use spacetimedb_sats::{ bsatn::{self, ToBsatn}, buffer::{CountWriter, TeeWriter}, @@ -31,7 +31,6 @@ pub struct InstanceEnv { pub tx: TxSlot, /// The timestamp the current reducer began running. pub start_time: Timestamp, - pub view_id: Option, } #[derive(Clone, Default)] @@ -173,7 +172,6 @@ impl InstanceEnv { scheduler, tx: TxSlot::default(), start_time: Timestamp::now(), - view_id: None, } } @@ -185,12 +183,6 @@ impl InstanceEnv { /// Signal to this `InstanceEnv` that a reducer call is beginning. pub fn start_reducer(&mut self, ts: Timestamp) { self.start_time = ts; - self.view_id = None; - } - - /// Signal to this `InstanceEnv` that a we're going to execute a view and compute its read set. - pub fn start_view(&mut self, view_id: ViewId) { - self.view_id = Some(view_id); } fn get_tx(&self) -> Result + '_, GetTxError> { @@ -383,7 +375,7 @@ impl InstanceEnv { let tx = &mut *self.tx.get()?; // Find all rows in the table to delete. - let (table_id, _, _, iter) = stdb.index_scan_range(tx, index_id, prefix, prefix_elems, rstart, rend)?; + let (table_id, iter) = stdb.index_scan_range(tx, index_id, prefix, prefix_elems, rstart, rend)?; // Re. `SmallVec`, `delete_by_field` only cares about 1 element, so optimize for that. let rows_to_delete = iter.map(|row_ref| row_ref.pointer()).collect::>(); @@ -468,11 +460,7 @@ impl InstanceEnv { let tx = &mut *self.get_tx()?; // Query the row count for id. - stdb.table_row_count_mut(tx, table_id) - .ok_or(NodesError::TableNotFound) - .inspect(|_| { - tx.record_table_scan(self.view_id, table_id); - }) + stdb.table_row_count_mut(tx, table_id).ok_or(NodesError::TableNotFound) } #[tracing::instrument(level = "trace", skip_all)] @@ -496,8 +484,6 @@ impl InstanceEnv { &mut bytes_scanned, ); - tx.record_table_scan(self.view_id, table_id); - tx.metrics.rows_scanned += rows_scanned; tx.metrics.bytes_scanned += bytes_scanned; @@ -522,13 +508,11 @@ impl InstanceEnv { let mut bytes_scanned = 0; // Open index iterator - let (table_id, lower, upper, iter) = stdb.index_scan_range(tx, index_id, prefix, prefix_elems, rstart, rend)?; + let (_, iter) = stdb.index_scan_range(tx, index_id, prefix, prefix_elems, rstart, rend)?; // Scan the index and serialize rows to bsatn let chunks = ChunkedWriter::collect_iter(pool, iter, &mut rows_scanned, &mut bytes_scanned); - tx.record_index_scan(self.view_id, table_id, index_id, lower, upper); - tx.metrics.index_seeks += 1; tx.metrics.rows_scanned += rows_scanned; tx.metrics.bytes_scanned += bytes_scanned; @@ -664,7 +648,6 @@ mod test { scheduler, tx: TxSlot::default(), start_time: Timestamp::now(), - view_id: None, }, runtime, )) diff --git a/crates/datastore/src/locking_tx_datastore/committed_state.rs b/crates/datastore/src/locking_tx_datastore/committed_state.rs index 0187aa41648..08579c5e18b 100644 --- a/crates/datastore/src/locking_tx_datastore/committed_state.rs +++ b/crates/datastore/src/locking_tx_datastore/committed_state.rs @@ -6,11 +6,15 @@ use super::{ tx_state::{IndexIdMap, PendingSchemaChange, TxState}, IterByColEqTx, }; +use crate::system_tables::{ + ST_CONNECTION_CREDENTIALS_ID, ST_CONNECTION_CREDENTIALS_IDX, ST_VIEW_COLUMN_ID, ST_VIEW_COLUMN_IDX, ST_VIEW_ID, + ST_VIEW_IDX, ST_VIEW_PARAM_ID, ST_VIEW_PARAM_IDX, +}; use crate::{ db_metrics::DB_METRICS, error::{DatastoreError, IndexError, TableError}, execution_context::ExecutionContext, - locking_tx_datastore::{mut_tx::ViewReadSets, state_view::iter_st_column_for_table}, + locking_tx_datastore::state_view::iter_st_column_for_table, system_tables::{ system_tables, StColumnRow, StConstraintData, StConstraintRow, StIndexRow, StSequenceRow, StTableFields, StTableRow, SystemTable, ST_CLIENT_ID, ST_CLIENT_IDX, ST_COLUMN_ID, ST_COLUMN_IDX, ST_COLUMN_NAME, @@ -21,19 +25,12 @@ use crate::{ }, traits::TxData, }; -use crate::{ - locking_tx_datastore::mut_tx::ReadSet, - system_tables::{ - ST_CONNECTION_CREDENTIALS_ID, ST_CONNECTION_CREDENTIALS_IDX, ST_VIEW_COLUMN_ID, ST_VIEW_COLUMN_IDX, ST_VIEW_ID, - ST_VIEW_IDX, ST_VIEW_PARAM_ID, ST_VIEW_PARAM_IDX, - }, -}; use anyhow::anyhow; use core::{convert::Infallible, ops::RangeBounds}; -use spacetimedb_data_structures::map::{HashMap, HashSet, IntMap, IntSet}; +use spacetimedb_data_structures::map::{HashSet, IntMap, IntSet}; use spacetimedb_durability::TxOffset; use spacetimedb_lib::{db::auth::StTableType, Identity}; -use spacetimedb_primitives::{ColId, ColList, ColSet, IndexId, TableId, ViewId}; +use spacetimedb_primitives::{ColId, ColList, ColSet, IndexId, TableId}; use spacetimedb_sats::{algebraic_value::de::ValueDeserializer, memory_usage::MemoryUsage, Deserialize}; use spacetimedb_sats::{AlgebraicValue, ProductValue}; use spacetimedb_schema::{ @@ -50,40 +47,6 @@ use std::collections::BTreeMap; use std::sync::Arc; use thin_vec::ThinVec; -type IndexKeyReadSet = HashMap>; -type IndexColReadSet = HashMap; - -#[derive(Default)] -struct CommittedReadSets { - tables: IntMap>, - index_keys: IntMap, -} - -impl MemoryUsage for CommittedReadSets { - fn heap_usage(&self) -> usize { - self.tables.heap_usage() + self.index_keys.heap_usage() - } -} - -impl CommittedReadSets { - /// Record in the [`CommittedState`] that this view scans this table - fn view_scans_table(&mut self, view_id: ViewId, table_id: TableId) { - self.tables.entry(table_id).or_default().insert(view_id); - } - - /// Record in the [`CommittedState`] that this view reads this index `key` for these table `cols` - fn view_reads_index_key(&mut self, view_id: ViewId, table_id: TableId, cols: ColList, key: &AlgebraicValue) { - self.index_keys - .entry(table_id) - .or_default() - .entry(cols) - .or_default() - .entry(key.clone()) - .or_default() - .insert(view_id); - } -} - /// Contains the live, in-memory snapshot of a database. This structure /// is exposed in order to support tools wanting to process the commit /// logs directly. For normal usage, see the RelationalDB struct instead. @@ -109,11 +72,6 @@ pub struct CommittedState { /// We should split `CommittedState` into two types /// where one, e.g., `ReplayCommittedState`, has this field. table_dropped: IntSet, - /// We track the read sets for each view in the committed state. - /// We check each reducer's write set against these read sets. - /// Any overlap will trigger a re-evaluation of the affected view, - /// and its read set will be updated accordingly. - read_sets: CommittedReadSets, } impl MemoryUsage for CommittedState { @@ -125,7 +83,6 @@ impl MemoryUsage for CommittedState { index_id_map, page_pool: _, table_dropped, - read_sets, } = self; // NOTE(centril): We do not want to include the heap usage of `page_pool` as it's a shared resource. next_tx_offset.heap_usage() @@ -133,7 +90,6 @@ impl MemoryUsage for CommittedState { + blob_store.heap_usage() + index_id_map.heap_usage() + table_dropped.heap_usage() - + read_sets.heap_usage() } } @@ -196,7 +152,6 @@ impl CommittedState { blob_store: <_>::default(), index_id_map: <_>::default(), table_dropped: <_>::default(), - read_sets: <_>::default(), page_pool, } } @@ -659,13 +614,10 @@ impl CommittedState { tx_data.has_rows_or_connect_disconnect(ctx.reducer_context()) } - pub(super) fn merge(&mut self, tx_state: TxState, read_sets: ViewReadSets, ctx: &ExecutionContext) -> TxData { + pub(super) fn merge(&mut self, tx_state: TxState, ctx: &ExecutionContext) -> TxData { let mut tx_data = TxData::default(); let mut truncates = IntSet::default(); - // Merge read sets from the `MutTxId` into the `CommittedState` - self.merge_read_sets(read_sets); - // First, apply deletes. This will free up space in the committed tables. self.merge_apply_deletes( &mut tx_data, @@ -696,26 +648,6 @@ impl CommittedState { tx_data } - fn merge_read_set(&mut self, view_id: ViewId, read_set: ReadSet) { - for table_id in read_set.tables_scanned() { - self.read_sets.view_scans_table(view_id, *table_id); - } - for (table_id, index_id, key) in read_set.index_keys_scanned() { - if let Some(cols) = self - .get_schema(*table_id) - .map(|table_schema| table_schema.col_list_for_index_id(*index_id)) - { - self.read_sets.view_reads_index_key(view_id, *table_id, cols, key); - } - } - } - - fn merge_read_sets(&mut self, read_sets: ViewReadSets) { - for (view_id, read_set) in read_sets { - self.merge_read_set(view_id, read_set); - } - } - fn merge_apply_deletes( &mut self, tx_data: &mut TxData, diff --git a/crates/datastore/src/locking_tx_datastore/datastore.rs b/crates/datastore/src/locking_tx_datastore/datastore.rs index 8393f9d9719..88b10f8bcf0 100644 --- a/crates/datastore/src/locking_tx_datastore/datastore.rs +++ b/crates/datastore/src/locking_tx_datastore/datastore.rs @@ -926,7 +926,6 @@ impl MutTx for Locking { sequence_state_lock, tx_state: TxState::default(), lock_wait_time, - read_sets: <_>::default(), timer, ctx, metrics, diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index 98ae9b93a19..59f40a73419 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -30,7 +30,6 @@ use core::ops::RangeBounds; use core::{cell::RefCell, mem}; use core::{iter, ops::Bound}; use smallvec::SmallVec; -use spacetimedb_data_structures::map::{IntMap, IntSet}; use spacetimedb_durability::TxOffset; use spacetimedb_execution::{dml::MutDatastore, Datastore, DeltaStore, Row}; use spacetimedb_lib::{db::raw_def::v9::RawSql, metrics::ExecutionMetrics}; @@ -69,55 +68,6 @@ use std::{ type DecodeResult = core::result::Result; -/// Views track their read sets and update the [`CommittedState`] with them. -/// The [`CommittedState`] maintains these read sets in order to determine when to re-evaluate a view. -#[derive(Default)] -pub struct ReadSet { - table_scans: IntSet, - index_keys: IntMap>, -} - -impl ReadSet { - /// Enumerate the tables that are scanned and tracked by this read set - pub fn tables_scanned(&self) -> impl Iterator + '_ { - self.table_scans.iter() - } - - /// Enumerate the single index keys that are tracked by this read set - pub fn index_keys_scanned(&self) -> impl Iterator + '_ { - self.index_keys - .iter() - .flat_map(|(table_id, keys)| keys.iter().map(move |(index_id, key)| (table_id, index_id, key))) - } - - /// Track a table scan in this read set - fn insert_table_scan(&mut self, table_id: TableId) { - self.table_scans.insert(table_id); - } - - /// Track an index scan in this read set. - /// If we only read a single index key we record the key. - /// If we read a range, we treat it as though we scanned the entire table. - fn insert_index_scan( - &mut self, - table_id: TableId, - index_id: IndexId, - lower: Bound, - upper: Bound, - ) { - match (lower, upper) { - (Bound::Included(lower), Bound::Included(upper)) if lower == upper => { - self.index_keys.entry(table_id).or_default().insert(index_id, lower); - } - _ => { - self.table_scans.insert(table_id); - } - } - } -} - -pub type ViewReadSets = IntMap; - /// Represents a Mutable transaction. Holds locks for its duration /// /// The initialization of this struct is sensitive because improper @@ -128,7 +78,6 @@ pub struct MutTxId { pub(super) committed_state_write_lock: SharedWriteGuard, pub(super) sequence_state_lock: SharedMutexGuard, pub(super) lock_wait_time: Duration, - pub(super) read_sets: ViewReadSets, // TODO(cloutiertyler): The below were made `pub` for the datastore split. We should // make these private again. pub timer: Instant, @@ -136,33 +85,7 @@ pub struct MutTxId { pub metrics: ExecutionMetrics, } -static_assert_size!(MutTxId, 432); - -impl MutTxId { - /// Record that a view performs a table scan in this transaction's read set - pub fn record_table_scan(&mut self, view_id: Option, table_id: TableId) { - if let Some(view_id) = view_id { - self.read_sets.entry(view_id).or_default().insert_table_scan(table_id) - } - } - - /// Record that a view performs an index scan in this transaction's read set - pub fn record_index_scan( - &mut self, - view_id: Option, - table_id: TableId, - index_id: IndexId, - lower: Bound, - upper: Bound, - ) { - if let Some(view_id) = view_id { - self.read_sets - .entry(view_id) - .or_default() - .insert_index_scan(table_id, index_id, lower, upper) - } - } -} +static_assert_size!(MutTxId, 400); impl Datastore for MutTxId { type TableIter<'a> @@ -1027,12 +950,7 @@ impl MutTxId { prefix_elems: ColId, rstart: &[u8], rend: &[u8], - ) -> Result<( - TableId, - Bound, - Bound, - IndexScanRanged<'a>, - )> { + ) -> Result<(TableId, IndexScanRanged<'a>)> { // Extract the table id, and commit/tx indices. let (table_id, commit_index, tx_index) = self .get_table_and_index(index_id) @@ -1051,12 +969,9 @@ impl MutTxId { let tx_iter = tx_index.map(|i| i.seek_range(&bounds)); let commit_iter = commit_index.seek_range(&bounds); - let (lower, upper) = bounds; - let dt = self.tx_state.get_delete_table(table_id); let iter = combine_range_index_iters(dt, tx_iter, commit_iter); - - Ok((table_id, lower, upper, iter)) + Ok((table_id, iter)) } /// Translate `index_id` to the table id, and commit/tx indices. @@ -1571,9 +1486,7 @@ impl MutTxId { /// - `String`, the name of the reducer which ran during this transaction. pub(super) fn commit(mut self) -> (TxOffset, TxData, TxMetrics, String) { let tx_offset = self.committed_state_write_lock.next_tx_offset; - let tx_data = self - .committed_state_write_lock - .merge(self.tx_state, self.read_sets, &self.ctx); + let tx_data = self.committed_state_write_lock.merge(self.tx_state, &self.ctx); // Compute and keep enough info that we can // record metrics after the transaction has ended @@ -1615,9 +1528,7 @@ impl MutTxId { /// - [`TxMetrics`], various measurements of the work performed by this transaction. /// - [`TxId`], a read-only transaction with a shared lock on the committed state. pub fn commit_downgrade(mut self, workload: Workload) -> (TxData, TxMetrics, TxId) { - let tx_data = self - .committed_state_write_lock - .merge(self.tx_state, self.read_sets, &self.ctx); + let tx_data = self.committed_state_write_lock.merge(self.tx_state, &self.ctx); // Compute and keep enough info that we can // record metrics after the transaction has ended From 00124054d38459a2f92fa3083eb2673d852b6563 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:29 -0700 Subject: [PATCH 08/16] [release/v1.7.0]: Revert "Auto-migration rules for views (#3484)" This reverts commit 39f91270e1846397e6a808908fee9d7997197a07. --- crates/core/src/db/relational_db.rs | 38 +- crates/core/src/db/update.rs | 13 +- crates/core/src/host/module_host.rs | 2 +- .../src/locking_tx_datastore/datastore.rs | 6 +- .../src/locking_tx_datastore/mut_tx.rs | 79 +- crates/datastore/src/system_tables.rs | 8 - crates/datastore/src/traits.rs | 1 - crates/sats/src/convert.rs | 3 +- crates/schema/src/auto_migrate.rs | 693 +----------------- crates/schema/src/auto_migrate/formatter.rs | 84 +-- .../src/auto_migrate/termcolor_formatter.rs | 44 -- crates/schema/src/def.rs | 125 +--- crates/schema/src/def/validate/v9.rs | 76 +- crates/schema/src/schema.rs | 54 +- ...__tests__empty_to_populated_migration.snap | 8 - ..._tests__updated pretty print no color.snap | 16 +- ..._migrate__tests__updated pretty print.snap | 14 - 17 files changed, 76 insertions(+), 1188 deletions(-) diff --git a/crates/core/src/db/relational_db.rs b/crates/core/src/db/relational_db.rs index f8423af7227..639ae8f0ba4 100644 --- a/crates/core/src/db/relational_db.rs +++ b/crates/core/src/db/relational_db.rs @@ -1055,30 +1055,13 @@ impl RelationalDB { Ok(self.inner.create_table_mut_tx(tx, schema)?) } - pub fn drop_table(&self, tx: &mut MutTx, table_id: TableId) -> Result<(), DBError> { - let table_name = self - .table_name_from_id_mut(tx, table_id)? - .map(|name| name.to_string()) - .unwrap_or_default(); - Ok(self.inner.drop_table_mut_tx(tx, table_id).map(|_| { - DB_METRICS - .rdb_num_table_rows - .with_label_values(&self.database_identity, &table_id.into(), &table_name) - .set(0) - })?) - } - - pub fn create_view( + pub fn create_view_table( &self, tx: &mut MutTx, module_def: &ModuleDef, view_def: &ViewDef, ) -> Result<(ViewId, TableId), DBError> { - Ok(tx.create_view(module_def, view_def)?) - } - - pub fn drop_view(&self, tx: &mut MutTx, view_id: ViewId) -> Result<(), DBError> { - Ok(tx.drop_view(view_id)?) + Ok(tx.create_view_with_backing_table(module_def, view_def)?) } pub fn create_table_for_test_with_the_works( @@ -1158,6 +1141,19 @@ impl RelationalDB { self.create_table_for_test_with_the_works(name, schema, &indexes[..], &[], StAccess::Public) } + pub fn drop_table(&self, tx: &mut MutTx, table_id: TableId) -> Result<(), DBError> { + let table_name = self + .table_name_from_id_mut(tx, table_id)? + .map(|name| name.to_string()) + .unwrap_or_default(); + Ok(self.inner.drop_table_mut_tx(tx, table_id).map(|_| { + DB_METRICS + .rdb_num_table_rows + .with_label_values(&self.database_identity, &table_id.into(), &table_name) + .set(0) + })?) + } + /// Rename a table. /// /// Sets the name of the table to `new_name` regardless of the previous value. This is a @@ -1168,10 +1164,6 @@ impl RelationalDB { Ok(self.inner.rename_table_mut_tx(tx, table_id, new_name)?) } - pub fn view_id_from_name_mut(&self, tx: &MutTx, view_name: &str) -> Result, DBError> { - Ok(self.inner.view_id_from_name_mut_tx(tx, view_name)?) - } - pub fn table_id_from_name_mut(&self, tx: &MutTx, table_name: &str) -> Result, DBError> { Ok(self.inner.table_id_from_name_mut_tx(tx, table_name)?) } diff --git a/crates/core/src/db/update.rs b/crates/core/src/db/update.rs index d4d3b6648c3..9edd7cc0244 100644 --- a/crates/core/src/db/update.rs +++ b/crates/core/src/db/update.rs @@ -7,7 +7,7 @@ use spacetimedb_lib::identity::AuthCtx; use spacetimedb_lib::AlgebraicValue; use spacetimedb_primitives::{ColSet, TableId}; use spacetimedb_schema::auto_migrate::{AutoMigratePlan, ManualMigratePlan, MigratePlan}; -use spacetimedb_schema::def::{TableDef, ViewDef}; +use spacetimedb_schema::def::TableDef; use spacetimedb_schema::schema::{column_schemas_from_defs, IndexSchema, Schema, SequenceSchema, TableSchema}; /// The logger used for by [`update_database`] and friends. @@ -137,17 +137,6 @@ fn auto_migrate_database( stdb.create_table(tx, table_schema)?; } - spacetimedb_schema::auto_migrate::AutoMigrateStep::AddView(view_name) => { - let view_def: &ViewDef = plan.new.expect_lookup(view_name); - stdb.create_view(tx, plan.new, view_def)?; - } - spacetimedb_schema::auto_migrate::AutoMigrateStep::RemoveView(view_name) => { - let view_id = stdb.view_id_from_name_mut(tx, view_name)?.unwrap(); - stdb.drop_view(tx, view_id)?; - } - spacetimedb_schema::auto_migrate::AutoMigrateStep::UpdateView(_) => { - unimplemented!("Recompute view and update its backing table") - } spacetimedb_schema::auto_migrate::AutoMigrateStep::AddIndex(index_name) => { let table_def = plan.new.stored_in_table_def(index_name).unwrap(); let index_def = table_def.indexes.get(index_name).unwrap(); diff --git a/crates/core/src/host/module_host.rs b/crates/core/src/host/module_host.rs index ea00f8a7330..899f374cff1 100644 --- a/crates/core/src/host/module_host.rs +++ b/crates/core/src/host/module_host.rs @@ -422,7 +422,7 @@ pub fn create_table_from_view_def( module_def: &ModuleDef, view_def: &ViewDef, ) -> anyhow::Result<()> { - stdb.create_view(tx, module_def, view_def) + stdb.create_view_table(tx, module_def, view_def) .with_context(|| format!("failed to create table for view {}", &view_def.name))?; Ok(()) } diff --git a/crates/datastore/src/locking_tx_datastore/datastore.rs b/crates/datastore/src/locking_tx_datastore/datastore.rs index 88b10f8bcf0..66a11d79ddc 100644 --- a/crates/datastore/src/locking_tx_datastore/datastore.rs +++ b/crates/datastore/src/locking_tx_datastore/datastore.rs @@ -35,7 +35,7 @@ use spacetimedb_durability::TxOffset; use spacetimedb_lib::{db::auth::StAccess, metrics::ExecutionMetrics}; use spacetimedb_lib::{ConnectionId, Identity}; use spacetimedb_paths::server::SnapshotDirPath; -use spacetimedb_primitives::{ColList, ConstraintId, IndexId, SequenceId, TableId, ViewId}; +use spacetimedb_primitives::{ColList, ConstraintId, IndexId, SequenceId, TableId}; use spacetimedb_sats::{ algebraic_value::de::ValueDeserializer, bsatn, buffer::BufReader, AlgebraicValue, ProductValue, }; @@ -512,10 +512,6 @@ impl MutTxDatastore for Locking { tx.rename_table(table_id, new_name) } - fn view_id_from_name_mut_tx(&self, tx: &Self::MutTx, view_name: &str) -> Result> { - tx.view_id_from_name(view_name) - } - fn table_id_from_name_mut_tx(&self, tx: &Self::MutTx, table_name: &str) -> Result> { tx.table_id_from_name(table_name) } diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index 59f40a73419..17a4cfec81e 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -9,9 +9,8 @@ use super::{ SharedMutexGuard, SharedWriteGuard, }; use crate::system_tables::{ - system_tables, ConnectionIdViaU128, StConnectionCredentialsFields, StConnectionCredentialsRow, StViewColumnFields, - StViewFields, StViewParamFields, StViewParamRow, ST_CONNECTION_CREDENTIALS_ID, ST_VIEW_COLUMN_ID, ST_VIEW_ID, - ST_VIEW_PARAM_ID, + system_tables, ConnectionIdViaU128, StConnectionCredentialsFields, StConnectionCredentialsRow, StViewFields, + StViewParamRow, ST_CONNECTION_CREDENTIALS_ID, ST_VIEW_COLUMN_ID, ST_VIEW_ID, ST_VIEW_PARAM_ID, }; use crate::traits::{InsertFlags, RowTypeForTable, TxData, UpdateFlags}; use crate::{ @@ -47,7 +46,7 @@ use spacetimedb_sats::{ AlgebraicType, AlgebraicValue, ProductType, ProductValue, WithTypespace, }; use spacetimedb_schema::{ - def::{ModuleDef, ViewColumnDef, ViewDef}, + def::{ColumnDef, ModuleDef, ViewDef}, schema::{ColumnSchema, ConstraintSchema, IndexSchema, RowLevelSecuritySchema, SequenceSchema, TableSchema}, }; use spacetimedb_table::{ @@ -216,7 +215,7 @@ impl MutTxId { Ok(()) } - /// Create a backing table for a view and update the system tables. + /// Create a backing table for a view. /// /// Requires: /// - Everything [`Self::create_table`] requires. @@ -225,7 +224,11 @@ impl MutTxId { /// - Everything [`Self::create_table`] ensures. /// - The returned [`ViewId`] is unique and not [`ViewId::SENTINEL`]. /// - All view metadata maintained by the datastore is created atomically - pub fn create_view(&mut self, module_def: &ModuleDef, view_def: &ViewDef) -> Result<(ViewId, TableId)> { + pub fn create_view_with_backing_table( + &mut self, + module_def: &ModuleDef, + view_def: &ViewDef, + ) -> Result<(ViewId, TableId)> { let table_schema = TableSchema::from_view_def(module_def, view_def); let table_id = self.create_table(table_schema)?; @@ -234,35 +237,16 @@ impl MutTxId { is_anonymous, is_public, params, - return_columns, + columns, .. } = view_def; let view_id = self.insert_into_st_view(name.clone().into(), table_id, *is_public, *is_anonymous)?; self.insert_into_st_view_param(view_id, params)?; - self.insert_into_st_view_column(view_id, return_columns)?; + self.insert_into_st_view_column(view_id, columns)?; Ok((view_id, table_id)) } - /// Drop the backing table of a view and update the system tables. - pub fn drop_view(&mut self, view_id: ViewId) -> Result<()> { - // Drop the view's metadata - self.drop_st_view(view_id)?; - self.drop_st_view_param(view_id)?; - self.drop_st_view_column(view_id)?; - - // Drop the view's backing table if materialized - if let StViewRow { - table_id: Some(table_id), - .. - } = self.lookup_st_view(view_id)? - { - return self.drop_table(table_id); - }; - - Ok(()) - } - /// Create a table. /// /// Requires: @@ -367,15 +351,6 @@ impl MutTxId { }) } - fn lookup_st_view(&self, view_id: ViewId) -> Result { - let row = self - .iter_by_col_eq(ST_VIEW_ID, StViewFields::ViewId, &view_id.into())? - .next() - .ok_or_else(|| TableError::IdNotFound(SystemTable::st_view, view_id.into()))?; - - StViewRow::try_from(row) - } - /// Insert a row into `st_view`, auto-increments and returns the [`ViewId`]. fn insert_into_st_view( &mut self, @@ -421,7 +396,7 @@ impl MutTxId { } /// For each column or field returned in a view, insert a row into `st_view_column`. - fn insert_into_st_view_column(&mut self, view_id: ViewId, columns: &[ViewColumnDef]) -> Result<()> { + fn insert_into_st_view_column(&mut self, view_id: ViewId, columns: &[ColumnDef]) -> Result<()> { for def in columns { self.insert_via_serialize_bsatn( ST_VIEW_COLUMN_ID, @@ -481,26 +456,6 @@ impl MutTxId { self.delete_col_eq(ST_COLUMN_ID, StColumnFields::TableId.col_id(), &table_id.into()) } - /// Drops the row in `st_table` for this `table_id` - fn drop_st_table(&mut self, table_id: TableId) -> Result<()> { - self.delete_col_eq(ST_TABLE_ID, StTableFields::TableId.col_id(), &table_id.into()) - } - - /// Drops the row in `st_view` for this `view_id` - fn drop_st_view(&mut self, view_id: ViewId) -> Result<()> { - self.delete_col_eq(ST_VIEW_ID, StViewFields::ViewId.col_id(), &view_id.into()) - } - - /// Drops the rows in `st_view_param` for this `view_id` - fn drop_st_view_param(&mut self, view_id: ViewId) -> Result<()> { - self.delete_col_eq(ST_VIEW_PARAM_ID, StViewParamFields::ViewId.col_id(), &view_id.into()) - } - - /// Drops the rows in `st_view_column` for this `view_id` - fn drop_st_view_column(&mut self, view_id: ViewId) -> Result<()> { - self.delete_col_eq(ST_VIEW_COLUMN_ID, StViewColumnFields::ViewId.col_id(), &view_id.into()) - } - pub fn drop_table(&mut self, table_id: TableId) -> Result<()> { self.clear_table(table_id)?; @@ -519,7 +474,7 @@ impl MutTxId { } // Drop the table and their columns - self.drop_st_table(table_id)?; + self.delete_col_eq(ST_TABLE_ID, StTableFields::TableId.col_id(), &table_id.into())?; self.drop_st_column(table_id)?; if let Some(schedule) = &schema.schedule { @@ -568,14 +523,6 @@ impl MutTxId { Ok(ret) } - pub fn view_id_from_name(&self, view_name: &str) -> Result> { - let view_name = &view_name.into(); - let row = self - .iter_by_col_eq(ST_VIEW_ID, StViewFields::ViewName, view_name)? - .next(); - Ok(row.map(|row| row.read_col(StViewFields::ViewId).unwrap())) - } - pub fn table_id_from_name(&self, table_name: &str) -> Result> { let table_name = &table_name.into(); let row = self diff --git a/crates/datastore/src/system_tables.rs b/crates/datastore/src/system_tables.rs index 7f33051469e..d5a16070a97 100644 --- a/crates/datastore/src/system_tables.rs +++ b/crates/datastore/src/system_tables.rs @@ -107,7 +107,6 @@ pub const ST_RESERVED_SEQUENCE_RANGE: u32 = 4096; #[derive(Debug, Display)] pub enum SystemTable { st_table, - st_view, st_column, st_sequence, st_index, @@ -750,13 +749,6 @@ pub struct StViewRow { pub is_anonymous: bool, } -impl TryFrom> for StViewRow { - type Error = DatastoreError; - fn try_from(row: RowRef<'_>) -> Result { - read_via_bsatn(row) - } -} - /// A wrapper around `AlgebraicType` that acts like `AlgegbraicType::bytes()` for serialization purposes. #[derive(Debug, Clone, PartialEq, Eq)] pub struct AlgebraicTypeViaBytes(pub AlgebraicType); diff --git a/crates/datastore/src/traits.rs b/crates/datastore/src/traits.rs index b04b2444863..d6fc69d57fb 100644 --- a/crates/datastore/src/traits.rs +++ b/crates/datastore/src/traits.rs @@ -509,7 +509,6 @@ pub trait MutTxDatastore: TxDatastore + MutTx { fn schema_for_table_mut_tx(&self, tx: &Self::MutTx, table_id: TableId) -> Result>; fn drop_table_mut_tx(&self, tx: &mut Self::MutTx, table_id: TableId) -> Result<()>; fn rename_table_mut_tx(&self, tx: &mut Self::MutTx, table_id: TableId, new_name: &str) -> Result<()>; - fn view_id_from_name_mut_tx(&self, tx: &Self::MutTx, view_name: &str) -> Result>; fn table_id_from_name_mut_tx(&self, tx: &Self::MutTx, table_name: &str) -> Result>; fn table_id_exists_mut_tx(&self, tx: &Self::MutTx, table_id: &TableId) -> bool; fn table_name_from_id_mut_tx<'a>(&'a self, tx: &'a Self::MutTx, table_id: TableId) -> Result>>; diff --git a/crates/sats/src/convert.rs b/crates/sats/src/convert.rs index 1729402cdc3..dfd4a8dbe7e 100644 --- a/crates/sats/src/convert.rs +++ b/crates/sats/src/convert.rs @@ -1,7 +1,7 @@ use crate::sum_value::SumTag; use crate::{i256, u256}; use crate::{AlgebraicType, AlgebraicValue, ProductType, ProductValue}; -use spacetimedb_primitives::{ColId, ConstraintId, IndexId, ScheduleId, SequenceId, TableId, ViewId}; +use spacetimedb_primitives::{ColId, ConstraintId, IndexId, ScheduleId, SequenceId, TableId}; impl crate::Value for AlgebraicValue { type Type = AlgebraicType; @@ -64,7 +64,6 @@ macro_rules! system_id { }; } system_id!(TableId); -system_id!(ViewId); system_id!(ColId); system_id!(SequenceId); system_id!(IndexId); diff --git a/crates/schema/src/auto_migrate.rs b/crates/schema/src/auto_migrate.rs index b6e179ef205..7a0f665fe7b 100644 --- a/crates/schema/src/auto_migrate.rs +++ b/crates/schema/src/auto_migrate.rs @@ -202,16 +202,6 @@ pub struct AutoMigratePlan<'def> { pub steps: Vec>, } -impl AutoMigratePlan<'_> { - fn any_step(&self, f: impl Fn(&AutoMigrateStep) -> bool) -> bool { - self.steps.iter().any(f) - } - - fn disconnects_all_users(&self) -> bool { - self.any_step(|step| matches!(step, AutoMigrateStep::DisconnectAllUsers)) - } -} - /// Checks that must be performed before performing an automatic migration. /// These checks can access table contents and other database state. #[derive(PartialEq, Eq, Debug, PartialOrd, Ord)] @@ -250,8 +240,6 @@ pub enum AutoMigrateStep<'def> { RemoveSequence(::Key<'def>), /// Remove a schedule annotation from a table. RemoveSchedule(::Key<'def>), - /// Remove a view and corresponding view table - RemoveView(::Key<'def>), /// Remove a row-level security query. RemoveRowLevelSecurity(::Key<'def>), @@ -280,17 +268,12 @@ pub enum AutoMigrateStep<'def> { AddSequence(::Key<'def>), /// Add a schedule annotation to a table. AddSchedule(::Key<'def>), - /// Add a view and corresponding view table - AddView(::Key<'def>), /// Add a row-level security query. AddRowLevelSecurity(::Key<'def>), /// Change the access of a table. ChangeAccess(::Key<'def>), - /// Recompute a view, update its backing table, and push updates to clients - UpdateView(::Key<'def>), - /// Disconnect all users connected to the module. DisconnectAllUsers, } @@ -445,7 +428,6 @@ pub fn ponder_auto_migrate<'def>(old: &'def ModuleDef, new: &'def ModuleDef) -> prechecks: Vec::new(), }; - let views_ok = auto_migrate_views(&mut plan); let tables_ok = auto_migrate_tables(&mut plan); // Our diffing algorithm will detect added constraints / indexes / sequences in new tables, we use this to filter those out. @@ -464,8 +446,7 @@ pub fn ponder_auto_migrate<'def>(old: &'def ModuleDef, new: &'def ModuleDef) -> // have already been reflected in the database state. let rls_ok = auto_migrate_row_level_security(&mut plan); - let ((), (), (), (), (), ()) = - (views_ok, tables_ok, indexes_ok, sequences_ok, constraints_ok, rls_ok).combine_errors()?; + let ((), (), (), (), ()) = (tables_ok, indexes_ok, sequences_ok, constraints_ok, rls_ok).combine_errors()?; plan.steps.sort(); plan.prechecks.sort(); @@ -508,116 +489,6 @@ fn diff<'def, T: ModuleDefLookup, I: Iterator>( })) } -fn auto_migrate_views(plan: &mut AutoMigratePlan<'_>) -> Result<()> { - diff(plan.old, plan.new, ModuleDef::views) - .map(|table_diff| -> Result<()> { - match table_diff { - Diff::Add { new } => { - plan.steps.push(AutoMigrateStep::AddView(new.key())); - Ok(()) - } - // From the user's perspective, views do not have persistent state. - // Hence removal does not require a manual migration - just disconnecting clients. - Diff::Remove { old } if plan.disconnects_all_users() => { - plan.steps.push(AutoMigrateStep::RemoveView(old.key())); - Ok(()) - } - Diff::Remove { old } => { - plan.steps.push(AutoMigrateStep::RemoveView(old.key())); - plan.steps.push(AutoMigrateStep::DisconnectAllUsers); - Ok(()) - } - Diff::MaybeChange { old, new } => auto_migrate_view(plan, old, new), - } - }) - .collect_all_errors() -} - -fn auto_migrate_view<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def ViewDef, new: &'def ViewDef) -> Result<()> { - let key = old.key(); - - if old.is_public != new.is_public { - plan.steps.push(AutoMigrateStep::ChangeAccess(key)); - } - - // We can always auto-migrate a view because we can always re-compute it. - // However certain things require us to disconnect clients: - // 1. If we add or remove a column or parameter - // 2. If we change the order of the columns or parameters - // 3. If we change the types of the columns or parameters - // 4. If we change the context parameter - let Any(incompatible_return_type) = diff(plan.old, plan.new, |def| { - def.lookup_expect::(key).return_columns.iter() - }) - .map(|col_diff| { - match col_diff { - // We must disconnect clients if we add or remove a parameter or column - Diff::Add { .. } | Diff::Remove { .. } => Any(true), - Diff::MaybeChange { old, new } => { - if old.col_id != new.col_id { - return Any(true); - }; - - ensure_old_ty_upgradable_to_new( - false, - &|| old.view_name.clone(), - &|| old.name.clone(), - &WithTypespace::new(plan.old.typespace(), &old.ty) - .resolve_refs() - .expect("valid ViewDefs must have valid type refs"), - &WithTypespace::new(plan.new.typespace(), &new.ty) - .resolve_refs() - .expect("valid ViewDefs must have valid type refs"), - ) - .unwrap_or(Any(true)) - } - } - }) - .collect(); - - let Any(incompatible_param_types) = diff(plan.old, plan.new, |def| { - def.lookup_expect::(key).param_columns.iter() - }) - .map(|col_diff| { - match col_diff { - // We must disconnect clients if we add or remove a parameter or column - Diff::Add { .. } | Diff::Remove { .. } => Any(true), - Diff::MaybeChange { old, new } => { - if old.col_id != new.col_id { - return Any(true); - }; - - ensure_old_ty_upgradable_to_new( - false, - &|| old.view_name.clone(), - &|| old.name.clone(), - &WithTypespace::new(plan.old.typespace(), &old.ty) - .resolve_refs() - .expect("valid ViewDefs must have valid type refs"), - &WithTypespace::new(plan.new.typespace(), &new.ty) - .resolve_refs() - .expect("valid ViewDefs must have valid type refs"), - ) - .unwrap_or(Any(true)) - } - } - }) - .collect(); - - if old.is_anonymous != new.is_anonymous || incompatible_return_type || incompatible_param_types { - plan.steps.push(AutoMigrateStep::AddView(new.key())); - plan.steps.push(AutoMigrateStep::RemoveView(old.key())); - - if !plan.disconnects_all_users() { - plan.steps.push(AutoMigrateStep::DisconnectAllUsers); - } - } else { - plan.steps.push(AutoMigrateStep::UpdateView(old.key())); - } - - Ok(()) -} - fn auto_migrate_tables(plan: &mut AutoMigratePlan<'_>) -> Result<()> { diff(plan.old, plan.new, ModuleDef::tables) .map(|table_diff| -> Result<()> { @@ -692,13 +563,7 @@ fn auto_migrate_table<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def TableDe let new_ty = WithTypespace::new(plan.new.typespace(), &new.ty) .resolve_refs() .expect("valid TableDef must have valid type refs"); - let types_ok = ensure_old_ty_upgradable_to_new( - false, - &|| old.table_name.clone(), - &|| old.name.clone(), - &old_ty, - &new_ty, - ); + let types_ok = ensure_old_ty_upgradable_to_new(false, old, &old_ty, &new_ty); // Note that the diff algorithm relies on `ModuleDefLookup` for `ColumnDef`, // which looks up columns by NAME, NOT position: precisely to allow this step to work! @@ -729,7 +594,11 @@ fn auto_migrate_table<'def>(plan: &mut AutoMigratePlan<'def>, old: &'def TableDe // If we're adding a column, we'll rewrite the whole table. // That makes any `ChangeColumns` moot, so we can skip it. if columns_added { - if !plan.disconnects_all_users() { + if !plan + .steps + .iter() + .any(|step| matches!(step, AutoMigrateStep::DisconnectAllUsers)) + { plan.steps.push(AutoMigrateStep::DisconnectAllUsers); } plan.steps.push(AutoMigrateStep::AddColumns(key)); @@ -779,21 +648,19 @@ impl + Default, M2: BitOr + Default> FromIte fn ensure_old_ty_upgradable_to_new( within: bool, - old_container_name: &impl Fn() -> Identifier, - old_column_name: &impl Fn() -> Identifier, + old: &ColumnDef, old_ty: &AlgebraicType, new_ty: &AlgebraicType, ) -> Result { use AutoMigrateError::*; // Ensures an `old_ty` within `old` is upgradable to `new_ty`. - let ensure = - |(old_ty, new_ty)| ensure_old_ty_upgradable_to_new(true, old_container_name, old_column_name, old_ty, new_ty); + let ensure = |(old_ty, new_ty)| ensure_old_ty_upgradable_to_new(true, old, old_ty, new_ty); // Returns a `ChangeColumnTypeParts` error using the current `old_ty` and `new_ty`. let parts_for_error = || ChangeColumnTypeParts { - table: old_container_name(), - column: old_column_name(), + table: old.table_name.clone(), + column: old.name.clone(), type1: old_ty.clone().into(), type2: new_ty.clone().into(), }; @@ -896,13 +763,9 @@ fn ensure_old_ty_upgradable_to_new( } // For arrays, we need to check each field's upgradability due to sums. - (AlgebraicType::Array(old_ty), AlgebraicType::Array(new_ty)) => ensure_old_ty_upgradable_to_new( - true, - old_container_name, - old_column_name, - &old_ty.elem_ty, - &new_ty.elem_ty, - ), + (AlgebraicType::Array(old_ty), AlgebraicType::Array(new_ty)) => { + ensure_old_ty_upgradable_to_new(true, old, &old_ty.elem_ty, &new_ty.elem_ty) + } // We only have the simple cases left, and there, no change is good change. (old_ty, new_ty) if old_ty == new_ty => Ok(Any(false)), @@ -1041,15 +904,6 @@ mod tests { use v9::{RawModuleDefV9Builder, TableAccess}; use validate::tests::expect_identifier; - fn create_module_def(build_module: impl Fn(&mut RawModuleDefV9Builder)) -> ModuleDef { - let mut builder = RawModuleDefV9Builder::new(); - build_module(&mut builder); - builder - .finish() - .try_into() - .expect("new_def should be a valid database definition") - } - fn initial_module_def() -> ModuleDef { let mut builder = RawModuleDefV9Builder::new(); let schedule_at = builder.add_type::(); @@ -1105,17 +959,6 @@ mod tests { None, ); - // Add a view and add its return type to the typespace - let view_return_ty = AlgebraicType::product([("a", AlgebraicType::U64), ("b", AlgebraicType::U64)]); - let view_return_ty_ref = builder.add_algebraic_type([], "my_view_return", view_return_ty, true); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32), ("y", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(view_return_ty_ref)), - ); - builder .build_table_with_new_type( "Inspections", @@ -1201,17 +1044,6 @@ mod tests { None, ); - // Add a view and add its return type to the typespace - let view_return_ty = AlgebraicType::product([("a", AlgebraicType::U64)]); - let view_return_ty_ref = builder.add_algebraic_type([], "my_view_return", view_return_ty, true); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(view_return_ty_ref)), - ); - let new_inspections_type = builder .build_table_with_new_type( "Inspections", @@ -1261,7 +1093,6 @@ mod tests { let bananas = expect_identifier("Bananas"); let deliveries = expect_identifier("Deliveries"); let oranges = expect_identifier("Oranges"); - let my_view = expect_identifier("my_view"); let bananas_sequence = "Bananas_id_seq"; let apples_unique_constraint = "Apples_id_key"; @@ -1343,9 +1174,6 @@ mod tests { assert!(steps.contains(&AutoMigrateStep::AddColumns(&bananas)), "{steps:?}"); // Column is changed but it will not reflect in steps due to `AutoMigrateStep::AddColumns` assert!(!steps.contains(&AutoMigrateStep::ChangeColumns(&bananas)), "{steps:?}"); - - assert!(steps.contains(&AutoMigrateStep::RemoveView(&my_view)), "{steps:?}"); - assert!(steps.contains(&AutoMigrateStep::AddView(&my_view)), "{steps:?}"); } #[test] @@ -1752,497 +1580,4 @@ mod tests { .expect("should pretty print") ); } - - #[test] - fn add_view() { - let old_def = create_module_def(|_| {}); - let new_def = create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::array(AlgebraicType::Ref(return_type_ref)), - ); - }); - - let my_view = expect_identifier("my_view"); - - let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed"); - let steps = &plan.steps[..]; - - assert!(!plan.disconnects_all_users(), "{plan:#?}"); - assert!(steps.contains(&AutoMigrateStep::AddView(&my_view)), "{steps:?}"); - assert!(!steps.contains(&AutoMigrateStep::RemoveView(&my_view)), "{steps:?}"); - } - - #[test] - fn remove_view() { - let old_def = create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::array(AlgebraicType::Ref(return_type_ref)), - ); - }); - let new_def = create_module_def(|_| {}); - - let my_view = expect_identifier("my_view"); - - let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed"); - let steps = &plan.steps[..]; - - assert!(plan.disconnects_all_users(), "{plan:#?}"); - assert!(steps.contains(&AutoMigrateStep::RemoveView(&my_view)), "{steps:?}"); - assert!(!steps.contains(&AutoMigrateStep::AddView(&my_view)), "{steps:?}"); - } - - #[test] - fn migrate_view_recompute() { - struct TestCase { - desc: &'static str, - old_def: ModuleDef, - new_def: ModuleDef, - } - - for TestCase { - desc: name, - old_def, - new_def, - } in [ - TestCase { - desc: "Return `Vec` instead of `Option`", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::array(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - TestCase { - desc: "No change; recompute view", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - ] { - let my_view = expect_identifier("my_view"); - - let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed"); - let steps = &plan.steps[..]; - - assert!(!plan.disconnects_all_users(), "{name}, plan: {plan:#?}"); - - assert!( - steps.contains(&AutoMigrateStep::UpdateView(&my_view)), - "{name}, steps: {steps:?}" - ); - assert!( - !steps.contains(&AutoMigrateStep::AddView(&my_view)), - "{name}, steps: {steps:?}" - ); - assert!( - !steps.contains(&AutoMigrateStep::RemoveView(&my_view)), - "{name}, steps: {steps:?}" - ); - } - } - - #[test] - fn migrate_view_disconnect_clients() { - struct TestCase { - desc: &'static str, - old_def: ModuleDef, - new_def: ModuleDef, - } - - for TestCase { - desc: name, - old_def, - new_def, - } in [ - TestCase { - desc: "Change context parameter", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - false, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - TestCase { - desc: "Add parameter", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32), ("y", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - TestCase { - desc: "Remove parameter", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32), ("y", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - TestCase { - desc: "Reorder parameters", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32), ("y", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("y", AlgebraicType::U32), ("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - TestCase { - desc: "Change parameter type", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::String)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - TestCase { - desc: "Add column", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64), ("b", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - TestCase { - desc: "Remove column", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64), ("b", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - TestCase { - desc: "Reorder columns", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64), ("b", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("b", AlgebraicType::U64), ("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - TestCase { - desc: "Change column type", - old_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::U64)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - new_def: create_module_def(|builder| { - let return_type_ref = builder.add_algebraic_type( - [], - "my_view_return_type", - AlgebraicType::product([("a", AlgebraicType::String)]), - true, - ); - builder.add_view( - "my_view", - true, - true, - ProductType::from([("x", AlgebraicType::U32)]), - AlgebraicType::option(AlgebraicType::Ref(return_type_ref)), - ); - }), - }, - ] { - let my_view = expect_identifier("my_view"); - - let plan = ponder_auto_migrate(&old_def, &new_def).expect("auto migration should succeed"); - let steps = &plan.steps[..]; - - assert!(plan.disconnects_all_users(), "{name}, plan: {plan:?}"); - - assert!( - steps.contains(&AutoMigrateStep::AddView(&my_view)), - "{name}, steps: {steps:?}" - ); - assert!( - steps.contains(&AutoMigrateStep::RemoveView(&my_view)), - "{name}, steps: {steps:?}" - ); - assert!( - !steps.contains(&AutoMigrateStep::UpdateView(&my_view)), - "{name}, steps: {steps:?}" - ); - } - } } diff --git a/crates/schema/src/auto_migrate/formatter.rs b/crates/schema/src/auto_migrate/formatter.rs index 06c82920c00..abdf443a9df 100644 --- a/crates/schema/src/auto_migrate/formatter.rs +++ b/crates/schema/src/auto_migrate/formatter.rs @@ -5,7 +5,7 @@ use std::io; use super::{AutoMigratePlan, IndexAlgorithm, ModuleDefLookup, TableDef}; use crate::{ auto_migrate::AutoMigrateStep, - def::{ConstraintData, FunctionKind, ModuleDef, ScheduleDef, ViewDef}, + def::{ConstraintData, FunctionKind, ModuleDef, ScheduleDef}, identifier::Identifier, }; use itertools::Itertools; @@ -32,18 +32,6 @@ fn format_step( plan: &super::AutoMigratePlan, ) -> Result<(), FormattingErrors> { match step { - AutoMigrateStep::AddView(view) => { - let view_info = extract_view_info(*view, plan.new)?; - f.format_view(&view_info, Action::Created) - } - AutoMigrateStep::RemoveView(view) => { - let view_info = extract_view_info(*view, plan.old)?; - f.format_view(&view_info, Action::Removed) - } - // This means the body of the view may have been updated. - // So we must recompute it and send any updates to clients. - // No need to include this step in the formatted plan. - AutoMigrateStep::UpdateView(_) => Ok(()), AutoMigrateStep::AddTable(t) => { let table_info = extract_table_info(*t, plan)?; f.format_add_table(&table_info) @@ -110,8 +98,6 @@ fn format_step( pub enum FormattingErrors { #[error("Table not found: {table}")] TableNotFound { table: Box }, - #[error("View not found: {view}")] - ViewNotFound { view: Box }, #[error("Index not found")] IndexNotFound, #[error("Constraint not found")] @@ -142,7 +128,6 @@ pub enum Action { pub trait MigrationFormatter { fn format_header(&mut self) -> io::Result<()>; fn format_add_table(&mut self, table_info: &TableInfo) -> io::Result<()>; - fn format_view(&mut self, view_info: &ViewInfo, action: Action) -> io::Result<()>; fn format_index(&mut self, index_info: &IndexInfo, action: Action) -> io::Result<()>; fn format_constraint(&mut self, constraint_info: &ConstraintInfo, action: Action) -> io::Result<()>; fn format_sequence(&mut self, sequence_info: &SequenceInfo, action: Action) -> io::Result<()>; @@ -166,26 +151,6 @@ pub struct TableInfo { pub schedule: Option, } -#[derive(Debug, Clone, PartialEq)] -pub struct ViewInfo { - pub name: String, - pub params: Vec, - pub columns: Vec, - pub is_anonymous: bool, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ViewColumnInfo { - pub name: Identifier, - pub type_name: AlgebraicType, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ViewParamInfo { - pub name: Identifier, - pub type_name: AlgebraicType, -} - #[derive(Debug, Clone, PartialEq)] pub struct ColumnInfo { pub name: Identifier, @@ -366,53 +331,6 @@ fn extract_table_info( }) } -fn extract_view_info( - view: ::Key<'_>, - module_def: &ModuleDef, -) -> Result { - let view_def = module_def.view(view).ok_or_else(|| FormattingErrors::ViewNotFound { - view: view.to_string().into(), - })?; - - let name = view_def.name.to_string(); - let is_anonymous = view_def.is_anonymous; - - let params = view_def - .param_columns - .iter() - .map(|column| { - let type_name = WithTypespace::new(module_def.typespace(), &column.ty) - .resolve_refs() - .map_err(|_| FormattingErrors::TypeResolution)?; - Ok(ViewParamInfo { - name: column.name.clone(), - type_name, - }) - }) - .collect::, FormattingErrors>>()?; - - let columns = view_def - .return_columns - .iter() - .map(|column| { - let type_name = WithTypespace::new(module_def.typespace(), &column.ty) - .resolve_refs() - .map_err(|_| FormattingErrors::TypeResolution)?; - Ok(ViewColumnInfo { - name: column.name.clone(), - type_name, - }) - }) - .collect::, FormattingErrors>>()?; - - Ok(ViewInfo { - name, - params, - columns, - is_anonymous, - }) -} - fn extract_index_info( index: ::Key<'_>, module_def: &ModuleDef, diff --git a/crates/schema/src/auto_migrate/termcolor_formatter.rs b/crates/schema/src/auto_migrate/termcolor_formatter.rs index 2bd3fb2cfc1..c5c05f7993a 100644 --- a/crates/schema/src/auto_migrate/termcolor_formatter.rs +++ b/crates/schema/src/auto_migrate/termcolor_formatter.rs @@ -5,8 +5,6 @@ use spacetimedb_lib::{db::raw_def::v9::TableAccess, AlgebraicType}; use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type; use termcolor::{Buffer, Color, ColorChoice, ColorSpec, WriteColor}; -use crate::auto_migrate::formatter::ViewInfo; - use super::formatter::{ AccessChangeInfo, Action, ColumnChange, ColumnChanges, ConstraintInfo, IndexInfo, MigrationFormatter, NewColumns, RlsInfo, ScheduleInfo, SequenceInfo, TableInfo, @@ -229,48 +227,6 @@ impl MigrationFormatter for TermColorFormatter { self.write_line("") } - fn format_view(&mut self, view: &ViewInfo, action: Action) -> io::Result<()> { - self.write_indent()?; - self.buffer.write_all("▸ ".to_string().as_bytes())?; - self.write_action_prefix(&action)?; - self.buffer.write_all(if view.is_anonymous { - b" anonymous view: " - } else { - b" view: " - })?; - self.write_colored(&view.name, Some(self.colors.table_name), true)?; - self.buffer.write_all(b"\n")?; - - self.indent(); - - if !view.params.is_empty() { - self.write_colored_line("Parameters:", Some(self.colors.section_header), true)?; - self.indent(); - for col in &view.params { - self.write_indent()?; - self.buffer.write_all(format!("• {}: ", col.name).as_bytes())?; - self.write_type_name(&col.type_name)?; - self.buffer.write_all(b"\n")?; - } - self.dedent(); - } - - if !view.columns.is_empty() { - self.write_colored_line("Columns:", Some(self.colors.section_header), true)?; - self.indent(); - for col in &view.columns { - self.write_indent()?; - self.buffer.write_all(format!("• {}: ", col.name).as_bytes())?; - self.write_type_name(&col.type_name)?; - self.buffer.write_all(b"\n")?; - } - self.dedent(); - } - - self.dedent(); - self.write_line("") - } - fn format_constraint(&mut self, c: &ConstraintInfo, action: Action) -> io::Result<()> { self.write_action_prefix(&action)?; let cols = c.columns.iter().map(|x| x.to_string()).collect::>().join(", "); diff --git a/crates/schema/src/def.rs b/crates/schema/src/def.rs index 146782fb019..8f23c746736 100644 --- a/crates/schema/src/def.rs +++ b/crates/schema/src/def.rs @@ -240,12 +240,6 @@ impl ModuleDef { self.tables.get(name) } - /// Convenience method to look up a view, possibly by a string. - pub fn view>(&self, name: &K) -> Option<&ViewDef> { - // If the string IS a valid identifier, we can just look it up. - self.views.get(name) - } - /// Convenience method to look up a reducer, possibly by a string. pub fn reducer>(&self, name: &K) -> Option<&ReducerDef> { // If the string IS a valid identifier, we can just look it up. @@ -719,67 +713,6 @@ pub struct ColumnDef { pub default_value: Option, } -/// A struct representing a validated view column -#[derive(Debug, Clone, Eq, PartialEq)] -#[non_exhaustive] -pub struct ViewColumnDef { - /// The name of the column. - pub name: Identifier, - - /// The position of this column in the view's return type. - pub col_id: ColId, - - /// The type of this column. - pub ty: AlgebraicType, - - /// The type of the column, formatted for client code generation. - pub ty_for_generate: AlgebraicTypeUse, - - /// The view this def is stored in. - pub view_name: Identifier, -} - -impl From for ViewColumnDef { - fn from( - ColumnDef { - name, - col_id, - ty, - ty_for_generate, - table_name: view_name, - .. - }: ColumnDef, - ) -> Self { - Self { - name, - col_id, - ty, - ty_for_generate, - view_name, - } - } -} - -/// A struct representing a validated view parameter -#[derive(Debug, Clone, Eq, PartialEq)] -#[non_exhaustive] -pub struct ViewParamDef { - /// The name of the parameter. - pub name: Identifier, - - /// The position of this parameter in the view's parameter list. - pub col_id: ColId, - - /// The type of this parameter. - pub ty: AlgebraicType, - - /// The type of the parameter, formatted for client code generation. - pub ty_for_generate: AlgebraicTypeUse, - - /// The view this def is stored in. - pub view_name: Identifier, -} - /// A constraint definition attached to a table. #[derive(Debug, Clone, Eq, PartialEq)] pub struct ConstraintDef { @@ -1078,27 +1011,10 @@ pub struct ViewDef { /// The return type of the view, formatted for client codegen. pub return_type_for_generate: AlgebraicTypeUse, - /// The return columns of this view. + /// The columns of this view. /// The same information is stored in `return_type`. /// This is just a more convenient-to-access format. - pub return_columns: Vec, - - /// The columns that track the arguments of this view. - /// The same information is stored in `params`. - /// This is just a more convenient-to-access format. - pub param_columns: Vec, -} - -impl ViewDef { - /// Get a column by the column's name. - pub fn get_column_by_name(&self, name: &Identifier) -> Option<&ViewColumnDef> { - self.return_columns.iter().find(|c| &c.name == name) - } - - /// Get a parameter by the parameter's name. - pub fn get_param_by_name(&self, name: &Identifier) -> Option<&ViewParamDef> { - self.param_columns.iter().find(|c| &c.name == name) - } + pub columns: Vec, } impl From for RawViewDefV9 { @@ -1111,8 +1027,7 @@ impl From for RawViewDefV9 { params_for_generate: _, return_type, return_type_for_generate: _, - return_columns: _, - param_columns: _, + columns: _, } = val; RawViewDefV9 { name: name.into(), @@ -1262,40 +1177,6 @@ impl ModuleDefLookup for ColumnDef { } } -impl ModuleDefLookup for ViewColumnDef { - // We don't use `ColId` here because we want this to be portable - // across migrations. - type Key<'a> = (&'a Identifier, &'a Identifier); - - fn key(&self) -> Self::Key<'_> { - (&self.view_name, &self.name) - } - - fn lookup<'a>(module_def: &'a ModuleDef, (view_name, name): Self::Key<'_>) -> Option<&'a Self> { - module_def - .views - .get(view_name) - .and_then(|view| view.get_column_by_name(name)) - } -} - -impl ModuleDefLookup for ViewParamDef { - // We don't use `ColId` here because we want this to be portable - // across migrations. - type Key<'a> = (&'a Identifier, &'a Identifier); - - fn key(&self) -> Self::Key<'_> { - (&self.view_name, &self.name) - } - - fn lookup<'a>(module_def: &'a ModuleDef, (view_name, name): Self::Key<'_>) -> Option<&'a Self> { - module_def - .views - .get(view_name) - .and_then(|view| view.get_param_by_name(name)) - } -} - impl ModuleDefLookup for ConstraintDef { type Key<'a> = &'a str; diff --git a/crates/schema/src/def/validate/v9.rs b/crates/schema/src/def/validate/v9.rs index 3f811dca6ed..b8d92cfd79c 100644 --- a/crates/schema/src/def/validate/v9.rs +++ b/crates/schema/src/def/validate/v9.rs @@ -496,7 +496,7 @@ impl ModuleValidator<'_> { view_name: Cow::Borrowed(&name), position, arg_name, - })?; + }); let return_type_for_generate = self.validate_for_type_use( &TypeLocation::ViewReturn { @@ -505,14 +505,7 @@ impl ModuleValidator<'_> { &return_type, ); - let mut view_in_progress = ViewValidator::new( - name.clone(), - product_type_ref, - product_type, - ¶ms, - ¶ms_for_generate, - self, - ); + let mut view_in_progress = ViewValidator::new(name.clone(), product_type_ref, product_type, self); // Views have the same interface as tables and therefore must be registered in the global namespace. // @@ -523,18 +516,12 @@ impl ModuleValidator<'_> { // See `check_function_names_are_unique`. let name = view_in_progress.add_to_global_namespace(name).and_then(identifier); - let n = product_type.elements.len(); - let return_columns = (0..n) - .map(|id| view_in_progress.validate_view_column_def(id.into())) - .collect_all_errors(); - - let n = params.elements.len(); - let param_columns = (0..n) - .map(|id| view_in_progress.validate_param_column_def(id.into())) + let columns = (0..product_type.elements.len()) + .map(|id| view_in_progress.validate_column_def(id.into())) .collect_all_errors(); - let (name, return_type_for_generate, return_columns, param_columns) = - (name, return_type_for_generate, return_columns, param_columns).combine_errors()?; + let (name, params_for_generate, return_type_for_generate, columns) = + (name, params_for_generate, return_type_for_generate, columns).combine_errors()?; Ok(ViewDef { name, @@ -547,8 +534,7 @@ impl ModuleValidator<'_> { }, return_type, return_type_for_generate, - return_columns, - param_columns, + columns, }) } @@ -696,8 +682,6 @@ impl ModuleValidator<'_> { /// 2. Insert view names into the global namespace. struct ViewValidator<'a, 'b> { inner: TableValidator<'a, 'b>, - params: &'a ProductType, - params_for_generate: &'a [(Identifier, AlgebraicTypeUse)], } impl<'a, 'b> ViewValidator<'a, 'b> { @@ -705,8 +689,6 @@ impl<'a, 'b> ViewValidator<'a, 'b> { raw_name: Box, product_type_ref: AlgebraicTypeRef, product_type: &'a ProductType, - params: &'a ProductType, - params_for_generate: &'a [(Identifier, AlgebraicTypeUse)], module_validator: &'a mut ModuleValidator<'b>, ) -> Self { Self { @@ -717,51 +699,11 @@ impl<'a, 'b> ViewValidator<'a, 'b> { module_validator, has_sequence: Default::default(), }, - params, - params_for_generate, } } - fn validate_param_column_def(&mut self, col_id: ColId) -> Result { - let column = &self - .params - .elements - .get(col_id.idx()) - .expect("enumerate is generating an out-of-range index..."); - - let (_, ty_for_generate) = self - .params_for_generate - .get(col_id.idx()) - .expect("enumerate is generating an out-of-range index..."); - - let name: Result = identifier( - column - .name() - .map(|name| name.into()) - .unwrap_or_else(|| format!("param_{}", col_id).into_boxed_str()), - ); - - // This error will be created multiple times if the view name is invalid, - // but we sort and deduplicate the error stream afterwards, - // so it isn't a huge deal. - // - // This is necessary because we require `ErrorStream` to be nonempty. - // We need to put something in there if the view name is invalid. - let view_name = identifier(self.inner.raw_name.clone()); - - let (name, view_name) = (name, view_name).combine_errors()?; - - Ok(ViewParamDef { - name, - ty: column.algebraic_type.clone(), - ty_for_generate: ty_for_generate.clone(), - col_id, - view_name, - }) - } - - fn validate_view_column_def(&mut self, col_id: ColId) -> Result { - self.inner.validate_column_def(col_id).map(ViewColumnDef::from) + fn validate_column_def(&mut self, col_id: ColId) -> Result { + self.inner.validate_column_def(col_id) } fn add_to_global_namespace(&mut self, name: Box) -> Result> { diff --git a/crates/schema/src/schema.rs b/crates/schema/src/schema.rs index c05104431b6..93c0cf83959 100644 --- a/crates/schema/src/schema.rs +++ b/crates/schema/src/schema.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use crate::def::{ ColumnDef, ConstraintData, ConstraintDef, IndexAlgorithm, IndexDef, ModuleDef, ModuleDefLookup, ScheduleDef, - SequenceDef, TableDef, UniqueConstraintData, ViewColumnDef, ViewDef, ViewParamDef, + SequenceDef, TableDef, UniqueConstraintData, ViewDef, }; use crate::identifier::Identifier; @@ -623,16 +623,15 @@ impl TableSchema { name, is_anonymous, is_public, - params: _, + params, params_for_generate: _, return_type: _, return_type_for_generate: _, - return_columns, - param_columns, + columns: cols, } = view_def; - let num_args = param_columns.len(); - let num_cols = return_columns.len(); + let num_args = params.elements.len(); + let num_cols = cols.len(); let n = num_args + num_cols + if *is_anonymous { 0 } else { 1 }; let mut columns = Vec::with_capacity(n); @@ -648,17 +647,20 @@ impl TableSchema { let n = columns.len(); - let param_iter = param_columns - .iter() - .map(|def| ColumnSchema::from_view_param_def(module_def, def)); + for (i, elem) in params.elements.iter().cloned().enumerate() { + columns.push(ColumnSchema { + table_id: TableId::SENTINEL, + col_pos: (n + i).into(), + col_name: elem.name.unwrap_or_else(|| format!("param_{i}").into_boxed_str()), + col_type: elem.algebraic_type, + }); + } - let column_iter = return_columns - .iter() - .map(|def| ColumnSchema::from_view_column_def(module_def, def)); + let n = columns.len(); columns.extend( - param_iter - .chain(column_iter) + column_schemas_from_defs(module_def, cols, TableId::SENTINEL) + .into_iter() .enumerate() .map(|(i, schema)| ColumnSchema { col_pos: (n + i).into(), @@ -879,30 +881,6 @@ impl ColumnSchema { col_type: ty, } } - - fn from_view_column_def(module_def: &ModuleDef, def: &ViewColumnDef) -> Self { - let col_type = WithTypespace::new(module_def.typespace(), &def.ty) - .resolve_refs() - .expect("validated module should have all types resolve"); - ColumnSchema { - table_id: TableId::SENTINEL, - col_pos: def.col_id, - col_name: (*def.name).into(), - col_type, - } - } - - fn from_view_param_def(module_def: &ModuleDef, def: &ViewParamDef) -> Self { - let col_type = WithTypespace::new(module_def.typespace(), &def.ty) - .resolve_refs() - .expect("validated module should have all types resolve"); - ColumnSchema { - table_id: TableId::SENTINEL, - col_pos: def.col_id, - col_name: (*def.name).into(), - col_type, - } - } } impl Schema for ColumnSchema { diff --git a/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__empty_to_populated_migration.snap b/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__empty_to_populated_migration.snap index 51487bc74fb..564c1bfb0e2 100644 --- a/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__empty_to_populated_migration.snap +++ b/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__empty_to_populated_migration.snap @@ -51,13 +51,5 @@ expression: "plan.pretty_print(PrettyPrintStyle::AnsiColor).expect(\"should pret Auto-increment constraints: • Inspections_scheduled_id_seq on scheduled_id -▸ ▸ Created anonymous view: my_view - Parameters: - • x: U32 - • y: U32 - Columns: - • a: U64 - • b: U64 - ▸ Created row level security policy: `SELECT * FROM Apples` diff --git a/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__updated pretty print no color.snap b/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__updated pretty print no color.snap index bb6cbc98ef0..c317d1216fc 100644 --- a/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__updated pretty print no color.snap +++ b/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__updated pretty print no color.snap @@ -1,6 +1,6 @@ --- source: crates/schema/src/auto_migrate.rs -expression: "plan.pretty_print(PrettyPrintStyle::NoColor).expect(\"should pretty print\")" +expression: "plan.pretty_print(true).expect(\"should pretty print\")" --- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Database Migration Plan @@ -10,14 +10,6 @@ Database Migration Plan ▸ Removed unique constraint Apples_id_key on [id] of table Apples ▸ Removed auto-increment constraint Apples_id_seq on column id of table Apples ▸ Removed schedule for table Deliveries_sched calling reducer check_deliveries -▸ ▸ Removed anonymous view: my_view - Parameters: - • x: U32 - • y: U32 - Columns: - • a: U64 - • b: U64 - ▸ Removed row level security policy: `SELECT * FROM Apples` ▸ Changed columns for table Apples @@ -39,12 +31,6 @@ Database Migration Plan ▸ Created index Apples_id_count_idx_btree on [id, count] of table Apples ▸ Created auto-increment constraint Bananas_id_seq on column id of table Bananas ▸ Created schedule for table Inspections_sched calling reducer perform_inspection -▸ ▸ Created anonymous view: my_view - Parameters: - • x: U32 - Columns: - • a: U64 - ▸ Created row level security policy: `SELECT * FROM Bananas` ▸ Changed access for table Bananas (public → private) diff --git a/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__updated pretty print.snap b/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__updated pretty print.snap index b9a9b7827bd..b6281e07036 100644 --- a/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__updated pretty print.snap +++ b/crates/schema/src/snapshots/spacetimedb_schema__auto_migrate__tests__updated pretty print.snap @@ -10,14 +10,6 @@ expression: "plan.pretty_print(PrettyPrintStyle::AnsiColor).expect(\"should pret ▸ Removed unique constraint Apples_id_key on [id] of table Apples ▸ Removed auto-increment constraint Apples_id_seq on column id of table Apples ▸ Removed schedule for table Deliveries_sched calling reducer check_deliveries -▸ ▸ Removed anonymous view: my_view - Parameters: - • x: U32 - • y: U32 - Columns: - • a: U64 - • b: U64 - ▸ Removed row level security policy: `SELECT * FROM Apples` ▸ Changed columns for table Apples @@ -39,12 +31,6 @@ expression: "plan.pretty_print(PrettyPrintStyle::AnsiColor).expect(\"should pret ▸ Created index Apples_id_count_idx_btree on [id, count] of table Apples ▸ Created auto-increment constraint Bananas_id_seq on column id of table Bananas ▸ Created schedule for table Inspections_sched calling reducer perform_inspection -▸ ▸ Created anonymous view: my_view - Parameters: - • x: U32 - Columns: - • a: U64 - ▸ Created row level security policy: `SELECT * FROM Bananas` ▸ Changed access for table Bananas (public → private) From 48e6689107aace704651eac66fa0ab0f21ad4bec Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:29 -0700 Subject: [PATCH 09/16] [release/v1.7.0]: Revert "Make `name` a required parameter of `view` macro (#3480)" This reverts commit 8f11ac81e32d3b8af5db71d9623474b6d6d39928. --- crates/bindings-macro/src/lib.rs | 2 +- crates/bindings-macro/src/view.rs | 57 +++++----- crates/bindings/src/lib.rs | 10 +- crates/bindings/tests/ui/views.rs | 34 +++--- crates/bindings/tests/ui/views.stderr | 156 +++++++++++++------------- crates/schema/src/schema.rs | 4 +- smoketests/tests/views.py | 6 +- 7 files changed, 129 insertions(+), 140 deletions(-) diff --git a/crates/bindings-macro/src/lib.rs b/crates/bindings-macro/src/lib.rs index c82ac7429df..3bf1c54dc2b 100644 --- a/crates/bindings-macro/src/lib.rs +++ b/crates/bindings-macro/src/lib.rs @@ -125,7 +125,7 @@ pub fn reducer(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream { #[proc_macro_attribute] pub fn view(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream { cvt_attr::(args, item, quote!(), |args, original_function| { - let args = view::ViewArgs::parse(args, &original_function.sig.ident)?; + let args = view::ViewArgs::parse(args)?; view::view_impl(args, original_function) }) } diff --git a/crates/bindings-macro/src/view.rs b/crates/bindings-macro/src/view.rs index 8575a226e06..e409cddd11f 100644 --- a/crates/bindings-macro/src/view.rs +++ b/crates/bindings-macro/src/view.rs @@ -1,56 +1,55 @@ -use heck::ToSnakeCase; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::ext::IdentExt; use syn::parse::Parser; use syn::{FnArg, ItemFn}; use crate::sym; -use crate::util::{check_duplicate_msg, match_meta}; +use crate::util::{ident_to_litstr, match_meta}; pub(crate) struct ViewArgs { - name: Ident, #[allow(unused)] public: bool, } impl ViewArgs { - /// Parse `#[view(name = ..., public)]` where both `name` and `public` are required. - pub(crate) fn parse(input: TokenStream, func_ident: &Ident) -> syn::Result { - let mut name = None; - let mut public = None; + /// Parse `#[view(public)]` where `public` is required. + pub(crate) fn parse(input: TokenStream) -> syn::Result { + if input.is_empty() { + return Err(syn::Error::new( + Span::call_site(), + "views must be declared as `#[view(public)]`; `public` is required", + )); + } + let mut public = false; syn::meta::parser(|meta| { match_meta!(match meta { - sym::name => { - check_duplicate_msg(&name, &meta, "`name` already specified")?; - name = Some(meta.value()?.parse()?); - } sym::public => { - check_duplicate_msg(&public, &meta, "`public` already specified")?; - public = Some(()); + if public { + return Err(syn::Error::new( + Span::call_site(), + "duplicate attribute argument: `public`", + )); + } + public = true; } }); Ok(()) }) .parse2(input)?; - let name = name.ok_or_else(|| { - let view = func_ident.to_string().to_snake_case(); - syn::Error::new( + if !public { + return Err(syn::Error::new( Span::call_site(), - format_args!("must specify view name, e.g. `#[spacetimedb::view(name = {view})]"), - ) - })?; - let () = public - .ok_or_else(|| syn::Error::new(Span::call_site(), "views must be `public`, e.g. `#[view(public)]`"))?; - Ok(Self { name, public: true }) + "views must be declared as `#[view(public)]`; `public` is required", + )); + } + Ok(Self { public }) } } -pub(crate) fn view_impl(args: ViewArgs, original_function: &ItemFn) -> syn::Result { - let vis = &original_function.vis; +pub(crate) fn view_impl(_args: ViewArgs, original_function: &ItemFn) -> syn::Result { let func_name = &original_function.sig.ident; - let view_ident = args.name; - let view_name = view_ident.unraw().to_string(); + let view_name = ident_to_litstr(func_name); + let vis = &original_function.vis; for param in &original_function.sig.generics.params { let err = |msg| syn::Error::new_spanned(param, msg); @@ -117,7 +116,7 @@ pub(crate) fn view_impl(args: ViewArgs, original_function: &ItemFn) -> syn::Resu } }; - let register_describer_symbol = format!("__preinit__20_register_describer_{}", view_name); + let register_describer_symbol = format!("__preinit__20_register_describer_{}", view_name.value()); let lt_params = &original_function.sig.generics; let lt_where_clause = <_params.where_clause; diff --git a/crates/bindings/src/lib.rs b/crates/bindings/src/lib.rs index 7e63f2ecc05..9e7ad78852a 100644 --- a/crates/bindings/src/lib.rs +++ b/crates/bindings/src/lib.rs @@ -819,25 +819,25 @@ pub use spacetimedb_bindings_macro::procedure; /// } /// /// // A view that selects at most one row from a table -/// #[view(name = my_player, public)] +/// #[view(public)] /// fn my_player(ctx: &ViewContext) -> Option { /// ctx.db.player().identity().find(ctx.sender) /// } /// /// // An example of column projection -/// #[view(name = my_player_id, public)] +/// #[view(public)] /// fn my_player_id(ctx: &ViewContext) -> Option { /// ctx.db.player().identity().find(ctx.sender).map(|Player { id, .. }| PlayerId { id }) /// } /// /// // An example of a parameterized view -/// #[view(name = players_at_level, public)] +/// #[view(public)] /// fn players_at_level(ctx: &AnonymousViewContext, level: u32) -> Vec { /// ctx.db.player().level().filter(level).collect() /// } /// /// // An example that is analogous to a semijoin in sql -/// #[view(name = players_at_coordinates, public)] +/// #[view(public)] /// fn players_at_coordinates(ctx: &AnonymousViewContext, x: u64, y: u64) -> Vec { /// ctx /// .db @@ -849,7 +849,7 @@ pub use spacetimedb_bindings_macro::procedure; /// } /// /// // An example of a join that combines fields from two different tables -/// #[view(name = players_with_coordinates, public)] +/// #[view(public)] /// fn players_with_coordinates(ctx: &AnonymousViewContext, x: u64, y: u64) -> Vec { /// ctx /// .db diff --git a/crates/bindings/tests/ui/views.rs b/crates/bindings/tests/ui/views.rs index 56c29161bea..5e5bdb09d94 100644 --- a/crates/bindings/tests/ui/views.rs +++ b/crates/bindings/tests/ui/views.rs @@ -72,60 +72,54 @@ struct Player { struct NotSpacetimeType {} -/// Private views not allowed; must be `#[view(public, ...)]` -#[view(name = view_def_no_public)] +/// Private views not allowed; must be `#[view(public)]` +#[view] fn view_def_no_public(_: &ViewContext) -> Vec { vec![] } /// Duplicate `public` -#[view(name = view_def_dup_public, public, public)] -fn view_def_dup_public() -> Vec { - vec![] -} - -/// Duplicate `name` -#[view(name = view_def_dup_name, name = view_def_dup_name, public)] -fn view_def_dup_name() -> Vec { +#[view(public, public)] +fn view_def_duplicate_attribute_arg() -> Vec { vec![] } /// Unsupported attribute arg -#[view(name = view_def_unsupported_arg, public, anonymous)] -fn view_def_unsupported_arg() -> Vec { +#[view(public, anonymous)] +fn view_def_unsupported_attribute_arg() -> Vec { vec![] } /// A `ViewContext` is required -#[view(name = view_def_no_context, public)] +#[view(public)] fn view_def_no_context() -> Vec { vec![] } /// A `ViewContext` is required -#[view(name = view_def_wrong_context, public)] +#[view(public)] fn view_def_wrong_context(_: &ReducerContext) -> Vec { vec![] } /// Must pass the `ViewContext` by ref -#[view(name = view_def_pass_context_by_value, public)] +#[view(public)] fn view_def_pass_context_by_value(_: ViewContext) -> Vec { vec![] } /// The view context must be the first parameter -#[view(name = view_def_wrong_context_position, public)] +#[view(public)] fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec { vec![] } /// Must return `Vec` or `Option` where `T` is a SpacetimeType -#[view(name = view_def_no_return, public)] +#[view(public)] fn view_def_no_return(_: &ViewContext) {} /// Must return `Vec` or `Option` where `T` is a SpacetimeType -#[view(name = view_def_wrong_return, public)] +#[view(public)] fn view_def_wrong_return(_: &ViewContext) -> Player { Player { identity: Identity::ZERO, @@ -133,7 +127,7 @@ fn view_def_wrong_return(_: &ViewContext) -> Player { } /// Must return `Vec` or `Option` where `T` is a SpacetimeType -#[view(name = view_def_returns_not_a_spacetime_type, public)] +#[view(public)] fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { None } @@ -150,7 +144,7 @@ struct ScheduledTable { } /// Cannot use a view as a scheduled function -#[view(name = scheduled_table_view, public)] +#[view(public)] fn scheduled_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { vec![] } diff --git a/crates/bindings/tests/ui/views.stderr b/crates/bindings/tests/ui/views.stderr index 1693aaa1e71..1f7b0ad4f78 100644 --- a/crates/bindings/tests/ui/views.stderr +++ b/crates/bindings/tests/ui/views.stderr @@ -1,52 +1,48 @@ -error: views must be `public`, e.g. `#[view(public)]` +error: views must be declared as `#[view(public)]`; `public` is required --> tests/ui/views.rs:76:1 | -76 | #[view(name = view_def_no_public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +76 | #[view] + | ^^^^^^^ | = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) -error: `public` already specified - --> tests/ui/views.rs:82:44 +error: duplicate attribute argument: `public` + --> tests/ui/views.rs:82:1 | -82 | #[view(name = view_def_dup_public, public, public)] - | ^^^^^^ - -error: `name` already specified - --> tests/ui/views.rs:88:34 +82 | #[view(public, public)] + | ^^^^^^^^^^^^^^^^^^^^^^^ | -88 | #[view(name = view_def_dup_name, name = view_def_dup_name, public)] - | ^^^^ + = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) -error: expected `name` or `public` - --> tests/ui/views.rs:94:49 +error: expected `public` + --> tests/ui/views.rs:88:16 | -94 | #[view(name = view_def_unsupported_arg, public, anonymous)] - | ^^^^^^^^^ +88 | #[view(public, anonymous)] + | ^^^^^^^^^ error: Views must always have a context parameter: `&ViewContext` or `&AnonymousViewContext` - --> tests/ui/views.rs:101:1 - | -101 | fn view_def_no_context() -> Vec { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui/views.rs:95:1 + | +95 | fn view_def_no_context() -> Vec { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: The first parameter of a view must be a context parameter: `&ViewContext` or `&AnonymousViewContext`; passed by reference - --> tests/ui/views.rs:113:38 + --> tests/ui/views.rs:107:38 | -113 | fn view_def_pass_context_by_value(_: ViewContext) -> Vec { +107 | fn view_def_pass_context_by_value(_: ViewContext) -> Vec { | ^^^^^^^^^^^ error: views must return `Vec` or `Option` where `T` is a `SpacetimeType` - --> tests/ui/views.rs:125:1 + --> tests/ui/views.rs:119:1 | -125 | fn view_def_no_return(_: &ViewContext) {} +119 | fn view_def_no_return(_: &ViewContext) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `ViewKind: ViewKindTrait` is not satisfied - --> tests/ui/views.rs:106:1 + --> tests/ui/views.rs:100:1 | -106 | #[view(name = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ViewKindTrait` is not implemented for `ViewKind` +100 | #[view(public)] + | ^^^^^^^^^^^^^^^ the trait `ViewKindTrait` is not implemented for `ViewKind` | = help: the following other types implement trait `ViewKindTrait`: ViewKind @@ -54,18 +50,18 @@ error[E0277]: the trait bound `ViewKind: ViewKindTrait` is not s = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0276]: impl has stricter requirements than trait - --> tests/ui/views.rs:106:1 + --> tests/ui/views.rs:100:1 | -106 | #[view(name = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl has extra requirement `ViewKind: ViewKindTrait` +100 | #[view(public)] + | ^^^^^^^^^^^^^^^ impl has extra requirement `ViewKind: ViewKindTrait` | = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `ViewKind: ViewKindTrait` is not satisfied - --> tests/ui/views.rs:118:1 + --> tests/ui/views.rs:112:1 | -118 | #[view(name = view_def_wrong_context_position, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ViewKindTrait` is not implemented for `ViewKind` +112 | #[view(public)] + | ^^^^^^^^^^^^^^^ the trait `ViewKindTrait` is not implemented for `ViewKind` | = help: the following other types implement trait `ViewKindTrait`: ViewKind @@ -73,10 +69,10 @@ error[E0277]: the trait bound `ViewKind: ViewKindTrait` is not satisfied = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0276]: impl has stricter requirements than trait - --> tests/ui/views.rs:118:1 + --> tests/ui/views.rs:112:1 | -118 | #[view(name = view_def_wrong_context_position, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl has extra requirement `ViewKind: ViewKindTrait` +112 | #[view(public)] + | ^^^^^^^^^^^^^^^ impl has extra requirement `ViewKind: ViewKindTrait` | = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -172,10 +168,10 @@ error[E0599]: no method named `delete` found for struct `RangedIndexReadOnly` in | ^^^^^^ method not found in `RangedIndexReadOnly` error[E0599]: no function or associated item named `register` found for struct `ViewRegistrar` in the current scope - --> tests/ui/views.rs:106:1 + --> tests/ui/views.rs:100:1 | -106 | #[view(name = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `ViewRegistrar` +100 | #[view(public)] + | ^^^^^^^^^^^^^^^ function or associated item not found in `ViewRegistrar` | = note: the function or associated item was found for - `ViewRegistrar` @@ -183,9 +179,9 @@ error[E0599]: no function or associated item named `register` found for struct ` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: The first parameter of a `#[view]` must be `&ViewContext` or `&AnonymousViewContext` - --> tests/ui/views.rs:107:31 + --> tests/ui/views.rs:101:31 | -107 | fn view_def_wrong_context(_: &ReducerContext) -> Vec { +101 | fn view_def_wrong_context(_: &ReducerContext) -> Vec { | ^^^^^^^^^^^^^^ the trait `ViewContextArg` is not implemented for `ReducerContext` | = help: the following other types implement trait `ViewContextArg`: @@ -193,10 +189,10 @@ error[E0277]: The first parameter of a `#[view]` must be `&ViewContext` or `&Ano ViewContext error[E0599]: no function or associated item named `invoke` found for struct `ViewDispatcher` in the current scope - --> tests/ui/views.rs:106:1 + --> tests/ui/views.rs:100:1 | -106 | #[view(name = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `ViewDispatcher` +100 | #[view(public)] + | ^^^^^^^^^^^^^^^ function or associated item not found in `ViewDispatcher` | = note: the function or associated item was found for - `ViewDispatcher` @@ -204,10 +200,10 @@ error[E0599]: no function or associated item named `invoke` found for struct `Vi = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no function or associated item named `register` found for struct `ViewRegistrar` in the current scope - --> tests/ui/views.rs:118:1 + --> tests/ui/views.rs:112:1 | -118 | #[view(name = view_def_wrong_context_position, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `ViewRegistrar` +112 | #[view(public)] + | ^^^^^^^^^^^^^^^ function or associated item not found in `ViewRegistrar` | = note: the function or associated item was found for - `ViewRegistrar` @@ -215,9 +211,9 @@ error[E0599]: no function or associated item named `register` found for struct ` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: The first parameter of a `#[view]` must be `&ViewContext` or `&AnonymousViewContext` - --> tests/ui/views.rs:119:40 + --> tests/ui/views.rs:113:40 | -119 | fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec { +113 | fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec { | ^^^ the trait `ViewContextArg` is not implemented for `u32` | = help: the following other types implement trait `ViewContextArg`: @@ -225,9 +221,9 @@ error[E0277]: The first parameter of a `#[view]` must be `&ViewContext` or `&Ano ViewContext error[E0277]: the view argument `&ViewContext` does not implement `SpacetimeType` - --> tests/ui/views.rs:119:48 + --> tests/ui/views.rs:113:48 | -119 | fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec { +113 | fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec { | ^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `ViewContext` | = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition @@ -245,10 +241,10 @@ error[E0277]: the view argument `&ViewContext` does not implement `SpacetimeType = note: required for `&ViewContext` to implement `ViewArg` error[E0599]: no function or associated item named `invoke` found for struct `ViewDispatcher` in the current scope - --> tests/ui/views.rs:118:1 + --> tests/ui/views.rs:112:1 | -118 | #[view(name = view_def_wrong_context_position, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `ViewDispatcher` +112 | #[view(public)] + | ^^^^^^^^^^^^^^^ function or associated item not found in `ViewDispatcher` | = note: the function or associated item was found for - `ViewDispatcher` @@ -256,10 +252,10 @@ error[E0599]: no function or associated item named `invoke` found for struct `Vi = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: invalid view signature - --> tests/ui/views.rs:128:1 + --> tests/ui/views.rs:122:1 | -128 | #[view(name = view_def_wrong_return, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this view signature is not valid +122 | #[view(public)] + | ^^^^^^^^^^^^^^^ this view signature is not valid | = help: the trait `spacetimedb::rt::View<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a ViewContext) -> Player {view_def_wrong_return}` = note: @@ -278,9 +274,9 @@ note: required by a bound in `ViewRegistrar::::register` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: Views must return `Vec` or `Option` where `T` is a `SpacetimeType` - --> tests/ui/views.rs:129:46 + --> tests/ui/views.rs:123:46 | -129 | fn view_def_wrong_return(_: &ViewContext) -> Player { +123 | fn view_def_wrong_return(_: &ViewContext) -> Player { | ^^^^^^ the trait `ViewReturn` is not implemented for `Player` | = help: the following other types implement trait `ViewReturn`: @@ -288,10 +284,10 @@ error[E0277]: Views must return `Vec` or `Option` where `T` is a `Spacetim Vec error[E0277]: invalid view signature - --> tests/ui/views.rs:128:1 + --> tests/ui/views.rs:122:1 | -128 | #[view(name = view_def_wrong_return, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this view signature is not valid +122 | #[view(public)] + | ^^^^^^^^^^^^^^^ this view signature is not valid | = help: the trait `spacetimedb::rt::View<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a ViewContext) -> Player {view_def_wrong_return}` = note: @@ -310,10 +306,10 @@ note: required by a bound in `ViewDispatcher::::invoke` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied - --> tests/ui/views.rs:136:1 + --> tests/ui/views.rs:130:1 | -136 | #[view(name = view_def_returns_not_a_spacetime_type, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `NotSpacetimeType` +130 | #[view(public)] + | ^^^^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `NotSpacetimeType` | = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition = help: the following other types implement trait `SpacetimeType`: @@ -338,10 +334,10 @@ note: required by a bound in `ViewRegistrar::::register` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `NotSpacetimeType: Serialize` is not satisfied - --> tests/ui/views.rs:136:1 + --> tests/ui/views.rs:130:1 | -136 | #[view(name = view_def_returns_not_a_spacetime_type, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `NotSpacetimeType` +130 | #[view(public)] + | ^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `NotSpacetimeType` | = help: the following other types implement trait `Serialize`: &T @@ -365,9 +361,9 @@ note: required by a bound in `ViewRegistrar::::register` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied - --> tests/ui/views.rs:137:71 + --> tests/ui/views.rs:131:71 | -137 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { +131 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `NotSpacetimeType` | = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition @@ -384,10 +380,10 @@ error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied = note: required for `Option` to implement `ViewReturn` error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied - --> tests/ui/views.rs:136:1 + --> tests/ui/views.rs:130:1 | -136 | #[view(name = view_def_returns_not_a_spacetime_type, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `NotSpacetimeType` +130 | #[view(public)] + | ^^^^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `NotSpacetimeType` | = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition = help: the following other types implement trait `SpacetimeType`: @@ -412,10 +408,10 @@ note: required by a bound in `ViewDispatcher::::invoke` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `NotSpacetimeType: Serialize` is not satisfied - --> tests/ui/views.rs:136:1 + --> tests/ui/views.rs:130:1 | -136 | #[view(name = view_def_returns_not_a_spacetime_type, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `NotSpacetimeType` +130 | #[view(public)] + | ^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `NotSpacetimeType` | = help: the following other types implement trait `Serialize`: &T @@ -439,9 +435,9 @@ note: required by a bound in `ViewDispatcher::::invoke` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied - --> tests/ui/views.rs:137:71 + --> tests/ui/views.rs:131:71 | -137 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { +131 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `NotSpacetimeType` | = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition @@ -458,9 +454,9 @@ error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied = note: required for `Option` to implement `SpacetimeType` error[E0277]: invalid signature for scheduled table reducer or procedure - --> tests/ui/views.rs:142:56 + --> tests/ui/views.rs:136:56 | -142 | #[spacetimedb::table(name = scheduled_table, scheduled(scheduled_table_view))] +136 | #[spacetimedb::table(name = scheduled_table, scheduled(scheduled_table_view))] | -------------------------------------------------------^^^^^^^^^^^^^^^^^^^^--- | | | | | unsatisfied trait bound diff --git a/crates/schema/src/schema.rs b/crates/schema/src/schema.rs index 93c0cf83959..2e2b6d2366d 100644 --- a/crates/schema/src/schema.rs +++ b/crates/schema/src/schema.rs @@ -596,10 +596,10 @@ impl TableSchema { /// b: u32, /// } /// - /// #[view(name = my_view, public)] + /// #[view(public)] /// fn my_view(ctx: &ViewContext, x: u32, y: u32) -> Vec { ... } /// - /// #[view(name = my_anonymous_view, public)] + /// #[view(public)] /// fn my_anonymous_view(ctx: &AnonymousViewContext, x: u32, y: u32) -> Vec { ... } /// ``` /// diff --git a/smoketests/tests/views.py b/smoketests/tests/views.py index 49e97f27906..d5a2ffd163a 100644 --- a/smoketests/tests/views.py +++ b/smoketests/tests/views.py @@ -14,7 +14,7 @@ class Views(Smoketest): level: u64, } -#[spacetimedb::view(name = player, public)] +#[spacetimedb::view(public)] pub fn player(ctx: &ViewContext, id: u64) -> Option { ctx.db.player_state().id().find(id) } @@ -60,7 +60,7 @@ class FailPublish(Smoketest): name: String, } -#[spacetimedb::view(name = person, public)] +#[spacetimedb::view(public)] pub fn person(ctx: &ViewContext) -> Option { None } @@ -76,7 +76,7 @@ class FailPublish(Smoketest): C, } -#[spacetimedb::view(name = person, public)] +#[spacetimedb::view(public)] pub fn person(ctx: &ViewContext) -> Option { None } From 59e566df603d10fddab942b4fe82b1b488911393 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:29 -0700 Subject: [PATCH 10/16] [release/v1.7.0]: Revert "Rust module bindings and macros for defining procedures (#3444)" This reverts commit dcd8640543b7d0d6d933591d0df3c518a717fbf8. --- crates/bindings-macro/src/lib.rs | 9 - crates/bindings-macro/src/procedure.rs | 130 ----------- crates/bindings-macro/src/reducer.rs | 47 ++-- crates/bindings-macro/src/table.rs | 33 +-- crates/bindings-macro/src/view.rs | 3 - crates/bindings-sys/src/lib.rs | 8 - crates/bindings/src/lib.rs | 164 +------------ crates/bindings/src/rt.rs | 286 +++-------------------- crates/bindings/src/table.rs | 2 +- crates/bindings/tests/ui/reducers.stderr | 8 +- crates/bindings/tests/ui/views.stderr | 28 ++- modules/module-test/src/lib.rs | 21 +- 12 files changed, 78 insertions(+), 661 deletions(-) delete mode 100644 crates/bindings-macro/src/procedure.rs diff --git a/crates/bindings-macro/src/lib.rs b/crates/bindings-macro/src/lib.rs index 3bf1c54dc2b..e5ca3ad52c7 100644 --- a/crates/bindings-macro/src/lib.rs +++ b/crates/bindings-macro/src/lib.rs @@ -8,7 +8,6 @@ // // (private documentation for the macro authors is totally fine here and you SHOULD write that!) -mod procedure; mod reducer; mod sats; mod table; @@ -106,14 +105,6 @@ mod sym { } } -#[proc_macro_attribute] -pub fn procedure(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream { - cvt_attr::(args, item, quote!(), |args, original_function| { - let args = procedure::ProcedureArgs::parse(args)?; - procedure::procedure_impl(args, original_function) - }) -} - #[proc_macro_attribute] pub fn reducer(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream { cvt_attr::(args, item, quote!(), |args, original_function| { diff --git a/crates/bindings-macro/src/procedure.rs b/crates/bindings-macro/src/procedure.rs deleted file mode 100644 index 0f4013d96ae..00000000000 --- a/crates/bindings-macro/src/procedure.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::reducer::{assert_only_lifetime_generics, extract_typed_args}; -use crate::sym; -use crate::util::{check_duplicate, ident_to_litstr, match_meta}; -use proc_macro2::TokenStream; -use quote::quote; -use syn::parse::Parser as _; -use syn::{ItemFn, LitStr}; - -#[derive(Default)] -pub(crate) struct ProcedureArgs { - /// For consistency with reducers: allow specifying a different export name than the Rust function name. - name: Option, -} - -impl ProcedureArgs { - pub(crate) fn parse(input: TokenStream) -> syn::Result { - let mut args = Self::default(); - syn::meta::parser(|meta| { - match_meta!(match meta { - sym::name => { - check_duplicate(&args.name, &meta)?; - args.name = Some(meta.value()?.parse()?); - } - }); - Ok(()) - }) - .parse2(input)?; - Ok(args) - } -} - -pub(crate) fn procedure_impl(args: ProcedureArgs, original_function: &ItemFn) -> syn::Result { - let func_name = &original_function.sig.ident; - let vis = &original_function.vis; - - let procedure_name = args.name.unwrap_or_else(|| ident_to_litstr(func_name)); - - assert_only_lifetime_generics(original_function, "procedures")?; - - let typed_args = extract_typed_args(original_function)?; - - // TODO: Require that procedures be `async` functions syntactically, - // and use `futures_util::FutureExt::now_or_never` to poll them. - // if !&original_function.sig.asyncness.is_some() { - // return Err(syn::Error::new_spanned( - // original_function.sig.clone(), - // "procedures must be `async`", - // )); - // }; - - // Extract all function parameter names. - let opt_arg_names = typed_args.iter().map(|arg| { - if let syn::Pat::Ident(i) = &*arg.pat { - let name = i.ident.to_string(); - quote!(Some(#name)) - } else { - quote!(None) - } - }); - - let arg_tys = typed_args.iter().map(|arg| arg.ty.as_ref()).collect::>(); - let first_arg_ty = arg_tys.first().into_iter(); - let rest_arg_tys = arg_tys.iter().skip(1); - - // Extract the return type. - let ret_ty_for_assert = match &original_function.sig.output { - syn::ReturnType::Default => None, - syn::ReturnType::Type(_, t) => Some(&**t), - } - .into_iter(); - - let ret_ty_for_info = match &original_function.sig.output { - syn::ReturnType::Default => quote!(()), - syn::ReturnType::Type(_, t) => quote!(#t), - }; - - let register_describer_symbol = format!("__preinit__20_register_describer_{}", procedure_name.value()); - - let lifetime_params = &original_function.sig.generics; - let lifetime_where_clause = &lifetime_params.where_clause; - - let generated_describe_function = quote! { - #[export_name = #register_describer_symbol] - pub extern "C" fn __register_describer() { - spacetimedb::rt::register_procedure::<_, _, #func_name>(#func_name) - } - }; - - Ok(quote! { - const _: () = { - #generated_describe_function - }; - #[allow(non_camel_case_types)] - #vis struct #func_name { _never: ::core::convert::Infallible } - const _: () = { - fn _assert_args #lifetime_params () #lifetime_where_clause { - #(let _ = <#first_arg_ty as spacetimedb::rt::ProcedureContextArg>::_ITEM;)* - #(let _ = <#rest_arg_tys as spacetimedb::rt::ProcedureArg>::_ITEM;)* - #(let _ = <#ret_ty_for_assert as spacetimedb::rt::IntoProcedureResult>::to_result;)* - } - }; - impl #func_name { - fn invoke(__ctx: spacetimedb::ProcedureContext, __args: &[u8]) -> spacetimedb::ProcedureResult { - spacetimedb::rt::invoke_procedure(#func_name, __ctx, __args) - } - } - #[automatically_derived] - impl spacetimedb::rt::FnInfo for #func_name { - /// The type of this function. - type Invoke = spacetimedb::rt::ProcedureFn; - - /// The function kind, which will cause scheduled tables to accept procedures. - type FnKind = spacetimedb::rt::FnKindProcedure<#ret_ty_for_info>; - - /// The name of this function - const NAME: &'static str = #procedure_name; - - /// The parameter names of this function - const ARG_NAMES: &'static [Option<&'static str>] = &[#(#opt_arg_names),*]; - - /// The pointer for invoking this function - const INVOKE: spacetimedb::rt::ProcedureFn = #func_name::invoke; - - /// The return type of this function - fn return_type(ts: &mut impl spacetimedb::sats::typespace::TypespaceBuilder) -> Option { - Some(<#ret_ty_for_info as spacetimedb::SpacetimeType>::make_type(ts)) - } - } - }) -} diff --git a/crates/bindings-macro/src/reducer.rs b/crates/bindings-macro/src/reducer.rs index 5adf4075284..2ca10c48b4f 100644 --- a/crates/bindings-macro/src/reducer.rs +++ b/crates/bindings-macro/src/reducer.rs @@ -4,7 +4,7 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::parse::Parser as _; use syn::spanned::Spanned; -use syn::{FnArg, Ident, ItemFn, LitStr, PatType}; +use syn::{FnArg, Ident, ItemFn, LitStr}; #[derive(Default)] pub(crate) struct ReducerArgs { @@ -59,29 +59,25 @@ impl ReducerArgs { } } -pub(crate) fn assert_only_lifetime_generics(original_function: &ItemFn, function_kind_plural: &str) -> syn::Result<()> { +pub(crate) fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn::Result { + let func_name = &original_function.sig.ident; + let vis = &original_function.vis; + + let reducer_name = args.name.unwrap_or_else(|| ident_to_litstr(func_name)); + for param in &original_function.sig.generics.params { let err = |msg| syn::Error::new_spanned(param, msg); match param { syn::GenericParam::Lifetime(_) => {} - syn::GenericParam::Type(_) => { - return Err(err(format!( - "type parameters are not allowed on {function_kind_plural}" - ))) - } - syn::GenericParam::Const(_) => { - return Err(err(format!( - "const parameters are not allowed on {function_kind_plural}" - ))) - } + syn::GenericParam::Type(_) => return Err(err("type parameters are not allowed on reducers")), + syn::GenericParam::Const(_) => return Err(err("const parameters are not allowed on reducers")), } } - Ok(()) -} -/// Extract all function parameters, except for `self` ones that aren't allowed. -pub(crate) fn extract_typed_args(original_function: &ItemFn) -> syn::Result> { - original_function + let lifecycle = args.lifecycle.iter().filter_map(|lc| lc.to_lifecycle_value()); + + // Extract all function parameters, except for `self` ones that aren't allowed. + let typed_args = original_function .sig .inputs .iter() @@ -89,20 +85,7 @@ pub(crate) fn extract_typed_args(original_function: &ItemFn) -> syn::Result Ok(arg), _ => Err(syn::Error::new_spanned(arg, "expected typed argument")), }) - .collect() -} - -pub(crate) fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn::Result { - let func_name = &original_function.sig.ident; - let vis = &original_function.vis; - - let reducer_name = args.name.unwrap_or_else(|| ident_to_litstr(func_name)); - - assert_only_lifetime_generics(original_function, "reducers")?; - - let lifecycle = args.lifecycle.iter().filter_map(|lc| lc.to_lifecycle_value()); - - let typed_args = extract_typed_args(original_function)?; + .collect::>>()?; // Extract all function parameter names. let opt_arg_names = typed_args.iter().map(|arg| { @@ -158,8 +141,6 @@ pub(crate) fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn #[automatically_derived] impl spacetimedb::rt::FnInfo for #func_name { type Invoke = spacetimedb::rt::ReducerFn; - /// The function kind, which will cause scheduled tables to accept reducers. - type FnKind = spacetimedb::rt::FnKindReducer; const NAME: &'static str = #reducer_name; #(const LIFECYCLE: Option = Some(#lifecycle);)* const ARG_NAMES: &'static [Option<&'static str>] = &[#(#opt_arg_names),*]; diff --git a/crates/bindings-macro/src/table.rs b/crates/bindings-macro/src/table.rs index 2748f195e64..dd67ec22e9d 100644 --- a/crates/bindings-macro/src/table.rs +++ b/crates/bindings-macro/src/table.rs @@ -40,7 +40,7 @@ impl TableAccess { struct ScheduledArg { span: Span, - reducer_or_procedure: Path, + reducer: Path, at: Option, } @@ -113,7 +113,7 @@ impl TableArgs { impl ScheduledArg { fn parse_meta(meta: ParseNestedMeta) -> syn::Result { let span = meta.path.span(); - let mut reducer_or_procedure = None; + let mut reducer = None; let mut at = None; meta.parse_nested_meta(|meta| { @@ -126,26 +126,16 @@ impl ScheduledArg { } }) } else { - check_duplicate_msg( - &reducer_or_procedure, - &meta, - "can only specify one scheduled reducer or procedure", - )?; - reducer_or_procedure = Some(meta.path); + check_duplicate_msg(&reducer, &meta, "can only specify one scheduled reducer")?; + reducer = Some(meta.path); } Ok(()) })?; - let reducer_or_procedure = reducer_or_procedure.ok_or_else(|| { - meta.error( - "must specify scheduled reducer or procedure associated with the table: scheduled(function_name)", - ) + let reducer = reducer.ok_or_else(|| { + meta.error("must specify scheduled reducer associated with the table: scheduled(reducer_name)") })?; - Ok(Self { - span, - reducer_or_procedure, - at, - }) + Ok(Self { span, reducer, at }) } } @@ -828,20 +818,17 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R ) })?; - let reducer_or_procedure = &sched.reducer_or_procedure; + let reducer = &sched.reducer; let scheduled_at_id = scheduled_at_column.index; let desc = quote!(spacetimedb::table::ScheduleDesc { - reducer_or_procedure_name: <#reducer_or_procedure as spacetimedb::rt::FnInfo>::NAME, + reducer_name: <#reducer as spacetimedb::rt::FnInfo>::NAME, scheduled_at_column: #scheduled_at_id, }); let primary_key_ty = primary_key_column.ty; let scheduled_at_ty = scheduled_at_column.ty; let typecheck = quote! { - spacetimedb::rt::scheduled_typecheck::< - #original_struct_ident, - <#reducer_or_procedure as spacetimedb::rt::FnInfo>::FnKind, - >(#reducer_or_procedure); + spacetimedb::rt::scheduled_reducer_typecheck::<#original_struct_ident>(#reducer); spacetimedb::rt::assert_scheduled_table_primary_key::<#primary_key_ty>(); let _ = |x: #scheduled_at_ty| { let _: spacetimedb::ScheduleAt = x; }; }; diff --git a/crates/bindings-macro/src/view.rs b/crates/bindings-macro/src/view.rs index e409cddd11f..ba6a529ebe3 100644 --- a/crates/bindings-macro/src/view.rs +++ b/crates/bindings-macro/src/view.rs @@ -158,9 +158,6 @@ pub(crate) fn view_impl(_args: ViewArgs, original_function: &ItemFn) -> syn::Res /// The type of this function type Invoke = as spacetimedb::rt::ViewKindTrait>::InvokeFn; - /// The function kind, which will cause scheduled tables to reject views. - type FnKind = spacetimedb::rt::FnKindView; - /// The name of this function const NAME: &'static str = #view_name; diff --git a/crates/bindings-sys/src/lib.rs b/crates/bindings-sys/src/lib.rs index 764b73387c6..52c3b47f38e 100644 --- a/crates/bindings-sys/src/lib.rs +++ b/crates/bindings-sys/src/lib.rs @@ -1218,11 +1218,3 @@ impl Drop for RowIter { } } } - -pub mod procedure { - //! Side-effecting or asynchronous operations which only procedures are allowed to perform. - #[inline] - pub fn sleep_until(_wake_at_timestamp: i64) -> i64 { - todo!("Add `procedure_sleep_until` host function") - } -} diff --git a/crates/bindings/src/lib.rs b/crates/bindings/src/lib.rs index 9e7ad78852a..7386f078957 100644 --- a/crates/bindings/src/lib.rs +++ b/crates/bindings/src/lib.rs @@ -48,8 +48,6 @@ pub use table::{ pub type ReducerResult = core::result::Result<(), Box>; -pub type ProcedureResult = Vec; - pub use spacetimedb_bindings_macro::duration; /// Generates code for registering a row-level security rule. @@ -543,7 +541,6 @@ pub use spacetimedb_bindings_macro::table; /// If an error occurs in the disconnect reducer, /// the client is still recorded as disconnected. /// -// TODO(docs): Move these docs to be on `table`, rather than `reducer`. This will reduce duplication with procedure docs. /// # Scheduled reducers /// /// In addition to life cycle annotations, reducers can be made **scheduled**. @@ -669,92 +666,6 @@ pub use spacetimedb_bindings_macro::table; #[doc(inline)] pub use spacetimedb_bindings_macro::reducer; -/// Marks a function as a SpacetimeDB procedure. -/// -/// A procedure is a function that runs within the database and can be invoked remotely by [clients], -/// but unlike a [`reducer`], a procedure is not automatically transactional. -/// This allows procedures to perform certain side-effecting operations, -/// but also means that module developers must be more careful not to corrupt the database state -/// when execution aborts or operations fail. -/// -/// When in doubt, prefer writing [`reducer`]s unless you need to perform an operation only available to procedures. -/// -/// The first argument of a procedure is always `&mut ProcedureContext`. -/// The [`ProcedureContext`] exposes information about the caller and allows side-effecting operations. -/// -/// After this, a procedure can take any number of arguments. -/// These arguments must implement the [`SpacetimeType`], [`Serialize`], and [`Deserialize`] traits. -/// All of these traits can be derived at once by marking a type with `#[derive(SpacetimeType)]`. -/// -/// A procedure may return any type that implements [`SpacetimeType`], [`Serialize`] and [`Deserialize`]. -/// Unlike [reducer]s, SpacetimeDB does not assign any special semantics to [`Result`] return values. -/// -/// If a procedure returns successfully (as opposed to panicking), its return value will be sent to the calling client. -/// If a procedure panics, its panic message will be sent to the calling client instead. -/// Procedure arguments and return values are not otherwise broadcast to clients. -/// -/// ```no_run -/// # use spacetimedb::{procedure, SpacetimeType, ProcedureContext, Timestamp}; -/// #[procedure] -/// fn return_value(ctx: &mut ProcedureContext, arg: MyArgument) -> MyReturnValue { -/// MyReturnValue { -/// a: format!("Hello, {}", ctx.sender), -/// b: ctx.timestamp, -/// } -/// } -/// -/// #[derive(SpacetimeType)] -/// struct MyArgument { -/// val: u32, -/// } -/// -/// #[derive(SpacetimeType)] -/// struct MyReturnValue { -/// a: String, -/// b: Timestamp, -/// } -/// ``` -/// -/// # Blocking operations -/// -/// Procedures are allowed to perform certain operations which take time. -/// During the execution of these operations, the procedure's execution will be suspended, -/// allowing other database operations to run in parallel. -/// The simplest (and least useful) of these operators is [`ProcedureContext::sleep_until`]. -/// -/// Procedures must not hold open a transaction while performing a blocking operation. -/// -/// ```no_run -/// # use std::time::Duration; -/// # use spacetimedb::{procedure, ProcedureContext}; -/// #[procedure] -/// fn sleep_one_second(ctx: &mut ProcedureContext) { -/// let prev_time = ctx.timestamp; -/// let target = prev_time + Duration::from_secs(1); -/// ctx.sleep_until(target); -/// let new_time = ctx.timestamp; -/// let actual_delta = new_time.duration_since(prev_time).unwrap(); -/// log::info!("Slept from {prev_time} to {new_time}, a total of {actual_delta:?}"); -/// } -/// ``` -// TODO(procedure-http): replace this example with an HTTP request. -// TODO(procedure-transaction): document obtaining and using a transaction within a procedure. -/// -/// # Scheduled procedures -// TODO(docs): after moving scheduled reducer docs into table secion, link there. -/// -/// Like [reducer]s, procedures can be made **scheduled**. -/// This allows calling procedures at a particular time, or in a loop. -/// It also allows reducers to enqueue procedure runs. -/// -/// Scheduled procedures are called on a best-effort basis and may be slightly delayed in their execution -/// when a database is under heavy load. -/// -/// [clients]: https://spacetimedb.com/docs/#client -// TODO(procedure-async): update docs and examples with `async`-ness. -#[doc(inline)] -pub use spacetimedb_bindings_macro::procedure; - /// Marks a function as a spacetimedb view. /// /// A view is a function with read-only access to the database. @@ -921,8 +832,11 @@ pub struct ReducerContext { /// The `ConnectionId` of the client that invoked the reducer. /// - /// Will be `None` for certain reducers invoked automatically by the host, - /// including `init` and scheduled reducers. + /// `None` if no `ConnectionId` was supplied to the `/database/call` HTTP endpoint, + /// or via the CLI's `spacetime call` subcommand. + /// + /// For automatic reducers, i.e. `init`, `client_connected`, `client_disconnected`, and scheduled reducers, + /// this will be the module's `ConnectionId`. pub connection_id: Option, /// Allows accessing the local database attached to a module. @@ -1032,70 +946,6 @@ impl ReducerContext { } } -/// The context that any procedure is provided with. -/// -/// Each procedure must accept `&mut ProcedureContext` as its first argument. -/// -/// Includes information about the client calling the procedure and the time of invocation, -/// and exposes methods for running transactions and performing side-effecting operations. -pub struct ProcedureContext { - /// The `Identity` of the client that invoked the procedure. - pub sender: Identity, - - /// The time at which the procedure was started. - pub timestamp: Timestamp, - - /// The `ConnectionId` of the client that invoked the procedure. - /// - /// Will be `None` for certain scheduled procedures. - pub connection_id: Option, - // TODO: Add rng? - // Complex and requires design because we may want procedure RNG to behave differently from reducer RNG, - // as it could actually be seeded by OS randomness rather than a deterministic source. -} - -impl ProcedureContext { - /// Read the current module's [`Identity`]. - pub fn identity(&self) -> Identity { - // Hypothetically, we *could* read the module identity out of the system tables. - // However, this would be: - // - Onerous, because we have no tooling to inspect the system tables from module code. - // - Slow (at least relatively), - // because it would involve multiple host calls which hit the datastore, - // as compared to a single host call which does not. - // As such, we've just defined a host call - // which reads the module identity out of the `InstanceEnv`. - Identity::from_byte_array(spacetimedb_bindings_sys::identity()) - } - - /// Suspend execution until approximately `Timestamp`. - /// - /// This will update `self.timestamp` to the new time after execution resumes. - /// - /// Actual time suspended may not be exactly equal to `duration`. - /// Callers should read `self.timestamp` after resuming to determine the new time. - /// - /// ```no_run - /// # use std::time::Duration; - /// # use spacetimedb::{procedure, ProcedureContext}; - /// # #[procedure] - /// # fn sleep_one_second(ctx: &mut ProcedureContext) { - /// let prev_time = ctx.timestamp; - /// let target = prev_time + Duration::from_secs(1); - /// ctx.sleep_until(target); - /// let new_time = ctx.timestamp; - /// let actual_delta = new_time.duration_since(prev_time).unwrap(); - /// log::info!("Slept from {prev_time} to {new_time}, a total of {actual_delta:?}"); - /// # } - /// ``` - // TODO(procedure-async): mark this method `async`. - pub fn sleep_until(&mut self, timestamp: Timestamp) { - let new_time = sys::procedure::sleep_until(timestamp.to_micros_since_unix_epoch()); - let new_time = Timestamp::from_micros_since_unix_epoch(new_time); - self.timestamp = new_time; - } -} - /// A handle on a database with a particular table schema. pub trait DbContext { /// A view into the tables of a database. @@ -1122,10 +972,6 @@ impl DbContext for ReducerContext { } } -// `ProcedureContext` is *not* a `DbContext`. We will add a `TxContext` -// which can be obtained from `ProcedureContext::start_tx`, -// and that will be a `DbContext`. - /// Allows accessing the local database attached to the module. /// /// This slightly strange type appears to have no methods, but that is misleading. diff --git a/crates/bindings/src/rt.rs b/crates/bindings/src/rt.rs index 8e159ce716c..ba5d66c762f 100644 --- a/crates/bindings/src/rt.rs +++ b/crates/bindings/src/rt.rs @@ -2,8 +2,7 @@ use crate::table::IndexAlgo; use crate::{ - sys, AnonymousViewContext, IterBuf, LocalReadOnly, ProcedureContext, ProcedureResult, ReducerContext, - ReducerResult, SpacetimeType, Table, ViewContext, + sys, AnonymousViewContext, IterBuf, LocalReadOnly, ReducerContext, ReducerResult, SpacetimeType, Table, ViewContext, }; pub use spacetimedb_lib::db::raw_def::v9::Lifecycle as LifecycleReducer; use spacetimedb_lib::db::raw_def::v9::{RawIndexAlgorithm, RawModuleDefV9Builder, TableType}; @@ -13,7 +12,6 @@ use spacetimedb_lib::sats::{impl_deserialize, impl_serialize, ProductTypeElement use spacetimedb_lib::ser::{Serialize, SerializeSeqProduct}; use spacetimedb_lib::{bsatn, AlgebraicType, ConnectionId, Identity, ProductType, RawModuleDef, Timestamp}; use spacetimedb_primitives::*; -use std::convert::Infallible; use std::fmt; use std::marker::PhantomData; use std::sync::{Mutex, OnceLock}; @@ -49,22 +47,6 @@ pub fn invoke_reducer<'a, A: Args<'a>>( reducer.invoke(&ctx, args) } - -pub fn invoke_procedure<'a, A: Args<'a>, Ret: IntoProcedureResult>( - procedure: impl Procedure<'a, A, Ret>, - mut ctx: ProcedureContext, - args: &'a [u8], -) -> ProcedureResult { - // Deserialize the arguments from a bsatn encoding. - let SerDeArgs(args) = bsatn::from_slice(args).expect("unable to decode args"); - - // TODO(procedure-async): get a future out of `procedure.invoke` and call `FutureExt::now_or_never` on it? - // Or maybe do that within the `Procedure::invoke` method? - let res = procedure.invoke(&mut ctx, args); - - res.to_result() -} - /// A trait for types representing the *execution logic* of a reducer. #[diagnostic::on_unimplemented( message = "invalid reducer signature", @@ -140,12 +122,6 @@ pub trait FnInfo { /// The type of function to invoke. type Invoke; - /// One of [`FnKindReducer`], [`FnKindProcedure`] or [`FnKindView`]. - /// - /// Used as a type argument to [`ExportFunctionForScheduledTable`] and [`scheduled_typecheck`]. - /// See for details on this technique. - type FnKind; - /// The name of the function. const NAME: &'static str; @@ -165,15 +141,7 @@ pub trait FnInfo { } } -pub trait Procedure<'de, A: Args<'de>, Ret: IntoProcedureResult> { - fn invoke(&self, ctx: &mut ProcedureContext, args: A) -> Ret; -} - -/// A trait of types representing the arguments of a reducer, procedure or view. -/// -/// This does not include the context first argument, -/// only the client-provided args. -/// As such, the same trait can be used for all sorts of exported functions. +/// A trait of types representing the arguments of a reducer. pub trait Args<'de>: Sized { /// How many arguments does the reducer accept? const LEN: usize; @@ -211,18 +179,6 @@ impl IntoReducerResult for Result<(), E> { } } -#[diagnostic::on_unimplemented( - message = "The procedure return type `{Self}` does not implement `SpacetimeType`", - note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition" -)] -pub trait IntoProcedureResult: SpacetimeType + Serialize { - #[inline] - fn to_result(&self) -> ProcedureResult { - bsatn::to_vec(&self).expect("Failed to serialize procedure result") - } -} -impl IntoProcedureResult for T {} - #[diagnostic::on_unimplemented( message = "the first argument of a reducer must be `&ReducerContext`", label = "first argument must be `&ReducerContext`" @@ -246,29 +202,6 @@ pub trait ReducerArg { } impl ReducerArg for T {} -#[diagnostic::on_unimplemented( - message = "the first argument of a procedure must be `&mut ProcedureContext`", - label = "first argument must be `&mut ProcedureContext`" -)] -pub trait ProcedureContextArg { - // a little hack used in the macro to make error messages nicer. it generates ::_ITEM - #[doc(hidden)] - const _ITEM: () = (); -} -impl ProcedureContextArg for &mut ProcedureContext {} - -/// A trait of types that can be an argument of a procedure. -#[diagnostic::on_unimplemented( - message = "the procedure argument `{Self}` does not implement `SpacetimeType`", - note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition" -)] -pub trait ProcedureArg { - // a little hack used in the macro to make error messages nicer. it generates ::_ITEM - #[doc(hidden)] - const _ITEM: () = (); -} -impl ProcedureArg for T {} - #[diagnostic::on_unimplemented( message = "The first parameter of a `#[view]` must be `&ViewContext` or `&AnonymousViewContext`" )] @@ -377,60 +310,21 @@ impl ViewRegistrar { } /// Assert that a reducer type-checks with a given type. -pub const fn scheduled_typecheck<'de, Row, FnKind>(_x: impl ExportFunctionForScheduledTable<'de, Row, FnKind>) +pub const fn scheduled_reducer_typecheck<'de, Row>(_x: impl ReducerForScheduledTable<'de, Row>) where Row: SpacetimeType + Serialize + Deserialize<'de>, { core::mem::forget(_x); } -/// Tacit marker argument to [`ExportFunctionForScheduledTable`] for reducers. -pub struct FnKindReducer { - _never: Infallible, -} - -/// Tacit marker argument to [`ExportFunctionForScheduledTable`] for procedures. -/// -/// Holds the procedure's return type in order to avoid an error due to an unconstrained type argument. -pub struct FnKindProcedure { - _never: Infallible, - _ret_ty: PhantomData Ret>, -} - -/// Tacit marker argument to [`ExportFunctionForScheduledTable`] for views. -/// -/// Because views are never scheduled, we don't need to distinguish between anonymous or sender-identity views, -/// or to include their return type. -pub struct FnKindView { - _never: Infallible, -} - -/// Trait bound for [`scheduled_typecheck`], which the [`crate::table`] macro generates to typecheck scheduled functions. -/// -/// The `FnKind` parameter here is a coherence-defeating marker, which Will Crichton calls a "tacit parameter." -/// See for details on this technique. -/// It will be one of [`FnKindReducer`] or [`FnKindProcedure`] in modules that compile successfully. -/// It may be [`FnKindView`], but that will always fail to typecheck, as views cannot be used as scheduled functions. #[diagnostic::on_unimplemented( - message = "invalid signature for scheduled table reducer or procedure", - note = "views cannot be scheduled", - note = "the scheduled function must take `{TableRow}` as its sole argument", - note = "e.g: `fn scheduled_reducer(ctx: &ReducerContext, arg: {TableRow})`", - // TODO(procedure-async): amend this to `async fn` once procedures are `async`-ified - note = "or `fn scheduled_procedure(ctx: &mut ProcedureContext, arg: {TableRow})`", + message = "invalid signature for scheduled table reducer", + note = "the scheduled reducer must take `{TableRow}` as its sole argument", + note = "e.g: `fn scheduled_reducer(ctx: &ReducerContext, arg: {TableRow})`" )] -pub trait ExportFunctionForScheduledTable<'de, TableRow, FnKind> {} -impl<'de, TableRow: SpacetimeType + Serialize + Deserialize<'de>, F: Reducer<'de, (TableRow,)>> - ExportFunctionForScheduledTable<'de, TableRow, FnKindReducer> for F -{ -} - -impl< - 'de, - TableRow: SpacetimeType + Serialize + Deserialize<'de>, - Ret: SpacetimeType + Serialize + Deserialize<'de>, - F: Procedure<'de, (TableRow,), Ret>, - > ExportFunctionForScheduledTable<'de, TableRow, FnKindProcedure> for F +pub trait ReducerForScheduledTable<'de, TableRow> {} +impl<'de, TableRow: SpacetimeType + Serialize + Deserialize<'de>, R: Reducer<'de, (TableRow,)>> + ReducerForScheduledTable<'de, TableRow> for R { } @@ -502,15 +396,15 @@ impl<'de, A: Args<'de>> de::ProductVisitor<'de> for ArgsVisitor { } } -macro_rules! impl_reducer_procedure_view { +macro_rules! impl_reducer { ($($T1:ident $(, $T:ident)*)?) => { - impl_reducer_procedure_view!(@impl $($T1 $(, $T)*)?); - $(impl_reducer_procedure_view!($($T),*);)? + impl_reducer!(@impl $($T1 $(, $T)*)?); + $(impl_reducer!($($T),*);)? }; (@impl $($T:ident),*) => { // Implement `Args` for the tuple type `($($T,)*)`. impl<'de, $($T: SpacetimeType + Deserialize<'de> + Serialize),*> Args<'de> for ($($T,)*) { - const LEN: usize = impl_reducer_procedure_view!(@count $($T)*); + const LEN: usize = impl_reducer!(@count $($T)*); #[allow(non_snake_case)] #[allow(unused)] fn visit_seq_product>(mut prod: Acc) -> Result { @@ -547,7 +441,7 @@ macro_rules! impl_reducer_procedure_view { } } - // Implement `Reducer<..., ContextArg>` for the tuple type `($($T,)*)`. + // Implement `Reducer<..., ContextArg>` for the tuple type `($($T,)*)`. impl<'de, Func, Ret, $($T: SpacetimeType + Deserialize<'de> + Serialize),*> Reducer<'de, ($($T,)*)> for Func where Func: Fn(&ReducerContext, $($T),*) -> Ret, @@ -560,18 +454,6 @@ macro_rules! impl_reducer_procedure_view { } } - impl<'de, Func, Ret, $($T: SpacetimeType + Deserialize<'de> + Serialize),*> Procedure<'de, ($($T,)*), Ret> for Func - where - Func: Fn(&mut ProcedureContext, $($T),*) -> Ret, - Ret: IntoProcedureResult, - { - #[allow(non_snake_case)] - fn invoke(&self, ctx: &mut ProcedureContext, args: ($($T,)*)) -> Ret { - let ($($T,)*) = args; - self(ctx, $($T),*) - } - } - // Implement `View<..., ViewContext>` for the tuple type `($($T,)*)`. impl<'de, Func, Elem, Retn, $($T),*> View<'de, ($($T,)*), Elem> for Func @@ -606,14 +488,12 @@ macro_rules! impl_reducer_procedure_view { }; // Counts the number of elements in the tuple. (@count $($T:ident)*) => { - 0 $(+ impl_reducer_procedure_view!(@drop $T 1))* + 0 $(+ impl_reducer!(@drop $T 1))* }; (@drop $a:tt $b:tt) => { $b }; } -impl_reducer_procedure_view!( - A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, AA, AB, AC, AD, AE, AF -); +impl_reducer!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, AA, AB, AC, AD, AE, AF); /// Provides deserialization and serialization for any type `A: Args`. struct SerDeArgs(A); @@ -680,7 +560,7 @@ pub fn register_table() { table = table.with_column_sequence(col); } if let Some(schedule) = T::SCHEDULE { - table = table.with_schedule(schedule.reducer_or_procedure_name, schedule.scheduled_at_column); + table = table.with_schedule(schedule.reducer_name, schedule.scheduled_at_column); } for col in T::get_default_col_values().iter_mut() { @@ -711,20 +591,6 @@ pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl }) } -pub fn register_procedure<'a, A, Ret, I>(_: impl Procedure<'a, A, Ret>) -where - A: Args<'a>, - Ret: SpacetimeType + Serialize, - I: FnInfo, -{ - register_describer(|module| { - let params = A::schema::(&mut module.inner); - let ret_ty = ::make_type(&mut module.inner); - module.inner.add_procedure(I::NAME, params, ret_ty); - module.procedures.push(I::INVOKE); - }) -} - /// Registers a describer for the view `I` with arguments `A` and return type `Vec`. pub fn register_view<'a, A, I, T>(_: impl View<'a, A, T>) where @@ -769,8 +635,6 @@ pub struct ModuleBuilder { inner: RawModuleDefV9Builder, /// The reducers of the module. reducers: Vec, - /// The procedures of the module. - procedures: Vec, /// The client specific views of the module. views: Vec, /// The anonymous views of the module. @@ -785,9 +649,6 @@ static DESCRIBERS: Mutex>> = Mutex::new(Vec::new()); pub type ReducerFn = fn(ReducerContext, &[u8]) -> ReducerResult; static REDUCERS: OnceLock> = OnceLock::new(); -pub type ProcedureFn = fn(ProcedureContext, &[u8]) -> ProcedureResult; -static PROCEDURES: OnceLock> = OnceLock::new(); - /// A view function takes in `(ViewContext, Args)` and returns a Vec of bytes. pub type ViewFn = fn(ViewContext, &[u8]) -> Vec; static VIEWS: OnceLock> = OnceLock::new(); @@ -824,9 +685,8 @@ extern "C" fn __describe_module__(description: BytesSink) { let module_def = RawModuleDef::V9(module_def); let bytes = bsatn::to_vec(&module_def).expect("unable to serialize typespace"); - // Write the sets of reducers, procedures and views. + // Write the set of reducers and views. REDUCERS.set(module.reducers).ok().unwrap(); - PROCEDURES.set(module.procedures).ok().unwrap(); VIEWS.set(module.views).ok().unwrap(); ANONYMOUS_VIEWS.set(module.views_anon).ok().unwrap(); @@ -880,11 +740,16 @@ extern "C" fn __call_reducer__( error: BytesSink, ) -> i16 { // Piece together `sender_i` into an `Identity`. - let sender = reconstruct_sender_identity(sender_0, sender_1, sender_2, sender_3); + let sender = [sender_0, sender_1, sender_2, sender_3]; + let sender: [u8; 32] = bytemuck::must_cast(sender); + let sender = Identity::from_byte_array(sender); // The LITTLE-ENDIAN constructor. // Piece together `conn_id_i` into a `ConnectionId`. // The all-zeros `ConnectionId` (`ConnectionId::ZERO`) is interpreted as `None`. - let conn_id = reconstruct_connection_id(conn_id_0, conn_id_1); + let conn_id = [conn_id_0, conn_id_1]; + let conn_id: [u8; 16] = bytemuck::must_cast(conn_id); + let conn_id = ConnectionId::from_le_byte_array(conn_id); // The LITTLE-ENDIAN constructor. + let conn_id = (conn_id != ConnectionId::ZERO).then_some(conn_id); // Assemble the `ReducerContext`. let timestamp = Timestamp::from_micros_since_unix_epoch(timestamp as i64); @@ -895,114 +760,15 @@ extern "C" fn __call_reducer__( // Dispatch to it with the arguments read. let res = with_read_args(args, |args| reducers[id](ctx, args)); // Convert any error message to an error code and writes to the `error` sink. - convert_err_to_errno(res, error) -} - -/// Reconstruct the `sender_i` args to [`__call_reducer__`] and [`__call_procedure__`] into an [`Identity`]. -fn reconstruct_sender_identity(sender_0: u64, sender_1: u64, sender_2: u64, sender_3: u64) -> Identity { - let sender = [sender_0, sender_1, sender_2, sender_3]; - let sender: [u8; 32] = bytemuck::must_cast(sender); - Identity::from_byte_array(sender) // The LITTLE-ENDIAN constructor. -} - -/// Reconstruct the `conn_id_i` args to [`__call_reducer__`] and [`__call_procedure__`] into a [`ConnectionId`]. -/// -/// The all-zeros `ConnectionId` (`ConnectionId::ZERO`) is interpreted as `None`. -fn reconstruct_connection_id(conn_id_0: u64, conn_id_1: u64) -> Option { - // Piece together `conn_id_i` into a `ConnectionId`. - // The all-zeros `ConnectionId` (`ConnectionId::ZERO`) is interpreted as `None`. - let conn_id = [conn_id_0, conn_id_1]; - let conn_id: [u8; 16] = bytemuck::must_cast(conn_id); - let conn_id = ConnectionId::from_le_byte_array(conn_id); // The LITTLE-ENDIAN constructor. - (conn_id != ConnectionId::ZERO).then_some(conn_id) -} - -/// If `res` is `Err`, write the message to `out` and return non-zero. -/// If `res` is `Ok`, return zero. -/// -/// Called by [`__call_reducer__`] and [`__call_procedure__`] -/// to convert the user-returned `Result` into a low-level errno return. -fn convert_err_to_errno(res: Result<(), Box>, out: BytesSink) -> i16 { match res { Ok(()) => 0, Err(msg) => { - write_to_sink(out, msg.as_bytes()); + write_to_sink(error, msg.as_bytes()); errno::HOST_CALL_FAILURE.get() as i16 } } } -/// Called by the host to execute a procedure -/// when the `sender` calls the procedure identified by `id` at `timestamp` with `args`. -/// -/// The `sender_{0-3}` are the pieces of a `[u8; 32]` (`u256`) representing the sender's `Identity`. -/// They are encoded as follows (assuming `identity.to_byte_array(): [u8; 32]`): -/// - `sender_0` contains bytes `[0 ..8 ]`. -/// - `sender_1` contains bytes `[8 ..16]`. -/// - `sender_2` contains bytes `[16..24]`. -/// - `sender_3` contains bytes `[24..32]`. -/// -/// Note that `to_byte_array` uses LITTLE-ENDIAN order! This matches most host systems. -/// -/// The `conn_id_{0-1}` are the pieces of a `[u8; 16]` (`u128`) representing the callers's [`ConnectionId`]. -/// They are encoded as follows (assuming `conn_id.as_le_byte_array(): [u8; 16]`): -/// - `conn_id_0` contains bytes `[0 ..8 ]`. -/// - `conn_id_1` contains bytes `[8 ..16]`. -/// -/// Again, note that `to_byte_array` uses LITTLE-ENDIAN order! This matches most host systems. -/// -/// The `args` is a `BytesSource`, registered on the host side, -/// which can be read with `bytes_source_read`. -/// The contents of the buffer are the BSATN-encoding of the arguments to the reducer. -/// In the case of empty arguments, `args` will be 0, that is, invalid. -/// -/// The `result_sink` is a `BytesSink`, registered on the host side, -/// which can be written to with `bytes_sink_write`. -/// Procedures are expected to always write to this sink -/// the BSATN-serialized bytes of a value of the procedure's return type. -/// -/// Procedures always return the error 0. All other return values are reserved. -#[no_mangle] -extern "C" fn __call_procedure__( - id: usize, - sender_0: u64, - sender_1: u64, - sender_2: u64, - sender_3: u64, - conn_id_0: u64, - conn_id_1: u64, - timestamp: u64, - args: BytesSource, - result_sink: BytesSink, -) -> i16 { - // Piece together `sender_i` into an `Identity`. - let sender = reconstruct_sender_identity(sender_0, sender_1, sender_2, sender_3); - - // Piece together `conn_id_i` into a `ConnectionId`. - let conn_id = reconstruct_connection_id(conn_id_0, conn_id_1); - - let timestamp = Timestamp::from_micros_since_unix_epoch(timestamp as i64); - - // Assemble the `ProcedureContext`. - let ctx = ProcedureContext { - connection_id: conn_id, - sender, - timestamp, - }; - - // Grab the list of procedures, which is populated by the preinit functions. - let procedures = PROCEDURES.get().unwrap(); - - // Deserialize the args and pass them to the actual procedure. - let res = with_read_args(args, |args| procedures[id](ctx, args)); - - // Write the result bytes to the `result_sink`. - write_to_sink(result_sink, &res); - - // Return 0 for no error. Procedures always either trap or return 0. - 0 -} - /// Called by the host to execute an anonymous view. /// /// The `args` is a `BytesSource`, registered on the host side, diff --git a/crates/bindings/src/table.rs b/crates/bindings/src/table.rs index 0dd7a6edc17..0ca8e174989 100644 --- a/crates/bindings/src/table.rs +++ b/crates/bindings/src/table.rs @@ -149,7 +149,7 @@ pub enum IndexAlgo<'a> { } pub struct ScheduleDesc<'a> { - pub reducer_or_procedure_name: &'a str, + pub reducer_name: &'a str, pub scheduled_at_column: u16, } diff --git a/crates/bindings/tests/ui/reducers.stderr b/crates/bindings/tests/ui/reducers.stderr index 3c1ea8ae015..91775aa9ee2 100644 --- a/crates/bindings/tests/ui/reducers.stderr +++ b/crates/bindings/tests/ui/reducers.stderr @@ -249,9 +249,9 @@ error[E0593]: function is expected to take 2 arguments, but it takes 3 arguments | ----------------------------------------------------------------- takes 3 arguments | = note: required for `for<'a> fn(&'a ReducerContext, u8, u8) {scheduled_table_reducer}` to implement `Reducer<'_, (ScheduledTable,)>` - = note: required for `for<'a> fn(&'a ReducerContext, u8, u8) {scheduled_table_reducer}` to implement `ExportFunctionForScheduledTable<'_, ScheduledTable, FnKindReducer>` -note: required by a bound in `scheduled_typecheck` + = note: required for `for<'a> fn(&'a ReducerContext, u8, u8) {scheduled_table_reducer}` to implement `ReducerForScheduledTable<'_, ScheduledTable>` +note: required by a bound in `scheduled_reducer_typecheck` --> src/rt.rs | - | pub const fn scheduled_typecheck<'de, Row, FnKind>(_x: impl ExportFunctionForScheduledTable<'de, Row, FnKind>) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `scheduled_typecheck` + | pub const fn scheduled_reducer_typecheck<'de, Row>(_x: impl ReducerForScheduledTable<'de, Row>) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `scheduled_reducer_typecheck` diff --git a/crates/bindings/tests/ui/views.stderr b/crates/bindings/tests/ui/views.stderr index 1f7b0ad4f78..99616d3c899 100644 --- a/crates/bindings/tests/ui/views.stderr +++ b/crates/bindings/tests/ui/views.stderr @@ -453,22 +453,28 @@ error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied and $N others = note: required for `Option` to implement `SpacetimeType` -error[E0277]: invalid signature for scheduled table reducer or procedure +error[E0631]: type mismatch in function arguments --> tests/ui/views.rs:136:56 | 136 | #[spacetimedb::table(name = scheduled_table, scheduled(scheduled_table_view))] | -------------------------------------------------------^^^^^^^^^^^^^^^^^^^^--- | | | - | | unsatisfied trait bound + | | expected due to this | required by a bound introduced by this call - | - = help: the trait `ExportFunctionForScheduledTable<'_, ScheduledTable, FnKindView>` is not implemented for fn item `for<'a> fn(&'a ViewContext, ScheduledTable) -> Vec {scheduled_table_view}` - = note: views cannot be scheduled - = note: the scheduled function must take `ScheduledTable` as its sole argument - = note: e.g: `fn scheduled_reducer(ctx: &ReducerContext, arg: ScheduledTable)` - = note: or `fn scheduled_procedure(ctx: &mut ProcedureContext, arg: ScheduledTable)` -note: required by a bound in `scheduled_typecheck` +... +148 | fn scheduled_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { + | ------------------------------------------------------------------------------ found signature defined here + | + = note: expected function signature `for<'a> fn(&'a ReducerContext, ScheduledTable) -> _` + found function signature `fn(&ViewContext, ScheduledTable) -> _` + = note: required for `for<'a> fn(&'a ViewContext, ScheduledTable) -> Vec {scheduled_table_view}` to implement `Reducer<'_, (ScheduledTable,)>` + = note: required for `for<'a> fn(&'a ViewContext, ScheduledTable) -> Vec {scheduled_table_view}` to implement `ReducerForScheduledTable<'_, ScheduledTable>` +note: required by a bound in `scheduled_reducer_typecheck` --> src/rt.rs | - | pub const fn scheduled_typecheck<'de, Row, FnKind>(_x: impl ExportFunctionForScheduledTable<'de, Row, FnKind>) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `scheduled_typecheck` + | pub const fn scheduled_reducer_typecheck<'de, Row>(_x: impl ReducerForScheduledTable<'de, Row>) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `scheduled_reducer_typecheck` +help: consider wrapping the function in a closure + | +136 | #[spacetimedb::table(name = scheduled_table, scheduled(|arg0: &ReducerContext, arg1: ScheduledTable| scheduled_table_view(/* &ViewContext */, arg1)))] + | +++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++ diff --git a/modules/module-test/src/lib.rs b/modules/module-test/src/lib.rs index 831b50b117a..7986af92841 100644 --- a/modules/module-test/src/lib.rs +++ b/modules/module-test/src/lib.rs @@ -1,12 +1,10 @@ #![allow(clippy::disallowed_names)] -use std::time::Duration; - +use spacetimedb::log; use spacetimedb::spacetimedb_lib::db::raw_def::v9::TableAccess; use spacetimedb::spacetimedb_lib::{self, bsatn}; use spacetimedb::{ duration, table, ConnectionId, Deserialize, Identity, ReducerContext, SpacetimeType, Table, Timestamp, }; -use spacetimedb::{log, ProcedureContext}; pub type TestAlias = TestA; @@ -439,20 +437,3 @@ fn assert_caller_identity_is_module_identity(ctx: &ReducerContext) { log::info!("Called by the owner {owner}"); } } - -#[spacetimedb::procedure] -fn sleep_one_second(ctx: &mut ProcedureContext) { - let prev_time = ctx.timestamp; - let target = prev_time + Duration::from_secs(1); - ctx.sleep_until(target); - let new_time = ctx.timestamp; - let actual_delta = new_time.duration_since(prev_time).unwrap(); - log::info!("Slept from {prev_time} to {new_time}, a total of {actual_delta:?}"); -} - -#[spacetimedb::procedure] -fn return_value(_ctx: &mut ProcedureContext, foo: u64) -> Baz { - Baz { - field: format!("{foo}"), - } -} From 58f3c31973f70fe677499b354fabdd1a0c04adf5 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:29 -0700 Subject: [PATCH 11/16] [release/v1.7.0]: Revert "Remove `connection_id` from ViewContext (#3464)" This reverts commit f8631cc74998fd8e826ce20550dee30708f9c95a. --- crates/bindings/src/lib.rs | 2 ++ crates/bindings/src/rt.rs | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/bindings/src/lib.rs b/crates/bindings/src/lib.rs index 7386f078957..fbb1e1646ec 100644 --- a/crates/bindings/src/lib.rs +++ b/crates/bindings/src/lib.rs @@ -805,6 +805,7 @@ pub struct AnonymousViewContext { /// Use this type if the view depends on the caller's identity. pub struct ViewContext { pub sender: Identity, + pub connection_id: Option, pub db: LocalReadOnly, } @@ -941,6 +942,7 @@ impl ReducerContext { pub fn as_read_only(&self) -> ViewContext { ViewContext { sender: self.sender, + connection_id: self.connection_id, db: LocalReadOnly {}, } } diff --git a/crates/bindings/src/rt.rs b/crates/bindings/src/rt.rs index ba5d66c762f..739dda3635d 100644 --- a/crates/bindings/src/rt.rs +++ b/crates/bindings/src/rt.rs @@ -807,6 +807,8 @@ extern "C" fn __call_view__( sender_1: u64, sender_2: u64, sender_3: u64, + conn_id_0: u64, + conn_id_1: u64, args: BytesSource, sink: BytesSink, ) -> i16 { @@ -815,12 +817,29 @@ extern "C" fn __call_view__( let sender: [u8; 32] = bytemuck::must_cast(sender); let sender = Identity::from_byte_array(sender); // The LITTLE-ENDIAN constructor. + // Piece together `conn_id_i` into a `ConnectionId`. + // The all-zeros `ConnectionId` (`ConnectionId::ZERO`) is interpreted as `None`. + let conn_id = [conn_id_0, conn_id_1]; + let conn_id: [u8; 16] = bytemuck::must_cast(conn_id); + let conn_id = ConnectionId::from_le_byte_array(conn_id); // The LITTLE-ENDIAN constructor. + let conn_id = (conn_id != ConnectionId::ZERO).then_some(conn_id); + let views = VIEWS.get().unwrap(); let db = LocalReadOnly {}; + let connection_id = conn_id; write_to_sink( sink, - &with_read_args(args, |args| views[id](ViewContext { sender, db }, args)), + &with_read_args(args, |args| { + views[id]( + ViewContext { + sender, + connection_id, + db, + }, + args, + ) + }), ); 0 } From 3301a9954c298d10dc1ff5b3aa146be17693915d Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:30 -0700 Subject: [PATCH 12/16] [release/v1.7.0]: Revert "Add macro bindings for views (#3429)" This reverts commit 0d325b2dcc14b4e37a383b6c42139f6ed9889e29. --- crates/bindings-macro/src/lib.rs | 9 - crates/bindings-macro/src/reducer.rs | 5 +- crates/bindings-macro/src/table.rs | 2 +- crates/bindings-macro/src/view.rs | 178 -------- crates/bindings/src/lib.rs | 127 ------ crates/bindings/src/rt.rs | 372 +---------------- crates/bindings/tests/ui/reducers.stderr | 16 +- crates/bindings/tests/ui/views.rs | 87 +--- crates/bindings/tests/ui/views.stderr | 390 ------------------ .../src/locking_tx_datastore/mut_tx.rs | 4 +- crates/schema/src/def/validate/v9.rs | 29 +- smoketests/tests/views.py | 103 ----- 12 files changed, 47 insertions(+), 1275 deletions(-) delete mode 100644 crates/bindings-macro/src/view.rs delete mode 100644 smoketests/tests/views.py diff --git a/crates/bindings-macro/src/lib.rs b/crates/bindings-macro/src/lib.rs index e5ca3ad52c7..cd85b80ef65 100644 --- a/crates/bindings-macro/src/lib.rs +++ b/crates/bindings-macro/src/lib.rs @@ -12,7 +12,6 @@ mod reducer; mod sats; mod table; mod util; -mod view; use proc_macro::TokenStream as StdTokenStream; use proc_macro2::TokenStream; @@ -113,14 +112,6 @@ pub fn reducer(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream { }) } -#[proc_macro_attribute] -pub fn view(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream { - cvt_attr::(args, item, quote!(), |args, original_function| { - let args = view::ViewArgs::parse(args)?; - view::view_impl(args, original_function) - }) -} - /// It turns out to be shockingly difficult to construct an [`Attribute`]. /// That type is not [`Parse`], instead having two distinct methods /// for parsing "inner" vs "outer" attributes. diff --git a/crates/bindings-macro/src/reducer.rs b/crates/bindings-macro/src/reducer.rs index 2ca10c48b4f..ff98cc6250b 100644 --- a/crates/bindings-macro/src/reducer.rs +++ b/crates/bindings-macro/src/reducer.rs @@ -139,12 +139,11 @@ pub(crate) fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn } } #[automatically_derived] - impl spacetimedb::rt::FnInfo for #func_name { - type Invoke = spacetimedb::rt::ReducerFn; + impl spacetimedb::rt::ReducerInfo for #func_name { const NAME: &'static str = #reducer_name; #(const LIFECYCLE: Option = Some(#lifecycle);)* const ARG_NAMES: &'static [Option<&'static str>] = &[#(#opt_arg_names),*]; - const INVOKE: Self::Invoke = #func_name::invoke; + const INVOKE: spacetimedb::rt::ReducerFn = #func_name::invoke; } }) } diff --git a/crates/bindings-macro/src/table.rs b/crates/bindings-macro/src/table.rs index dd67ec22e9d..4e2b079992f 100644 --- a/crates/bindings-macro/src/table.rs +++ b/crates/bindings-macro/src/table.rs @@ -821,7 +821,7 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R let reducer = &sched.reducer; let scheduled_at_id = scheduled_at_column.index; let desc = quote!(spacetimedb::table::ScheduleDesc { - reducer_name: <#reducer as spacetimedb::rt::FnInfo>::NAME, + reducer_name: <#reducer as spacetimedb::rt::ReducerInfo>::NAME, scheduled_at_column: #scheduled_at_id, }); diff --git a/crates/bindings-macro/src/view.rs b/crates/bindings-macro/src/view.rs deleted file mode 100644 index ba6a529ebe3..00000000000 --- a/crates/bindings-macro/src/view.rs +++ /dev/null @@ -1,178 +0,0 @@ -use proc_macro2::{Span, TokenStream}; -use quote::quote; -use syn::parse::Parser; -use syn::{FnArg, ItemFn}; - -use crate::sym; -use crate::util::{ident_to_litstr, match_meta}; - -pub(crate) struct ViewArgs { - #[allow(unused)] - public: bool, -} - -impl ViewArgs { - /// Parse `#[view(public)]` where `public` is required. - pub(crate) fn parse(input: TokenStream) -> syn::Result { - if input.is_empty() { - return Err(syn::Error::new( - Span::call_site(), - "views must be declared as `#[view(public)]`; `public` is required", - )); - } - let mut public = false; - syn::meta::parser(|meta| { - match_meta!(match meta { - sym::public => { - if public { - return Err(syn::Error::new( - Span::call_site(), - "duplicate attribute argument: `public`", - )); - } - public = true; - } - }); - Ok(()) - }) - .parse2(input)?; - if !public { - return Err(syn::Error::new( - Span::call_site(), - "views must be declared as `#[view(public)]`; `public` is required", - )); - } - Ok(Self { public }) - } -} - -pub(crate) fn view_impl(_args: ViewArgs, original_function: &ItemFn) -> syn::Result { - let func_name = &original_function.sig.ident; - let view_name = ident_to_litstr(func_name); - let vis = &original_function.vis; - - for param in &original_function.sig.generics.params { - let err = |msg| syn::Error::new_spanned(param, msg); - match param { - syn::GenericParam::Lifetime(_) => {} - syn::GenericParam::Type(_) => return Err(err("type parameters are not allowed on views")), - syn::GenericParam::Const(_) => return Err(err("const parameters are not allowed on views")), - } - } - - // Extract parameters - let typed_args = original_function - .sig - .inputs - .iter() - .map(|arg| match arg { - FnArg::Typed(arg) => Ok(arg), - FnArg::Receiver(_) => Err(syn::Error::new_spanned( - arg, - "The `self` parameter is not allowed in views", - )), - }) - .collect::>>()?; - - // Extract parameter names - let opt_arg_names = typed_args.iter().map(|arg| { - if let syn::Pat::Ident(i) = &*arg.pat { - let name = i.ident.to_string(); - quote!(Some(#name)) - } else { - quote!(None) - } - }); - - let arg_tys = typed_args.iter().map(|arg| arg.ty.as_ref()).collect::>(); - - // Extract the context type and the rest of the parameter types - let [ctx_ty, arg_tys @ ..] = &arg_tys[..] else { - return Err(syn::Error::new_spanned( - &original_function.sig, - "Views must always have a context parameter: `&ViewContext` or `&AnonymousViewContext`", - )); - }; - - // Extract the context type - let ctx_ty = match ctx_ty { - syn::Type::Reference(ctx_ty) => ctx_ty.elem.as_ref(), - _ => { - return Err(syn::Error::new_spanned( - ctx_ty, - "The first parameter of a view must be a context parameter: `&ViewContext` or `&AnonymousViewContext`; passed by reference", - )); - } - }; - - // Views must return a result - let ret_ty = match &original_function.sig.output { - syn::ReturnType::Type(_, t) => t.as_ref(), - syn::ReturnType::Default => { - return Err(syn::Error::new_spanned( - &original_function.sig, - "views must return `Vec` or `Option` where `T` is a `SpacetimeType`", - )); - } - }; - - let register_describer_symbol = format!("__preinit__20_register_describer_{}", view_name.value()); - - let lt_params = &original_function.sig.generics; - let lt_where_clause = <_params.where_clause; - - let generated_describe_function = quote! { - #[export_name = #register_describer_symbol] - pub extern "C" fn __register_describer() { - spacetimedb::rt::ViewRegistrar::<#ctx_ty>::register::<_, #func_name, _, _>(#func_name) - } - }; - - Ok(quote! { - const _: () = { #generated_describe_function }; - - #[allow(non_camel_case_types)] - #vis struct #func_name { _never: ::core::convert::Infallible } - - const _: () = { - fn _assert_args #lt_params () #lt_where_clause { - let _ = <#ctx_ty as spacetimedb::rt::ViewContextArg>::_ITEM; - let _ = <#ret_ty as spacetimedb::rt::ViewReturn>::_ITEM; - } - }; - - const _: () = { - fn _assert_args #lt_params () #lt_where_clause { - #(let _ = <#arg_tys as spacetimedb::rt::ViewArg>::_ITEM;)* - } - }; - - impl #func_name { - fn invoke(__ctx: #ctx_ty, __args: &[u8]) -> Vec { - spacetimedb::rt::ViewDispatcher::<#ctx_ty>::invoke::<_, _, _>(#func_name, __ctx, __args) - } - } - - #[automatically_derived] - impl spacetimedb::rt::FnInfo for #func_name { - /// The type of this function - type Invoke = as spacetimedb::rt::ViewKindTrait>::InvokeFn; - - /// The name of this function - const NAME: &'static str = #view_name; - - /// The parameter names of this function - const ARG_NAMES: &'static [Option<&'static str>] = &[#(#opt_arg_names),*]; - - /// The pointer for invoking this function - const INVOKE: Self::Invoke = #func_name::invoke; - - /// The return type of this function - fn return_type( - ts: &mut impl spacetimedb::sats::typespace::TypespaceBuilder - ) -> Option { - Some(<#ret_ty as spacetimedb::SpacetimeType>::make_type(ts)) - } - } - }) -} diff --git a/crates/bindings/src/lib.rs b/crates/bindings/src/lib.rs index fbb1e1646ec..4c557ba4240 100644 --- a/crates/bindings/src/lib.rs +++ b/crates/bindings/src/lib.rs @@ -666,133 +666,6 @@ pub use spacetimedb_bindings_macro::table; #[doc(inline)] pub use spacetimedb_bindings_macro::reducer; -/// Marks a function as a spacetimedb view. -/// -/// A view is a function with read-only access to the database. -/// -/// The first argument of a view is always a [`&ViewContext`] or [`&AnonymousViewContext`]. -/// The former can only read from the database whereas latter can also access info about the caller. -/// -/// After this, a view can take any number of arguments just like reducers. -/// These arguments must implement the [`SpacetimeType`], [`Serialize`], and [`Deserialize`] traits. -/// All of these traits can be derived at once by marking a type with `#[derive(SpacetimeType)]`. -/// -/// Views return `Vec` or `Option` where `T` is a `SpacetimeType`. -/// -/// ```no_run -/// # mod demo { -/// use spacetimedb::{view, table, AnonymousViewContext, SpacetimeType, ViewContext}; -/// use spacetimedb_lib::Identity; -/// -/// #[table(name = player)] -/// struct Player { -/// #[auto_inc] -/// #[primary_key] -/// id: u64, -/// -/// #[unique] -/// identity: Identity, -/// -/// #[index(btree)] -/// level: u32, -/// } -/// -/// impl Player { -/// fn merge(self, location: Location) -> PlayerAndLocation { -/// PlayerAndLocation { -/// player_id: self.id, -/// level: self.level, -/// x: location.x, -/// y: location.y, -/// } -/// } -/// } -/// -/// #[derive(SpacetimeType)] -/// struct PlayerId { -/// id: u64, -/// } -/// -/// #[table(name = location, index(name = coordinates, btree(columns = [x, y])))] -/// struct Location { -/// #[unique] -/// player_id: u64, -/// x: u64, -/// y: u64, -/// } -/// -/// #[derive(SpacetimeType)] -/// struct PlayerAndLocation { -/// player_id: u64, -/// level: u32, -/// x: u64, -/// y: u64, -/// } -/// -/// // A view that selects at most one row from a table -/// #[view(public)] -/// fn my_player(ctx: &ViewContext) -> Option { -/// ctx.db.player().identity().find(ctx.sender) -/// } -/// -/// // An example of column projection -/// #[view(public)] -/// fn my_player_id(ctx: &ViewContext) -> Option { -/// ctx.db.player().identity().find(ctx.sender).map(|Player { id, .. }| PlayerId { id }) -/// } -/// -/// // An example of a parameterized view -/// #[view(public)] -/// fn players_at_level(ctx: &AnonymousViewContext, level: u32) -> Vec { -/// ctx.db.player().level().filter(level).collect() -/// } -/// -/// // An example that is analogous to a semijoin in sql -/// #[view(public)] -/// fn players_at_coordinates(ctx: &AnonymousViewContext, x: u64, y: u64) -> Vec { -/// ctx -/// .db -/// .location() -/// .coordinates() -/// .filter((x, y)) -/// .filter_map(|location| ctx.db.player().id().find(location.player_id)) -/// .collect() -/// } -/// -/// // An example of a join that combines fields from two different tables -/// #[view(public)] -/// fn players_with_coordinates(ctx: &AnonymousViewContext, x: u64, y: u64) -> Vec { -/// ctx -/// .db -/// .location() -/// .coordinates() -/// .filter((x, y)) -/// .filter_map(|location| ctx -/// .db -/// .player() -/// .id() -/// .find(location.player_id) -/// .map(|player| player.merge(location)) -/// ) -/// .collect() -/// } -/// # } -/// ``` -/// -/// Just like reducers, views are limited in their ability to interact with the outside world. -/// They have no access to any network or filesystem interfaces. -/// Calling methods from [`std::io`], [`std::net`], or [`std::fs`] will result in runtime errors. -/// -/// Views are callable by reducers and other views simply by passing their `ViewContext`.. -/// This is a regular function call. -/// The callee will run within the caller's transaction. -/// -/// -/// [`&ViewContext`]: `ViewContext` -/// [`&AnonymousViewContext`]: `AnonymousViewContext` -#[doc(inline)] -pub use spacetimedb_bindings_macro::view; - /// One of two possible types that can be passed as the first argument to a `#[view]`. /// The other is [`ViewContext`]. /// Use this type if the view does not depend on the caller's identity. diff --git a/crates/bindings/src/rt.rs b/crates/bindings/src/rt.rs index 739dda3635d..be513ea52f4 100644 --- a/crates/bindings/src/rt.rs +++ b/crates/bindings/src/rt.rs @@ -1,38 +1,20 @@ #![deny(unsafe_op_in_unsafe_fn)] use crate::table::IndexAlgo; -use crate::{ - sys, AnonymousViewContext, IterBuf, LocalReadOnly, ReducerContext, ReducerResult, SpacetimeType, Table, ViewContext, -}; +use crate::{sys, IterBuf, ReducerContext, ReducerResult, SpacetimeType, Table}; pub use spacetimedb_lib::db::raw_def::v9::Lifecycle as LifecycleReducer; use spacetimedb_lib::db::raw_def::v9::{RawIndexAlgorithm, RawModuleDefV9Builder, TableType}; use spacetimedb_lib::de::{self, Deserialize, Error as _, SeqProductAccess}; use spacetimedb_lib::sats::typespace::TypespaceBuilder; use spacetimedb_lib::sats::{impl_deserialize, impl_serialize, ProductTypeElement}; use spacetimedb_lib::ser::{Serialize, SerializeSeqProduct}; -use spacetimedb_lib::{bsatn, AlgebraicType, ConnectionId, Identity, ProductType, RawModuleDef, Timestamp}; +use spacetimedb_lib::{bsatn, ConnectionId, Identity, ProductType, RawModuleDef, Timestamp}; use spacetimedb_primitives::*; use std::fmt; use std::marker::PhantomData; use std::sync::{Mutex, OnceLock}; use sys::raw::{BytesSink, BytesSource}; -pub trait IntoVec { - fn into_vec(self) -> Vec; -} - -impl IntoVec for Vec { - fn into_vec(self) -> Vec { - self - } -} - -impl IntoVec for Option { - fn into_vec(self) -> Vec { - self.into_iter().collect() - } -} - /// The `sender` invokes `reducer` at `timestamp` and provides it with the given `args`. /// /// Returns an invalid buffer on success @@ -61,84 +43,19 @@ pub trait Reducer<'de, A: Args<'de>> { fn invoke(&self, ctx: &ReducerContext, args: A) -> ReducerResult; } -/// Invoke a caller-specific view. -/// Returns a BSATN encoded `Vec` of rows. -pub fn invoke_view<'a, A: Args<'a>, T: SpacetimeType + Serialize>( - view: impl View<'a, A, T>, - ctx: ViewContext, - args: &'a [u8], -) -> Vec { - // Deserialize the arguments from a bsatn encoding. - let SerDeArgs(args) = bsatn::from_slice(args).expect("unable to decode args"); - let rows: Vec = view.invoke(&ctx, args); - let mut buf = IterBuf::take(); - buf.serialize_into(&rows).expect("unable to encode rows"); - std::mem::take(&mut *buf) -} -/// A trait for types representing the execution logic of a caller-specific view. -#[diagnostic::on_unimplemented( - message = "invalid view signature", - label = "this view signature is not valid", - note = "", - note = "view signatures must match:", - note = " `Fn(&ViewContext, [T1, ...]) -> Vec | Option`", - note = "where each `Ti` implements `SpacetimeType`.", - note = "" -)] -pub trait View<'de, A: Args<'de>, T: SpacetimeType + Serialize> { - fn invoke(&self, ctx: &ViewContext, args: A) -> Vec; -} - -/// Invoke an anonymous view. -/// Returns a BSATN encoded `Vec` of rows. -pub fn invoke_anonymous_view<'a, A: Args<'a>, T: SpacetimeType + Serialize>( - view: impl AnonymousView<'a, A, T>, - ctx: AnonymousViewContext, - args: &'a [u8], -) -> Vec { - // Deserialize the arguments from a bsatn encoding. - let SerDeArgs(args) = bsatn::from_slice(args).expect("unable to decode args"); - let rows: Vec = view.invoke(&ctx, args); - let mut buf = IterBuf::take(); - buf.serialize_into(&rows).expect("unable to encode rows"); - std::mem::take(&mut *buf) -} -/// A trait for types representing the execution logic of an anonymous view. -#[diagnostic::on_unimplemented( - message = "invalid anonymous view signature", - label = "this view signature is not valid", - note = "", - note = "anonymous view signatures must match:", - note = " `Fn(&AnonymousViewContext, [T1, ...]) -> Vec | Option`", - note = "where each `Ti` implements `SpacetimeType`.", - note = "" -)] -pub trait AnonymousView<'de, A: Args<'de>, T: SpacetimeType + Serialize> { - fn invoke(&self, ctx: &AnonymousViewContext, args: A) -> Vec; -} - -/// A trait for types that can *describe* a callable function such as a reducer or view. -pub trait FnInfo { - /// The type of function to invoke. - type Invoke; - - /// The name of the function. +/// A trait for types that can *describe* a reducer. +pub trait ReducerInfo { + /// The name of the reducer. const NAME: &'static str; - /// The lifecycle of the function, if there is one. + /// The lifecycle of the reducer, if there is one. const LIFECYCLE: Option = None; - /// A description of the parameter names of the function. + /// A description of the parameter names of the reducer. const ARG_NAMES: &'static [Option<&'static str>]; - /// The function to invoke. - const INVOKE: Self::Invoke; - - /// The return type of this function. - /// Currently only implemented for views. - fn return_type(_ts: &mut impl TypespaceBuilder) -> Option { - None - } + /// The function to call to invoke the reducer. + const INVOKE: ReducerFn; } /// A trait of types representing the arguments of a reducer. @@ -152,8 +69,8 @@ pub trait Args<'de>: Sized { /// Serialize the arguments in `self` into the sequence `prod` according to the type `S`. fn serialize_seq_product(&self, prod: &mut S) -> Result<(), S::Error>; - /// Returns the schema of the args for this function provided a `typespace`. - fn schema(typespace: &mut impl TypespaceBuilder) -> ProductType; + /// Returns the schema for this reducer provided a `typespace`. + fn schema(typespace: &mut impl TypespaceBuilder) -> ProductType; } /// A trait of types representing the result of executing a reducer. @@ -202,113 +119,6 @@ pub trait ReducerArg { } impl ReducerArg for T {} -#[diagnostic::on_unimplemented( - message = "The first parameter of a `#[view]` must be `&ViewContext` or `&AnonymousViewContext`" -)] -pub trait ViewContextArg { - #[doc(hidden)] - const _ITEM: () = (); -} -impl ViewContextArg for ViewContext {} -impl ViewContextArg for AnonymousViewContext {} - -/// A trait of types that can be an argument of a view. -#[diagnostic::on_unimplemented( - message = "the view argument `{Self}` does not implement `SpacetimeType`", - note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition" -)] -pub trait ViewArg { - #[doc(hidden)] - const _ITEM: () = (); -} -impl ViewArg for T {} - -/// A trait of types that can be the return type of a view. -#[diagnostic::on_unimplemented(message = "Views must return `Vec` or `Option` where `T` is a `SpacetimeType`")] -pub trait ViewReturn { - #[doc(hidden)] - const _ITEM: () = (); -} -impl ViewReturn for Vec {} -impl ViewReturn for Option {} - -/// Map the correct dispatcher based on the `Ctx` type -pub struct ViewKind { - _marker: PhantomData, -} - -pub trait ViewKindTrait { - type InvokeFn; -} - -impl ViewKindTrait for ViewKind { - type InvokeFn = ViewFn; -} - -impl ViewKindTrait for ViewKind { - type InvokeFn = AnonymousFn; -} - -/// Invoke the correct dispatcher based on the `Ctx` type -pub struct ViewDispatcher { - _marker: PhantomData, -} - -impl ViewDispatcher { - #[inline] - pub fn invoke<'a, A, T, V>(view: V, ctx: ViewContext, args: &'a [u8]) -> Vec - where - A: Args<'a>, - T: SpacetimeType + Serialize, - V: View<'a, A, T>, - { - invoke_view(view, ctx, args) - } -} - -impl ViewDispatcher { - #[inline] - pub fn invoke<'a, A, T, V>(view: V, ctx: AnonymousViewContext, args: &'a [u8]) -> Vec - where - A: Args<'a>, - T: SpacetimeType + Serialize, - V: AnonymousView<'a, A, T>, - { - invoke_anonymous_view(view, ctx, args) - } -} - -/// Register the correct dispatcher based on the `Ctx` type -pub struct ViewRegistrar { - _marker: PhantomData, -} - -impl ViewRegistrar { - #[inline] - pub fn register<'a, A, I, T, V>(view: V) - where - A: Args<'a>, - T: SpacetimeType + Serialize, - I: FnInfo, - V: View<'a, A, T>, - { - register_view::(view) - } -} - -impl ViewRegistrar { - #[inline] - pub fn register<'a, A, I, T, V>(view: V) - where - A: Args<'a>, - T: SpacetimeType + Serialize, - I: FnInfo, - V: AnonymousView<'a, A, T>, - { - register_anonymous_view::(view) - } -} - /// Assert that a reducer type-checks with a given type. pub const fn scheduled_reducer_typecheck<'de, Row>(_x: impl ReducerForScheduledTable<'de, Row>) where @@ -429,7 +239,7 @@ macro_rules! impl_reducer { #[inline] #[allow(non_snake_case, irrefutable_let_patterns)] - fn schema(_typespace: &mut impl TypespaceBuilder) -> ProductType { + fn schema(_typespace: &mut impl TypespaceBuilder) -> ProductType { // Extract the names of the arguments. let [.., $($T),*] = Info::ARG_NAMES else { panic!() }; ProductType::new(vec![ @@ -454,37 +264,6 @@ macro_rules! impl_reducer { } } - // Implement `View<..., ViewContext>` for the tuple type `($($T,)*)`. - impl<'de, Func, Elem, Retn, $($T),*> - View<'de, ($($T,)*), Elem> for Func - where - $($T: SpacetimeType + Deserialize<'de> + Serialize,)* - Func: Fn(&ViewContext, $($T),*) -> Retn, - Retn: IntoVec, - Elem: SpacetimeType + Serialize, - { - #[allow(non_snake_case)] - fn invoke(&self, ctx: &ViewContext, args: ($($T,)*)) -> Vec { - let ($($T,)*) = args; - self(ctx, $($T),*).into_vec() - } - } - - // Implement `View<..., AnonymousViewContext>` for the tuple type `($($T,)*)`. - impl<'de, Func, Elem, Retn, $($T),*> - AnonymousView<'de, ($($T,)*), Elem> for Func - where - $($T: SpacetimeType + Deserialize<'de> + Serialize,)* - Func: Fn(&AnonymousViewContext, $($T),*) -> Retn, - Retn: IntoVec, - Elem: SpacetimeType + Serialize, - { - #[allow(non_snake_case)] - fn invoke(&self, ctx: &AnonymousViewContext, args: ($($T,)*)) -> Vec { - let ($($T,)*) = args; - self(ctx, $($T),*).into_vec() - } - } }; // Counts the number of elements in the tuple. (@count $($T:ident)*) => { @@ -583,7 +362,7 @@ impl From> for RawIndexAlgorithm { } /// Registers a describer for the reducer `I` with arguments `A`. -pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl Reducer<'a, A>) { +pub fn register_reducer<'a, A: Args<'a>, I: ReducerInfo>(_: impl Reducer<'a, A>) { register_describer(|module| { let params = A::schema::(&mut module.inner); module.inner.add_reducer(I::NAME, params, I::LIFECYCLE); @@ -591,36 +370,6 @@ pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl }) } -/// Registers a describer for the view `I` with arguments `A` and return type `Vec`. -pub fn register_view<'a, A, I, T>(_: impl View<'a, A, T>) -where - A: Args<'a>, - I: FnInfo, - T: SpacetimeType + Serialize, -{ - register_describer(|module| { - let params = A::schema::(&mut module.inner); - let return_type = I::return_type(&mut module.inner).unwrap(); - module.inner.add_view(I::NAME, true, false, params, return_type); - module.views.push(I::INVOKE); - }) -} - -/// Registers a describer for the anonymous view `I` with arguments `A` and return type `Vec`. -pub fn register_anonymous_view<'a, A, I, T>(_: impl AnonymousView<'a, A, T>) -where - A: Args<'a>, - I: FnInfo, - T: SpacetimeType + Serialize, -{ - register_describer(|module| { - let params = A::schema::(&mut module.inner); - let return_type = I::return_type(&mut module.inner).unwrap(); - module.inner.add_view(I::NAME, true, true, params, return_type); - module.views_anon.push(I::INVOKE); - }) -} - /// Registers a row-level security policy. pub fn register_row_level_security(sql: &'static str) { register_describer(|module| { @@ -630,15 +379,11 @@ pub fn register_row_level_security(sql: &'static str) { /// A builder for a module. #[derive(Default)] -pub struct ModuleBuilder { +struct ModuleBuilder { /// The module definition. inner: RawModuleDefV9Builder, /// The reducers of the module. reducers: Vec, - /// The client specific views of the module. - views: Vec, - /// The anonymous views of the module. - views_anon: Vec, } // Not actually a mutex; because WASM is single-threaded this basically just turns into a refcell. @@ -649,14 +394,6 @@ static DESCRIBERS: Mutex>> = Mutex::new(Vec::new()); pub type ReducerFn = fn(ReducerContext, &[u8]) -> ReducerResult; static REDUCERS: OnceLock> = OnceLock::new(); -/// A view function takes in `(ViewContext, Args)` and returns a Vec of bytes. -pub type ViewFn = fn(ViewContext, &[u8]) -> Vec; -static VIEWS: OnceLock> = OnceLock::new(); - -/// An anonymous view function takes in `(AnonymousViewContext, Args)` and returns a Vec of bytes. -pub type AnonymousFn = fn(AnonymousViewContext, &[u8]) -> Vec; -static ANONYMOUS_VIEWS: OnceLock> = OnceLock::new(); - /// Called by the host when the module is initialized /// to describe the module into a serialized form that is returned. /// @@ -685,10 +422,8 @@ extern "C" fn __describe_module__(description: BytesSink) { let module_def = RawModuleDef::V9(module_def); let bytes = bsatn::to_vec(&module_def).expect("unable to serialize typespace"); - // Write the set of reducers and views. + // Write the set of reducers. REDUCERS.set(module.reducers).ok().unwrap(); - VIEWS.set(module.views).ok().unwrap(); - ANONYMOUS_VIEWS.set(module.views_anon).ok().unwrap(); // Write the bsatn data into the sink. write_to_sink(description, &bytes); @@ -769,81 +504,6 @@ extern "C" fn __call_reducer__( } } -/// Called by the host to execute an anonymous view. -/// -/// The `args` is a `BytesSource`, registered on the host side, -/// which can be read with `bytes_source_read`. -/// The contents of the buffer are the BSATN-encoding of the arguments to the view. -/// In the case of empty arguments, `args` will be 0, that is, invalid. -/// -/// The output of the view is written to a `BytesSink`, -/// registered on the host side, with `bytes_sink_write`. -#[no_mangle] -extern "C" fn __call_view_anon__(id: usize, args: BytesSource, sink: BytesSink) -> i16 { - let views = ANONYMOUS_VIEWS.get().unwrap(); - write_to_sink( - sink, - &with_read_args(args, |args| { - views[id](AnonymousViewContext { db: LocalReadOnly {} }, args) - }), - ); - 0 -} - -/// Called by the host to execute a view when the `sender` calls the view identified by `id` with `args`. -/// See [`__call_reducer__`] for more commentary on the arguments. -/// -/// The `args` is a `BytesSource`, registered on the host side, -/// which can be read with `bytes_source_read`. -/// The contents of the buffer are the BSATN-encoding of the arguments to the view. -/// In the case of empty arguments, `args` will be 0, that is, invalid. -/// -/// The output of the view is written to a `BytesSink`, -/// registered on the host side, with `bytes_sink_write`. -#[no_mangle] -extern "C" fn __call_view__( - id: usize, - sender_0: u64, - sender_1: u64, - sender_2: u64, - sender_3: u64, - conn_id_0: u64, - conn_id_1: u64, - args: BytesSource, - sink: BytesSink, -) -> i16 { - // Piece together `sender_i` into an `Identity`. - let sender = [sender_0, sender_1, sender_2, sender_3]; - let sender: [u8; 32] = bytemuck::must_cast(sender); - let sender = Identity::from_byte_array(sender); // The LITTLE-ENDIAN constructor. - - // Piece together `conn_id_i` into a `ConnectionId`. - // The all-zeros `ConnectionId` (`ConnectionId::ZERO`) is interpreted as `None`. - let conn_id = [conn_id_0, conn_id_1]; - let conn_id: [u8; 16] = bytemuck::must_cast(conn_id); - let conn_id = ConnectionId::from_le_byte_array(conn_id); // The LITTLE-ENDIAN constructor. - let conn_id = (conn_id != ConnectionId::ZERO).then_some(conn_id); - - let views = VIEWS.get().unwrap(); - let db = LocalReadOnly {}; - let connection_id = conn_id; - - write_to_sink( - sink, - &with_read_args(args, |args| { - views[id]( - ViewContext { - sender, - connection_id, - db, - }, - args, - ) - }), - ); - 0 -} - /// Run `logic` with `args` read from the host into a `&[u8]`. fn with_read_args(args: BytesSource, logic: impl FnOnce(&[u8]) -> R) -> R { if args == BytesSource::INVALID { @@ -964,7 +624,7 @@ macro_rules! __make_register_reftype { #[cfg(feature = "unstable")] #[doc(hidden)] -pub fn volatile_nonatomic_schedule_immediate<'de, A: Args<'de>, R: Reducer<'de, A>, R2: FnInfo>( +pub fn volatile_nonatomic_schedule_immediate<'de, A: Args<'de>, R: Reducer<'de, A>, R2: ReducerInfo>( _reducer: R, args: A, ) { diff --git a/crates/bindings/tests/ui/reducers.stderr b/crates/bindings/tests/ui/reducers.stderr index 91775aa9ee2..14814c9e055 100644 --- a/crates/bindings/tests/ui/reducers.stderr +++ b/crates/bindings/tests/ui/reducers.stderr @@ -37,8 +37,8 @@ error[E0277]: invalid reducer signature note: required by a bound in `register_reducer` --> src/rt.rs | - | pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl Reducer<'a, A>) { - | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` + | pub fn register_reducer<'a, A: Args<'a>, I: ReducerInfo>(_: impl Reducer<'a, A>) { + | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` error[E0277]: the reducer argument `Test` does not implement `SpacetimeType` --> tests/ui/reducers.rs:6:40 @@ -98,8 +98,8 @@ error[E0277]: invalid reducer signature note: required by a bound in `register_reducer` --> src/rt.rs | - | pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl Reducer<'a, A>) { - | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` + | pub fn register_reducer<'a, A: Args<'a>, I: ReducerInfo>(_: impl Reducer<'a, A>) { + | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` error[E0277]: `Test` is not a valid reducer return type --> tests/ui/reducers.rs:9:46 @@ -151,8 +151,8 @@ error[E0277]: invalid reducer signature note: required by a bound in `register_reducer` --> src/rt.rs | - | pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl Reducer<'a, A>) { - | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` + | pub fn register_reducer<'a, A: Args<'a>, I: ReducerInfo>(_: impl Reducer<'a, A>) { + | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` error[E0277]: the first argument of a reducer must be `&ReducerContext` --> tests/ui/reducers.rs:23:20 @@ -202,8 +202,8 @@ error[E0277]: invalid reducer signature note: required by a bound in `register_reducer` --> src/rt.rs | - | pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl Reducer<'a, A>) { - | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` + | pub fn register_reducer<'a, A: Args<'a>, I: ReducerInfo>(_: impl Reducer<'a, A>) { + | ^^^^^^^^^^^^^^ required by this bound in `register_reducer` error[E0277]: the first argument of a reducer must be `&ReducerContext` --> tests/ui/reducers.rs:26:21 diff --git a/crates/bindings/tests/ui/views.rs b/crates/bindings/tests/ui/views.rs index 5e5bdb09d94..131a0cf2f47 100644 --- a/crates/bindings/tests/ui/views.rs +++ b/crates/bindings/tests/ui/views.rs @@ -1,4 +1,4 @@ -use spacetimedb::{reducer, table, view, AnonymousViewContext, Identity, ReducerContext, ViewContext}; +use spacetimedb::{reducer, table, ReducerContext}; #[table(name = test)] struct Test { @@ -64,89 +64,4 @@ fn read_only_btree_index_no_delete(ctx: &ReducerContext) { read_only.db.test().x().delete(0u32..); } -#[table(name = player)] -struct Player { - #[unique] - identity: Identity, -} - -struct NotSpacetimeType {} - -/// Private views not allowed; must be `#[view(public)]` -#[view] -fn view_def_no_public(_: &ViewContext) -> Vec { - vec![] -} - -/// Duplicate `public` -#[view(public, public)] -fn view_def_duplicate_attribute_arg() -> Vec { - vec![] -} - -/// Unsupported attribute arg -#[view(public, anonymous)] -fn view_def_unsupported_attribute_arg() -> Vec { - vec![] -} - -/// A `ViewContext` is required -#[view(public)] -fn view_def_no_context() -> Vec { - vec![] -} - -/// A `ViewContext` is required -#[view(public)] -fn view_def_wrong_context(_: &ReducerContext) -> Vec { - vec![] -} - -/// Must pass the `ViewContext` by ref -#[view(public)] -fn view_def_pass_context_by_value(_: ViewContext) -> Vec { - vec![] -} - -/// The view context must be the first parameter -#[view(public)] -fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec { - vec![] -} - -/// Must return `Vec` or `Option` where `T` is a SpacetimeType -#[view(public)] -fn view_def_no_return(_: &ViewContext) {} - -/// Must return `Vec` or `Option` where `T` is a SpacetimeType -#[view(public)] -fn view_def_wrong_return(_: &ViewContext) -> Player { - Player { - identity: Identity::ZERO, - } -} - -/// Must return `Vec` or `Option` where `T` is a SpacetimeType -#[view(public)] -fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { - None -} - -/// Cannot use a view as a scheduled function -#[spacetimedb::table(name = scheduled_table, scheduled(scheduled_table_view))] -struct ScheduledTable { - #[primary_key] - #[auto_inc] - scheduled_id: u64, - scheduled_at: spacetimedb::ScheduleAt, - x: u8, - y: u8, -} - -/// Cannot use a view as a scheduled function -#[view(public)] -fn scheduled_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { - vec![] -} - fn main() {} diff --git a/crates/bindings/tests/ui/views.stderr b/crates/bindings/tests/ui/views.stderr index 99616d3c899..95108add08c 100644 --- a/crates/bindings/tests/ui/views.stderr +++ b/crates/bindings/tests/ui/views.stderr @@ -1,81 +1,3 @@ -error: views must be declared as `#[view(public)]`; `public` is required - --> tests/ui/views.rs:76:1 - | -76 | #[view] - | ^^^^^^^ - | - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: duplicate attribute argument: `public` - --> tests/ui/views.rs:82:1 - | -82 | #[view(public, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: expected `public` - --> tests/ui/views.rs:88:16 - | -88 | #[view(public, anonymous)] - | ^^^^^^^^^ - -error: Views must always have a context parameter: `&ViewContext` or `&AnonymousViewContext` - --> tests/ui/views.rs:95:1 - | -95 | fn view_def_no_context() -> Vec { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: The first parameter of a view must be a context parameter: `&ViewContext` or `&AnonymousViewContext`; passed by reference - --> tests/ui/views.rs:107:38 - | -107 | fn view_def_pass_context_by_value(_: ViewContext) -> Vec { - | ^^^^^^^^^^^ - -error: views must return `Vec` or `Option` where `T` is a `SpacetimeType` - --> tests/ui/views.rs:119:1 - | -119 | fn view_def_no_return(_: &ViewContext) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: the trait bound `ViewKind: ViewKindTrait` is not satisfied - --> tests/ui/views.rs:100:1 - | -100 | #[view(public)] - | ^^^^^^^^^^^^^^^ the trait `ViewKindTrait` is not implemented for `ViewKind` - | - = help: the following other types implement trait `ViewKindTrait`: - ViewKind - ViewKind - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0276]: impl has stricter requirements than trait - --> tests/ui/views.rs:100:1 - | -100 | #[view(public)] - | ^^^^^^^^^^^^^^^ impl has extra requirement `ViewKind: ViewKindTrait` - | - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `ViewKind: ViewKindTrait` is not satisfied - --> tests/ui/views.rs:112:1 - | -112 | #[view(public)] - | ^^^^^^^^^^^^^^^ the trait `ViewKindTrait` is not implemented for `ViewKind` - | - = help: the following other types implement trait `ViewKindTrait`: - ViewKind - ViewKind - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0276]: impl has stricter requirements than trait - --> tests/ui/views.rs:112:1 - | -112 | #[view(public)] - | ^^^^^^^^^^^^^^^ impl has extra requirement `ViewKind: ViewKindTrait` - | - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0599]: no method named `iter` found for reference `&test__ViewHandle` in the current scope --> tests/ui/views.rs:15:34 | @@ -166,315 +88,3 @@ error[E0599]: no method named `delete` found for struct `RangedIndexReadOnly` in | 64 | read_only.db.test().x().delete(0u32..); | ^^^^^^ method not found in `RangedIndexReadOnly` - -error[E0599]: no function or associated item named `register` found for struct `ViewRegistrar` in the current scope - --> tests/ui/views.rs:100:1 - | -100 | #[view(public)] - | ^^^^^^^^^^^^^^^ function or associated item not found in `ViewRegistrar` - | - = note: the function or associated item was found for - - `ViewRegistrar` - - `ViewRegistrar` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: The first parameter of a `#[view]` must be `&ViewContext` or `&AnonymousViewContext` - --> tests/ui/views.rs:101:31 - | -101 | fn view_def_wrong_context(_: &ReducerContext) -> Vec { - | ^^^^^^^^^^^^^^ the trait `ViewContextArg` is not implemented for `ReducerContext` - | - = help: the following other types implement trait `ViewContextArg`: - AnonymousViewContext - ViewContext - -error[E0599]: no function or associated item named `invoke` found for struct `ViewDispatcher` in the current scope - --> tests/ui/views.rs:100:1 - | -100 | #[view(public)] - | ^^^^^^^^^^^^^^^ function or associated item not found in `ViewDispatcher` - | - = note: the function or associated item was found for - - `ViewDispatcher` - - `ViewDispatcher` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0599]: no function or associated item named `register` found for struct `ViewRegistrar` in the current scope - --> tests/ui/views.rs:112:1 - | -112 | #[view(public)] - | ^^^^^^^^^^^^^^^ function or associated item not found in `ViewRegistrar` - | - = note: the function or associated item was found for - - `ViewRegistrar` - - `ViewRegistrar` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: The first parameter of a `#[view]` must be `&ViewContext` or `&AnonymousViewContext` - --> tests/ui/views.rs:113:40 - | -113 | fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec { - | ^^^ the trait `ViewContextArg` is not implemented for `u32` - | - = help: the following other types implement trait `ViewContextArg`: - AnonymousViewContext - ViewContext - -error[E0277]: the view argument `&ViewContext` does not implement `SpacetimeType` - --> tests/ui/views.rs:113:48 - | -113 | fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec { - | ^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `ViewContext` - | - = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition - = help: the following other types implement trait `SpacetimeType`: - &T - () - AlgebraicType - AlgebraicTypeRef - Arc - ArrayType - Box - ColId - and $N others - = note: required for `&ViewContext` to implement `SpacetimeType` - = note: required for `&ViewContext` to implement `ViewArg` - -error[E0599]: no function or associated item named `invoke` found for struct `ViewDispatcher` in the current scope - --> tests/ui/views.rs:112:1 - | -112 | #[view(public)] - | ^^^^^^^^^^^^^^^ function or associated item not found in `ViewDispatcher` - | - = note: the function or associated item was found for - - `ViewDispatcher` - - `ViewDispatcher` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: invalid view signature - --> tests/ui/views.rs:122:1 - | -122 | #[view(public)] - | ^^^^^^^^^^^^^^^ this view signature is not valid - | - = help: the trait `spacetimedb::rt::View<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a ViewContext) -> Player {view_def_wrong_return}` - = note: - = note: view signatures must match: - = note: `Fn(&ViewContext, [T1, ...]) -> Vec | Option` - = note: where each `Ti` implements `SpacetimeType`. - = note: -note: required by a bound in `ViewRegistrar::::register` - --> src/rt.rs - | - | pub fn register<'a, A, I, T, V>(view: V) - | -------- required by a bound in this associated function -... - | V: View<'a, A, T>, - | ^^^^^^^^^^^^^^ required by this bound in `ViewRegistrar::::register` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: Views must return `Vec` or `Option` where `T` is a `SpacetimeType` - --> tests/ui/views.rs:123:46 - | -123 | fn view_def_wrong_return(_: &ViewContext) -> Player { - | ^^^^^^ the trait `ViewReturn` is not implemented for `Player` - | - = help: the following other types implement trait `ViewReturn`: - Option - Vec - -error[E0277]: invalid view signature - --> tests/ui/views.rs:122:1 - | -122 | #[view(public)] - | ^^^^^^^^^^^^^^^ this view signature is not valid - | - = help: the trait `spacetimedb::rt::View<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a ViewContext) -> Player {view_def_wrong_return}` - = note: - = note: view signatures must match: - = note: `Fn(&ViewContext, [T1, ...]) -> Vec | Option` - = note: where each `Ti` implements `SpacetimeType`. - = note: -note: required by a bound in `ViewDispatcher::::invoke` - --> src/rt.rs - | - | pub fn invoke<'a, A, T, V>(view: V, ctx: ViewContext, args: &'a [u8]) -> Vec - | ------ required by a bound in this associated function -... - | V: View<'a, A, T>, - | ^^^^^^^^^^^^^^ required by this bound in `ViewDispatcher::::invoke` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied - --> tests/ui/views.rs:130:1 - | -130 | #[view(public)] - | ^^^^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `NotSpacetimeType` - | - = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition - = help: the following other types implement trait `SpacetimeType`: - &T - () - AlgebraicType - AlgebraicTypeRef - Arc - ArrayType - Box - ColId - and $N others - = note: required for `for<'a> fn(&'a AnonymousViewContext) -> Option {view_def_returns_not_a_spacetime_type}` to implement `AnonymousView<'_, (), NotSpacetimeType>` -note: required by a bound in `ViewRegistrar::::register` - --> src/rt.rs - | - | pub fn register<'a, A, I, T, V>(view: V) - | -------- required by a bound in this associated function -... - | V: AnonymousView<'a, A, T>, - | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ViewRegistrar::::register` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `NotSpacetimeType: Serialize` is not satisfied - --> tests/ui/views.rs:130:1 - | -130 | #[view(public)] - | ^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `NotSpacetimeType` - | - = help: the following other types implement trait `Serialize`: - &T - () - (T0, T1) - (T0, T1, T2) - (T0, T1, T2, T3) - (T0, T1, T2, T3, T4) - (T0, T1, T2, T3, T4, T5) - (T0, T1, T2, T3, T4, T5, T6) - and $N others - = note: required for `for<'a> fn(&'a AnonymousViewContext) -> Option {view_def_returns_not_a_spacetime_type}` to implement `AnonymousView<'_, (), NotSpacetimeType>` -note: required by a bound in `ViewRegistrar::::register` - --> src/rt.rs - | - | pub fn register<'a, A, I, T, V>(view: V) - | -------- required by a bound in this associated function -... - | V: AnonymousView<'a, A, T>, - | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ViewRegistrar::::register` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied - --> tests/ui/views.rs:131:71 - | -131 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `NotSpacetimeType` - | - = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition - = help: the following other types implement trait `SpacetimeType`: - &T - () - AlgebraicType - AlgebraicTypeRef - Arc - ArrayType - Box - ColId - and $N others - = note: required for `Option` to implement `ViewReturn` - -error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied - --> tests/ui/views.rs:130:1 - | -130 | #[view(public)] - | ^^^^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `NotSpacetimeType` - | - = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition - = help: the following other types implement trait `SpacetimeType`: - &T - () - AlgebraicType - AlgebraicTypeRef - Arc - ArrayType - Box - ColId - and $N others - = note: required for `for<'a> fn(&'a AnonymousViewContext) -> Option {view_def_returns_not_a_spacetime_type}` to implement `AnonymousView<'_, (), NotSpacetimeType>` -note: required by a bound in `ViewDispatcher::::invoke` - --> src/rt.rs - | - | pub fn invoke<'a, A, T, V>(view: V, ctx: AnonymousViewContext, args: &'a [u8]) -> Vec - | ------ required by a bound in this associated function -... - | V: AnonymousView<'a, A, T>, - | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ViewDispatcher::::invoke` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `NotSpacetimeType: Serialize` is not satisfied - --> tests/ui/views.rs:130:1 - | -130 | #[view(public)] - | ^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `NotSpacetimeType` - | - = help: the following other types implement trait `Serialize`: - &T - () - (T0, T1) - (T0, T1, T2) - (T0, T1, T2, T3) - (T0, T1, T2, T3, T4) - (T0, T1, T2, T3, T4, T5) - (T0, T1, T2, T3, T4, T5, T6) - and $N others - = note: required for `for<'a> fn(&'a AnonymousViewContext) -> Option {view_def_returns_not_a_spacetime_type}` to implement `AnonymousView<'_, (), NotSpacetimeType>` -note: required by a bound in `ViewDispatcher::::invoke` - --> src/rt.rs - | - | pub fn invoke<'a, A, T, V>(view: V, ctx: AnonymousViewContext, args: &'a [u8]) -> Vec - | ------ required by a bound in this associated function -... - | V: AnonymousView<'a, A, T>, - | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ViewDispatcher::::invoke` - = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied - --> tests/ui/views.rs:131:71 - | -131 | fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option { - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `SpacetimeType` is not implemented for `NotSpacetimeType` - | - = note: if you own the type, try adding `#[derive(SpacetimeType)]` to its definition - = help: the following other types implement trait `SpacetimeType`: - &T - () - AlgebraicType - AlgebraicTypeRef - Arc - ArrayType - Box - ColId - and $N others - = note: required for `Option` to implement `SpacetimeType` - -error[E0631]: type mismatch in function arguments - --> tests/ui/views.rs:136:56 - | -136 | #[spacetimedb::table(name = scheduled_table, scheduled(scheduled_table_view))] - | -------------------------------------------------------^^^^^^^^^^^^^^^^^^^^--- - | | | - | | expected due to this - | required by a bound introduced by this call -... -148 | fn scheduled_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { - | ------------------------------------------------------------------------------ found signature defined here - | - = note: expected function signature `for<'a> fn(&'a ReducerContext, ScheduledTable) -> _` - found function signature `fn(&ViewContext, ScheduledTable) -> _` - = note: required for `for<'a> fn(&'a ViewContext, ScheduledTable) -> Vec {scheduled_table_view}` to implement `Reducer<'_, (ScheduledTable,)>` - = note: required for `for<'a> fn(&'a ViewContext, ScheduledTable) -> Vec {scheduled_table_view}` to implement `ReducerForScheduledTable<'_, ScheduledTable>` -note: required by a bound in `scheduled_reducer_typecheck` - --> src/rt.rs - | - | pub const fn scheduled_reducer_typecheck<'de, Row>(_x: impl ReducerForScheduledTable<'de, Row>) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `scheduled_reducer_typecheck` -help: consider wrapping the function in a closure - | -136 | #[spacetimedb::table(name = scheduled_table, scheduled(|arg0: &ReducerContext, arg1: ScheduledTable| scheduled_table_view(/* &ViewContext */, arg1)))] - | +++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++ diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index 17a4cfec81e..f43fc5d6b88 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -10,7 +10,7 @@ use super::{ }; use crate::system_tables::{ system_tables, ConnectionIdViaU128, StConnectionCredentialsFields, StConnectionCredentialsRow, StViewFields, - StViewParamRow, ST_CONNECTION_CREDENTIALS_ID, ST_VIEW_COLUMN_ID, ST_VIEW_ID, ST_VIEW_PARAM_ID, + StViewParamRow, ST_CONNECTION_CREDENTIALS_ID, ST_VIEW_COLUMN_ID, ST_VIEW_ID, }; use crate::traits::{InsertFlags, RowTypeForTable, TxData, UpdateFlags}; use crate::{ @@ -380,7 +380,7 @@ impl MutTxId { fn insert_into_st_view_param(&mut self, view_id: ViewId, params: &ProductType) -> Result<()> { for (i, field) in params.elements.iter().enumerate() { self.insert_via_serialize_bsatn( - ST_VIEW_PARAM_ID, + ST_VIEW_COLUMN_ID, &StViewParamRow { view_id, param_pos: i.into(), diff --git a/crates/schema/src/def/validate/v9.rs b/crates/schema/src/def/validate/v9.rs index b8d92cfd79c..678d81f6ff5 100644 --- a/crates/schema/src/def/validate/v9.rs +++ b/crates/schema/src/def/validate/v9.rs @@ -465,21 +465,26 @@ impl ModuleValidator<'_> { }) }; - // The possible return types of a view are `Vec` or `Option`, + // The possible return types of a view are `T`, `Option`, or `Vec`, // where `T` is a `ProductType` in the `Typespace`. // Here we extract the inner product type ref `T`. // We exit early for errors since this breaks all the other checks. - let product_type_ref = return_type - .as_option() - .and_then(AlgebraicType::as_ref) - .or_else(|| { - return_type - .as_array() - .map(|array_type| array_type.elem_ty.as_ref()) - .and_then(AlgebraicType::as_ref) - }) - .cloned() - .ok_or_else(invalid_return_type)?; + let product_type_ref = if return_type.is_option() { + return_type + .as_option() + .and_then(AlgebraicType::as_ref) + .cloned() + .ok_or_else(invalid_return_type)? + } else if return_type.is_array() { + return_type + .as_array() + .map(|array_type| array_type.elem_ty.as_ref()) + .and_then(AlgebraicType::as_ref) + .cloned() + .ok_or_else(invalid_return_type)? + } else { + return_type.as_ref().cloned().ok_or_else(invalid_return_type)? + }; let product_type = self .typespace diff --git a/smoketests/tests/views.py b/smoketests/tests/views.py deleted file mode 100644 index d5a2ffd163a..00000000000 --- a/smoketests/tests/views.py +++ /dev/null @@ -1,103 +0,0 @@ -from .. import Smoketest, random_string - - -class Views(Smoketest): - MODULE_CODE = """ -use spacetimedb::ViewContext; - -#[derive(Copy, Clone)] -#[spacetimedb::table(name = player_state)] -pub struct PlayerState { - #[primary_key] - id: u64, - #[index(btree)] - level: u64, -} - -#[spacetimedb::view(public)] -pub fn player(ctx: &ViewContext, id: u64) -> Option { - ctx.db.player_state().id().find(id) -} -""" - - def assertSql(self, sql, expected): - self.maxDiff = None - sql_out = self.spacetime("sql", self.database_identity, sql) - sql_out = "\n".join([line.rstrip() for line in sql_out.splitlines()]) - expected = "\n".join([line.rstrip() for line in expected.splitlines()]) - self.assertMultiLineEqual(sql_out, expected) - - def test_st_view_tables(self): - """This test asserts that views populate the st_view_* system tables""" - - self.assertSql("SELECT * FROM st_view", """\ - view_id | view_name | table_id | is_public | is_anonymous ----------+-----------+---------------+-----------+-------------- - 4096 | "player" | (some = 4097) | true | false -""") - - self.assertSql("SELECT * FROM st_view_param", """\ - view_id | param_pos | param_name | param_type ----------+-----------+------------+------------ - 4096 | 0 | "id" | 0x0d -""") - - self.assertSql("SELECT * FROM st_view_column", """\ - view_id | col_pos | col_name | col_type ----------+---------+----------+---------- - 4096 | 0 | "id" | 0x0d - 4096 | 1 | "level" | 0x0d -""") - -class FailPublish(Smoketest): - AUTOPUBLISH = False - - MODULE_CODE_BROKEN_NAMESPACE = """ -use spacetimedb::ViewContext; - -#[spacetimedb::table(name = person, public)] -pub struct Person { - name: String, -} - -#[spacetimedb::view(public)] -pub fn person(ctx: &ViewContext) -> Option { - None -} -""" - - MODULE_CODE_BROKEN_RETURN_TYPE = """ -use spacetimedb::{SpacetimeType, ViewContext}; - -#[derive(SpacetimeType)] -pub enum ABC { - A, - B, - C, -} - -#[spacetimedb::view(public)] -pub fn person(ctx: &ViewContext) -> Option { - None -} -""" - - def test_fail_publish_namespace_collision(self): - """Publishing a module should fail if a table and view have the same name""" - - name = random_string() - - self.write_module_code(self.MODULE_CODE_BROKEN_NAMESPACE) - - with self.assertRaises(Exception): - self.publish_module(name) - - def test_fail_publish_wrong_return_type(self): - """Publishing a module should fail if the inner return type is not a product type""" - - name = random_string() - - self.write_module_code(self.MODULE_CODE_BROKEN_RETURN_TYPE) - - with self.assertRaises(Exception): - self.publish_module(name) From 6c277b2c55f1be55cbeb0150a7fa3f9a48e35095 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:30 -0700 Subject: [PATCH 13/16] [release/v1.7.0]: Revert "Add module def types for views (#3427)" This reverts commit 8d1298b76d876f37fa71d2d4f850f4fa64d1153b. --- .../Autogen/RawMiscModuleExportV9.g.cs | 3 +- .../Internal/Autogen/RawViewDefV9.g.cs | 49 ----- crates/core/src/db/relational_db.rs | 11 +- crates/core/src/host/module_host.rs | 23 +- .../src/locking_tx_datastore/mut_tx.rs | 107 +-------- crates/datastore/src/system_tables.rs | 2 +- crates/lib/src/db/raw_def/v9.rs | 56 +---- crates/sats/src/algebraic_type.rs | 5 - crates/schema/src/def.rs | 109 +--------- crates/schema/src/def/validate/v9.rs | 205 ++---------------- crates/schema/src/error.rs | 41 +--- crates/schema/src/schema.rs | 104 +-------- crates/schema/src/type_for_generate.rs | 4 +- 13 files changed, 36 insertions(+), 683 deletions(-) delete mode 100644 crates/bindings-csharp/Runtime/Internal/Autogen/RawViewDefV9.g.cs diff --git a/crates/bindings-csharp/Runtime/Internal/Autogen/RawMiscModuleExportV9.g.cs b/crates/bindings-csharp/Runtime/Internal/Autogen/RawMiscModuleExportV9.g.cs index ca42e53f391..45b5cf690cd 100644 --- a/crates/bindings-csharp/Runtime/Internal/Autogen/RawMiscModuleExportV9.g.cs +++ b/crates/bindings-csharp/Runtime/Internal/Autogen/RawMiscModuleExportV9.g.cs @@ -10,7 +10,6 @@ namespace SpacetimeDB.Internal [SpacetimeDB.Type] public partial record RawMiscModuleExportV9 : SpacetimeDB.TaggedEnum<( RawColumnDefaultValueV9 ColumnDefaultValue, - RawProcedureDefV9 Procedure, - RawViewDefV9 View + RawProcedureDefV9 Procedure )>; } diff --git a/crates/bindings-csharp/Runtime/Internal/Autogen/RawViewDefV9.g.cs b/crates/bindings-csharp/Runtime/Internal/Autogen/RawViewDefV9.g.cs deleted file mode 100644 index e7fb7c3f5ad..00000000000 --- a/crates/bindings-csharp/Runtime/Internal/Autogen/RawViewDefV9.g.cs +++ /dev/null @@ -1,49 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB.Internal -{ - [SpacetimeDB.Type] - [DataContract] - public sealed partial class RawViewDefV9 - { - [DataMember(Name = "name")] - public string Name; - [DataMember(Name = "is_public")] - public bool IsPublic; - [DataMember(Name = "is_anonymous")] - public bool IsAnonymous; - [DataMember(Name = "params")] - public List Params; - [DataMember(Name = "return_type")] - public SpacetimeDB.BSATN.AlgebraicType ReturnType; - - public RawViewDefV9( - string Name, - bool IsPublic, - bool IsAnonymous, - List Params, - SpacetimeDB.BSATN.AlgebraicType ReturnType - ) - { - this.Name = Name; - this.IsPublic = IsPublic; - this.IsAnonymous = IsAnonymous; - this.Params = Params; - this.ReturnType = ReturnType; - } - - public RawViewDefV9() - { - this.Name = ""; - this.Params = new(); - this.ReturnType = null!; - } - } -} diff --git a/crates/core/src/db/relational_db.rs b/crates/core/src/db/relational_db.rs index 639ae8f0ba4..c7b05a7bbfb 100644 --- a/crates/core/src/db/relational_db.rs +++ b/crates/core/src/db/relational_db.rs @@ -42,7 +42,7 @@ use spacetimedb_primitives::*; use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type; use spacetimedb_sats::memory_usage::MemoryUsage; use spacetimedb_sats::{AlgebraicType, AlgebraicValue, ProductType, ProductValue}; -use spacetimedb_schema::def::{ModuleDef, TableDef, ViewDef}; +use spacetimedb_schema::def::{ModuleDef, TableDef}; use spacetimedb_schema::schema::{ ColumnSchema, IndexSchema, RowLevelSecuritySchema, Schema, SequenceSchema, TableSchema, }; @@ -1055,15 +1055,6 @@ impl RelationalDB { Ok(self.inner.create_table_mut_tx(tx, schema)?) } - pub fn create_view_table( - &self, - tx: &mut MutTx, - module_def: &ModuleDef, - view_def: &ViewDef, - ) -> Result<(ViewId, TableId), DBError> { - Ok(tx.create_view_with_backing_table(module_def, view_def)?) - } - pub fn create_table_for_test_with_the_works( &self, name: &str, diff --git a/crates/core/src/host/module_host.rs b/crates/core/src/host/module_host.rs index 899f374cff1..69d6e4fe0b5 100644 --- a/crates/core/src/host/module_host.rs +++ b/crates/core/src/host/module_host.rs @@ -51,7 +51,7 @@ use spacetimedb_query::compile_subscription; use spacetimedb_sats::ProductValue; use spacetimedb_schema::auto_migrate::{AutoMigrateError, MigrationPolicy}; use spacetimedb_schema::def::deserialize::ArgsSeed; -use spacetimedb_schema::def::{ModuleDef, ReducerDef, TableDef, ViewDef}; +use spacetimedb_schema::def::{ModuleDef, ReducerDef, TableDef}; use spacetimedb_schema::schema::{Schema, TableSchema}; use spacetimedb_vm::relation::RelValue; use std::collections::VecDeque; @@ -415,18 +415,6 @@ pub fn create_table_from_def( Ok(()) } -/// Creates the table for `view_def` in `stdb`. -pub fn create_table_from_view_def( - stdb: &RelationalDB, - tx: &mut MutTxId, - module_def: &ModuleDef, - view_def: &ViewDef, -) -> anyhow::Result<()> { - stdb.create_view_table(tx, module_def, view_def) - .with_context(|| format!("failed to create table for view {}", &view_def.name))?; - Ok(()) -} - /// If the module instance's replica_ctx is uninitialized, initialize it. fn init_database( replica_ctx: &ReplicaContext, @@ -450,15 +438,6 @@ fn init_database( logger.info(&format!("Creating table `{}`", &def.name)); create_table_from_def(stdb, tx, module_def, def)?; } - - let mut view_defs: Vec<_> = module_def.views().collect(); - view_defs.sort_by(|a, b| a.name.cmp(&b.name)); - - for def in view_defs { - logger.info(&format!("Creating table for view `{}`", &def.name)); - create_table_from_view_def(stdb, tx, module_def, def)?; - } - // Insert the late-bound row-level security expressions. for rls in module_def.row_level_security() { logger.info(&format!("Creating row level security `{}`", rls.sql)); diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index f43fc5d6b88..d000187cf40 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -8,9 +8,11 @@ use super::{ tx_state::{IndexIdMap, PendingSchemaChange, TxState, TxTableForInsertion}, SharedMutexGuard, SharedWriteGuard, }; +use crate::execution_context::ExecutionContext; +use crate::execution_context::Workload; use crate::system_tables::{ - system_tables, ConnectionIdViaU128, StConnectionCredentialsFields, StConnectionCredentialsRow, StViewFields, - StViewParamRow, ST_CONNECTION_CREDENTIALS_ID, ST_VIEW_COLUMN_ID, ST_VIEW_ID, + system_tables, ConnectionIdViaU128, StConnectionCredentialsFields, StConnectionCredentialsRow, + ST_CONNECTION_CREDENTIALS_ID, }; use crate::traits::{InsertFlags, RowTypeForTable, TxData, UpdateFlags}; use crate::{ @@ -23,8 +25,6 @@ use crate::{ ST_SEQUENCE_ID, ST_TABLE_ID, }, }; -use crate::{execution_context::ExecutionContext, system_tables::StViewColumnRow}; -use crate::{execution_context::Workload, system_tables::StViewRow}; use core::ops::RangeBounds; use core::{cell::RefCell, mem}; use core::{iter, ops::Bound}; @@ -37,7 +37,7 @@ use spacetimedb_lib::{ ConnectionId, Identity, }; use spacetimedb_primitives::{ - col_list, ColId, ColList, ColSet, ConstraintId, IndexId, ScheduleId, SequenceId, TableId, ViewId, + col_list, ColId, ColList, ColSet, ConstraintId, IndexId, ScheduleId, SequenceId, TableId, }; use spacetimedb_sats::{ bsatn::{self, to_writer, DecodeError, Deserializer}, @@ -45,9 +45,8 @@ use spacetimedb_sats::{ ser::Serialize, AlgebraicType, AlgebraicValue, ProductType, ProductValue, WithTypespace, }; -use spacetimedb_schema::{ - def::{ColumnDef, ModuleDef, ViewDef}, - schema::{ColumnSchema, ConstraintSchema, IndexSchema, RowLevelSecuritySchema, SequenceSchema, TableSchema}, +use spacetimedb_schema::schema::{ + ColumnSchema, ConstraintSchema, IndexSchema, RowLevelSecuritySchema, SequenceSchema, TableSchema, }; use spacetimedb_table::{ blob_store::BlobStore, @@ -215,38 +214,6 @@ impl MutTxId { Ok(()) } - /// Create a backing table for a view. - /// - /// Requires: - /// - Everything [`Self::create_table`] requires. - /// - /// Ensures: - /// - Everything [`Self::create_table`] ensures. - /// - The returned [`ViewId`] is unique and not [`ViewId::SENTINEL`]. - /// - All view metadata maintained by the datastore is created atomically - pub fn create_view_with_backing_table( - &mut self, - module_def: &ModuleDef, - view_def: &ViewDef, - ) -> Result<(ViewId, TableId)> { - let table_schema = TableSchema::from_view_def(module_def, view_def); - let table_id = self.create_table(table_schema)?; - - let ViewDef { - name, - is_anonymous, - is_public, - params, - columns, - .. - } = view_def; - - let view_id = self.insert_into_st_view(name.clone().into(), table_id, *is_public, *is_anonymous)?; - self.insert_into_st_view_param(view_id, params)?; - self.insert_into_st_view_column(view_id, columns)?; - Ok((view_id, table_id)) - } - /// Create a table. /// /// Requires: @@ -351,66 +318,6 @@ impl MutTxId { }) } - /// Insert a row into `st_view`, auto-increments and returns the [`ViewId`]. - fn insert_into_st_view( - &mut self, - view_name: Box, - table_id: TableId, - is_public: bool, - is_anonymous: bool, - ) -> Result { - Ok(self - .insert_via_serialize_bsatn( - ST_VIEW_ID, - &StViewRow { - view_id: ViewId::SENTINEL, - view_name, - table_id: Some(table_id), - is_public, - is_anonymous, - }, - )? - .1 - .collapse() - .read_col(StViewFields::ViewId)?) - } - - /// For each parameter of a view, insert a row into `st_view_param`. - /// This does not include the context parameter. - fn insert_into_st_view_param(&mut self, view_id: ViewId, params: &ProductType) -> Result<()> { - for (i, field) in params.elements.iter().enumerate() { - self.insert_via_serialize_bsatn( - ST_VIEW_COLUMN_ID, - &StViewParamRow { - view_id, - param_pos: i.into(), - param_name: field - .name - .clone() - .unwrap_or_else(|| format!("param_{i}").into_boxed_str()), - param_type: field.algebraic_type.clone().into(), - }, - )?; - } - Ok(()) - } - - /// For each column or field returned in a view, insert a row into `st_view_column`. - fn insert_into_st_view_column(&mut self, view_id: ViewId, columns: &[ColumnDef]) -> Result<()> { - for def in columns { - self.insert_via_serialize_bsatn( - ST_VIEW_COLUMN_ID, - &StViewColumnRow { - view_id, - col_pos: def.col_id, - col_name: def.name.clone().into(), - col_type: def.ty.clone().into(), - }, - )?; - } - Ok(()) - } - fn create_table_internal(&mut self, schema: Arc) { // Construct the in memory tables. let table_id = schema.table_id; diff --git a/crates/datastore/src/system_tables.rs b/crates/datastore/src/system_tables.rs index d5a16070a97..4bd48bf57b1 100644 --- a/crates/datastore/src/system_tables.rs +++ b/crates/datastore/src/system_tables.rs @@ -739,7 +739,7 @@ pub struct StViewRow { /// Currently all views are materialized and therefore are assigned a [`TableId`] by default. pub table_id: Option, /// Is this a public or a private view? - /// Currently only public views are supported. + /// Currently all views are public by default. /// Private views may be supported in the future. pub is_public: bool, /// Is this view anonymous? diff --git a/crates/lib/src/db/raw_def/v9.rs b/crates/lib/src/db/raw_def/v9.rs index d094c1a9a84..58816384383 100644 --- a/crates/lib/src/db/raw_def/v9.rs +++ b/crates/lib/src/db/raw_def/v9.rs @@ -372,15 +372,13 @@ pub struct RawRowLevelSecurityDefV9 { /// If/when we define `RawModuleDefV10`, these should allbe moved out of `misc_exports` and into their own fields. #[derive(Debug, Clone, SpacetimeType)] #[sats(crate = crate)] -#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord, derive_more::From))] +#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))] #[non_exhaustive] pub enum RawMiscModuleExportV9 { /// A default value for a column added during a supervised automigration. ColumnDefaultValue(RawColumnDefaultValueV9), /// A procedure definition. Procedure(RawProcedureDefV9), - /// A view definition. - View(RawViewDefV9), } /// Marks a particular table's column as having a particular default. @@ -446,41 +444,6 @@ impl fmt::Debug for RawScopedTypeNameV9 { } } -/// A view definition. -#[derive(Debug, Clone, SpacetimeType)] -#[sats(crate = crate)] -#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))] -pub struct RawViewDefV9 { - /// The name of the view function as defined in the module - pub name: RawIdentifier, - - /// Is this a public or a private view? - /// Currently only public views are supported. - /// Private views may be supported in the future. - pub is_public: bool, - - /// Is this view anonymous? - /// An anonymous view does not know who called it. - /// Specifically, it is a view that has an `AnonymousViewContext` as its first argument. - /// This type does not have access to the `Identity` of the caller. - pub is_anonymous: bool, - - /// The types and optional names of the parameters, in order. - /// This `ProductType` need not be registered in the typespace. - pub params: ProductType, - - /// The return type of the view. - /// Either `T`, `Option`, or `Vec` where `T` is a `SpacetimeType`. - /// - /// More strictly `T` must be a SATS `ProductType`, - /// however this will be validated by the server on publish. - /// - /// This is the single source of truth for the views's columns. - /// All elements of the inner `ProductType` must have names. - /// This again will be validated by the server on publish. - pub return_type: AlgebraicType, -} - /// A reducer definition. #[derive(Debug, Clone, SpacetimeType)] #[sats(crate = crate)] @@ -729,23 +692,6 @@ impl RawModuleDefV9Builder { })) } - pub fn add_view( - &mut self, - name: impl Into, - is_public: bool, - is_anonymous: bool, - params: ProductType, - return_type: AlgebraicType, - ) { - self.module.misc_exports.push(RawMiscModuleExportV9::View(RawViewDefV9 { - name: name.into(), - is_public, - is_anonymous, - params, - return_type, - })); - } - /// Add a row-level security policy to the module. /// /// The `sql` expression should be a valid SQL expression that will be used to filter rows. diff --git a/crates/sats/src/algebraic_type.rs b/crates/sats/src/algebraic_type.rs index e42ba015639..c4a45fe1d3b 100644 --- a/crates/sats/src/algebraic_type.rs +++ b/crates/sats/src/algebraic_type.rs @@ -202,11 +202,6 @@ impl AlgebraicType { matches!(self, Self::Sum(p) if p.is_empty()) } - /// Returns whether this type is an option type. - pub fn is_option(&self) -> bool { - matches!(self, Self::Sum(p) if p.is_option()) - } - /// If this type is the standard option type, returns the type of the `some` variant. /// Otherwise, returns `None`. pub fn as_option(&self) -> Option<&AlgebraicType> { diff --git a/crates/schema/src/def.rs b/crates/schema/src/def.rs index 8f23c746736..fb8db9fccf3 100644 --- a/crates/schema/src/def.rs +++ b/crates/schema/src/def.rs @@ -35,7 +35,7 @@ use spacetimedb_lib::db::raw_def::v9::{ Lifecycle, RawColumnDefaultValueV9, RawConstraintDataV9, RawConstraintDefV9, RawIdentifier, RawIndexAlgorithm, RawIndexDefV9, RawMiscModuleExportV9, RawModuleDefV9, RawProcedureDefV9, RawReducerDefV9, RawRowLevelSecurityDefV9, RawScheduleDefV9, RawScopedTypeNameV9, RawSequenceDefV9, RawSql, RawTableDefV9, RawTypeDefV9, - RawUniqueConstraintDataV9, RawViewDefV9, TableAccess, TableType, + RawUniqueConstraintDataV9, TableAccess, TableType, }; use spacetimedb_lib::{ProductType, RawModuleDef}; use spacetimedb_primitives::{ColId, ColList, ColOrCols, ColSet, ProcedureId, ReducerId, TableId}; @@ -109,12 +109,6 @@ pub struct ModuleDef { /// so that `__call_procedure__` receives stable integer IDs. procedures: IndexMap, - /// The views of the module definition. - /// - /// Like `reducers`, this uses [`IndexMap`] to preserve order - /// so that `__call_view__` receives stable integer IDs. - views: IndexMap, - /// A map from lifecycle reducer kind to reducer id. lifecycle_reducers: EnumMap>, @@ -178,11 +172,6 @@ impl ModuleDef { self.procedures.values() } - /// The views of the module definition. - pub fn views(&self) -> impl Iterator { - self.views.values() - } - /// The type definitions of the module definition. pub fn types(&self) -> impl Iterator { self.types.values() @@ -375,7 +364,6 @@ impl From for RawModuleDefV9 { fn from(val: ModuleDef) -> Self { let ModuleDef { tables, - views, reducers, lifecycle_reducers: _, types, @@ -394,8 +382,7 @@ impl From for RawModuleDefV9 { // TODO: Do we need to include default values here? misc_exports: procedures .into_iter() - .map(|(_, def)| def.into()) - .chain(views.into_iter().map(|(_, def)| def.into())) + .map(|(_, def)| RawMiscModuleExportV9::Procedure(def.into())) .collect(), typespace, row_level_security: row_level_security_raw.into_iter().map(|(_, def)| def).collect(), @@ -971,80 +958,6 @@ impl From for RawScopedTypeNameV9 { } } -/// A view exported by the module. -#[derive(Debug, Clone, Eq, PartialEq)] -#[non_exhaustive] -pub struct ViewDef { - /// The name of the view. This must be unique within the module. - pub name: Identifier, - - /// Is this a public or a private view? - /// Currently only public views are supported. - /// Private views may be supported in the future. - pub is_public: bool, - - /// Is this view anonymous? - /// An anonymous view does not know who called it. - /// Specifically, it is a view that has an `AnonymousViewContext` as its first argument. - /// This type does not have access to the `Identity` of the caller. - pub is_anonymous: bool, - - /// The parameters of the view. - /// - /// This `ProductType` need not be registered in the module's `Typespace`. - pub params: ProductType, - - /// The parameters of the view, formatted for client codegen. - /// - /// This `ProductType` need not be registered in the module's `TypespaceForGenerate`. - pub params_for_generate: ProductTypeDef, - - /// The return type of the view. - /// Either `T`, `Option`, or `Vec` where `T` is a [`ProductType`]. - /// - /// Here `Option` refers to [`AlgebraicType::option()`] and `Vec` refers to [`AlgebraicType::array()`]. - /// - /// `T` defines the columns of the view. - /// `T` will be registered in the module's `Typespace`. - pub return_type: AlgebraicType, - - /// The return type of the view, formatted for client codegen. - pub return_type_for_generate: AlgebraicTypeUse, - - /// The columns of this view. - /// The same information is stored in `return_type`. - /// This is just a more convenient-to-access format. - pub columns: Vec, -} - -impl From for RawViewDefV9 { - fn from(val: ViewDef) -> Self { - let ViewDef { - name, - is_anonymous, - is_public, - params, - params_for_generate: _, - return_type, - return_type_for_generate: _, - columns: _, - } = val; - RawViewDefV9 { - name: name.into(), - is_anonymous, - is_public, - params, - return_type, - } - } -} - -impl From for RawMiscModuleExportV9 { - fn from(def: ViewDef) -> Self { - Self::View(def.into()) - } -} - /// A reducer exported by the module. #[derive(Debug, Clone, Eq, PartialEq)] #[non_exhaustive] @@ -1117,12 +1030,6 @@ impl From for RawProcedureDefV9 { } } -impl From for RawMiscModuleExportV9 { - fn from(def: ProcedureDef) -> Self { - Self::Procedure(def.into()) - } -} - impl ModuleDefLookup for TableDef { type Key<'a> = &'a Identifier; @@ -1242,18 +1149,6 @@ impl ModuleDefLookup for ReducerDef { } } -impl ModuleDefLookup for ViewDef { - type Key<'a> = &'a Identifier; - - fn key(&self) -> Self::Key<'_> { - &self.name - } - - fn lookup<'a>(view_def: &'a ModuleDef, key: Self::Key<'_>) -> Option<&'a Self> { - view_def.views.get(key) - } -} - fn to_raw(data: HashMap) -> Vec where Def: ModuleDefLookup + Into, diff --git a/crates/schema/src/def/validate/v9.rs b/crates/schema/src/def/validate/v9.rs index 678d81f6ff5..fa7a8b88067 100644 --- a/crates/schema/src/def/validate/v9.rs +++ b/crates/schema/src/def/validate/v9.rs @@ -5,7 +5,6 @@ use crate::{def::validate::Result, error::TypeLocation}; use spacetimedb_data_structures::error_stream::{CollectAllErrors, CombineErrors}; use spacetimedb_data_structures::map::HashSet; use spacetimedb_lib::db::default_element_ordering::{product_type_has_default_ordering, sum_type_has_default_ordering}; -use spacetimedb_lib::db::raw_def::v9::RawViewDefV9; use spacetimedb_lib::ProductType; use spacetimedb_primitives::col_list; use spacetimedb_sats::{bsatn::de::Deserializer, de::DeserializeSeed, WithTypespace}; @@ -55,19 +54,13 @@ pub fn validate(def: RawModuleDefV9) -> Result { // Later on, in `check_function_names_are_unique`, we'll transform this into an `IndexMap`. .collect_all_errors::>(); - let (procedures, misc_exports) = + let (procedures, non_procedure_misc_exports) = misc_exports .into_iter() .partition::, _>(|misc_export| { matches!(misc_export, RawMiscModuleExportV9::Procedure(_)) }); - let (views, misc_exports) = misc_exports - .into_iter() - .partition::, _>(|misc_export| { - matches!(misc_export, RawMiscModuleExportV9::View(_)) - }); - let procedures = procedures .into_iter() .map(|procedure| { @@ -85,21 +78,6 @@ pub fn validate(def: RawModuleDefV9) -> Result { // Later on, in `check_function_names_are_unique`, we'll transform this into an `IndexMap`. .collect_all_errors::>(); - let views = views - .into_iter() - .map(|view| { - let RawMiscModuleExportV9::View(view) = view else { - unreachable!("Already partitioned views separate from other `RawMiscModuleExportV9` variants"); - }; - view - }) - .map(|view| { - validator - .validate_view_def(view) - .map(|view_def| (view_def.name.clone(), view_def)) - }) - .collect_all_errors(); - let tables = tables .into_iter() .map(|table| { @@ -125,17 +103,18 @@ pub fn validate(def: RawModuleDefV9) -> Result { }) .collect_all_errors::>(); - let tables_types_reducers_procedures_views = (tables, types, reducers, procedures, views) - .combine_errors() - .and_then(|(mut tables, types, reducers, procedures, views)| { - let ((reducers, procedures, views), ()) = ( - check_function_names_are_unique(reducers, procedures, views), - check_non_procedure_misc_exports(misc_exports, &validator, &mut tables), - ) - .combine_errors()?; - check_scheduled_functions_exist(&mut tables, &reducers, &procedures)?; - Ok((tables, types, reducers, procedures, views)) - }); + let tables_types_reducers_procedures = + (tables, types, reducers, procedures) + .combine_errors() + .and_then(|(mut tables, types, reducers, procedures)| { + let ((reducers, procedures), ()) = ( + check_function_names_are_unique(reducers, procedures), + check_non_procedure_misc_exports(non_procedure_misc_exports, &validator, &mut tables), + ) + .combine_errors()?; + check_scheduled_functions_exist(&mut tables, &reducers, &procedures)?; + Ok((tables, types, reducers, procedures)) + }); let ModuleValidator { stored_in_table_def, @@ -144,15 +123,14 @@ pub fn validate(def: RawModuleDefV9) -> Result { .. } = validator; - let (tables, types, reducers, procedures, views) = - (tables_types_reducers_procedures_views).map_err(|errors| errors.sort_deduplicate())?; + let (tables, types, reducers, procedures) = + (tables_types_reducers_procedures).map_err(|errors| errors.sort_deduplicate())?; let typespace_for_generate = typespace_for_generate.finish(); Ok(ModuleDef { tables, reducers, - views, types, typespace, typespace_for_generate, @@ -448,101 +426,6 @@ impl ModuleValidator<'_> { }) } - /// Validate a view definition. - fn validate_view_def(&mut self, view_def: RawViewDefV9) -> Result { - let RawViewDefV9 { - name, - is_public, - is_anonymous, - params, - return_type, - } = view_def; - - let invalid_return_type = || { - ValidationErrors::from(ValidationError::InvalidViewReturnType { - view: name.clone(), - ty: return_type.clone().into(), - }) - }; - - // The possible return types of a view are `T`, `Option`, or `Vec`, - // where `T` is a `ProductType` in the `Typespace`. - // Here we extract the inner product type ref `T`. - // We exit early for errors since this breaks all the other checks. - let product_type_ref = if return_type.is_option() { - return_type - .as_option() - .and_then(AlgebraicType::as_ref) - .cloned() - .ok_or_else(invalid_return_type)? - } else if return_type.is_array() { - return_type - .as_array() - .map(|array_type| array_type.elem_ty.as_ref()) - .and_then(AlgebraicType::as_ref) - .cloned() - .ok_or_else(invalid_return_type)? - } else { - return_type.as_ref().cloned().ok_or_else(invalid_return_type)? - }; - - let product_type = self - .typespace - .get(product_type_ref) - .and_then(AlgebraicType::as_product) - .ok_or_else(|| { - ValidationErrors::from(ValidationError::InvalidProductTypeRef { - table: name.clone(), - ref_: product_type_ref, - }) - })?; - - let params_for_generate = self.params_for_generate(¶ms, |position, arg_name| TypeLocation::ViewArg { - view_name: Cow::Borrowed(&name), - position, - arg_name, - }); - - let return_type_for_generate = self.validate_for_type_use( - &TypeLocation::ViewReturn { - view_name: Cow::Borrowed(&name), - }, - &return_type, - ); - - let mut view_in_progress = ViewValidator::new(name.clone(), product_type_ref, product_type, self); - - // Views have the same interface as tables and therefore must be registered in the global namespace. - // - // Note, views also share the "function namespace" with reducers and procedures. - // While this isn't strictly necessary because reducers and views have different calling contexts, - // we may want to support calling views in the same context as reducers in the future (e.g. `spacetime call`). - // Hence we validate uniqueness among reducer, procedure, and view names in a later pass. - // See `check_function_names_are_unique`. - let name = view_in_progress.add_to_global_namespace(name).and_then(identifier); - - let columns = (0..product_type.elements.len()) - .map(|id| view_in_progress.validate_column_def(id.into())) - .collect_all_errors(); - - let (name, params_for_generate, return_type_for_generate, columns) = - (name, params_for_generate, return_type_for_generate, columns).combine_errors()?; - - Ok(ViewDef { - name, - is_anonymous, - is_public, - params, - params_for_generate: ProductTypeDef { - elements: params_for_generate, - recursive: false, // A `ProductTypeDef` not stored in a `Typespace` cannot be recursive. - }, - return_type, - return_type_for_generate, - columns, - }) - } - fn validate_column_default_value( &self, tables: &HashMap, @@ -680,42 +563,6 @@ impl ModuleValidator<'_> { } } -/// A partially validated view. -/// -/// This is just a small wrapper around [`TableValidator`] so that we can: -/// 1. Validate column defs -/// 2. Insert view names into the global namespace. -struct ViewValidator<'a, 'b> { - inner: TableValidator<'a, 'b>, -} - -impl<'a, 'b> ViewValidator<'a, 'b> { - fn new( - raw_name: Box, - product_type_ref: AlgebraicTypeRef, - product_type: &'a ProductType, - module_validator: &'a mut ModuleValidator<'b>, - ) -> Self { - Self { - inner: TableValidator { - raw_name, - product_type_ref, - product_type, - module_validator, - has_sequence: Default::default(), - }, - } - } - - fn validate_column_def(&mut self, col_id: ColId) -> Result { - self.inner.validate_column_def(col_id) - } - - fn add_to_global_namespace(&mut self, name: Box) -> Result> { - self.inner.add_to_global_namespace(name) - } -} - /// A partially validated table. struct TableValidator<'a, 'b> { module_validator: &'a mut ModuleValidator<'b>, @@ -1183,19 +1030,13 @@ fn check_scheduled_functions_exist( .collect_all_errors() } -/// Check that all function (reducer, procedure, or view) names are unique, +/// Check that all function (reducer and procedure) names are unique, /// then re-organize the reducers and procedures into [`IndexMap`]s /// for storage in the [`ModuleDef`]. -#[allow(clippy::type_complexity)] fn check_function_names_are_unique( reducers: Vec<(Identifier, ReducerDef)>, procedures: Vec<(Identifier, ProcedureDef)>, - views: Vec<(Identifier, ViewDef)>, -) -> Result<( - IndexMap, - IndexMap, - IndexMap, -)> { +) -> Result<(IndexMap, IndexMap)> { let mut errors = vec![]; let mut reducers_map = IndexMap::with_capacity(reducers.len()); @@ -1218,17 +1059,7 @@ fn check_function_names_are_unique( } } - let mut views_map = IndexMap::with_capacity(views.len()); - - for (name, def) in views { - if reducers_map.contains_key(&name) || procedures_map.contains_key(&name) || views_map.contains_key(&name) { - errors.push(ValidationError::DuplicateFunctionName { name }); - } else { - views_map.insert(name, def); - } - } - - ErrorStream::add_extra_errors(Ok((reducers_map, procedures_map, views_map)), errors) + ErrorStream::add_extra_errors(Ok((reducers_map, procedures_map)), errors) } fn check_non_procedure_misc_exports( diff --git a/crates/schema/src/error.rs b/crates/schema/src/error.rs index c67456991a9..f7d05c8febf 100644 --- a/crates/schema/src/error.rs +++ b/crates/schema/src/error.rs @@ -82,11 +82,6 @@ pub enum ValidationError { start: Option, max_value: Option, }, - #[error("View {view} has invalid return type {ty}")] - InvalidViewReturnType { - view: RawIdentifier, - ty: PrettyAlgebraicType, - }, #[error("Table {table} has invalid product_type_ref {ref_}")] InvalidProductTypeRef { table: RawIdentifier, @@ -136,7 +131,7 @@ pub enum ValidationError { MultipleColumnDefaultValues { table: RawIdentifier, col_id: ColId }, #[error("Table {table} not found")] TableNotFound { table: RawIdentifier }, - #[error("Name {name} is used for multiple reducers, procedures and/or views")] + #[error("Name {name} is used for multiple reducers and/or procedures")] DuplicateFunctionName { name: Identifier }, } @@ -187,16 +182,8 @@ pub enum TypeLocation<'a> { position: usize, arg_name: Option>, }, - /// A view argument. - ViewArg { - view_name: Cow<'a, str>, - position: usize, - arg_name: Option>, - }, /// A procedure return type. ProcedureReturn { procedure_name: Cow<'a, str> }, - /// A view return type. - ViewReturn { view_name: Cow<'a, str> }, /// A type in the typespace. InTypespace { /// The reference to the type within the typespace. @@ -226,21 +213,9 @@ impl TypeLocation<'_> { position, arg_name: arg_name.map(|s| s.to_string().into()), }, - TypeLocation::ViewArg { - view_name, - position, - arg_name, - } => TypeLocation::ViewArg { - view_name: view_name.to_string().into(), - position, - arg_name: arg_name.map(|s| s.to_string().into()), - }, Self::ProcedureReturn { procedure_name } => TypeLocation::ProcedureReturn { procedure_name: procedure_name.to_string().into(), }, - Self::ViewReturn { view_name } => TypeLocation::ViewReturn { - view_name: view_name.to_string().into(), - }, // needed to convince rustc this is allowed. TypeLocation::InTypespace { ref_ } => TypeLocation::InTypespace { ref_ }, } @@ -272,23 +247,9 @@ impl fmt::Display for TypeLocation<'_> { } Ok(()) } - TypeLocation::ViewArg { - view_name, - position, - arg_name, - } => { - write!(f, "view `{view_name}` argument {position}")?; - if let Some(arg_name) = arg_name { - write!(f, " (`{arg_name}`)")?; - } - Ok(()) - } TypeLocation::ProcedureReturn { procedure_name } => { write!(f, "procedure `{procedure_name}` return value") } - TypeLocation::ViewReturn { view_name } => { - write!(f, "view `{view_name}` return value") - } TypeLocation::InTypespace { ref_ } => { write!(f, "typespace ref `{ref_}`") } diff --git a/crates/schema/src/schema.rs b/crates/schema/src/schema.rs index 2e2b6d2366d..149cb08eb5b 100644 --- a/crates/schema/src/schema.rs +++ b/crates/schema/src/schema.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use crate::def::{ ColumnDef, ConstraintData, ConstraintDef, IndexAlgorithm, IndexDef, ModuleDef, ModuleDefLookup, ScheduleDef, - SequenceDef, TableDef, UniqueConstraintData, ViewDef, + SequenceDef, TableDef, UniqueConstraintData, }; use crate::identifier::Identifier; @@ -587,108 +587,6 @@ pub fn column_schemas_from_defs(module_def: &ModuleDef, columns: &[ColumnDef], t .collect() } -impl TableSchema { - /// Every view is materialized by default. For example: - /// ```rust,ignore - /// #[table] - /// pub struct MyTable { - /// a: u32, - /// b: u32, - /// } - /// - /// #[view(public)] - /// fn my_view(ctx: &ViewContext, x: u32, y: u32) -> Vec { ... } - /// - /// #[view(public)] - /// fn my_anonymous_view(ctx: &AnonymousViewContext, x: u32, y: u32) -> Vec { ... } - /// ``` - /// - /// The above views are materialized with the following schemas: - /// - /// my_view: - /// - /// | sender | x | y | a | b | - /// |----------|-----|-----|-----|-----| - /// | Identity | u32 | u32 | u32 | u32 | - /// - /// my_anonymous_view: - /// - /// | x | y | a | b | - /// |-----|-----|-----|-----| - /// | u32 | u32 | u32 | u32 | - pub fn from_view_def(module_def: &ModuleDef, view_def: &ViewDef) -> Self { - module_def.expect_contains(view_def); - - let ViewDef { - name, - is_anonymous, - is_public, - params, - params_for_generate: _, - return_type: _, - return_type_for_generate: _, - columns: cols, - } = view_def; - - let num_args = params.elements.len(); - let num_cols = cols.len(); - let n = num_args + num_cols + if *is_anonymous { 0 } else { 1 }; - - let mut columns = Vec::with_capacity(n); - - if !is_anonymous { - columns.push(ColumnSchema { - table_id: TableId::SENTINEL, - col_pos: ColId(0), - col_name: "sender".into(), - col_type: AlgebraicType::identity(), - }); - } - - let n = columns.len(); - - for (i, elem) in params.elements.iter().cloned().enumerate() { - columns.push(ColumnSchema { - table_id: TableId::SENTINEL, - col_pos: (n + i).into(), - col_name: elem.name.unwrap_or_else(|| format!("param_{i}").into_boxed_str()), - col_type: elem.algebraic_type, - }); - } - - let n = columns.len(); - - columns.extend( - column_schemas_from_defs(module_def, cols, TableId::SENTINEL) - .into_iter() - .enumerate() - .map(|(i, schema)| ColumnSchema { - col_pos: (n + i).into(), - ..schema - }), - ); - - let table_access = if *is_public { - StAccess::Public - } else { - StAccess::Private - }; - - TableSchema::new( - TableId::SENTINEL, - (*name).clone().into(), - columns, - vec![], - vec![], - vec![], - StTableType::User, - table_access, - None, - None, - ) - } -} - impl Schema for TableSchema { type Def = TableDef; type Id = TableId; diff --git a/crates/schema/src/type_for_generate.rs b/crates/schema/src/type_for_generate.rs index df2eff0446c..4b1e83be69b 100644 --- a/crates/schema/src/type_for_generate.rs +++ b/crates/schema/src/type_for_generate.rs @@ -146,7 +146,7 @@ impl Index<&'_ AlgebraicTypeRef> for TypespaceForGenerate { } /// An algebraic type definition. -#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] +#[derive(Debug, Clone, EnumAsInner)] pub enum AlgebraicTypeDef { /// A product type declaration. Product(ProductTypeDef), @@ -237,7 +237,7 @@ pub struct SumTypeDef { } /// A sum type, all of whose variants contain (). -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone)] pub struct PlainEnumTypeDef { pub variants: Box<[Identifier]>, } From 0f1bc126a06a38cf35dced1cdb9436b56234a290 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:30 -0700 Subject: [PATCH 14/16] [release/v1.7.0]: Revert "add system tables for views (#3419)" This reverts commit 542d26d7ffecafe93e40ac1a991c2ef2b4e4d0cb. --- .../locking_tx_datastore/committed_state.rs | 9 +- .../src/locking_tx_datastore/datastore.rs | 48 +----- .../src/locking_tx_datastore/mut_tx.rs | 13 +- crates/datastore/src/system_tables.rs | 148 +----------------- crates/primitives/src/ids.rs | 6 - crates/primitives/src/lib.rs | 4 +- crates/sats/src/de/impls.rs | 1 - crates/sats/src/ser/impls.rs | 1 - crates/sats/src/typespace.rs | 1 - crates/table/src/read_column.rs | 1 - 10 files changed, 14 insertions(+), 218 deletions(-) diff --git a/crates/datastore/src/locking_tx_datastore/committed_state.rs b/crates/datastore/src/locking_tx_datastore/committed_state.rs index 08579c5e18b..a57aaeef2e4 100644 --- a/crates/datastore/src/locking_tx_datastore/committed_state.rs +++ b/crates/datastore/src/locking_tx_datastore/committed_state.rs @@ -6,10 +6,7 @@ use super::{ tx_state::{IndexIdMap, PendingSchemaChange, TxState}, IterByColEqTx, }; -use crate::system_tables::{ - ST_CONNECTION_CREDENTIALS_ID, ST_CONNECTION_CREDENTIALS_IDX, ST_VIEW_COLUMN_ID, ST_VIEW_COLUMN_IDX, ST_VIEW_ID, - ST_VIEW_IDX, ST_VIEW_PARAM_ID, ST_VIEW_PARAM_IDX, -}; +use crate::system_tables::{ST_CONNECTION_CREDENTIALS_ID, ST_CONNECTION_CREDENTIALS_IDX}; use crate::{ db_metrics::DB_METRICS, error::{DatastoreError, IndexError, TableError}, @@ -256,10 +253,6 @@ impl CommittedState { schemas[ST_CONNECTION_CREDENTIALS_IDX].clone(), ); - self.create_table(ST_VIEW_ID, schemas[ST_VIEW_IDX].clone()); - self.create_table(ST_VIEW_PARAM_ID, schemas[ST_VIEW_PARAM_IDX].clone()); - self.create_table(ST_VIEW_COLUMN_ID, schemas[ST_VIEW_COLUMN_IDX].clone()); - // Insert the sequences into `st_sequences` let (st_sequences, blob_store, pool) = self.get_table_and_blob_store_or_create(ST_SEQUENCE_ID, &schemas[ST_SEQUENCE_IDX]); diff --git a/crates/datastore/src/locking_tx_datastore/datastore.rs b/crates/datastore/src/locking_tx_datastore/datastore.rs index 66a11d79ddc..88054c7e26b 100644 --- a/crates/datastore/src/locking_tx_datastore/datastore.rs +++ b/crates/datastore/src/locking_tx_datastore/datastore.rs @@ -1247,12 +1247,11 @@ mod tests { use crate::system_tables::{ system_tables, StColumnRow, StConnectionCredentialsFields, StConstraintData, StConstraintFields, StConstraintRow, StIndexAlgorithm, StIndexFields, StIndexRow, StRowLevelSecurityFields, StScheduledFields, - StSequenceFields, StSequenceRow, StTableRow, StVarFields, StViewFields, ST_CLIENT_NAME, ST_COLUMN_ID, - ST_COLUMN_NAME, ST_CONNECTION_CREDENTIALS_ID, ST_CONNECTION_CREDENTIALS_NAME, ST_CONSTRAINT_ID, - ST_CONSTRAINT_NAME, ST_INDEX_ID, ST_INDEX_NAME, ST_MODULE_NAME, ST_RESERVED_SEQUENCE_RANGE, - ST_ROW_LEVEL_SECURITY_ID, ST_ROW_LEVEL_SECURITY_NAME, ST_SCHEDULED_ID, ST_SCHEDULED_NAME, ST_SEQUENCE_ID, - ST_SEQUENCE_NAME, ST_TABLE_NAME, ST_VAR_ID, ST_VAR_NAME, ST_VIEW_COLUMN_ID, ST_VIEW_COLUMN_NAME, ST_VIEW_ID, - ST_VIEW_NAME, ST_VIEW_PARAM_ID, ST_VIEW_PARAM_NAME, + StSequenceFields, StSequenceRow, StTableRow, StVarFields, ST_CLIENT_NAME, ST_COLUMN_ID, ST_COLUMN_NAME, + ST_CONNECTION_CREDENTIALS_ID, ST_CONNECTION_CREDENTIALS_NAME, ST_CONSTRAINT_ID, ST_CONSTRAINT_NAME, + ST_INDEX_ID, ST_INDEX_NAME, ST_MODULE_NAME, ST_RESERVED_SEQUENCE_RANGE, ST_ROW_LEVEL_SECURITY_ID, + ST_ROW_LEVEL_SECURITY_NAME, ST_SCHEDULED_ID, ST_SCHEDULED_NAME, ST_SEQUENCE_ID, ST_SEQUENCE_NAME, + ST_TABLE_NAME, ST_VAR_ID, ST_VAR_NAME, }; use crate::traits::{IsolationLevel, MutTx}; use crate::Result; @@ -1266,7 +1265,7 @@ mod tests { use spacetimedb_lib::error::ResultTest; use spacetimedb_lib::st_var::StVarValue; use spacetimedb_lib::{resolved_type_via_v9, ScheduleAt, TimeDuration}; - use spacetimedb_primitives::{col_list, ColId, ScheduleId, ViewId}; + use spacetimedb_primitives::{col_list, ColId, ScheduleId}; use spacetimedb_sats::algebraic_value::ser::value_serialize; use spacetimedb_sats::bsatn::ToBsatn; use spacetimedb_sats::layout::RowTypeLayout; @@ -1705,10 +1704,6 @@ mod tests { TableRow { id: ST_SCHEDULED_ID.into(), name: ST_SCHEDULED_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: Some(StScheduledFields::ScheduleId.into()) }, TableRow { id: ST_ROW_LEVEL_SECURITY_ID.into(), name: ST_ROW_LEVEL_SECURITY_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: Some(StRowLevelSecurityFields::Sql.into()) }, TableRow { id: ST_CONNECTION_CREDENTIALS_ID.into(), name: ST_CONNECTION_CREDENTIALS_NAME, ty: StTableType::System, access: StAccess::Private, primary_key: Some(StConnectionCredentialsFields::ConnectionId.into()) }, - TableRow { id: ST_VIEW_ID.into(), name: ST_VIEW_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: Some(StViewFields::ViewId.into()) }, - TableRow { id: ST_VIEW_PARAM_ID.into(), name: ST_VIEW_PARAM_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: None }, - TableRow { id: ST_VIEW_COLUMN_ID.into(), name: ST_VIEW_COLUMN_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: None }, - ])); #[rustfmt::skip] assert_eq!(query.scan_st_columns()?, map_array([ @@ -1767,22 +1762,6 @@ mod tests { ColRow { table: ST_CONNECTION_CREDENTIALS_ID.into(), pos: 0, name: "connection_id", ty: AlgebraicType::U128 }, ColRow { table: ST_CONNECTION_CREDENTIALS_ID.into(), pos: 1, name: "jwt_payload", ty: AlgebraicType::String }, - - ColRow { table: ST_VIEW_ID.into(), pos: 0, name: "view_id", ty: ViewId::get_type() }, - ColRow { table: ST_VIEW_ID.into(), pos: 1, name: "view_name", ty: AlgebraicType::String }, - ColRow { table: ST_VIEW_ID.into(), pos: 2, name: "table_id", ty: AlgebraicType::option(TableId::get_type()) }, - ColRow { table: ST_VIEW_ID.into(), pos: 3, name: "is_public", ty: AlgebraicType::Bool }, - ColRow { table: ST_VIEW_ID.into(), pos: 4, name: "is_anonymous", ty: AlgebraicType::Bool }, - - ColRow { table: ST_VIEW_PARAM_ID.into(), pos: 0, name: "view_id", ty: ViewId::get_type() }, - ColRow { table: ST_VIEW_PARAM_ID.into(), pos: 1, name: "param_pos", ty: ColId::get_type() }, - ColRow { table: ST_VIEW_PARAM_ID.into(), pos: 2, name: "param_name", ty: AlgebraicType::String }, - ColRow { table: ST_VIEW_PARAM_ID.into(), pos: 3, name: "param_type", ty: AlgebraicType::bytes() }, - - ColRow { table: ST_VIEW_COLUMN_ID.into(), pos: 0, name: "view_id", ty: ViewId::get_type() }, - ColRow { table: ST_VIEW_COLUMN_ID.into(), pos: 1, name: "col_pos", ty: ColId::get_type() }, - ColRow { table: ST_VIEW_COLUMN_ID.into(), pos: 2, name: "col_name", ty: AlgebraicType::String }, - ColRow { table: ST_VIEW_COLUMN_ID.into(), pos: 3, name: "col_type", ty: AlgebraicType::bytes() }, ])); #[rustfmt::skip] assert_eq!(query.scan_st_indexes()?, map_array([ @@ -1799,10 +1778,6 @@ mod tests { IndexRow { id: 11, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(0), name: "st_row_level_security_table_id_idx_btree", }, IndexRow { id: 12, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(1), name: "st_row_level_security_sql_idx_btree", }, IndexRow { id: 13, table: ST_CONNECTION_CREDENTIALS_ID.into(), col: col(0), name: "st_connection_credentials_connection_id_idx_btree", }, - IndexRow { id: 14, table: ST_VIEW_ID.into(), col: col(0), name: "st_view_view_id_idx_btree", }, - IndexRow { id: 15, table: ST_VIEW_ID.into(), col: col(1), name: "st_view_view_name_idx_btree", }, - IndexRow { id: 16, table: ST_VIEW_PARAM_ID.into(), col: col_list![0, 1], name: "st_view_param_view_id_param_pos_idx_btree", }, - IndexRow { id: 17, table: ST_VIEW_COLUMN_ID.into(), col: col_list![0, 1], name: "st_view_column_view_id_col_pos_idx_btree", }, ])); let start = ST_RESERVED_SEQUENCE_RANGE as i128 + 1; #[rustfmt::skip] @@ -1813,7 +1788,6 @@ mod tests { SequenceRow { id: 2, table: ST_INDEX_ID.into(), col_pos: 0, name: "st_index_index_id_seq", start }, SequenceRow { id: 3, table: ST_CONSTRAINT_ID.into(), col_pos: 0, name: "st_constraint_constraint_id_seq", start }, SequenceRow { id: 4, table: ST_SCHEDULED_ID.into(), col_pos: 0, name: "st_scheduled_schedule_id_seq", start }, - SequenceRow { id: 6, table: ST_VIEW_ID.into(), col_pos: 0, name: "st_view_view_id_seq", start }, ], |row| StSequenceRow { allocated: start - 1, @@ -1834,11 +1808,7 @@ mod tests { ConstraintRow { constraint_id: 10, table_id: ST_SCHEDULED_ID.into(), unique_columns: col(1), constraint_name: "st_scheduled_table_id_key", }, ConstraintRow { constraint_id: 11, table_id: ST_ROW_LEVEL_SECURITY_ID.into(), unique_columns: col(1), constraint_name: "st_row_level_security_sql_key", }, ConstraintRow { constraint_id: 12, table_id: ST_CONNECTION_CREDENTIALS_ID.into(), unique_columns: col(0), constraint_name: "st_connection_credentials_connection_id_key", }, - ConstraintRow { constraint_id: 13, table_id: ST_VIEW_ID.into(), unique_columns: col(0), constraint_name: "st_view_view_id_key", }, - ConstraintRow { constraint_id: 14, table_id: ST_VIEW_ID.into(), unique_columns: col(1), constraint_name: "st_view_view_name_key", }, - ConstraintRow { constraint_id: 15, table_id: ST_VIEW_PARAM_ID.into(), unique_columns: col_list![0, 1], constraint_name: "st_view_param_view_id_param_pos_key", }, - ConstraintRow { constraint_id: 16, table_id: ST_VIEW_COLUMN_ID.into(), unique_columns: col_list![0, 1], constraint_name: "st_view_column_view_id_col_pos_key", }, - ])); + ])); // Verify we get back the tables correctly with the proper ids... let cols = query.scan_st_columns()?; @@ -2254,10 +2224,6 @@ mod tests { IndexRow { id: 11, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(0), name: "st_row_level_security_table_id_idx_btree", }, IndexRow { id: 12, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(1), name: "st_row_level_security_sql_idx_btree", }, IndexRow { id: 13, table: ST_CONNECTION_CREDENTIALS_ID.into(), col: col(0), name: "st_connection_credentials_connection_id_idx_btree", }, - IndexRow { id: 14, table: ST_VIEW_ID.into(), col: col(0), name: "st_view_view_id_idx_btree", }, - IndexRow { id: 15, table: ST_VIEW_ID.into(), col: col(1), name: "st_view_view_name_idx_btree", }, - IndexRow { id: 16, table: ST_VIEW_PARAM_ID.into(), col: col_list![0, 1], name: "st_view_param_view_id_param_pos_idx_btree", }, - IndexRow { id: 17, table: ST_VIEW_COLUMN_ID.into(), col: col_list![0, 1], name: "st_view_column_view_id_col_pos_idx_btree", }, IndexRow { id: seq_start, table: FIRST_NON_SYSTEM_ID, col: col(0), name: "Foo_id_idx_btree", }, IndexRow { id: seq_start + 1, table: FIRST_NON_SYSTEM_ID, col: col(1), name: "Foo_name_idx_btree", }, IndexRow { id: seq_start + 2, table: FIRST_NON_SYSTEM_ID, col: col(2), name: "Foo_age_idx_btree", }, diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index d000187cf40..0adae3415a4 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -1029,19 +1029,14 @@ impl MutTxId { /// - The sequence metadata is inserted into the system tables (and other data structures reflecting them). /// - The returned ID is unique and not `SequenceId::SENTINEL`. pub fn create_sequence(&mut self, seq: SequenceSchema) -> Result { + if seq.sequence_id != SequenceId::SENTINEL { + return Err(anyhow::anyhow!("`sequence_id` must be `SequenceId::SENTINEL` in `{:#?}`", seq).into()); + } if seq.table_id == TableId::SENTINEL { return Err(anyhow::anyhow!("`table_id` must not be `TableId::SENTINEL` in `{seq:#?}`").into()); } let table_id = seq.table_id; - let matching_system_table_schema = system_tables().iter().find(|s| s.table_id == table_id).cloned(); - - if seq.sequence_id != SequenceId::SENTINEL && matching_system_table_schema.is_none() { - return Err(anyhow::anyhow!("`sequence_id` must be `SequenceId::SENTINEL` in `{:#?}`", seq).into()); - } - - let sequence_id = seq.sequence_id; - log::trace!( "SEQUENCE CREATING: {} for table: {} and col: {}", seq.sequence_name, @@ -1053,7 +1048,7 @@ impl MutTxId { // NOTE: Because st_sequences has a unique index on sequence_name, this will // fail if the table already exists. let mut sequence_row = StSequenceRow { - sequence_id, + sequence_id: SequenceId::SENTINEL, sequence_name: seq.sequence_name, table_id, col_pos: seq.col_pos, diff --git a/crates/datastore/src/system_tables.rs b/crates/datastore/src/system_tables.rs index 4bd48bf57b1..af60872b705 100644 --- a/crates/datastore/src/system_tables.rs +++ b/crates/datastore/src/system_tables.rs @@ -65,13 +65,6 @@ pub const ST_ROW_LEVEL_SECURITY_ID: TableId = TableId(10); /// The static ID of the table that stores the credentials for each connection. pub const ST_CONNECTION_CREDENTIALS_ID: TableId = TableId(11); -/// The static ID of the table that tracks views -pub const ST_VIEW_ID: TableId = TableId(12); -/// The static ID of the table that tracks view parameters -pub const ST_VIEW_PARAM_ID: TableId = TableId(13); -/// The static ID of the table that tracks view columns -pub const ST_VIEW_COLUMN_ID: TableId = TableId(14); - pub(crate) const ST_CONNECTION_CREDENTIALS_NAME: &str = "st_connection_credentials"; pub const ST_TABLE_NAME: &str = "st_table"; pub const ST_COLUMN_NAME: &str = "st_column"; @@ -83,9 +76,6 @@ pub(crate) const ST_CLIENT_NAME: &str = "st_client"; pub(crate) const ST_SCHEDULED_NAME: &str = "st_scheduled"; pub(crate) const ST_VAR_NAME: &str = "st_var"; pub(crate) const ST_ROW_LEVEL_SECURITY_NAME: &str = "st_row_level_security"; -pub(crate) const ST_VIEW_NAME: &str = "st_view"; -pub(crate) const ST_VIEW_PARAM_NAME: &str = "st_view_param"; -pub(crate) const ST_VIEW_COLUMN_NAME: &str = "st_view_column"; /// Reserved range of sequence values used for system tables. /// /// Ids for user-created tables will start at `ST_RESERVED_SEQUENCE_RANGE`. @@ -114,7 +104,7 @@ pub enum SystemTable { st_row_level_security, } -pub fn system_tables() -> [TableSchema; 14] { +pub fn system_tables() -> [TableSchema; 11] { [ // The order should match the `id` of the system table, that start with [ST_TABLE_IDX]. st_table_schema(), @@ -128,9 +118,6 @@ pub fn system_tables() -> [TableSchema; 14] { st_row_level_security_schema(), st_sequence_schema(), st_connection_credential_schema(), - st_view_schema(), - st_view_param_schema(), - st_view_column_schema(), ] } @@ -170,9 +157,6 @@ pub(crate) const ST_SCHEDULED_IDX: usize = 7; pub(crate) const ST_ROW_LEVEL_SECURITY_IDX: usize = 8; pub(crate) const ST_SEQUENCE_IDX: usize = 9; pub(crate) const ST_CONNECTION_CREDENTIALS_IDX: usize = 10; -pub(crate) const ST_VIEW_IDX: usize = 11; -pub(crate) const ST_VIEW_PARAM_IDX: usize = 12; -pub(crate) const ST_VIEW_COLUMN_IDX: usize = 13; macro_rules! st_fields_enum { ($(#[$attr:meta])* enum $ty_name:ident { $($name:expr, $var:ident = $discr:expr,)* }) => { @@ -217,14 +201,6 @@ st_fields_enum!(enum StTableFields { "table_primary_key", PrimaryKey = 4, }); // WARNING: For a stable schema, don't change the field names and discriminants. -st_fields_enum!(enum StViewFields { - "view_id", ViewId = 0, - "view_name", ViewName = 1, - "table_id", TableId = 2, - "is_public", IsPublic = 3, - "is_anonymous", IsAnonymous = 4, -}); -// WARNING: For a stable schema, don't change the field names and discriminants. st_fields_enum!(enum StColumnFields { "table_id", TableId = 0, "col_pos", ColPos = 1, @@ -232,20 +208,6 @@ st_fields_enum!(enum StColumnFields { "col_type", ColType = 3, }); // WARNING: For a stable schema, don't change the field names and discriminants. -st_fields_enum!(enum StViewColumnFields { - "view_id", ViewId = 0, - "col_pos", ColPos = 1, - "col_name", ColName = 2, - "col_type", ColType = 3, -}); -// WARNING: For a stable schema, don't change the field names and discriminants. -st_fields_enum!(enum StViewParamFields { - "view_id", ViewId = 0, - "param_pos", ParamPos = 1, - "param_name", ParamName = 2, - "param_type", ParamType = 3, -}); -// WARNING: For a stable schema, don't change the field names and discriminants. st_fields_enum!(enum StIndexFields { "index_id", IndexId = 0, "table_id", TableId = 1, @@ -342,15 +304,6 @@ fn system_module_def() -> ModuleDef { .with_unique_constraint(StTableFields::TableName) .with_index_no_accessor_name(btree(StTableFields::TableName)); - let st_view_type = builder.add_type::(); - builder - .build_table(ST_VIEW_NAME, *st_view_type.as_ref().expect("should be ref")) - .with_type(TableType::System) - .with_auto_inc_primary_key(StViewFields::ViewId) - .with_index_no_accessor_name(btree(StViewFields::ViewId)) - .with_unique_constraint(StViewFields::ViewName) - .with_index_no_accessor_name(btree(StViewFields::ViewName)); - let st_raw_column_type = builder.add_type::(); let st_col_row_unique_cols = [StColumnFields::TableId.col_id(), StColumnFields::ColPos.col_id()]; builder @@ -359,22 +312,6 @@ fn system_module_def() -> ModuleDef { .with_unique_constraint(st_col_row_unique_cols) .with_index_no_accessor_name(btree(st_col_row_unique_cols)); - let st_view_col_type = builder.add_type::(); - let st_view_col_unique_cols = [StViewColumnFields::ViewId.col_id(), StViewColumnFields::ColPos.col_id()]; - builder - .build_table(ST_VIEW_COLUMN_NAME, *st_view_col_type.as_ref().expect("should be ref")) - .with_type(TableType::System) - .with_unique_constraint(st_view_col_unique_cols) - .with_index_no_accessor_name(btree(st_view_col_unique_cols)); - - let st_view_param_type = builder.add_type::(); - let st_view_param_unique_cols = [StViewParamFields::ViewId.col_id(), StViewParamFields::ParamPos.col_id()]; - builder - .build_table(ST_VIEW_PARAM_NAME, *st_view_param_type.as_ref().expect("should be ref")) - .with_type(TableType::System) - .with_unique_constraint(st_view_param_unique_cols) - .with_index_no_accessor_name(btree(st_view_param_unique_cols)); - let st_index_type = builder.add_type::(); builder .build_table(ST_INDEX_NAME, *st_index_type.as_ref().expect("should be ref")) @@ -471,9 +408,6 @@ fn system_module_def() -> ModuleDef { validate_system_table::(&result, ST_VAR_NAME); validate_system_table::(&result, ST_SCHEDULED_NAME); validate_system_table::(&result, ST_CONNECTION_CREDENTIALS_NAME); - validate_system_table::(&result, ST_VIEW_NAME); - validate_system_table::(&result, ST_VIEW_PARAM_NAME); - validate_system_table::(&result, ST_VIEW_COLUMN_NAME); result } @@ -509,10 +443,6 @@ lazy_static::lazy_static! { m.insert("st_scheduled_table_id_key", ConstraintId(10)); m.insert("st_row_level_security_sql_key", ConstraintId(11)); m.insert("st_connection_credentials_connection_id_key", ConstraintId(12)); - m.insert("st_view_view_id_key", ConstraintId(13)); - m.insert("st_view_view_name_key", ConstraintId(14)); - m.insert("st_view_param_view_id_param_pos_key", ConstraintId(15)); - m.insert("st_view_column_view_id_col_pos_key", ConstraintId(16)); m }; } @@ -535,10 +465,6 @@ lazy_static::lazy_static! { m.insert("st_row_level_security_table_id_idx_btree", IndexId(11)); m.insert("st_row_level_security_sql_idx_btree", IndexId(12)); m.insert("st_connection_credentials_connection_id_idx_btree", IndexId(13)); - m.insert("st_view_view_id_idx_btree", IndexId(14)); - m.insert("st_view_view_name_idx_btree", IndexId(15)); - m.insert("st_view_param_view_id_param_pos_idx_btree", IndexId(16)); - m.insert("st_view_column_view_id_col_pos_idx_btree", IndexId(17)); m }; } @@ -553,7 +479,6 @@ lazy_static::lazy_static! { m.insert("st_constraint_constraint_id_seq", SequenceId(3)); m.insert("st_scheduled_schedule_id_seq", SequenceId(4)); m.insert("st_sequence_sequence_id_seq", SequenceId(5)); - m.insert("st_view_view_id_seq", SequenceId(6)); m }; } @@ -654,18 +579,6 @@ pub fn st_var_schema() -> TableSchema { st_schema(ST_VAR_NAME, ST_VAR_ID) } -pub fn st_view_schema() -> TableSchema { - st_schema(ST_VIEW_NAME, ST_VIEW_ID) -} - -pub fn st_view_param_schema() -> TableSchema { - st_schema(ST_VIEW_PARAM_NAME, ST_VIEW_PARAM_ID) -} - -pub fn st_view_column_schema() -> TableSchema { - st_schema(ST_VIEW_COLUMN_NAME, ST_VIEW_COLUMN_ID) -} - /// If `table_id` refers to a known system table, return its schema. /// /// Used when restoring from a snapshot; system tables are reinstantiated with this schema, @@ -685,9 +598,6 @@ pub(crate) fn system_table_schema(table_id: TableId) -> Option { ST_CONNECTION_CREDENTIALS_ID => Some(st_connection_credential_schema()), ST_VAR_ID => Some(st_var_schema()), ST_SCHEDULED_ID => Some(st_scheduled_schema()), - ST_VIEW_ID => Some(st_view_schema()), - ST_VIEW_PARAM_ID => Some(st_view_param_schema()), - ST_VIEW_COLUMN_ID => Some(st_view_column_schema()), _ => None, } } @@ -723,32 +633,6 @@ impl From for ProductValue { } } -/// System Table [ST_VIEW_NAME] -/// -/// | view_id | view_name | table_id | is_public | is_anonymous | -/// |---------|-----------|----------|-----------|--------------| -/// | 1 | "player" | 4 | true | true | -#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)] -#[sats(crate = spacetimedb_lib)] -pub struct StViewRow { - /// An auto-inc id for each view - pub view_id: ViewId, - /// The name of the view function as defined in the module - pub view_name: Box, - /// The [`TableId`] for this view if materialized. - /// Currently all views are materialized and therefore are assigned a [`TableId`] by default. - pub table_id: Option, - /// Is this a public or a private view? - /// Currently all views are public by default. - /// Private views may be supported in the future. - pub is_public: bool, - /// Is this view anonymous? - /// An anonymous view does not know who called it. - /// Specifically, it is a view that has an `AnonymousViewContext` as its first argument. - /// This type does not have access to the [`Identity`] of the caller. - pub is_anonymous: bool, -} - /// A wrapper around `AlgebraicType` that acts like `AlgegbraicType::bytes()` for serialization purposes. #[derive(Debug, Clone, PartialEq, Eq)] pub struct AlgebraicTypeViaBytes(pub AlgebraicType); @@ -825,36 +709,6 @@ impl From for StColumnRow { } } -/// System Table [ST_VIEW_COLUMN_NAME] -/// -/// | view_id | col_pos | col_name | col_type | -/// |---------|---------|----------|--------------------| -/// | 1 | 0 | "x" | AlgebraicType::U32 | -#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)] -#[sats(crate = spacetimedb_lib)] -pub struct StViewColumnRow { - /// A foreign key referencing [`ST_VIEW_NAME`]. - pub view_id: ViewId, - pub col_pos: ColId, - pub col_name: Box, - pub col_type: AlgebraicTypeViaBytes, -} - -/// System Table [ST_VIEW_PARAM_NAME] -/// -/// | view_id | param_pos | param_name | param_type | -/// |---------|-----------|------------|-----------------------| -/// | 1 | 0 | "y" | AlgebraicType::U32 | -#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)] -#[sats(crate = spacetimedb_lib)] -pub struct StViewParamRow { - /// A foreign key referencing [`ST_VIEW_NAME`]. - pub view_id: ViewId, - pub param_pos: ColId, - pub param_name: Box, - pub param_type: AlgebraicTypeViaBytes, -} - /// System Table [ST_INDEX_NAME] /// /// | index_id | table_id | index_name | index_algorithm | diff --git a/crates/primitives/src/ids.rs b/crates/primitives/src/ids.rs index 5aadfb8455b..77e1352635b 100644 --- a/crates/primitives/src/ids.rs +++ b/crates/primitives/src/ids.rs @@ -78,12 +78,6 @@ system_id! { } auto_inc_system_id!(TableId); -system_id! { - /// An identifier for a view, unique within a database. - pub struct ViewId(pub u32); -} -auto_inc_system_id!(ViewId); - system_id! { /// An identifier for a sequence, unique within a database. pub struct SequenceId(pub u32); diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 715fc77cec0..d88e541f195 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -7,9 +7,7 @@ mod ids; pub use attr::{AttributeKind, ColumnAttribute, ConstraintKind, Constraints}; pub use col_list::{ColList, ColOrCols, ColSet}; -pub use ids::{ - ColId, ConstraintId, FunctionId, IndexId, ProcedureId, ReducerId, ScheduleId, SequenceId, TableId, ViewId, -}; +pub use ids::{ColId, ConstraintId, FunctionId, IndexId, ProcedureId, ReducerId, ScheduleId, SequenceId, TableId}; /// The minimum size of a chunk yielded by a wasm abi RowIter. pub const ROW_ITER_CHUNK_SIZE: usize = 32 * 1024; diff --git a/crates/sats/src/de/impls.rs b/crates/sats/src/de/impls.rs index cf1090253af..0e50206703f 100644 --- a/crates/sats/src/de/impls.rs +++ b/crates/sats/src/de/impls.rs @@ -742,7 +742,6 @@ impl FieldNameVisitor<'_> for TupleNameVisitor<'_> { } impl_deserialize!([] spacetimedb_primitives::TableId, de => u32::deserialize(de).map(Self)); -impl_deserialize!([] spacetimedb_primitives::ViewId, de => u32::deserialize(de).map(Self)); impl_deserialize!([] spacetimedb_primitives::SequenceId, de => u32::deserialize(de).map(Self)); impl_deserialize!([] spacetimedb_primitives::IndexId, de => u32::deserialize(de).map(Self)); impl_deserialize!([] spacetimedb_primitives::ConstraintId, de => u32::deserialize(de).map(Self)); diff --git a/crates/sats/src/ser/impls.rs b/crates/sats/src/ser/impls.rs index dc737c5b50c..15b2bc67638 100644 --- a/crates/sats/src/ser/impls.rs +++ b/crates/sats/src/ser/impls.rs @@ -258,7 +258,6 @@ impl_serialize!([] ValueWithType<'_, ArrayValue>, (self, ser) => { }); impl_serialize!([] spacetimedb_primitives::TableId, (self, ser) => ser.serialize_u32(self.0)); -impl_serialize!([] spacetimedb_primitives::ViewId, (self, ser) => ser.serialize_u32(self.0)); impl_serialize!([] spacetimedb_primitives::SequenceId, (self, ser) => ser.serialize_u32(self.0)); impl_serialize!([] spacetimedb_primitives::IndexId, (self, ser) => ser.serialize_u32(self.0)); impl_serialize!([] spacetimedb_primitives::ConstraintId, (self, ser) => ser.serialize_u32(self.0)); diff --git a/crates/sats/src/typespace.rs b/crates/sats/src/typespace.rs index 36058c79195..46aeb3ddaa5 100644 --- a/crates/sats/src/typespace.rs +++ b/crates/sats/src/typespace.rs @@ -412,7 +412,6 @@ impl_st!([T] Option, ts => AlgebraicType::option(T::make_type(ts))); impl_st!([] spacetimedb_primitives::ColId, AlgebraicType::U16); impl_st!([] spacetimedb_primitives::TableId, AlgebraicType::U32); -impl_st!([] spacetimedb_primitives::ViewId, AlgebraicType::U32); impl_st!([] spacetimedb_primitives::IndexId, AlgebraicType::U32); impl_st!([] spacetimedb_primitives::SequenceId, AlgebraicType::U32); impl_st!([] spacetimedb_primitives::ConstraintId, AlgebraicType::U32); diff --git a/crates/table/src/read_column.rs b/crates/table/src/read_column.rs index be58029f714..123b751f5a3 100644 --- a/crates/table/src/read_column.rs +++ b/crates/table/src/read_column.rs @@ -326,7 +326,6 @@ macro_rules! impl_read_column_via_from { impl_read_column_via_from! { u16 => spacetimedb_primitives::ColId; - u32 => spacetimedb_primitives::ViewId; u32 => spacetimedb_primitives::TableId; u32 => spacetimedb_primitives::IndexId; u32 => spacetimedb_primitives::ConstraintId; From bc13c8f028da91d5046dcc99c8619bf6f3b4e6f9 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Fri, 31 Oct 2025 11:50:30 -0700 Subject: [PATCH 15/16] [release/v1.7.0]: Revert "ModuleDef and schema changes to support procedures (#3392)" This reverts commit 0a6aa7c5d92570cde0382f6afc7cf1801022acee. --- Cargo.lock | 1 - .../Autogen/RawMiscModuleExportV9.g.cs | 2 +- .../Internal/Autogen/RawProcedureDefV9.g.cs | 41 --- ...ps__spacetimedb_bindings_dependencies.snap | 12 +- crates/client-api/src/routes/database.rs | 4 +- crates/core/src/client/client_connection.rs | 4 +- crates/core/src/client/message_handlers.rs | 6 +- crates/core/src/host/mod.rs | 56 ++-- crates/core/src/host/module_host.rs | 41 ++- crates/core/src/host/scheduler.rs | 10 +- crates/core/src/host/v8/syscall.rs | 2 +- .../src/host/wasmtime/wasm_instance_env.rs | 2 +- .../src/locking_tx_datastore/datastore.rs | 2 +- .../src/locking_tx_datastore/mut_tx.rs | 2 +- crates/datastore/src/system_tables.rs | 7 +- crates/lib/src/db/raw_def/v9.rs | 74 +---- crates/primitives/Cargo.toml | 1 - crates/primitives/src/ids.rs | 17 - crates/primitives/src/lib.rs | 2 +- crates/schema/src/auto_migrate/formatter.rs | 11 +- .../src/auto_migrate/termcolor_formatter.rs | 4 +- crates/schema/src/def.rs | 129 +------- crates/schema/src/def/deserialize.rs | 87 +---- crates/schema/src/def/validate/v8.rs | 4 +- crates/schema/src/def/validate/v9.rs | 306 ++++-------------- crates/schema/src/error.rs | 49 +-- crates/schema/src/schema.rs | 16 +- crates/testing/src/modules.rs | 8 +- 28 files changed, 184 insertions(+), 716 deletions(-) delete mode 100644 crates/bindings-csharp/Runtime/Internal/Autogen/RawProcedureDefV9.g.cs diff --git a/Cargo.lock b/Cargo.lock index ed929d1e017..3ed80a7b0d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7695,7 +7695,6 @@ version = "1.6.0" dependencies = [ "bitflags 2.10.0", "either", - "enum-as-inner", "itertools 0.12.1", "nohash-hasher", "proptest", diff --git a/crates/bindings-csharp/Runtime/Internal/Autogen/RawMiscModuleExportV9.g.cs b/crates/bindings-csharp/Runtime/Internal/Autogen/RawMiscModuleExportV9.g.cs index 45b5cf690cd..b2887f2b49e 100644 --- a/crates/bindings-csharp/Runtime/Internal/Autogen/RawMiscModuleExportV9.g.cs +++ b/crates/bindings-csharp/Runtime/Internal/Autogen/RawMiscModuleExportV9.g.cs @@ -10,6 +10,6 @@ namespace SpacetimeDB.Internal [SpacetimeDB.Type] public partial record RawMiscModuleExportV9 : SpacetimeDB.TaggedEnum<( RawColumnDefaultValueV9 ColumnDefaultValue, - RawProcedureDefV9 Procedure + SpacetimeDB.Unit _Reserved )>; } diff --git a/crates/bindings-csharp/Runtime/Internal/Autogen/RawProcedureDefV9.g.cs b/crates/bindings-csharp/Runtime/Internal/Autogen/RawProcedureDefV9.g.cs deleted file mode 100644 index b286db08c11..00000000000 --- a/crates/bindings-csharp/Runtime/Internal/Autogen/RawProcedureDefV9.g.cs +++ /dev/null @@ -1,41 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB.Internal -{ - [SpacetimeDB.Type] - [DataContract] - public sealed partial class RawProcedureDefV9 - { - [DataMember(Name = "name")] - public string Name; - [DataMember(Name = "params")] - public List Params; - [DataMember(Name = "return_type")] - public SpacetimeDB.BSATN.AlgebraicType ReturnType; - - public RawProcedureDefV9( - string Name, - List Params, - SpacetimeDB.BSATN.AlgebraicType ReturnType - ) - { - this.Name = Name; - this.Params = Params; - this.ReturnType = ReturnType; - } - - public RawProcedureDefV9() - { - this.Name = ""; - this.Params = new(); - this.ReturnType = null!; - } - } -} diff --git a/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap b/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap index a0b8fc583e8..6d29d31a881 100644 --- a/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap +++ b/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap @@ -42,11 +42,6 @@ spacetimedb │ ├── spacetimedb_primitives │ │ ├── bitflags │ │ ├── either -│ │ ├── enum_as_inner -│ │ │ ├── heck -│ │ │ ├── proc_macro2 (*) -│ │ │ ├── quote (*) -│ │ │ └── syn (*) │ │ ├── itertools │ │ │ └── either │ │ └── nohash_hasher @@ -55,7 +50,6 @@ spacetimedb │ └── spacetimedb_primitives │ ├── bitflags │ ├── either -│ ├── enum_as_inner (*) │ ├── itertools │ │ └── either │ └── nohash_hasher @@ -76,7 +70,11 @@ spacetimedb │ │ [build-dependencies] │ │ └── autocfg │ ├── derive_more (*) -│ ├── enum_as_inner (*) +│ ├── enum_as_inner +│ │ ├── heck +│ │ ├── proc_macro2 (*) +│ │ ├── quote (*) +│ │ └── syn (*) │ ├── hex │ ├── itertools (*) │ ├── spacetimedb_bindings_macro (*) diff --git a/crates/client-api/src/routes/database.rs b/crates/client-api/src/routes/database.rs index 7be200d5c26..70a2473ec69 100644 --- a/crates/client-api/src/routes/database.rs +++ b/crates/client-api/src/routes/database.rs @@ -23,7 +23,7 @@ use spacetimedb::host::module_host::ClientConnectedError; use spacetimedb::host::ReducerCallError; use spacetimedb::host::ReducerOutcome; use spacetimedb::host::UpdateDatabaseResult; -use spacetimedb::host::{FunctionArgs, MigratePlanResult}; +use spacetimedb::host::{MigratePlanResult, ReducerArgs}; use spacetimedb::identity::Identity; use spacetimedb::messages::control_db::{Database, HostType}; use spacetimedb_client_api_messages::name::{ @@ -61,7 +61,7 @@ pub async fn call( } let caller_identity = auth.claims.identity; - let args = FunctionArgs::Json(body); + let args = ReducerArgs::Json(body); let db_identity = name_or_identity.resolve(&worker_ctx).await?; let database = worker_ctx_find_database(&worker_ctx, &db_identity) diff --git a/crates/core/src/client/client_connection.rs b/crates/core/src/client/client_connection.rs index ccc63497bc2..85d43482d16 100644 --- a/crates/core/src/client/client_connection.rs +++ b/crates/core/src/client/client_connection.rs @@ -12,7 +12,7 @@ use super::{message_handlers, ClientActorId, MessageHandleError}; use crate::db::relational_db::RelationalDB; use crate::error::DBError; use crate::host::module_host::ClientConnectedError; -use crate::host::{FunctionArgs, ModuleHost, NoSuchModule, ReducerCallError, ReducerCallResult}; +use crate::host::{ModuleHost, NoSuchModule, ReducerArgs, ReducerCallError, ReducerCallResult}; use crate::messages::websocket::Subscribe; use crate::util::asyncify; use crate::util::prometheus_handle::IntGaugeExt; @@ -809,7 +809,7 @@ impl ClientConnection { pub async fn call_reducer( &self, reducer: &str, - args: FunctionArgs, + args: ReducerArgs, request_id: RequestId, timer: Instant, flags: CallReducerFlags, diff --git a/crates/core/src/client/message_handlers.rs b/crates/core/src/client/message_handlers.rs index 90829afa9e5..e2077d948e4 100644 --- a/crates/core/src/client/message_handlers.rs +++ b/crates/core/src/client/message_handlers.rs @@ -2,7 +2,7 @@ use super::messages::{SubscriptionUpdateMessage, SwitchedServerMessage, ToProtoc use super::{ClientConnection, DataMessage, Protocol}; use crate::energy::EnergyQuanta; use crate::host::module_host::{EventStatus, ModuleEvent, ModuleFunctionCall}; -use crate::host::{FunctionArgs, ReducerId}; +use crate::host::{ReducerArgs, ReducerId}; use crate::identity::Identity; use crate::messages::websocket::{CallReducer, ClientMessage, OneOffQuery}; use crate::worker_metrics::WORKER_METRICS; @@ -36,14 +36,14 @@ pub async fn handle(client: &ClientConnection, message: DataMessage, timer: Inst let DeserializeWrapper(message) = serde_json::from_str::>>>(&text)?; message.map_args(|s| { - FunctionArgs::Json(match s { + ReducerArgs::Json(match s { Cow::Borrowed(s) => text.slice_ref(s), Cow::Owned(string) => string.into(), }) }) } DataMessage::Binary(message_buf) => bsatn::from_slice::>(&message_buf)? - .map_args(|b| FunctionArgs::Bsatn(message_buf.slice_ref(b))), + .map_args(|b| ReducerArgs::Bsatn(message_buf.slice_ref(b))), }; let module = client.module(); diff --git a/crates/core/src/host/mod.rs b/crates/core/src/host/mod.rs index d49ae054b27..935de8c7e6b 100644 --- a/crates/core/src/host/mod.rs +++ b/crates/core/src/host/mod.rs @@ -5,9 +5,10 @@ use derive_more::Display; use enum_map::Enum; use once_cell::sync::OnceCell; use spacetimedb_lib::bsatn; -use spacetimedb_lib::de::{serde::SeedWrapper, DeserializeSeed}; +use spacetimedb_lib::de::serde::SeedWrapper; +use spacetimedb_lib::de::DeserializeSeed; use spacetimedb_lib::ProductValue; -use spacetimedb_schema::def::deserialize::{ArgsSeed, FunctionDef}; +use spacetimedb_schema::def::deserialize::ReducerArgsDeserializeSeed; mod disk_storage; mod host_controller; @@ -30,37 +31,37 @@ pub use host_controller::{ pub use module_host::{ModuleHost, NoSuchModule, ReducerCallError, UpdateDatabaseResult}; pub use scheduler::Scheduler; -/// Encoded arguments to a database function. -/// -/// A database function is either a reducer or a procedure. #[derive(Debug)] -pub enum FunctionArgs { +pub enum ReducerArgs { Json(ByteString), Bsatn(Bytes), Nullary, } -impl FunctionArgs { - fn into_tuple(self, seed: ArgsSeed<'_, Def>) -> Result { - self._into_tuple(seed).map_err(|err| InvalidFunctionArguments { +impl ReducerArgs { + fn into_tuple(self, seed: ReducerArgsDeserializeSeed) -> Result { + self._into_tuple(seed).map_err(|err| InvalidReducerArguments { err, - function_name: seed.name().into(), + reducer: (*seed.reducer_def().name).into(), }) } - fn _into_tuple(self, seed: ArgsSeed<'_, Def>) -> anyhow::Result { + fn _into_tuple(self, seed: ReducerArgsDeserializeSeed) -> anyhow::Result { Ok(match self { - FunctionArgs::Json(json) => ArgsTuple { + ReducerArgs::Json(json) => ArgsTuple { tuple: from_json_seed(&json, SeedWrapper(seed))?, bsatn: OnceCell::new(), json: OnceCell::with_value(json), }, - FunctionArgs::Bsatn(bytes) => ArgsTuple { + ReducerArgs::Bsatn(bytes) => ArgsTuple { tuple: seed.deserialize(bsatn::Deserializer::new(&mut &bytes[..]))?, bsatn: OnceCell::with_value(bytes), json: OnceCell::new(), }, - FunctionArgs::Nullary => { - anyhow::ensure!(seed.params().elements.is_empty(), "failed to typecheck args"); + ReducerArgs::Nullary => { + anyhow::ensure!( + seed.reducer_def().params.elements.is_empty(), + "failed to typecheck args" + ); ArgsTuple::nullary() } }) @@ -105,33 +106,14 @@ impl Default for ArgsTuple { // TODO(noa): replace imports from this module with imports straight from primitives. pub use spacetimedb_primitives::ReducerId; -/// Inner error type for [`InvalidReducerArguments`] and [`InvalidProcedureArguments`]. #[derive(thiserror::Error, Debug)] -#[error("invalid arguments for function {function_name}: {err}")] -pub struct InvalidFunctionArguments { +#[error("invalid arguments for reducer {reducer}: {err}")] +pub struct InvalidReducerArguments { #[source] err: anyhow::Error, - function_name: Box, + reducer: Box, } -/// Newtype over [`InvalidFunctionArguments`] which renders with the word "reducer". -#[derive(thiserror::Error, Debug)] -#[error("invalid arguments for reducer {}: {}", .0.function_name, .0.err)] -pub struct InvalidReducerArguments( - #[from] - #[source] - InvalidFunctionArguments, -); - -/// Newtype over [`InvalidFunctionArguments`] which renders with the word "procedure". -#[derive(thiserror::Error, Debug)] -#[error("invalid arguments for procedure {}: {}", .0.function_name, .0.err)] -pub struct InvalidProcedureArguments( - #[from] - #[source] - InvalidFunctionArguments, -); - fn from_json_seed<'de, T: serde::de::DeserializeSeed<'de>>(s: &'de str, seed: T) -> anyhow::Result { let mut de = serde_json::Deserializer::from_str(s); let mut track = serde_path_to_error::Track::new(); diff --git a/crates/core/src/host/module_host.rs b/crates/core/src/host/module_host.rs index 69d6e4fe0b5..a296f48d508 100644 --- a/crates/core/src/host/module_host.rs +++ b/crates/core/src/host/module_host.rs @@ -1,6 +1,4 @@ -use super::{ - ArgsTuple, FunctionArgs, InvalidReducerArguments, ReducerCallResult, ReducerId, ReducerOutcome, Scheduler, -}; +use super::{ArgsTuple, InvalidReducerArguments, ReducerArgs, ReducerCallResult, ReducerId, ReducerOutcome, Scheduler}; use crate::client::messages::{OneOffQueryResponseMessage, SerializableMessage}; use crate::client::{ClientActorId, ClientConnectionSender}; use crate::database_logger::{LogLevel, Record}; @@ -9,7 +7,6 @@ use crate::energy::EnergyQuanta; use crate::error::DBError; use crate::estimation::estimate_rows_scanned; use crate::hash::Hash; -use crate::host::InvalidFunctionArguments; use crate::identity::Identity; use crate::messages::control_db::{Database, HostType}; use crate::module_host_context::ModuleCreationContext; @@ -50,7 +47,7 @@ use spacetimedb_primitives::TableId; use spacetimedb_query::compile_subscription; use spacetimedb_sats::ProductValue; use spacetimedb_schema::auto_migrate::{AutoMigrateError, MigrationPolicy}; -use spacetimedb_schema::def::deserialize::ArgsSeed; +use spacetimedb_schema::def::deserialize::ReducerArgsDeserializeSeed; use spacetimedb_schema::def::{ModuleDef, ReducerDef, TableDef}; use spacetimedb_schema::schema::{Schema, TableSchema}; use spacetimedb_vm::relation::RelValue; @@ -887,7 +884,7 @@ impl ModuleHost { None, reducer_id, reducer_def, - FunctionArgs::Nullary, + ReducerArgs::Nullary, inst, )?; @@ -982,10 +979,10 @@ impl ModuleHost { log::error!( "`call_identity_disconnected`: fallback transaction to delete from `st_client` failed: {err}" ); - InvalidReducerArguments(InvalidFunctionArguments { + InvalidReducerArguments { err: err.into(), - function_name: reducer_name.into(), - }) + reducer: reducer_name.into(), + } .into() }) }; @@ -1014,7 +1011,7 @@ impl ModuleHost { None, reducer_id, reducer_def, - FunctionArgs::Nullary, + ReducerArgs::Nullary, inst, ); @@ -1100,10 +1097,10 @@ impl ModuleHost { timer: Option, reducer_id: ReducerId, reducer_def: &ReducerDef, - args: FunctionArgs, + args: ReducerArgs, ) -> Result { - let reducer_seed = ArgsSeed(self.info.module_def.typespace().with_type(reducer_def)); - let args = args.into_tuple(reducer_seed).map_err(InvalidReducerArguments)?; + let reducer_seed = ReducerArgsDeserializeSeed(self.info.module_def.typespace().with_type(reducer_def)); + let args = args.into_tuple(reducer_seed)?; let caller_connection_id = caller_connection_id.unwrap_or(ConnectionId::ZERO); Ok(self @@ -1134,11 +1131,11 @@ impl ModuleHost { timer: Option, reducer_id: ReducerId, reducer_def: &ReducerDef, - args: FunctionArgs, + args: ReducerArgs, module_instance: &mut Instance, ) -> Result { - let reducer_seed = ArgsSeed(self.info.module_def.typespace().with_type(reducer_def)); - let args = args.into_tuple(reducer_seed).map_err(InvalidReducerArguments)?; + let reducer_seed = ReducerArgsDeserializeSeed(self.info.module_def.typespace().with_type(reducer_def)); + let args = args.into_tuple(reducer_seed)?; let caller_connection_id = caller_connection_id.unwrap_or(ConnectionId::ZERO); Ok(module_instance.call_reducer( @@ -1164,7 +1161,7 @@ impl ModuleHost { request_id: Option, timer: Option, reducer_name: &str, - args: FunctionArgs, + args: ReducerArgs, ) -> Result { let res = async { let (reducer_id, reducer_def) = self @@ -1243,12 +1240,10 @@ impl ModuleHost { Ok(inst.call_reducer(Some(tx), params)) } Ok(None) => Err(ReducerCallError::ScheduleReducerNotFound), - Err(err) => Err(ReducerCallError::Args(InvalidReducerArguments( - InvalidFunctionArguments { - err, - function_name: REDUCER.into(), - }, - ))), + Err(err) => Err(ReducerCallError::Args(InvalidReducerArguments { + err, + reducer: REDUCER.into(), + })), } }) .await? diff --git a/crates/core/src/host/scheduler.rs b/crates/core/src/host/scheduler.rs index cc6ee632154..620c8f059dc 100644 --- a/crates/core/src/host/scheduler.rs +++ b/crates/core/src/host/scheduler.rs @@ -21,7 +21,7 @@ use super::module_host::ModuleEvent; use super::module_host::ModuleFunctionCall; use super::module_host::{CallReducerParams, WeakModuleHost}; use super::module_host::{DatabaseUpdate, EventStatus}; -use super::{FunctionArgs, ModuleHost, ReducerCallError}; +use super::{ModuleHost, ReducerArgs, ReducerCallError}; use spacetimedb_datastore::execution_context::Workload; use spacetimedb_datastore::locking_tx_datastore::MutTxId; use spacetimedb_datastore::system_tables::{StFields, StScheduledFields, ST_SCHEDULED_ID}; @@ -60,7 +60,7 @@ enum SchedulerMessage { }, ScheduleImmediate { reducer_name: String, - args: FunctionArgs, + args: ReducerArgs, }, } @@ -242,7 +242,7 @@ impl Scheduler { Ok(()) } - pub fn volatile_nonatomic_schedule_immediate(&self, reducer_name: String, args: FunctionArgs) { + pub fn volatile_nonatomic_schedule_immediate(&self, reducer_name: String, args: ReducerArgs) { let _ = self.tx.send(MsgOrExit::Msg(SchedulerMessage::ScheduleImmediate { reducer_name, args, @@ -267,7 +267,7 @@ struct SchedulerActor { enum QueueItem { Id { id: ScheduledReducerId, at: Timestamp }, - VolatileNonatomicImmediate { reducer_name: String, args: FunctionArgs }, + VolatileNonatomicImmediate { reducer_name: String, args: ReducerArgs }, } #[cfg(target_pointer_width = "64")] @@ -349,7 +349,7 @@ impl SchedulerActor { .reducer_arg_deserialize_seed(&reducer[..]) .ok_or_else(|| anyhow!("Reducer not found: {reducer}"))?; - let reducer_args = FunctionArgs::Bsatn(bsatn_args.into()).into_tuple(reducer_seed)?; + let reducer_args = ReducerArgs::Bsatn(bsatn_args.into()).into_tuple(reducer_seed)?; // the timestamp we tell the reducer it's running at will be // at least the timestamp it was scheduled to run at. diff --git a/crates/core/src/host/v8/syscall.rs b/crates/core/src/host/v8/syscall.rs index 98eacd38b43..dcf1ba2beed 100644 --- a/crates/core/src/host/v8/syscall.rs +++ b/crates/core/src/host/v8/syscall.rs @@ -1158,7 +1158,7 @@ fn volatile_nonatomic_schedule_immediate<'scope>( get_env(scope)? .instance_env .scheduler - .volatile_nonatomic_schedule_immediate(name, crate::host::FunctionArgs::Bsatn(args.into())); + .volatile_nonatomic_schedule_immediate(name, crate::host::ReducerArgs::Bsatn(args.into())); Ok(()) } diff --git a/crates/core/src/host/wasmtime/wasm_instance_env.rs b/crates/core/src/host/wasmtime/wasm_instance_env.rs index 03df1cac2d8..b17fcf6fffb 100644 --- a/crates/core/src/host/wasmtime/wasm_instance_env.rs +++ b/crates/core/src/host/wasmtime/wasm_instance_env.rs @@ -1006,7 +1006,7 @@ impl WasmInstanceEnv { let args = mem.deref_slice(args, args_len)?; env.instance_env.scheduler.volatile_nonatomic_schedule_immediate( name.to_owned(), - crate::host::FunctionArgs::Bsatn(args.to_vec().into()), + crate::host::ReducerArgs::Bsatn(args.to_vec().into()), ); Ok(()) diff --git a/crates/datastore/src/locking_tx_datastore/datastore.rs b/crates/datastore/src/locking_tx_datastore/datastore.rs index 88054c7e26b..01c0f72b8b8 100644 --- a/crates/datastore/src/locking_tx_datastore/datastore.rs +++ b/crates/datastore/src/locking_tx_datastore/datastore.rs @@ -3314,7 +3314,7 @@ mod tests { table_id: TableId::SENTINEL, schedule_id: ScheduleId::SENTINEL, schedule_name: "schedule".into(), - function_name: "reducer".into(), + reducer_name: "reducer".into(), at_column: 1.into(), }; let sum_ty = AlgebraicType::sum([("foo", AlgebraicType::Bool), ("bar", AlgebraicType::U16)]); diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index 0adae3415a4..d449717d03e 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -275,7 +275,7 @@ impl MutTxId { table_id: schedule.table_id, schedule_id: schedule.schedule_id, schedule_name: schedule.schedule_name, - reducer_name: schedule.function_name, + reducer_name: schedule.reducer_name, at_column: schedule.at_column, }; let id = self diff --git a/crates/datastore/src/system_tables.rs b/crates/datastore/src/system_tables.rs index af60872b705..da97e186e4f 100644 --- a/crates/datastore/src/system_tables.rs +++ b/crates/datastore/src/system_tables.rs @@ -1232,11 +1232,6 @@ impl TryFrom> for StVarRow { pub struct StScheduledRow { pub(crate) schedule_id: ScheduleId, pub(crate) table_id: TableId, - /// The name of the reducer or procedure which will run when this table's rows reach their execution time. - /// - /// Note that, despite the column name, this may refer to either a reducer or a procedure. - /// We cannot change the schema of existing system tables, - /// so we are unable to rename this column. pub(crate) reducer_name: Box, pub(crate) schedule_name: Box, pub(crate) at_column: ColId, @@ -1259,7 +1254,7 @@ impl From for ScheduleSchema { fn from(row: StScheduledRow) -> Self { Self { table_id: row.table_id, - function_name: row.reducer_name, + reducer_name: row.reducer_name, schedule_id: row.schedule_id, schedule_name: row.schedule_name, at_column: row.at_column, diff --git a/crates/lib/src/db/raw_def/v9.rs b/crates/lib/src/db/raw_def/v9.rs index 58816384383..948597c9680 100644 --- a/crates/lib/src/db/raw_def/v9.rs +++ b/crates/lib/src/db/raw_def/v9.rs @@ -82,16 +82,9 @@ pub struct RawModuleDefV9 { pub types: Vec, /// Miscellaneous additional module exports. - /// - /// The enum [`RawMiscModuleExportV9`] can have new variants added - /// without breaking existing compiled modules. - /// As such, this acts as a sort of dumping ground for any exports added after we defined `RawModuleDefV9`. - /// - /// If/when we define `RawModuleDefV10`, these should be moved out of `misc_exports` and into their own fields, - /// and the new `misc_exports` should once again be initially empty. pub misc_exports: Vec, - /// Row level security definitions. + /// Low level security definitions. /// /// Each definition must have a unique name. pub row_level_security: Vec, @@ -301,7 +294,7 @@ pub fn direct(col: impl Into) -> RawIndexAlgorithm { RawIndexAlgorithm::Direct { column: col.into() } } -/// Marks a table as a timer table for a scheduled reducer or procedure. +/// Marks a table as a timer table for a scheduled reducer. /// /// The table must have columns: /// - `scheduled_id` of type `u64`. @@ -314,9 +307,7 @@ pub struct RawScheduleDefV9 { /// Even though there is ABSOLUTELY NO REASON TO. pub name: Option>, - /// The name of the reducer or procedure to call. - /// - /// Despite the field name here, this may be either a reducer or a procedure. + /// The name of the reducer to call. pub reducer_name: RawIdentifier, /// The column of the `scheduled_at` field of this scheduled table. @@ -367,18 +358,12 @@ pub struct RawRowLevelSecurityDefV9 { } /// A miscellaneous module export. -/// -/// All of the variants here were added after the format of [`RawModuleDefV9`] was already stabilized. -/// If/when we define `RawModuleDefV10`, these should allbe moved out of `misc_exports` and into their own fields. #[derive(Debug, Clone, SpacetimeType)] #[sats(crate = crate)] #[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))] #[non_exhaustive] pub enum RawMiscModuleExportV9 { - /// A default value for a column added during a supervised automigration. ColumnDefaultValue(RawColumnDefaultValueV9), - /// A procedure definition. - Procedure(RawProcedureDefV9), } /// Marks a particular table's column as having a particular default. @@ -474,27 +459,6 @@ pub enum Lifecycle { OnDisconnect, } -/// A procedure definition. -/// -/// Will be wrapped in [`RawMiscModuleExportV9`] and included in the [`RawModuleDefV9`]'s `misc_exports` vec. -#[derive(Debug, Clone, SpacetimeType)] -#[sats(crate = crate)] -#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))] -pub struct RawProcedureDefV9 { - /// The name of the procedure. - pub name: RawIdentifier, - - /// The types and optional names of the parameters, in order. - /// This `ProductType` need not be registered in the typespace. - pub params: ProductType, - - /// The type of the return value. - /// - /// If this is a user-defined product or sum type, - /// it should be registered in the typespace and indirected through an [`AlgebraicType::Ref`]. - pub return_type: AlgebraicType, -} - /// A builder for a [`RawModuleDefV9`]. #[derive(Default)] pub struct RawModuleDefV9Builder { @@ -667,31 +631,6 @@ impl RawModuleDefV9Builder { }); } - /// Add a procedure to the in-progress module. - /// - /// Accepts a `ProductType` of arguments. - /// The arguments `ProductType` need not be registered in the typespace. - /// - /// Also accepts an `AlgebraicType` return type. - /// If this is a user-defined product or sum type, - /// it should be registered in the typespace and indirected through an `AlgebraicType::Ref`. - /// - /// The `&mut ProcedureContext` first argument to the procedure should not be included in the `params`. - pub fn add_procedure( - &mut self, - name: impl Into, - params: spacetimedb_sats::ProductType, - return_type: spacetimedb_sats::AlgebraicType, - ) { - self.module - .misc_exports - .push(RawMiscModuleExportV9::Procedure(RawProcedureDefV9 { - name: name.into(), - params, - return_type, - })) - } - /// Add a row-level security policy to the module. /// /// The `sql` expression should be a valid SQL expression that will be used to filter rows. @@ -857,16 +796,13 @@ impl RawTableDefBuilder<'_> { /// Adds a schedule definition to the table. /// - /// The `function_name` should name a reducer or procedure - /// which accepts one argument, a row of this table. - /// /// The table must have the appropriate columns for a scheduled table. pub fn with_schedule( mut self, - function_name: impl Into, + reducer_name: impl Into, scheduled_at_column: impl Into, ) -> Self { - let reducer_name = function_name.into(); + let reducer_name = reducer_name.into(); let scheduled_at_column = scheduled_at_column.into(); self.table.schedule = Some(RawScheduleDefV9 { name: None, diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 2715ccb9b6b..8da5f76e113 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -12,7 +12,6 @@ memory-usage = ["dep:spacetimedb-memory-usage"] [dependencies] bitflags.workspace = true either.workspace = true -enum-as-inner.workspace = true nohash-hasher.workspace = true itertools.workspace = true spacetimedb-memory-usage = { workspace = true, optional = true, default-features = false } diff --git a/crates/primitives/src/ids.rs b/crates/primitives/src/ids.rs index 77e1352635b..fc9a9b69e61 100644 --- a/crates/primitives/src/ids.rs +++ b/crates/primitives/src/ids.rs @@ -2,8 +2,6 @@ use core::fmt; -use enum_as_inner::EnumAsInner; - macro_rules! system_id { ($(#[$($doc_comment:tt)*])* pub struct $name:ident(pub $backing_ty:ty);) => { @@ -118,18 +116,3 @@ system_id! { // This is never stored in a system table, but is useful to have defined here. pub struct ReducerId(pub u32); } - -system_id! { - /// The index of a procedure as defined in a module's procedure list. - // This is never stored in a system table, but is useful to have defined here. - pub struct ProcedureId(pub u32); -} - -/// An id for a function exported from a module, which may be a reducer or a procedure. -// This is never stored in a system table, -// but is useful to have defined here to provide a shared language for downstream crates. -#[derive(Clone, Copy, Debug, EnumAsInner)] -pub enum FunctionId { - Reducer(ReducerId), - Procedure(ProcedureId), -} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index d88e541f195..7ae37765514 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -7,7 +7,7 @@ mod ids; pub use attr::{AttributeKind, ColumnAttribute, ConstraintKind, Constraints}; pub use col_list::{ColList, ColOrCols, ColSet}; -pub use ids::{ColId, ConstraintId, FunctionId, IndexId, ProcedureId, ReducerId, ScheduleId, SequenceId, TableId}; +pub use ids::{ColId, ConstraintId, IndexId, ReducerId, ScheduleId, SequenceId, TableId}; /// The minimum size of a chunk yielded by a wasm abi RowIter. pub const ROW_ITER_CHUNK_SIZE: usize = 32 * 1024; diff --git a/crates/schema/src/auto_migrate/formatter.rs b/crates/schema/src/auto_migrate/formatter.rs index abdf443a9df..54b4ed87ce3 100644 --- a/crates/schema/src/auto_migrate/formatter.rs +++ b/crates/schema/src/auto_migrate/formatter.rs @@ -5,7 +5,7 @@ use std::io; use super::{AutoMigratePlan, IndexAlgorithm, ModuleDefLookup, TableDef}; use crate::{ auto_migrate::AutoMigrateStep, - def::{ConstraintData, FunctionKind, ModuleDef, ScheduleDef}, + def::{ConstraintData, ModuleDef, ScheduleDef}, identifier::Identifier, }; use itertools::Itertools; @@ -188,8 +188,7 @@ pub struct AccessChangeInfo { #[derive(Debug, Clone, PartialEq)] pub struct ScheduleInfo { pub table_name: String, - pub function_name: Identifier, - pub function_kind: FunctionKind, + pub reducer_name: Identifier, } #[derive(Debug, Clone, PartialEq)] @@ -315,8 +314,7 @@ fn extract_table_info( let schedule = table_def.schedule.as_ref().map(|schedule| ScheduleInfo { table_name: table_def.name.to_string().clone(), - function_name: schedule.function_name.clone(), - function_kind: schedule.function_kind, + reducer_name: schedule.reducer_name.clone(), }); Ok(TableInfo { @@ -440,8 +438,7 @@ fn extract_schedule_info( Ok(ScheduleInfo { table_name: schedule_def.name.to_string().clone(), - function_name: schedule_def.function_name.clone(), - function_kind: schedule_def.function_kind, + reducer_name: schedule_def.reducer_name.clone(), }) } diff --git a/crates/schema/src/auto_migrate/termcolor_formatter.rs b/crates/schema/src/auto_migrate/termcolor_formatter.rs index c5c05f7993a..85648e3c244 100644 --- a/crates/schema/src/auto_migrate/termcolor_formatter.rs +++ b/crates/schema/src/auto_migrate/termcolor_formatter.rs @@ -219,7 +219,7 @@ impl MigrationFormatter for TermColorFormatter { if let Some(s) = &table.schedule { self.write_colored_line("Schedule:", Some(self.colors.section_header), true)?; self.indent(); - self.write_bullet(&format!("Calls {}: {}", s.function_kind, s.function_name))?; + self.write_bullet(&format!("Calls reducer: {}", s.reducer_name))?; self.dedent(); } @@ -276,7 +276,7 @@ impl MigrationFormatter for TermColorFormatter { self.buffer.write_all(b" schedule for table ")?; self.write_colored(&s.table_name, Some(self.colors.table_name), true)?; self.buffer - .write_all(format!(" calling {} {}\n", s.function_kind, s.function_name).as_bytes()) + .write_all(format!(" calling reducer {}\n", s.reducer_name).as_bytes()) } fn format_rls(&mut self, r: &RlsInfo, action: Action) -> io::Result<()> { diff --git a/crates/schema/src/def.rs b/crates/schema/src/def.rs index fb8db9fccf3..0e5cea3c82a 100644 --- a/crates/schema/src/def.rs +++ b/crates/schema/src/def.rs @@ -23,7 +23,7 @@ use crate::error::{IdentifierError, ValidationErrors}; use crate::identifier::Identifier; use crate::schema::{Schema, TableSchema}; use crate::type_for_generate::{AlgebraicTypeUse, ProductTypeDef, TypespaceForGenerate}; -use deserialize::ArgsSeed; +use deserialize::ReducerArgsDeserializeSeed; use enum_map::EnumMap; use hashbrown::Equivalent; use indexmap::IndexMap; @@ -33,12 +33,12 @@ use spacetimedb_data_structures::map::HashMap; use spacetimedb_lib::db::raw_def; use spacetimedb_lib::db::raw_def::v9::{ Lifecycle, RawColumnDefaultValueV9, RawConstraintDataV9, RawConstraintDefV9, RawIdentifier, RawIndexAlgorithm, - RawIndexDefV9, RawMiscModuleExportV9, RawModuleDefV9, RawProcedureDefV9, RawReducerDefV9, RawRowLevelSecurityDefV9, - RawScheduleDefV9, RawScopedTypeNameV9, RawSequenceDefV9, RawSql, RawTableDefV9, RawTypeDefV9, - RawUniqueConstraintDataV9, TableAccess, TableType, + RawIndexDefV9, RawMiscModuleExportV9, RawModuleDefV9, RawReducerDefV9, RawRowLevelSecurityDefV9, RawScheduleDefV9, + RawScopedTypeNameV9, RawSequenceDefV9, RawSql, RawTableDefV9, RawTypeDefV9, RawUniqueConstraintDataV9, TableAccess, + TableType, }; use spacetimedb_lib::{ProductType, RawModuleDef}; -use spacetimedb_primitives::{ColId, ColList, ColOrCols, ColSet, ProcedureId, ReducerId, TableId}; +use spacetimedb_primitives::{ColId, ColList, ColOrCols, ColSet, ReducerId, TableId}; use spacetimedb_sats::{AlgebraicType, AlgebraicValue}; use spacetimedb_sats::{AlgebraicTypeRef, Typespace}; @@ -103,12 +103,6 @@ pub struct ModuleDef { /// and must be preserved for future calls to `__call_reducer__`. reducers: IndexMap, - /// The procedures of the module definition. - /// - /// Like `reducers`, this uses [`IndexMap`] to preserve order - /// so that `__call_procedure__` receives stable integer IDs. - procedures: IndexMap, - /// A map from lifecycle reducer kind to reducer id. lifecycle_reducers: EnumMap>, @@ -167,11 +161,6 @@ impl ModuleDef { self.reducers.values() } - /// The procedures of the module definition. - pub fn procedures(&self) -> impl Iterator { - self.procedures.values() - } - /// The type definitions of the module definition. pub fn types(&self) -> impl Iterator { self.types.values() @@ -254,25 +243,6 @@ impl ModuleDef { self.reducers.get_index(id.idx()).map(|(_, def)| def) } - /// Convenience method to look up a procedure, possibly by a string, returning its id as well. - pub fn procedure_full>( - &self, - name: &K, - ) -> Option<(ProcedureId, &ProcedureDef)> { - // If the string IS a valid identifier, we can just look it up. - self.procedures.get_full(name).map(|(idx, _, def)| (idx.into(), def)) - } - - /// Look up a procuedure by its id, panicking if it doesn't exist. - pub fn procedure_by_id(&self, id: ProcedureId) -> &ProcedureDef { - &self.procedures[id.idx()] - } - - /// Look up a procuedure by its id, returning `None` if it doesn't exist. - pub fn get_procedure_by_id(&self, id: ProcedureId) -> Option<&ProcedureDef> { - self.procedures.get_index(id.idx()).map(|(_, def)| def) - } - /// Looks up a lifecycle reducer defined in the module. pub fn lifecycle_reducer(&self, lifecycle: Lifecycle) -> Option<(ReducerId, &ReducerDef)> { self.lifecycle_reducers[lifecycle].map(|i| (i, &self.reducers[i.idx()])) @@ -283,9 +253,9 @@ impl ModuleDef { pub fn reducer_arg_deserialize_seed>( &self, name: &K, - ) -> Option<(ReducerId, ArgsSeed<'_, ReducerDef>)> { + ) -> Option<(ReducerId, ReducerArgsDeserializeSeed<'_>)> { let (id, reducer) = self.reducer_full(name)?; - Some((id, ArgsSeed(self.typespace.with_type(reducer)))) + Some((id, ReducerArgsDeserializeSeed(self.typespace.with_type(reducer)))) } /// Look up the name corresponding to an `AlgebraicTypeRef`. @@ -372,18 +342,13 @@ impl From for RawModuleDefV9 { typespace_for_generate: _, refmap: _, row_level_security_raw, - procedures, } = val; RawModuleDefV9 { tables: to_raw(tables), reducers: reducers.into_iter().map(|(_, def)| def.into()).collect(), types: to_raw(types), - // TODO: Do we need to include default values here? - misc_exports: procedures - .into_iter() - .map(|(_, def)| RawMiscModuleExportV9::Procedure(def.into())) - .collect(), + misc_exports: vec![], typespace, row_level_security: row_level_security_raw.into_iter().map(|(_, def)| def).collect(), } @@ -780,30 +745,7 @@ impl From for RawRowLevelSecurityDefV9 { } } -#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)] -pub enum FunctionKind { - /// Functions which have not yet been determined to be reducers or procedures. - /// - /// Used as a placeholder during module validation, - /// when pre-processing [`ScheduleDef`]s prior to validating their scheduled functions. - /// Will never appear in a fully-validated [`ModuleDef`], - /// and should not be placed in errors either. - Unknown, - Reducer, - Procedure, -} - -impl fmt::Display for FunctionKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match self { - FunctionKind::Unknown => "exported function", - FunctionKind::Reducer => "reducer", - FunctionKind::Procedure => "procedure", - }) - } -} - -/// Marks a table as a timer table for a scheduled reducer or procedure. +/// Marks a table as a timer table for a scheduled reducer. #[derive(Debug, Clone, Eq, PartialEq)] #[non_exhaustive] pub struct ScheduleDef { @@ -820,18 +762,16 @@ pub struct ScheduleDef { /// Must be named `scheduled_id` and be of type `u64`. pub id_column: ColId, - /// The name of the reducer or procedure to call. - pub function_name: Identifier, - - /// Whether the `function_name` refers to a reducer or a procedure. - pub function_kind: FunctionKind, + /// The name of the reducer to call. Not yet an `Identifier` because + /// reducer names are not currently validated. + pub reducer_name: Identifier, } impl From for RawScheduleDefV9 { fn from(val: ScheduleDef) -> Self { RawScheduleDefV9 { name: Some(val.name), - reducer_name: val.function_name.into(), + reducer_name: val.reducer_name.into(), scheduled_at_column: val.at_column, } } @@ -962,7 +902,7 @@ impl From for RawScopedTypeNameV9 { #[derive(Debug, Clone, Eq, PartialEq)] #[non_exhaustive] pub struct ReducerDef { - /// The name of the reducer. This must be unique within the module's set of reducers and procedures. + /// The name of the reducer. This must be unique within the module. pub name: Identifier, /// The parameters of the reducer. @@ -989,47 +929,6 @@ impl From for RawReducerDefV9 { } } -#[derive(Debug, Clone, Eq, PartialEq)] -#[non_exhaustive] -pub struct ProcedureDef { - /// The name of the procedure. - /// - /// This must be unique within the module's set of reducers and procedures. - pub name: Identifier, - - /// The parameters of the procedure. - /// - /// This `ProductType` need not be registered in the module's `Typespace`. - pub params: ProductType, - - /// The parameters of the procedure, formatted for client codegen. - /// - /// This `ProductType` need not be registered in the module's `TypespaceForGenerate`. - pub params_for_generate: ProductTypeDef, - - /// The return type of the procedure. - /// - /// If this is a non-special compound type, it should be registered in the module's `Typespace` - /// and indirected through an [`AlgebraicType::Ref`]. - pub return_type: AlgebraicType, - - /// The return type of the procedure. - /// - /// If this is a non-special compound type, it should be registered in the module's `TypespaceForGenerate` - /// and indirected through an [`AlgebraicTypeUse::Ref`]. - pub return_type_for_generate: AlgebraicTypeUse, -} - -impl From for RawProcedureDefV9 { - fn from(val: ProcedureDef) -> Self { - RawProcedureDefV9 { - name: val.name.into(), - params: val.params, - return_type: val.return_type, - } - } -} - impl ModuleDefLookup for TableDef { type Key<'a> = &'a Identifier; diff --git a/crates/schema/src/def/deserialize.rs b/crates/schema/src/def/deserialize.rs index 09b78f3d2d4..a5a14240584 100644 --- a/crates/schema/src/def/deserialize.rs +++ b/crates/schema/src/def/deserialize.rs @@ -1,60 +1,21 @@ //! Helpers to allow deserializing data using a ReducerDef. -use crate::def::{ProcedureDef, ReducerDef}; -use spacetimedb_lib::{ - sats::{self, de, impl_serialize, ser, ProductValue}, - ProductType, -}; +use crate::def::ReducerDef; +use spacetimedb_lib::sats::{self, de, ProductValue}; -/// Wrapper around a function def that allows deserializing to a [`ProductValue`] at the type of the def's parameter [`ProductType`]. -/// -/// Sensible instantiations for `Def` are [`ProcedureDef`] and [`ReducerDef`]. -pub struct ArgsSeed<'a, Def>(pub sats::WithTypespace<'a, Def>); +/// Wrapper around a `ReducerDef` that allows deserializing to a `ProductValue` at the type +/// of the reducer's parameter `ProductType`. +#[derive(Clone, Copy)] +pub struct ReducerArgsDeserializeSeed<'a>(pub sats::WithTypespace<'a, ReducerDef>); -// Manual impls of traits rather than derives, -// 'cause derives are always constrained on all type parameters, -// even though `ArgsSeed: Copy` in our case. -impl Clone for ArgsSeed<'_, Def> { - fn clone(&self) -> Self { - *self +impl<'a> ReducerArgsDeserializeSeed<'a> { + /// Get the reducer def of this seed. + pub fn reducer_def(&self) -> &'a ReducerDef { + self.0.ty() } } -impl Copy for ArgsSeed<'_, Def> {} -pub trait FunctionDef { - fn params(&self) -> &ProductType; - fn name(&self) -> &str; -} - -impl FunctionDef for ReducerDef { - fn params(&self) -> &ProductType { - &self.params - } - fn name(&self) -> &str { - &self.name - } -} - -impl FunctionDef for ProcedureDef { - fn params(&self) -> &ProductType { - &self.params - } - fn name(&self) -> &str { - &self.name - } -} - -impl ArgsSeed<'_, Def> { - pub fn name(&self) -> &str { - self.0.ty().name() - } - - pub fn params(&self) -> &ProductType { - self.0.ty().params() - } -} - -impl<'de, Def: FunctionDef> de::DeserializeSeed<'de> for ArgsSeed<'_, Def> { +impl<'de> de::DeserializeSeed<'de> for ReducerArgsDeserializeSeed<'_> { type Output = ProductValue; fn deserialize>(self, deserializer: D) -> Result { @@ -62,40 +23,24 @@ impl<'de, Def: FunctionDef> de::DeserializeSeed<'de> for ArgsSeed<'_, Def> { } } -impl<'de, Def: FunctionDef> de::ProductVisitor<'de> for ArgsSeed<'_, Def> { +impl<'de> de::ProductVisitor<'de> for ReducerArgsDeserializeSeed<'_> { type Output = ProductValue; fn product_name(&self) -> Option<&str> { - Some(self.0.ty().name()) + Some(&self.0.ty().name) } - fn product_len(&self) -> usize { - self.0.ty().params().elements.len() + self.0.ty().params.elements.len() } - fn product_kind(&self) -> de::ProductKind { de::ProductKind::ReducerArgs } fn visit_seq_product>(self, tup: A) -> Result { - de::visit_seq_product(self.0.map(|r| &*r.params().elements), &self, tup) + de::visit_seq_product(self.0.map(|r| &*r.params.elements), &self, tup) } fn visit_named_product>(self, tup: A) -> Result { - de::visit_named_product(self.0.map(|r| &*r.params().elements), &self, tup) + de::visit_named_product(self.0.map(|r| &*r.params.elements), &self, tup) } } - -pub struct ReducerArgsWithSchema<'a> { - value: &'a ProductValue, - ty: sats::WithTypespace<'a, ReducerDef>, -} -impl_serialize!([] ReducerArgsWithSchema<'_>, (self, ser) => { - use itertools::Itertools; - use ser::SerializeSeqProduct; - let mut seq = ser.serialize_seq_product(self.value.elements.len())?; - for (value, elem) in self.value.elements.iter().zip_eq(&*self.ty.ty().params.elements) { - seq.serialize_element(&self.ty.with(&elem.algebraic_type).with_value(value))?; - } - seq.end() -}); diff --git a/crates/schema/src/def/validate/v8.rs b/crates/schema/src/def/validate/v8.rs index 347e1f47e07..89a853a995e 100644 --- a/crates/schema/src/def/validate/v8.rs +++ b/crates/schema/src/def/validate/v8.rs @@ -57,8 +57,6 @@ fn upgrade_module(def: RawModuleDefV8, extra_errors: &mut Vec) tables, reducers, types, - // V8 module defs don't have procedures or column default values, - // which are all we use the `misc_exports` for at this time (pgoldman 2025-10-09). misc_exports: Default::default(), row_level_security: vec![], // v8 doesn't have row-level security } @@ -528,7 +526,7 @@ mod tests { assert_eq!(delivery_def.columns[2].ty, AlgebraicType::U64); assert_eq!(delivery_def.schedule.as_ref().unwrap().at_column, 1.into()); assert_eq!( - &delivery_def.schedule.as_ref().unwrap().function_name[..], + &delivery_def.schedule.as_ref().unwrap().reducer_name[..], "check_deliveries" ); assert_eq!(delivery_def.primary_key, Some(ColId(2))); diff --git a/crates/schema/src/def/validate/v9.rs b/crates/schema/src/def/validate/v9.rs index fa7a8b88067..6adbbf01b9a 100644 --- a/crates/schema/src/def/validate/v9.rs +++ b/crates/schema/src/def/validate/v9.rs @@ -8,7 +8,6 @@ use spacetimedb_lib::db::default_element_ordering::{product_type_has_default_ord use spacetimedb_lib::ProductType; use spacetimedb_primitives::col_list; use spacetimedb_sats::{bsatn::de::Deserializer, de::DeserializeSeed, WithTypespace}; -use std::borrow::Cow; /// Validate a `RawModuleDefV9` and convert it into a `ModuleDef`, /// or return a stream of errors if the definition is invalid. @@ -50,33 +49,7 @@ pub fn validate(def: RawModuleDefV9) -> Result { .validate_reducer_def(reducer, ReducerId(idx as u32)) .map(|reducer_def| (reducer_def.name.clone(), reducer_def)) }) - // Collect into a `Vec` first to preserve duplicate names. - // Later on, in `check_function_names_are_unique`, we'll transform this into an `IndexMap`. - .collect_all_errors::>(); - - let (procedures, non_procedure_misc_exports) = - misc_exports - .into_iter() - .partition::, _>(|misc_export| { - matches!(misc_export, RawMiscModuleExportV9::Procedure(_)) - }); - - let procedures = procedures - .into_iter() - .map(|procedure| { - let RawMiscModuleExportV9::Procedure(procedure) = procedure else { - unreachable!("Already partitioned procedures separate from other `RawMiscModuleExportV9` variants"); - }; - procedure - }) - .map(|procedure| { - validator - .validate_procedure_def(procedure) - .map(|procedure_def| (procedure_def.name.clone(), procedure_def)) - }) - // Collect into a `Vec` first to preserve duplicate names. - // Later on, in `check_function_names_are_unique`, we'll transform this into an `IndexMap`. - .collect_all_errors::>(); + .collect_all_errors(); let tables = tables .into_iter() @@ -103,18 +76,15 @@ pub fn validate(def: RawModuleDefV9) -> Result { }) .collect_all_errors::>(); - let tables_types_reducers_procedures = - (tables, types, reducers, procedures) - .combine_errors() - .and_then(|(mut tables, types, reducers, procedures)| { - let ((reducers, procedures), ()) = ( - check_function_names_are_unique(reducers, procedures), - check_non_procedure_misc_exports(non_procedure_misc_exports, &validator, &mut tables), - ) - .combine_errors()?; - check_scheduled_functions_exist(&mut tables, &reducers, &procedures)?; - Ok((tables, types, reducers, procedures)) - }); + let tables_types_reducers = (tables, types, reducers) + .combine_errors() + .and_then(|(mut tables, types, reducers)| { + let sched_exists = check_scheduled_reducers_exist(&tables, &reducers); + let default_values_work = proccess_misc_exports(misc_exports, &validator, &mut tables); + (sched_exists, default_values_work).combine_errors()?; + + Ok((tables, types, reducers)) + }); let ModuleValidator { stored_in_table_def, @@ -123,8 +93,7 @@ pub fn validate(def: RawModuleDefV9) -> Result { .. } = validator; - let (tables, types, reducers, procedures) = - (tables_types_reducers_procedures).map_err(|errors| errors.sort_deduplicate())?; + let (tables, types, reducers) = (tables_types_reducers).map_err(|errors| errors.sort_deduplicate())?; let typespace_for_generate = typespace_for_generate.finish(); @@ -138,7 +107,6 @@ pub fn validate(def: RawModuleDefV9) -> Result { refmap, row_level_security_raw, lifecycle_reducers, - procedures, }) } @@ -318,19 +286,26 @@ impl ModuleValidator<'_> { }) } - fn params_for_generate<'a>( - &mut self, - params: &'a ProductType, - make_type_location: impl Fn(usize, Option>) -> TypeLocation<'a>, - ) -> Result> { - params + /// Validate a reducer definition. + fn validate_reducer_def(&mut self, reducer_def: RawReducerDefV9, reducer_id: ReducerId) -> Result { + let RawReducerDefV9 { + name, + params, + lifecycle, + } = reducer_def; + + let params_for_generate: Result<_> = params .elements .iter() .enumerate() .map(|(position, param)| { // Note: this does not allocate, since `TypeLocation` is defined using `Cow`. // We only allocate if an error is returned. - let location = make_type_location(position, param.name().map(Into::into)); + let location = TypeLocation::ReducerArg { + reducer_name: (&*name).into(), + position, + arg_name: param.name().map(Into::into), + }; let param_name = param .name() .ok_or_else(|| { @@ -344,27 +319,10 @@ impl ModuleValidator<'_> { let ty_use = self.validate_for_type_use(&location, ¶m.algebraic_type); (param_name, ty_use).combine_errors() }) - .collect_all_errors() - } - - /// Validate a reducer definition. - fn validate_reducer_def(&mut self, reducer_def: RawReducerDefV9, reducer_id: ReducerId) -> Result { - let RawReducerDefV9 { - name, - params, - lifecycle, - } = reducer_def; - - let params_for_generate: Result<_> = - self.params_for_generate(¶ms, |position, arg_name| TypeLocation::ReducerArg { - reducer_name: (&*name).into(), - position, - arg_name, - }); + .collect_all_errors(); - // Reducers share the "function namespace" with procedures. - // Uniqueness is validated in a later pass, in `check_function_names_are_unique`. - let name = identifier(name.clone()); + // reducers don't live in the global namespace. + let name = identifier(name); let lifecycle = lifecycle .map(|lifecycle| match &mut self.lifecycle_reducers[lifecycle] { @@ -375,7 +333,9 @@ impl ModuleValidator<'_> { Some(_) => Err(ValidationError::DuplicateLifecycle { lifecycle }.into()), }) .transpose(); + let (name, params_for_generate, lifecycle) = (name, params_for_generate, lifecycle).combine_errors()?; + Ok(ReducerDef { name, params: params.clone(), @@ -387,45 +347,6 @@ impl ModuleValidator<'_> { }) } - fn validate_procedure_def(&mut self, procedure_def: RawProcedureDefV9) -> Result { - let RawProcedureDefV9 { - name, - params, - return_type, - } = procedure_def; - - let params_for_generate = self.params_for_generate(¶ms, |position, arg_name| TypeLocation::ProcedureArg { - procedure_name: Cow::Borrowed(&name), - position, - arg_name, - }); - - let return_type_for_generate = self.validate_for_type_use( - &TypeLocation::ProcedureReturn { - procedure_name: Cow::Borrowed(&name), - }, - &return_type, - ); - - // Procedures share the "function namespace" with reducers. - // Uniqueness is validated in a later pass, in `check_function_names_are_unique`. - let name = identifier(name); - - let (name, params_for_generate, return_type_for_generate) = - (name, params_for_generate, return_type_for_generate).combine_errors()?; - - Ok(ProcedureDef { - name, - params, - params_for_generate: ProductTypeDef { - elements: params_for_generate, - recursive: false, // A ProductTypeDef not stored in a Typespace cannot be recursive. - }, - return_type, - return_type_for_generate, - }) - } - fn validate_column_default_value( &self, tables: &HashMap, @@ -796,8 +717,7 @@ impl TableValidator<'_, '_> { /// Validate a schedule definition. fn validate_schedule_def(&mut self, schedule: RawScheduleDefV9, primary_key: Option) -> Result { let RawScheduleDefV9 { - // Despite the field name, a `RawScheduleDefV9` may refer to either a reducer or a function. - reducer_name: function_name, + reducer_name, scheduled_at_column, name, } = schedule; @@ -829,20 +749,15 @@ impl TableValidator<'_, '_> { }); let name = self.add_to_global_namespace(name); - let function_name = identifier(function_name); + let reducer_name = identifier(reducer_name); - let (name, (at_column, id_column), function_name) = (name, at_id, function_name).combine_errors()?; + let (name, (at_column, id_column), reducer_name) = (name, at_id, reducer_name).combine_errors()?; Ok(ScheduleDef { name, at_column, id_column, - function_name, - - // Fill this in as a placeholder now. - // It will be populated with the correct `FunctionKind` later, - // in `check_scheduled_functions_exist`. - function_kind: FunctionKind::Unknown, + reducer_name, }) } @@ -982,44 +897,32 @@ fn identifier(name: Box) -> Result { Identifier::new(name).map_err(|error| ValidationError::IdentifierError { error }.into()) } -/// Check that every [`ScheduleDef`]'s `function_name` refers to a real reducer or procedure -/// and that the function's arguments are appropriate for the table, -/// then record the scheduled function's [`FunctionKind`] in the [`ScheduleDef`]. -fn check_scheduled_functions_exist( - tables: &mut IdentifierMap, +fn check_scheduled_reducers_exist( + tables: &IdentifierMap, reducers: &IndexMap, - procedures: &IndexMap, ) -> Result<()> { - let validate_params = - |params_from_function: &ProductType, table_row_type_ref: AlgebraicTypeRef, function_name: &str| { - if params_from_function.elements.len() == 1 - && params_from_function.elements[0].algebraic_type == table_row_type_ref.into() - { - Ok(()) - } else { - Err(ValidationError::IncorrectScheduledFunctionParams { - function_name: function_name.into(), - function_kind: FunctionKind::Reducer, - expected: AlgebraicType::product([AlgebraicType::Ref(table_row_type_ref)]).into(), - actual: params_from_function.clone().into(), - }) - } - }; - tables - .values_mut() + .values() .map(|table| -> Result<()> { - if let Some(schedule) = &mut table.schedule { - if let Some(reducer) = reducers.get(&schedule.function_name) { - schedule.function_kind = FunctionKind::Reducer; - validate_params(&reducer.params, table.product_type_ref, &reducer.name).map_err(Into::into) - } else if let Some(procedure) = procedures.get(&schedule.function_name) { - schedule.function_kind = FunctionKind::Procedure; - validate_params(&procedure.params, table.product_type_ref, &procedure.name).map_err(Into::into) + if let Some(schedule) = &table.schedule { + let reducer = reducers.get(&schedule.reducer_name); + if let Some(reducer) = reducer { + if reducer.params.elements.len() == 1 + && reducer.params.elements[0].algebraic_type == table.product_type_ref.into() + { + Ok(()) + } else { + Err(ValidationError::IncorrectScheduledReducerParams { + reducer: (&*schedule.reducer_name).into(), + expected: AlgebraicType::product([AlgebraicType::Ref(table.product_type_ref)]).into(), + actual: reducer.params.clone().into(), + } + .into()) + } } else { - Err(ValidationError::MissingScheduledFunction { + Err(ValidationError::MissingScheduledReducer { schedule: schedule.name.clone(), - function: schedule.function_name.clone(), + reducer: schedule.reducer_name.clone(), } .into()) } @@ -1030,39 +933,7 @@ fn check_scheduled_functions_exist( .collect_all_errors() } -/// Check that all function (reducer and procedure) names are unique, -/// then re-organize the reducers and procedures into [`IndexMap`]s -/// for storage in the [`ModuleDef`]. -fn check_function_names_are_unique( - reducers: Vec<(Identifier, ReducerDef)>, - procedures: Vec<(Identifier, ProcedureDef)>, -) -> Result<(IndexMap, IndexMap)> { - let mut errors = vec![]; - - let mut reducers_map = IndexMap::with_capacity(reducers.len()); - - for (name, def) in reducers { - if reducers_map.contains_key(&name) { - errors.push(ValidationError::DuplicateFunctionName { name }); - } else { - reducers_map.insert(name, def); - } - } - - let mut procedures_map = IndexMap::with_capacity(procedures.len()); - - for (name, def) in procedures { - if reducers_map.contains_key(&name) || procedures_map.contains_key(&name) { - errors.push(ValidationError::DuplicateFunctionName { name }); - } else { - procedures_map.insert(name, def); - } - } - - ErrorStream::add_extra_errors(Ok((reducers_map, procedures_map)), errors) -} - -fn check_non_procedure_misc_exports( +fn proccess_misc_exports( misc_exports: Vec, validator: &ModuleValidator, tables: &mut IdentifierMap, @@ -1071,9 +942,6 @@ fn check_non_procedure_misc_exports( .into_iter() .map(|export| match export { RawMiscModuleExportV9::ColumnDefaultValue(cdv) => process_column_default_value(&cdv, validator, tables), - RawMiscModuleExportV9::Procedure(_proc) => { - unreachable!("Procedure defs should already have been sorted out of `misc_exports`") - } _ => unimplemented!("unknown misc export"), }) .collect_all_errors::<()>() @@ -1125,8 +993,7 @@ mod tests { }; use crate::def::{validate::Result, ModuleDef}; use crate::def::{ - BTreeAlgorithm, ConstraintData, ConstraintDef, DirectAlgorithm, FunctionKind, IndexDef, SequenceDef, - UniqueConstraintData, + BTreeAlgorithm, ConstraintData, ConstraintDef, DirectAlgorithm, IndexDef, SequenceDef, UniqueConstraintData, }; use crate::error::*; use crate::type_for_generate::ClientCodegenError; @@ -1343,13 +1210,9 @@ mod tests { assert_eq!(delivery_def.columns[2].ty, AlgebraicType::U64); assert_eq!(delivery_def.schedule.as_ref().unwrap().at_column, 1.into()); assert_eq!( - &delivery_def.schedule.as_ref().unwrap().function_name[..], + &delivery_def.schedule.as_ref().unwrap().reducer_name[..], "check_deliveries" ); - assert_eq!( - delivery_def.schedule.as_ref().unwrap().function_kind, - FunctionKind::Reducer - ); assert_eq!(delivery_def.primary_key, Some(ColId(2))); assert_eq!(def.typespace.get(product_type_ref), Some(&product_type)); @@ -1797,9 +1660,9 @@ mod tests { .finish(); let result: Result = builder.finish().try_into(); - expect_error_matching!(result, ValidationError::MissingScheduledFunction { schedule, function } => { + expect_error_matching!(result, ValidationError::MissingScheduledReducer { schedule, reducer } => { &schedule[..] == "Deliveries_sched" && - function == &expect_identifier("check_deliveries") + reducer == &expect_identifier("check_deliveries") }); } @@ -1825,11 +1688,10 @@ mod tests { builder.add_reducer("check_deliveries", ProductType::from([("a", AlgebraicType::U64)]), None); let result: Result = builder.finish().try_into(); - expect_error_matching!(result, ValidationError::IncorrectScheduledFunctionParams { function_name, function_kind, expected, actual } => { - &function_name[..] == "check_deliveries" && - *function_kind == FunctionKind::Reducer && - expected.0 == AlgebraicType::product([AlgebraicType::Ref(deliveries_product_type)]) && - actual.0 == ProductType::from([("a", AlgebraicType::U64)]).into() + expect_error_matching!(result, ValidationError::IncorrectScheduledReducerParams { reducer, expected, actual } => { + &reducer[..] == "check_deliveries" && + expected.0 == AlgebraicType::product([AlgebraicType::Ref(deliveries_product_type)]) && + actual.0 == ProductType::from([("a", AlgebraicType::U64)]).into() }); } @@ -1875,46 +1737,4 @@ mod tests { assert!(def.lookup::("wacky.index()").is_some()); assert!(def.lookup::("wacky.sequence()").is_some()); } - - #[test] - fn duplicate_reducer_names() { - let mut builder = RawModuleDefV9Builder::new(); - - builder.add_reducer("foo", [("i", AlgebraicType::I32)].into(), None); - builder.add_reducer("foo", [("name", AlgebraicType::String)].into(), None); - - let result: Result = builder.finish().try_into(); - - expect_error_matching!(result, ValidationError::DuplicateFunctionName { name } => { - &name[..] == "foo" - }); - } - - #[test] - fn duplicate_procedure_names() { - let mut builder = RawModuleDefV9Builder::new(); - - builder.add_procedure("foo", [("i", AlgebraicType::I32)].into(), AlgebraicType::unit()); - builder.add_procedure("foo", [("name", AlgebraicType::String)].into(), AlgebraicType::unit()); - - let result: Result = builder.finish().try_into(); - - expect_error_matching!(result, ValidationError::DuplicateFunctionName { name } => { - &name[..] == "foo" - }); - } - - #[test] - fn duplicate_procedure_and_reducer_name() { - let mut builder = RawModuleDefV9Builder::new(); - - builder.add_reducer("foo", [("i", AlgebraicType::I32)].into(), None); - builder.add_procedure("foo", [("i", AlgebraicType::I32)].into(), AlgebraicType::unit()); - - let result: Result = builder.finish().try_into(); - - expect_error_matching!(result, ValidationError::DuplicateFunctionName { name } => { - &name[..] == "foo" - }); - } } diff --git a/crates/schema/src/error.rs b/crates/schema/src/error.rs index f7d05c8febf..e7526ad095d 100644 --- a/crates/schema/src/error.rs +++ b/crates/schema/src/error.rs @@ -7,7 +7,7 @@ use spacetimedb_sats::{bsatn::DecodeError, AlgebraicType, AlgebraicTypeRef}; use std::borrow::Cow; use std::fmt; -use crate::def::{FunctionKind, ScopedTypeName}; +use crate::def::ScopedTypeName; use crate::identifier::Identifier; use crate::type_for_generate::ClientCodegenError; @@ -108,12 +108,11 @@ pub enum ValidationError { MissingPrimaryKeyUniqueConstraint { column: RawColumnName }, #[error("Table {table} should have a type definition for its product_type_element, but does not")] TableTypeNameMismatch { table: Identifier }, - #[error("Schedule {schedule} refers to a scheduled reducer or procedure {function} that does not exist")] - MissingScheduledFunction { schedule: Box, function: Identifier }, - #[error("Scheduled {function_kind} {function_name} expected to have type {expected}, but has type {actual}")] - IncorrectScheduledFunctionParams { - function_name: RawIdentifier, - function_kind: FunctionKind, + #[error("Schedule {schedule} refers to a scheduled reducer {reducer} that does not exist")] + MissingScheduledReducer { schedule: Box, reducer: Identifier }, + #[error("Scheduled reducer {reducer} expected to have type {expected}, but has type {actual}")] + IncorrectScheduledReducerParams { + reducer: RawIdentifier, expected: PrettyAlgebraicType, actual: PrettyAlgebraicType, }, @@ -131,8 +130,6 @@ pub enum ValidationError { MultipleColumnDefaultValues { table: RawIdentifier, col_id: ColId }, #[error("Table {table} not found")] TableNotFound { table: RawIdentifier }, - #[error("Name {name} is used for multiple reducers and/or procedures")] - DuplicateFunctionName { name: Identifier }, } /// A wrapper around an `AlgebraicType` that implements `fmt::Display`. @@ -176,14 +173,6 @@ pub enum TypeLocation<'a> { position: usize, arg_name: Option>, }, - /// A procedure argument. - ProcedureArg { - procedure_name: Cow<'a, str>, - position: usize, - arg_name: Option>, - }, - /// A procedure return type. - ProcedureReturn { procedure_name: Cow<'a, str> }, /// A type in the typespace. InTypespace { /// The reference to the type within the typespace. @@ -204,18 +193,6 @@ impl TypeLocation<'_> { position, arg_name: arg_name.map(|s| s.to_string().into()), }, - TypeLocation::ProcedureArg { - procedure_name, - position, - arg_name, - } => TypeLocation::ProcedureArg { - procedure_name: procedure_name.to_string().into(), - position, - arg_name: arg_name.map(|s| s.to_string().into()), - }, - Self::ProcedureReturn { procedure_name } => TypeLocation::ProcedureReturn { - procedure_name: procedure_name.to_string().into(), - }, // needed to convince rustc this is allowed. TypeLocation::InTypespace { ref_ } => TypeLocation::InTypespace { ref_ }, } @@ -236,20 +213,6 @@ impl fmt::Display for TypeLocation<'_> { } Ok(()) } - TypeLocation::ProcedureArg { - procedure_name, - position, - arg_name, - } => { - write!(f, "procedure `{procedure_name}` argument {position}")?; - if let Some(arg_name) = arg_name { - write!(f, " (`{arg_name}`)")?; - } - Ok(()) - } - TypeLocation::ProcedureReturn { procedure_name } => { - write!(f, "procedure `{procedure_name}` return value") - } TypeLocation::InTypespace { ref_ } => { write!(f, "typespace ref `{ref_}`") } diff --git a/crates/schema/src/schema.rs b/crates/schema/src/schema.rs index 149cb08eb5b..f9d104795f2 100644 --- a/crates/schema/src/schema.rs +++ b/crates/schema/src/schema.rs @@ -922,20 +922,20 @@ pub struct ScheduleSchema { /// The name of the schedule. pub schedule_name: Box, - /// The name of the reducer or procedure to call. - pub function_name: Box, + /// The name of the reducer to call. + pub reducer_name: Box, /// The column containing the `ScheduleAt` enum. pub at_column: ColId, } impl ScheduleSchema { - pub fn for_test(name: impl Into>, function: impl Into>, at: impl Into) -> Self { + pub fn for_test(name: impl Into>, reducer: impl Into>, at: impl Into) -> Self { Self { table_id: TableId::SENTINEL, schedule_id: ScheduleId::SENTINEL, schedule_name: name.into(), - function_name: function.into(), + reducer_name: reducer.into(), at_column: at.into(), } } @@ -955,7 +955,7 @@ impl Schema for ScheduleSchema { table_id: parent_id, schedule_id: id, schedule_name: (*def.name).into(), - function_name: (*def.function_name).into(), + reducer_name: (*def.reducer_name).into(), at_column: def.at_column, // Ignore def.at_column and id_column. Those are recovered at runtime. } @@ -964,9 +964,9 @@ impl Schema for ScheduleSchema { fn check_compatible(&self, _module_def: &ModuleDef, def: &Self::Def) -> Result<(), anyhow::Error> { ensure_eq!(&self.schedule_name[..], &def.name[..], "Schedule name mismatch"); ensure_eq!( - &self.function_name[..], - &def.function_name[..], - "Schedule function name mismatch" + &self.reducer_name[..], + &def.reducer_name[..], + "Schedule reducer name mismatch" ); Ok(()) } diff --git a/crates/testing/src/modules.rs b/crates/testing/src/modules.rs index 7dbcd442b06..5a883c429a4 100644 --- a/crates/testing/src/modules.rs +++ b/crates/testing/src/modules.rs @@ -19,7 +19,7 @@ use tokio::runtime::{Builder, Runtime}; use spacetimedb::client::{ClientActorId, ClientConfig, ClientConnection, DataMessage}; use spacetimedb::database_logger::DatabaseLogger; use spacetimedb::db::{Config, Storage}; -use spacetimedb::host::FunctionArgs; +use spacetimedb::host::ReducerArgs; use spacetimedb::messages::websocket::CallReducerFlags; use spacetimedb_client_api::{ControlStateReadAccess, ControlStateWriteAccess, DatabaseDef, NodeDelegate}; use spacetimedb_lib::{bsatn, sats}; @@ -55,7 +55,7 @@ pub struct ModuleHandle { } impl ModuleHandle { - async fn call_reducer(&self, reducer: &str, args: FunctionArgs) -> anyhow::Result<()> { + async fn call_reducer(&self, reducer: &str, args: ReducerArgs) -> anyhow::Result<()> { let result = self .client .call_reducer(reducer, args, 0, Instant::now(), CallReducerFlags::FullUpdate) @@ -72,12 +72,12 @@ impl ModuleHandle { pub async fn call_reducer_json(&self, reducer: &str, args: &sats::ProductValue) -> anyhow::Result<()> { let args = serde_json::to_string(&args).unwrap(); - self.call_reducer(reducer, FunctionArgs::Json(args.into())).await + self.call_reducer(reducer, ReducerArgs::Json(args.into())).await } pub async fn call_reducer_binary(&self, reducer: &str, args: &sats::ProductValue) -> anyhow::Result<()> { let args = bsatn::to_vec(&args).unwrap(); - self.call_reducer(reducer, FunctionArgs::Bsatn(args.into())).await + self.call_reducer(reducer, ReducerArgs::Bsatn(args.into())).await } pub async fn send(&self, message: impl Into) -> anyhow::Result<()> { From f18626411d23d6efb2e16b5f1b8c6c7dabef7636 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Mon, 3 Nov 2025 07:59:36 -0800 Subject: [PATCH 16/16] [release/v1.7.0]: Update DLLs to 1.7.0 --- .../{1.5.0.meta => 1.7.0.meta} | 0 .../{1.5.0 => 1.7.0}/analyzers.meta | 0 .../{1.5.0 => 1.7.0}/analyzers/dotnet.meta | 0 .../{1.5.0 => 1.7.0}/analyzers/dotnet/cs.meta | 0 .../dotnet/cs/SpacetimeDB.BSATN.Codegen.dll | Bin 75264 -> 75264 bytes .../cs/SpacetimeDB.BSATN.Codegen.dll.meta | 0 .../{1.5.0 => 1.7.0}/lib.meta | 0 .../{1.5.0 => 1.7.0}/lib/netstandard2.1.meta | 0 .../SpacetimeDB.BSATN.Runtime.dll | Bin 68608 -> 68608 bytes .../SpacetimeDB.BSATN.Runtime.dll.meta | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename sdks/csharp/packages/spacetimedb.bsatn.runtime/{1.5.0.meta => 1.7.0.meta} (100%) rename sdks/csharp/packages/spacetimedb.bsatn.runtime/{1.5.0 => 1.7.0}/analyzers.meta (100%) rename sdks/csharp/packages/spacetimedb.bsatn.runtime/{1.5.0 => 1.7.0}/analyzers/dotnet.meta (100%) rename sdks/csharp/packages/spacetimedb.bsatn.runtime/{1.5.0 => 1.7.0}/analyzers/dotnet/cs.meta (100%) rename sdks/csharp/packages/spacetimedb.bsatn.runtime/{1.5.0 => 1.7.0}/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll (98%) rename sdks/csharp/packages/spacetimedb.bsatn.runtime/{1.5.0 => 1.7.0}/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll.meta (100%) rename sdks/csharp/packages/spacetimedb.bsatn.runtime/{1.5.0 => 1.7.0}/lib.meta (100%) rename sdks/csharp/packages/spacetimedb.bsatn.runtime/{1.5.0 => 1.7.0}/lib/netstandard2.1.meta (100%) rename sdks/csharp/packages/spacetimedb.bsatn.runtime/{1.5.0 => 1.7.0}/lib/netstandard2.1/SpacetimeDB.BSATN.Runtime.dll (97%) rename sdks/csharp/packages/spacetimedb.bsatn.runtime/{1.5.0 => 1.7.0}/lib/netstandard2.1/SpacetimeDB.BSATN.Runtime.dll.meta (100%) diff --git a/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0.meta b/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0.meta similarity index 100% rename from sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0.meta rename to sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0.meta diff --git a/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/analyzers.meta b/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/analyzers.meta similarity index 100% rename from sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/analyzers.meta rename to sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/analyzers.meta diff --git a/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/analyzers/dotnet.meta b/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/analyzers/dotnet.meta similarity index 100% rename from sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/analyzers/dotnet.meta rename to sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/analyzers/dotnet.meta diff --git a/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/analyzers/dotnet/cs.meta b/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/analyzers/dotnet/cs.meta similarity index 100% rename from sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/analyzers/dotnet/cs.meta rename to sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/analyzers/dotnet/cs.meta diff --git a/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll b/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll similarity index 98% rename from sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll rename to sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll index 1db1c1f2115bc79e9596ada9efe982f13af94caa..d74a8cb72312ef99ed89027cea358164e3dffe3b 100755 GIT binary patch delta 320 zcmZoT!_shuWkLtbmuQcP8+#JwGqP`fJ3l{KzSIPfNf~85Q=XJ$ZVJG~-eQsOV~#C{TUr z+M=d4Z`$0_7p}SaZ@Z!lV>%NjI|CyF2(WLTD94!3#Av>qQIRp4G2EO%kHLUJ8;Fe< zk{Qex%o$P`41sJzh9sa^3J_Z`m;gmB7*ZJ$8PXVx8BD?QX<$)Hpr|=e+!(0C45-?e h!4e2f7!0OwR$?^dgcuIAV0)tsqX^@6Mis{Yi~!^i`9ratZ58%k>#PW_!8Bh9!}0V>)969uVH zJh0NE=|tj^+qEU^+ZAOP)0sF~85kKrfOY#sImUb@M$_$#ij2{W;ie3F3|52AR8oRz+ePqTL7UEgE^3I4irxX ciA~?E#AwI~F&t>Y_C^^-5ytI|DvbXb0mboK&;S4c diff --git a/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll.meta b/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll.meta similarity index 100% rename from sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll.meta rename to sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll.meta diff --git a/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/lib.meta b/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/lib.meta similarity index 100% rename from sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/lib.meta rename to sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/lib.meta diff --git a/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/lib/netstandard2.1.meta b/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/lib/netstandard2.1.meta similarity index 100% rename from sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/lib/netstandard2.1.meta rename to sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/lib/netstandard2.1.meta diff --git a/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/lib/netstandard2.1/SpacetimeDB.BSATN.Runtime.dll b/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/lib/netstandard2.1/SpacetimeDB.BSATN.Runtime.dll similarity index 97% rename from sdks/csharp/packages/spacetimedb.bsatn.runtime/1.5.0/lib/netstandard2.1/SpacetimeDB.BSATN.Runtime.dll rename to sdks/csharp/packages/spacetimedb.bsatn.runtime/1.7.0/lib/netstandard2.1/SpacetimeDB.BSATN.Runtime.dll index b66c06e7b8ae68617901c3ba38455e42b1925501..6fc14119dbfced7e774aa13c2b9c259ebf407e49 100755 GIT binary patch delta 305 zcmZpe!O}2;WkLtbvevkX8+*$3GqP`HKCo9^prrMS-H~0Z-8N?|6)^hx>)hrG&(Dim zn(GH#_021dzd<|&3ohDin~1{Nmf7O9D8#-@fwX(r~Dspg5sMrJ9- zmX;<4n{WKR!_2apOZ@Wm9gK_t0*fn{pIBTw<+=IUW&gNky`QH`FflGwfQnkeM1ksq znTn;}Y~|W=(N0sRX!~6z#?_3R>JK7W!eB7HnuF00Xvta*#$aXuYyVj+ delta 305 zcmZpe!O}2;WkLrFb6ow(jXh=i8Cf?oAK0rdAnwsL$8`H8zln+$WVq%sE#7?L`FT-G zQ#}Jc0|o|TMg~1YJs?NhGBw#CDaFjv*wW0v#4^z`$<#D4*)loB)H2DyD8<6U$UMp1 zG}S0&^NqiEm{~S)F|U}ugOM>n;JDV-|5I<;cy;c6)cr%qoOQYc6XQ|^sOYJaP*ITj zNM?hpzXP*2$@A6j*nXFZaWx|+D+40~2(WIS$iisO#Av$xBpYKiW4I}U9)kgcHiIQY zDnl}mmBf(3U