From 1be047727bd3656ca6d8f82c030139b1d25f712a Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 1 Jun 2026 17:35:08 -0500 Subject: [PATCH 01/24] Splat some Python database pool access code from @reivilibre From `rei/exp_db_rust_via_shim`, https://github.com/element-hq/synapse/commit/e19dfa15a4fdf29f6727bc15f353eab69df05aff --- rust/src/db.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 rust/src/db.rs diff --git a/rust/src/db.rs b/rust/src/db.rs new file mode 100644 index 00000000000..973a34fb0a2 --- /dev/null +++ b/rust/src/db.rs @@ -0,0 +1,83 @@ +/* + * This file is licensed under the Affero General Public License (AGPL) version 3. + * + * Copyright (C) 2026 Element Creations Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * See the GNU Affero General Public License for more details: + * . + * + */ + +use pyo3::prelude::*; + +/// Wrapper for a `LoggingTransaction` from the Python side of Synapse. +pub struct LoggingTransactionWrapper<'py> { + /// The underlying LoggingTransaction + raw: &'py PyAny, + + database_engine: DatabaseEngine, +} + +impl<'source> FromPyObject<'source> for LoggingTransactionWrapper<'source> { + fn extract(ob: &'source PyAny) -> PyResult { + let database_engine = match ob + .getattr("database_engine")? + .get_type() + .name() + .expect("DB engine should have a type name") + { + "PostgresEngine" => DatabaseEngine::Postgres, + "Sqlite3Engine" => DatabaseEngine::Sqlite, + other => panic!("Unknown engine {other:?}"), + }; + Ok(Self { + raw: ob, + database_engine, + }) + } +} + +impl<'py> LoggingTransactionWrapper<'py> { + pub fn execute>(&mut self, sql: &str, args: T) -> PyResult<()> { + let execute_fn = self.raw.getattr(intern!(self.raw.py(), "execute"))?; + execute_fn.call1((sql, args))?; + Ok(()) + } + + pub fn execute_values, R: FromPyObject<'py> + ValidDatabaseReturnType>( + &mut self, + sql: &str, + args: T, + ) -> PyResult> { + let execute_fn = self.raw.getattr(intern!(self.raw.py(), "execute_values"))?; + Ok(execute_fn.call1((sql, args))?.extract()?) + } + + pub fn fetchall + ValidDatabaseReturnType>( + &mut self, + ) -> anyhow::Result> { + let fetch_fn = self.raw.getattr(intern!(self.raw.py(), "fetchall"))?; + Ok(fetch_fn.call0()?.extract()?) + } +} + +#[derive(Copy, Clone, Debug)] +pub enum DatabaseEngine { + Sqlite, + Postgres, +} + +impl DatabaseEngine { + //[inline] + pub fn supports_using_any_list(&self) -> bool { + match self { + DatabaseEngine::Sqlite => false, + DatabaseEngine::Postgres => true, + } + } +} From d65dbc57dee2c57e9911e726d56f51c9d939a1f5 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 1 Jun 2026 17:36:31 -0500 Subject: [PATCH 02/24] Iterate --- rust/src/db.rs | 52 +++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/rust/src/db.rs b/rust/src/db.rs index 973a34fb0a2..84c041e5ce1 100644 --- a/rust/src/db.rs +++ b/rust/src/db.rs @@ -15,17 +15,34 @@ use pyo3::prelude::*; +#[derive(Copy, Clone, Debug)] +pub enum DatabaseEngine { + Sqlite, + Postgres, +} + +impl DatabaseEngine { + //[inline] + pub fn supports_using_any_list(&self) -> bool { + match self { + DatabaseEngine::Sqlite => false, + DatabaseEngine::Postgres => true, + } + } +} + /// Wrapper for a `LoggingTransaction` from the Python side of Synapse. pub struct LoggingTransactionWrapper<'py> { - /// The underlying LoggingTransaction + /// The underlying `LoggingTransaction` raw: &'py PyAny, database_engine: DatabaseEngine, } impl<'source> FromPyObject<'source> for LoggingTransactionWrapper<'source> { - fn extract(ob: &'source PyAny) -> PyResult { - let database_engine = match ob + /// From Python `LoggingTransaction` + fn extract(logging_transaction_python_object: &'source PyAny) -> PyResult { + let database_engine = match logging_transaction_python_object .getattr("database_engine")? .get_type() .name() @@ -36,7 +53,7 @@ impl<'source> FromPyObject<'source> for LoggingTransactionWrapper<'source> { other => panic!("Unknown engine {other:?}"), }; Ok(Self { - raw: ob, + raw: logging_transaction_python_object, database_engine, }) } @@ -54,8 +71,15 @@ impl<'py> LoggingTransactionWrapper<'py> { sql: &str, args: T, ) -> PyResult> { - let execute_fn = self.raw.getattr(intern!(self.raw.py(), "execute_values"))?; - Ok(execute_fn.call1((sql, args))?.extract()?) + match self.database_engine { + DatabaseEngine::Postgres => { + let execute_fn = self.raw.getattr(intern!(self.raw.py(), "execute_values"))?; + Ok(execute_fn.call1((sql, args))?.extract()?) + } + DatabaseEngine::Sqlite => { + unimplemented!("execute_values is not supported when using SQLite. This is a Synapse programming error"); + } + } } pub fn fetchall + ValidDatabaseReturnType>( @@ -65,19 +89,3 @@ impl<'py> LoggingTransactionWrapper<'py> { Ok(fetch_fn.call0()?.extract()?) } } - -#[derive(Copy, Clone, Debug)] -pub enum DatabaseEngine { - Sqlite, - Postgres, -} - -impl DatabaseEngine { - //[inline] - pub fn supports_using_any_list(&self) -> bool { - match self { - DatabaseEngine::Sqlite => false, - DatabaseEngine::Postgres => true, - } - } -} From 2a31e7dd954448a8ba801af0b23f22f70ffc1b19 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 1 Jun 2026 18:08:33 -0500 Subject: [PATCH 03/24] Iterate PyO3 --- rust/src/db.rs | 46 ++++++++++++++-------------------------------- rust/src/lib.rs | 1 + 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/rust/src/db.rs b/rust/src/db.rs index 84c041e5ce1..5f45c05c698 100644 --- a/rust/src/db.rs +++ b/rust/src/db.rs @@ -13,7 +13,7 @@ * */ -use pyo3::prelude::*; +use pyo3::{ffi::PyObject, intern, prelude::*}; #[derive(Copy, Clone, Debug)] pub enum DatabaseEngine { @@ -34,58 +34,40 @@ impl DatabaseEngine { /// Wrapper for a `LoggingTransaction` from the Python side of Synapse. pub struct LoggingTransactionWrapper<'py> { /// The underlying `LoggingTransaction` - raw: &'py PyAny, + raw: Bound<'py, PyObject>, database_engine: DatabaseEngine, } -impl<'source> FromPyObject<'source> for LoggingTransactionWrapper<'source> { +impl<'py> FromPyObject<'_, 'py> for LoggingTransactionWrapper<'py> { + type Error = PyErr; + /// From Python `LoggingTransaction` - fn extract(logging_transaction_python_object: &'source PyAny) -> PyResult { + fn extract(logging_transaction_python_object: Borrowed<'_, 'py, PyAny>) -> PyResult { let database_engine = match logging_transaction_python_object - .getattr("database_engine")? + .getattr("database_engine") + .expect("Expected the Python object you passed to be `LoggingTransaction` which should have `database_engine` attr") .get_type() .name() - .expect("DB engine should have a type name") + .expect("Expected `LoggingTransaction.database_engine` to have a type name") + .to_str() + .expect("Expected to be able to convert the `LoggingTransaction.database_engine` type to a string") { "PostgresEngine" => DatabaseEngine::Postgres, "Sqlite3Engine" => DatabaseEngine::Sqlite, - other => panic!("Unknown engine {other:?}"), + other => unimplemented!("Unknown database engine {other:?}. This is a Synapse programming error."), }; Ok(Self { - raw: logging_transaction_python_object, + raw: logging_transaction_python_object.cast()?.to_owned(), database_engine, }) } } impl<'py> LoggingTransactionWrapper<'py> { - pub fn execute>(&mut self, sql: &str, args: T) -> PyResult<()> { + pub fn execute(&mut self, sql: &str, args: &'py Bound<'py, PyAny>) -> PyResult<()> { let execute_fn = self.raw.getattr(intern!(self.raw.py(), "execute"))?; execute_fn.call1((sql, args))?; Ok(()) } - - pub fn execute_values, R: FromPyObject<'py> + ValidDatabaseReturnType>( - &mut self, - sql: &str, - args: T, - ) -> PyResult> { - match self.database_engine { - DatabaseEngine::Postgres => { - let execute_fn = self.raw.getattr(intern!(self.raw.py(), "execute_values"))?; - Ok(execute_fn.call1((sql, args))?.extract()?) - } - DatabaseEngine::Sqlite => { - unimplemented!("execute_values is not supported when using SQLite. This is a Synapse programming error"); - } - } - } - - pub fn fetchall + ValidDatabaseReturnType>( - &mut self, - ) -> anyhow::Result> { - let fetch_fn = self.raw.getattr(intern!(self.raw.py(), "fetchall"))?; - Ok(fetch_fn.call0()?.extract()?) - } } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 8ed4e24b813..a5ce7b5a4eb 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -6,6 +6,7 @@ use pyo3_log::ResetHandle; pub mod acl; pub mod canonical_json; +pub mod db; pub mod duration; pub mod errors; pub mod events; From 3107464d7f5e00d174eeaba2910a8257813d408e Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 1 Jun 2026 18:13:01 -0500 Subject: [PATCH 04/24] Iterate PyO3 --- rust/src/db.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/src/db.rs b/rust/src/db.rs index 5f45c05c698..6fcbba85d27 100644 --- a/rust/src/db.rs +++ b/rust/src/db.rs @@ -13,7 +13,7 @@ * */ -use pyo3::{ffi::PyObject, intern, prelude::*}; +use pyo3::{intern, prelude::*}; #[derive(Copy, Clone, Debug)] pub enum DatabaseEngine { @@ -34,7 +34,7 @@ impl DatabaseEngine { /// Wrapper for a `LoggingTransaction` from the Python side of Synapse. pub struct LoggingTransactionWrapper<'py> { /// The underlying `LoggingTransaction` - raw: Bound<'py, PyObject>, + raw: Bound<'py, PyAny>, database_engine: DatabaseEngine, } @@ -51,7 +51,7 @@ impl<'py> FromPyObject<'_, 'py> for LoggingTransactionWrapper<'py> { .name() .expect("Expected `LoggingTransaction.database_engine` to have a type name") .to_str() - .expect("Expected to be able to convert the `LoggingTransaction.database_engine` type to a string") + .expect("Expected to be able to convert the `LoggingTransaction.database_engine` type name to a string") { "PostgresEngine" => DatabaseEngine::Postgres, "Sqlite3Engine" => DatabaseEngine::Sqlite, From 99163a259f9e55f1c85d94cf53d0a7add8320ad3 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 1 Jun 2026 18:53:52 -0500 Subject: [PATCH 05/24] Some re-org --- rust/src/db/db.rs | 46 ++++++++++++++++++++++++ rust/src/db/mod.rs | 18 ++++++++++ rust/src/{db.rs => db/python_db_pool.rs} | 16 +++++++++ 3 files changed, 80 insertions(+) create mode 100644 rust/src/db/db.rs create mode 100644 rust/src/db/mod.rs rename rust/src/{db.rs => db/python_db_pool.rs} (82%) diff --git a/rust/src/db/db.rs b/rust/src/db/db.rs new file mode 100644 index 00000000000..55511dcccc1 --- /dev/null +++ b/rust/src/db/db.rs @@ -0,0 +1,46 @@ +/* + * This file is licensed under the Affero General Public License (AGPL) version 3. + * + * Copyright (C) 2026 Element Creations Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * See the GNU Affero General Public License for more details: + * . + * + */ + +use crate::db::python_db_pool::DatabasePool as PythonDatabasePool; + +/// A `tokio-postgres` looking thing that we can use on the Rust side to interact with +/// the database +pub trait Database { + pub fn query(&self) -> None { + todo!("TODO"); + } +} + +/// Database access backed by the database pool we already have in the Python side of Synapse +struct SynapsePythonDatabase { + db_pool: PythonDatabasePool, +} + +impl Database for SynapsePythonDatabase { + pub fn query(&self) -> None { + todo!("TODO"); + } +} + +/// Native Rust database access backed by `tokio-postgres` (for use in synapse-rust-apps) +struct TokioPostgresDatabase { + // TODO +} + +impl Database for TokioPostgresDatabase { + pub fn query(&self) -> None { + todo!("TODO"); + } +} diff --git a/rust/src/db/mod.rs b/rust/src/db/mod.rs new file mode 100644 index 00000000000..70319bc960e --- /dev/null +++ b/rust/src/db/mod.rs @@ -0,0 +1,18 @@ +/* + * This file is licensed under the Affero General Public License (AGPL) version 3. + * + * Copyright (C) 2026 Element Creations Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * See the GNU Affero General Public License for more details: + * . + * + */ + +pub mod python_db_pool; + +pub mod db; diff --git a/rust/src/db.rs b/rust/src/db/python_db_pool.rs similarity index 82% rename from rust/src/db.rs rename to rust/src/db/python_db_pool.rs index 6fcbba85d27..5d14d7ea2ab 100644 --- a/rust/src/db.rs +++ b/rust/src/db/python_db_pool.rs @@ -31,11 +31,27 @@ impl DatabaseEngine { } } +/// Wrapper for a `DatabasePool` from the Python side of Synapse. +pub struct DatabasePool<'py> { + /// The underlying `DatabasePool` + raw: Bound<'py, PyAny>, +} + +impl<'py> DatabasePool<'py> { + pub fn get_transaction(&mut self, description: &str) -> LoggingTransactionWrapper { + todo!("TODO"); + // let execute_fn = self.raw.getattr(intern!(self.raw.py(), "runInteraction"))?; + // execute_fn.call1((sql, args))?; + // Ok(()) + } +} + /// Wrapper for a `LoggingTransaction` from the Python side of Synapse. pub struct LoggingTransactionWrapper<'py> { /// The underlying `LoggingTransaction` raw: Bound<'py, PyAny>, + /// Dissambiguate which underyling database engine we're working with database_engine: DatabaseEngine, } From 9f99d7492375fdf4fdd66f1e631d5c8e9b2fca44 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 1 Jun 2026 19:09:23 -0500 Subject: [PATCH 06/24] `Transaction` --- rust/src/db/db.rs | 56 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/rust/src/db/db.rs b/rust/src/db/db.rs index 55511dcccc1..8af7925c115 100644 --- a/rust/src/db/db.rs +++ b/rust/src/db/db.rs @@ -15,32 +15,58 @@ use crate::db::python_db_pool::DatabasePool as PythonDatabasePool; -/// A `tokio-postgres` looking thing that we can use on the Rust side to interact with -/// the database -pub trait Database { - pub fn query(&self) -> None { +/// A [`tokio_postgres::Transaction`] looking thing that we can use on the Rust side to +/// interact with the database +pub trait Transaction { + pub fn query(&self, sql: &str, args: &[&str]) -> None { todo!("TODO"); } } /// Database access backed by the database pool we already have in the Python side of Synapse -struct SynapsePythonDatabase { +struct SynapsePythonTransaction { db_pool: PythonDatabasePool, } -impl Database for SynapsePythonDatabase { - pub fn query(&self) -> None { +impl Transaction for SynapsePythonTransaction { + fn query(&self, sql: &str, args: &[&str]) -> None { todo!("TODO"); } } /// Native Rust database access backed by `tokio-postgres` (for use in synapse-rust-apps) -struct TokioPostgresDatabase { - // TODO -} +// struct TokioPostgresTransaction { +// db_pool: bb8::Pool>, +// } -impl Database for TokioPostgresDatabase { - pub fn query(&self) -> None { - todo!("TODO"); - } -} +// impl Transaction for TokioPostgresTransaction { +// fn query(&self, sql: &str, args: &[&str]) -> None { +// todo!("TODO"); + +// // TODO: Set isolation level + +// let mut conn = self +// .db_pool +// .get() +// .instrument(tracing::info_span!("acquire database connection")) +// .await +// .map_err(|e| { +// sentry::capture_error(&e); +// tracing::error!( +// error = e.to_string(), +// "Failed to acquire database connection" +// ); +// anyhow::anyhow!("Failed to acquire database connection: {e}") +// })?; + +// let txn = conn +// .transaction() +// .instrument(tracing::info_span!("start transaction")) +// .await +// .context("Failed to start transaction")?; + +// let rows = txn.query(sql, args).await?; + +// rows +// } +// } From 9820945f3ea55c1bc0edc1b3faf8c89b00c67196 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 3 Jun 2026 17:59:22 -0500 Subject: [PATCH 07/24] WIP: Add some db usage (`/versions` endpoint) --- rust/src/db/python_db_pool.rs | 1 + rust/src/handlers/versions.rs | 162 ++++++++++++++++++++++++++++++++ synapse/rest/client/versions.py | 134 +------------------------- 3 files changed, 168 insertions(+), 129 deletions(-) create mode 100644 rust/src/handlers/versions.rs diff --git a/rust/src/db/python_db_pool.rs b/rust/src/db/python_db_pool.rs index 5d14d7ea2ab..17010a8b47e 100644 --- a/rust/src/db/python_db_pool.rs +++ b/rust/src/db/python_db_pool.rs @@ -15,6 +15,7 @@ use pyo3::{intern, prelude::*}; +/// The database engines we support in the Python side of Synapse #[derive(Copy, Clone, Debug)] pub enum DatabaseEngine { Sqlite, diff --git a/rust/src/handlers/versions.rs b/rust/src/handlers/versions.rs new file mode 100644 index 00000000000..6f1d4ffe6a5 --- /dev/null +++ b/rust/src/handlers/versions.rs @@ -0,0 +1,162 @@ + +/* + * This file is licensed under the Affero General Public License (AGPL) version 3. + * + * Copyright (C) 2026 Element Creations Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * See the GNU Affero General Public License for more details: + * . + * + */ + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct VersionsResponse { + versions: Vec, + /// as per MSC1497 + unstable_features: std::collections::BTreeMap, +} + +async fn get_versions( + user_id: Option<&str>, + config: +) -> VersionsResponse { + match user_id { + Some(user_id) => { + msc3881_enabled = store.is_feature_enabled( + user_id, ExperimentalFeature.MSC3881 + ).await; + msc3575_enabled = store.is_feature_enabled( + user_id, ExperimentalFeature.MSC3575 + ).await; + }, + None => { + msc3881_enabled = config.experimental.msc3881_enabled; + msc3575_enabled = config.experimental.msc3575_enabled; + } + } + + // TODO: Calculate these once since they shouldn't change after start-up. + e2ee_forced_public = ( + RoomCreationPreset.PUBLIC_CHAT + in config.room.encryption_enabled_by_default_for_room_presets + ); + e2ee_forced_private = ( + RoomCreationPreset.PRIVATE_CHAT + in config.room.encryption_enabled_by_default_for_room_presets + ); + e2ee_forced_trusted_private = ( + RoomCreationPreset.TRUSTED_PRIVATE_CHAT + in config.room.encryption_enabled_by_default_for_room_presets + ); + + return VersionsResponse { + versions: Vec::from([ + // XXX: at some point we need to decide whether we need to include + // the previous version numbers, given we've defined r0.3.0 to be + // backwards compatible with r0.2.0. But need to check how + // conscientious we've been in compatibility, and decide whether the + // middle number is the major revision when at 0.X.Y (as opposed to + // X.Y.Z). And we need to decide whether it's fair to make clients + // parse the version string to figure out what's going on. + "r0.0.1", + "r0.1.0", + "r0.2.0", + "r0.3.0", + "r0.4.0", + "r0.5.0", + "r0.6.0", + "r0.6.1", + "v1.1", + "v1.2", + "v1.3", + "v1.4", + "v1.5", + "v1.6", + "v1.7", + "v1.8", + "v1.9", + "v1.10", + "v1.11", + "v1.12", + ]), + unstable_features: BTreeMap::from([ + // Implements support for label-based filtering as described in + // MSC2326. + ("org.matrix.label_based_filtering": true), + // Implements support for cross signing as described in MSC1756 + ("org.matrix.e2e_cross_signing": true), + // Implements additional endpoints as described in MSC2432 + ("org.matrix.msc2432": true), + // Implements additional endpoints as described in MSC2666 + ("uk.half-shot.msc2666.query_mutual_rooms.stable": true), + // Whether new rooms will be set to encrypted or not (based on presets). + ("io.element.e2ee_forced.public": e2ee_forced_public), + ("io.element.e2ee_forced.private": e2ee_forced_private), + ("io.element.e2ee_forced.trusted_private": e2ee_forced_trusted_private), + // Supports the busy presence state described in MSC3026. + ("org.matrix.msc3026.busy_presence": config.experimental.msc3026_enabled), + // Supports receiving private read receipts as per MSC2285 + ("org.matrix.msc2285.stable": true), // TODO: Remove when MSC2285 becomes a part of the spec + // Supports filtering of /publicRooms by room type as per MSC3827 + ("org.matrix.msc3827.stable": true), + // Adds support for thread relations, per MSC3440. + ("org.matrix.msc3440.stable": true), // TODO: remove when "v1.3" is added above + // Support for thread read receipts & notification counts. + ("org.matrix.msc3771": true), + ("org.matrix.msc3773": config.experimental.msc3773_enabled), + // Allows moderators to fetch redacted event content as described in MSC2815 + ("fi.mau.msc2815": config.experimental.msc2815_enabled), + // Adds a ping endpoint for appservices to check HS->AS connection + ("fi.mau.msc2659.stable": true), // TODO: remove when "v1.7" is added above + // TODO: this is no longer needed once unstable MSC3882 does not need to be supported: + ("org.matrix.msc3882": config.auth.login_via_existing_enabled), + // Adds support for remotely enabling/disabling pushers, as per MSC3881 + ("org.matrix.msc3881": msc3881_enabled), + // Adds support for filtering /messages by event relation. + ("org.matrix.msc3874": config.experimental.msc3874_enabled), + // Adds support for relation-based redactions as per MSC3912. + ("org.matrix.msc3912": config.experimental.msc3912_enabled), + // Whether recursively provide relations is supported. + // TODO This is no longer needed once unstable MSC3981 does not need to be supported. + ("org.matrix.msc3981": true), + // Adds support for deleting account data. + ("org.matrix.msc3391": config.experimental.msc3391_enabled), + // Allows clients to inhibit profile update propagation. + ("org.matrix.msc4069": config.experimental.msc4069_profile_inhibit_propagation), + // Allows clients to handle push for encrypted events. + ("org.matrix.msc4028": config.experimental.msc4028_push_encrypted_events), + // MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code - 2024 version + ("org.matrix.msc4108": ( + config.experimental.msc4108_enabled + or ( + config.experimental.msc4108_delegation_endpoint + is not None + ) + )), + // MSC4140: Delayed events + ("org.matrix.msc4140": bool(config.server.max_event_delay_ms)), + // Simplified sliding sync + ("org.matrix.simplified_msc3575": msc3575_enabled), + // Arbitrary key-value profile fields. + ("uk.tcpip.msc4133": config.experimental.msc4133_enabled), + ("uk.tcpip.msc4133.stable": true), + // MSC4155: Invite filtering + ("org.matrix.msc4155": config.experimental.msc4155_enabled), + // MSC4306: Support for thread subscriptions + ("org.matrix.msc4306": config.experimental.msc4306_enabled), + // MSC4169: Backwards-compatible redaction sending using `/send` + ("com.beeper.msc4169": config.experimental.msc4169_enabled), + // MSC4354: Sticky events + ("org.matrix.msc4354": config.experimental.msc4354_enabled), + // MSC4380: Invite blocking + ("org.matrix.msc4380.stable": true), + // MSC4445: Sync timeline order + ("org.matrix.msc4445.initial_sync_timeline_topological_ordering": true), + ]), + } +} diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index bb1711f2cf2..a9925e5cf4f 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -31,6 +31,7 @@ from synapse.http.site import SynapseRequest from synapse.rest.admin.experimental_features import ExperimentalFeature from synapse.types import JsonDict +from synapse.synapse_rust.handlers.versions import get_versions if TYPE_CHECKING: from synapse.server import HomeServer @@ -48,24 +49,9 @@ def __init__(self, hs: "HomeServer"): self.auth = hs.get_auth() self.store = hs.get_datastores().main - # Calculate these once since they shouldn't change after start-up. - self.e2ee_forced_public = ( - RoomCreationPreset.PUBLIC_CHAT - in self.config.room.encryption_enabled_by_default_for_room_presets - ) - self.e2ee_forced_private = ( - RoomCreationPreset.PRIVATE_CHAT - in self.config.room.encryption_enabled_by_default_for_room_presets - ) - self.e2ee_forced_trusted_private = ( - RoomCreationPreset.TRUSTED_PRIVATE_CHAT - in self.config.room.encryption_enabled_by_default_for_room_presets - ) async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]: - msc3881_enabled = self.config.experimental.msc3881_enabled - msc3575_enabled = self.config.experimental.msc3575_enabled - + user_id = None if self.auth.has_access_token(request): requester = await self.auth.get_user_by_req( request, @@ -74,13 +60,6 @@ async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]: allow_expired=True, ) user_id = requester.user.to_string() - - msc3881_enabled = await self.store.is_feature_enabled( - user_id, ExperimentalFeature.MSC3881 - ) - msc3575_enabled = await self.store.is_feature_enabled( - user_id, ExperimentalFeature.MSC3575 - ) else: # Allow caching of unauthenticated responses, as they only depend # on server configuration which rarely changes. @@ -102,114 +81,11 @@ async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]: # authenticated responses are not served from cache. request.setHeader(b"Vary", b"Authorization") + versions_response_body = await get_versions(user_id) + return ( 200, - { - "versions": [ - # XXX: at some point we need to decide whether we need to include - # the previous version numbers, given we've defined r0.3.0 to be - # backwards compatible with r0.2.0. But need to check how - # conscientious we've been in compatibility, and decide whether the - # middle number is the major revision when at 0.X.Y (as opposed to - # X.Y.Z). And we need to decide whether it's fair to make clients - # parse the version string to figure out what's going on. - "r0.0.1", - "r0.1.0", - "r0.2.0", - "r0.3.0", - "r0.4.0", - "r0.5.0", - "r0.6.0", - "r0.6.1", - "v1.1", - "v1.2", - "v1.3", - "v1.4", - "v1.5", - "v1.6", - "v1.7", - "v1.8", - "v1.9", - "v1.10", - "v1.11", - "v1.12", - ], - # as per MSC1497: - "unstable_features": { - # Implements support for label-based filtering as described in - # MSC2326. - "org.matrix.label_based_filtering": True, - # Implements support for cross signing as described in MSC1756 - "org.matrix.e2e_cross_signing": True, - # Implements additional endpoints as described in MSC2432 - "org.matrix.msc2432": True, - # Implements additional endpoints as described in MSC2666 - "uk.half-shot.msc2666.query_mutual_rooms.stable": True, - # Whether new rooms will be set to encrypted or not (based on presets). - "io.element.e2ee_forced.public": self.e2ee_forced_public, - "io.element.e2ee_forced.private": self.e2ee_forced_private, - "io.element.e2ee_forced.trusted_private": self.e2ee_forced_trusted_private, - # Supports the busy presence state described in MSC3026. - "org.matrix.msc3026.busy_presence": self.config.experimental.msc3026_enabled, - # Supports receiving private read receipts as per MSC2285 - "org.matrix.msc2285.stable": True, # TODO: Remove when MSC2285 becomes a part of the spec - # Supports filtering of /publicRooms by room type as per MSC3827 - "org.matrix.msc3827.stable": True, - # Adds support for thread relations, per MSC3440. - "org.matrix.msc3440.stable": True, # TODO: remove when "v1.3" is added above - # Support for thread read receipts & notification counts. - "org.matrix.msc3771": True, - "org.matrix.msc3773": self.config.experimental.msc3773_enabled, - # Allows moderators to fetch redacted event content as described in MSC2815 - "fi.mau.msc2815": self.config.experimental.msc2815_enabled, - # Adds a ping endpoint for appservices to check HS->AS connection - "fi.mau.msc2659.stable": True, # TODO: remove when "v1.7" is added above - # TODO: this is no longer needed once unstable MSC3882 does not need to be supported: - "org.matrix.msc3882": self.config.auth.login_via_existing_enabled, - # Adds support for remotely enabling/disabling pushers, as per MSC3881 - "org.matrix.msc3881": msc3881_enabled, - # Adds support for filtering /messages by event relation. - "org.matrix.msc3874": self.config.experimental.msc3874_enabled, - # Adds support for relation-based redactions as per MSC3912. - "org.matrix.msc3912": self.config.experimental.msc3912_enabled, - # Whether recursively provide relations is supported. - # TODO This is no longer needed once unstable MSC3981 does not need to be supported. - "org.matrix.msc3981": True, - # Adds support for deleting account data. - "org.matrix.msc3391": self.config.experimental.msc3391_enabled, - # Allows clients to inhibit profile update propagation. - "org.matrix.msc4069": self.config.experimental.msc4069_profile_inhibit_propagation, - # Allows clients to handle push for encrypted events. - "org.matrix.msc4028": self.config.experimental.msc4028_push_encrypted_events, - # MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code - 2024 version - "org.matrix.msc4108": ( - self.config.experimental.msc4108_enabled - or ( - self.config.experimental.msc4108_delegation_endpoint - is not None - ) - ), - # MSC4140: Delayed events - "org.matrix.msc4140": bool(self.config.server.max_event_delay_ms), - # Simplified sliding sync - "org.matrix.simplified_msc3575": msc3575_enabled, - # Arbitrary key-value profile fields. - "uk.tcpip.msc4133": self.config.experimental.msc4133_enabled, - "uk.tcpip.msc4133.stable": True, - # MSC4155: Invite filtering - "org.matrix.msc4155": self.config.experimental.msc4155_enabled, - # MSC4306: Support for thread subscriptions - "org.matrix.msc4306": self.config.experimental.msc4306_enabled, - # MSC4169: Backwards-compatible redaction sending using `/send` - "com.beeper.msc4169": self.config.experimental.msc4169_enabled, - # MSC4354: Sticky events - "org.matrix.msc4354": self.config.experimental.msc4354_enabled, - # MSC4380: Invite blocking - "org.matrix.msc4380.stable": True, - # MSC4445: Sync timeline order - "org.matrix.msc4445.initial_sync_timeline_topological_ordering": True, - }, - }, + versions_response_body, ) From 51ff8ee3d6a5b71cd5f6cd15de372590294375e6 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 4 Jun 2026 11:31:45 -0500 Subject: [PATCH 08/24] Refine usage --- rust/src/config/mod.rs | 35 +++ rust/src/handlers/mod.rs | 42 ++++ rust/src/handlers/versions.rs | 244 ++++++++++---------- rust/src/lib.rs | 5 +- rust/src/{ => storage}/db/db.rs | 0 rust/src/{ => storage}/db/mod.rs | 0 rust/src/{ => storage}/db/python_db_pool.rs | 0 rust/src/storage/mod.rs | 17 ++ rust/src/storage/store.rs | 29 +++ synapse/storage/util/id_generators.py | 2 + 10 files changed, 257 insertions(+), 117 deletions(-) create mode 100644 rust/src/config/mod.rs create mode 100644 rust/src/handlers/mod.rs rename rust/src/{ => storage}/db/db.rs (100%) rename rust/src/{ => storage}/db/mod.rs (100%) rename rust/src/{ => storage}/db/python_db_pool.rs (100%) create mode 100644 rust/src/storage/mod.rs create mode 100644 rust/src/storage/store.rs diff --git a/rust/src/config/mod.rs b/rust/src/config/mod.rs new file mode 100644 index 00000000000..af7ccee1aa4 --- /dev/null +++ b/rust/src/config/mod.rs @@ -0,0 +1,35 @@ +/* + * This file is licensed under the Affero General Public License (AGPL) version 3. + * + * Copyright (C) 2026 Element Creations Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * See the GNU Affero General Public License for more details: + * . + * + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SynapseConfig { + pub experimental: ExperimentalConfig, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub enum RoomCreationPreset { + PrviateChat, + PublicChat, + TrustedPrivateChat, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ExperimentalConfig { + pub msc3881_enabled: bool, + pub msc3575_enabled: bool, +} diff --git a/rust/src/handlers/mod.rs b/rust/src/handlers/mod.rs new file mode 100644 index 00000000000..559dd809b58 --- /dev/null +++ b/rust/src/handlers/mod.rs @@ -0,0 +1,42 @@ +/* + * This file is licensed under the Affero General Public License (AGPL) version 3. + * + * Copyright (C) 2026 Element Creations Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * See the GNU Affero General Public License for more details: + * . + * + */ + +use pyo3::{ + types::{PyAnyMethods, PyModule, PyModuleMethods}, + wrap_pyfunction, Bound, PyResult, Python, +}; +use serde::{Deserialize, Serialize}; + +pub mod versions; + +/// Called when registering modules with python. +pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { + let child_module = PyModule::new(py, "handlers")?; + child_module.add_class::()?; + child_module.add_class::()?; + child_module.add_class::()?; + child_module.add_class::()?; + child_module.add_function(wrap_pyfunction!(get_base_rule_ids, m)?)?; + + m.add_submodule(&child_module)?; + + // We need to manually add the module to sys.modules to make `from + // synapse.synapse_rust import push` work. + py.import("sys")? + .getattr("modules")? + .set_item("synapse.synapse_rust.handlers", child_module)?; + + Ok(()) +} diff --git a/rust/src/handlers/versions.rs b/rust/src/handlers/versions.rs index 6f1d4ffe6a5..6d8493b1411 100644 --- a/rust/src/handlers/versions.rs +++ b/rust/src/handlers/versions.rs @@ -1,4 +1,3 @@ - /* * This file is licensed under the Affero General Public License (AGPL) version 3. * @@ -14,6 +13,12 @@ * */ +use serde::{Deserialize, Serialize}; + +use crate::config::SynapseConfig; +use crate::storage::store::{PerUserExperimentalFeature, Store}; + +/// `GET /_matrix/client/versions` response #[derive(Serialize, Deserialize, Clone, Debug)] struct VersionsResponse { versions: Vec, @@ -21,38 +26,45 @@ struct VersionsResponse { unstable_features: std::collections::BTreeMap, } +/// Assemble a `/versions` response async fn get_versions( + store: Store, user_id: Option<&str>, - config: + config: SynapseConfig, ) -> VersionsResponse { - match user_id { + let msc3881_enabled = match user_id { Some(user_id) => { - msc3881_enabled = store.is_feature_enabled( - user_id, ExperimentalFeature.MSC3881 - ).await; - msc3575_enabled = store.is_feature_enabled( - user_id, ExperimentalFeature.MSC3575 - ).await; - }, + store + .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3881) + .await + } + None => config.experimental.msc3881_enabled, + }; + + let msc3575_enabled = match user_id { + Some(user_id) => { + store + .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3575) + .await + } None => { - msc3881_enabled = config.experimental.msc3881_enabled; - msc3575_enabled = config.experimental.msc3575_enabled; + config.experimental.msc3575_enabled; } - } + }; // TODO: Calculate these once since they shouldn't change after start-up. - e2ee_forced_public = ( - RoomCreationPreset.PUBLIC_CHAT - in config.room.encryption_enabled_by_default_for_room_presets - ); - e2ee_forced_private = ( - RoomCreationPreset.PRIVATE_CHAT - in config.room.encryption_enabled_by_default_for_room_presets - ); - e2ee_forced_trusted_private = ( - RoomCreationPreset.TRUSTED_PRIVATE_CHAT - in config.room.encryption_enabled_by_default_for_room_presets - ); + // e2ee_forced_public = ( + // RoomCreationPreset.PUBLIC_CHAT + // in config.room.encryption_enabled_by_default_for_room_presets + // ); + // e2ee_forced_private = ( + // RoomCreationPreset.PRIVATE_CHAT + // in config.room.encryption_enabled_by_default_for_room_presets + // ); + // e2ee_forced_trusted_private = ( + // RoomCreationPreset.TRUSTED_PRIVATE_CHAT + // in config.room.encryption_enabled_by_default_for_room_presets + // ); return VersionsResponse { versions: Vec::from([ @@ -63,100 +75,100 @@ async fn get_versions( // middle number is the major revision when at 0.X.Y (as opposed to // X.Y.Z). And we need to decide whether it's fair to make clients // parse the version string to figure out what's going on. - "r0.0.1", - "r0.1.0", - "r0.2.0", - "r0.3.0", - "r0.4.0", - "r0.5.0", - "r0.6.0", - "r0.6.1", - "v1.1", - "v1.2", - "v1.3", - "v1.4", - "v1.5", - "v1.6", - "v1.7", - "v1.8", - "v1.9", - "v1.10", - "v1.11", - "v1.12", + "r0.0.1".to_string(), + "r0.1.0".to_string(), + "r0.2.0".to_string(), + "r0.3.0".to_string(), + "r0.4.0".to_string(), + "r0.5.0".to_string(), + "r0.6.0".to_string(), + "r0.6.1".to_string(), + "v1.1".to_string(), + "v1.2".to_string(), + "v1.3".to_string(), + "v1.4".to_string(), + "v1.5".to_string(), + "v1.6".to_string(), + "v1.7".to_string(), + "v1.8".to_string(), + "v1.9".to_string(), + "v1.10".to_string(), + "v1.11".to_string(), + "v1.12".to_string(), ]), unstable_features: BTreeMap::from([ - // Implements support for label-based filtering as described in - // MSC2326. - ("org.matrix.label_based_filtering": true), - // Implements support for cross signing as described in MSC1756 - ("org.matrix.e2e_cross_signing": true), - // Implements additional endpoints as described in MSC2432 - ("org.matrix.msc2432": true), - // Implements additional endpoints as described in MSC2666 - ("uk.half-shot.msc2666.query_mutual_rooms.stable": true), - // Whether new rooms will be set to encrypted or not (based on presets). - ("io.element.e2ee_forced.public": e2ee_forced_public), - ("io.element.e2ee_forced.private": e2ee_forced_private), - ("io.element.e2ee_forced.trusted_private": e2ee_forced_trusted_private), - // Supports the busy presence state described in MSC3026. - ("org.matrix.msc3026.busy_presence": config.experimental.msc3026_enabled), - // Supports receiving private read receipts as per MSC2285 - ("org.matrix.msc2285.stable": true), // TODO: Remove when MSC2285 becomes a part of the spec - // Supports filtering of /publicRooms by room type as per MSC3827 - ("org.matrix.msc3827.stable": true), - // Adds support for thread relations, per MSC3440. - ("org.matrix.msc3440.stable": true), // TODO: remove when "v1.3" is added above - // Support for thread read receipts & notification counts. - ("org.matrix.msc3771": true), - ("org.matrix.msc3773": config.experimental.msc3773_enabled), - // Allows moderators to fetch redacted event content as described in MSC2815 - ("fi.mau.msc2815": config.experimental.msc2815_enabled), - // Adds a ping endpoint for appservices to check HS->AS connection - ("fi.mau.msc2659.stable": true), // TODO: remove when "v1.7" is added above - // TODO: this is no longer needed once unstable MSC3882 does not need to be supported: - ("org.matrix.msc3882": config.auth.login_via_existing_enabled), + // // Implements support for label-based filtering as described in + // // MSC2326. + // ("org.matrix.label_based_filtering", true), + // // Implements support for cross signing as described in MSC1756 + // ("org.matrix.e2e_cross_signing", true), + // // Implements additional endpoints as described in MSC2432 + // ("org.matrix.msc2432", true), + // // Implements additional endpoints as described in MSC2666 + // ("uk.half-shot.msc2666.query_mutual_rooms.stable", true), + // // Whether new rooms will be set to encrypted or not (based on presets). + // ("io.element.e2ee_forced.public", e2ee_forced_public), + // ("io.element.e2ee_forced.private", e2ee_forced_private), + // ("io.element.e2ee_forced.trusted_private", e2ee_forced_trusted_private), + // // Supports the busy presence state described in MSC3026. + // ("org.matrix.msc3026.busy_presence", config.experimental.msc3026_enabled), + // // Supports receiving private read receipts as per MSC2285 + // ("org.matrix.msc2285.stable", true), // TODO: Remove when MSC2285 becomes a part of the spec + // // Supports filtering of /publicRooms by room type as per MSC3827 + // ("org.matrix.msc3827.stable", true), + // // Adds support for thread relations, per MSC3440. + // ("org.matrix.msc3440.stable", true), // TODO: remove when "v1.3" is added above + // // Support for thread read receipts & notification counts. + // ("org.matrix.msc3771", true), + // ("org.matrix.msc3773", config.experimental.msc3773_enabled), + // // Allows moderators to fetch redacted event content as described in MSC2815 + // ("fi.mau.msc2815", config.experimental.msc2815_enabled), + // // Adds a ping endpoint for appservices to check HS->AS connection + // ("fi.mau.msc2659.stable", true), // TODO: remove when "v1.7" is added above + // // TODO: this is no longer needed once unstable MSC3882 does not need to be supported: + // ("org.matrix.msc3882", config.auth.login_via_existing_enabled), // Adds support for remotely enabling/disabling pushers, as per MSC3881 - ("org.matrix.msc3881": msc3881_enabled), - // Adds support for filtering /messages by event relation. - ("org.matrix.msc3874": config.experimental.msc3874_enabled), - // Adds support for relation-based redactions as per MSC3912. - ("org.matrix.msc3912": config.experimental.msc3912_enabled), - // Whether recursively provide relations is supported. - // TODO This is no longer needed once unstable MSC3981 does not need to be supported. - ("org.matrix.msc3981": true), - // Adds support for deleting account data. - ("org.matrix.msc3391": config.experimental.msc3391_enabled), - // Allows clients to inhibit profile update propagation. - ("org.matrix.msc4069": config.experimental.msc4069_profile_inhibit_propagation), - // Allows clients to handle push for encrypted events. - ("org.matrix.msc4028": config.experimental.msc4028_push_encrypted_events), - // MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code - 2024 version - ("org.matrix.msc4108": ( - config.experimental.msc4108_enabled - or ( - config.experimental.msc4108_delegation_endpoint - is not None - ) - )), - // MSC4140: Delayed events - ("org.matrix.msc4140": bool(config.server.max_event_delay_ms)), + ("org.matrix.msc3881", msc3881_enabled), + // // Adds support for filtering /messages by event relation. + // ("org.matrix.msc3874", config.experimental.msc3874_enabled), + // // Adds support for relation-based redactions as per MSC3912. + // ("org.matrix.msc3912", config.experimental.msc3912_enabled), + // // Whether recursively provide relations is supported. + // // TODO This is no longer needed once unstable MSC3981 does not need to be supported. + // ("org.matrix.msc3981", true), + // // Adds support for deleting account data. + // ("org.matrix.msc3391", config.experimental.msc3391_enabled), + // // Allows clients to inhibit profile update propagation. + // ("org.matrix.msc4069", config.experimental.msc4069_profile_inhibit_propagation), + // // Allows clients to handle push for encrypted events. + // ("org.matrix.msc4028", config.experimental.msc4028_push_encrypted_events), + // // MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code - 2024 version + // ("org.matrix.msc4108", ( + // config.experimental.msc4108_enabled + // or ( + // config.experimental.msc4108_delegation_endpoint + // is not None + // ) + // )), + // // MSC4140: Delayed events + // ("org.matrix.msc4140", bool(config.server.max_event_delay_ms)), // Simplified sliding sync - ("org.matrix.simplified_msc3575": msc3575_enabled), - // Arbitrary key-value profile fields. - ("uk.tcpip.msc4133": config.experimental.msc4133_enabled), - ("uk.tcpip.msc4133.stable": true), - // MSC4155: Invite filtering - ("org.matrix.msc4155": config.experimental.msc4155_enabled), - // MSC4306: Support for thread subscriptions - ("org.matrix.msc4306": config.experimental.msc4306_enabled), - // MSC4169: Backwards-compatible redaction sending using `/send` - ("com.beeper.msc4169": config.experimental.msc4169_enabled), - // MSC4354: Sticky events - ("org.matrix.msc4354": config.experimental.msc4354_enabled), - // MSC4380: Invite blocking - ("org.matrix.msc4380.stable": true), - // MSC4445: Sync timeline order - ("org.matrix.msc4445.initial_sync_timeline_topological_ordering": true), + ("org.matrix.simplified_msc3575", msc3575_enabled), + // // Arbitrary key-value profile fields. + // ("uk.tcpip.msc4133", config.experimental.msc4133_enabled), + // ("uk.tcpip.msc4133.stable", true), + // // MSC4155: Invite filtering + // ("org.matrix.msc4155", config.experimental.msc4155_enabled), + // // MSC4306: Support for thread subscriptions + // ("org.matrix.msc4306", config.experimental.msc4306_enabled), + // // MSC4169: Backwards-compatible redaction sending using `/send` + // ("com.beeper.msc4169", config.experimental.msc4169_enabled), + // // MSC4354: Sticky events + // ("org.matrix.msc4354", config.experimental.msc4354_enabled), + // // MSC4380: Invite blocking + // ("org.matrix.msc4380.stable", true), + // // MSC4445: Sync timeline order + // ("org.matrix.msc4445.initial_sync_timeline_topological_ordering", true), ]), - } + }; } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index a5ce7b5a4eb..38bae6e77a0 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -6,10 +6,11 @@ use pyo3_log::ResetHandle; pub mod acl; pub mod canonical_json; -pub mod db; +pub mod config; pub mod duration; pub mod errors; pub mod events; +pub mod handlers; pub mod http; pub mod http_client; pub mod identifier; @@ -20,6 +21,7 @@ pub mod push; pub mod rendezvous; pub mod room_versions; pub mod segmenter; +pub mod storage; lazy_static! { static ref LOGGING_HANDLE: ResetHandle = pyo3_log::init(); @@ -67,6 +69,7 @@ fn synapse_rust(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { acl::register_module(py, m)?; push::register_module(py, m)?; events::register_module(py, m)?; + handlers::register_module(py, m)?; http_client::register_module(py, m)?; rendezvous::register_module(py, m)?; msc4388_rendezvous::register_module(py, m)?; diff --git a/rust/src/db/db.rs b/rust/src/storage/db/db.rs similarity index 100% rename from rust/src/db/db.rs rename to rust/src/storage/db/db.rs diff --git a/rust/src/db/mod.rs b/rust/src/storage/db/mod.rs similarity index 100% rename from rust/src/db/mod.rs rename to rust/src/storage/db/mod.rs diff --git a/rust/src/db/python_db_pool.rs b/rust/src/storage/db/python_db_pool.rs similarity index 100% rename from rust/src/db/python_db_pool.rs rename to rust/src/storage/db/python_db_pool.rs diff --git a/rust/src/storage/mod.rs b/rust/src/storage/mod.rs new file mode 100644 index 00000000000..735fcecb266 --- /dev/null +++ b/rust/src/storage/mod.rs @@ -0,0 +1,17 @@ +/* + * This file is licensed under the Affero General Public License (AGPL) version 3. + * + * Copyright (C) 2026 Element Creations Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * See the GNU Affero General Public License for more details: + * . + * + */ + +pub mod db; +pub mod store; diff --git a/rust/src/storage/store.rs b/rust/src/storage/store.rs new file mode 100644 index 00000000000..0f582d94f36 --- /dev/null +++ b/rust/src/storage/store.rs @@ -0,0 +1,29 @@ +/* + * This file is licensed under the Affero General Public License (AGPL) version 3. + * + * Copyright (C) 2026 Element Creations Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * See the GNU Affero General Public License for more details: + * . + * + */ + +/// Currently supported per-user features +pub enum PerUserExperimentalFeature { + MSC3881, + MSC3575, + MSC4222, +} + +pub struct Store {} + +impl Store { + pub fn is_feature_enabled(feature: PerUserExperimentalFeature) -> Result { + todo!("..."); + } +} diff --git a/synapse/storage/util/id_generators.py b/synapse/storage/util/id_generators.py index 1e053be6aff..4534ce85e74 100644 --- a/synapse/storage/util/id_generators.py +++ b/synapse/storage/util/id_generators.py @@ -831,6 +831,8 @@ def _add_persisted_position(self, new_id: int) -> None: # Hacky debug logging to attempt to trace https://github.com/element-hq/synapse/issues/19795 if ( issue9533_logger.isEnabledFor(logging.DEBUG) + # Only log if we are the instance that is doing the persisting + and our_current_position > 0 and self._stream_name == "to_device" ): issue9533_logger.debug( From 67f23913ef847b6a6c737f3303a8d26332a378a3 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 4 Jun 2026 12:07:57 -0500 Subject: [PATCH 09/24] Refine more --- rust/src/config/mod.rs | 11 +++++ rust/src/handlers/mod.rs | 10 ++-- rust/src/handlers/versions.rs | 86 ++++++++++++++++----------------- rust/src/storage/store.rs | 6 ++- synapse/rest/client/versions.py | 3 +- 5 files changed, 62 insertions(+), 54 deletions(-) diff --git a/rust/src/config/mod.rs b/rust/src/config/mod.rs index af7ccee1aa4..f34d9a75eac 100644 --- a/rust/src/config/mod.rs +++ b/rust/src/config/mod.rs @@ -13,6 +13,8 @@ * */ +// use pyo3::PyAny; +// use pyo3::{intern, prelude::*}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug)] @@ -20,6 +22,15 @@ pub struct SynapseConfig { pub experimental: ExperimentalConfig, } +// impl<'py> FromPyObject<'_, 'py> for SynapseConfig<'py> { +// type Error = PyErr; + +// /// From Python `LoggingTransaction` +// fn extract(config_python_object: Borrowed<'_, 'py, PyAny>) -> PyResult { +// todo!("..."); +// } +// } + #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "snake_case")] pub enum RoomCreationPreset { diff --git a/rust/src/handlers/mod.rs b/rust/src/handlers/mod.rs index 559dd809b58..1087596bbe0 100644 --- a/rust/src/handlers/mod.rs +++ b/rust/src/handlers/mod.rs @@ -15,20 +15,16 @@ use pyo3::{ types::{PyAnyMethods, PyModule, PyModuleMethods}, - wrap_pyfunction, Bound, PyResult, Python, + Bound, PyResult, Python, }; -use serde::{Deserialize, Serialize}; pub mod versions; /// Called when registering modules with python. pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { let child_module = PyModule::new(py, "handlers")?; - child_module.add_class::()?; - child_module.add_class::()?; - child_module.add_class::()?; - child_module.add_class::()?; - child_module.add_function(wrap_pyfunction!(get_base_rule_ids, m)?)?; + // TODO: Expose `get_versions` + // child_module.add_class::()?; m.add_submodule(&child_module)?; diff --git a/rust/src/handlers/versions.rs b/rust/src/handlers/versions.rs index 6d8493b1411..4ca738d595c 100644 --- a/rust/src/handlers/versions.rs +++ b/rust/src/handlers/versions.rs @@ -28,15 +28,15 @@ struct VersionsResponse { /// Assemble a `/versions` response async fn get_versions( - store: Store, + store: &Store, user_id: Option<&str>, config: SynapseConfig, -) -> VersionsResponse { +) -> Result { let msc3881_enabled = match user_id { Some(user_id) => { store .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3881) - .await + .await? } None => config.experimental.msc3881_enabled, }; @@ -45,11 +45,9 @@ async fn get_versions( Some(user_id) => { store .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3575) - .await - } - None => { - config.experimental.msc3575_enabled; + .await? } + None => config.experimental.msc3575_enabled, }; // TODO: Calculate these once since they shouldn't change after start-up. @@ -66,7 +64,7 @@ async fn get_versions( // in config.room.encryption_enabled_by_default_for_room_presets // ); - return VersionsResponse { + return Ok(VersionsResponse { versions: Vec::from([ // XXX: at some point we need to decide whether we need to include // the previous version numbers, given we've defined r0.3.0 to be @@ -96,54 +94,54 @@ async fn get_versions( "v1.11".to_string(), "v1.12".to_string(), ]), - unstable_features: BTreeMap::from([ + unstable_features: std::collections::BTreeMap::from([ // // Implements support for label-based filtering as described in // // MSC2326. - // ("org.matrix.label_based_filtering", true), + // ("org.matrix.label_based_filtering".to_string(), true), // // Implements support for cross signing as described in MSC1756 - // ("org.matrix.e2e_cross_signing", true), + // ("org.matrix.e2e_cross_signing".to_string(), true), // // Implements additional endpoints as described in MSC2432 - // ("org.matrix.msc2432", true), + // ("org.matrix.msc2432".to_string(), true), // // Implements additional endpoints as described in MSC2666 - // ("uk.half-shot.msc2666.query_mutual_rooms.stable", true), + // ("uk.half-shot.msc2666.query_mutual_rooms.stable".to_string(), true), // // Whether new rooms will be set to encrypted or not (based on presets). - // ("io.element.e2ee_forced.public", e2ee_forced_public), - // ("io.element.e2ee_forced.private", e2ee_forced_private), - // ("io.element.e2ee_forced.trusted_private", e2ee_forced_trusted_private), + // ("io.element.e2ee_forced.public".to_string(), e2ee_forced_public), + // ("io.element.e2ee_forced.private".to_string(), e2ee_forced_private), + // ("io.element.e2ee_forced.trusted_private".to_string(), e2ee_forced_trusted_private), // // Supports the busy presence state described in MSC3026. - // ("org.matrix.msc3026.busy_presence", config.experimental.msc3026_enabled), + // ("org.matrix.msc3026.busy_presence".to_string(), config.experimental.msc3026_enabled), // // Supports receiving private read receipts as per MSC2285 - // ("org.matrix.msc2285.stable", true), // TODO: Remove when MSC2285 becomes a part of the spec + // ("org.matrix.msc2285.stable".to_string(), true), // TODO: Remove when MSC2285 becomes a part of the spec // // Supports filtering of /publicRooms by room type as per MSC3827 - // ("org.matrix.msc3827.stable", true), + // ("org.matrix.msc3827.stable".to_string(), true), // // Adds support for thread relations, per MSC3440. - // ("org.matrix.msc3440.stable", true), // TODO: remove when "v1.3" is added above + // ("org.matrix.msc3440.stable".to_string(), true), // TODO: remove when "v1.3" is added above // // Support for thread read receipts & notification counts. - // ("org.matrix.msc3771", true), - // ("org.matrix.msc3773", config.experimental.msc3773_enabled), + // ("org.matrix.msc3771".to_string(), true), + // ("org.matrix.msc3773".to_string(), config.experimental.msc3773_enabled), // // Allows moderators to fetch redacted event content as described in MSC2815 - // ("fi.mau.msc2815", config.experimental.msc2815_enabled), + // ("fi.mau.msc2815".to_string(), config.experimental.msc2815_enabled), // // Adds a ping endpoint for appservices to check HS->AS connection - // ("fi.mau.msc2659.stable", true), // TODO: remove when "v1.7" is added above + // ("fi.mau.msc2659.stable".to_string(), true), // TODO: remove when "v1.7" is added above // // TODO: this is no longer needed once unstable MSC3882 does not need to be supported: - // ("org.matrix.msc3882", config.auth.login_via_existing_enabled), + // ("org.matrix.msc3882".to_string(), config.auth.login_via_existing_enabled), // Adds support for remotely enabling/disabling pushers, as per MSC3881 - ("org.matrix.msc3881", msc3881_enabled), + ("org.matrix.msc3881".to_string(), msc3881_enabled), // // Adds support for filtering /messages by event relation. - // ("org.matrix.msc3874", config.experimental.msc3874_enabled), + // ("org.matrix.msc3874".to_string(), config.experimental.msc3874_enabled), // // Adds support for relation-based redactions as per MSC3912. - // ("org.matrix.msc3912", config.experimental.msc3912_enabled), + // ("org.matrix.msc3912".to_string(), config.experimental.msc3912_enabled), // // Whether recursively provide relations is supported. // // TODO This is no longer needed once unstable MSC3981 does not need to be supported. - // ("org.matrix.msc3981", true), + // ("org.matrix.msc3981".to_string(), true), // // Adds support for deleting account data. - // ("org.matrix.msc3391", config.experimental.msc3391_enabled), + // ("org.matrix.msc3391".to_string(), config.experimental.msc3391_enabled), // // Allows clients to inhibit profile update propagation. - // ("org.matrix.msc4069", config.experimental.msc4069_profile_inhibit_propagation), + // ("org.matrix.msc4069".to_string(), config.experimental.msc4069_profile_inhibit_propagation), // // Allows clients to handle push for encrypted events. - // ("org.matrix.msc4028", config.experimental.msc4028_push_encrypted_events), + // ("org.matrix.msc4028".to_string(), config.experimental.msc4028_push_encrypted_events), // // MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code - 2024 version - // ("org.matrix.msc4108", ( + // ("org.matrix.msc4108".to_string(), ( // config.experimental.msc4108_enabled // or ( // config.experimental.msc4108_delegation_endpoint @@ -151,24 +149,24 @@ async fn get_versions( // ) // )), // // MSC4140: Delayed events - // ("org.matrix.msc4140", bool(config.server.max_event_delay_ms)), + // ("org.matrix.msc4140".to_string(), bool(config.server.max_event_delay_ms)), // Simplified sliding sync - ("org.matrix.simplified_msc3575", msc3575_enabled), + ("org.matrix.simplified_msc3575".to_string(), msc3575_enabled), // // Arbitrary key-value profile fields. - // ("uk.tcpip.msc4133", config.experimental.msc4133_enabled), - // ("uk.tcpip.msc4133.stable", true), + // ("uk.tcpip.msc4133".to_string(), config.experimental.msc4133_enabled), + // ("uk.tcpip.msc4133.stable".to_string(), true), // // MSC4155: Invite filtering - // ("org.matrix.msc4155", config.experimental.msc4155_enabled), + // ("org.matrix.msc4155".to_string(), config.experimental.msc4155_enabled), // // MSC4306: Support for thread subscriptions - // ("org.matrix.msc4306", config.experimental.msc4306_enabled), + // ("org.matrix.msc4306".to_string(), config.experimental.msc4306_enabled), // // MSC4169: Backwards-compatible redaction sending using `/send` - // ("com.beeper.msc4169", config.experimental.msc4169_enabled), + // ("com.beeper.msc4169".to_string(), config.experimental.msc4169_enabled), // // MSC4354: Sticky events - // ("org.matrix.msc4354", config.experimental.msc4354_enabled), + // ("org.matrix.msc4354".to_string(), config.experimental.msc4354_enabled), // // MSC4380: Invite blocking - // ("org.matrix.msc4380.stable", true), + // ("org.matrix.msc4380.stable".to_string(), true), // // MSC4445: Sync timeline order - // ("org.matrix.msc4445.initial_sync_timeline_topological_ordering", true), + // ("org.matrix.msc4445.initial_sync_timeline_topological_ordering".to_string(), true), ]), - }; + }); } diff --git a/rust/src/storage/store.rs b/rust/src/storage/store.rs index 0f582d94f36..ffdefc2f906 100644 --- a/rust/src/storage/store.rs +++ b/rust/src/storage/store.rs @@ -23,7 +23,11 @@ pub enum PerUserExperimentalFeature { pub struct Store {} impl Store { - pub fn is_feature_enabled(feature: PerUserExperimentalFeature) -> Result { + pub async fn is_feature_enabled( + &self, + user_id: &str, + feature: PerUserExperimentalFeature, + ) -> Result { todo!("..."); } } diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index a9925e5cf4f..d1f47ff817a 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -49,7 +49,6 @@ def __init__(self, hs: "HomeServer"): self.auth = hs.get_auth() self.store = hs.get_datastores().main - async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]: user_id = None if self.auth.has_access_token(request): @@ -81,7 +80,7 @@ async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]: # authenticated responses are not served from cache. request.setHeader(b"Vary", b"Authorization") - versions_response_body = await get_versions(user_id) + versions_response_body = await get_versions(user_id, self.config) return ( 200, From 99b13354d276ac89dfe10f9a9940dfe4d25fee7c Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 4 Jun 2026 16:31:46 -0500 Subject: [PATCH 10/24] Iterate on structure --- Cargo.lock | 854 ++++++++++++++++++++++++-- rust/Cargo.toml | 6 + rust/src/config/mod.rs | 1 + rust/src/handlers/mod.rs | 22 +- rust/src/handlers/versions.rs | 284 ++++----- rust/src/storage/db/db.rs | 72 --- rust/src/storage/db/mod.rs | 12 +- rust/src/storage/db/python_db_pool.rs | 86 ++- rust/src/storage/db/rust_db_pool.rs | 71 +++ rust/src/storage/store.rs | 43 +- synapse/rest/client/versions.py | 9 +- synapse/server.py | 4 + 12 files changed, 1160 insertions(+), 304 deletions(-) delete mode 100644 rust/src/storage/db/db.rs create mode 100644 rust/src/storage/db/rust_db_pool.rs diff --git a/Cargo.lock b/Cargo.lock index 761e99b3e65..5759240eb95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,17 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -35,6 +46,30 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bb8" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89aabfae550a5c44b43ab941844ffcd2e993cb6900b342debf59e9ea74acdb8" +dependencies = [ + "async-trait", + "futures-util", + "parking_lot", + "tokio", +] + +[[package]] +name = "bb8-postgres" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ac82c42eb30889b5c4ee4763a24b8c566518171ebea648cd7e3bc532c60680" +dependencies = [ + "async-trait", + "bb8", + "tokio", + "tokio-postgres", +] + [[package]] name = "bitflags" version = "2.9.1" @@ -47,7 +82,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -59,12 +94,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" @@ -92,6 +142,39 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -126,6 +209,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -136,17 +228,47 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.6", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "const-oid", + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -170,12 +292,55 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -305,11 +470,25 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + [[package]] name = "h2" version = "0.4.11" @@ -334,6 +513,9 @@ name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] [[package]] name = "headers" @@ -371,6 +553,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "http" version = "1.4.0" @@ -416,6 +607,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.6.0" @@ -609,6 +809,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5360a2fbe97f617c4f8b944356dedb36d423f7da7f13c070995cf89e59f01220" +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.0.3" @@ -638,6 +844,7 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] @@ -673,10 +880,12 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -687,11 +896,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.174" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -699,12 +914,36 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -717,6 +956,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.3", +] + [[package]] name = "memchr" version = "2.7.5" @@ -740,36 +989,197 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "objc2-core-foundation", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "openssl" +version = "0.10.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-sys" +version = "0.9.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared", + "serde", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "postgres-native-tls" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef4de47bb81477e0c3deaf153a1b10ae176484713ff1640969f4cb96b653ebc" +dependencies = [ + "native-tls", + "tokio", + "tokio-native-tls", + "tokio-postgres", +] + +[[package]] +name = "postgres-protocol" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56201207dac53e2f38e848e31b4b91616a6bb6e0c7205b77718994a7f49e70fc" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand 0.10.1", + "sha2 0.11.0", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc729a129e682e8d24170cd30ae1aa01b336b096cbb56df6d534ffec133d186" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -789,6 +1199,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -909,7 +1329,7 @@ dependencies = [ "bytes", "getrandom 0.3.3", "lru-slab", - "rand", + "rand 0.9.4", "ring", "rustc-hash", "rustls", @@ -950,6 +1370,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.9.4" @@ -957,7 +1383,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.3", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -967,7 +1404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", ] [[package]] @@ -979,6 +1416,21 @@ dependencies = [ "getrandom 0.3.3", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.12.3" @@ -1079,6 +1531,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.31" @@ -1102,7 +1567,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", ] [[package]] @@ -1147,6 +1612,25 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.2.0" @@ -1154,7 +1638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -1238,8 +1722,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] @@ -1249,8 +1733,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1259,6 +1754,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "slab" version = "0.4.11" @@ -1297,6 +1798,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "subtle" version = "2.6.1" @@ -1305,9 +1817,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1320,6 +1832,8 @@ version = "0.1.0" dependencies = [ "anyhow", "base64", + "bb8", + "bb8-postgres", "blake2", "bytes", "futures", @@ -1333,6 +1847,7 @@ dependencies = [ "log", "mime", "once_cell", + "postgres-native-tls", "pyo3", "pyo3-log", "pythonize", @@ -1341,7 +1856,7 @@ dependencies = [ "rustc_version", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "tokio", "ulid", ] @@ -1372,6 +1887,19 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "2.0.12" @@ -1426,11 +1954,48 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "socket2 0.6.0", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-postgres" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd8df5ef180f6364759a6f00f7aadda4fbbac86cdee37480826a6ff9f3574ce" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand 0.10.1", + "socket2 0.6.0", + "tokio", + "tokio-util", + "whoami", +] + [[package]] name = "tokio-rustls" version = "0.26.2" @@ -1526,9 +2091,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.18.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ulid" @@ -1536,16 +2101,43 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" dependencies = [ - "rand", + "rand 0.9.4", "web-time", ] +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -1569,6 +2161,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -1600,49 +2198,60 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen" -version = "0.2.100" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", + "wit-bindgen 0.57.1", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasite" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" +dependencies = [ + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1650,26 +2259,48 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -1683,11 +2314,23 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -1703,6 +2346,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "whoami" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" +dependencies = [ + "libc", + "libredox", + "objc2-system-configuration", + "wasite", + "web-sys", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -1800,6 +2456,32 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -1809,6 +2491,74 @@ dependencies = [ "bitflags", ] +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "writeable" version = "0.6.1" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 162bc981820..dfd6d87b618 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -64,6 +64,12 @@ tokio = { version = "1.44.2", features = ["rt", "rt-multi-thread"] } once_cell = "1.18.0" itertools = "0.14.0" +# TODO: Remove: These are just used to make sure a tokio-postgres backed database pool makes sense +# with our interfaces +bb8 = "0.8.3" +bb8-postgres = "0.8.1" +postgres-native-tls = "0.5.0" + [build-dependencies] blake2 = "0.10.4" hex = "0.4.3" diff --git a/rust/src/config/mod.rs b/rust/src/config/mod.rs index f34d9a75eac..4f99f8ef600 100644 --- a/rust/src/config/mod.rs +++ b/rust/src/config/mod.rs @@ -43,4 +43,5 @@ pub enum RoomCreationPreset { pub struct ExperimentalConfig { pub msc3881_enabled: bool, pub msc3575_enabled: bool, + pub msc4222_enabled: bool, } diff --git a/rust/src/handlers/mod.rs b/rust/src/handlers/mod.rs index 1087596bbe0..f03fdd37218 100644 --- a/rust/src/handlers/mod.rs +++ b/rust/src/handlers/mod.rs @@ -14,17 +14,35 @@ */ use pyo3::{ + prelude::*, types::{PyAnyMethods, PyModule, PyModuleMethods}, Bound, PyResult, Python, }; +use crate::storage::store::Store; + pub mod versions; +#[pyclass] +struct RustHandlers { + versions: versions::VersionsHandler, +} + +#[pymethods] +impl RustHandlers { + #[new] + #[pyo3(signature = (homeserver))] + pub fn py_new(py: Python<'_>, homeserver: &Bound<'_, PyAny>) -> PyResult { + RustHandlers { + versions: versions::VersionsHandler { store: Store {} }, + } + } +} + /// Called when registering modules with python. pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { let child_module = PyModule::new(py, "handlers")?; - // TODO: Expose `get_versions` - // child_module.add_class::()?; + child_module.add_class::()?; m.add_submodule(&child_module)?; diff --git a/rust/src/handlers/versions.rs b/rust/src/handlers/versions.rs index 4ca738d595c..de28eabf009 100644 --- a/rust/src/handlers/versions.rs +++ b/rust/src/handlers/versions.rs @@ -26,147 +26,153 @@ struct VersionsResponse { unstable_features: std::collections::BTreeMap, } -/// Assemble a `/versions` response -async fn get_versions( +pub struct VersionsHandler { store: &Store, - user_id: Option<&str>, - config: SynapseConfig, -) -> Result { - let msc3881_enabled = match user_id { - Some(user_id) => { - store - .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3881) - .await? - } - None => config.experimental.msc3881_enabled, - }; +} + +impl VersionsHandler { + /// Assemble a `/versions` response + async fn get_versions( + &self, + user_id: Option<&str>, + config: SynapseConfig, + ) -> Result { + let msc3881_enabled = match user_id { + Some(user_id) => { + self.store + .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3881) + .await? + } + None => PerUserExperimentalFeature::MSC3881.is_globally_enabled(config), + }; - let msc3575_enabled = match user_id { - Some(user_id) => { - store - .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3575) - .await? - } - None => config.experimental.msc3575_enabled, - }; + let msc3575_enabled = match user_id { + Some(user_id) => { + self.store + .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3575) + .await? + } + None => PerUserExperimentalFeature::MSC3575.is_globally_enabled(config), + }; - // TODO: Calculate these once since they shouldn't change after start-up. - // e2ee_forced_public = ( - // RoomCreationPreset.PUBLIC_CHAT - // in config.room.encryption_enabled_by_default_for_room_presets - // ); - // e2ee_forced_private = ( - // RoomCreationPreset.PRIVATE_CHAT - // in config.room.encryption_enabled_by_default_for_room_presets - // ); - // e2ee_forced_trusted_private = ( - // RoomCreationPreset.TRUSTED_PRIVATE_CHAT - // in config.room.encryption_enabled_by_default_for_room_presets - // ); + // TODO: Calculate these once since they shouldn't change after start-up. + // e2ee_forced_public = ( + // RoomCreationPreset.PUBLIC_CHAT + // in config.room.encryption_enabled_by_default_for_room_presets + // ); + // e2ee_forced_private = ( + // RoomCreationPreset.PRIVATE_CHAT + // in config.room.encryption_enabled_by_default_for_room_presets + // ); + // e2ee_forced_trusted_private = ( + // RoomCreationPreset.TRUSTED_PRIVATE_CHAT + // in config.room.encryption_enabled_by_default_for_room_presets + // ); - return Ok(VersionsResponse { - versions: Vec::from([ - // XXX: at some point we need to decide whether we need to include - // the previous version numbers, given we've defined r0.3.0 to be - // backwards compatible with r0.2.0. But need to check how - // conscientious we've been in compatibility, and decide whether the - // middle number is the major revision when at 0.X.Y (as opposed to - // X.Y.Z). And we need to decide whether it's fair to make clients - // parse the version string to figure out what's going on. - "r0.0.1".to_string(), - "r0.1.0".to_string(), - "r0.2.0".to_string(), - "r0.3.0".to_string(), - "r0.4.0".to_string(), - "r0.5.0".to_string(), - "r0.6.0".to_string(), - "r0.6.1".to_string(), - "v1.1".to_string(), - "v1.2".to_string(), - "v1.3".to_string(), - "v1.4".to_string(), - "v1.5".to_string(), - "v1.6".to_string(), - "v1.7".to_string(), - "v1.8".to_string(), - "v1.9".to_string(), - "v1.10".to_string(), - "v1.11".to_string(), - "v1.12".to_string(), - ]), - unstable_features: std::collections::BTreeMap::from([ - // // Implements support for label-based filtering as described in - // // MSC2326. - // ("org.matrix.label_based_filtering".to_string(), true), - // // Implements support for cross signing as described in MSC1756 - // ("org.matrix.e2e_cross_signing".to_string(), true), - // // Implements additional endpoints as described in MSC2432 - // ("org.matrix.msc2432".to_string(), true), - // // Implements additional endpoints as described in MSC2666 - // ("uk.half-shot.msc2666.query_mutual_rooms.stable".to_string(), true), - // // Whether new rooms will be set to encrypted or not (based on presets). - // ("io.element.e2ee_forced.public".to_string(), e2ee_forced_public), - // ("io.element.e2ee_forced.private".to_string(), e2ee_forced_private), - // ("io.element.e2ee_forced.trusted_private".to_string(), e2ee_forced_trusted_private), - // // Supports the busy presence state described in MSC3026. - // ("org.matrix.msc3026.busy_presence".to_string(), config.experimental.msc3026_enabled), - // // Supports receiving private read receipts as per MSC2285 - // ("org.matrix.msc2285.stable".to_string(), true), // TODO: Remove when MSC2285 becomes a part of the spec - // // Supports filtering of /publicRooms by room type as per MSC3827 - // ("org.matrix.msc3827.stable".to_string(), true), - // // Adds support for thread relations, per MSC3440. - // ("org.matrix.msc3440.stable".to_string(), true), // TODO: remove when "v1.3" is added above - // // Support for thread read receipts & notification counts. - // ("org.matrix.msc3771".to_string(), true), - // ("org.matrix.msc3773".to_string(), config.experimental.msc3773_enabled), - // // Allows moderators to fetch redacted event content as described in MSC2815 - // ("fi.mau.msc2815".to_string(), config.experimental.msc2815_enabled), - // // Adds a ping endpoint for appservices to check HS->AS connection - // ("fi.mau.msc2659.stable".to_string(), true), // TODO: remove when "v1.7" is added above - // // TODO: this is no longer needed once unstable MSC3882 does not need to be supported: - // ("org.matrix.msc3882".to_string(), config.auth.login_via_existing_enabled), - // Adds support for remotely enabling/disabling pushers, as per MSC3881 - ("org.matrix.msc3881".to_string(), msc3881_enabled), - // // Adds support for filtering /messages by event relation. - // ("org.matrix.msc3874".to_string(), config.experimental.msc3874_enabled), - // // Adds support for relation-based redactions as per MSC3912. - // ("org.matrix.msc3912".to_string(), config.experimental.msc3912_enabled), - // // Whether recursively provide relations is supported. - // // TODO This is no longer needed once unstable MSC3981 does not need to be supported. - // ("org.matrix.msc3981".to_string(), true), - // // Adds support for deleting account data. - // ("org.matrix.msc3391".to_string(), config.experimental.msc3391_enabled), - // // Allows clients to inhibit profile update propagation. - // ("org.matrix.msc4069".to_string(), config.experimental.msc4069_profile_inhibit_propagation), - // // Allows clients to handle push for encrypted events. - // ("org.matrix.msc4028".to_string(), config.experimental.msc4028_push_encrypted_events), - // // MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code - 2024 version - // ("org.matrix.msc4108".to_string(), ( - // config.experimental.msc4108_enabled - // or ( - // config.experimental.msc4108_delegation_endpoint - // is not None - // ) - // )), - // // MSC4140: Delayed events - // ("org.matrix.msc4140".to_string(), bool(config.server.max_event_delay_ms)), - // Simplified sliding sync - ("org.matrix.simplified_msc3575".to_string(), msc3575_enabled), - // // Arbitrary key-value profile fields. - // ("uk.tcpip.msc4133".to_string(), config.experimental.msc4133_enabled), - // ("uk.tcpip.msc4133.stable".to_string(), true), - // // MSC4155: Invite filtering - // ("org.matrix.msc4155".to_string(), config.experimental.msc4155_enabled), - // // MSC4306: Support for thread subscriptions - // ("org.matrix.msc4306".to_string(), config.experimental.msc4306_enabled), - // // MSC4169: Backwards-compatible redaction sending using `/send` - // ("com.beeper.msc4169".to_string(), config.experimental.msc4169_enabled), - // // MSC4354: Sticky events - // ("org.matrix.msc4354".to_string(), config.experimental.msc4354_enabled), - // // MSC4380: Invite blocking - // ("org.matrix.msc4380.stable".to_string(), true), - // // MSC4445: Sync timeline order - // ("org.matrix.msc4445.initial_sync_timeline_topological_ordering".to_string(), true), - ]), - }); + return Ok(VersionsResponse { + versions: Vec::from([ + // XXX: at some point we need to decide whether we need to include + // the previous version numbers, given we've defined r0.3.0 to be + // backwards compatible with r0.2.0. But need to check how + // conscientious we've been in compatibility, and decide whether the + // middle number is the major revision when at 0.X.Y (as opposed to + // X.Y.Z). And we need to decide whether it's fair to make clients + // parse the version string to figure out what's going on. + "r0.0.1".to_string(), + "r0.1.0".to_string(), + "r0.2.0".to_string(), + "r0.3.0".to_string(), + "r0.4.0".to_string(), + "r0.5.0".to_string(), + "r0.6.0".to_string(), + "r0.6.1".to_string(), + "v1.1".to_string(), + "v1.2".to_string(), + "v1.3".to_string(), + "v1.4".to_string(), + "v1.5".to_string(), + "v1.6".to_string(), + "v1.7".to_string(), + "v1.8".to_string(), + "v1.9".to_string(), + "v1.10".to_string(), + "v1.11".to_string(), + "v1.12".to_string(), + ]), + unstable_features: std::collections::BTreeMap::from([ + // // Implements support for label-based filtering as described in + // // MSC2326. + // ("org.matrix.label_based_filtering".to_string(), true), + // // Implements support for cross signing as described in MSC1756 + // ("org.matrix.e2e_cross_signing".to_string(), true), + // // Implements additional endpoints as described in MSC2432 + // ("org.matrix.msc2432".to_string(), true), + // // Implements additional endpoints as described in MSC2666 + // ("uk.half-shot.msc2666.query_mutual_rooms.stable".to_string(), true), + // // Whether new rooms will be set to encrypted or not (based on presets). + // ("io.element.e2ee_forced.public".to_string(), e2ee_forced_public), + // ("io.element.e2ee_forced.private".to_string(), e2ee_forced_private), + // ("io.element.e2ee_forced.trusted_private".to_string(), e2ee_forced_trusted_private), + // // Supports the busy presence state described in MSC3026. + // ("org.matrix.msc3026.busy_presence".to_string(), config.experimental.msc3026_enabled), + // // Supports receiving private read receipts as per MSC2285 + // ("org.matrix.msc2285.stable".to_string(), true), // TODO: Remove when MSC2285 becomes a part of the spec + // // Supports filtering of /publicRooms by room type as per MSC3827 + // ("org.matrix.msc3827.stable".to_string(), true), + // // Adds support for thread relations, per MSC3440. + // ("org.matrix.msc3440.stable".to_string(), true), // TODO: remove when "v1.3" is added above + // // Support for thread read receipts & notification counts. + // ("org.matrix.msc3771".to_string(), true), + // ("org.matrix.msc3773".to_string(), config.experimental.msc3773_enabled), + // // Allows moderators to fetch redacted event content as described in MSC2815 + // ("fi.mau.msc2815".to_string(), config.experimental.msc2815_enabled), + // // Adds a ping endpoint for appservices to check HS->AS connection + // ("fi.mau.msc2659.stable".to_string(), true), // TODO: remove when "v1.7" is added above + // // TODO: this is no longer needed once unstable MSC3882 does not need to be supported: + // ("org.matrix.msc3882".to_string(), config.auth.login_via_existing_enabled), + // Adds support for remotely enabling/disabling pushers, as per MSC3881 + ("org.matrix.msc3881".to_string(), msc3881_enabled), + // // Adds support for filtering /messages by event relation. + // ("org.matrix.msc3874".to_string(), config.experimental.msc3874_enabled), + // // Adds support for relation-based redactions as per MSC3912. + // ("org.matrix.msc3912".to_string(), config.experimental.msc3912_enabled), + // // Whether recursively provide relations is supported. + // // TODO This is no longer needed once unstable MSC3981 does not need to be supported. + // ("org.matrix.msc3981".to_string(), true), + // // Adds support for deleting account data. + // ("org.matrix.msc3391".to_string(), config.experimental.msc3391_enabled), + // // Allows clients to inhibit profile update propagation. + // ("org.matrix.msc4069".to_string(), config.experimental.msc4069_profile_inhibit_propagation), + // // Allows clients to handle push for encrypted events. + // ("org.matrix.msc4028".to_string(), config.experimental.msc4028_push_encrypted_events), + // // MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code - 2024 version + // ("org.matrix.msc4108".to_string(), ( + // config.experimental.msc4108_enabled + // or ( + // config.experimental.msc4108_delegation_endpoint + // is not None + // ) + // )), + // // MSC4140: Delayed events + // ("org.matrix.msc4140".to_string(), bool(config.server.max_event_delay_ms)), + // Simplified sliding sync + ("org.matrix.simplified_msc3575".to_string(), msc3575_enabled), + // // Arbitrary key-value profile fields. + // ("uk.tcpip.msc4133".to_string(), config.experimental.msc4133_enabled), + // ("uk.tcpip.msc4133.stable".to_string(), true), + // // MSC4155: Invite filtering + // ("org.matrix.msc4155".to_string(), config.experimental.msc4155_enabled), + // // MSC4306: Support for thread subscriptions + // ("org.matrix.msc4306".to_string(), config.experimental.msc4306_enabled), + // // MSC4169: Backwards-compatible redaction sending using `/send` + // ("com.beeper.msc4169".to_string(), config.experimental.msc4169_enabled), + // // MSC4354: Sticky events + // ("org.matrix.msc4354".to_string(), config.experimental.msc4354_enabled), + // // MSC4380: Invite blocking + // ("org.matrix.msc4380.stable".to_string(), true), + // // MSC4445: Sync timeline order + // ("org.matrix.msc4445.initial_sync_timeline_topological_ordering".to_string(), true), + ]), + }); + } } diff --git a/rust/src/storage/db/db.rs b/rust/src/storage/db/db.rs deleted file mode 100644 index 8af7925c115..00000000000 --- a/rust/src/storage/db/db.rs +++ /dev/null @@ -1,72 +0,0 @@ -/* - * This file is licensed under the Affero General Public License (AGPL) version 3. - * - * Copyright (C) 2026 Element Creations Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * See the GNU Affero General Public License for more details: - * . - * - */ - -use crate::db::python_db_pool::DatabasePool as PythonDatabasePool; - -/// A [`tokio_postgres::Transaction`] looking thing that we can use on the Rust side to -/// interact with the database -pub trait Transaction { - pub fn query(&self, sql: &str, args: &[&str]) -> None { - todo!("TODO"); - } -} - -/// Database access backed by the database pool we already have in the Python side of Synapse -struct SynapsePythonTransaction { - db_pool: PythonDatabasePool, -} - -impl Transaction for SynapsePythonTransaction { - fn query(&self, sql: &str, args: &[&str]) -> None { - todo!("TODO"); - } -} - -/// Native Rust database access backed by `tokio-postgres` (for use in synapse-rust-apps) -// struct TokioPostgresTransaction { -// db_pool: bb8::Pool>, -// } - -// impl Transaction for TokioPostgresTransaction { -// fn query(&self, sql: &str, args: &[&str]) -> None { -// todo!("TODO"); - -// // TODO: Set isolation level - -// let mut conn = self -// .db_pool -// .get() -// .instrument(tracing::info_span!("acquire database connection")) -// .await -// .map_err(|e| { -// sentry::capture_error(&e); -// tracing::error!( -// error = e.to_string(), -// "Failed to acquire database connection" -// ); -// anyhow::anyhow!("Failed to acquire database connection: {e}") -// })?; - -// let txn = conn -// .transaction() -// .instrument(tracing::info_span!("start transaction")) -// .await -// .context("Failed to start transaction")?; - -// let rows = txn.query(sql, args).await?; - -// rows -// } -// } diff --git a/rust/src/storage/db/mod.rs b/rust/src/storage/db/mod.rs index 70319bc960e..7c6c4fbb9a6 100644 --- a/rust/src/storage/db/mod.rs +++ b/rust/src/storage/db/mod.rs @@ -14,5 +14,15 @@ */ pub mod python_db_pool; +pub mod rust_db_pool; -pub mod db; +pub trait DatabasePool { + async fn get_transaction(&self, description: &str) -> dyn Transaction; +} + +/// A [`tokio_postgres::Transaction`] looking thing that we can use on the Rust side to +/// interact with the database +pub trait Transaction { + async fn query(&self, sql: &str, args: &[&str]) -> (); + async fn commit(&self) -> (); +} diff --git a/rust/src/storage/db/python_db_pool.rs b/rust/src/storage/db/python_db_pool.rs index 17010a8b47e..979b8069aa0 100644 --- a/rust/src/storage/db/python_db_pool.rs +++ b/rust/src/storage/db/python_db_pool.rs @@ -15,6 +15,8 @@ use pyo3::{intern, prelude::*}; +use crate::storage::db::{DatabasePool, Transaction}; + /// The database engines we support in the Python side of Synapse #[derive(Copy, Clone, Debug)] pub enum DatabaseEngine { @@ -32,59 +34,79 @@ impl DatabaseEngine { } } -/// Wrapper for a `DatabasePool` from the Python side of Synapse. -pub struct DatabasePool<'py> { - /// The underlying `DatabasePool` - raw: Bound<'py, PyAny>, -} +pub struct PythonDatabasePool {} -impl<'py> DatabasePool<'py> { - pub fn get_transaction(&mut self, description: &str) -> LoggingTransactionWrapper { - todo!("TODO"); +impl DatabasePool for PythonDatabasePool { + pub fn get_transaction(&self, description: &str) -> dyn Transaction { + todo!("..."); // let execute_fn = self.raw.getattr(intern!(self.raw.py(), "runInteraction"))?; // execute_fn.call1((sql, args))?; // Ok(()) } } +fn detect_engine(txn_py: &Bound<'_, PyAny>) -> PyResult { + let name = txn_py + .getattr("database_engine") + .expect("`LoggingTransaction` must have `database_engine` attr") + .get_type() + .name() + .expect("`database_engine` type must have a name") + .to_str() + .expect("`database_engine` type name must be valid UTF-8") + .to_owned(); + + Ok(match name.as_str() { + "PostgresEngine" => DatabaseEngine::Postgres, + "Sqlite3Engine" => DatabaseEngine::Sqlite, + other => unimplemented!( + "Unknown database engine {other:?}. This is a Synapse programming error." + ), + }) +} + /// Wrapper for a `LoggingTransaction` from the Python side of Synapse. -pub struct LoggingTransactionWrapper<'py> { +/// +/// Holds no `'py` lifetime so it can be stored and moved freely across threads. +/// Use [`execute`](Self::execute) (or other methods) while holding the GIL. +pub struct LoggingTransactionWrapper { /// The underlying `LoggingTransaction` - raw: Bound<'py, PyAny>, + raw: Py, - /// Dissambiguate which underyling database engine we're working with - database_engine: DatabaseEngine, + /// Disambiguate which underlying database engine we're working with + pub database_engine: DatabaseEngine, } -impl<'py> FromPyObject<'_, 'py> for LoggingTransactionWrapper<'py> { +impl<'a, 'py> FromPyObject<'a, 'py> for LoggingTransactionWrapper { type Error = PyErr; - /// From Python `LoggingTransaction` - fn extract(logging_transaction_python_object: Borrowed<'_, 'py, PyAny>) -> PyResult { - let database_engine = match logging_transaction_python_object - .getattr("database_engine") - .expect("Expected the Python object you passed to be `LoggingTransaction` which should have `database_engine` attr") - .get_type() - .name() - .expect("Expected `LoggingTransaction.database_engine` to have a type name") - .to_str() - .expect("Expected to be able to convert the `LoggingTransaction.database_engine` type name to a string") - { - "PostgresEngine" => DatabaseEngine::Postgres, - "Sqlite3Engine" => DatabaseEngine::Sqlite, - other => unimplemented!("Unknown database engine {other:?}. This is a Synapse programming error."), - }; + /// Extract from a Python `LoggingTransaction` passed as an argument. + /// + /// The resulting wrapper has `done_tx = None`; Python owns the transaction lifetime. + fn extract(obj: Borrowed<'a, 'py, PyAny>) -> PyResult { + let database_engine = detect_engine(&obj.to_owned())?; Ok(Self { - raw: logging_transaction_python_object.cast()?.to_owned(), + raw: obj.to_owned().unbind(), database_engine, }) } } -impl<'py> LoggingTransactionWrapper<'py> { - pub fn execute(&mut self, sql: &str, args: &'py Bound<'py, PyAny>) -> PyResult<()> { - let execute_fn = self.raw.getattr(intern!(self.raw.py(), "execute"))?; +impl LoggingTransactionWrapper { + pub fn execute<'py>( + &mut self, + py: Python<'py>, + sql: &str, + args: &Bound<'py, PyAny>, + ) -> PyResult<()> { + let execute_fn = self.raw.bind(py).getattr(intern!(py, "execute"))?; execute_fn.call1((sql, args))?; Ok(()) } } + +impl Transaction for LoggingTransactionWrapper { + fn query(&self, sql: &str, args: &[&str]) -> () { + self.execute(sql, args); + } +} diff --git a/rust/src/storage/db/rust_db_pool.rs b/rust/src/storage/db/rust_db_pool.rs new file mode 100644 index 00000000000..83206bb5fc8 --- /dev/null +++ b/rust/src/storage/db/rust_db_pool.rs @@ -0,0 +1,71 @@ +/* + * This file is licensed under the Affero General Public License (AGPL) version 3. + * + * Copyright (C) 2026 Element Creations Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * See the GNU Affero General Public License for more details: + * . + * + */ + + +// TODO: remove. This is just here to make sure our `DatabasePool`/`Transaction` +// interfaces are compatible with `tokio-postgres`. + +use anyhow::Context; +use bb8_postgres::{tokio_postgres::Row, PostgresConnectionManager}; +use postgres_native_tls::MakeTlsConnector; + +use crate::storage::db::{DatabasePool, Transaction}; + +/// Native Rust database access backed by `tokio-postgres` (for use in synapse-rust-apps) +pub struct RustDatabasePool { + db_pool: bb8::Pool>, +} + +impl DatabasePool for RustDatabasePool { + async fn get_transaction(&self, description: &str) -> dyn Transaction { + let mut conn = self + .db_pool + .get() + // .instrument(tracing::info_span!("acquire database connection")) + .await + .context("Failed to acquire database connection")?; + + let txn = conn + .transaction() + // .instrument(tracing::info_span!("start transaction")) + .await + .context("Failed to start transaction")?; + + // TODO: Set isolation level + txn + } +} + +struct TokioPostgresTransaction<'a> { + txn: bb8_postgres::tokio_postgres::Transaction<'a>, +} + +impl Transaction for TokioPostgresTransaction<'_> { + async fn query(&self, sql: &str, args: &[&str]) -> () { + todo!("TODO"); + + let rows = self.txn.query(sql, args).await?; + + rows + } + + async fn commit(&self) -> () { + self.txn + .commit() + // .instrument(tracing::info_span!("commit transaction")) + .await + .context("Failed to commit transaction")?; + } +} diff --git a/rust/src/storage/store.rs b/rust/src/storage/store.rs index ffdefc2f906..96fe803c3dc 100644 --- a/rust/src/storage/store.rs +++ b/rust/src/storage/store.rs @@ -13,6 +13,10 @@ * */ +use sha2::digest::typenum::Len; + +use crate::{config::SynapseConfig, storage::db::DatabasePool}; + /// Currently supported per-user features pub enum PerUserExperimentalFeature { MSC3881, @@ -20,7 +24,20 @@ pub enum PerUserExperimentalFeature { MSC4222, } -pub struct Store {} +impl PerUserExperimentalFeature { + pub fn is_globally_enabled(&self, config: SynapseConfig) -> bool { + match self { + PerUserExperimentalFeature::MSC3881 => config.experimental.msc3881_enabled, + PerUserExperimentalFeature::MSC3575 => config.experimental.msc3575_enabled, + PerUserExperimentalFeature::MSC4222 => config.experimental.msc4222_enabled, + } + } +} + +pub struct Store { + config: SynapseConfig, + db_pool: dyn DatabasePool, +} impl Store { pub async fn is_feature_enabled( @@ -28,6 +45,28 @@ impl Store { user_id: &str, feature: PerUserExperimentalFeature, ) -> Result { - todo!("..."); + if feature.is_globally_enabled(self.config) { + return Ok(true); + } + + let txn = self.db_pool.get_transaction("is_feature_enabled").await; + let rows = txn + .query( + r#" + SELECT enabled + FROM per_user_experimental_features + WHERE user_id = ? AND feature = ? + "#, + &[user_id, feature], + ) + .await; + + match (rows.len(), rows.first()) { + (1, Some(enabled)) => Ok(enabled), + (0, None) => Ok(false), + _ => { + panic!("Synapse programming error"); + } + } } } diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index d1f47ff817a..d8b0a0545db 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -25,13 +25,11 @@ import re from typing import TYPE_CHECKING -from synapse.api.constants import RoomCreationPreset from synapse.http.server import HttpServer from synapse.http.servlet import RestServlet from synapse.http.site import SynapseRequest -from synapse.rest.admin.experimental_features import ExperimentalFeature -from synapse.types import JsonDict from synapse.synapse_rust.handlers.versions import get_versions +from synapse.types import JsonDict if TYPE_CHECKING: from synapse.server import HomeServer @@ -48,6 +46,7 @@ def __init__(self, hs: "HomeServer"): self.config = hs.config self.auth = hs.get_auth() self.store = hs.get_datastores().main + self.rust_handlers = hs.get_rust_handlers() async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]: user_id = None @@ -80,7 +79,9 @@ async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]: # authenticated responses are not served from cache. request.setHeader(b"Vary", b"Authorization") - versions_response_body = await get_versions(user_id, self.config) + versions_response_body = await self.rust_handlers.versions.get_versions( + user_id, self.config + ) return ( 200, diff --git a/synapse/server.py b/synapse/server.py index 8bf19f11b5d..f46f5845e4b 100644 --- a/synapse/server.py +++ b/synapse/server.py @@ -963,6 +963,10 @@ def get_send_email_handler(self) -> SendEmailHandler: def get_set_password_handler(self) -> SetPasswordHandler: return SetPasswordHandler(self) + @cache_in_self + def get_rust_handlers(self) -> RustHandlers: + return RustHandlers(self) + @cache_in_self def get_event_sources(self) -> EventSources: return EventSources(self) From 66737780dd14c5ade81e03a8a0d1a1138eb8979c Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 4 Jun 2026 18:44:04 -0500 Subject: [PATCH 11/24] Slow going --- Cargo.lock | 1 + rust/Cargo.toml | 1 + rust/src/storage/db/mod.rs | 17 ++++++-- rust/src/storage/db/python_db_pool.rs | 62 +++++++++++++++++++++------ rust/src/storage/db/rust_db_pool.rs | 25 ++++++----- 5 files changed, 79 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5759240eb95..5faf827e1db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1831,6 +1831,7 @@ name = "synapse" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "base64", "bb8", "bb8-postgres", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index dfd6d87b618..1e16b8769c8 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -22,6 +22,7 @@ crate-type = ["lib", "cdylib"] name = "synapse.synapse_rust" [dependencies] +async-trait = "0.1.89" anyhow = "1.0.63" base64 = "0.22.1" bytes = "1.6.0" diff --git a/rust/src/storage/db/mod.rs b/rust/src/storage/db/mod.rs index 7c6c4fbb9a6..14f4a2692ec 100644 --- a/rust/src/storage/db/mod.rs +++ b/rust/src/storage/db/mod.rs @@ -16,13 +16,24 @@ pub mod python_db_pool; pub mod rust_db_pool; +#[async_trait::async_trait] pub trait DatabasePool { - async fn get_transaction(&self, description: &str) -> dyn Transaction; + /// TODO + /// + /// Arguments: + /// description of the transaction, for logging and metrics + async fn get_transaction( + &self, + description: &str, + ) -> Result, anyhow::Error>; } /// A [`tokio_postgres::Transaction`] looking thing that we can use on the Rust side to /// interact with the database +#[async_trait::async_trait] pub trait Transaction { - async fn query(&self, sql: &str, args: &[&str]) -> (); - async fn commit(&self) -> (); + async fn query(&self, sql: &str, args: &[&str]) -> Vec; + async fn commit(self) -> Result<(), anyhow::Error>; } + +pub type Row = Vec; diff --git a/rust/src/storage/db/python_db_pool.rs b/rust/src/storage/db/python_db_pool.rs index 979b8069aa0..a2636716cfe 100644 --- a/rust/src/storage/db/python_db_pool.rs +++ b/rust/src/storage/db/python_db_pool.rs @@ -15,7 +15,7 @@ use pyo3::{intern, prelude::*}; -use crate::storage::db::{DatabasePool, Transaction}; +use crate::storage::db::{DatabasePool, Row, Transaction}; /// The database engines we support in the Python side of Synapse #[derive(Copy, Clone, Debug)] @@ -34,14 +34,40 @@ impl DatabaseEngine { } } -pub struct PythonDatabasePool {} +/// Wrapper for a `DatabasePool` from the Python side of Synapse. +pub struct PythonDatabasePool { + /// The underlying `DatabasePool` + database_pool_py: Bound<'py, PyAny>, +} +#[async_trait::async_trait] impl DatabasePool for PythonDatabasePool { - pub fn get_transaction(&self, description: &str) -> dyn Transaction { - todo!("..."); - // let execute_fn = self.raw.getattr(intern!(self.raw.py(), "runInteraction"))?; - // execute_fn.call1((sql, args))?; - // Ok(()) + async fn get_transaction( + &self, + description: &str, + ) -> Result, anyhow::Error> { + // Synapse has built-in retry functionality and can call this function multiple + // times under certain failure modes. Normally, everything in the transaction + // happens in the callback but since we have a little bit of a different API + // surface, we instead extract the transaction for us to use outside. + // + // Re-using `runInteraction`, means we get all of the logging, metrics, etc for + // free. + let callback_func = + PyCFunction::new_closure(py, None, None, move |args, _| -> PyResult> { + // TODO: Error if already called + + let py = args.py(); + let txn_py = args.get_item(0)?; + txn + }); + + let execute_fn = self + .database_pool_py + .getattr(intern!(self.database_pool_py.py(), "runInteraction"))?; + execute_fn.call1((description, callback_func))?; + + Ok(Box::new(txn)) } } @@ -71,7 +97,7 @@ fn detect_engine(txn_py: &Bound<'_, PyAny>) -> PyResult { /// Use [`execute`](Self::execute) (or other methods) while holding the GIL. pub struct LoggingTransactionWrapper { /// The underlying `LoggingTransaction` - raw: Py, + logging_transaction_py: Py, /// Disambiguate which underlying database engine we're working with pub database_engine: DatabaseEngine, @@ -83,10 +109,10 @@ impl<'a, 'py> FromPyObject<'a, 'py> for LoggingTransactionWrapper { /// Extract from a Python `LoggingTransaction` passed as an argument. /// /// The resulting wrapper has `done_tx = None`; Python owns the transaction lifetime. - fn extract(obj: Borrowed<'a, 'py, PyAny>) -> PyResult { - let database_engine = detect_engine(&obj.to_owned())?; + fn extract(logging_transaction_py: Borrowed<'a, 'py, PyAny>) -> PyResult { + let database_engine = detect_engine(&logging_transaction_py.to_owned())?; Ok(Self { - raw: obj.to_owned().unbind(), + logging_transaction_py: logging_transaction_py.to_owned().unbind(), database_engine, }) } @@ -99,14 +125,22 @@ impl LoggingTransactionWrapper { sql: &str, args: &Bound<'py, PyAny>, ) -> PyResult<()> { - let execute_fn = self.raw.bind(py).getattr(intern!(py, "execute"))?; + let execute_fn = self + .logging_transaction_py + .bind(py) + .getattr(intern!(py, "execute"))?; execute_fn.call1((sql, args))?; Ok(()) } } +#[async_trait::async_trait] impl Transaction for LoggingTransactionWrapper { - fn query(&self, sql: &str, args: &[&str]) -> () { - self.execute(sql, args); + async fn query(&self, sql: &str, args: &[&str]) -> Vec { + self.execute(sql, args).await; + } + + async fn commit(&self) -> Result<(), anyhow::Error> { + // In Synapse, `commit` is part of `LoggingDatabaseConnection` } } diff --git a/rust/src/storage/db/rust_db_pool.rs b/rust/src/storage/db/rust_db_pool.rs index 83206bb5fc8..c12cd811997 100644 --- a/rust/src/storage/db/rust_db_pool.rs +++ b/rust/src/storage/db/rust_db_pool.rs @@ -13,23 +13,26 @@ * */ - // TODO: remove. This is just here to make sure our `DatabasePool`/`Transaction` // interfaces are compatible with `tokio-postgres`. use anyhow::Context; -use bb8_postgres::{tokio_postgres::Row, PostgresConnectionManager}; +use bb8_postgres::PostgresConnectionManager; use postgres_native_tls::MakeTlsConnector; -use crate::storage::db::{DatabasePool, Transaction}; +use crate::storage::db::{DatabasePool, Row, Transaction}; /// Native Rust database access backed by `tokio-postgres` (for use in synapse-rust-apps) pub struct RustDatabasePool { db_pool: bb8::Pool>, } +#[async_trait::async_trait] impl DatabasePool for RustDatabasePool { - async fn get_transaction(&self, description: &str) -> dyn Transaction { + async fn get_transaction( + &self, + _description: &str, + ) -> Result, anyhow::Error> { let mut conn = self .db_pool .get() @@ -37,14 +40,15 @@ impl DatabasePool for RustDatabasePool { .await .context("Failed to acquire database connection")?; + + // TODO: Set repeatable-read isolation level (like Synapse) let txn = conn .transaction() // .instrument(tracing::info_span!("start transaction")) .await .context("Failed to start transaction")?; - // TODO: Set isolation level - txn + Ok(Box::new(TokioPostgresTransaction { txn })) } } @@ -52,20 +56,21 @@ struct TokioPostgresTransaction<'a> { txn: bb8_postgres::tokio_postgres::Transaction<'a>, } +#[async_trait::async_trait] impl Transaction for TokioPostgresTransaction<'_> { - async fn query(&self, sql: &str, args: &[&str]) -> () { - todo!("TODO"); + async fn query(&self, sql: &str, args: &[&str]) -> Vec { + // TODO: Convert `?` SQL param style to `tokio-postgres` compatible let rows = self.txn.query(sql, args).await?; rows } - async fn commit(&self) -> () { + async fn commit(self) -> Result<(), anyhow::Error> { self.txn .commit() // .instrument(tracing::info_span!("commit transaction")) .await - .context("Failed to commit transaction")?; + .context("Failed to commit transaction") } } From bdffe562ad945dd4ede30b5fd007209f25bfc2db Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 4 Jun 2026 19:21:08 -0500 Subject: [PATCH 12/24] Split connection vs transaction --- rust/src/storage/db/mod.rs | 10 ++- rust/src/storage/db/python_db_pool.rs | 104 +++++++++++++++++++++++--- rust/src/storage/db/rust_db_pool.rs | 26 +++++-- 3 files changed, 120 insertions(+), 20 deletions(-) diff --git a/rust/src/storage/db/mod.rs b/rust/src/storage/db/mod.rs index 14f4a2692ec..c09c3dba2de 100644 --- a/rust/src/storage/db/mod.rs +++ b/rust/src/storage/db/mod.rs @@ -18,6 +18,14 @@ pub mod rust_db_pool; #[async_trait::async_trait] pub trait DatabasePool { + /// TODO + async fn get_connection(&self) -> Result, anyhow::Error>; +} + +/// A `tokio_postgres` Connection looking thing that we can use on the Rust side to +/// interact with the database +#[async_trait::async_trait] +pub trait DatabaseConnection { /// TODO /// /// Arguments: @@ -32,7 +40,7 @@ pub trait DatabasePool { /// interact with the database #[async_trait::async_trait] pub trait Transaction { - async fn query(&self, sql: &str, args: &[&str]) -> Vec; + async fn query(&self, sql: &str, args: &[&str]) -> Result, anyhow::Error>; async fn commit(self) -> Result<(), anyhow::Error>; } diff --git a/rust/src/storage/db/python_db_pool.rs b/rust/src/storage/db/python_db_pool.rs index a2636716cfe..b284515f699 100644 --- a/rust/src/storage/db/python_db_pool.rs +++ b/rust/src/storage/db/python_db_pool.rs @@ -15,7 +15,7 @@ use pyo3::{intern, prelude::*}; -use crate::storage::db::{DatabasePool, Row, Transaction}; +use crate::storage::db::{DatabaseConnection, DatabasePool, Row, Transaction}; /// The database engines we support in the Python side of Synapse #[derive(Copy, Clone, Debug)] @@ -42,14 +42,48 @@ pub struct PythonDatabasePool { #[async_trait::async_trait] impl DatabasePool for PythonDatabasePool { + async fn get_connection(&self) -> Result, anyhow::Error> { + let callback_func = + PyCFunction::new_closure(py, None, None, move |args, _| -> PyResult> { + // TODO: Error if already called + + let py = args.py(); + // We found our `LoggingDatabaseConnection` + let conn_py = args.get_item(0)?; + tx.send(conn_py); + }); + + let execute_fn = self + .database_pool_py + .getattr(intern!(self.database_pool_py.py(), "runWithConnection"))?; + execute_fn.call1((callback_func,))?; + + Ok(Box::new(LoggingDatabaseConnectionWrapper { + database_pool_py: self, + logging_database_connection_py: conn_py, + })) + } +} + +/// Wrapper for a `LoggingDatabaseConnection` from the Python side of Synapse. +pub struct LoggingDatabaseConnectionWrapper { + /// The underlying `DatabasePool` + database_pool_py: Bound<'py, PyAny>, + /// The underlying `LoggingDatabaseConnection` + logging_database_connection_py: Py, +} + +#[async_trait::async_trait] +impl DatabaseConnection for LoggingDatabaseConnectionWrapper { async fn get_transaction( &self, description: &str, ) -> Result, anyhow::Error> { - // Synapse has built-in retry functionality and can call this function multiple - // times under certain failure modes. Normally, everything in the transaction - // happens in the callback but since we have a little bit of a different API - // surface, we instead extract the transaction for us to use outside. + // Synapse's `DatabasePool.new_transaction` has built-in retry functionality and + // can call this function multiple times under certain failure modes. Normally, + // everything in the transaction happens in the callback but since we have a + // little bit of a different API surface, we instead extract the transaction for + // us to use outside. // // Re-using `runInteraction`, means we get all of the logging, metrics, etc for // free. @@ -58,14 +92,23 @@ impl DatabasePool for PythonDatabasePool { // TODO: Error if already called let py = args.py(); - let txn_py = args.get_item(0)?; - txn + let txn_py: LoggingTransactionWrapper = args.get_item(0)?; + tx.send(txn_py); + + // Wait until we see the signal that we're `done_with_txn_rx` }); let execute_fn = self .database_pool_py - .getattr(intern!(self.database_pool_py.py(), "runInteraction"))?; - execute_fn.call1((description, callback_func))?; + .getattr(intern!(self.database_pool_py.py(), "new_transaction"))?; + execute_fn.call1(( + self.logging_database_connection_py, + description, + [], + [], + [], + callback_func, + ))?; Ok(Box::new(txn)) } @@ -93,6 +136,7 @@ fn detect_engine(txn_py: &Bound<'_, PyAny>) -> PyResult { /// Wrapper for a `LoggingTransaction` from the Python side of Synapse. /// +/// TODO: Verify if this is the correct approach or we should use`Bound<'py, PyAny>`: /// Holds no `'py` lifetime so it can be stored and moved freely across threads. /// Use [`execute`](Self::execute) (or other methods) while holding the GIL. pub struct LoggingTransactionWrapper { @@ -118,6 +162,26 @@ impl<'a, 'py> FromPyObject<'a, 'py> for LoggingTransactionWrapper { } } +pub trait ValidDatabaseFieldType {} +pub trait ValidDatabaseReturnType {} +impl ValidDatabaseFieldType for String {} +impl ValidDatabaseFieldType for usize {} +impl ValidDatabaseFieldType for Option {} +impl ValidDatabaseReturnType for (T0,) {} +impl ValidDatabaseReturnType for (T0, T1) {} +impl + ValidDatabaseReturnType for (T0, T1, T2) +{ +} +impl< + T0: ValidDatabaseFieldType, + T1: ValidDatabaseFieldType, + T2: ValidDatabaseFieldType, + T3: ValidDatabaseFieldType, + > ValidDatabaseReturnType for (T0, T1, T2, T3) +{ +} + impl LoggingTransactionWrapper { pub fn execute<'py>( &mut self, @@ -132,15 +196,33 @@ impl LoggingTransactionWrapper { execute_fn.call1((sql, args))?; Ok(()) } + + pub fn fetchall + ValidDatabaseReturnType>( + &mut self, + ) -> anyhow::Result> { + let fetch_fn = self + .logging_transaction_py + .getattr(intern!(self.logging_transaction_py.py(), "fetchall"))?; + Ok(fetch_fn.call0()?.extract()?) + } } #[async_trait::async_trait] impl Transaction for LoggingTransactionWrapper { - async fn query(&self, sql: &str, args: &[&str]) -> Vec { + async fn query(&self, sql: &str, args: &[&str]) -> Result, anyhow::Error> { self.execute(sql, args).await; + let rows = self.fetchall(sql, args).await?; + + Ok(rows) } async fn commit(&self) -> Result<(), anyhow::Error> { - // In Synapse, `commit` is part of `LoggingDatabaseConnection` + // In Synapse, `commit` is part of `LoggingDatabaseConnection` and will be + // called as part of the `new_transaction(...)` machinery we used to create the + // transaction in the first place. + // + // We just need to send the proper signal which will finish the txn callback and + // have it run. + done_with_txn_tx.send(()) } } diff --git a/rust/src/storage/db/rust_db_pool.rs b/rust/src/storage/db/rust_db_pool.rs index c12cd811997..bdf7ed375b6 100644 --- a/rust/src/storage/db/rust_db_pool.rs +++ b/rust/src/storage/db/rust_db_pool.rs @@ -20,7 +20,7 @@ use anyhow::Context; use bb8_postgres::PostgresConnectionManager; use postgres_native_tls::MakeTlsConnector; -use crate::storage::db::{DatabasePool, Row, Transaction}; +use crate::storage::db::{DatabaseConnection, DatabasePool, Row, Transaction}; /// Native Rust database access backed by `tokio-postgres` (for use in synapse-rust-apps) pub struct RustDatabasePool { @@ -29,10 +29,7 @@ pub struct RustDatabasePool { #[async_trait::async_trait] impl DatabasePool for RustDatabasePool { - async fn get_transaction( - &self, - _description: &str, - ) -> Result, anyhow::Error> { + async fn get_connection(&self) -> Result, anyhow::Error> { let mut conn = self .db_pool .get() @@ -40,9 +37,22 @@ impl DatabasePool for RustDatabasePool { .await .context("Failed to acquire database connection")?; + Ok(Box::new(RustConnection { connection: conn })) + } +} + +pub struct RustConnection<'a> { + connection: bb8::PooledConnection<'a, PostgresConnectionManager>, +} +impl DatabaseConnection for RustConnection<'_> { + async fn get_transaction( + &self, + _description: &str, + ) -> Result, anyhow::Error> { // TODO: Set repeatable-read isolation level (like Synapse) - let txn = conn + let txn = self + .connection .transaction() // .instrument(tracing::info_span!("start transaction")) .await @@ -58,12 +68,12 @@ struct TokioPostgresTransaction<'a> { #[async_trait::async_trait] impl Transaction for TokioPostgresTransaction<'_> { - async fn query(&self, sql: &str, args: &[&str]) -> Vec { + async fn query(&self, sql: &str, args: &[&str]) -> Result, anyhow::Error> { // TODO: Convert `?` SQL param style to `tokio-postgres` compatible let rows = self.txn.query(sql, args).await?; - rows + Ok(rows) } async fn commit(self) -> Result<(), anyhow::Error> { From 66a1886dc76753833888141dc1f9e9a4a267c5c3 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 14:11:06 -0500 Subject: [PATCH 13/24] `SynapseConfig` `FromPyObject` --- rust/src/config/mod.rs | 29 +++++++++------------------ rust/src/handlers/mod.rs | 23 ++++++++++++++++++--- rust/src/handlers/versions.rs | 13 +++++------- rust/src/storage/db/python_db_pool.rs | 2 ++ rust/src/storage/store.rs | 4 ++-- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/rust/src/config/mod.rs b/rust/src/config/mod.rs index 4f99f8ef600..65755befaaf 100644 --- a/rust/src/config/mod.rs +++ b/rust/src/config/mod.rs @@ -13,33 +13,22 @@ * */ -// use pyo3::PyAny; -// use pyo3::{intern, prelude::*}; -use serde::{Deserialize, Serialize}; +use pyo3::prelude::*; -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(FromPyObject)] pub struct SynapseConfig { pub experimental: ExperimentalConfig, } -// impl<'py> FromPyObject<'_, 'py> for SynapseConfig<'py> { -// type Error = PyErr; - -// /// From Python `LoggingTransaction` -// fn extract(config_python_object: Borrowed<'_, 'py, PyAny>) -> PyResult { -// todo!("..."); -// } +// #[derive(FromPyObject)] +// #[serde(rename_all = "snake_case")] +// pub enum RoomCreationPreset { +// PrviateChat, +// PublicChat, +// TrustedPrivateChat, // } -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub enum RoomCreationPreset { - PrviateChat, - PublicChat, - TrustedPrivateChat, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(FromPyObject)] pub struct ExperimentalConfig { pub msc3881_enabled: bool, pub msc3575_enabled: bool, diff --git a/rust/src/handlers/mod.rs b/rust/src/handlers/mod.rs index f03fdd37218..2c7d97c4ebd 100644 --- a/rust/src/handlers/mod.rs +++ b/rust/src/handlers/mod.rs @@ -19,6 +19,8 @@ use pyo3::{ Bound, PyResult, Python, }; +use crate::config::SynapseConfig; +use crate::storage::db::python_db_pool::PythonDatabasePool; use crate::storage::store::Store; pub mod versions; @@ -33,9 +35,24 @@ impl RustHandlers { #[new] #[pyo3(signature = (homeserver))] pub fn py_new(py: Python<'_>, homeserver: &Bound<'_, PyAny>) -> PyResult { - RustHandlers { - versions: versions::VersionsHandler { store: Store {} }, - } + let config: SynapseConfig = homeserver.getattr("config")?.extract()?; + + // hs.get_datastores().main.db_pool + let db_pool: PythonDatabasePool = homeserver + .call_method0("get_datastores")? + .into_pyobject(py) + .unwrap_infallible() + .unbind() + .getattr("main")? + .getattr("db_pool")? + .extract()?; + + Ok(RustHandlers { + versions: versions::VersionsHandler { + config, + store: Store { config, db_pool }, + }, + }) } } diff --git a/rust/src/handlers/versions.rs b/rust/src/handlers/versions.rs index de28eabf009..9ac848a0778 100644 --- a/rust/src/handlers/versions.rs +++ b/rust/src/handlers/versions.rs @@ -27,23 +27,20 @@ struct VersionsResponse { } pub struct VersionsHandler { - store: &Store, + pub config: SynapseConfig, + pub store: &Store, } impl VersionsHandler { /// Assemble a `/versions` response - async fn get_versions( - &self, - user_id: Option<&str>, - config: SynapseConfig, - ) -> Result { + async fn get_versions(&self, user_id: Option<&str>) -> Result { let msc3881_enabled = match user_id { Some(user_id) => { self.store .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3881) .await? } - None => PerUserExperimentalFeature::MSC3881.is_globally_enabled(config), + None => PerUserExperimentalFeature::MSC3881.is_globally_enabled(self.config), }; let msc3575_enabled = match user_id { @@ -52,7 +49,7 @@ impl VersionsHandler { .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3575) .await? } - None => PerUserExperimentalFeature::MSC3575.is_globally_enabled(config), + None => PerUserExperimentalFeature::MSC3575.is_globally_enabled(self.config), }; // TODO: Calculate these once since they shouldn't change after start-up. diff --git a/rust/src/storage/db/python_db_pool.rs b/rust/src/storage/db/python_db_pool.rs index b284515f699..b26445b3051 100644 --- a/rust/src/storage/db/python_db_pool.rs +++ b/rust/src/storage/db/python_db_pool.rs @@ -224,5 +224,7 @@ impl Transaction for LoggingTransactionWrapper { // We just need to send the proper signal which will finish the txn callback and // have it run. done_with_txn_tx.send(()) + // TODO: How can we guarantee that `commit` was run? Perhaps we have to wait for + // `new_transaction(...)` to complete successfully } } diff --git a/rust/src/storage/store.rs b/rust/src/storage/store.rs index 96fe803c3dc..6747b4abfcf 100644 --- a/rust/src/storage/store.rs +++ b/rust/src/storage/store.rs @@ -35,8 +35,8 @@ impl PerUserExperimentalFeature { } pub struct Store { - config: SynapseConfig, - db_pool: dyn DatabasePool, + pub config: SynapseConfig, + pub db_pool: dyn DatabasePool, } impl Store { From 7757712ebc6eff93c0f6a1403e0823c2cae2ecbb Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 14:32:01 -0500 Subject: [PATCH 14/24] Fix `db_pool` extraction --- rust/src/handlers/mod.rs | 2 +- rust/src/storage/db/python_db_pool.rs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/rust/src/handlers/mod.rs b/rust/src/handlers/mod.rs index 2c7d97c4ebd..4e9272734b5 100644 --- a/rust/src/handlers/mod.rs +++ b/rust/src/handlers/mod.rs @@ -22,6 +22,7 @@ use pyo3::{ use crate::config::SynapseConfig; use crate::storage::db::python_db_pool::PythonDatabasePool; use crate::storage::store::Store; +use crate::UnwrapInfallible; pub mod versions; @@ -42,7 +43,6 @@ impl RustHandlers { .call_method0("get_datastores")? .into_pyobject(py) .unwrap_infallible() - .unbind() .getattr("main")? .getattr("db_pool")? .extract()?; diff --git a/rust/src/storage/db/python_db_pool.rs b/rust/src/storage/db/python_db_pool.rs index b26445b3051..44c4ddad01a 100644 --- a/rust/src/storage/db/python_db_pool.rs +++ b/rust/src/storage/db/python_db_pool.rs @@ -37,7 +37,18 @@ impl DatabaseEngine { /// Wrapper for a `DatabasePool` from the Python side of Synapse. pub struct PythonDatabasePool { /// The underlying `DatabasePool` - database_pool_py: Bound<'py, PyAny>, + database_pool_py: Py, +} + +impl<'a, 'py> FromPyObject<'a, 'py> for PythonDatabasePool { + type Error = PyErr; + + /// Extract from a Python `DatabasePool` passed as an argument. + fn extract(database_pool_py: Borrowed<'a, 'py, PyAny>) -> PyResult { + Ok(Self { + database_pool_py: database_pool_py.to_owned().unbind(), + }) + } } #[async_trait::async_trait] @@ -151,8 +162,6 @@ impl<'a, 'py> FromPyObject<'a, 'py> for LoggingTransactionWrapper { type Error = PyErr; /// Extract from a Python `LoggingTransaction` passed as an argument. - /// - /// The resulting wrapper has `done_tx = None`; Python owns the transaction lifetime. fn extract(logging_transaction_py: Borrowed<'a, 'py, PyAny>) -> PyResult { let database_engine = detect_engine(&logging_transaction_py.to_owned())?; Ok(Self { From 7f608b4920ed487a1159a9da5240341485ddabf0 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 14:39:59 -0500 Subject: [PATCH 15/24] Use `Arc` to share --- rust/src/handlers/mod.rs | 7 ++++++- rust/src/handlers/versions.rs | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/rust/src/handlers/mod.rs b/rust/src/handlers/mod.rs index 4e9272734b5..6f56bb30f9c 100644 --- a/rust/src/handlers/mod.rs +++ b/rust/src/handlers/mod.rs @@ -13,6 +13,8 @@ * */ +use std::sync::Arc; + use pyo3::{ prelude::*, types::{PyAnyMethods, PyModule, PyModuleMethods}, @@ -47,10 +49,13 @@ impl RustHandlers { .getattr("db_pool")? .extract()?; + // Store is shared across all of the handlers so let's use an `Arc` + let store = Arc::new(Store { config, db_pool }); + Ok(RustHandlers { versions: versions::VersionsHandler { config, - store: Store { config, db_pool }, + store: Arc::clone(&store), }, }) } diff --git a/rust/src/handlers/versions.rs b/rust/src/handlers/versions.rs index 9ac848a0778..32168488396 100644 --- a/rust/src/handlers/versions.rs +++ b/rust/src/handlers/versions.rs @@ -14,6 +14,7 @@ */ use serde::{Deserialize, Serialize}; +use std::sync::Arc; use crate::config::SynapseConfig; use crate::storage::store::{PerUserExperimentalFeature, Store}; @@ -28,7 +29,7 @@ struct VersionsResponse { pub struct VersionsHandler { pub config: SynapseConfig, - pub store: &Store, + pub store: Arc, } impl VersionsHandler { From 6a1acef43d19c6026f7f5e7c966ba359665aa4fa Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 14:46:17 -0500 Subject: [PATCH 16/24] Use `Box` so the size is known and because the `db_pool` is not shared ``` error[E0277]: the size for values of type `(dyn storage::db::DatabasePool + 'static)` cannot be known at compilation time --> rust/src/handlers/mod.rs:53:30 | 53 | let store = Arc::new(Store { config, db_pool }); | ^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | = help: within `storage::store::Store`, the trait `std::marker::Sized` is not implemented for `(dyn storage::db::DatabasePool + 'static)` note: required because it appears within the type `storage::store::Store` --> rust/src/storage/store.rs:37:12 | 37 | pub struct Store { | ^^^^^ = note: structs must have a statically known size to be initialized ``` --- rust/src/handlers/mod.rs | 5 ++++- rust/src/storage/store.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rust/src/handlers/mod.rs b/rust/src/handlers/mod.rs index 6f56bb30f9c..3036de72418 100644 --- a/rust/src/handlers/mod.rs +++ b/rust/src/handlers/mod.rs @@ -50,7 +50,10 @@ impl RustHandlers { .extract()?; // Store is shared across all of the handlers so let's use an `Arc` - let store = Arc::new(Store { config, db_pool }); + let store = Arc::new(Store { + config, + db_pool: Box::new(db_pool), + }); Ok(RustHandlers { versions: versions::VersionsHandler { diff --git a/rust/src/storage/store.rs b/rust/src/storage/store.rs index 6747b4abfcf..e7952e48325 100644 --- a/rust/src/storage/store.rs +++ b/rust/src/storage/store.rs @@ -36,7 +36,7 @@ impl PerUserExperimentalFeature { pub struct Store { pub config: SynapseConfig, - pub db_pool: dyn DatabasePool, + pub db_pool: Box, } impl Store { From 7a15f0185c5d59f6d4611d4676fb77fe325b64f6 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 14:48:52 -0500 Subject: [PATCH 17/24] Using `Send + Sync` traits so this can stored in the `#[pyclass]` just fine ``` error[E0277]: `(dyn storage::db::DatabasePool + 'static)` cannot be shared between threads safely --> rust/src/handlers/mod.rs:32:8 | 32 | struct RustHandlers { | ^^^^^^^^^^^^ `(dyn storage::db::DatabasePool + 'static)` cannot be shared between threads safely | = help: the trait `std::marker::Sync` is not implemented for `(dyn storage::db::DatabasePool + 'static)` = note: required for `std::ptr::Unique<(dyn storage::db::DatabasePool + 'static)>` to implement `std::marker::Sync` note: required because it appears within the type `std::boxed::Box<(dyn storage::db::DatabasePool + 'static)>` --> /home/eric/.rustup/toolchains/1.91.1-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:231:12 | 231 | pub struct Box< | ^^^ note: required because it appears within the type `storage::store::Store` --> rust/src/storage/store.rs:37:12 | 37 | pub struct Store { | ^^^^^ = note: required for `std::sync::Arc` to implement `std::marker::Send` note: required because it appears within the type `handlers::versions::VersionsHandler` --> rust/src/handlers/versions.rs:30:12 | 30 | pub struct VersionsHandler { | ^^^^^^^^^^^^^^^ note: required because it appears within the type `handlers::RustHandlers` --> rust/src/handlers/mod.rs:32:8 | 32 | struct RustHandlers { | ^^^^^^^^^^^^ note: required by a bound in `pyo3::impl_::pyclass::assertions::assert_pyclass_send_sync` --> /home/eric/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pyo3-0.28.3/src/impl_/pyclass/assertions.rs:6:8 | 4 | pub const fn assert_pyclass_send_sync() | ------------------------ required by a bound in this function 5 | where 6 | T: Send + Sync, | ^^^^ required by this bound in `assert_pyclass_send_sync` ``` --- rust/src/storage/db/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/src/storage/db/mod.rs b/rust/src/storage/db/mod.rs index c09c3dba2de..662fe8d14fc 100644 --- a/rust/src/storage/db/mod.rs +++ b/rust/src/storage/db/mod.rs @@ -16,8 +16,9 @@ pub mod python_db_pool; pub mod rust_db_pool; +// Using `Send + Sync` traits so this can stored in the `#[pyclass]` just fine #[async_trait::async_trait] -pub trait DatabasePool { +pub trait DatabasePool: Send + Sync { /// TODO async fn get_connection(&self) -> Result, anyhow::Error>; } From 24e5aa1026fa3184b4dc778c4e8cf1417593d2d5 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 14:50:15 -0500 Subject: [PATCH 18/24] Clone `config` --- rust/src/config/mod.rs | 4 ++-- rust/src/handlers/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/src/config/mod.rs b/rust/src/config/mod.rs index 65755befaaf..763b9fcb1f1 100644 --- a/rust/src/config/mod.rs +++ b/rust/src/config/mod.rs @@ -15,7 +15,7 @@ use pyo3::prelude::*; -#[derive(FromPyObject)] +#[derive(FromPyObject, Clone)] pub struct SynapseConfig { pub experimental: ExperimentalConfig, } @@ -28,7 +28,7 @@ pub struct SynapseConfig { // TrustedPrivateChat, // } -#[derive(FromPyObject)] +#[derive(FromPyObject, Clone)] pub struct ExperimentalConfig { pub msc3881_enabled: bool, pub msc3575_enabled: bool, diff --git a/rust/src/handlers/mod.rs b/rust/src/handlers/mod.rs index 3036de72418..a2a0d6af123 100644 --- a/rust/src/handlers/mod.rs +++ b/rust/src/handlers/mod.rs @@ -51,13 +51,13 @@ impl RustHandlers { // Store is shared across all of the handlers so let's use an `Arc` let store = Arc::new(Store { - config, + config: config.clone(), db_pool: Box::new(db_pool), }); Ok(RustHandlers { versions: versions::VersionsHandler { - config, + config: config.clone(), store: Arc::clone(&store), }, }) From fdf809bd4922a1f51055a74451e93ec50bbf7247 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 14:50:51 -0500 Subject: [PATCH 19/24] No need to move `config` --- rust/src/handlers/versions.rs | 4 ++-- rust/src/storage/store.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/src/handlers/versions.rs b/rust/src/handlers/versions.rs index 32168488396..322d3a06205 100644 --- a/rust/src/handlers/versions.rs +++ b/rust/src/handlers/versions.rs @@ -41,7 +41,7 @@ impl VersionsHandler { .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3881) .await? } - None => PerUserExperimentalFeature::MSC3881.is_globally_enabled(self.config), + None => PerUserExperimentalFeature::MSC3881.is_globally_enabled(&self.config), }; let msc3575_enabled = match user_id { @@ -50,7 +50,7 @@ impl VersionsHandler { .is_feature_enabled(user_id, PerUserExperimentalFeature::MSC3575) .await? } - None => PerUserExperimentalFeature::MSC3575.is_globally_enabled(self.config), + None => PerUserExperimentalFeature::MSC3575.is_globally_enabled(&self.config), }; // TODO: Calculate these once since they shouldn't change after start-up. diff --git a/rust/src/storage/store.rs b/rust/src/storage/store.rs index e7952e48325..041403f39f6 100644 --- a/rust/src/storage/store.rs +++ b/rust/src/storage/store.rs @@ -25,7 +25,7 @@ pub enum PerUserExperimentalFeature { } impl PerUserExperimentalFeature { - pub fn is_globally_enabled(&self, config: SynapseConfig) -> bool { + pub fn is_globally_enabled(&self, config: &SynapseConfig) -> bool { match self { PerUserExperimentalFeature::MSC3881 => config.experimental.msc3881_enabled, PerUserExperimentalFeature::MSC3575 => config.experimental.msc3575_enabled, From 8672bb4be350b7355ec4cef2f7a18a11fbef5de5 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 15:25:20 -0500 Subject: [PATCH 20/24] Better figure out `Bound<'py, PyAny>` vs `Py` Docs: https://pyo3.rs/v0.28.3/types.html --- rust/src/handlers/mod.rs | 4 +-- rust/src/storage/db/python_db_pool.rs | 50 ++++++++++++++++++--------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/rust/src/handlers/mod.rs b/rust/src/handlers/mod.rs index a2a0d6af123..cc650a9d658 100644 --- a/rust/src/handlers/mod.rs +++ b/rust/src/handlers/mod.rs @@ -22,7 +22,7 @@ use pyo3::{ }; use crate::config::SynapseConfig; -use crate::storage::db::python_db_pool::PythonDatabasePool; +use crate::storage::db::python_db_pool::PythonDatabasePoolWrapper; use crate::storage::store::Store; use crate::UnwrapInfallible; @@ -41,7 +41,7 @@ impl RustHandlers { let config: SynapseConfig = homeserver.getattr("config")?.extract()?; // hs.get_datastores().main.db_pool - let db_pool: PythonDatabasePool = homeserver + let db_pool: PythonDatabasePoolWrapper = homeserver .call_method0("get_datastores")? .into_pyobject(py) .unwrap_infallible() diff --git a/rust/src/storage/db/python_db_pool.rs b/rust/src/storage/db/python_db_pool.rs index 44c4ddad01a..9a29d3f0bdd 100644 --- a/rust/src/storage/db/python_db_pool.rs +++ b/rust/src/storage/db/python_db_pool.rs @@ -13,7 +13,12 @@ * */ -use pyo3::{intern, prelude::*}; +//! We have these three main classes: +//! - Database pool [`PythonDatabasePoolWrapper`] which creates +//! - connections [`LoggingDatabaseConnectionWrapper`] which creates +//! - transactions [`LoggingTransactionWrapper`] + +use pyo3::{intern, prelude::*, types::PyCFunction}; use crate::storage::db::{DatabaseConnection, DatabasePool, Row, Transaction}; @@ -35,12 +40,12 @@ impl DatabaseEngine { } /// Wrapper for a `DatabasePool` from the Python side of Synapse. -pub struct PythonDatabasePool { +pub struct PythonDatabasePoolWrapper { /// The underlying `DatabasePool` database_pool_py: Py, } -impl<'a, 'py> FromPyObject<'a, 'py> for PythonDatabasePool { +impl<'a, 'py> FromPyObject<'a, 'py> for PythonDatabasePoolWrapper { type Error = PyErr; /// Extract from a Python `DatabasePool` passed as an argument. @@ -52,7 +57,7 @@ impl<'a, 'py> FromPyObject<'a, 'py> for PythonDatabasePool { } #[async_trait::async_trait] -impl DatabasePool for PythonDatabasePool { +impl DatabasePool for PythonDatabasePoolWrapper { async fn get_connection(&self) -> Result, anyhow::Error> { let callback_func = PyCFunction::new_closure(py, None, None, move |args, _| -> PyResult> { @@ -61,7 +66,7 @@ impl DatabasePool for PythonDatabasePool { let py = args.py(); // We found our `LoggingDatabaseConnection` let conn_py = args.get_item(0)?; - tx.send(conn_py); + tx.send(conn_py.unbind()); }); let execute_fn = self @@ -70,8 +75,8 @@ impl DatabasePool for PythonDatabasePool { execute_fn.call1((callback_func,))?; Ok(Box::new(LoggingDatabaseConnectionWrapper { - database_pool_py: self, - logging_database_connection_py: conn_py, + database_pool_py: self.database_pool_py, + logging_database_connection_py: connection, })) } } @@ -79,7 +84,12 @@ impl DatabasePool for PythonDatabasePool { /// Wrapper for a `LoggingDatabaseConnection` from the Python side of Synapse. pub struct LoggingDatabaseConnectionWrapper { /// The underlying `DatabasePool` - database_pool_py: Bound<'py, PyAny>, + /// + /// We purposely avoid `Bound<'py, PyAny>` so it can be stored and moved freely + /// across threads (like to extract it from the `runWithConnection(...)` callback). + /// You will need to acquire your own `py` and bind it using + /// `database_pool_py.bind(py)` to do anything useful. + database_pool_py: Py, /// The underlying `LoggingDatabaseConnection` logging_database_connection_py: Py, } @@ -103,8 +113,8 @@ impl DatabaseConnection for LoggingDatabaseConnectionWrapper { // TODO: Error if already called let py = args.py(); - let txn_py: LoggingTransactionWrapper = args.get_item(0)?; - tx.send(txn_py); + let txn: LoggingTransactionWrapper = args.get_item(0)?; + tx.send(txn); // Wait until we see the signal that we're `done_with_txn_rx` }); @@ -146,12 +156,13 @@ fn detect_engine(txn_py: &Bound<'_, PyAny>) -> PyResult { } /// Wrapper for a `LoggingTransaction` from the Python side of Synapse. -/// -/// TODO: Verify if this is the correct approach or we should use`Bound<'py, PyAny>`: -/// Holds no `'py` lifetime so it can be stored and moved freely across threads. -/// Use [`execute`](Self::execute) (or other methods) while holding the GIL. pub struct LoggingTransactionWrapper { /// The underlying `LoggingTransaction` + /// + /// We purposely avoid `Bound<'py, PyAny>` so it can be stored and moved freely + /// across threads (like to extract it from the `new_transaction(...)` callback). + /// You will need to acquire your own `py` and bind it using + /// `logging_transaction_py.bind(py)` to do anything useful. logging_transaction_py: Py, /// Disambiguate which underlying database engine we're working with @@ -208,9 +219,11 @@ impl LoggingTransactionWrapper { pub fn fetchall + ValidDatabaseReturnType>( &mut self, + py: Python<'py>, ) -> anyhow::Result> { let fetch_fn = self .logging_transaction_py + .bind(py) .getattr(intern!(self.logging_transaction_py.py(), "fetchall"))?; Ok(fetch_fn.call0()?.extract()?) } @@ -219,10 +232,13 @@ impl LoggingTransactionWrapper { #[async_trait::async_trait] impl Transaction for LoggingTransactionWrapper { async fn query(&self, sql: &str, args: &[&str]) -> Result, anyhow::Error> { - self.execute(sql, args).await; - let rows = self.fetchall(sql, args).await?; + Python::attach(|py| -> PyResult> { + self.execute(py, sql, args).await; + let rows = self.fetchall(py, sql, args).await?; - Ok(rows) + Ok(rows) + }) + .map_err(anyhow::Error::from) } async fn commit(&self) -> Result<(), anyhow::Error> { From c289dd19a6ea6127f11ac2025d207ef9b0237762 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 15:31:10 -0500 Subject: [PATCH 21/24] Resolve `fetchall` lifetimes -> `FromPyObjectOwned` We can use `FromPyObjectOwned` (instead of `FromPyObject`) because we don't borrow anything ``` error[E0107]: trait takes 2 lifetime arguments but 1 lifetime argument was supplied --> rust/src/storage/db/python_db_pool.rs:220:24 | 220 | pub fn fetchall + ValidDatabaseReturnType>( | ^^^^^^^^^^^^ --- supplied 1 lifetime argument | | | expected 2 lifetime arguments | note: trait defined here, with 2 lifetime parameters: `'a`, `'py` --> /home/eric/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pyo3-0.28.3/src/conversion.rs:401:11 | 401 | pub trait FromPyObject<'a, 'py>: Sized { | ^^^^^^^^^^^^ -- --- help: add missing lifetime argument | 220 | pub fn fetchall + ValidDatabaseReturnType>( | +++++ ``` --- rust/src/storage/db/python_db_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/src/storage/db/python_db_pool.rs b/rust/src/storage/db/python_db_pool.rs index 9a29d3f0bdd..2c4bb4f2372 100644 --- a/rust/src/storage/db/python_db_pool.rs +++ b/rust/src/storage/db/python_db_pool.rs @@ -217,7 +217,7 @@ impl LoggingTransactionWrapper { Ok(()) } - pub fn fetchall + ValidDatabaseReturnType>( + pub fn fetchall<'py, T: FromPyObjectOwned<'py> + ValidDatabaseReturnType>( &mut self, py: Python<'py>, ) -> anyhow::Result> { From 7e709fb86145b5def8da7ed9fa9dbf87711ad695 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 15:40:13 -0500 Subject: [PATCH 22/24] Fix tricky Rust error which turned out to just needing to use an actual `py` ``` error[E0277]: the trait bound `std::vec::Vec: pyo3::FromPyObject<'_, '_>` is not satisfied --> rust/src/storage/db/python_db_pool.rs:228:30 | 228 | Ok(fetch_fn.call0()?.extract()?) | ^^^^^^^ the trait `pyo3::FromPyObject<'_, '_>` is not implemented for `std::vec::Vec` | note: required by a bound in `pyo3::types::PyAnyMethods::extract` --> /home/eric/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pyo3-0.28.3/src/types/any.rs:881:12 | 879 | fn extract<'a, T>(&'a self) -> Result | ------- required by a bound in this associated function 880 | where 881 | T: FromPyObject<'a, 'py>; | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyAnyMethods::extract` help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement | 223 | ) -> anyhow::Result> where std::vec::Vec: pyo3::FromPyObject<'_, '_> { | ++++++++++++++++++++++++++++++++++++++++++++++++++ ``` --- rust/src/storage/db/python_db_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/src/storage/db/python_db_pool.rs b/rust/src/storage/db/python_db_pool.rs index 2c4bb4f2372..d7387ccb639 100644 --- a/rust/src/storage/db/python_db_pool.rs +++ b/rust/src/storage/db/python_db_pool.rs @@ -224,7 +224,7 @@ impl LoggingTransactionWrapper { let fetch_fn = self .logging_transaction_py .bind(py) - .getattr(intern!(self.logging_transaction_py.py(), "fetchall"))?; + .getattr(intern!(py, "fetchall"))?; Ok(fetch_fn.call0()?.extract()?) } } From 287c0656d63bd2748ddf8efd4eac4caf7cf6cdaf Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 5 Jun 2026 15:57:48 -0500 Subject: [PATCH 23/24] Convert SQL args to compatible with the Python side ``` error[E0308]: mismatched types --> rust/src/storage/db/python_db_pool.rs:236:35 | 236 | self.execute(py, sql, args)?; | ------- ^^^^ expected `&Bound<'_, PyAny>`, found `&[&str]` | | | arguments to this method are incorrect | = note: expected reference `&pyo3::Bound<'_, pyo3::PyAny>` found reference `&'life2 [&'life3 str]` note: method defined here --> rust/src/storage/db/python_db_pool.rs:206:12 | 206 | pub fn execute<'py>( | ^^^^^^^ ... 210 | args: &Bound<'py, PyAny>, | ------------------------ ``` --- rust/src/storage/db/python_db_pool.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rust/src/storage/db/python_db_pool.rs b/rust/src/storage/db/python_db_pool.rs index d7387ccb639..9dbd7cc8a5f 100644 --- a/rust/src/storage/db/python_db_pool.rs +++ b/rust/src/storage/db/python_db_pool.rs @@ -18,7 +18,7 @@ //! - connections [`LoggingDatabaseConnectionWrapper`] which creates //! - transactions [`LoggingTransactionWrapper`] -use pyo3::{intern, prelude::*, types::PyCFunction}; +use pyo3::{intern, prelude::*, types::PyCFunction, types::PyList}; use crate::storage::db::{DatabaseConnection, DatabasePool, Row, Transaction}; @@ -233,8 +233,13 @@ impl LoggingTransactionWrapper { impl Transaction for LoggingTransactionWrapper { async fn query(&self, sql: &str, args: &[&str]) -> Result, anyhow::Error> { Python::attach(|py| -> PyResult> { - self.execute(py, sql, args).await; - let rows = self.fetchall(py, sql, args).await?; + // Convert the Rust `&[&str]` of SQL parameters into a Python sequence so it + // can be passed through to the Python-side `execute`. + let args = PyList::new(py, args)?; + // Run the query + self.execute(py, sql, args.as_any())?; + // Get the results + let rows = self.fetchall(py)?; Ok(rows) }) From eb060ce6fa5597e619653848a6ced3324913899f Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 9 Jun 2026 17:52:50 -0500 Subject: [PATCH 24/24] Remove stray change --- synapse/storage/util/id_generators.py | 1 - 1 file changed, 1 deletion(-) diff --git a/synapse/storage/util/id_generators.py b/synapse/storage/util/id_generators.py index 8614c33809a..c9c339b235c 100644 --- a/synapse/storage/util/id_generators.py +++ b/synapse/storage/util/id_generators.py @@ -832,7 +832,6 @@ def _add_persisted_position(self, new_id: int) -> None: # If this is the to-device stream, and we are a writer for that stream, log some stats if ( issue9533_logger.isEnabledFor(logging.DEBUG) - # Only log if we are the instance that is doing the persisting and our_current_position > 0 and self._stream_name == "to_device" ):