fix: sqlx AnyDb is kinda broken...

This commit is contained in:
əlemi 2024-01-03 13:46:09 +01:00
parent 460ebef1bf
commit 41f86634e5
Signed by: alemi
GPG key ID: A4895B84D311642C
2 changed files with 39 additions and 27 deletions

View file

@ -20,23 +20,16 @@ pub struct Page {
pub public: bool, pub public: bool,
} }
// TODO // TODO this is only necessary until sqlx fixes parsing BOOL and NULL, check model.rs for more
// deserializing Option<T> 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
impl<'r> sqlx::FromRow<'r, sqlx::any::AnyRow> for Page { impl<'r> sqlx::FromRow<'r, sqlx::any::AnyRow> for Page {
fn from_row(row: &'r sqlx::any::AnyRow) -> Result<Self, sqlx::Error> { fn from_row(row: &'r sqlx::any::AnyRow) -> Result<Self, sqlx::Error> {
Ok( Ok(
Page { Page {
id: row.get(0), id: row.get::<i64, usize>(0),
author: row.get(1), author: row.get::<String, usize>(1),
contact: row.try_get(2).ok(), contact: _non_empty_string(row.get::<String, usize>(2)),
body: row.get(3), body: row.get::<String, usize>(3),
timestamp: row.get(4), timestamp: row.get::<i64, usize>(4),
public: row.get::<i32, usize>(5) > 0, public: row.get::<i32, usize>(5) > 0,
} }
) )
@ -45,7 +38,6 @@ impl<'r> sqlx::FromRow<'r, sqlx::any::AnyRow> for Page {
#[derive(Debug, Clone, Default, Serialize)] #[derive(Debug, Clone, Default, Serialize)]
pub struct PageView { pub struct PageView {
pub id: i64, pub id: i64,
@ -128,3 +120,10 @@ pub struct PageOptions {
pub offset: Option<i32>, pub offset: Option<i32>,
pub limit: Option<i32>, pub limit: Option<i32>,
} }
fn _non_empty_string(input: String) -> Option<String> {
match input.is_empty() {
true => None,
false => Some(input),
}
}

View file

@ -1,4 +1,5 @@
use chrono::Utc; use chrono::Utc;
use sqlx::Database;
use crate::{model::{PageView, PageInsertion, Page}, config::ConfigOverrides}; use crate::{model::{PageView, PageInsertion, Page}, config::ConfigOverrides};
@ -8,17 +9,29 @@ pub struct StorageProvider {
overrides: ConfigOverrides, overrides: ConfigOverrides,
} }
// TODO bool type is not supported in Any driver????? // TODO what the fuck is wrong with AnyPool driver?????
// so the `public` field is an integer which is ridicolous // * deserializing Option<T> values on AnyDriver is broken, pr to fix is in progress
// but literally cannot get it to work ffs // https://github.com/launchbadge/sqlx/issues/2416
// https://github.com/launchbadge/sqlx/pull/2716
// until this is merged, must implement by hand sqlx::FromRow<sqlx::any::AnyRow>
// once this is merged, just do #[derive(sqlx::FromRow)]
// //
// * serializing Option<T> 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 // https://github.com/launchbadge/sqlx/issues/2778
// so the `public` field is an integer which is ridicolous
const SQLITE_SCHEMA : &str = " const SQLITE_SCHEMA : &str = "
CREATE TABLE IF NOT EXISTS pages ( CREATE TABLE IF NOT EXISTS pages (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
author VARCHAR NOT NULL, author VARCHAR NOT NULL,
contact VARCHAR, contact VARCHAR NOT NULL,
body VARCHAR NOT NULL, body VARCHAR NOT NULL,
timestamp INTEGER NOT NULL, timestamp INTEGER NOT NULL,
public INTEGER NOT NULL public INTEGER NOT NULL
@ -29,7 +42,7 @@ const POSTGRES_SCHEMA : &str = "
CREATE TABLE IF NOT EXISTS pages ( CREATE TABLE IF NOT EXISTS pages (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
author TEXT NOT NULL, author TEXT NOT NULL,
contact TEXT, contact TEXT NOT NULL,
body TEXT NOT NULL, body TEXT NOT NULL,
timestamp INTEGER NOT NULL, timestamp INTEGER NOT NULL,
public INTEGER NOT NULL public INTEGER NOT NULL
@ -41,9 +54,9 @@ impl StorageProvider {
let db = sqlx::AnyPool::connect(dest).await?; let db = sqlx::AnyPool::connect(dest).await?;
match db.acquire().await?.backend_name() { match db.acquire().await?.backend_name() {
"PostgreSQL" => { sqlx::query(POSTGRES_SCHEMA).execute(&db).await?; }, sqlx::Postgres::NAME => { sqlx::query(POSTGRES_SCHEMA).execute(&db).await?; },
"SQLite" => { sqlx::query(SQLITE_SCHEMA).execute(&db).await?; }, sqlx::Sqlite::NAME => { sqlx::query(SQLITE_SCHEMA).execute(&db).await?; },
"MySQL" => { sqlx::query(SQLITE_SCHEMA).execute(&db).await?; }, // TODO will this work? sqlx::MySql::NAME => { sqlx::query(SQLITE_SCHEMA).execute(&db).await?; }, // TODO will this work?
_ => tracing::warn!("could not ensure schema: unsupported database type"), _ => tracing::warn!("could not ensure schema: unsupported database type"),
} }
@ -54,9 +67,9 @@ impl StorageProvider {
page.sanitize(); page.sanitize();
page.overrides(&self.overrides); page.overrides(&self.overrides);
let result = sqlx::query("INSERT INTO pages (author, contact, body, timestamp, public) VALUES ($1, $2, $3, $4, $5)") 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.author.as_deref().unwrap_or("anonymous"))
.bind(page.contact.clone()) .bind(page.contact.as_deref().unwrap_or(""))
.bind(page.body.clone()) .bind(page.body.as_str())
.bind(page.date.unwrap_or(Utc::now()).timestamp()) .bind(page.date.unwrap_or(Utc::now()).timestamp())
.bind(if page.public.unwrap_or(true) { 1 } else { 0 }) .bind(if page.public.unwrap_or(true) { 1 } else { 0 })
.execute(&self.db) .execute(&self.db)
@ -74,7 +87,7 @@ impl StorageProvider {
} }
pub async fn extract(&self, offset: i32, window: i32, public: bool) -> sqlx::Result<Vec<PageView>> { pub async fn extract(&self, offset: i32, window: i32, public: bool) -> sqlx::Result<Vec<PageView>> {
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(if public { 1 } else { 0 }) // TODO since AnyPool won't handle booleans we compare with an integer
.bind(window) .bind(window)
.bind(offset) .bind(offset)