fix: support sqlx 0.9

This commit is contained in:
Jon Seager 2026-06-08 10:56:18 +00:00
parent deefd10a71
commit 7563250809
8 changed files with 28 additions and 17 deletions

View file

@ -1,5 +1,5 @@
[tools] [tools]
rust = { version = "1.93", components = "rustfmt,clippy,rust-analyzer,rust-src" } rust = { version = "1.94", components = "rustfmt,clippy,rust-analyzer,rust-src" }
node = "latest" node = "latest"
uv = "latest" uv = "latest"
prek = "latest" prek = "latest"

View file

@ -4,16 +4,23 @@
# Uses chisel to create a minimal Ubuntu rootfs for the runtime image. # Uses chisel to create a minimal Ubuntu rootfs for the runtime image.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Builder — Ubuntu with Rust toolchain pre-installed # Builder — Ubuntu with Rust toolchain installed
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
FROM ubuntu/rust:1.93-26.04_edge AS builder FROM ubuntu:26.04 AS builder
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:${PATH}
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
build-essential \
pkg-config \ pkg-config \
libssl-dev \ libssl-dev \
mold \ mold \
binutils \ binutils \
curl \ curl \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN curl -sSf https://sh.rustup.rs \
| sh -s -- -y --profile minimal --default-toolchain 1.94.1
# Install tailwindcss standalone (needed by build.rs) # Install tailwindcss standalone (needed by build.rs)
RUN mkdir -p /usr/local/bin \ RUN mkdir -p /usr/local/bin \

View file

@ -4,6 +4,7 @@ use anyhow::{Context, bail};
use chrono::{DateTime, NaiveDate, Utc}; use chrono::{DateTime, NaiveDate, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_string}; use serde_json::{from_str, to_string};
use sqlx::AssertSqlSafe;
use crate::domain::bags::Bag; use crate::domain::bags::Bag;
use crate::domain::brews::{Brew, QuickNote}; use crate::domain::brews::{Brew, QuickNote};
@ -175,7 +176,7 @@ impl BackupService {
for table in tables { for table in tables {
let query = format!("DELETE FROM {table}"); let query = format!("DELETE FROM {table}");
sqlx::query(&query) sqlx::query(AssertSqlSafe(query))
.execute(&mut *tx) .execute(&mut *tx)
.await .await
.with_context(|| format!("failed to delete from {table}"))?; .with_context(|| format!("failed to delete from {table}"))?;
@ -316,7 +317,7 @@ impl BackupService {
for table in tables { for table in tables {
let query = format!("SELECT COUNT(*) as count FROM {table}"); let query = format!("SELECT COUNT(*) as count FROM {table}");
let row: (i64,) = sqlx::query_as(&query) let row: (i64,) = sqlx::query_as(AssertSqlSafe(query))
.fetch_one(&self.pool) .fetch_one(&self.pool)
.await .await
.with_context(|| format!("failed to check table {table}"))?; .with_context(|| format!("failed to check table {table}"))?;

View file

@ -1,6 +1,6 @@
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, NaiveDate, Utc}; use chrono::{DateTime, NaiveDate, Utc};
use sqlx::{QueryBuilder, query_as}; use sqlx::{AssertSqlSafe, QueryBuilder, query_as};
use crate::domain::RepositoryError; use crate::domain::RepositoryError;
use crate::domain::bags::{Bag, BagFilter, BagSortKey, BagWithRoast, NewBag, UpdateBag}; use crate::domain::bags::{Bag, BagFilter, BagSortKey, BagWithRoast, NewBag, UpdateBag};
@ -119,7 +119,7 @@ impl BagRepository for SqlBagRepository {
async fn get_with_roast(&self, id: BagId) -> Result<BagWithRoast, RepositoryError> { async fn get_with_roast(&self, id: BagId) -> Result<BagWithRoast, RepositoryError> {
let query = format!("{BASE_SELECT} WHERE b.id = ?"); let query = format!("{BASE_SELECT} WHERE b.id = ?");
let record = query_as::<_, BagWithRoastRecord>(&query) let record = query_as::<_, BagWithRoastRecord>(AssertSqlSafe(query))
.bind(id.into_inner()) .bind(id.into_inner())
.fetch_optional(&self.pool) .fetch_optional(&self.pool)
.await .await

View file

@ -1,6 +1,6 @@
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use sqlx::{QueryBuilder, query_as}; use sqlx::{AssertSqlSafe, QueryBuilder, query_as};
use crate::domain::RepositoryError; use crate::domain::RepositoryError;
use crate::domain::brews::{ use crate::domain::brews::{
@ -189,7 +189,7 @@ impl BrewRepository for SqlBrewRepository {
async fn get_with_details(&self, id: BrewId) -> Result<BrewWithDetails, RepositoryError> { async fn get_with_details(&self, id: BrewId) -> Result<BrewWithDetails, RepositoryError> {
let query = format!("{BASE_SELECT} WHERE br.id = ?"); let query = format!("{BASE_SELECT} WHERE br.id = ?");
let record = query_as::<_, BrewWithDetailsRecord>(&query) let record = query_as::<_, BrewWithDetailsRecord>(AssertSqlSafe(query))
.bind(id.into_inner()) .bind(id.into_inner())
.fetch_optional(&self.pool) .fetch_optional(&self.pool)
.await .await

View file

@ -1,6 +1,6 @@
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use sqlx::{QueryBuilder, query, query_as}; use sqlx::{AssertSqlSafe, QueryBuilder, query, query_as};
use crate::domain::RepositoryError; use crate::domain::RepositoryError;
use crate::domain::cups::{Cup, CupFilter, CupSortKey, CupWithDetails, NewCup, UpdateCup}; use crate::domain::cups::{Cup, CupFilter, CupSortKey, CupWithDetails, NewCup, UpdateCup};
@ -113,7 +113,7 @@ impl CupRepository for SqlCupRepository {
async fn get_with_details(&self, id: CupId) -> Result<CupWithDetails, RepositoryError> { async fn get_with_details(&self, id: CupId) -> Result<CupWithDetails, RepositoryError> {
let query = format!("{BASE_SELECT} WHERE c.id = ?"); let query = format!("{BASE_SELECT} WHERE c.id = ?");
let record = query_as::<_, CupWithDetailsRecord>(&query) let record = query_as::<_, CupWithDetailsRecord>(AssertSqlSafe(query))
.bind(id.into_inner()) .bind(id.into_inner())
.fetch_optional(&self.pool) .fetch_optional(&self.pool)
.await .await

View file

@ -1,4 +1,4 @@
use sqlx::{FromRow, QueryBuilder, query_scalar}; use sqlx::{AssertSqlSafe, FromRow, QueryBuilder, query_scalar};
use crate::domain::RepositoryError; use crate::domain::RepositoryError;
use crate::domain::listing::{ListRequest, Page, PageSize, SortKey}; use crate::domain::listing::{ListRequest, Page, PageSize, SortKey};
@ -102,7 +102,7 @@ where
qb.push(" OFFSET "); qb.push(" OFFSET ");
qb.push_bind(offset); qb.push_bind(offset);
} }
qb.build_query_as() qb.build_query_as::<R>()
.fetch_all(pool) .fetch_all(pool)
.await .await
.map_err(|err| RepositoryError::unexpected(err.to_string())) .map_err(|err| RepositoryError::unexpected(err.to_string()))
@ -117,13 +117,13 @@ async fn fetch_count(
let mut qb = QueryBuilder::new(count_query); let mut qb = QueryBuilder::new(count_query);
append_search_condition(&mut qb, count_query, sf); append_search_condition(&mut qb, count_query, sf);
let row: (i64,) = qb let row: (i64,) = qb
.build_query_as() .build_query_as::<(i64,)>()
.fetch_one(pool) .fetch_one(pool)
.await .await
.map_err(|err| RepositoryError::unexpected(err.to_string()))?; .map_err(|err| RepositoryError::unexpected(err.to_string()))?;
Ok(row.0) Ok(row.0)
} else { } else {
query_scalar(count_query) query_scalar(AssertSqlSafe(count_query))
.fetch_one(pool) .fetch_one(pool)
.await .await
.map_err(|err| RepositoryError::unexpected(err.to_string())) .map_err(|err| RepositoryError::unexpected(err.to_string()))
@ -131,7 +131,7 @@ async fn fetch_count(
} }
fn append_search_condition( fn append_search_condition(
qb: &mut QueryBuilder<'_, DatabaseDriver>, qb: &mut QueryBuilder<DatabaseDriver>,
base_sql: &str, base_sql: &str,
search: &SearchFilter, search: &SearchFilter,
) { ) {

View file

@ -233,7 +233,10 @@ async fn checkin_with_new_cafe_and_scanned_roast() {
.unwrap(); .unwrap();
prompt_input.send_keys(Key::Enter).await.unwrap(); prompt_input.send_keys(Key::Enter).await.unwrap();
// Wait for scan to complete — step 3 becomes visible with the submit button // Wait for scan to complete and populate the review before submitting.
wait_for_text(&session.driver, "body", "Kiandu AA")
.await
.unwrap();
let submit_btn = wait_for_visible(&session.driver, "button[type='submit']") let submit_btn = wait_for_visible(&session.driver, "button[type='submit']")
.await .await
.unwrap(); .unwrap();