From 41f86634e5e01e5106588e8a3a981932a9cf0d99 Mon Sep 17 00:00:00 2001 From: alemi Date: Wed, 3 Jan 2024 13:46:09 +0100 Subject: [PATCH] fix: sqlx AnyDb is kinda broken... --- src/model.rs | 27 +++++++++++++-------------- src/storage.rs | 39 ++++++++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/model.rs b/src/model.rs index 7cd53af..c72e401 100644 --- a/src/model.rs +++ b/src/model.rs @@ -20,23 +20,16 @@ pub struct Page { pub public: bool, } -// TODO -// deserializing Option values on AnyDriver is broken, pr to fix is in progress -// https://github.com/launchbadge/sqlx/issues/2416 -// https://github.com/launchbadge/sqlx/pull/2716 -// until this is merged, must implement by hand -// once this is merged, just do #[derive(sqlx::FromRow)] -// also what the fuck is going on with bools??? -// https://github.com/launchbadge/sqlx/issues/2778 +// TODO this is only necessary until sqlx fixes parsing BOOL and NULL, check model.rs for more impl<'r> sqlx::FromRow<'r, sqlx::any::AnyRow> for Page { fn from_row(row: &'r sqlx::any::AnyRow) -> Result { Ok( Page { - id: row.get(0), - author: row.get(1), - contact: row.try_get(2).ok(), - body: row.get(3), - timestamp: row.get(4), + id: row.get::(0), + author: row.get::(1), + contact: _non_empty_string(row.get::(2)), + body: row.get::(3), + timestamp: row.get::(4), public: row.get::(5) > 0, } ) @@ -45,7 +38,6 @@ impl<'r> sqlx::FromRow<'r, sqlx::any::AnyRow> for Page { - #[derive(Debug, Clone, Default, Serialize)] pub struct PageView { pub id: i64, @@ -128,3 +120,10 @@ pub struct PageOptions { pub offset: Option, pub limit: Option, } + +fn _non_empty_string(input: String) -> Option { + match input.is_empty() { + true => None, + false => Some(input), + } +} diff --git a/src/storage.rs b/src/storage.rs index 6cf61d7..bc8eb6b 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,4 +1,5 @@ use chrono::Utc; +use sqlx::Database; use crate::{model::{PageView, PageInsertion, Page}, config::ConfigOverrides}; @@ -8,17 +9,29 @@ pub struct StorageProvider { overrides: ConfigOverrides, } -// TODO bool type is not supported in Any driver????? -// so the `public` field is an integer which is ridicolous -// but literally cannot get it to work ffs +// TODO what the fuck is wrong with AnyPool driver????? +// * deserializing Option values on AnyDriver is broken, pr to fix is in progress +// https://github.com/launchbadge/sqlx/issues/2416 +// https://github.com/launchbadge/sqlx/pull/2716 +// until this is merged, must implement by hand sqlx::FromRow +// once this is merged, just do #[derive(sqlx::FromRow)] // -// https://github.com/launchbadge/sqlx/issues/2778 +// * serializing Option values on Postgres is unreliable, not even sure why? +// `incorrect binary data format in bind parameter` +// it seems related to expression caching and types that change (sometimes T, sometimes NULL) +// https://github.com/launchbadge/sqlx/issues/2885 +// the suggested solution (`.persistent(false)`) doesn't work! +// we are forced to serialize NULLs as empty strings and lose the distinction +// +// * deserializing BOOL just doesn't work for some reason +// https://github.com/launchbadge/sqlx/issues/2778 +// so the `public` field is an integer which is ridicolous const SQLITE_SCHEMA : &str = " CREATE TABLE IF NOT EXISTS pages ( id INTEGER PRIMARY KEY AUTOINCREMENT, author VARCHAR NOT NULL, - contact VARCHAR, + contact VARCHAR NOT NULL, body VARCHAR NOT NULL, timestamp INTEGER NOT NULL, public INTEGER NOT NULL @@ -29,7 +42,7 @@ const POSTGRES_SCHEMA : &str = " CREATE TABLE IF NOT EXISTS pages ( id SERIAL PRIMARY KEY, author TEXT NOT NULL, - contact TEXT, + contact TEXT NOT NULL, body TEXT NOT NULL, timestamp INTEGER NOT NULL, public INTEGER NOT NULL @@ -41,9 +54,9 @@ impl StorageProvider { let db = sqlx::AnyPool::connect(dest).await?; match db.acquire().await?.backend_name() { - "PostgreSQL" => { sqlx::query(POSTGRES_SCHEMA).execute(&db).await?; }, - "SQLite" => { sqlx::query(SQLITE_SCHEMA).execute(&db).await?; }, - "MySQL" => { sqlx::query(SQLITE_SCHEMA).execute(&db).await?; }, // TODO will this work? + sqlx::Postgres::NAME => { sqlx::query(POSTGRES_SCHEMA).execute(&db).await?; }, + sqlx::Sqlite::NAME => { sqlx::query(SQLITE_SCHEMA).execute(&db).await?; }, + sqlx::MySql::NAME => { sqlx::query(SQLITE_SCHEMA).execute(&db).await?; }, // TODO will this work? _ => tracing::warn!("could not ensure schema: unsupported database type"), } @@ -54,9 +67,9 @@ impl StorageProvider { page.sanitize(); page.overrides(&self.overrides); let result = sqlx::query("INSERT INTO pages (author, contact, body, timestamp, public) VALUES ($1, $2, $3, $4, $5)") - .bind(page.author.as_deref().unwrap_or("anonymous").to_string()) - .bind(page.contact.clone()) - .bind(page.body.clone()) + .bind(page.author.as_deref().unwrap_or("anonymous")) + .bind(page.contact.as_deref().unwrap_or("")) + .bind(page.body.as_str()) .bind(page.date.unwrap_or(Utc::now()).timestamp()) .bind(if page.public.unwrap_or(true) { 1 } else { 0 }) .execute(&self.db) @@ -74,7 +87,7 @@ impl StorageProvider { } pub async fn extract(&self, offset: i32, window: i32, public: bool) -> sqlx::Result> { - let out = sqlx::query_as("SELECT * FROM pages WHERE public = $1 LIMIT $2 OFFSET $3") + let out = sqlx::query_as::<_, Page>("SELECT * FROM pages WHERE public = $1 LIMIT $2 OFFSET $3") .bind(if public { 1 } else { 0 }) // TODO since AnyPool won't handle booleans we compare with an integer .bind(window) .bind(offset)