Map inet columns to IpAddr through sqlx.toml

Every `inet` column holds a single host address, so a type override in
the new per-crate `sqlx.toml` replaces the `as "col: IpAddr"` casts the
queries needed to avoid the default `IpNetwork` mapping. Both crates
using the query macros also pin `chrono` as the date/time crate.

This needs the `sqlx-toml` feature on `sqlx`. `sqlx-cli` needs it too,
as it refuses to run in a crate directory that contains a `sqlx.toml`
file otherwise. It is a default feature of `sqlx-cli`, so only the
`--no-default-features` install in the docs has to name it.
This commit is contained in:
Quentin Gliech committed 2026-10-12 13:48:42 +02:00
1 parent c71e1f6103
commit ceedf82679
22 files changed
+111 -40

No files matched your search

Generated
+36 -2
View File
@@ -5464,7 +5464,7 @@ dependencies = [
"ruma-identifiers-validation",
"serde",
"syn 2.0.106",
"toml",
"toml 1.1.2+spec-1.1.0",
]
[[package]]
@@ -5995,6 +5995,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "1.1.1"
@@ -6302,6 +6311,7 @@ dependencies = [
"thiserror 2.0.18",
"tokio",
"tokio-stream",
"toml 0.8.23",
"tracing",
"url",
"uuid",
@@ -6841,6 +6851,18 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_edit",
]
[[package]]
name = "toml"
version = "1.1.2+spec-1.1.0"
@@ -6848,7 +6870,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
dependencies = [
"serde_core",
"serde_spanned",
"serde_spanned 1.1.1",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"winnow 1.0.3",
@@ -6859,6 +6881,9 @@ name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
@@ -6876,7 +6901,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap 2.14.0",
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_write",
"winnow 0.7.13",
]
@@ -6889,6 +6917,12 @@ dependencies = [
"winnow 1.0.3",
]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "tonic"
version = "0.14.2"
+1
View File
@@ -635,6 +635,7 @@ features = [
"json",
"uuid",
"ipnetwork",
"sqlx-toml",
]
# Custom error types
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n , ip_address as \"ip_address: IpAddr\"\n , user_agent\n , locale\n FROM\n oauth2_device_code_grant\n\n WHERE oauth2_device_code_grant_id = $1\n ",
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n , ip_address\n , user_agent\n , locale\n FROM\n oauth2_device_code_grant\n\n WHERE oauth2_device_code_grant_id = $1\n ",
"describe": {
"columns": [
{
@@ -137,7 +137,7 @@
},
{
"ordinal": 12,
"name": "ip_address: IpAddr",
"name": "ip_address",
"type_info": "Inet",
"origin": {
"Table": {
@@ -192,5 +192,5 @@
true
]
},
"hash": "e0dd18421e3b19079f291ba0ae9ba64031c0e635e5b40684a26565d50b3905f2"
"hash": "2fe8320d8d92e6361f83800a6387d9148846724279e95cb9ff72468ede7ca5f3"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT oauth2_session_id\n , user_id\n , user_session_id\n , oauth2_client_id\n , scope_list\n , created_at\n , finished_at\n , user_agent\n , last_active_at\n , last_active_ip as \"last_active_ip: IpAddr\"\n , human_name\n FROM oauth2_sessions\n\n WHERE oauth2_session_id = $1\n ",
"query": "\n SELECT oauth2_session_id\n , user_id\n , user_session_id\n , oauth2_client_id\n , scope_list\n , created_at\n , finished_at\n , user_agent\n , last_active_at\n , last_active_ip\n , human_name\n FROM oauth2_sessions\n\n WHERE oauth2_session_id = $1\n ",
"describe": {
"columns": [
{
@@ -104,7 +104,7 @@
},
{
"ordinal": 9,
"name": "last_active_ip: IpAddr",
"name": "last_active_ip",
"type_info": "Inet",
"origin": {
"Table": {
@@ -144,5 +144,5 @@
true
]
},
"hash": "6b8d28b76d7ab33178b46dbb28c11e41d86f22b3fa899a952cad00129e59bee6"
"hash": "30538b3c249c37c64f37b66bea73a51a989f02686ca3f9ea1182c01512b062c1"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT compat_session_id\n , device_id\n , human_name\n , user_id\n , user_session_id\n , created_at\n , finished_at\n , is_synapse_admin\n , user_agent\n , last_active_at\n , last_active_ip as \"last_active_ip: IpAddr\"\n FROM compat_sessions\n WHERE compat_session_id = $1\n ",
"query": "\n SELECT compat_session_id\n , device_id\n , human_name\n , user_id\n , user_session_id\n , created_at\n , finished_at\n , is_synapse_admin\n , user_agent\n , last_active_at\n , last_active_ip\n FROM compat_sessions\n WHERE compat_session_id = $1\n ",
"describe": {
"columns": [
{
@@ -115,7 +115,7 @@
},
{
"ordinal": 10,
"name": "last_active_ip: IpAddr",
"name": "last_active_ip",
"type_info": "Inet",
"origin": {
"Table": {
@@ -144,5 +144,5 @@
true
]
},
"hash": "9b7363000017fa3dee46441bc0679cb16f9f8df08fa258cc907007fb9bcd0bc7"
"hash": "3d240e280f1f207fc652ab3d1977279de2bf12d2b35c568769b97cc7559d428a"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n , ip_address as \"ip_address: IpAddr\"\n , user_agent\n , locale\n FROM\n oauth2_device_code_grant\n\n WHERE user_code = $1\n ",
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n , ip_address\n , user_agent\n , locale\n FROM\n oauth2_device_code_grant\n\n WHERE device_code = $1\n ",
"describe": {
"columns": [
{
@@ -137,7 +137,7 @@
},
{
"ordinal": 12,
"name": "ip_address: IpAddr",
"name": "ip_address",
"type_info": "Inet",
"origin": {
"Table": {
@@ -192,5 +192,5 @@
true
]
},
"hash": "89674770c8d7929382e266a88937df017289d5cabe62ad00b115a2a408f4936b"
"hash": "65892801c9f79e25a82af2ce689e5b02811cafaacb84e8e18dd5b6d1d0f5c3d1"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT user_registration_id\n , ip_address as \"ip_address: IpAddr\"\n , user_agent\n , post_auth_action\n , username\n , display_name\n , terms_url\n , email_authentication_id\n , user_registration_token_id\n , hashed_password\n , hashed_password_version\n , upstream_oauth_authorization_session_id\n , created_at\n , completed_at\n FROM user_registrations\n WHERE user_registration_id = $1\n ",
"query": "\n SELECT user_registration_id\n , ip_address\n , user_agent\n , post_auth_action\n , username\n , display_name\n , terms_url\n , email_authentication_id\n , user_registration_token_id\n , hashed_password\n , hashed_password_version\n , upstream_oauth_authorization_session_id\n , created_at\n , completed_at\n FROM user_registrations\n WHERE user_registration_id = $1\n ",
"describe": {
"columns": [
{
@@ -16,7 +16,7 @@
},
{
"ordinal": 1,
"name": "ip_address: IpAddr",
"name": "ip_address",
"type_info": "Inet",
"origin": {
"Table": {
@@ -180,5 +180,5 @@
true
]
},
"hash": "b91cc2458e1a530e7cadbd1ca3e2eaf93e1c44108b6770a24c9a24ac29db37d3"
"hash": "8f2533e9dd57add790826039820f5751c53bccd0df633cadb51cde7612f20978"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n user_recovery_session_id\n , email\n , user_agent\n , ip_address as \"ip_address: IpAddr\"\n , locale\n , created_at\n , consumed_at\n FROM user_recovery_sessions\n WHERE user_recovery_session_id = $1\n ",
"query": "\n SELECT\n user_recovery_session_id\n , email\n , user_agent\n , ip_address\n , locale\n , created_at\n , consumed_at\n FROM user_recovery_sessions\n WHERE user_recovery_session_id = $1\n ",
"describe": {
"columns": [
{
@@ -38,7 +38,7 @@
},
{
"ordinal": 3,
"name": "ip_address: IpAddr",
"name": "ip_address",
"type_info": "Inet",
"origin": {
"Table": {
@@ -96,5 +96,5 @@
true
]
},
"hash": "f46e87bbb149b35e1d13b2b3cd2bdeab3c28a56a395f52f001a7bb013a5dfece"
"hash": "b07561f6f443417590054f1a5986639c51800e986b0fd08d9c9759792478bc0b"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT s.user_session_id\n , s.created_at AS \"user_session_created_at\"\n , s.finished_at AS \"user_session_finished_at\"\n , s.user_agent AS \"user_session_user_agent\"\n , s.last_active_at AS \"user_session_last_active_at\"\n , s.last_active_ip AS \"user_session_last_active_ip: IpAddr\"\n , u.user_id\n , u.username AS \"user_username\"\n , u.created_at AS \"user_created_at\"\n , u.locked_at AS \"user_locked_at\"\n , u.deactivated_at AS \"user_deactivated_at\"\n , u.can_request_admin AS \"user_can_request_admin\"\n , u.is_guest AS \"user_is_guest\"\n FROM user_sessions s\n INNER JOIN users u\n USING (user_id)\n WHERE s.user_session_id = $1\n ",
"query": "\n SELECT s.user_session_id\n , s.created_at AS \"user_session_created_at\"\n , s.finished_at AS \"user_session_finished_at\"\n , s.user_agent AS \"user_session_user_agent\"\n , s.last_active_at AS \"user_session_last_active_at\"\n , s.last_active_ip AS \"user_session_last_active_ip\"\n , u.user_id\n , u.username AS \"user_username\"\n , u.created_at AS \"user_created_at\"\n , u.locked_at AS \"user_locked_at\"\n , u.deactivated_at AS \"user_deactivated_at\"\n , u.can_request_admin AS \"user_can_request_admin\"\n , u.is_guest AS \"user_is_guest\"\n FROM user_sessions s\n INNER JOIN users u\n USING (user_id)\n WHERE s.user_session_id = $1\n ",
"describe": {
"columns": [
{
@@ -60,7 +60,7 @@
},
{
"ordinal": 5,
"name": "user_session_last_active_ip: IpAddr",
"name": "user_session_last_active_ip",
"type_info": "Inet",
"origin": {
"Table": {
@@ -168,5 +168,5 @@
false
]
},
"hash": "572ead41d62cfbe40e6f0c8edf6928e8eebd99036255b62d688ac02b5bd74b40"
"hash": "da85dd2a5b82b1ad9806047acee53416938bb20fe0f406c601223d756b7f8f32"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n , ip_address as \"ip_address: IpAddr\"\n , user_agent\n , locale\n FROM\n oauth2_device_code_grant\n\n WHERE device_code = $1\n ",
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n , ip_address\n , user_agent\n , locale\n FROM\n oauth2_device_code_grant\n\n WHERE user_code = $1\n ",
"describe": {
"columns": [
{
@@ -137,7 +137,7 @@
},
{
"ordinal": 12,
"name": "ip_address: IpAddr",
"name": "ip_address",
"type_info": "Inet",
"origin": {
"Table": {
@@ -192,5 +192,5 @@
true
]
},
"hash": "ca8db2854f873a17f0ead6adfefe658aa71cade3ee70d17ee09a334599edc561"
"hash": "e3e60d6ab74ac0fbca59fa7d72043b6b5b68e0fe2a087e98a64e1d062a76f2a3"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT personal_session_id\n , owner_user_id\n , owner_oauth2_client_id\n , actor_user_id\n , scope_list\n , created_at\n , revoked_at\n , human_name\n , last_active_at\n , last_active_ip as \"last_active_ip: IpAddr\"\n FROM personal_sessions\n\n WHERE personal_session_id = $1\n ",
"query": "\n SELECT personal_session_id\n , owner_user_id\n , owner_oauth2_client_id\n , actor_user_id\n , scope_list\n , created_at\n , revoked_at\n , human_name\n , last_active_at\n , last_active_ip\n FROM personal_sessions\n\n WHERE personal_session_id = $1\n ",
"describe": {
"columns": [
{
@@ -104,7 +104,7 @@
},
{
"ordinal": 9,
"name": "last_active_ip: IpAddr",
"name": "last_active_ip",
"type_info": "Inet",
"origin": {
"Table": {
@@ -132,5 +132,5 @@
true
]
},
"hash": "fd32368fa6cd16a9704cdea54f7729681d450669563dd1178c492ffce51e5ff2"
"hash": "e594f82c425e472737c9a0819eab1a66d7489b1f9e71647f8ef30cdeab7c24b8"
}
+16
View File
@@ -0,0 +1,16 @@
# Copyright 2026 Element Creations Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
# Please see LICENSE files in the repository root for full details.
# Read by the `sqlx` query macros and by `sqlx-cli`, both of which need the
# `sqlx-toml` feature.
[macros.preferred-crates]
# Keeps the macros on `chrono` if another date/time crate enters the dependency
# graph.
date-time = "chrono"
[macros.type-overrides]
# `inet` columns only ever hold a single host address, never a network range.
'INET' = "std::net::IpAddr"
+1 -1
View File
@@ -308,7 +308,7 @@ impl CompatSessionRepository for PgCompatSessionRepository<'_> {
, is_synapse_admin
, user_agent
, last_active_at
, last_active_ip as "last_active_ip: IpAddr"
, last_active_ip
FROM compat_sessions
WHERE compat_session_id = $1
"#,
@@ -239,7 +239,7 @@ impl OAuth2DeviceCodeGrantRepository for PgOAuth2DeviceCodeGrantRepository<'_> {
, exchanged_at
, user_session_id
, oauth2_session_id
, ip_address as "ip_address: IpAddr"
, ip_address
, user_agent
, locale
FROM
@@ -286,7 +286,7 @@ impl OAuth2DeviceCodeGrantRepository for PgOAuth2DeviceCodeGrantRepository<'_> {
, exchanged_at
, user_session_id
, oauth2_session_id
, ip_address as "ip_address: IpAddr"
, ip_address
, user_agent
, locale
FROM
@@ -333,7 +333,7 @@ impl OAuth2DeviceCodeGrantRepository for PgOAuth2DeviceCodeGrantRepository<'_> {
, exchanged_at
, user_session_id
, oauth2_session_id
, ip_address as "ip_address: IpAddr"
, ip_address
, user_agent
, locale
FROM
+1 -1
View File
@@ -242,7 +242,7 @@ impl OAuth2SessionRepository for PgOAuth2SessionRepository<'_> {
, finished_at
, user_agent
, last_active_at
, last_active_ip as "last_active_ip: IpAddr"
, last_active_ip
, human_name
FROM oauth2_sessions
+1 -1
View File
@@ -215,7 +215,7 @@ impl PersonalSessionRepository for PgPersonalSessionRepository<'_> {
, revoked_at
, human_name
, last_active_at
, last_active_ip as "last_active_ip: IpAddr"
, last_active_ip
FROM personal_sessions
WHERE personal_session_id = $1
+1 -1
View File
@@ -101,7 +101,7 @@ impl UserRecoveryRepository for PgUserRecoveryRepository<'_> {
user_recovery_session_id
, email
, user_agent
, ip_address as "ip_address: IpAddr"
, ip_address
, locale
, created_at
, consumed_at
+1 -1
View File
@@ -129,7 +129,7 @@ impl UserRegistrationRepository for PgUserRegistrationRepository<'_> {
UserRegistrationLookup,
r#"
SELECT user_registration_id
, ip_address as "ip_address: IpAddr"
, ip_address
, user_agent
, post_auth_action
, username
+1 -1
View File
@@ -204,7 +204,7 @@ impl BrowserSessionRepository for PgBrowserSessionRepository<'_> {
, s.finished_at AS "user_session_finished_at"
, s.user_agent AS "user_session_user_agent"
, s.last_active_at AS "user_session_last_active_at"
, s.last_active_ip AS "user_session_last_active_ip: IpAddr"
, s.last_active_ip AS "user_session_last_active_ip"
, u.user_id
, u.username AS "user_username"
, u.created_at AS "user_created_at"
+12
View File
@@ -0,0 +1,12 @@
# Copyright 2026 Element Creations Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
# Please see LICENSE files in the repository root for full details.
# Read by the `sqlx` query macros and by `sqlx-cli`, both of which need the
# `sqlx-toml` feature.
[macros.preferred-crates]
# Keeps the macros on `chrono` if another date/time crate enters the dependency
# graph.
date-time = "chrono"
+3
View File
@@ -82,6 +82,9 @@ skip = [
{ name = "serde_html_form", version = "0.2.8" }, # axum-extra
{ name = "toml_datetime", version = "0.6.11" }, # proc-macro-crate -> toml_edit
{ name = "winnow", version = "0.7.13" }, # cron, toml_edit
# sqlx-core's `sqlx-toml` feature is still on toml 0.8
{ name = "toml", version = "0.8.23" },
{ name = "serde_spanned", version = "0.6.9" },
# hyper-util -> system-configuration depends on this old version
{ name = "core-foundation", version = "0.9.4" },
# wasmtime pulls in older versions of these
+7 -2
View File
@@ -30,14 +30,19 @@ Usually it does so by having the database available at compile time, but to avoi
Preparing this flat file is done through `sqlx-cli`, and should be done everytime the database schema or the queries changed.
```sh
# Install the CLI
cargo install sqlx-cli --no-default-features --features postgres
# Install the CLI. The `sqlx-toml` feature is required because the CLI refuses
# to run in a crate directory that contains a `sqlx.toml` file without it.
cargo install sqlx-cli --no-default-features --features postgres,sqlx-toml
cd crates/storage-pg/ # Must be in the mas-storage-pg crate folder
export DATABASE_URL=postgresql:///matrix_auth
cargo sqlx prepare
```
Both `mas-storage-pg` and `syn2mas` use the query macros and have their own
`.sqlx` cache and `sqlx.toml`. The `misc/sqlx_update.sh` script regenerates both
in one go (it also sets up the temporary tables `syn2mas` needs).
## Migrations
Migration files live in the `migrations` folder in the `mas-storage-pg` crate.