commit f79415c78ea2080a3eb518023f307a3e53d91b7e
parent 7bc5a312248ea985861e342e6dc0e70ab1f50ccf
Author: triesap <tyson@radroots.org>
Date: Wed, 15 Jul 2026 05:31:55 +0000
sqlite: move app store to SQLx
- Replace the app store rusqlite runtime with the shared SQLx bundled SQLite path.
- Port store repositories, migrations, interop imports, and workflow receipt persistence to SQLx row/query handling.
- Add store-owned SQLite drift guards, typed test-support SQL helpers, and clippy-clean relay ingest inputs.
- Align desktop runtime tests and typed ID generation with direct target-state APIs.
Diffstat:
32 files changed, 2214 insertions(+), 1558 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1903,18 +1903,6 @@ dependencies = [
]
[[package]]
-name = "fallible-iterator"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
-
-[[package]]
-name = "fallible-streaming-iterator"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
-
-[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5159,12 +5147,13 @@ dependencies = [
name = "radroots_geocoder"
version = "0.1.0-alpha.2"
dependencies = [
+ "futures-executor",
"hex",
"radroots_runtime_paths",
"reqwest",
- "rusqlite",
"serde",
"sha2",
+ "sqlx",
"tempfile",
"thiserror 1.0.69",
"url",
@@ -5376,9 +5365,10 @@ name = "radroots_sql_core"
version = "0.1.0-alpha.2"
dependencies = [
"chrono",
- "rusqlite",
+ "futures-executor",
"serde",
"serde_json",
+ "sqlx",
"uuid",
]
@@ -5469,6 +5459,7 @@ dependencies = [
name = "radroots_studio_app_sqlite"
version = "0.1.0"
dependencies = [
+ "futures-executor",
"radroots_core",
"radroots_event",
"radroots_event_codec",
@@ -5477,8 +5468,8 @@ dependencies = [
"radroots_studio_app_sync",
"radroots_studio_app_view",
"radroots_trade",
- "rusqlite",
"serde_json",
+ "sqlx",
"thiserror 2.0.18",
"uuid",
]
@@ -5944,31 +5935,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
-name = "rsqlite-vfs"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
-dependencies = [
- "hashbrown 0.16.1",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "rusqlite"
-version = "0.39.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e"
-dependencies = [
- "bitflags 2.11.1",
- "fallible-iterator",
- "fallible-streaming-iterator",
- "hashlink 0.11.1",
- "libsqlite3-sys",
- "smallvec",
- "sqlite-wasm-rs",
-]
-
-[[package]]
name = "rust-embed"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6726,18 +6692,6 @@ dependencies = [
]
[[package]]
-name = "sqlite-wasm-rs"
-version = "0.5.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75"
-dependencies = [
- "cc",
- "js-sys",
- "rsqlite-vfs",
- "wasm-bindgen",
-]
-
-[[package]]
name = "sqlx"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -26,6 +26,7 @@ readme = "README.md"
[workspace.dependencies]
chrono = { version = "0.4", default-features = false, features = ["clock"] }
+futures-executor = "0.3"
futures-util = "0.3"
gpui = "0.2.2"
gpui-component = "0.5.1"
@@ -54,9 +55,9 @@ radroots_studio_app_sqlite = { path = "crates/store", version = "0.1.0" }
radroots_studio_app_state = { path = "crates/state", version = "0.1.0" }
radroots_studio_app_sync = { path = "crates/sync", version = "0.1.0" }
radroots_studio_app_ui = { path = "crates/ui", version = "0.1.0" }
-rusqlite = { version = "0.39.0", features = ["bundled"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
+sqlx = { version = "0.9.0", default-features = false, features = ["derive", "sqlite-bundled"] }
thiserror = "2"
tokio = { version = "1", features = ["macros", "net", "rt", "sync"] }
tokio-tungstenite = "0.26.2"
diff --git a/crates/desktop/Cargo.toml b/crates/desktop/Cargo.toml
@@ -42,6 +42,7 @@ tracing.workspace = true
uuid.workspace = true
[dev-dependencies]
+radroots_studio_app_sqlite = { workspace = true, features = ["test-support"] }
tracing-subscriber.workspace = true
[lints]
diff --git a/crates/desktop/src/accounts.rs b/crates/desktop/src/accounts.rs
@@ -292,7 +292,7 @@ fn default_farmer_surface_activation(account_id: &str) -> AccountSurfaceActivati
AccountSurfaceActivationProjection::new(
account_id,
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
- FarmerActivationProjection::active(FarmId::new()),
+ FarmerActivationProjection::active(FarmId::generate()),
)
}
@@ -454,7 +454,7 @@ mod tests {
account_id.as_str(),
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
radroots_studio_app_view::FarmerActivationProjection::active(
- radroots_studio_app_view::FarmId::new(),
+ radroots_studio_app_view::FarmId::generate(),
),
);
sqlite_store
@@ -592,7 +592,7 @@ mod tests {
second_account_id.as_str(),
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
radroots_studio_app_view::FarmerActivationProjection::active(
- radroots_studio_app_view::FarmId::new(),
+ radroots_studio_app_view::FarmId::generate(),
),
);
sqlite_store
@@ -653,7 +653,7 @@ mod tests {
first_account_id.as_str(),
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
radroots_studio_app_view::FarmerActivationProjection::active(
- radroots_studio_app_view::FarmId::new(),
+ radroots_studio_app_view::FarmId::generate(),
),
);
sqlite_store
@@ -703,7 +703,7 @@ mod tests {
first_account_id.as_str(),
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
radroots_studio_app_view::FarmerActivationProjection::active(
- radroots_studio_app_view::FarmId::new(),
+ radroots_studio_app_view::FarmId::generate(),
),
))
.expect("first activation should save");
@@ -712,7 +712,7 @@ mod tests {
second_account_id.as_str(),
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
radroots_studio_app_view::FarmerActivationProjection::active(
- radroots_studio_app_view::FarmId::new(),
+ radroots_studio_app_view::FarmId::generate(),
),
))
.expect("second activation should save");
diff --git a/crates/desktop/src/pack_day_host_handoff.rs b/crates/desktop/src/pack_day_host_handoff.rs
@@ -348,8 +348,8 @@ mod tests {
fn sample_bundle(bundle_directory: &PathBuf) -> PackDayExportBundle {
PackDayExportBundle {
- fulfillment_window_id: radroots_studio_app_view::FulfillmentWindowId::new(),
- export_instance_id: radroots_studio_app_view::PackDayExportInstanceId::new(),
+ fulfillment_window_id: radroots_studio_app_view::FulfillmentWindowId::generate(),
+ export_instance_id: radroots_studio_app_view::PackDayExportInstanceId::generate(),
generated_at_utc: "2026-04-23T15:00:00Z".to_owned(),
bundle_directory: bundle_directory.to_string_lossy().into_owned(),
artifacts: vec![
diff --git a/crates/desktop/src/pack_day_print.rs b/crates/desktop/src/pack_day_print.rs
@@ -936,8 +936,8 @@ mod tests {
fn sample_bundle(bundle_directory: &PathBuf) -> PackDayExportBundle {
PackDayExportBundle {
- fulfillment_window_id: radroots_studio_app_view::FulfillmentWindowId::new(),
- export_instance_id: PackDayExportInstanceId::new(),
+ fulfillment_window_id: radroots_studio_app_view::FulfillmentWindowId::generate(),
+ export_instance_id: PackDayExportInstanceId::generate(),
generated_at_utc: "2026-04-23T15:00:00Z".to_owned(),
bundle_directory: bundle_directory.to_string_lossy().into_owned(),
artifacts: vec![
@@ -1285,7 +1285,7 @@ mod tests {
write_all_artifacts(temp_dir.path());
let bundle = sample_bundle(temp_dir.path());
let mut request = sample_batch_request(&bundle);
- request.export_instance_id = PackDayExportInstanceId::new();
+ request.export_instance_id = PackDayExportInstanceId::generate();
let artifacts = request.artifacts.clone();
let error = plan_pack_day_batch_print(&bundle, &request)
@@ -1434,7 +1434,7 @@ mod tests {
#[test]
fn batch_execution_rejects_empty_command_plan_without_submitting_artifacts() {
let plan = PackDayBatchPrintCommandPlan {
- export_instance_id: PackDayExportInstanceId::new(),
+ export_instance_id: PackDayExportInstanceId::generate(),
plans: Vec::new(),
};
let mut submitted = false;
@@ -1576,7 +1576,7 @@ mod tests {
#[test]
fn cleanup_prepared_customer_label_asset_root_removes_existing_directories() {
let root = prepared_customer_label_asset_root();
- let stale_directory = root.join(PackDayExportInstanceId::new().to_string());
+ let stale_directory = root.join(PackDayExportInstanceId::generate().to_string());
fs::create_dir_all(&stale_directory).expect("stale prepared directory should create");
fs::write(stale_directory.join("stale.ps"), "stale").expect("stale asset should write");
diff --git a/crates/desktop/src/runtime.rs b/crates/desktop/src/runtime.rs
@@ -58,7 +58,7 @@ use radroots_sdk::{
TRADE_REVISION_DECISION_OPERATION_KIND, TRADE_REVISION_PROPOSAL_OPERATION_KIND,
TRADE_SUBMIT_OPERATION_KIND, TargetSet,
};
-use radroots_sql_core::SqliteExecutor;
+use radroots_sql_core::SqlxSqliteExecutor;
use radroots_studio_app_core::{
AppBuildIdentity, AppDesktopRuntimePaths, AppRuntimeCapture, AppRuntimeMode,
AppRuntimePathsError, AppRuntimeSnapshot, AppSdkConfig, AppSdkDiagnostics,
@@ -75,10 +75,11 @@ use radroots_studio_app_remote_signer::{
RadrootsAppRemoteSignerApprovedSession, RadrootsAppRemoteSignerPendingSession,
};
use radroots_studio_app_sqlite::{
- APP_ACTIVITY_CONTEXT_LIMIT, AppLocalInteropImportReport, AppSdkWorkflowReceiptInput,
- AppSdkWorkflowReceiptSourceKind, AppSdkWorkflowReceiptState, AppSqliteError, AppSqliteStore,
- BuyerOrderRuntimeStoreExport, BuyerOrderRuntimeStoreLine, BuyerRepeatDemandApplyOutcome,
- DatabaseTarget, SelectedBuyerOrderScope, SellerOrderDecisionExport, StoredPendingSyncOperation,
+ APP_ACTIVITY_CONTEXT_LIMIT, AppLocalInteropImportReport, AppRelayIngestFailureInput,
+ AppRelayIngestSuccessInput, AppSdkWorkflowReceiptInput, AppSdkWorkflowReceiptSourceKind,
+ AppSdkWorkflowReceiptState, AppSqliteError, AppSqliteStore, BuyerOrderRuntimeStoreExport,
+ BuyerOrderRuntimeStoreLine, BuyerRepeatDemandApplyOutcome, DatabaseTarget,
+ SelectedBuyerOrderScope, SellerOrderDecisionExport, StoredPendingSyncOperation,
StoredRelayIngestCursor, StoredSyncConflict, derive_farm_rules_readiness,
projected_order_id_from_trade_request,
};
@@ -4220,27 +4221,27 @@ impl DesktopAppRuntimeState {
.map_or(started_unix_seconds, |last_event_created_at| {
started_unix_seconds.max(last_event_created_at)
});
- sqlite_store.record_relay_ingest_success(
- APP_DIRECT_RELAY_INGEST_SCOPE_KEY,
- relay.relay_url.as_str(),
+ sqlite_store.record_relay_ingest_success(AppRelayIngestSuccessInput {
+ scope_key: APP_DIRECT_RELAY_INGEST_SCOPE_KEY,
+ relay_url: relay.relay_url.as_str(),
cursor_since_unix_seconds,
- relay.last_event_created_at_unix_seconds,
+ last_event_created_at_unix_seconds: relay.last_event_created_at_unix_seconds,
started_at,
started_unix_seconds,
completed_at,
completed_unix_seconds,
- )?;
+ })?;
}
for failure in &receipt.failed_relays {
- sqlite_store.record_relay_ingest_failure(
- APP_DIRECT_RELAY_INGEST_SCOPE_KEY,
- failure.relay_url.as_str(),
+ sqlite_store.record_relay_ingest_failure(AppRelayIngestFailureInput {
+ scope_key: APP_DIRECT_RELAY_INGEST_SCOPE_KEY,
+ relay_url: failure.relay_url.as_str(),
started_at,
started_unix_seconds,
completed_at,
completed_unix_seconds,
- failure.error.as_str(),
- )?;
+ error_message: failure.error.as_str(),
+ })?;
}
Ok(())
@@ -5910,7 +5911,7 @@ impl DesktopAppRuntimeState {
fn open_shared_runtime_store_store(
&self,
- ) -> Result<Option<RuntimeStore<SqliteExecutor>>, AppSqliteError> {
+ ) -> Result<Option<RuntimeStore<SqlxSqliteExecutor>>, AppSqliteError> {
let Some(shared_accounts_paths) = self.shared_accounts_paths.as_ref() else {
return Ok(None);
};
@@ -5925,7 +5926,7 @@ impl DesktopAppRuntimeState {
source,
})?;
}
- let executor = SqliteExecutor::open(database_path.as_path()).map_err(|source| {
+ let executor = SqlxSqliteExecutor::open(database_path.as_path()).map_err(|source| {
AppSqliteError::RuntimeStoreSql {
operation: "open shared runtime store database",
source,
@@ -6246,7 +6247,7 @@ impl DesktopAppRuntimeState {
source,
})?;
}
- let executor = SqliteExecutor::open(database_path.as_path()).map_err(|source| {
+ let executor = SqlxSqliteExecutor::open(database_path.as_path()).map_err(|source| {
AppSqliteError::RuntimeStoreSql {
operation: "open shared runtime store database",
source,
@@ -6289,7 +6290,7 @@ impl DesktopAppRuntimeState {
source,
})?;
}
- let executor = SqliteExecutor::open(database_path.as_path()).map_err(|source| {
+ let executor = SqlxSqliteExecutor::open(database_path.as_path()).map_err(|source| {
AppSqliteError::RuntimeStoreSql {
operation: "open shared runtime store database",
source,
@@ -9880,7 +9881,7 @@ mod tests {
TRADE_DECISION_OPERATION_KIND, TRADE_REVISION_DECISION_OPERATION_KIND,
TRADE_SUBMIT_OPERATION_KIND,
};
- use radroots_sql_core::{SqlExecutor, SqliteExecutor};
+ use radroots_sql_core::{SqlExecutor, SqlxSqliteExecutor};
use radroots_studio_app_core::{
AppDesktopRuntimePaths, AppRuntimeHostEnvironment, AppRuntimePlatform,
AppSdkLifecycleState, AppSdkProjectionLifecycleState, AppSdkPublicFarmLocation,
@@ -10169,8 +10170,8 @@ mod tests {
farm_pubkey: String,
source: &str,
) -> AppPublishPayload {
- let farm_id = FarmId::new();
- let product_id = ProductId::new();
+ let farm_id = FarmId::generate();
+ let product_id = ProductId::generate();
AppPublishPayload::Listing(AppListingPublishPayload {
context: AppPublishContext::new(account_id.to_owned(), source),
product_id,
@@ -10185,7 +10186,7 @@ mod tests {
price_minor_units: Some(750),
price_currency: "USD".to_owned(),
stock_quantity: Some(12),
- availability_window_id: Some(FulfillmentWindowId::new()),
+ availability_window_id: Some(FulfillmentWindowId::generate()),
availability_starts_at: Some("2099-05-25T14:00:00Z".to_owned()),
availability_ends_at: Some("2099-05-25T18:00:00Z".to_owned()),
fulfillment_method: Some("pickup".to_owned()),
@@ -10463,7 +10464,7 @@ mod tests {
let account_id = manager
.generate_identity(Some("Farmer".to_owned()), true)
.expect("local signing account should generate");
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
let payload = AppPublishPayload::FarmProfile(AppFarmProfilePublishPayload {
context: AppPublishContext::new(account_id.to_string(), "farm_setup")
.with_source_local_event_id("app:local_work:farm:direct"),
@@ -10538,13 +10539,13 @@ mod tests {
.expect("buyer signer lookup should succeed")
.expect("buyer account should have local signer");
let seller_identity = RadrootsIdentity::generate();
- let product_id = ProductId::new();
- let order_id = OrderId::new();
+ let product_id = ProductId::generate();
+ let order_id = OrderId::generate();
let listing_event_id = "1".repeat(64);
let listing_addr = format!(
"30402:{}:{}",
seller_identity.public_key_hex(),
- super::d_tag_from_uuid(ProductId::new().as_uuid())
+ super::d_tag_from_uuid(ProductId::generate().as_uuid())
);
let order_document = RadrootsOrderRequest {
order_id: test_order_id(order_id.to_string().as_str()),
@@ -10584,7 +10585,7 @@ mod tests {
context: AppPublishContext::new(account_id.to_string(), "place_personal_order")
.with_source_local_event_id("app:local_work:order_request:direct"),
order_id,
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
status: Some("needs_action".to_owned()),
order_document_json: Some(json!({"document": {"order": order_document}})),
listing_addr: Some(listing_addr),
@@ -10633,8 +10634,8 @@ mod tests {
let buyer_pubkey = "1111111111111111111111111111111111111111111111111111111111111111";
let payload = AppPublishPayload::OrderDecision(AppOrderDecisionPublishPayload {
context: AppPublishContext::new(account_id.to_string(), "seller_order_decision"),
- app_order_id: OrderId::new(),
- farm_id: FarmId::new(),
+ app_order_id: OrderId::generate(),
+ farm_id: FarmId::generate(),
trade_order_id: "order-1".to_owned(),
request_event_id: test_event_id_seed("order-request-event-1"),
listing_event_id: Some(test_event_id_seed("listing-event-1")),
@@ -10685,8 +10686,8 @@ mod tests {
.get_signing_identity(&seller_account_id)
.expect("seller signer lookup should succeed")
.expect("seller account should have local signer");
- let app_order_id = OrderId::new();
- let farm_id = FarmId::new();
+ let app_order_id = OrderId::generate();
+ let farm_id = FarmId::generate();
let listing_addr = format!(
"30402:{}:AAAAAAAAAAAAAAAAAAAAAg",
seller_identity.public_key_hex()
@@ -10908,8 +10909,8 @@ mod tests {
.expect("seller signing lookup should succeed")
.expect("seller account should have local signer");
let seller_pubkey = identity.public_key_hex();
- let farm_id = FarmId::new();
- let product_id = ProductId::new();
+ let farm_id = FarmId::generate();
+ let product_id = ProductId::generate();
let listing_d_tag = super::d_tag_from_uuid(product_id.as_uuid());
let projected_product_id = deterministic_cli_listing_product_id(
Some(seller_pubkey.as_str()),
@@ -10929,7 +10930,7 @@ mod tests {
price_minor_units: Some(450),
price_currency: "USD".to_owned(),
stock_quantity: Some(6),
- availability_window_id: Some(FulfillmentWindowId::new()),
+ availability_window_id: Some(FulfillmentWindowId::generate()),
availability_starts_at: Some("2099-04-25T14:00:00Z".to_owned()),
availability_ends_at: Some("2099-04-25T18:00:00Z".to_owned()),
fulfillment_method: Some("pickup".to_owned()),
@@ -11117,7 +11118,7 @@ mod tests {
.account
.account_id
.clone();
- let pending_farm_id = FarmId::new();
+ let pending_farm_id = FarmId::generate();
runtime
.lock_state_mut()
.enqueue_selected_account_sync_operations(vec![pending_sync_upsert(
@@ -11220,7 +11221,7 @@ mod tests {
PendingSyncOperation::from_publish_payload(payload, "2026-05-24T12:00:00Z")
.expect("typed listing publish work should serialize");
let unsupported_operation = PendingSyncOperation::new(
- SyncAggregateRef::Product(ProductId::new()),
+ SyncAggregateRef::Product(ProductId::generate()),
SyncOperationKind::Delete,
"{}",
"2026-05-24T12:01:00Z",
@@ -11316,8 +11317,8 @@ mod tests {
let seller_pubkey = "2222222222222222222222222222222222222222222222222222222222222222";
let payload = AppPublishPayload::OrderRequest(AppOrderRequestPublishPayload {
context: AppPublishContext::new(account_id.to_string(), "order_missing_listing_relay"),
- order_id: OrderId::new(),
- farm_id: FarmId::new(),
+ order_id: OrderId::generate(),
+ farm_id: FarmId::generate(),
status: Some("needs_action".to_owned()),
order_document_json: Some(json!({"document": {"order": {}}})),
listing_addr: Some(format!("30402:{seller_pubkey}:listing-key")),
@@ -11326,7 +11327,7 @@ mod tests {
buyer_pubkey: Some(identity.public_key_hex()),
seller_pubkey: Some(seller_pubkey.to_owned()),
items: vec![AppOrderRequestItemPayload {
- product_id: ProductId::new(),
+ product_id: ProductId::generate(),
quantity: 1,
}],
currency_code: Some("USD".to_owned()),
@@ -11390,7 +11391,7 @@ mod tests {
fn runtime_direct_relay_transport_rejects_missing_account_publish_work() {
let relay = ThreadedAckRelay::spawn();
let manager = RadrootsNostrAccountsManager::new_in_memory();
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
let missing_account_id = RadrootsIdentity::generate().id();
let payload = AppPublishPayload::FarmProfile(AppFarmProfilePublishPayload {
context: AppPublishContext::new(missing_account_id.to_string(), "farm_setup"),
@@ -11426,7 +11427,7 @@ mod tests {
.expect("watch-only account");
let payload = AppPublishPayload::FarmProfile(AppFarmProfilePublishPayload {
context: AppPublishContext::new(account_id.to_string(), "farm_setup"),
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
display_name: "North field farm".to_owned(),
readiness: Some(FarmReadiness::Ready),
});
@@ -11472,7 +11473,7 @@ mod tests {
.expect("mismatched secret");
let payload = AppPublishPayload::FarmProfile(AppFarmProfilePublishPayload {
context: AppPublishContext::new(account_id.to_string(), "farm_setup"),
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
display_name: "North field farm".to_owned(),
readiness: Some(FarmReadiness::Ready),
});
@@ -11811,8 +11812,8 @@ mod tests {
fn runtime_product_publishable_save_enqueues_typed_listing_publish_work() {
let (runtime, paths) = bootstrapped_runtime("publishable_product_listing_work");
let (account_id, farm_id) = provision_ready_farmer_account(&runtime);
- let pickup_location_id = PickupLocationId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let pickup_location_id = PickupLocationId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
runtime
.save_farm_rules_projection(FarmRulesProjection {
@@ -11958,8 +11959,8 @@ mod tests {
fn runtime_product_publishable_save_returns_error_when_sdk_listing_enqueue_fails() {
let (runtime, paths) = bootstrapped_runtime("publishable_product_listing_sdk_failure");
let (_account_id, farm_id) = provision_ready_farmer_account(&runtime);
- let pickup_location_id = PickupLocationId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let pickup_location_id = PickupLocationId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
runtime
.save_farm_rules_projection(FarmRulesProjection {
@@ -12128,8 +12129,8 @@ mod tests {
fn runtime_product_stock_update_retries_sdk_listing_enqueue_after_local_save() {
let (runtime, paths) = bootstrapped_runtime("stock_listing_sdk_retry");
let (_account_id, farm_id) = provision_ready_farmer_account(&runtime);
- let pickup_location_id = PickupLocationId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let pickup_location_id = PickupLocationId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
runtime
.save_farm_rules_projection(FarmRulesProjection {
@@ -12275,9 +12276,9 @@ mod tests {
fn runtime_product_stale_availability_save_records_blocker_without_publish_work() {
let (runtime, paths) = bootstrapped_runtime("stale_product_listing_work");
let (account_id, farm_id) = provision_ready_farmer_account(&runtime);
- let pickup_location_id = PickupLocationId::new();
- let active_window_id = FulfillmentWindowId::new();
- let stale_window_id = FulfillmentWindowId::new();
+ let pickup_location_id = PickupLocationId::generate();
+ let active_window_id = FulfillmentWindowId::generate();
+ let stale_window_id = FulfillmentWindowId::generate();
runtime
.save_farm_rules_projection(FarmRulesProjection {
@@ -12335,8 +12336,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch("PRAGMA foreign_keys = OFF;")
+ .execute_test_sql("PRAGMA foreign_keys = OFF;")
.expect("foreign keys should disable for stale fixture");
let save_result = runtime.save_product_editor_draft(ProductEditorDraft {
title: "Salad mix".to_owned(),
@@ -12354,8 +12354,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch("PRAGMA foreign_keys = ON;")
+ .execute_test_sql("PRAGMA foreign_keys = ON;")
.expect("foreign keys should restore");
assert!(save_result.expect("stale product editor save should succeed"));
@@ -12586,7 +12585,7 @@ mod tests {
fn runtime_partial_sync_result_dequeues_successful_prefix_only() {
let runtime = memory_runtime();
let (account_id, farm_id) = provision_ready_farmer_account(&runtime);
- let product_id = ProductId::new();
+ let product_id = ProductId::generate();
runtime
.lock_state_mut()
.enqueue_selected_account_sync_operations(vec![
@@ -13257,8 +13256,8 @@ mod tests {
let database_path = paths
.shared_runtime_store_database_path()
.expect("shared runtime store path");
- let executor =
- SqliteExecutor::open(database_path.as_path()).expect("open shared runtime store db");
+ let executor = SqlxSqliteExecutor::open(database_path.as_path())
+ .expect("open shared runtime store db");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate shared runtime store");
store
@@ -13299,7 +13298,7 @@ mod tests {
#[test]
fn runtime_app_local_work_without_resolved_pubkey_is_non_exportable() {
let (runtime, paths) = bootstrapped_runtime("app_local_work_unresolved_pubkey");
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
let account = SelectedAccountProjection::new(
AccountSummary {
account_id: "acct_unresolved".to_owned(),
@@ -13329,7 +13328,7 @@ mod tests {
.expect("unresolved farm local work should append");
state
.append_app_listing_local_work_record(
- ProductId::new(),
+ ProductId::generate(),
&ProductEditorDraft {
title: "Eggs".to_owned(),
subtitle: "Fresh eggs".to_owned(),
@@ -14086,8 +14085,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update products
set availability_window_id = '{fulfillment_window_id}'
where id = '{product_id}'"
@@ -14200,8 +14198,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update orders
set status = 'packed', updated_at = '2026-04-20T09:45:00Z'
where id = '{order_id}' and farm_id = '{farm_id}'"
@@ -14252,7 +14249,7 @@ mod tests {
);
let mut persisted_state = runtime.lock_state().state_store.persisted_state().clone();
persisted_state.seller.orders_query.fulfillment_window_id =
- Some(FulfillmentWindowId::new());
+ Some(FulfillmentWindowId::generate());
let mut repository =
FileBackedAppStateRepository::new(paths.app.data.join(APP_STATE_FILE_NAME));
repository
@@ -14287,7 +14284,7 @@ mod tests {
assert!(runtime.open_pack_day(None).expect("pack day should open"));
let mut persisted_state = runtime.lock_state().state_store.persisted_state().clone();
- let stale_fulfillment_window_id = FulfillmentWindowId::new();
+ let stale_fulfillment_window_id = FulfillmentWindowId::generate();
persisted_state.seller.pack_day_query.fulfillment_window_id =
Some(stale_fulfillment_window_id);
let mut repository =
@@ -14346,12 +14343,12 @@ mod tests {
let cloned_runtime = runtime.clone();
let today_agenda = TodayAgendaProjection {
farm: Some(FarmSummary {
- farm_id: radroots_studio_app_view::FarmId::new(),
+ farm_id: radroots_studio_app_view::FarmId::generate(),
display_name: "North field farm".to_owned(),
readiness: FarmReadiness::Incomplete,
}),
summary: Some(TodaySummary {
- farm_id: radroots_studio_app_view::FarmId::new(),
+ farm_id: radroots_studio_app_view::FarmId::generate(),
orders_needing_action: 2,
low_stock_products: 1,
draft_products: 3,
@@ -14546,8 +14543,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch("DROP TABLE activity_events")
+ .execute_test_sql("DROP TABLE activity_events")
.expect("activity table should drop");
assert!(matches!(
@@ -14701,8 +14697,8 @@ mod tests {
fn runtime_personal_search_queries_refresh_repository_backed_marketplace_projection() {
let runtime = memory_runtime();
let (account_id, farm_id) = provision_ready_farmer_account(&runtime);
- let pickup_location_id = PickupLocationId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let pickup_location_id = PickupLocationId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let sql = format!(
"insert into pickup_locations (
id,
@@ -14762,8 +14758,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&sql)
+ .execute_test_sql(&sql)
.expect("buyer search workspace should seed");
let salad_mix_id = seed_product(
&runtime,
@@ -14788,8 +14783,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update products
set availability_window_id = '{fulfillment_window_id}'
where id in ('{salad_mix_id}', '{pea_shoots_id}')"
@@ -14875,8 +14869,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update products
set availability_window_id = '{fulfillment_window_id}'
where id = '{product_id}'"
@@ -14969,8 +14962,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update products
set availability_window_id = '{first_window_id}'
where id = '{first_product_id}'"
@@ -14987,7 +14979,7 @@ mod tests {
.expect("first buyer product should add to cart")
);
- let other_farm_id = FarmId::new();
+ let other_farm_id = FarmId::generate();
runtime
.lock_state()
.sqlite_store
@@ -15020,8 +15012,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update products
set availability_window_id = '{second_window_id}'
where id = '{second_product_id}'"
@@ -15136,8 +15127,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update products
set availability_window_id = '{fulfillment_window_id}'
where id = '{product_id}'"
@@ -15211,8 +15201,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update products
set availability_window_id = '{fulfillment_window_id}'
where id = '{product_id}'"
@@ -15306,7 +15295,7 @@ mod tests {
#[test]
fn runtime_guest_order_review_requires_account_before_order_write() {
let runtime = memory_runtime();
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
runtime
.lock_state()
.sqlite_store
@@ -15344,8 +15333,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update products
set availability_window_id = '{fulfillment_window_id}'
where id = '{product_id}'"
@@ -15416,20 +15404,14 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .query_row("select count(*) from orders", [], |row| row.get(0))
+ .query_test_i64("select count(*) from orders")
.expect("order count should load");
let coordination_count: i64 = runtime
.lock_state()
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .query_row(
- "select count(*) from buyer_order_coordination_records",
- [],
- |row| row.get(0),
- )
+ .query_test_i64("select count(*) from buyer_order_coordination_records")
.expect("coordination count should load");
assert_eq!(order_count, 0);
assert_eq!(coordination_count, 0);
@@ -15797,11 +15779,9 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute(
- "update products set listing_bin_id = 'mutated-bin' where id = ?1",
- [product_id.to_string()],
- )
+ .execute_test_sql(&format!(
+ "update products set listing_bin_id = 'mutated-bin' where id = '{product_id}'"
+ ))
.expect("listing projection should mutate after cart snapshot");
assert!(
runtime
@@ -15969,11 +15949,9 @@ mod tests {
let state = runtime.lock_state_mut();
let sqlite_store = state.sqlite_store.as_ref().expect("sqlite store");
sqlite_store
- .connection()
- .execute(
- "update orders set status = 'scheduled' where id = ?1",
- [order_id.to_string()],
- )
+ .execute_test_sql(&format!(
+ "update orders set status = 'scheduled' where id = '{order_id}'"
+ ))
.expect("buyer order status should mutate before retry refresh");
}
unblock_shared_runtime_store_database(&paths);
@@ -16217,8 +16195,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update products
set availability_window_id = '{fulfillment_window_id}'
where id = '{product_id}'"
@@ -16591,8 +16568,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&format!(
+ .execute_test_sql(&format!(
"update products
set availability_window_id = '{fulfillment_window_id}'
where id in ('{available_product_id}', '{unavailable_product_id}')"
@@ -16641,11 +16617,9 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute(
- "update products set status = 'archived' where id = ?1",
- [unavailable_product_id.to_string()],
- )
+ .execute_test_sql(&format!(
+ "update products set status = 'archived' where id = '{unavailable_product_id}'"
+ ))
.expect("product should archive");
assert!(
@@ -16989,7 +16963,7 @@ mod tests {
quantity_display: "Do not trust screen strings".to_owned(),
}],
pickup_roster: vec![PackDayRosterRow {
- order_id: OrderId::new(),
+ order_id: OrderId::generate(),
order_number: "R-999".to_owned(),
customer_display_name: "Bogus".to_owned(),
}],
@@ -17045,7 +17019,7 @@ mod tests {
fn runtime_bootstrap_sweeps_prepared_pack_day_print_assets() {
let paths = temp_desktop_runtime_paths("pack_day_print_bootstrap_sweep");
let stale_root = prepared_customer_label_asset_root();
- let stale_directory = stale_root.join(PackDayExportInstanceId::new().to_string());
+ let stale_directory = stale_root.join(PackDayExportInstanceId::generate().to_string());
let _ = fs::remove_file(&stale_root);
let _ = fs::remove_dir_all(&stale_root);
fs::create_dir_all(&stale_directory).expect("stale prepared directory should create");
@@ -18248,8 +18222,8 @@ mod tests {
let runtime = memory_runtime();
let (_, farm_id) = provision_ready_farmer_account(&runtime);
let (fulfillment_window_id, order_id) = seed_order_workspace(&runtime, farm_id);
- let other_fulfillment_window_id = FulfillmentWindowId::new();
- let other_order_id = OrderId::new();
+ let other_fulfillment_window_id = FulfillmentWindowId::generate();
+ let other_order_id = OrderId::generate();
let sql = format!(
"insert into fulfillment_windows (
id,
@@ -18299,8 +18273,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&sql)
+ .execute_test_sql(&sql)
.expect("second orders workspace should seed");
assert!(
@@ -18329,8 +18302,8 @@ mod tests {
let runtime = memory_runtime();
let (_, farm_id) = provision_ready_farmer_account(&runtime);
let (fulfillment_window_id, scheduled_order_id) = seed_order_workspace(&runtime, farm_id);
- let packed_order_id = OrderId::new();
- let completed_order_id = OrderId::new();
+ let packed_order_id = OrderId::generate();
+ let completed_order_id = OrderId::generate();
let sql = format!(
"update orders
@@ -18376,8 +18349,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&sql)
+ .execute_test_sql(&sql)
.expect("order should update to scheduled");
assert!(
@@ -19222,10 +19194,10 @@ mod tests {
.expect("account should select")
);
- let default_pickup_location_id = PickupLocationId::new();
- let market_pickup_location_id = PickupLocationId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
- let blackout_period_id = BlackoutPeriodId::new();
+ let default_pickup_location_id = PickupLocationId::generate();
+ let market_pickup_location_id = PickupLocationId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let blackout_period_id = BlackoutPeriodId::generate();
let saved_projection = runtime
.save_farm_rules_projection(radroots_studio_app_view::FarmRulesProjection {
@@ -19628,8 +19600,8 @@ mod tests {
if let Some(parent) = database_path.parent() {
fs::create_dir_all(parent).expect("shared runtime store directory should create");
}
- let executor =
- SqliteExecutor::open(database_path.as_path()).expect("open shared runtime store db");
+ let executor = SqlxSqliteExecutor::open(database_path.as_path())
+ .expect("open shared runtime store db");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate shared runtime store");
let farm_key = "AAAAAAAAAAAAAAAAAAAAAA";
@@ -19745,8 +19717,8 @@ mod tests {
if let Some(parent) = database_path.parent() {
fs::create_dir_all(parent).expect("shared runtime store directory should create");
}
- let executor =
- SqliteExecutor::open(database_path.as_path()).expect("open shared runtime store db");
+ let executor = SqlxSqliteExecutor::open(database_path.as_path())
+ .expect("open shared runtime store db");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate shared runtime store");
let farm_key = "CCCCCCCCCCCCCCCCCCCCCC";
@@ -19892,8 +19864,8 @@ mod tests {
.expect("selected buyer account should resolve")
.public_identity
.public_key_hex;
- let farm_key = super::d_tag_from_uuid(FarmId::new().as_uuid());
- let listing_key = super::d_tag_from_uuid(ProductId::new().as_uuid());
+ let farm_key = super::d_tag_from_uuid(FarmId::generate().as_uuid());
+ let listing_key = super::d_tag_from_uuid(ProductId::generate().as_uuid());
let listing_addr = format!("30402:{seller_pubkey}:{listing_key}");
let listing_event_id = signed_listing_event_id(label);
let trade_order_id = format!("{label}-trade-order");
@@ -19972,7 +19944,7 @@ mod tests {
.public_identity
.public_key_hex;
let buyer_pubkey = SDK_TEST_BUYER_PUBLIC_KEY_HEX.to_owned();
- let product_id = ProductId::new();
+ let product_id = ProductId::generate();
let trade_order_id = "seller-order-decision-1";
let farm_key = super::d_tag_from_uuid(farm_id.as_uuid());
let listing_key = super::d_tag_from_uuid(product_id.as_uuid());
@@ -20038,7 +20010,7 @@ mod tests {
.public_identity
.public_key_hex;
let buyer_pubkey = SDK_TEST_BUYER_PUBLIC_KEY_HEX.to_owned();
- let product_id = ProductId::new();
+ let product_id = ProductId::generate();
let trade_order_id = "seller-order-decision-1";
let farm_key = super::d_tag_from_uuid(farm_id.as_uuid());
let listing_key = super::d_tag_from_uuid(product_id.as_uuid());
@@ -20171,8 +20143,8 @@ mod tests {
if let Some(parent) = database_path.parent() {
fs::create_dir_all(parent).expect("shared runtime store directory should create");
}
- let executor =
- SqliteExecutor::open(database_path.as_path()).expect("open shared runtime store db");
+ let executor = SqlxSqliteExecutor::open(database_path.as_path())
+ .expect("open shared runtime store db");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate shared runtime store");
let listing_addr = format!("30402:{seller_pubkey}:{listing_key}");
@@ -20277,8 +20249,8 @@ mod tests {
if let Some(parent) = database_path.parent() {
fs::create_dir_all(parent).expect("shared runtime store directory should create");
}
- let executor =
- SqliteExecutor::open(database_path.as_path()).expect("open shared runtime store db");
+ let executor = SqlxSqliteExecutor::open(database_path.as_path())
+ .expect("open shared runtime store db");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate shared runtime store");
let order = RadrootsOrderRequest {
@@ -20374,8 +20346,8 @@ mod tests {
if let Some(parent) = database_path.parent() {
fs::create_dir_all(parent).expect("shared runtime store directory should create");
}
- let executor =
- SqliteExecutor::open(database_path.as_path()).expect("open shared runtime store db");
+ let executor = SqlxSqliteExecutor::open(database_path.as_path())
+ .expect("open shared runtime store db");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate shared runtime store");
let order = RadrootsOrderRequest {
@@ -20660,8 +20632,8 @@ mod tests {
if let Some(parent) = database_path.parent() {
fs::create_dir_all(parent).expect("shared runtime store directory should create");
}
- let executor =
- SqliteExecutor::open(database_path.as_path()).expect("open shared runtime store db");
+ let executor = SqlxSqliteExecutor::open(database_path.as_path())
+ .expect("open shared runtime store db");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate shared runtime store");
let created_at = test_event_created_at(record_id, 1_774_000_020);
@@ -20725,8 +20697,8 @@ mod tests {
let database_path = paths
.shared_runtime_store_database_path()
.expect("shared runtime store path");
- let executor =
- SqliteExecutor::open(database_path.as_path()).expect("open shared runtime store db");
+ let executor = SqlxSqliteExecutor::open(database_path.as_path())
+ .expect("open shared runtime store db");
executor
.exec(
"UPDATE runtime_store_record
@@ -20744,8 +20716,8 @@ mod tests {
let database_path = paths
.shared_runtime_store_database_path()
.expect("shared runtime store path");
- let executor =
- SqliteExecutor::open(database_path.as_path()).expect("open shared runtime store db");
+ let executor = SqlxSqliteExecutor::open(database_path.as_path())
+ .expect("open shared runtime store db");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate shared runtime store");
let pubkey = "2222222222222222222222222222222222222222222222222222222222222222";
@@ -20928,8 +20900,8 @@ mod tests {
let database_path = paths
.shared_runtime_store_database_path()
.expect("shared runtime store path");
- let executor =
- SqliteExecutor::open(database_path.as_path()).expect("open shared runtime store db");
+ let executor = SqlxSqliteExecutor::open(database_path.as_path())
+ .expect("open shared runtime store db");
let store = RuntimeStore::new(executor);
store
.list_records_after_seq(0, 100)
@@ -20973,12 +20945,9 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .query_row(
- "select status from orders where id = ?1 limit 1",
- [order_id.to_string()],
- |row| row.get::<_, String>(0),
- )
+ .query_test_string(&format!(
+ "select status from orders where id = '{order_id}' limit 1"
+ ))
.expect("order status should load")
}
@@ -20991,12 +20960,9 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .query_row(
- "select workflow_agreement from orders where id = ?1 limit 1",
- [order_id.to_string()],
- |row| row.get::<_, String>(0),
- )
+ .query_test_string(&format!(
+ "select workflow_agreement from orders where id = '{order_id}' limit 1"
+ ))
.expect("order workflow agreement should load")
}
@@ -21345,7 +21311,7 @@ mod tests {
account_id,
SelectedSurfaceProjection::new(active_surface),
if farmer_active {
- FarmerActivationProjection::active(FarmId::new())
+ FarmerActivationProjection::active(FarmId::generate())
} else {
FarmerActivationProjection::inactive()
},
@@ -21364,7 +21330,7 @@ mod tests {
account_id: &str,
active_surface: ActiveSurface,
) -> FarmId {
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
let activation = AccountSurfaceActivationProjection::new(
account_id,
SelectedSurfaceProjection::new(active_surface),
@@ -21389,7 +21355,7 @@ mod tests {
stock_count: Option<u32>,
updated_at: &str,
) -> radroots_studio_app_view::ProductId {
- let product_id = radroots_studio_app_view::ProductId::new();
+ let product_id = radroots_studio_app_view::ProductId::generate();
let stock_count = stock_count
.map(|value| value.to_string())
.unwrap_or_else(|| "null".to_owned());
@@ -21432,8 +21398,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&sql)
+ .execute_test_sql(&sql)
.expect("product should seed");
product_id
@@ -21446,8 +21411,8 @@ mod tests {
farm_display_name: &str,
fulfillment_label: &str,
) -> FulfillmentWindowId {
- let pickup_location_id = PickupLocationId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let pickup_location_id = PickupLocationId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let sql = format!(
"insert into pickup_locations (
id,
@@ -21530,8 +21495,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&sql)
+ .execute_test_sql(&sql)
.expect("buyer marketplace support should seed");
fulfillment_window_id
@@ -21609,9 +21573,9 @@ mod tests {
runtime: &DesktopAppRuntime,
farm_id: FarmId,
) -> (FulfillmentWindowId, OrderId) {
- let pickup_location_id = PickupLocationId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
- let order_id = OrderId::new();
+ let pickup_location_id = PickupLocationId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let order_id = OrderId::generate();
let sql = format!(
"insert into pickup_locations (
id,
@@ -21695,8 +21659,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&sql)
+ .execute_test_sql(&sql)
.expect("orders workspace should seed");
(fulfillment_window_id, order_id)
@@ -21707,8 +21670,8 @@ mod tests {
farm_id: FarmId,
source_fulfillment_window_id: FulfillmentWindowId,
) -> (FulfillmentWindowId, OrderId) {
- let fulfillment_window_id = FulfillmentWindowId::new();
- let order_id = OrderId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let order_id = OrderId::generate();
let sql = format!(
"insert into fulfillment_windows (
id,
@@ -21758,8 +21721,7 @@ mod tests {
.sqlite_store
.as_ref()
.expect("sqlite store")
- .connection()
- .execute_batch(&sql)
+ .execute_test_sql(&sql)
.expect("second orders workspace should seed");
(fulfillment_window_id, order_id)
diff --git a/crates/desktop/src/window.rs b/crates/desktop/src/window.rs
@@ -6350,7 +6350,7 @@ impl SettingsFarmPanelState {
fn add_pickup_location(&mut self, window: &mut Window, cx: &mut Context<SettingsWindowView>) {
let record = PickupLocationRecord {
- pickup_location_id: PickupLocationId::new(),
+ pickup_location_id: PickupLocationId::generate(),
farm_id: self.farm_id,
label: String::new(),
address_line: String::new(),
@@ -6400,7 +6400,7 @@ impl SettingsFarmPanelState {
.map(|pickup_location| pickup_location.pickup_location_id);
let fulfillment_window = SettingsFulfillmentWindowFormState::new(
&SettingsFulfillmentWindowDraft {
- fulfillment_window_id: FulfillmentWindowId::new(),
+ fulfillment_window_id: FulfillmentWindowId::generate(),
selected_pickup_location_id,
label: String::new(),
starts_at: String::new(),
@@ -6445,7 +6445,7 @@ impl SettingsFarmPanelState {
fn add_blackout_period(&mut self, window: &mut Window, cx: &mut Context<SettingsWindowView>) {
let blackout_period = SettingsBlackoutPeriodFormState::new(
&SettingsBlackoutPeriodDraft {
- blackout_period_id: BlackoutPeriodId::new(),
+ blackout_period_id: BlackoutPeriodId::generate(),
label: String::new(),
starts_at: String::new(),
ends_at: String::new(),
@@ -16387,7 +16387,7 @@ mod tests {
impl TestDirectory {
fn new() -> Self {
- let path = std::env::temp_dir().join(FulfillmentWindowId::new().to_string());
+ let path = std::env::temp_dir().join(FulfillmentWindowId::generate().to_string());
fs::create_dir_all(&path).unwrap();
Self { path }
}
@@ -16504,8 +16504,8 @@ mod tests {
#[test]
fn buyer_order_detail_focus_reopens_same_selected_detail() {
- let order_id = OrderId::new();
- let farm_id = FarmId::new();
+ let order_id = OrderId::generate();
+ let farm_id = FarmId::generate();
let mut runtime = summary(
HomeRoute::Personal,
TodayAgendaProjection::default(),
@@ -16540,15 +16540,15 @@ mod tests {
Some(HomeFocusedView::BuyerOrderDetail(order_id))
);
assert_eq!(
- buyer_order_detail_focus_after_open(false, &runtime, OrderId::new()),
+ buyer_order_detail_focus_after_open(false, &runtime, OrderId::generate()),
None
);
}
#[test]
fn farmer_order_detail_focus_reopens_same_selected_detail() {
- let order_id = OrderId::new();
- let farm_id = FarmId::new();
+ let order_id = OrderId::generate();
+ let farm_id = FarmId::generate();
let mut runtime = summary(
HomeRoute::Today,
TodayAgendaProjection::default(),
@@ -16581,7 +16581,7 @@ mod tests {
Some(HomeFocusedView::FarmerOrderDetail(order_id))
);
assert_eq!(
- farmer_order_detail_focus_after_open(false, &runtime, OrderId::new()),
+ farmer_order_detail_focus_after_open(false, &runtime, OrderId::generate()),
None
);
}
@@ -16620,7 +16620,10 @@ mod tests {
block_shared_runtime_store_database(&paths);
assert!(
- view.open_personal_product_detail_update(PersonalSection::Browse, ProductId::new())
+ view.open_personal_product_detail_update(
+ PersonalSection::Browse,
+ ProductId::generate()
+ )
);
assert_eq!(
view.buyer_workspace_notice.as_deref(),
@@ -16632,8 +16635,8 @@ mod tests {
fn sample_pack_day_bundle(bundle_directory: &PathBuf) -> PackDayExportBundle {
PackDayExportBundle {
- fulfillment_window_id: FulfillmentWindowId::new(),
- export_instance_id: radroots_studio_app_view::PackDayExportInstanceId::new(),
+ fulfillment_window_id: FulfillmentWindowId::generate(),
+ export_instance_id: radroots_studio_app_view::PackDayExportInstanceId::generate(),
generated_at_utc: "2026-04-23T15:00:00Z".to_owned(),
bundle_directory: bundle_directory.to_string_lossy().into_owned(),
artifacts: vec![
@@ -17079,8 +17082,8 @@ mod tests {
Some(HomeAutoFocusTarget::BuyerOrderReviewNameInput)
);
- let order_id = OrderId::new();
- let farm_id = FarmId::new();
+ let order_id = OrderId::generate();
+ let farm_id = FarmId::generate();
let mut buyer_orders = buyer_search.clone();
buyer_orders.shell_projection = AppShellProjection::new(
ActiveSurface::Personal,
@@ -17145,7 +17148,7 @@ mod tests {
Some(HomeAutoFocusTarget::FarmerSetupStart)
);
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
let incomplete_farm = FarmSummary {
farm_id,
display_name: String::new(),
@@ -17172,7 +17175,7 @@ mod tests {
);
let saved_farm = FarmSummary {
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
display_name: String::new(),
readiness: FarmReadiness::Ready,
};
@@ -17217,8 +17220,8 @@ mod tests {
ActiveSurface::Farmer,
ShellSection::Farmer(FarmerSection::Orders),
);
- let farmer_order_id = OrderId::new();
- let farmer_order_farm_id = FarmId::new();
+ let farmer_order_id = OrderId::generate();
+ let farmer_order_farm_id = FarmId::generate();
orders.orders_projection.list.rows = vec![OrdersListRow {
order_id: farmer_order_id,
farm_id: farmer_order_farm_id,
@@ -17322,14 +17325,14 @@ mod tests {
#[test]
fn farmer_home_farm_state_distinguishes_no_farm_incomplete_and_configured() {
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
let incomplete_farm = FarmSummary {
farm_id,
display_name: String::new(),
readiness: FarmReadiness::Incomplete,
};
let configured_farm = FarmSummary {
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
display_name: String::new(),
readiness: FarmReadiness::Ready,
};
@@ -17382,7 +17385,7 @@ mod tests {
#[test]
fn pack_day_availability_tracks_the_contextual_window_projection() {
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
let mut runtime = summary(
HomeRoute::Today,
TodayAgendaProjection::default(),
@@ -17401,7 +17404,7 @@ mod tests {
runtime.pack_day_projection.projection = PackDayProjection {
fulfillment_window: Some(FulfillmentWindowSummary {
- fulfillment_window_id: FulfillmentWindowId::new(),
+ fulfillment_window_id: FulfillmentWindowId::generate(),
farm_id,
starts_at: String::new(),
ends_at: String::new(),
@@ -17417,8 +17420,8 @@ mod tests {
#[test]
fn pack_day_export_action_enabled_requires_a_window_and_exportable_rows() {
- let farm_id = FarmId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let farm_id = FarmId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let mut runtime = summary(
HomeRoute::Today,
TodayAgendaProjection::default(),
@@ -17487,10 +17490,10 @@ mod tests {
#[test]
fn pack_day_export_detail_rows_surface_bundle_and_failure_details() {
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let bundle = PackDayExportBundle {
fulfillment_window_id,
- export_instance_id: radroots_studio_app_view::PackDayExportInstanceId::new(),
+ export_instance_id: radroots_studio_app_view::PackDayExportInstanceId::generate(),
generated_at_utc: "2026-04-23T15:00:00Z".to_owned(),
bundle_directory: "exports/pack_day/window-1/20260423T150000Z".to_owned(),
artifacts: vec![
@@ -18151,7 +18154,7 @@ mod tests {
#[test]
fn saved_farm_falls_back_to_local_projection_when_today_is_empty() {
let saved_farm = FarmSummary {
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
display_name: String::new(),
readiness: FarmReadiness::Ready,
};
@@ -18388,8 +18391,8 @@ mod tests {
#[test]
fn reminder_action_target_prefers_order_detail_before_pack_day() {
- let order_id = radroots_studio_app_view::OrderId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let order_id = radroots_studio_app_view::OrderId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
assert_eq!(
reminder_action_target(&fixture_reminder(
@@ -18448,7 +18451,7 @@ mod tests {
fn reminder_deadline_text_uses_the_typed_due_label() {
let reminder = fixture_reminder(
None,
- Some(FulfillmentWindowId::new()),
+ Some(FulfillmentWindowId::generate()),
ReminderKind::FulfillmentWindow,
ReminderUrgency::Upcoming,
);
@@ -18488,7 +18491,7 @@ mod tests {
);
let due_soon = fixture_reminder(
None,
- Some(FulfillmentWindowId::new()),
+ Some(FulfillmentWindowId::generate()),
ReminderKind::FulfillmentWindow,
ReminderUrgency::DueSoon,
);
@@ -18558,7 +18561,7 @@ mod tests {
let blocking_conflict = DesktopAppSyncConflictSummary {
conflict_id: String::new(),
conflict: SyncConflict {
- aggregate: SyncAggregateRef::Farm(FarmId::new()),
+ aggregate: SyncAggregateRef::Farm(FarmId::generate()),
kind: SyncConflictKind::RevisionMismatch,
severity: SyncConflictSeverity::Blocking,
resolution: SyncConflictResolutionStatus::Unresolved,
@@ -18571,7 +18574,7 @@ mod tests {
let review_conflict = DesktopAppSyncConflictSummary {
conflict_id: String::new(),
conflict: SyncConflict {
- aggregate: SyncAggregateRef::Order(radroots_studio_app_view::OrderId::new()),
+ aggregate: SyncAggregateRef::Order(radroots_studio_app_view::OrderId::generate()),
kind: SyncConflictKind::RemoteValidationReject,
severity: SyncConflictSeverity::ReviewRequired,
resolution: SyncConflictResolutionStatus::Unresolved,
@@ -18898,8 +18901,8 @@ mod tests {
urgency: ReminderUrgency,
) -> ReminderDeadlineProjection {
ReminderDeadlineProjection {
- reminder_id: ReminderId::new(),
- farm_id: FarmId::new(),
+ reminder_id: ReminderId::generate(),
+ farm_id: FarmId::generate(),
order_id,
fulfillment_window_id,
kind,
diff --git a/crates/runtime/src/pack_day_export.rs b/crates/runtime/src/pack_day_export.rs
@@ -87,7 +87,7 @@ pub fn prepare_pack_day_export_bundle_at_data_root(
.collect::<Vec<_>>();
let bundle = PackDayExportBundle {
fulfillment_window_id: source.fulfillment_window.fulfillment_window_id,
- export_instance_id: PackDayExportInstanceId::new(),
+ export_instance_id: PackDayExportInstanceId::generate(),
generated_at_utc: generated_at.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
bundle_directory: bundle_directory.to_string_lossy().into_owned(),
artifacts,
@@ -413,8 +413,8 @@ mod tests {
}
fn sample_source() -> PackDayOutputSource {
- let farm_id = FarmId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let farm_id = FarmId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
PackDayOutputSource {
fulfillment_window: PackDayOutputWindow {
fulfillment_window_id,
@@ -436,7 +436,7 @@ mod tests {
],
pack_list: vec![
PackDayOutputPackListEntry {
- order_id: OrderId::new(),
+ order_id: OrderId::generate(),
order_number: "R-1001".to_owned(),
customer_display_name: "Casey".to_owned(),
order_state: PackDayOutputOrderState::Scheduled,
@@ -444,7 +444,7 @@ mod tests {
quantity: PackDayOutputQuantity::new(2, "bags"),
},
PackDayOutputPackListEntry {
- order_id: OrderId::new(),
+ order_id: OrderId::generate(),
order_number: "R-1002".to_owned(),
customer_display_name: "Taylor".to_owned(),
order_state: PackDayOutputOrderState::Packed,
@@ -454,13 +454,13 @@ mod tests {
],
pickup_roster: vec![
PackDayOutputCustomerOrder {
- order_id: OrderId::new(),
+ order_id: OrderId::generate(),
order_number: "R-1001".to_owned(),
customer_display_name: "Casey".to_owned(),
order_state: PackDayOutputOrderState::Scheduled,
},
PackDayOutputCustomerOrder {
- order_id: OrderId::new(),
+ order_id: OrderId::generate(),
order_number: "R-1002".to_owned(),
customer_display_name: "Taylor".to_owned(),
order_state: PackDayOutputOrderState::Packed,
diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs
@@ -2293,7 +2293,7 @@ mod tests {
custody: AccountCustody::LocalManaged,
},
SelectedSurfaceProjection::new(surface),
- FarmerActivationProjection::active(FarmId::new()),
+ FarmerActivationProjection::active(FarmId::generate()),
),
)
}
@@ -2332,7 +2332,7 @@ mod tests {
) -> PackDayExportBundle {
PackDayExportBundle {
fulfillment_window_id,
- export_instance_id: PackDayExportInstanceId::new(),
+ export_instance_id: PackDayExportInstanceId::generate(),
generated_at_utc: "2026-04-23T15:00:00Z".to_owned(),
bundle_directory: "exports/pack_day/window-1/20260423T150000Z".to_owned(),
artifacts: vec![
@@ -2487,9 +2487,9 @@ mod tests {
fn orders_and_pack_day_queries_refresh_as_local_app_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let farm_id = FarmId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
- let order_id = OrderId::new();
+ let farm_id = FarmId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let order_id = OrderId::generate();
let order_economics = TradeEconomicsProjection {
subtotal_minor_units: Some(1300),
total_minor_units: Some(1300),
@@ -2549,7 +2549,7 @@ mod tests {
};
let orders_reminders = ReminderFeedProjection {
items: vec![radroots_studio_app_view::ReminderDeadlineProjection {
- reminder_id: radroots_studio_app_view::ReminderId::new(),
+ reminder_id: radroots_studio_app_view::ReminderId::generate(),
farm_id,
order_id: Some(order_id),
fulfillment_window_id: Some(fulfillment_window_id),
@@ -2745,7 +2745,7 @@ mod tests {
fn pack_day_export_state_is_restart_ephemeral_and_skips_persistence() {
let mut store =
AppStateStore::load(FailingRepository).expect("failing repository should still load");
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let request = sample_pack_day_export_request(fulfillment_window_id);
let bundle = sample_pack_day_export_bundle(fulfillment_window_id);
@@ -2800,7 +2800,7 @@ mod tests {
fn pack_day_print_state_is_restart_ephemeral_and_skips_persistence() {
let mut store =
AppStateStore::load(FailingRepository).expect("failing repository should still load");
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let request = sample_pack_day_print_request(
fulfillment_window_id,
PackDayPrintKind::PrintCustomerLabels,
@@ -2870,7 +2870,7 @@ mod tests {
fn pack_day_batch_print_state_is_restart_ephemeral_and_skips_persistence() {
let mut store =
AppStateStore::load(FailingRepository).expect("failing repository should still load");
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let request = sample_pack_day_batch_print_request(fulfillment_window_id);
assert_eq!(
@@ -2959,7 +2959,7 @@ mod tests {
fn pack_day_host_handoff_state_is_restart_ephemeral_and_skips_persistence() {
let mut store =
AppStateStore::load(FailingRepository).expect("failing repository should still load");
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let request = sample_pack_day_host_handoff_request(
fulfillment_window_id,
PackDayHostHandoffKind::RevealBundle,
@@ -3017,8 +3017,8 @@ mod tests {
fn changing_pack_day_window_clears_stale_export_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let fulfillment_window_id = FulfillmentWindowId::new();
- let next_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let next_window_id = FulfillmentWindowId::generate();
let request = sample_pack_day_export_request(fulfillment_window_id);
assert_eq!(
@@ -3052,8 +3052,8 @@ mod tests {
fn changing_pack_day_window_clears_stale_host_handoff_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let fulfillment_window_id = FulfillmentWindowId::new();
- let next_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let next_window_id = FulfillmentWindowId::generate();
let request = sample_pack_day_host_handoff_request(
fulfillment_window_id,
PackDayHostHandoffKind::OpenPickupRoster,
@@ -3090,8 +3090,8 @@ mod tests {
fn changing_pack_day_window_clears_stale_print_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let fulfillment_window_id = FulfillmentWindowId::new();
- let next_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let next_window_id = FulfillmentWindowId::generate();
let request =
sample_pack_day_print_request(fulfillment_window_id, PackDayPrintKind::PrintPackSheet);
@@ -3126,8 +3126,8 @@ mod tests {
fn changing_pack_day_window_clears_stale_batch_print_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let fulfillment_window_id = FulfillmentWindowId::new();
- let next_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let next_window_id = FulfillmentWindowId::generate();
let request = sample_pack_day_batch_print_request(fulfillment_window_id);
assert_eq!(
@@ -3161,7 +3161,7 @@ mod tests {
fn changing_pack_day_export_state_clears_stale_host_handoff_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let export_request = sample_pack_day_export_request(fulfillment_window_id);
let host_handoff_request = sample_pack_day_host_handoff_request(
fulfillment_window_id,
@@ -3202,7 +3202,7 @@ mod tests {
fn changing_pack_day_export_state_clears_stale_print_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let export_request = sample_pack_day_export_request(fulfillment_window_id);
let print_request = sample_pack_day_print_request(
fulfillment_window_id,
@@ -3241,7 +3241,7 @@ mod tests {
fn changing_pack_day_export_state_clears_stale_batch_print_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let export_request = sample_pack_day_export_request(fulfillment_window_id);
let batch_request = sample_pack_day_batch_print_request(fulfillment_window_id);
@@ -3277,9 +3277,9 @@ mod tests {
fn replacing_pack_day_projection_with_new_window_clears_stale_host_handoff_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let farm_id = FarmId::new();
- let current_window_id = FulfillmentWindowId::new();
- let next_window_id = FulfillmentWindowId::new();
+ let farm_id = FarmId::generate();
+ let current_window_id = FulfillmentWindowId::generate();
+ let next_window_id = FulfillmentWindowId::generate();
let request = sample_pack_day_host_handoff_request(
current_window_id,
PackDayHostHandoffKind::OpenCustomerLabels,
@@ -3320,9 +3320,9 @@ mod tests {
fn replacing_pack_day_projection_with_new_window_clears_stale_print_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let farm_id = FarmId::new();
- let current_window_id = FulfillmentWindowId::new();
- let next_window_id = FulfillmentWindowId::new();
+ let farm_id = FarmId::generate();
+ let current_window_id = FulfillmentWindowId::generate();
+ let next_window_id = FulfillmentWindowId::generate();
let request =
sample_pack_day_print_request(current_window_id, PackDayPrintKind::PrintCustomerLabels);
@@ -3361,9 +3361,9 @@ mod tests {
fn replacing_pack_day_projection_with_new_window_clears_stale_batch_print_state() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let farm_id = FarmId::new();
- let current_window_id = FulfillmentWindowId::new();
- let next_window_id = FulfillmentWindowId::new();
+ let farm_id = FarmId::generate();
+ let current_window_id = FulfillmentWindowId::generate();
+ let next_window_id = FulfillmentWindowId::generate();
let request = sample_pack_day_batch_print_request(current_window_id);
assert_eq!(
@@ -3474,7 +3474,7 @@ mod tests {
fn product_editor_state_transitions_are_explicit() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let product_id = ProductId::new();
+ let product_id = ProductId::generate();
let ready_draft = ProductEditorDraft {
title: "Heirloom tomatoes".to_owned(),
subtitle: "Brandywine".to_owned(),
@@ -3483,7 +3483,7 @@ mod tests {
price_minor_units: Some(450),
price_currency: "USD".to_owned(),
stock_quantity: Some(12),
- availability_window_id: Some(FulfillmentWindowId::new()),
+ availability_window_id: Some(FulfillmentWindowId::generate()),
status: radroots_studio_app_view::ProductStatus::Draft,
};
@@ -3558,11 +3558,11 @@ mod tests {
fn product_editor_publish_blockers_require_current_fulfillment_window() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let farm_id = FarmId::new();
- let pickup_location_id = PickupLocationId::new();
- let active_window_id = FulfillmentWindowId::new();
- let stale_window_id = FulfillmentWindowId::new();
- let product_id = ProductId::new();
+ let farm_id = FarmId::generate();
+ let pickup_location_id = PickupLocationId::generate();
+ let active_window_id = FulfillmentWindowId::generate();
+ let stale_window_id = FulfillmentWindowId::generate();
+ let product_id = ProductId::generate();
let publishable_draft = ProductEditorDraft {
title: "Salad mix".to_owned(),
subtitle: "Spring blend".to_owned(),
@@ -4045,7 +4045,7 @@ mod tests {
fn replace_today_agenda_updates_in_memory_state_without_touching_repository() {
let mut store =
AppStateStore::load(FailingRepository).expect("failing repository should still load");
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
let today = TodayAgendaProjection {
farm: Some(radroots_studio_app_view::FarmSummary {
farm_id,
@@ -4138,7 +4138,7 @@ mod tests {
fn saved_farm_in_today_projection_synchronizes_ready_home_route() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
assert_eq!(
store.apply(AppStateCommand::replace_identity_projection(
@@ -4268,7 +4268,7 @@ mod tests {
);
let conflicts = vec![
SyncConflict {
- aggregate: radroots_studio_app_sync::SyncAggregateRef::Farm(FarmId::new()),
+ aggregate: radroots_studio_app_sync::SyncAggregateRef::Farm(FarmId::generate()),
kind: SyncConflictKind::RevisionMismatch,
severity: SyncConflictSeverity::Blocking,
resolution: SyncConflictResolutionStatus::Unresolved,
@@ -4278,7 +4278,7 @@ mod tests {
resolved_at: None,
},
SyncConflict {
- aggregate: radroots_studio_app_sync::SyncAggregateRef::Farm(FarmId::new()),
+ aggregate: radroots_studio_app_sync::SyncAggregateRef::Farm(FarmId::generate()),
kind: SyncConflictKind::RemoteValidationReject,
severity: SyncConflictSeverity::ReviewRequired,
resolution: SyncConflictResolutionStatus::AcceptedRemote,
diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml
@@ -17,10 +17,14 @@ radroots_studio_app_view.workspace = true
radroots_studio_app_sync.workspace = true
radroots_sql_core.workspace = true
radroots_trade.workspace = true
-rusqlite = { workspace = true }
+futures-executor.workspace = true
serde_json.workspace = true
+sqlx.workspace = true
thiserror.workspace = true
uuid.workspace = true
+[features]
+test-support = []
+
[lints]
workspace = true
diff --git a/crates/store/src/db.rs b/crates/store/src/db.rs
@@ -0,0 +1,371 @@
+#![forbid(unsafe_code)]
+
+use std::{
+ path::Path,
+ sync::{Arc, Mutex},
+};
+
+use sqlx::{
+ Connection,
+ sqlite::{SqliteArguments, SqliteConnectOptions, SqliteConnection, SqliteRow},
+};
+
+#[derive(Clone, Debug)]
+pub(crate) enum AppSqliteValue {
+ Null,
+ Integer(i64),
+ Real(f64),
+ Text(String),
+}
+
+pub(crate) type AppSqliteParams = Vec<AppSqliteValue>;
+
+pub(crate) trait IntoAppSqliteParams {
+ fn into_app_sqlite_params(self) -> AppSqliteParams;
+}
+
+pub(crate) trait OptionalSqliteResult<T> {
+ fn optional(self) -> Result<Option<T>, sqlx::Error>;
+}
+
+pub(crate) trait ToAppSqliteValue {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue;
+}
+
+impl ToAppSqliteValue for str {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Text(self.to_owned())
+ }
+}
+
+impl ToAppSqliteValue for &str {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Text((*self).to_owned())
+ }
+}
+
+impl ToAppSqliteValue for String {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Text(self.clone())
+ }
+}
+
+impl ToAppSqliteValue for &String {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Text((*self).clone())
+ }
+}
+
+impl ToAppSqliteValue for bool {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Integer(i64::from(*self))
+ }
+}
+
+impl ToAppSqliteValue for i64 {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Integer(*self)
+ }
+}
+
+impl ToAppSqliteValue for &i64 {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Integer(**self)
+ }
+}
+
+impl ToAppSqliteValue for i32 {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Integer(i64::from(*self))
+ }
+}
+
+impl ToAppSqliteValue for u32 {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Integer(i64::from(*self))
+ }
+}
+
+impl ToAppSqliteValue for usize {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Integer(i64::try_from(*self).unwrap_or(i64::MAX))
+ }
+}
+
+impl ToAppSqliteValue for f64 {
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ AppSqliteValue::Real(*self)
+ }
+}
+
+impl<T> ToAppSqliteValue for Option<T>
+where
+ T: ToAppSqliteValue,
+{
+ fn to_app_sqlite_value(&self) -> AppSqliteValue {
+ self.as_ref()
+ .map(ToAppSqliteValue::to_app_sqlite_value)
+ .unwrap_or(AppSqliteValue::Null)
+ }
+}
+
+impl IntoAppSqliteParams for AppSqliteParams {
+ fn into_app_sqlite_params(self) -> AppSqliteParams {
+ self
+ }
+}
+
+impl IntoAppSqliteParams for [(); 0] {
+ fn into_app_sqlite_params(self) -> AppSqliteParams {
+ Vec::new()
+ }
+}
+
+impl<T, const N: usize> IntoAppSqliteParams for [T; N]
+where
+ T: ToAppSqliteValue,
+{
+ fn into_app_sqlite_params(self) -> AppSqliteParams {
+ self.into_iter()
+ .map(|value| value.to_app_sqlite_value())
+ .collect()
+ }
+}
+
+impl<T> IntoAppSqliteParams for Vec<T>
+where
+ T: ToAppSqliteValue,
+{
+ fn into_app_sqlite_params(self) -> AppSqliteParams {
+ self.into_iter()
+ .map(|value| value.to_app_sqlite_value())
+ .collect()
+ }
+}
+
+impl<T> OptionalSqliteResult<T> for Result<T, sqlx::Error> {
+ fn optional(self) -> Result<Option<T>, sqlx::Error> {
+ match self {
+ Ok(value) => Ok(Some(value)),
+ Err(sqlx::Error::RowNotFound) => Ok(None),
+ Err(error) => Err(error),
+ }
+ }
+}
+
+pub(crate) fn app_sqlite_value<T>(value: &T) -> AppSqliteValue
+where
+ T: ToAppSqliteValue + ?Sized,
+{
+ value.to_app_sqlite_value()
+}
+
+#[derive(Clone)]
+pub(crate) struct AppSqliteDatabase {
+ connection: Arc<Mutex<SqliteConnection>>,
+}
+
+impl AppSqliteDatabase {
+ pub(crate) fn open_path(path: &Path) -> Result<Self, sqlx::Error> {
+ let options = SqliteConnectOptions::new()
+ .filename(path)
+ .create_if_missing(true);
+ Self::connect(options)
+ }
+
+ pub(crate) fn open_in_memory() -> Result<Self, sqlx::Error> {
+ Self::connect(SqliteConnectOptions::new().in_memory(true))
+ }
+
+ pub(crate) fn execute_statement(
+ &self,
+ sql: &str,
+ params: AppSqliteParams,
+ ) -> Result<u64, sqlx::Error> {
+ let mut connection = self.lock()?;
+ let query = bind_params(sqlx::query(sqlx::AssertSqlSafe(sql)), params);
+ let result = futures_executor::block_on(query.execute(&mut *connection))?;
+ Ok(result.rows_affected())
+ }
+
+ pub(crate) fn execute<P>(&self, sql: &str, params: P) -> Result<u64, sqlx::Error>
+ where
+ P: IntoAppSqliteParams,
+ {
+ self.execute_statement(sql, params.into_app_sqlite_params())
+ }
+
+ pub(crate) fn execute_script(&self, sql: &str) -> Result<(), sqlx::Error> {
+ let mut connection = self.lock()?;
+ futures_executor::block_on(
+ sqlx::raw_sql(sqlx::AssertSqlSafe(sql)).execute(&mut *connection),
+ )?;
+ Ok(())
+ }
+
+ pub(crate) fn execute_batch(&self, sql: &str) -> Result<(), sqlx::Error> {
+ self.execute_script(sql)
+ }
+
+ pub(crate) fn fetch_one<T, F>(
+ &self,
+ sql: &str,
+ params: AppSqliteParams,
+ map: F,
+ ) -> Result<T, sqlx::Error>
+ where
+ F: FnOnce(&SqliteRow) -> Result<T, sqlx::Error>,
+ {
+ let mut connection = self.lock()?;
+ let query = bind_params(sqlx::query(sqlx::AssertSqlSafe(sql)), params);
+ let row = futures_executor::block_on(query.fetch_one(&mut *connection))?;
+ map(&row)
+ }
+
+ pub(crate) fn query_row<T, P, F>(&self, sql: &str, params: P, map: F) -> Result<T, sqlx::Error>
+ where
+ P: IntoAppSqliteParams,
+ F: FnOnce(&SqliteRow) -> Result<T, sqlx::Error>,
+ {
+ self.fetch_one(sql, params.into_app_sqlite_params(), map)
+ }
+
+ pub(crate) fn fetch_optional<T, F>(
+ &self,
+ sql: &str,
+ params: AppSqliteParams,
+ map: F,
+ ) -> Result<Option<T>, sqlx::Error>
+ where
+ F: FnOnce(&SqliteRow) -> Result<T, sqlx::Error>,
+ {
+ let mut connection = self.lock()?;
+ let query = bind_params(sqlx::query(sqlx::AssertSqlSafe(sql)), params);
+ let Some(row) = futures_executor::block_on(query.fetch_optional(&mut *connection))? else {
+ return Ok(None);
+ };
+ map(&row).map(Some)
+ }
+
+ pub(crate) fn fetch_mapped<T, F>(
+ &self,
+ sql: &str,
+ params: AppSqliteParams,
+ map: F,
+ ) -> Result<std::vec::IntoIter<Result<T, sqlx::Error>>, sqlx::Error>
+ where
+ F: FnMut(&SqliteRow) -> Result<T, sqlx::Error>,
+ {
+ let rows = {
+ let mut connection = self.lock()?;
+ let query = bind_params(sqlx::query(sqlx::AssertSqlSafe(sql)), params);
+ futures_executor::block_on(query.fetch_all(&mut *connection))?
+ };
+ Ok(rows.iter().map(map).collect::<Vec<_>>().into_iter())
+ }
+
+ pub(crate) fn prepare(&self, sql: &str) -> Result<AppSqliteStatement<'_>, sqlx::Error> {
+ Ok(AppSqliteStatement {
+ database: self,
+ sql: sql.to_owned(),
+ })
+ }
+
+ #[cfg(test)]
+ pub(crate) fn query_rows<P>(&self, sql: &str, params: P) -> Result<AppSqliteRows, sqlx::Error>
+ where
+ P: IntoAppSqliteParams,
+ {
+ let rows = {
+ let mut connection = self.lock()?;
+ let query = bind_params(
+ sqlx::query(sqlx::AssertSqlSafe(sql)),
+ params.into_app_sqlite_params(),
+ );
+ futures_executor::block_on(query.fetch_all(&mut *connection))?
+ };
+ Ok(AppSqliteRows {
+ rows: rows.into_iter(),
+ })
+ }
+
+ fn connect(options: SqliteConnectOptions) -> Result<Self, sqlx::Error> {
+ let connection = futures_executor::block_on(SqliteConnection::connect_with(&options))?;
+ Ok(Self {
+ connection: Arc::new(Mutex::new(connection)),
+ })
+ }
+
+ fn lock(&self) -> Result<std::sync::MutexGuard<'_, SqliteConnection>, sqlx::Error> {
+ self.connection
+ .lock()
+ .map_err(|_| sqlx::Error::Protocol("sqlite connection mutex poisoned".to_owned()))
+ }
+}
+
+pub(crate) struct AppSqliteStatement<'a> {
+ database: &'a AppSqliteDatabase,
+ sql: String,
+}
+
+impl AppSqliteStatement<'_> {
+ pub(crate) fn execute<P>(&mut self, params: P) -> Result<u64, sqlx::Error>
+ where
+ P: IntoAppSqliteParams,
+ {
+ self.database.execute(&self.sql, params)
+ }
+
+ pub(crate) fn query_map<T, P, F>(
+ &mut self,
+ params: P,
+ map: F,
+ ) -> Result<std::vec::IntoIter<Result<T, sqlx::Error>>, sqlx::Error>
+ where
+ P: IntoAppSqliteParams,
+ F: FnMut(&SqliteRow) -> Result<T, sqlx::Error>,
+ {
+ self.database
+ .fetch_mapped(&self.sql, params.into_app_sqlite_params(), map)
+ }
+
+ #[cfg(test)]
+ pub(crate) fn query<P>(&mut self, params: P) -> Result<AppSqliteRows, sqlx::Error>
+ where
+ P: IntoAppSqliteParams,
+ {
+ self.database.query_rows(&self.sql, params)
+ }
+}
+
+#[cfg(test)]
+pub(crate) struct AppSqliteRows {
+ rows: std::vec::IntoIter<SqliteRow>,
+}
+
+#[cfg(test)]
+impl AppSqliteRows {
+ pub(crate) fn next(&mut self) -> Result<Option<SqliteRow>, sqlx::Error> {
+ Ok(self.rows.next())
+ }
+}
+
+pub(crate) fn empty_params() -> AppSqliteParams {
+ Vec::new()
+}
+
+fn bind_params<'q>(
+ query: sqlx::query::Query<'q, sqlx::Sqlite, SqliteArguments>,
+ params: AppSqliteParams,
+) -> sqlx::query::Query<'q, sqlx::Sqlite, SqliteArguments> {
+ let mut query = query;
+ for param in params {
+ query = match param {
+ AppSqliteValue::Null => query.bind(Option::<String>::None),
+ AppSqliteValue::Integer(value) => query.bind(value),
+ AppSqliteValue::Real(value) => query.bind(value),
+ AppSqliteValue::Text(value) => query.bind(value),
+ };
+ }
+ query
+}
diff --git a/crates/store/src/error.rs b/crates/store/src/error.rs
@@ -16,28 +16,28 @@ pub enum AppSqliteError {
OpenPath {
path: PathBuf,
#[source]
- source: rusqlite::Error,
+ source: sqlx::Error,
},
#[error("failed to open in-memory sqlite database")]
OpenInMemory {
#[source]
- source: rusqlite::Error,
+ source: sqlx::Error,
},
#[error("failed to configure sqlite busy timeout")]
ConfigureBusyTimeout {
#[source]
- source: rusqlite::Error,
+ source: sqlx::Error,
},
#[error("failed to apply sqlite pragma `{pragma}`")]
ApplyPragma {
pragma: &'static str,
#[source]
- source: rusqlite::Error,
+ source: sqlx::Error,
},
#[error("failed to read sqlite schema version")]
ReadSchemaVersion {
#[source]
- source: rusqlite::Error,
+ source: sqlx::Error,
},
#[error(
"sqlite schema version {current} is newer than supported version {latest}; manual migration is required"
@@ -47,31 +47,31 @@ pub enum AppSqliteError {
BeginMigration {
version: u32,
#[source]
- source: rusqlite::Error,
+ source: sqlx::Error,
},
#[error("failed to execute sqlite migration {version}")]
ExecuteMigration {
version: u32,
#[source]
- source: rusqlite::Error,
+ source: sqlx::Error,
},
#[error("failed to record sqlite schema version {version}")]
RecordSchemaVersion {
version: u32,
#[source]
- source: rusqlite::Error,
+ source: sqlx::Error,
},
#[error("failed to commit sqlite migration {version}")]
CommitMigration {
version: u32,
#[source]
- source: rusqlite::Error,
+ source: sqlx::Error,
},
#[error("failed to execute sqlite query for {operation}")]
Query {
operation: &'static str,
#[source]
- source: rusqlite::Error,
+ source: sqlx::Error,
},
#[error("failed to encode sqlite JSON column `{field}`")]
EncodeJson {
diff --git a/crates/store/src/interop.rs b/crates/store/src/interop.rs
@@ -27,7 +27,7 @@ use radroots_runtime_store::{
PublishOutboxStatus, RelayDeliveryEvidence, RelayDeliveryState, RuntimeStore,
RuntimeStoreRecord, RuntimeStoreRecordFamily, RuntimeStoreRecordStatus, SourceRuntime,
};
-use radroots_sql_core::{SqlExecutor, SqliteExecutor};
+use radroots_sql_core::{SqlExecutor, SqlxSqliteExecutor};
use radroots_studio_app_view::{
FarmId, FarmOrderMethod, FarmReadiness, FarmSetupDraft, FarmSetupProjection, FarmSummary,
FulfillmentWindowId, OrderId, OrderStatus, PickupLocationId, ProductId, ProductStatus,
@@ -43,7 +43,9 @@ use radroots_trade::order::{
use radroots_trade::validation_receipt::{
RadrootsTradeValidationReceipt, RadrootsValidationReceiptTags, validation_receipt_from_event,
};
-use rusqlite::{Connection, OptionalExtension, params};
+use sqlx::Row;
+
+use crate::{AppSqliteDatabase, OptionalSqliteResult};
use serde_json::Value;
use uuid::Uuid;
@@ -98,11 +100,11 @@ pub struct StoredLocalInteropRecord {
}
pub struct AppLocalInteropRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppLocalInteropRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
@@ -116,7 +118,7 @@ impl<'a> AppLocalInteropRepository<'a> {
source,
})?;
}
- let executor = SqliteExecutor::open(shared_database_path).map_err(|source| {
+ let executor = SqlxSqliteExecutor::open(shared_database_path).map_err(|source| {
AppSqliteError::RuntimeStoreSql {
operation: "open shared runtime store database",
source,
@@ -212,23 +214,23 @@ impl<'a> AppLocalInteropRepository<'a> {
source,
})?;
let rows = statement
- .query_map([], |row| {
+ .query_map(crate::empty_params(), |row| {
Ok(StoredLocalInteropRecord {
- record_id: row.get(0)?,
- local_seq: row.get(1)?,
- record_family: row.get(2)?,
- local_status: row.get(3)?,
- source_runtime: row.get(4)?,
- owner_account_id: row.get(5)?,
- owner_pubkey: row.get(6)?,
- farm_key: row.get(7)?,
- listing_addr: row.get(8)?,
- projected_kind: row.get(9)?,
- projected_id: row.get(10)?,
- event_id: row.get(11)?,
- event_kind: row.get(12)?,
- outbox_status: row.get(13)?,
- relay_delivery_json: row.get(14)?,
+ record_id: row.try_get(0)?,
+ local_seq: row.try_get(1)?,
+ record_family: row.try_get(2)?,
+ local_status: row.try_get(3)?,
+ source_runtime: row.try_get(4)?,
+ owner_account_id: row.try_get(5)?,
+ owner_pubkey: row.try_get(6)?,
+ farm_key: row.try_get(7)?,
+ listing_addr: row.try_get(8)?,
+ projected_kind: row.try_get(9)?,
+ projected_id: row.try_get(10)?,
+ event_id: row.try_get(11)?,
+ event_kind: row.try_get(12)?,
+ outbox_status: row.try_get(13)?,
+ relay_delivery_json: row.try_get(14)?,
})
})
.map_err(|source| AppSqliteError::Query {
@@ -273,18 +275,18 @@ impl<'a> AppLocalInteropRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![event_kind], |row| {
+ .query_map(crate::app_sqlite_params![event_kind], |row| {
Ok(StoredLocalInteropSignedEventEvidence {
- event_id: row.get(0)?,
- event_kind: row.get(1)?,
- local_status: row.get(2)?,
- outbox_status: row.get(3)?,
- relay_delivery_json: row.get(4)?,
- event_pubkey: row.get(5)?,
- event_created_at: row.get(6)?,
- event_tags_json: row.get(7)?,
- event_content: row.get(8)?,
- event_sig: row.get(9)?,
+ event_id: row.try_get(0)?,
+ event_kind: row.try_get(1)?,
+ local_status: row.try_get(2)?,
+ outbox_status: row.try_get(3)?,
+ relay_delivery_json: row.try_get(4)?,
+ event_pubkey: row.try_get(5)?,
+ event_created_at: row.try_get(6)?,
+ event_tags_json: row.try_get(7)?,
+ event_content: row.try_get(8)?,
+ event_sig: row.try_get(9)?,
})
})
.map_err(|source| AppSqliteError::Query {
@@ -335,18 +337,18 @@ impl<'a> AppLocalInteropRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![event_id], |row| {
+ .query_map(crate::app_sqlite_params![event_id], |row| {
Ok(StoredLocalInteropSignedEventEvidence {
- event_id: row.get(0)?,
- event_kind: row.get(1)?,
- local_status: row.get(2)?,
- outbox_status: row.get(3)?,
- relay_delivery_json: row.get(4)?,
- event_pubkey: row.get(5)?,
- event_created_at: row.get(6)?,
- event_tags_json: row.get(7)?,
- event_content: row.get(8)?,
- event_sig: row.get(9)?,
+ event_id: row.try_get(0)?,
+ event_kind: row.try_get(1)?,
+ local_status: row.try_get(2)?,
+ outbox_status: row.try_get(3)?,
+ relay_delivery_json: row.try_get(4)?,
+ event_pubkey: row.try_get(5)?,
+ event_created_at: row.try_get(6)?,
+ event_tags_json: row.try_get(7)?,
+ event_content: row.try_get(8)?,
+ event_sig: row.try_get(9)?,
})
})
.map_err(|source| AppSqliteError::Query {
@@ -377,10 +379,10 @@ impl<'a> AppLocalInteropRepository<'a> {
WHERE consumer_id = ?1
LIMIT 1",
[APP_LOCAL_INTEROP_CURSOR_ID],
- |row| row.get::<_, i64>(0),
+ |row| row.try_get::<i64, _>(0),
) {
Ok(last_change_seq) => Ok(last_change_seq),
- Err(rusqlite::Error::QueryReturnedNoRows) => Ok(0),
+ Err(sqlx::Error::RowNotFound) => Ok(0),
Err(source) => Err(AppSqliteError::Query {
operation: "read app local interop projection cursor",
source,
@@ -402,7 +404,7 @@ impl<'a> AppLocalInteropRepository<'a> {
excluded.last_change_seq
),
updated_at = excluded.updated_at",
- params![APP_LOCAL_INTEROP_CURSOR_ID, last_change_seq],
+ crate::app_sqlite_params![APP_LOCAL_INTEROP_CURSOR_ID, last_change_seq],
)
.map_err(|source| AppSqliteError::Query {
operation: "advance app local interop projection cursor",
@@ -448,13 +450,13 @@ impl<'a> AppLocalInteropRepository<'a> {
let projected_kind = projection.kind;
let projected_id = projection.projected_id;
self.record_import(record, projected_kind, projected_id.clone())?;
- if projected_kind == "listing" {
- if let Some(projected_id) = projected_id.as_deref() {
- self.finish_duplicate_listing_replacement(
- &superseded_listing_ids,
- projected_id,
- )?;
- }
+ if projected_kind == "listing"
+ && let Some(projected_id) = projected_id.as_deref()
+ {
+ self.finish_duplicate_listing_replacement(
+ &superseded_listing_ids,
+ projected_id,
+ )?;
}
Ok(ImportOutcome::Imported)
}
@@ -521,14 +523,17 @@ impl<'a> AppLocalInteropRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![event_id, record.record_id.as_str()], |row| {
- Ok(StoredSignedEventDuplicate {
- source_runtime: row.get(0)?,
- owner_account_id: row.get(1)?,
- local_status: row.get(2)?,
- outbox_status: row.get(3)?,
- })
- })
+ .query_map(
+ crate::app_sqlite_params![event_id, record.record_id.as_str()],
+ |row| {
+ Ok(StoredSignedEventDuplicate {
+ source_runtime: row.try_get(0)?,
+ owner_account_id: row.try_get(1)?,
+ local_status: row.try_get(2)?,
+ outbox_status: row.try_get(3)?,
+ })
+ },
+ )
.map_err(|source| AppSqliteError::Query {
operation: "query duplicate local interop signed events",
source,
@@ -579,7 +584,7 @@ impl<'a> AppLocalInteropRepository<'a> {
WHERE event_id = ?1
AND record_id <> ?2
AND record_family = 'signed_event'",
- params![event_id, record_id],
+ crate::app_sqlite_params![event_id, record_id],
)
.map_err(|source| AppSqliteError::Query {
operation: "delete superseded duplicate local interop signed event",
@@ -622,7 +627,9 @@ impl<'a> AppLocalInteropRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![event_id, record_id], |row| row.get::<_, String>(0))
+ .query_map(crate::app_sqlite_params![event_id, record_id], |row| {
+ row.try_get::<String, _>(0)
+ })
.map_err(|source| AppSqliteError::Query {
operation: "query superseded duplicate listing projections",
source,
@@ -651,7 +658,7 @@ impl<'a> AppLocalInteropRepository<'a> {
WHERE projected_kind = 'listing'
AND projected_id = ?1
)",
- params![product_id],
+ crate::app_sqlite_params![product_id],
)
.map_err(|source| AppSqliteError::Query {
operation: "delete unreferenced superseded listing product",
@@ -715,7 +722,7 @@ impl<'a> AppLocalInteropRepository<'a> {
seller_pubkey = coalesce(nullif(buyer_cart_lines.seller_pubkey, ''), excluded.seller_pubkey),
listing_relays_json = coalesce(nullif(buyer_cart_lines.listing_relays_json, ''), excluded.listing_relays_json),
updated_at = excluded.updated_at",
- params![product_id, canonical_product_id],
+ crate::app_sqlite_params![product_id, canonical_product_id],
)
.map_err(|source| AppSqliteError::Query {
operation: "migrate duplicate listing buyer cart lines",
@@ -725,7 +732,7 @@ impl<'a> AppLocalInteropRepository<'a> {
.execute(
"DELETE FROM buyer_cart_lines
WHERE product_id = ?1",
- params![product_id],
+ crate::app_sqlite_params![product_id],
)
.map_err(|source| AppSqliteError::Query {
operation: "delete migrated duplicate listing buyer cart lines",
@@ -1267,7 +1274,7 @@ impl<'a> AppLocalInteropRepository<'a> {
status = excluded.status,
buyer_context_key = coalesce(orders.buyer_context_key, excluded.buyer_context_key),
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
order_id.to_string(),
farm_id.to_string(),
order_number.as_str(),
@@ -1310,7 +1317,7 @@ impl<'a> AppLocalInteropRepository<'a> {
workflow_provenance_last_event_id = ?7,
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
WHERE id = ?1",
- params![
+ crate::app_sqlite_params![
workflow.order_id.to_string(),
status.storage_key(),
workflow.revision.storage_key(),
@@ -1386,7 +1393,7 @@ impl<'a> AppLocalInteropRepository<'a> {
reducer_output_root = excluded.reducer_output_root,
public_values_hash = excluded.public_values_hash,
event_created_at = excluded.event_created_at",
- params![
+ crate::app_sqlite_params![
event.id_str(),
order_id.map(|order_id| order_id.to_string()),
tags.order_id.as_str(),
@@ -1449,7 +1456,7 @@ impl<'a> AppLocalInteropRepository<'a> {
WHERE root_event_id = ?1
AND raw_order_id = ?2
AND order_id IS NULL",
- params![root_event_id, raw_order_id, order_id.to_string()],
+ crate::app_sqlite_params![root_event_id, raw_order_id, order_id.to_string()],
)
.map_err(|source| AppSqliteError::Query {
operation: "attach local interop validation receipts to request",
@@ -1469,8 +1476,8 @@ impl<'a> AppLocalInteropRepository<'a> {
"SELECT status, workflow_agreement, workflow_provenance_last_event_id
FROM orders
WHERE id = ?1",
- params![order_id.to_string()],
- |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
+ crate::app_sqlite_params![order_id.to_string()],
+ |row| Ok((row.try_get(0)?, row.try_get(1)?, row.try_get(2)?)),
)
.optional()
.map_err(|source| AppSqliteError::Query {
@@ -1506,8 +1513,8 @@ impl<'a> AppLocalInteropRepository<'a> {
AND target_event_id = ?2
ORDER BY event_created_at DESC, event_id DESC
LIMIT 1",
- params![order_id.to_string(), target_event_id.as_str()],
- |row| row.get(0),
+ crate::app_sqlite_params![order_id.to_string(), target_event_id.as_str()],
+ |row| row.try_get(0),
)
.optional()
.map_err(|source| AppSqliteError::Query {
@@ -1545,7 +1552,7 @@ impl<'a> AppLocalInteropRepository<'a> {
workflow_provenance_last_event_id = ?6,
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
WHERE id = ?1",
- params![
+ crate::app_sqlite_params![
order_id.to_string(),
status,
agreement,
@@ -1589,7 +1596,7 @@ impl<'a> AppLocalInteropRepository<'a> {
self.connection
.execute(
"DELETE FROM order_lines WHERE order_id = ?1",
- params![order_id.to_string()],
+ crate::app_sqlite_params![order_id.to_string()],
)
.map_err(|source| AppSqliteError::Query {
operation: "replace local interop order lines",
@@ -1633,7 +1640,7 @@ impl<'a> AppLocalInteropRepository<'a> {
seller_pubkey,
sort_index
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, null, ?13, ?14)",
- params![
+ crate::app_sqlite_params![
format!(
"{}:{}",
order_id,
@@ -1673,7 +1680,7 @@ impl<'a> AppLocalInteropRepository<'a> {
self.connection
.execute(
"DELETE FROM order_lines WHERE order_id = ?1",
- params![order_id.to_string()],
+ crate::app_sqlite_params![order_id.to_string()],
)
.map_err(|source| AppSqliteError::Query {
operation: "replace local interop active order agreement lines",
@@ -1728,7 +1735,7 @@ impl<'a> AppLocalInteropRepository<'a> {
seller_pubkey,
sort_index
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)",
- params![
+ crate::app_sqlite_params![
format!(
"{}:{}",
order_id,
@@ -1780,11 +1787,11 @@ impl<'a> AppLocalInteropRepository<'a> {
WHERE order_id = ?1
ORDER BY sort_index ASC, id ASC
LIMIT 1",
- params![order_id.to_string()],
+ crate::app_sqlite_params![order_id.to_string()],
|row| {
Ok(ExistingOrderLineMetadata {
- listing_event_id: row.get::<_, Option<String>>(0)?,
- listing_relays_json: row.get::<_, Option<String>>(1)?,
+ listing_event_id: row.try_get::<Option<String>, _>(0)?,
+ listing_relays_json: row.try_get::<Option<String>, _>(1)?,
})
},
)
@@ -1804,7 +1811,7 @@ impl<'a> AppLocalInteropRepository<'a> {
display_name = excluded.display_name,
readiness = excluded.readiness,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
farm.farm_id.to_string(),
farm.display_name.as_str(),
farm_readiness_storage_key(farm.readiness),
@@ -1830,7 +1837,7 @@ impl<'a> AppLocalInteropRepository<'a> {
ELSE excluded.readiness
END,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
farm.farm_id.to_string(),
farm.display_name.as_str(),
farm_readiness_storage_key(farm.readiness),
@@ -1898,7 +1905,7 @@ impl<'a> AppLocalInteropRepository<'a> {
saved_farm_display_name = excluded.saved_farm_display_name,
saved_farm_readiness = excluded.saved_farm_readiness,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
account_id,
display_name.as_str(),
i64::from(method == FarmOrderMethod::Pickup),
@@ -1920,7 +1927,7 @@ impl<'a> AppLocalInteropRepository<'a> {
.query_row(
"SELECT EXISTS(SELECT 1 FROM farms WHERE id = ?1)",
[farm_id.to_string()],
- |row| row.get::<_, bool>(0),
+ |row| row.try_get::<bool, _>(0),
)
.map_err(|source| AppSqliteError::Query {
operation: "check local interop farm existence",
@@ -1941,7 +1948,7 @@ impl<'a> AppLocalInteropRepository<'a> {
.query_row(
"SELECT display_name FROM farms WHERE id = ?1 LIMIT 1",
[farm_id.to_string()],
- |row| row.get::<_, String>(0),
+ |row| row.try_get::<String, _>(0),
)
.optional()
.map_err(|source| AppSqliteError::Query {
@@ -1958,7 +1965,7 @@ impl<'a> AppLocalInteropRepository<'a> {
.query_row(
"SELECT readiness FROM farms WHERE id = ?1 LIMIT 1",
[farm_id.to_string()],
- |row| row.get::<_, String>(0),
+ |row| row.try_get::<String, _>(0),
)
.optional()
.map_err(|source| AppSqliteError::Query {
@@ -2022,7 +2029,7 @@ impl<'a> AppLocalInteropRepository<'a> {
pickup_location_id = excluded.pickup_location_id,
order_cutoff_at = excluded.order_cutoff_at,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
fulfillment_window_id.to_string(),
farm_id_string.as_str(),
starts_at.as_str(),
@@ -2065,7 +2072,7 @@ impl<'a> AppLocalInteropRepository<'a> {
label = excluded.label,
address_line = excluded.address_line,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
pickup_location_id.to_string(),
farm_id_string.as_str(),
location_primary,
@@ -2091,7 +2098,7 @@ impl<'a> AppLocalInteropRepository<'a> {
.query_row(
"SELECT strftime('%Y-%m-%dT%H:%M:%SZ', ?1, 'unixepoch')",
[seconds],
- |row| row.get::<_, Option<String>>(0),
+ |row| row.try_get::<Option<String>, _>(0),
)
.map_err(|source| AppSqliteError::Query { operation, source })?;
timestamp.ok_or(AppSqliteError::InvalidProjection {
@@ -2138,7 +2145,7 @@ impl<'a> AppLocalInteropRepository<'a> {
END,
listing_bin_id = coalesce(excluded.listing_bin_id, products.listing_bin_id),
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
projection.product_id.to_string(),
projection.farm_id.to_string(),
projection.title.as_str(),
@@ -2189,12 +2196,12 @@ impl<'a> AppLocalInteropRepository<'a> {
[listing_addr],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, Option<String>>(4)?,
- row.get::<_, Option<String>>(5)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<Option<String>, _>(4)?,
+ row.try_get::<Option<String>, _>(5)?,
))
},
)
@@ -2289,7 +2296,7 @@ impl<'a> AppLocalInteropRepository<'a> {
AND products.id = ?1
AND products.farm_id = ?5
LIMIT 1",
- params![
+ crate::app_sqlite_params![
product_id.to_string(),
farm_key,
listing_addr,
@@ -2298,12 +2305,12 @@ impl<'a> AppLocalInteropRepository<'a> {
],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, Option<String>>(4)?,
- row.get::<_, Option<String>>(5)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<Option<String>, _>(4)?,
+ row.try_get::<Option<String>, _>(5)?,
))
},
)
@@ -2390,7 +2397,10 @@ impl<'a> AppLocalInteropRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![listing_addr.as_str()], listing_currentness_row)
+ .query_map(
+ crate::app_sqlite_params![listing_addr.as_str()],
+ listing_currentness_row,
+ )
.map_err(|source| AppSqliteError::Query {
operation: "query current listing-address evidence",
source,
@@ -2436,7 +2446,7 @@ impl<'a> AppLocalInteropRepository<'a> {
})?;
let rows = statement
.query_map(
- params![event_kind, event_pubkey.as_str()],
+ crate::app_sqlite_params![event_kind, event_pubkey.as_str()],
listing_currentness_identity_row,
)
.map_err(|source| AppSqliteError::Query {
@@ -2537,7 +2547,7 @@ impl<'a> AppLocalInteropRepository<'a> {
outbox_status = excluded.outbox_status,
relay_delivery_json = excluded.relay_delivery_json,
imported_at = excluded.imported_at",
- params![
+ crate::app_sqlite_params![
record.record_id.as_str(),
record.seq,
record.family.as_str(),
@@ -2890,35 +2900,35 @@ impl ActiveOrderEvidenceBuckets {
}
fn listing_currentness_row(
- row: &rusqlite::Row<'_>,
-) -> rusqlite::Result<StoredListingCurrentnessEvidence> {
+ row: &sqlx::sqlite::SqliteRow,
+) -> Result<StoredListingCurrentnessEvidence, sqlx::Error> {
Ok(StoredListingCurrentnessEvidence {
- event_id: row.get(0)?,
- event_created_at: row.get(1)?,
- source_runtime: row.get(2)?,
- owner_account_id: row.get(3)?,
- local_status: row.get(4)?,
- outbox_status: row.get(5)?,
- relay_delivery_json: row.get(6)?,
+ event_id: row.try_get(0)?,
+ event_created_at: row.try_get(1)?,
+ source_runtime: row.try_get(2)?,
+ owner_account_id: row.try_get(3)?,
+ local_status: row.try_get(4)?,
+ outbox_status: row.try_get(5)?,
+ relay_delivery_json: row.try_get(6)?,
})
}
fn listing_currentness_identity_row(
- row: &rusqlite::Row<'_>,
-) -> rusqlite::Result<StoredListingCurrentnessIdentityEvidence> {
+ row: &sqlx::sqlite::SqliteRow,
+) -> Result<StoredListingCurrentnessIdentityEvidence, sqlx::Error> {
Ok(StoredListingCurrentnessIdentityEvidence {
currentness: StoredListingCurrentnessEvidence {
- event_id: row.get(0)?,
- event_created_at: row.get(1)?,
- source_runtime: row.get(2)?,
- owner_account_id: row.get(3)?,
- local_status: row.get(4)?,
- outbox_status: row.get(5)?,
- relay_delivery_json: row.get(6)?,
+ event_id: row.try_get(0)?,
+ event_created_at: row.try_get(1)?,
+ source_runtime: row.try_get(2)?,
+ owner_account_id: row.try_get(3)?,
+ local_status: row.try_get(4)?,
+ outbox_status: row.try_get(5)?,
+ relay_delivery_json: row.try_get(6)?,
},
- event_tags_json: row.get(7)?,
- event_content: row.get(8)?,
- listing_addr: row.get(9)?,
+ event_tags_json: row.try_get(7)?,
+ event_content: row.try_get(8)?,
+ listing_addr: row.try_get(9)?,
})
}
@@ -3439,14 +3449,14 @@ fn deterministic_order_number(order_id: &str) -> String {
}
fn existing_order_number(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
) -> Result<Option<String>, AppSqliteError> {
connection
.query_row(
"SELECT order_number FROM orders WHERE id = ?1 LIMIT 1",
- params![order_id.to_string()],
- |row| row.get::<_, String>(0),
+ crate::app_sqlite_params![order_id.to_string()],
+ |row| row.try_get::<String, _>(0),
)
.optional()
.map_err(|source| AppSqliteError::Query {
@@ -3893,7 +3903,7 @@ mod tests {
PublishOutboxStatus, RelayDeliveryEvidence, RuntimeStore, RuntimeStoreRecordFamily,
RuntimeStoreRecordInput, RuntimeStoreRecordStatus, RuntimeStoreRecordUpdate, SourceRuntime,
};
- use radroots_sql_core::SqliteExecutor;
+ use radroots_sql_core::SqlxSqliteExecutor;
use radroots_studio_app_view::{
BuyerContext, BuyerOrderStatus, FarmId, FarmOrderMethod, OrderId, OrderStatus,
OrdersFilter, OrdersScreenQueryState, ProductAvailabilityState, ProductId,
@@ -3907,8 +3917,8 @@ mod tests {
RadrootsValidationReceiptStatement, RadrootsValidationReceiptType,
VALIDATION_RECEIPT_DOMAIN, VALIDATION_RECEIPT_VERSION, validation_receipt_event_build,
};
- use rusqlite::params;
use serde_json::json;
+ use sqlx::Row;
use uuid::Uuid;
use super::{
@@ -3918,8 +3928,8 @@ mod tests {
};
use crate::{AppSqliteStore, BuyerRepeatDemandApplyOutcome, DatabaseTarget};
- fn runtime_store_store() -> RuntimeStore<SqliteExecutor> {
- let executor = SqliteExecutor::open_memory().expect("open runtime store memory db");
+ fn runtime_store_store() -> RuntimeStore<SqlxSqliteExecutor> {
+ let executor = SqlxSqliteExecutor::open_memory().expect("open runtime store memory db");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate runtime store store");
store
@@ -4089,6 +4099,7 @@ mod tests {
}
}
+ #[expect(clippy::too_many_arguments)]
fn signed_market_listing_record(
record_id: &str,
owner_pubkey: &str,
@@ -4331,7 +4342,7 @@ mod tests {
.execute(
"INSERT INTO farms (id, display_name, readiness, created_at, updated_at)
VALUES (?1, 'Origin Farm', 'ready', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
- params![farm_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string()],
)
.expect("seed origin farm");
app_store
@@ -4362,7 +4373,7 @@ mod tests {
NULL,
'2026-01-01T00:00:00Z'
)",
- params![product_id.to_string(), farm_id.to_string()],
+ crate::app_sqlite_params![product_id.to_string(), farm_id.to_string()],
)
.expect("seed origin product");
}
@@ -4530,6 +4541,7 @@ mod tests {
}
}
+ #[expect(clippy::too_many_arguments)]
fn revision_decision_payload(
revision_id: &str,
order_id: &str,
@@ -4569,7 +4581,7 @@ mod tests {
struct ValidationReceiptOrderFixture {
app_store: AppSqliteStore,
- events: RuntimeStore<SqliteExecutor>,
+ events: RuntimeStore<SqlxSqliteExecutor>,
buyer_context: BuyerContext,
seller_farm_id: FarmId,
order_id: OrderId,
@@ -4808,6 +4820,7 @@ mod tests {
}
}
+ #[expect(clippy::too_many_arguments)]
fn validation_receipt_event(
event_id: &str,
author: &str,
@@ -4958,7 +4971,7 @@ mod tests {
.query_row(
"SELECT buyer_context_key FROM orders WHERE id = ?1",
[order_id.to_string()],
- |row| row.get(0),
+ |row| row.try_get(0),
)
.expect("load buyer context key");
@@ -5917,8 +5930,8 @@ mod tests {
.connection()
.query_row(
"SELECT count(*) FROM order_validation_receipts WHERE order_id IS NULL",
- [],
- |row| row.get(0),
+ crate::empty_params(),
+ |row| row.try_get(0),
)
.expect("count pending validation receipts");
assert_eq!(pending_count, 1);
@@ -6555,7 +6568,11 @@ mod tests {
.expect("load imported records");
let order_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM orders", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM orders",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("load order count");
assert_eq!(report.imported_records, 1);
@@ -6893,14 +6910,25 @@ mod tests {
);
let product_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("product count");
let product: (String, String, Option<i64>, Option<i64>) = app_store
.connection()
.query_row(
"SELECT title, status, price_minor_units, stock_count FROM products",
- [],
- |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
+ crate::empty_params(),
+ |row| {
+ Ok((
+ row.try_get(0)?,
+ row.try_get(1)?,
+ row.try_get(2)?,
+ row.try_get(3)?,
+ ))
+ },
)
.expect("load product");
assert_eq!(product_count, 1);
@@ -7109,14 +7137,18 @@ mod tests {
.expect("load imported records");
let product_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("product count");
let app_product: (String, Option<i64>) = app_store
.connection()
.query_row(
"SELECT title, stock_count FROM products WHERE id = ?1",
[product_uuid.to_string()],
- |row| Ok((row.get(0)?, row.get(1)?)),
+ |row| Ok((row.try_get(0)?, row.try_get(1)?)),
)
.expect("load app product");
let network_product_id =
@@ -7126,7 +7158,14 @@ mod tests {
.query_row(
"SELECT id, farm_id, title, stock_count FROM products WHERE id = ?1",
[network_product_id.to_string()],
- |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
+ |row| {
+ Ok((
+ row.try_get(0)?,
+ row.try_get(1)?,
+ row.try_get(2)?,
+ row.try_get(3)?,
+ ))
+ },
)
.expect("load network product");
let buyer_listings = app_store
@@ -7260,14 +7299,25 @@ mod tests {
.expect("import network app-origin listing");
let product_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("product count");
let product: (String, String, String, Option<i64>) = app_store
.connection()
.query_row(
"SELECT id, farm_id, title, stock_count FROM products",
- [],
- |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
+ crate::empty_params(),
+ |row| {
+ Ok((
+ row.try_get(0)?,
+ row.try_get(1)?,
+ row.try_get(2)?,
+ row.try_get(3)?,
+ ))
+ },
)
.expect("load product");
let imported = app_store
@@ -7391,14 +7441,18 @@ mod tests {
.expect("import network app-origin listing");
let product_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("product count");
let app_product: (String, Option<i64>) = app_store
.connection()
.query_row(
"SELECT title, stock_count FROM products WHERE id = ?1",
[product_uuid.to_string()],
- |row| Ok((row.get(0)?, row.get(1)?)),
+ |row| Ok((row.try_get(0)?, row.try_get(1)?)),
)
.expect("load app product");
let foreign_product_id =
@@ -7408,7 +7462,7 @@ mod tests {
.query_row(
"SELECT COUNT(*) FROM products WHERE id = ?1",
[foreign_product_id.to_string()],
- |row| row.get(0),
+ |row| row.try_get(0),
)
.expect("foreign product count");
@@ -7459,7 +7513,11 @@ mod tests {
deterministic_product_id(Some(seller_pubkey), listing_key.as_str());
let network_product_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("network product count");
assert_eq!(network_product_count, 1);
assert_ne!(network_product_id.as_uuid(), product_uuid);
@@ -7565,14 +7623,18 @@ mod tests {
.expect("load imported records");
let product_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("product count");
let stale_product_count: i64 = app_store
.connection()
.query_row(
"SELECT COUNT(*) FROM products WHERE id = ?1",
[network_product_id.to_string()],
- |row| row.get(0),
+ |row| row.try_get(0),
)
.expect("stale product count");
let listing_import = imported
@@ -7587,7 +7649,7 @@ mod tests {
.query_row(
"SELECT id FROM order_lines WHERE order_id = ?1",
[order_id.to_string()],
- |row| row.get(0),
+ |row| row.try_get(0),
)
.expect("order line id should load");
@@ -7734,14 +7796,18 @@ mod tests {
.expect("load imported records");
let product_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("product count");
let stale_cart_quantity: i64 = app_store
.connection()
.query_row(
"SELECT quantity FROM buyer_cart_lines WHERE product_id = ?1",
[network_product_id.to_string()],
- |row| row.get(0),
+ |row| row.try_get(0),
)
.expect("stale cart quantity");
let canonical_cart_count: i64 = app_store
@@ -7749,7 +7815,7 @@ mod tests {
.query_row(
"SELECT COUNT(*) FROM buyer_cart_lines WHERE product_id = ?1",
[product_uuid.to_string()],
- |row| row.get(0),
+ |row| row.try_get(0),
)
.expect("canonical cart count");
let network_product_title: String = app_store
@@ -7757,7 +7823,7 @@ mod tests {
.query_row(
"SELECT title FROM products WHERE id = ?1",
[network_product_id.to_string()],
- |row| row.get(0),
+ |row| row.try_get(0),
)
.expect("network product title");
@@ -8005,9 +8071,11 @@ mod tests {
.expect("import older listing");
let product: (String, Option<i64>) = app_store
.connection()
- .query_row("SELECT title, stock_count FROM products", [], |row| {
- Ok((row.get(0)?, row.get(1)?))
- })
+ .query_row(
+ "SELECT title, stock_count FROM products",
+ crate::empty_params(),
+ |row| Ok((row.try_get(0)?, row.try_get(1)?)),
+ )
.expect("load product");
let imported = app_store
.load_local_interop_records()
@@ -8086,9 +8154,11 @@ mod tests {
.expect("import losing listing");
let product: (String, Option<i64>) = app_store
.connection()
- .query_row("SELECT title, stock_count FROM products", [], |row| {
- Ok((row.get(0)?, row.get(1)?))
- })
+ .query_row(
+ "SELECT title, stock_count FROM products",
+ crate::empty_params(),
+ |row| Ok((row.try_get(0)?, row.try_get(1)?)),
+ )
.expect("load product");
assert_eq!(product.0, "Tie Winner Eggs");
@@ -8150,9 +8220,11 @@ mod tests {
.expect("load imported records");
let stored_farm: (String, String) = app_store
.connection()
- .query_row("SELECT id, display_name FROM farms", [], |row| {
- Ok((row.get(0)?, row.get(1)?))
- })
+ .query_row(
+ "SELECT id, display_name FROM farms",
+ crate::empty_params(),
+ |row| Ok((row.try_get(0)?, row.try_get(1)?)),
+ )
.expect("load farm");
assert_eq!(report.imported_records, 1);
@@ -8237,9 +8309,11 @@ mod tests {
.expect("load imported records");
let product: (String, String) = app_store
.connection()
- .query_row("SELECT id, farm_id FROM products", [], |row| {
- Ok((row.get(0)?, row.get(1)?))
- })
+ .query_row(
+ "SELECT id, farm_id FROM products",
+ crate::empty_params(),
+ |row| Ok((row.try_get(0)?, row.try_get(1)?)),
+ )
.expect("load product");
assert_eq!(report.imported_records, 1);
@@ -8522,9 +8596,11 @@ mod tests {
.expect("import shared local work after relay");
let stored_farm: (String, String, String) = app_store
.connection()
- .query_row("SELECT id, display_name, readiness FROM farms", [], |row| {
- Ok((row.get(0)?, row.get(1)?, row.get(2)?))
- })
+ .query_row(
+ "SELECT id, display_name, readiness FROM farms",
+ crate::empty_params(),
+ |row| Ok((row.try_get(0)?, row.try_get(1)?, row.try_get(2)?)),
+ )
.expect("load farm");
assert_eq!(direct_report.imported_records, 1);
@@ -8605,9 +8681,11 @@ mod tests {
.expect("import listing and farm");
let stored_farm: (String, String, String) = app_store
.connection()
- .query_row("SELECT id, display_name, readiness FROM farms", [], |row| {
- Ok((row.get(0)?, row.get(1)?, row.get(2)?))
- })
+ .query_row(
+ "SELECT id, display_name, readiness FROM farms",
+ crate::empty_params(),
+ |row| Ok((row.try_get(0)?, row.try_get(1)?, row.try_get(2)?)),
+ )
.expect("load farm");
assert_eq!(report.imported_records, 2);
@@ -8644,7 +8722,11 @@ mod tests {
.expect("import signed listing");
let product_status: String = app_store
.connection()
- .query_row("SELECT status FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT status FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("load product status");
assert_eq!(report.imported_records, 1);
@@ -8685,7 +8767,11 @@ mod tests {
.expect("import observed signed listing");
let product_status: String = app_store
.connection()
- .query_row("SELECT status FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT status FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("load product status");
assert_eq!(report.imported_records, 1);
@@ -8717,7 +8803,11 @@ mod tests {
.expect("load imported records");
let product_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("product count");
assert_eq!(report.imported_records, 0);
@@ -8770,7 +8860,11 @@ mod tests {
.expect("import unconfirmed signed listing");
let product_status: String = app_store
.connection()
- .query_row("SELECT status FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT status FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("load product status");
let imported = app_store
.load_local_interop_records()
@@ -8830,7 +8924,11 @@ mod tests {
.expect("import updated listing");
let product_status: String = app_store
.connection()
- .query_row("SELECT status FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT status FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("load product status");
let imported = app_store
.load_local_interop_records()
@@ -8929,20 +9027,24 @@ mod tests {
.expect("import unchanged shared runtime store into origin store");
let origin_product_count: i64 = origin_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("origin product count");
let origin_product: (String, String, String, Option<i64>, Option<i64>) = origin_store
.connection()
.query_row(
"SELECT id, farm_id, title, price_minor_units, stock_count FROM products",
- [],
+ crate::empty_params(),
|row| {
Ok((
- row.get(0)?,
- row.get(1)?,
- row.get(2)?,
- row.get(3)?,
- row.get(4)?,
+ row.try_get(0)?,
+ row.try_get(1)?,
+ row.try_get(2)?,
+ row.try_get(3)?,
+ row.try_get(4)?,
))
},
)
@@ -8971,13 +9073,19 @@ mod tests {
.expect("import shared runtime store into fresh store");
let fresh_product_count: i64 = fresh_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("fresh product count");
let fresh_product: (String, String, String) = fresh_store
.connection()
- .query_row("SELECT id, farm_id, title FROM products", [], |row| {
- Ok((row.get(0)?, row.get(1)?, row.get(2)?))
- })
+ .query_row(
+ "SELECT id, farm_id, title FROM products",
+ crate::empty_params(),
+ |row| Ok((row.try_get(0)?, row.try_get(1)?, row.try_get(2)?)),
+ )
.expect("load fresh product");
let fresh_imports = fresh_store
.load_local_interop_records()
@@ -9031,7 +9139,9 @@ mod tests {
.expect("load imported records");
let farm_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM farms", [], |row| row.get(0))
+ .query_row("SELECT COUNT(*) FROM farms", crate::empty_params(), |row| {
+ row.try_get(0)
+ })
.expect("farm count");
assert_eq!(report.scanned_records, 1);
@@ -9169,20 +9279,24 @@ mod tests {
.collect::<Vec<_>>();
let product_count: i64 = app_store
.connection()
- .query_row("SELECT COUNT(*) FROM products", [], |row| row.get(0))
+ .query_row(
+ "SELECT COUNT(*) FROM products",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.expect("product count");
let product: (String, String, String, Option<i64>, Option<i64>) = app_store
.connection()
.query_row(
"SELECT id, farm_id, status, price_minor_units, stock_count FROM products",
- [],
+ crate::empty_params(),
|row| {
Ok((
- row.get(0)?,
- row.get(1)?,
- row.get(2)?,
- row.get(3)?,
- row.get(4)?,
+ row.try_get(0)?,
+ row.try_get(1)?,
+ row.try_get(2)?,
+ row.try_get(3)?,
+ row.try_get(4)?,
))
},
)
diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs
@@ -1,14 +1,20 @@
#![forbid(unsafe_code)]
+mod db;
mod error;
mod interop;
mod migrations;
mod repo;
mod sdk_workflow_receipts;
+#[cfg(test)]
+mod source_guards;
mod sync;
-use std::{collections::BTreeSet, fs, path::PathBuf, time::Duration};
+use std::{collections::BTreeSet, fs, path::PathBuf};
+pub(crate) use db::{
+ AppSqliteDatabase, IntoAppSqliteParams, OptionalSqliteResult, app_sqlite_value, empty_params,
+};
use radroots_studio_app_sync::{
AppRelayIngestScopeFreshness, PendingSyncOperation, SyncCheckpointStatus, SyncConflict,
SyncConflictResolutionStatus,
@@ -24,7 +30,7 @@ use radroots_studio_app_view::{
ProductsListProjection, ProductsSort, ReminderFeedProjection, ReminderLogEntryProjection,
ReminderLogProjection, TodayAgendaProjection,
};
-use rusqlite::Connection;
+use sqlx::Row;
pub use error::AppSqliteError;
pub use interop::{
@@ -46,11 +52,23 @@ pub use sdk_workflow_receipts::{
AppSdkWorkflowReceiptSourceKind, AppSdkWorkflowReceiptState,
};
pub use sync::{
- AppSyncRepository, StoredPendingSyncOperation, StoredRelayIngestCursor, StoredSyncConflict,
+ AppRelayIngestFailureInput, AppRelayIngestSuccessInput, AppSyncRepository,
+ StoredPendingSyncOperation, StoredRelayIngestCursor, StoredSyncConflict,
};
const SQLITE_BUSY_TIMEOUT_MS: u64 = 5_000;
+macro_rules! app_sqlite_params {
+ () => {
+ $crate::empty_params()
+ };
+ ($($value:expr),+ $(,)?) => {
+ vec![$($crate::app_sqlite_value(&$value)),+]
+ };
+}
+
+pub(crate) use app_sqlite_params;
+
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DatabaseTarget {
InMemory,
@@ -58,23 +76,50 @@ pub enum DatabaseTarget {
}
pub struct AppSqliteStore {
- connection: Connection,
+ connection: AppSqliteDatabase,
}
impl AppSqliteStore {
pub fn open(target: DatabaseTarget) -> Result<Self, AppSqliteError> {
- let mut connection = open_connection(&target)?;
- bootstrap_connection(&mut connection, &target)?;
+ let connection = open_connection(&target)?;
+ bootstrap_connection(&connection, &target)?;
Ok(Self { connection })
}
- pub fn connection(&self) -> &Connection {
+ #[cfg(test)]
+ pub(crate) fn connection(&self) -> &AppSqliteDatabase {
&self.connection
}
- pub fn into_connection(self) -> Connection {
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn execute_test_sql(&self, sql: &str) -> Result<(), AppSqliteError> {
self.connection
+ .execute_script(sql)
+ .map_err(|source| AppSqliteError::Query {
+ operation: "execute test sqlite script",
+ source,
+ })
+ }
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn query_test_i64(&self, sql: &str) -> Result<i64, AppSqliteError> {
+ self.connection
+ .fetch_one(sql, empty_params(), |row| row.try_get::<i64, _>(0))
+ .map_err(|source| AppSqliteError::Query {
+ operation: "query test sqlite i64",
+ source,
+ })
+ }
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn query_test_string(&self, sql: &str) -> Result<String, AppSqliteError> {
+ self.connection
+ .fetch_one(sql, empty_params(), |row| row.try_get::<String, _>(0))
+ .map_err(|source| AppSqliteError::Query {
+ operation: "query test sqlite string",
+ source,
+ })
}
pub fn schema_version(&self) -> Result<u32, AppSqliteError> {
@@ -612,46 +657,16 @@ impl AppSqliteStore {
pub fn record_relay_ingest_success(
&self,
- scope_key: &str,
- relay_url: &str,
- cursor_since_unix_seconds: i64,
- last_event_created_at_unix_seconds: Option<i64>,
- started_at: &str,
- started_unix_seconds: i64,
- completed_at: &str,
- completed_unix_seconds: i64,
+ input: AppRelayIngestSuccessInput<'_>,
) -> Result<(), AppSqliteError> {
- self.sync_repository().record_relay_ingest_success(
- scope_key,
- relay_url,
- cursor_since_unix_seconds,
- last_event_created_at_unix_seconds,
- started_at,
- started_unix_seconds,
- completed_at,
- completed_unix_seconds,
- )
+ self.sync_repository().record_relay_ingest_success(input)
}
pub fn record_relay_ingest_failure(
&self,
- scope_key: &str,
- relay_url: &str,
- started_at: &str,
- started_unix_seconds: i64,
- completed_at: &str,
- completed_unix_seconds: i64,
- error_message: &str,
+ input: AppRelayIngestFailureInput<'_>,
) -> Result<(), AppSqliteError> {
- self.sync_repository().record_relay_ingest_failure(
- scope_key,
- relay_url,
- started_at,
- started_unix_seconds,
- completed_at,
- completed_unix_seconds,
- error_message,
- )
+ self.sync_repository().record_relay_ingest_failure(input)
}
pub fn record_sync_conflict(
@@ -690,24 +705,23 @@ impl AppSqliteStore {
}
}
-fn open_connection(target: &DatabaseTarget) -> Result<Connection, AppSqliteError> {
+fn open_connection(target: &DatabaseTarget) -> Result<AppSqliteDatabase, AppSqliteError> {
match target {
- DatabaseTarget::InMemory => {
- Connection::open_in_memory().map_err(|source| AppSqliteError::OpenInMemory { source })
- }
+ DatabaseTarget::InMemory => AppSqliteDatabase::open_in_memory()
+ .map_err(|source| AppSqliteError::OpenInMemory { source }),
DatabaseTarget::Path(path) => {
- if let Some(parent) = path.parent() {
- if !parent.as_os_str().is_empty() {
- fs::create_dir_all(parent).map_err(|source| {
- AppSqliteError::CreateParentDirectory {
- path: parent.to_path_buf(),
- source,
- }
- })?;
- }
+ if let Some(parent) = path.parent()
+ && !parent.as_os_str().is_empty()
+ {
+ fs::create_dir_all(parent).map_err(|source| {
+ AppSqliteError::CreateParentDirectory {
+ path: parent.to_path_buf(),
+ source,
+ }
+ })?;
}
- Connection::open(path).map_err(|source| AppSqliteError::OpenPath {
+ AppSqliteDatabase::open_path(path).map_err(|source| AppSqliteError::OpenPath {
path: path.clone(),
source,
})
@@ -716,20 +730,19 @@ fn open_connection(target: &DatabaseTarget) -> Result<Connection, AppSqliteError
}
fn bootstrap_connection(
- connection: &mut Connection,
+ connection: &AppSqliteDatabase,
target: &DatabaseTarget,
) -> Result<(), AppSqliteError> {
connection
- .busy_timeout(Duration::from_millis(SQLITE_BUSY_TIMEOUT_MS))
+ .execute_script(&format!("PRAGMA busy_timeout = {SQLITE_BUSY_TIMEOUT_MS}"))
.map_err(|source| AppSqliteError::ConfigureBusyTimeout { source })?;
-
apply_pragma(connection, "foreign_keys", "ON")?;
apply_pragma(connection, "synchronous", "NORMAL")?;
if matches!(target, DatabaseTarget::Path(_)) {
connection
- .query_row("PRAGMA journal_mode = WAL", [], |row| {
- row.get::<_, String>(0)
+ .fetch_one("PRAGMA journal_mode = WAL", empty_params(), |row| {
+ row.try_get::<String, _>(0)
})
.map_err(|source| AppSqliteError::ApplyPragma {
pragma: "journal_mode",
@@ -741,23 +754,25 @@ fn bootstrap_connection(
}
fn apply_pragma(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
pragma: &'static str,
value: &str,
) -> Result<(), AppSqliteError> {
let sql = format!("PRAGMA {pragma} = {value}");
connection
- .execute_batch(&sql)
+ .execute_script(&sql)
.map_err(|source| AppSqliteError::ApplyPragma { pragma, source })
}
-fn schema_version(connection: &Connection) -> Result<u32, AppSqliteError> {
+fn schema_version(connection: &AppSqliteDatabase) -> Result<u32, AppSqliteError> {
connection
- .query_row("PRAGMA user_version", [], |row| row.get(0))
+ .fetch_one("PRAGMA user_version", empty_params(), |row| {
+ row.try_get::<u32, _>(0)
+ })
.map_err(|source| AppSqliteError::ReadSchemaVersion { source })
}
-fn apply_migrations(connection: &mut Connection) -> Result<(), AppSqliteError> {
+fn apply_migrations(connection: &AppSqliteDatabase) -> Result<(), AppSqliteError> {
let current_version = schema_version(connection)?;
let latest_version = migrations::latest_schema_version();
@@ -769,18 +784,23 @@ fn apply_migrations(connection: &mut Connection) -> Result<(), AppSqliteError> {
}
for (version, sql) in migrations::pending_migrations(current_version) {
- let transaction = connection
- .transaction()
+ connection
+ .execute_script("BEGIN IMMEDIATE")
.map_err(|source| AppSqliteError::BeginMigration { version, source })?;
- transaction
- .execute_batch(sql)
- .map_err(|source| AppSqliteError::ExecuteMigration { version, source })?;
- transaction
- .pragma_update(None, "user_version", version)
- .map_err(|source| AppSqliteError::RecordSchemaVersion { version, source })?;
- transaction
- .commit()
+ if let Err(source) = connection.execute_script(sql) {
+ let _ = connection.execute_script("ROLLBACK");
+ return Err(AppSqliteError::ExecuteMigration { version, source });
+ }
+
+ let record_version_sql = format!("PRAGMA user_version = {version}");
+ if let Err(source) = connection.execute_script(&record_version_sql) {
+ let _ = connection.execute_script("ROLLBACK");
+ return Err(AppSqliteError::RecordSchemaVersion { version, source });
+ }
+
+ connection
+ .execute_script("COMMIT")
.map_err(|source| AppSqliteError::CommitMigration { version, source })?;
}
@@ -790,7 +810,9 @@ fn apply_migrations(connection: &mut Connection) -> Result<(), AppSqliteError> {
#[cfg(test)]
mod tests {
use super::{AppSqliteStore, DatabaseTarget, latest_schema_version};
- use rusqlite::{Connection, params};
+ use sqlx::Row;
+
+ use crate::AppSqliteDatabase;
use std::{
env, fs,
path::PathBuf,
@@ -1072,7 +1094,7 @@ mod tests {
'acknowledged',
'2026-01-01T00:00:00Z'
)",
- [],
+ crate::empty_params(),
)
.expect("local interop imports should accept validation receipt projections");
assert_eq!(row_count(connection, "sync_checkpoints"), 0);
@@ -1132,7 +1154,7 @@ mod tests {
.execute(
"INSERT INTO farms (id, display_name, readiness, created_at, updated_at)
VALUES (?1, 'Schema Farm', 'ready', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
- params!["farm_schema"],
+ crate::app_sqlite_params!["farm_schema"],
)
.expect("farm should insert");
connection
@@ -1156,7 +1178,7 @@ mod tests {
'invalid',
'needs_review'
)",
- [],
+ crate::empty_params(),
)
.expect("agreement-only workflow projection should insert");
connection
@@ -1180,7 +1202,7 @@ mod tests {
'agreed_pending_rhi',
'reserved'
)",
- [],
+ crate::empty_params(),
)
.expect("pending rhi workflow projection should insert");
@@ -1194,7 +1216,7 @@ mod tests {
updated_at,
workflow_agreement
) VALUES ('order_agreement_invalid', 'farm_schema', 'invalid', 'Buyer', 'scheduled', '2026-01-01T00:00:00Z', 'complete')",
- [],
+ crate::empty_params(),
);
assert!(invalid_result.is_err());
}
@@ -1230,7 +1252,7 @@ mod tests {
.execute(
"INSERT INTO farms (id, display_name, readiness, created_at, updated_at)
VALUES (?1, 'Schema Farm', 'ready', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
- params!["farm_status"],
+ crate::app_sqlite_params!["farm_status"],
)
.expect("farm should insert");
connection
@@ -1260,7 +1282,7 @@ mod tests {
'',
''
)",
- [],
+ crate::empty_params(),
)
.expect("order should insert");
connection
@@ -1278,7 +1300,7 @@ mod tests {
2,
'2 each'
)",
- [],
+ crate::empty_params(),
)
.expect("order line should insert");
connection
@@ -1296,7 +1318,7 @@ mod tests {
'2026-01-01T00:00:00Z',
'2026-01-01T00:00:00Z'
)",
- [],
+ crate::empty_params(),
)
.expect("buyer coordination should insert");
@@ -1312,62 +1334,62 @@ mod tests {
connection
.execute(
"UPDATE orders SET status = 'declined' WHERE id = 'order_status'",
- [],
+ crate::empty_params(),
)
.expect("declined status should satisfy current check");
connection
.execute(
"UPDATE orders SET status = 'needs_review' WHERE id = 'order_status'",
- [],
+ crate::empty_params(),
)
.expect("needs review status should satisfy current check");
let status: String = connection
.query_row(
"SELECT status FROM orders WHERE id = 'order_status'",
- [],
- |row| row.get(0),
+ crate::empty_params(),
+ |row| row.try_get(0),
)
.expect("status should load");
assert_eq!(status, "needs_review");
connection
.execute(
"UPDATE orders SET workflow_agreement = 'agreed_pending_rhi' WHERE id = 'order_status'",
- [],
+ crate::empty_params(),
)
.expect("agreed pending rhi agreement should satisfy current check");
}
- fn table_exists(connection: &Connection, table_name: &str) -> bool {
+ fn table_exists(connection: &AppSqliteDatabase, table_name: &str) -> bool {
connection
.query_row(
"SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?1)",
[table_name],
- |row| row.get::<_, i64>(0),
+ |row| row.try_get::<i64, _>(0),
)
.expect("table existence query should succeed")
== 1
}
- fn row_count(connection: &Connection, table_name: &str) -> i64 {
+ fn row_count(connection: &AppSqliteDatabase, table_name: &str) -> i64 {
let sql = format!("SELECT COUNT(*) FROM {table_name}");
connection
- .query_row(&sql, [], |row| row.get(0))
+ .query_row(&sql, crate::empty_params(), |row| row.try_get(0))
.expect("row count query should succeed")
}
- fn column_exists(connection: &Connection, table_name: &str, column_name: &str) -> bool {
+ fn column_exists(connection: &AppSqliteDatabase, table_name: &str, column_name: &str) -> bool {
let sql = format!("PRAGMA table_info({table_name})");
let mut statement = connection
.prepare(&sql)
.expect("table info statement should prepare");
let mut rows = statement
- .query([])
+ .query(crate::empty_params())
.expect("table info query should succeed");
while let Some(row) = rows.next().expect("table info row should load") {
if row
- .get::<_, String>(1)
+ .try_get::<String, _>(1)
.expect("table info name should load")
== column_name
{
@@ -1378,11 +1400,13 @@ mod tests {
false
}
- fn foreign_key_violation_count(connection: &Connection) -> usize {
+ fn foreign_key_violation_count(connection: &AppSqliteDatabase) -> usize {
let mut statement = connection
.prepare("PRAGMA foreign_key_check")
.expect("foreign key check should prepare");
- let mut rows = statement.query([]).expect("foreign key check should run");
+ let mut rows = statement
+ .query(crate::empty_params())
+ .expect("foreign key check should run");
let mut count = 0;
while rows
.next()
@@ -1394,17 +1418,17 @@ mod tests {
count
}
- fn pragma_i64(connection: &Connection, pragma_name: &str) -> i64 {
+ fn pragma_i64(connection: &AppSqliteDatabase, pragma_name: &str) -> i64 {
let sql = format!("PRAGMA {pragma_name}");
connection
- .query_row(&sql, [], |row| row.get(0))
+ .query_row(&sql, crate::empty_params(), |row| row.try_get(0))
.expect("pragma query should succeed")
}
- fn pragma_text(connection: &Connection, pragma_name: &str) -> String {
+ fn pragma_text(connection: &AppSqliteDatabase, pragma_name: &str) -> String {
let sql = format!("PRAGMA {pragma_name}");
connection
- .query_row(&sql, [], |row| row.get(0))
+ .query_row(&sql, crate::empty_params(), |row| row.try_get(0))
.expect("pragma query should succeed")
}
diff --git a/crates/store/src/repo/activation.rs b/crates/store/src/repo/activation.rs
@@ -2,16 +2,16 @@ use radroots_studio_app_view::{
AccountSurfaceActivationProjection, ActiveSurface, FarmId, FarmerActivationProjection,
SelectedSurfaceProjection,
};
-use rusqlite::{Connection, OptionalExtension, params};
+use sqlx::Row;
-use crate::AppSqliteError;
+use crate::{AppSqliteDatabase, AppSqliteError};
pub struct AppActivationRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppActivationRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
@@ -21,21 +21,20 @@ impl<'a> AppActivationRepository<'a> {
) -> Result<Option<AccountSurfaceActivationProjection>, AppSqliteError> {
let row = self
.connection
- .query_row(
+ .fetch_optional(
"SELECT account_id, selected_surface, farmer_farm_id
FROM account_surface_activations
WHERE account_id = ?1
LIMIT 1",
- [account_id],
+ crate::app_sqlite_params![account_id],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, Option<String>>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<Option<String>, _>(2)?,
))
},
)
- .optional()
.map_err(|source| AppSqliteError::Query {
operation: "load account surface activation",
source,
@@ -64,7 +63,7 @@ impl<'a> AppActivationRepository<'a> {
projection: &AccountSurfaceActivationProjection,
) -> Result<(), AppSqliteError> {
self.connection
- .execute(
+ .execute_statement(
"INSERT INTO account_surface_activations (
account_id,
selected_surface,
@@ -75,7 +74,7 @@ impl<'a> AppActivationRepository<'a> {
selected_surface = excluded.selected_surface,
farmer_farm_id = excluded.farmer_farm_id,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
projection.account_id,
projection.active_surface().storage_key(),
projection
@@ -94,9 +93,9 @@ impl<'a> AppActivationRepository<'a> {
pub fn clear_surface_activation(&self, account_id: &str) -> Result<(), AppSqliteError> {
self.connection
- .execute(
+ .execute_statement(
"DELETE FROM account_surface_activations WHERE account_id = ?1",
- [account_id],
+ crate::app_sqlite_params![account_id],
)
.map_err(|source| AppSqliteError::Query {
operation: "clear account surface activation",
@@ -157,7 +156,7 @@ mod tests {
let projection = AccountSurfaceActivationProjection::new(
"acct_farmer",
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
- FarmerActivationProjection::active(FarmId::new()),
+ FarmerActivationProjection::active(FarmId::generate()),
);
store
@@ -178,7 +177,7 @@ mod tests {
let first = AccountSurfaceActivationProjection::new(
"acct_surface",
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
- FarmerActivationProjection::active(FarmId::new()),
+ FarmerActivationProjection::active(FarmId::generate()),
);
let second = AccountSurfaceActivationProjection::new(
"acct_surface",
diff --git a/crates/store/src/repo/activity.rs b/crates/store/src/repo/activity.rs
@@ -2,31 +2,31 @@ use radroots_studio_app_view::{
ActivityEventId, AppActivityContext, AppActivityEvent, AppActivityKind, SettingsPreference,
SettingsSection,
};
-use rusqlite::{Connection, params};
+use sqlx::Row;
-use crate::AppSqliteError;
+use crate::{AppSqliteDatabase, AppSqliteError};
pub const APP_ACTIVITY_CONTEXT_LIMIT: usize = 64;
pub const APP_ACTIVITY_RETENTION_LIMIT: i64 = 5_000;
pub struct AppActivityRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppActivityRepository<'a> {
- pub fn new(connection: &'a Connection) -> Self {
+ pub(crate) fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
pub fn record(&self, kind: &AppActivityKind) -> Result<(), AppSqliteError> {
- let activity_event_id = ActivityEventId::new().to_string();
+ let activity_event_id = ActivityEventId::generate().to_string();
let event_kind = kind.storage_key();
let settings_section = settings_section_value(kind);
let settings_preference = settings_preference_value(kind);
let preference_enabled = preference_enabled_value(kind);
self.connection
- .execute(
+ .execute_statement(
"INSERT INTO activity_events (
activity_event_id,
event_kind,
@@ -34,7 +34,7 @@ impl<'a> AppActivityRepository<'a> {
settings_preference,
preference_enabled
) VALUES (?1, ?2, ?3, ?4, ?5)",
- params![
+ crate::app_sqlite_params![
activity_event_id,
event_kind,
settings_section,
@@ -53,9 +53,12 @@ impl<'a> AppActivityRepository<'a> {
}
pub fn load_recent(&self, limit: usize) -> Result<Vec<AppActivityEvent>, AppSqliteError> {
- let mut statement = self
+ let query_limit = i64::try_from(limit).map_err(|_| AppSqliteError::InvalidProjection {
+ reason: "activity query limit exceeds sqlite integer range",
+ })?;
+ let rows = self
.connection
- .prepare(
+ .fetch_mapped(
"SELECT
activity_event_id,
recorded_at,
@@ -66,30 +69,26 @@ impl<'a> AppActivityRepository<'a> {
FROM activity_events
ORDER BY recorded_at DESC, activity_event_id DESC
LIMIT ?1",
+ crate::app_sqlite_params![query_limit],
+ |row| {
+ let activity_event_id = row.try_get::<String, _>(0)?;
+ let recorded_at = row.try_get::<String, _>(1)?;
+ let event_kind = row.try_get::<String, _>(2)?;
+ let settings_section = row.try_get::<Option<String>, _>(3)?;
+ let settings_preference = row.try_get::<Option<String>, _>(4)?;
+ let preference_enabled = row.try_get::<Option<i64>, _>(5)?;
+
+ Ok((
+ activity_event_id,
+ recorded_at,
+ event_kind,
+ settings_section,
+ settings_preference,
+ preference_enabled,
+ ))
+ },
)
.map_err(|source| AppSqliteError::Query {
- operation: "prepare recent activity query",
- source,
- })?;
- let rows = statement
- .query_map([limit as i64], |row| {
- let activity_event_id = row.get::<_, String>(0)?;
- let recorded_at = row.get::<_, String>(1)?;
- let event_kind = row.get::<_, String>(2)?;
- let settings_section = row.get::<_, Option<String>>(3)?;
- let settings_preference = row.get::<_, Option<String>>(4)?;
- let preference_enabled = row.get::<_, Option<i64>>(5)?;
-
- Ok((
- activity_event_id,
- recorded_at,
- event_kind,
- settings_section,
- settings_preference,
- preference_enabled,
- ))
- })
- .map_err(|source| AppSqliteError::Query {
operation: "query recent activity events",
source,
})?;
@@ -127,7 +126,7 @@ impl<'a> AppActivityRepository<'a> {
fn trim_retained_events(&self, retention_limit: i64) -> Result<(), AppSqliteError> {
self.connection
- .execute(
+ .execute_statement(
"DELETE FROM activity_events
WHERE activity_event_id IN (
SELECT activity_event_id
@@ -135,7 +134,7 @@ impl<'a> AppActivityRepository<'a> {
ORDER BY recorded_at DESC, activity_event_id DESC
LIMIT -1 OFFSET ?1
)",
- [retention_limit],
+ crate::app_sqlite_params![retention_limit],
)
.map_err(|source| AppSqliteError::Query {
operation: "trim retained activity events",
@@ -266,9 +265,9 @@ fn preference_enabled_value(kind: &AppActivityKind) -> Option<i64> {
#[cfg(test)]
mod tests {
use radroots_studio_app_view::{AppActivityKind, SettingsPreference, SettingsSection};
- use rusqlite::Connection;
+ use sqlx::Row;
- use crate::{AppSqliteStore, DatabaseTarget};
+ use crate::{AppSqliteDatabase, AppSqliteStore, DatabaseTarget, empty_params};
use super::{APP_ACTIVITY_CONTEXT_LIMIT, APP_ACTIVITY_RETENTION_LIMIT, AppActivityRepository};
@@ -330,12 +329,12 @@ mod tests {
#[test]
fn activity_repository_trims_events_to_retention_limit() {
- let connection = Connection::open_in_memory().expect("open in-memory connection");
+ let connection = AppSqliteDatabase::open_in_memory().expect("open in-memory connection");
connection
- .execute_batch(include_str!("../../migrations/0001_init.sql"))
+ .execute_script(include_str!("../../migrations/0001_init.sql"))
.expect("apply init migration");
connection
- .execute_batch(include_str!("../../migrations/0002_activity_journal.sql"))
+ .execute_script(include_str!("../../migrations/0002_activity_journal.sql"))
.expect("apply activity migration");
let repository = AppActivityRepository::new(&connection);
@@ -350,10 +349,10 @@ mod tests {
assert_eq!(retained, APP_ACTIVITY_RETENTION_LIMIT);
}
- fn count_rows(connection: &Connection, table_name: &str) -> i64 {
+ fn count_rows(connection: &AppSqliteDatabase, table_name: &str) -> i64 {
let sql = format!("SELECT COUNT(*) FROM {table_name}");
connection
- .query_row(&sql, [], |row| row.get(0))
+ .fetch_one(&sql, empty_params(), |row| row.try_get(0))
.expect("row count query should succeed")
}
}
diff --git a/crates/store/src/repo/buyer.rs b/crates/store/src/repo/buyer.rs
@@ -10,7 +10,9 @@ use radroots_studio_app_view::{
ProductPricePresentation, ProductStatus, ProductStockState, ProductStockSummary,
RepeatDemandEligibility, RepeatDemandHandoffProjection,
};
-use rusqlite::{Connection, OptionalExtension, params, params_from_iter};
+use sqlx::Row;
+
+use crate::{AppSqliteDatabase, OptionalSqliteResult};
use serde_json::Value;
use super::{
@@ -147,11 +149,11 @@ pub struct BuyerOrderCoordinationRecord {
}
pub struct AppBuyerRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppBuyerRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
@@ -231,7 +233,7 @@ impl<'a> AppBuyerRepository<'a> {
on conflict(buyer_context_key) do update set
farm_id = excluded.farm_id,
updated_at = excluded.updated_at",
- params![context_key.as_str(), farm_id.map(|id| id.to_string())],
+ crate::app_sqlite_params![context_key.as_str(), farm_id.map(|id| id.to_string())],
)
.map_err(|source| AppSqliteError::Query {
operation: "save buyer cart header",
@@ -240,7 +242,7 @@ impl<'a> AppBuyerRepository<'a> {
self.connection
.execute(
"delete from buyer_cart_lines where buyer_context_key = ?1",
- params![context_key.as_str()],
+ crate::app_sqlite_params![context_key.as_str()],
)
.map_err(|source| AppSqliteError::Query {
operation: "clear buyer cart lines",
@@ -267,7 +269,7 @@ impl<'a> AppBuyerRepository<'a> {
seller_pubkey,
updated_at
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))",
- params![
+ crate::app_sqlite_params![
context_key.as_str(),
line.product_id.to_string(),
i64::from(line.quantity),
@@ -297,7 +299,7 @@ impl<'a> AppBuyerRepository<'a> {
self.connection
.execute(
"delete from buyer_cart_lines where buyer_context_key = ?1",
- params![context_key.as_str()],
+ crate::app_sqlite_params![context_key.as_str()],
)
.map_err(|source| AppSqliteError::Query {
operation: "delete buyer cart lines",
@@ -311,7 +313,7 @@ impl<'a> AppBuyerRepository<'a> {
buyer_order_note = '',
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
where buyer_context_key = ?1",
- params![context_key.as_str()],
+ crate::app_sqlite_params![context_key.as_str()],
)
.map_err(|source| AppSqliteError::Query {
operation: "clear buyer cart header",
@@ -369,7 +371,7 @@ impl<'a> AppBuyerRepository<'a> {
updated_at
) values (?1, null, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
on conflict(buyer_context_key) do nothing",
- params![context_key.as_str()],
+ crate::app_sqlite_params![context_key.as_str()],
)
.map_err(|source| AppSqliteError::Query {
operation: "ensure buyer order review header",
@@ -386,7 +388,7 @@ impl<'a> AppBuyerRepository<'a> {
buyer_order_note_public_confirmed = ?6,
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
where buyer_context_key = ?1",
- params![
+ crate::app_sqlite_params![
context_key.as_str(),
draft.name.trim(),
draft.email.trim(),
@@ -424,7 +426,7 @@ impl<'a> AppBuyerRepository<'a> {
reason: "buyer cart farm is missing",
})?;
let fulfillment_window_id = shared_fulfillment_window_id(&line_records)?;
- let order_id = OrderId::new();
+ let order_id = OrderId::generate();
let order_number = self.next_order_number(farm_id)?;
self.connection
@@ -464,7 +466,7 @@ impl<'a> AppBuyerRepository<'a> {
?9,
?10
)",
- params![
+ crate::app_sqlite_params![
order_id.to_string(),
farm_id.to_string(),
fulfillment_window_id.map(|id| id.to_string()),
@@ -503,7 +505,7 @@ impl<'a> AppBuyerRepository<'a> {
seller_pubkey,
sort_index
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)",
- params![
+ crate::app_sqlite_params![
format!("{}:{}", order_id, line.listing.product_id),
order_id.to_string(),
line.listing.title,
@@ -530,7 +532,7 @@ impl<'a> AppBuyerRepository<'a> {
self.connection
.execute(
"delete from buyer_cart_lines where buyer_context_key = ?1",
- params![context_key.as_str()],
+ crate::app_sqlite_params![context_key.as_str()],
)
.map_err(|source| AppSqliteError::Query {
operation: "clear buyer cart lines after order review",
@@ -545,7 +547,7 @@ impl<'a> AppBuyerRepository<'a> {
buyer_order_note_public_confirmed = 0,
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
where buyer_context_key = ?1",
- params![context_key.as_str()],
+ crate::app_sqlite_params![context_key.as_str()],
)
.map_err(|source| AppSqliteError::Query {
operation: "reset buyer cart header after order review",
@@ -593,16 +595,16 @@ impl<'a> AppBuyerRepository<'a> {
from buyer_order_coordination_records
where buyer_context_key = ?1 and order_id = ?2
limit 1",
- params![context_key.as_str(), order_id.to_string()],
+ crate::app_sqlite_params![context_key.as_str(), order_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, Option<String>>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, Option<String>>(4)?,
- row.get::<_, i64>(5)?,
- row.get::<_, Option<String>>(6)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<Option<String>, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<Option<String>, _>(4)?,
+ row.try_get::<i64, _>(5)?,
+ row.try_get::<Option<String>, _>(6)?,
))
},
)
@@ -640,15 +642,15 @@ impl<'a> AppBuyerRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![context_key.as_str()], |row| {
+ .query_map(crate::app_sqlite_params![context_key.as_str()], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, Option<String>>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, Option<String>>(4)?,
- row.get::<_, i64>(5)?,
- row.get::<_, Option<String>>(6)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<Option<String>, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<Option<String>, _>(4)?,
+ row.try_get::<i64, _>(5)?,
+ row.try_get::<Option<String>, _>(6)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -721,7 +723,7 @@ impl<'a> AppBuyerRepository<'a> {
synced_at = null
where buyer_order_coordination_records.buyer_context_key = excluded.buyer_context_key
and buyer_order_coordination_records.state <> 'synced'",
- params![
+ crate::app_sqlite_params![
order_id.to_string(),
context_key.as_str(),
record_id,
@@ -752,7 +754,7 @@ impl<'a> AppBuyerRepository<'a> {
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
synced_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
where buyer_context_key = ?1 and order_id = ?2",
- params![context_key.as_str(), order_id.to_string()],
+ crate::app_sqlite_params![context_key.as_str(), order_id.to_string()],
)
.map_err(|source| AppSqliteError::Query {
operation: "mark buyer order coordination synced",
@@ -779,7 +781,11 @@ impl<'a> AppBuyerRepository<'a> {
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
synced_at = null
where buyer_context_key = ?1 and order_id = ?2",
- params![context_key.as_str(), order_id.to_string(), error_message],
+ crate::app_sqlite_params![
+ context_key.as_str(),
+ order_id.to_string(),
+ error_message
+ ],
)
.map_err(|source| AppSqliteError::Query {
operation: "mark buyer order coordination failed",
@@ -836,21 +842,21 @@ impl<'a> AppBuyerRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params_from_iter(context_keys.iter()), |row| {
+ .query_map(context_keys.to_vec(), |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, String>(7)?,
- row.get::<_, Option<String>>(8)?,
- row.get::<_, String>(9)?,
- row.get::<_, Option<String>>(10)?,
- row.get::<_, Option<String>>(11)?,
- row.get::<_, Option<String>>(12)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<String, _>(7)?,
+ row.try_get::<Option<String>, _>(8)?,
+ row.try_get::<String, _>(9)?,
+ row.try_get::<Option<String>, _>(10)?,
+ row.try_get::<Option<String>, _>(11)?,
+ row.try_get::<Option<String>, _>(12)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -968,22 +974,22 @@ impl<'a> AppBuyerRepository<'a> {
params.push(order_id.to_string());
let record = self
.connection
- .query_row(query.as_str(), params_from_iter(params.iter()), |row| {
+ .query_row(query.as_str(), params, |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, String>(7)?,
- row.get::<_, String>(8)?,
- row.get::<_, Option<String>>(9)?,
- row.get::<_, String>(10)?,
- row.get::<_, Option<String>>(11)?,
- row.get::<_, Option<String>>(12)?,
- row.get::<_, Option<String>>(13)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<String, _>(7)?,
+ row.try_get::<String, _>(8)?,
+ row.try_get::<Option<String>, _>(9)?,
+ row.try_get::<String, _>(10)?,
+ row.try_get::<Option<String>, _>(11)?,
+ row.try_get::<Option<String>, _>(12)?,
+ row.try_get::<Option<String>, _>(13)?,
))
})
.optional()
@@ -1088,25 +1094,25 @@ impl<'a> AppBuyerRepository<'a> {
left join fulfillment_windows fw on fw.id = o.fulfillment_window_id
where o.buyer_context_key = ?1 and o.id = ?2
limit 1",
- params![context_key.as_str(), order_id.to_string()],
+ crate::app_sqlite_params![context_key.as_str(), order_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, Option<String>>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, String>(7)?,
- row.get::<_, String>(8)?,
- row.get::<_, bool>(9)?,
- row.get::<_, String>(10)?,
- row.get::<_, String>(11)?,
- row.get::<_, Option<String>>(12)?,
- row.get::<_, Option<String>>(13)?,
- row.get::<_, Option<String>>(14)?,
- row.get::<_, Option<String>>(15)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<Option<String>, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<String, _>(7)?,
+ row.try_get::<String, _>(8)?,
+ row.try_get::<bool, _>(9)?,
+ row.try_get::<String, _>(10)?,
+ row.try_get::<String, _>(11)?,
+ row.try_get::<Option<String>, _>(12)?,
+ row.try_get::<Option<String>, _>(13)?,
+ row.try_get::<Option<String>, _>(14)?,
+ row.try_get::<Option<String>, _>(15)?,
))
},
)
@@ -1293,9 +1299,11 @@ impl<'a> AppBuyerRepository<'a> {
fn current_utc_timestamp(&self) -> Result<String, AppSqliteError> {
self.connection
- .query_row("select strftime('%Y-%m-%dT%H:%M:%SZ', 'now')", [], |row| {
- row.get(0)
- })
+ .query_row(
+ "select strftime('%Y-%m-%dT%H:%M:%SZ', 'now')",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.map_err(|source| AppSqliteError::Query {
operation: "load buyer current utc timestamp",
source,
@@ -1324,7 +1332,7 @@ impl<'a> AppBuyerRepository<'a> {
strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
)",
- params![order_id.to_string(), context_key],
+ crate::app_sqlite_params![order_id.to_string(), context_key],
)
.map_err(|source| AppSqliteError::Query {
operation: "insert pending buyer order coordination record",
@@ -1428,33 +1436,33 @@ impl<'a> AppBuyerRepository<'a> {
source,
})?;
let rows = statement
- .query_map([], |row| {
+ .query_map(crate::empty_params(), |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, String>(7)?,
- row.get::<_, Option<u32>>(8)?,
- row.get::<_, String>(9)?,
- row.get::<_, Option<String>>(10)?,
- row.get::<_, Option<String>>(11)?,
- row.get::<_, Option<String>>(12)?,
- row.get::<_, Option<String>>(13)?,
- row.get::<_, Option<String>>(14)?,
- row.get::<_, Option<String>>(15)?,
- row.get::<_, Option<u32>>(16)?,
- row.get::<_, Option<String>>(17)?,
- row.get::<_, Option<String>>(18)?,
- row.get::<_, Option<String>>(19)?,
- row.get::<_, Option<String>>(20)?,
- row.get::<_, Option<String>>(21)?,
- row.get::<_, i64>(22)?,
- row.get::<_, i64>(23)?,
- row.get::<_, i64>(24)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<String, _>(7)?,
+ row.try_get::<Option<u32>, _>(8)?,
+ row.try_get::<String, _>(9)?,
+ row.try_get::<Option<String>, _>(10)?,
+ row.try_get::<Option<String>, _>(11)?,
+ row.try_get::<Option<String>, _>(12)?,
+ row.try_get::<Option<String>, _>(13)?,
+ row.try_get::<Option<String>, _>(14)?,
+ row.try_get::<Option<String>, _>(15)?,
+ row.try_get::<Option<u32>, _>(16)?,
+ row.try_get::<Option<String>, _>(17)?,
+ row.try_get::<Option<String>, _>(18)?,
+ row.try_get::<Option<String>, _>(19)?,
+ row.try_get::<Option<String>, _>(20)?,
+ row.try_get::<Option<String>, _>(21)?,
+ row.try_get::<i64, _>(22)?,
+ row.try_get::<i64, _>(23)?,
+ row.try_get::<i64, _>(24)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -1582,15 +1590,15 @@ impl<'a> AppBuyerRepository<'a> {
from buyer_carts
where buyer_context_key = ?1
limit 1",
- params![context_key],
+ crate::app_sqlite_params![context_key],
|row| {
Ok((
- row.get::<_, Option<String>>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, bool>(5)?,
+ row.try_get::<Option<String>, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<bool, _>(5)?,
))
},
)
@@ -1723,34 +1731,34 @@ impl<'a> AppBuyerRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![context_key], |row| {
+ .query_map(crate::app_sqlite_params![context_key], |row| {
Ok((
- row.get::<_, u32>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, String>(7)?,
- row.get::<_, String>(8)?,
- row.get::<_, Option<u32>>(9)?,
- row.get::<_, String>(10)?,
- row.get::<_, Option<String>>(11)?,
- row.get::<_, Option<String>>(12)?,
- row.get::<_, Option<String>>(13)?,
- row.get::<_, Option<String>>(14)?,
- row.get::<_, Option<String>>(15)?,
- row.get::<_, Option<String>>(16)?,
- row.get::<_, Option<u32>>(17)?,
- row.get::<_, Option<String>>(18)?,
- row.get::<_, Option<String>>(19)?,
- row.get::<_, Option<String>>(20)?,
- row.get::<_, Option<String>>(21)?,
- row.get::<_, Option<String>>(22)?,
- row.get::<_, i64>(23)?,
- row.get::<_, i64>(24)?,
- row.get::<_, i64>(25)?,
+ row.try_get::<u32, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<String, _>(7)?,
+ row.try_get::<String, _>(8)?,
+ row.try_get::<Option<u32>, _>(9)?,
+ row.try_get::<String, _>(10)?,
+ row.try_get::<Option<String>, _>(11)?,
+ row.try_get::<Option<String>, _>(12)?,
+ row.try_get::<Option<String>, _>(13)?,
+ row.try_get::<Option<String>, _>(14)?,
+ row.try_get::<Option<String>, _>(15)?,
+ row.try_get::<Option<String>, _>(16)?,
+ row.try_get::<Option<u32>, _>(17)?,
+ row.try_get::<Option<String>, _>(18)?,
+ row.try_get::<Option<String>, _>(19)?,
+ row.try_get::<Option<String>, _>(20)?,
+ row.try_get::<Option<String>, _>(21)?,
+ row.try_get::<Option<String>, _>(22)?,
+ row.try_get::<i64, _>(23)?,
+ row.try_get::<i64, _>(24)?,
+ row.try_get::<i64, _>(25)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -1862,14 +1870,14 @@ impl<'a> AppBuyerRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![order_id], |row| {
+ .query_map(crate::app_sqlite_params![order_id], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, i64>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, Option<u32>>(4)?,
- row.get::<_, Option<String>>(5)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<i64, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<Option<u32>, _>(4)?,
+ row.try_get::<Option<String>, _>(5)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -1934,21 +1942,21 @@ impl<'a> AppBuyerRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![order_id_string.as_str()], |row| {
+ .query_map(crate::app_sqlite_params![order_id_string.as_str()], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, i64>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, Option<u32>>(5)?,
- row.get::<_, Option<String>>(6)?,
- row.get::<_, Option<String>>(7)?,
- row.get::<_, Option<String>>(8)?,
- row.get::<_, Option<String>>(9)?,
- row.get::<_, Option<String>>(10)?,
- row.get::<_, Option<String>>(11)?,
- row.get::<_, Option<String>>(12)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<i64, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<Option<u32>, _>(5)?,
+ row.try_get::<Option<String>, _>(6)?,
+ row.try_get::<Option<String>, _>(7)?,
+ row.try_get::<Option<String>, _>(8)?,
+ row.try_get::<Option<String>, _>(9)?,
+ row.try_get::<Option<String>, _>(10)?,
+ row.try_get::<Option<String>, _>(11)?,
+ row.try_get::<Option<String>, _>(12)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -2024,11 +2032,11 @@ impl<'a> AppBuyerRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![order_id.to_string()], |row| {
+ .query_map(crate::app_sqlite_params![order_id.to_string()], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, i64>(1)?,
- row.get::<_, Option<String>>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<i64, _>(1)?,
+ row.try_get::<Option<String>, _>(2)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -2084,8 +2092,8 @@ impl<'a> AppBuyerRepository<'a> {
let mut params = context_keys.to_vec();
params.push(order_id.to_string());
self.connection
- .query_row(query.as_str(), params_from_iter(params.iter()), |row| {
- Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
+ .query_row(query.as_str(), params, |row| {
+ Ok((row.try_get::<String, _>(0)?, row.try_get::<String, _>(1)?))
})
.optional()
.map_err(|source| AppSqliteError::Query {
@@ -2187,8 +2195,8 @@ impl<'a> AppBuyerRepository<'a> {
self.connection
.query_row(
"select display_name from farms where id = ?1 limit 1",
- params![farm_id.to_string()],
- |row| row.get::<_, String>(0),
+ crate::app_sqlite_params![farm_id.to_string()],
+ |row| row.try_get::<String, _>(0),
)
.optional()
.map_err(|source| AppSqliteError::Query {
@@ -2206,8 +2214,8 @@ impl<'a> AppBuyerRepository<'a> {
where farm_id = ?1
and order_number like 'R-%'
and substr(order_number, 3) glob '[0-9]*'",
- params![farm_id.to_string()],
- |row| row.get::<_, i64>(0),
+ crate::app_sqlite_params![farm_id.to_string()],
+ |row| row.try_get::<i64, _>(0),
)
.map_err(|source| AppSqliteError::Query {
operation: "load next buyer order number",
@@ -2978,7 +2986,9 @@ mod tests {
OrderId, PickupLocationId, ProductId, ProductPricePresentation, TradeAgreementStatus,
TradeInventoryStatus, TradeRevisionStatus, TradeWorkflowSource,
};
- use rusqlite::{Connection, params};
+ use sqlx::Row;
+
+ use crate::AppSqliteDatabase;
use serde_json::json;
use crate::{
@@ -2994,11 +3004,11 @@ mod tests {
#[test]
fn buyer_order_review_requires_public_note_confirmation() {
let cart = BuyerCartProjection {
- farm_id: Some(FarmId::new()),
+ farm_id: Some(FarmId::generate()),
farm_display_name: Some("Willow Farm".to_owned()),
lines: vec![BuyerCartLineProjection {
- product_id: ProductId::new(),
- farm_id: FarmId::new(),
+ product_id: ProductId::generate(),
+ farm_id: FarmId::generate(),
farm_display_name: "Willow Farm".to_owned(),
title: "Salad mix".to_owned(),
quantity: 1,
@@ -3219,8 +3229,8 @@ mod tests {
let connection = store.connection();
let repository = AppBuyerRepository::new(connection);
let farm_id = insert_farm(connection, "Willow Farm", "ready");
- let account_order_id = OrderId::new();
- let relay_order_id = OrderId::new();
+ let account_order_id = OrderId::generate();
+ let relay_order_id = OrderId::generate();
let linked_context_key = format!("nostr:{LINKED_BUYER_PUBKEY}");
insert_order(
connection,
@@ -3491,7 +3501,7 @@ mod tests {
connection
.execute(
"update products set status = 'archived' where id = ?1",
- params![unavailable_product_id.to_string()],
+ crate::app_sqlite_params![unavailable_product_id.to_string()],
)
.expect("product should archive");
@@ -3618,7 +3628,10 @@ mod tests {
connection
.execute(
"update orders set buyer_context_key = ?1 where id = ?2",
- params![format!("nostr:{LINKED_BUYER_PUBKEY}"), order_id.to_string()],
+ crate::app_sqlite_params![
+ format!("nostr:{LINKED_BUYER_PUBKEY}"),
+ order_id.to_string()
+ ],
)
.expect("linked order context should mutate");
@@ -3640,8 +3653,8 @@ mod tests {
let nostr_cart_line_count: u32 = connection
.query_row(
"select count(*) from buyer_cart_lines where buyer_context_key = ?1",
- params![format!("nostr:{LINKED_BUYER_PUBKEY}")],
- |row| row.get(0),
+ crate::app_sqlite_params![format!("nostr:{LINKED_BUYER_PUBKEY}")],
+ |row| row.try_get(0),
)
.expect("nostr cart line count should load");
@@ -3733,7 +3746,7 @@ mod tests {
connection
.execute(
"update products set stock_count = 1 where id = ?1",
- params![product_id.to_string()],
+ crate::app_sqlite_params![product_id.to_string()],
)
.expect("product stock should lower");
@@ -3774,7 +3787,7 @@ mod tests {
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
"R-100",
"needs_action",
@@ -3785,7 +3798,7 @@ mod tests {
);
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
"R-101",
"scheduled",
@@ -3796,7 +3809,7 @@ mod tests {
);
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
"R-102",
"packed",
@@ -3826,7 +3839,7 @@ mod tests {
let repository = AppBuyerRepository::new(connection);
let context = BuyerContext::Guest;
let farm_id = insert_farm(connection, "Willow Farm", "ready");
- let order_id = OrderId::new();
+ let order_id = OrderId::generate();
insert_order(
connection,
@@ -3879,8 +3892,8 @@ mod tests {
let repository = AppBuyerRepository::new(connection);
let context = BuyerContext::account("acct_buyer");
let farm_id = insert_farm(connection, "Willow Farm", "ready");
- let order_id = OrderId::new();
- let product_id = ProductId::new();
+ let order_id = OrderId::generate();
+ let product_id = ProductId::generate();
insert_order(
connection,
@@ -3949,7 +3962,7 @@ mod tests {
let repository = AppBuyerRepository::new(connection);
let context = BuyerContext::account("acct_buyer");
let farm_id = insert_farm(connection, "Willow Farm", "ready");
- let order_id = OrderId::new();
+ let order_id = OrderId::generate();
insert_order(
connection,
@@ -4004,8 +4017,8 @@ mod tests {
#[test]
fn buyer_cart_rejects_cross_farm_lines() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
- let farm_id = FarmId::new();
- let other_farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
+ let other_farm_id = FarmId::generate();
let error = repository_error(&store, farm_id, other_farm_id);
@@ -4024,7 +4037,7 @@ mod tests {
farm_id: Some(farm_id),
farm_display_name: Some("Willow Farm".to_owned()),
lines: vec![radroots_studio_app_view::BuyerCartLineProjection {
- product_id: ProductId::new(),
+ product_id: ProductId::generate(),
farm_id: other_farm_id,
farm_display_name: "Other Farm".to_owned(),
title: "Mismatch".to_owned(),
@@ -4045,8 +4058,8 @@ mod tests {
.expect_err("cross-farm cart should fail")
}
- fn insert_farm(connection: &Connection, display_name: &str, readiness: &str) -> FarmId {
- let farm_id = FarmId::new();
+ fn insert_farm(connection: &AppSqliteDatabase, display_name: &str, readiness: &str) -> FarmId {
+ let farm_id = FarmId::generate();
connection
.execute(
@@ -4059,7 +4072,7 @@ mod tests {
created_at,
updated_at
) values (?1, ?2, ?3, 'UTC', 'USD', '2026-04-20T08:00:00Z', '2026-04-20T08:00:00Z')",
- params![farm_id.to_string(), display_name, readiness],
+ crate::app_sqlite_params![farm_id.to_string(), display_name, readiness],
)
.expect("farm insert should succeed");
@@ -4067,11 +4080,11 @@ mod tests {
}
fn insert_pickup_location(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
farm_id: FarmId,
label: &str,
) -> PickupLocationId {
- let pickup_location_id = PickupLocationId::new();
+ let pickup_location_id = PickupLocationId::generate();
connection
.execute(
@@ -4085,7 +4098,7 @@ mod tests {
created_at,
updated_at
) values (?1, ?2, ?3, '14 County Road', null, 1, '2026-04-20T08:00:00Z', '2026-04-20T08:00:00Z')",
- params![pickup_location_id.to_string(), farm_id.to_string(), label],
+ crate::app_sqlite_params![pickup_location_id.to_string(), farm_id.to_string(), label],
)
.expect("pickup location insert should succeed");
@@ -4093,14 +4106,14 @@ mod tests {
}
fn insert_window(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
farm_id: FarmId,
pickup_location_id: Option<PickupLocationId>,
label: &str,
starts_at: &str,
ends_at: &str,
) -> FulfillmentWindowId {
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
connection
.execute(
@@ -4116,7 +4129,7 @@ mod tests {
label,
order_cutoff_at
) values (?1, ?2, ?3, ?4, null, ?3, ?3, ?5, ?6, ?3)",
- params![
+ crate::app_sqlite_params![
fulfillment_window_id.to_string(),
farm_id.to_string(),
starts_at,
@@ -4131,7 +4144,7 @@ mod tests {
}
fn insert_farm_setup_binding(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
account_id: &str,
farm_id: FarmId,
pickup_enabled: bool,
@@ -4152,7 +4165,7 @@ mod tests {
saved_farm_readiness,
updated_at
) values (?1, 'Willow Farm', 'County Road', ?2, ?3, ?4, ?5, 'Willow Farm', 'ready', '2026-04-20T08:00:00Z')",
- params![
+ crate::app_sqlite_params![
account_id,
i64::from(pickup_enabled),
i64::from(delivery_enabled),
@@ -4175,11 +4188,11 @@ mod tests {
}
fn insert_product(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
farm_id: FarmId,
product: SeedProduct<'_>,
) -> ProductId {
- let product_id = ProductId::new();
+ let product_id = ProductId::generate();
connection
.execute(
@@ -4196,7 +4209,7 @@ mod tests {
availability_window_id,
updated_at
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, '2026-04-20T09:00:00Z')",
- params![
+ crate::app_sqlite_params![
product_id.to_string(),
farm_id.to_string(),
product.title,
@@ -4214,8 +4227,9 @@ mod tests {
product_id
}
+ #[expect(clippy::too_many_arguments)]
fn insert_order(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
farm_id: FarmId,
order_number: &str,
@@ -4240,7 +4254,7 @@ mod tests {
buyer_phone,
buyer_order_note
) values (?1, ?2, null, ?3, 'Casey', ?4, '2026-04-20T10:00:00Z', ?5, ?6, ?7, ?8)",
- params![
+ crate::app_sqlite_params![
order_id.to_string(),
farm_id.to_string(),
order_number,
@@ -4255,20 +4269,20 @@ mod tests {
}
fn set_order_workflow_revision(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
workflow_revision: &str,
) {
connection
.execute(
"update orders set workflow_revision = ?1 where id = ?2",
- params![workflow_revision, order_id.to_string()],
+ crate::app_sqlite_params![workflow_revision, order_id.to_string()],
)
.expect("order workflow revision update should succeed");
}
fn set_order_workflow_display_projection(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
agreement: &str,
inventory: &str,
@@ -4283,7 +4297,7 @@ mod tests {
workflow_provenance_source = ?3,
workflow_provenance_last_event_id = ?4
where id = ?5",
- params![
+ crate::app_sqlite_params![
agreement,
inventory,
provenance_source,
@@ -4294,8 +4308,9 @@ mod tests {
.expect("order workflow display projection update should succeed");
}
+ #[expect(clippy::too_many_arguments)]
fn insert_order_line(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
product_id: ProductId,
title: &str,
@@ -4317,7 +4332,7 @@ mod tests {
price_currency,
sort_index
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, 0)",
- params![
+ crate::app_sqlite_params![
format!("{order_id}:{product_id}"),
order_id.to_string(),
title,
@@ -4332,7 +4347,7 @@ mod tests {
}
fn corrupt_order_workflow_revision(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
workflow_revision: &str,
) {
@@ -4346,7 +4361,7 @@ mod tests {
}
fn corrupt_order_workflow_display_projection(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
column: &str,
value: &str,
@@ -4363,7 +4378,10 @@ mod tests {
_ => panic!("unsupported workflow display projection column {column}"),
};
connection
- .execute(statement, params![value, order_id.to_string()])
+ .execute(
+ statement,
+ crate::app_sqlite_params![value, order_id.to_string()],
+ )
.expect("order workflow display projection corruption should succeed");
connection
.execute_batch("pragma ignore_check_constraints = off")
@@ -4380,11 +4398,11 @@ mod tests {
}
}
- fn row_count(connection: &Connection, table_name: &str) -> i64 {
+ fn row_count(connection: &AppSqliteDatabase, table_name: &str) -> i64 {
let sql = format!("SELECT COUNT(*) FROM {table_name}");
connection
- .query_row(&sql, [], |row| row.get(0))
+ .query_row(&sql, crate::empty_params(), |row| row.try_get(0))
.expect("row count query should succeed")
}
}
diff --git a/crates/store/src/repo/farm_rules.rs b/crates/store/src/repo/farm_rules.rs
@@ -5,16 +5,18 @@ use radroots_studio_app_view::{
FarmReadinessBlocker, FarmRulesProjection, FarmRulesReadiness, FarmTimingConflict,
FarmTimingConflictKind, FulfillmentWindowRecord, PickupLocationRecord,
};
-use rusqlite::{Connection, OptionalExtension, params, params_from_iter};
+use sqlx::Row;
+
+use crate::{AppSqliteDatabase, OptionalSqliteResult};
use crate::AppSqliteError;
pub struct AppFarmRulesRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppFarmRulesRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
@@ -123,10 +125,10 @@ impl<'a> AppFarmRulesRepository<'a> {
[farm_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
))
},
)
@@ -166,12 +168,12 @@ impl<'a> AppFarmRulesRepository<'a> {
let rows = statement
.query_map([farm_id.to_string()], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, Option<String>>(4)?,
- row.get::<_, i64>(5)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<Option<String>, _>(4)?,
+ row.try_get::<i64, _>(5)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -209,9 +211,9 @@ impl<'a> AppFarmRulesRepository<'a> {
[farm_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, i64>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<i64, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -265,13 +267,13 @@ impl<'a> AppFarmRulesRepository<'a> {
let rows = statement
.query_map([farm_id.to_string()], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -330,11 +332,11 @@ impl<'a> AppFarmRulesRepository<'a> {
let rows = statement
.query_map([farm_id.to_string()], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -387,7 +389,7 @@ impl<'a> AppFarmRulesRepository<'a> {
timezone = excluded.timezone,
currency_code = excluded.currency_code,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
farm_profile.farm_id.to_string(),
farm_profile.display_name,
farm_readiness_storage_key(ready),
@@ -426,7 +428,7 @@ impl<'a> AppFarmRulesRepository<'a> {
promise_lead_hours = excluded.promise_lead_hours,
substitution_policy = excluded.substitution_policy,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
operating_rules.farm_id.to_string(),
i64::from(operating_rules.promise_lead_hours),
operating_rules.substitution_policy,
@@ -486,7 +488,7 @@ impl<'a> AppFarmRulesRepository<'a> {
directions = excluded.directions,
is_default = excluded.is_default,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
pickup_location.pickup_location_id.to_string(),
pickup_location.farm_id.to_string(),
pickup_location.label,
@@ -540,7 +542,7 @@ impl<'a> AppFarmRulesRepository<'a> {
label = excluded.label,
order_cutoff_at = excluded.order_cutoff_at,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
fulfillment_window.fulfillment_window_id.to_string(),
fulfillment_window.farm_id.to_string(),
fulfillment_window.starts_at,
@@ -587,7 +589,7 @@ impl<'a> AppFarmRulesRepository<'a> {
starts_at = excluded.starts_at,
ends_at = excluded.ends_at,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
blackout_period.blackout_period_id.to_string(),
blackout_period.farm_id.to_string(),
blackout_period.label,
@@ -760,10 +762,7 @@ fn derive_farm_rules_readiness_parts(
blockers.push(FarmReadinessBlocker::MissingProfileBasics);
}
- if !pickup_locations
- .iter()
- .any(|pickup_location| pickup_location_is_present(pickup_location))
- {
+ if !pickup_locations.iter().any(pickup_location_is_present) {
blockers.push(FarmReadinessBlocker::MissingPickupLocation);
}
@@ -837,7 +836,7 @@ fn pickup_location_is_present(pickup_location: &PickupLocationRecord) -> bool {
}
fn delete_missing_rows<T>(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
table_name: &str,
id_column: &str,
farm_id: FarmId,
@@ -866,19 +865,16 @@ where
values.extend(keep_ids.iter().map(ToString::to_string));
connection
- .execute(&sql, params_from_iter(values.iter()))
+ .execute(&sql, values)
.map_err(|source| AppSqliteError::Query { operation, source })?;
Ok(())
}
-fn collect_rows<T, F>(
+fn collect_rows<T>(
operation: &'static str,
- rows: rusqlite::MappedRows<'_, F>,
-) -> Result<Vec<T>, AppSqliteError>
-where
- F: FnMut(&rusqlite::Row<'_>) -> rusqlite::Result<T>,
-{
+ rows: std::vec::IntoIter<Result<T, sqlx::Error>>,
+) -> Result<Vec<T>, AppSqliteError> {
let mut values = Vec::new();
for row in rows {
@@ -947,7 +943,7 @@ mod tests {
let repository = AppFarmRulesRepository::new(store.connection());
let projection = repository
- .load_farm_rules(FarmId::new())
+ .load_farm_rules(FarmId::generate())
.expect("missing farm rules should load");
assert_eq!(projection, FarmRulesProjection::default());
@@ -956,10 +952,10 @@ mod tests {
#[test]
fn save_farm_rules_round_trips_across_restart() {
let path = temp_database_path("farm-rules-roundtrip");
- let farm_id = FarmId::new();
- let pickup_location_id = PickupLocationId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
- let blackout_period_id = BlackoutPeriodId::new();
+ let farm_id = FarmId::generate();
+ let pickup_location_id = PickupLocationId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let blackout_period_id = BlackoutPeriodId::generate();
let projection = FarmRulesProjection {
farm_profile: Some(FarmProfileRecord {
farm_id,
@@ -1024,10 +1020,10 @@ mod tests {
fn load_farm_rules_derives_missing_and_conflict_readiness() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let repository = AppFarmRulesRepository::new(store.connection());
- let farm_id = FarmId::new();
- let pickup_location_id = PickupLocationId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
- let blackout_period_id = BlackoutPeriodId::new();
+ let farm_id = FarmId::generate();
+ let pickup_location_id = PickupLocationId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let blackout_period_id = BlackoutPeriodId::generate();
repository
.save_farm_rules(&FarmRulesProjection {
@@ -1091,7 +1087,7 @@ mod tests {
#[test]
fn blank_pickup_location_rows_do_not_count_as_present_for_readiness() {
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
let readiness = derive_farm_rules_readiness(&FarmRulesProjection {
farm_profile: Some(FarmProfileRecord {
farm_id,
@@ -1100,7 +1096,7 @@ mod tests {
currency_code: "USD".to_owned(),
}),
pickup_locations: vec![PickupLocationRecord {
- pickup_location_id: PickupLocationId::new(),
+ pickup_location_id: PickupLocationId::generate(),
farm_id,
label: " ".to_owned(),
address_line: String::new(),
@@ -1131,8 +1127,8 @@ mod tests {
#[test]
fn zero_promise_lead_hours_keep_operating_rules_incomplete() {
- let farm_id = FarmId::new();
- let pickup_location_id = PickupLocationId::new();
+ let farm_id = FarmId::generate();
+ let pickup_location_id = PickupLocationId::generate();
let readiness = derive_farm_rules_readiness(&FarmRulesProjection {
farm_profile: Some(FarmProfileRecord {
farm_id,
@@ -1154,7 +1150,7 @@ mod tests {
substitution_policy: "ask_customer".to_owned(),
}),
fulfillment_windows: vec![FulfillmentWindowRecord {
- fulfillment_window_id: FulfillmentWindowId::new(),
+ fulfillment_window_id: FulfillmentWindowId::generate(),
farm_id,
pickup_location_id,
label: "Friday pickup".to_owned(),
@@ -1175,8 +1171,8 @@ mod tests {
#[test]
fn complete_pickup_location_row_counts_as_present_for_readiness() {
- let farm_id = FarmId::new();
- let pickup_location_id = PickupLocationId::new();
+ let farm_id = FarmId::generate();
+ let pickup_location_id = PickupLocationId::generate();
let readiness = derive_farm_rules_readiness(&FarmRulesProjection {
farm_profile: Some(FarmProfileRecord {
farm_id,
@@ -1198,7 +1194,7 @@ mod tests {
substitution_policy: "ask_customer".to_owned(),
}),
fulfillment_windows: vec![FulfillmentWindowRecord {
- fulfillment_window_id: FulfillmentWindowId::new(),
+ fulfillment_window_id: FulfillmentWindowId::generate(),
farm_id,
pickup_location_id,
label: "Friday pickup".to_owned(),
diff --git a/crates/store/src/repo/farm_setup.rs b/crates/store/src/repo/farm_setup.rs
@@ -3,23 +3,23 @@ use std::collections::BTreeSet;
use radroots_studio_app_view::{
FarmOrderMethod, FarmReadiness, FarmSetupDraft, FarmSetupProjection, FarmSummary,
};
-use rusqlite::{Connection, OptionalExtension, params};
+use sqlx::Row;
-use crate::AppSqliteError;
+use crate::{AppSqliteDatabase, AppSqliteError};
pub struct AppFarmSetupRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppFarmSetupRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
pub fn load_farm_setup(&self, account_id: &str) -> Result<FarmSetupProjection, AppSqliteError> {
let row = self
.connection
- .query_row(
+ .fetch_optional(
"SELECT
farm_name,
location_or_service_area,
@@ -32,21 +32,20 @@ impl<'a> AppFarmSetupRepository<'a> {
FROM account_farm_setups
WHERE account_id = ?1
LIMIT 1",
- [account_id],
+ crate::app_sqlite_params![account_id],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, i64>(2)?,
- row.get::<_, i64>(3)?,
- row.get::<_, i64>(4)?,
- row.get::<_, Option<String>>(5)?,
- row.get::<_, Option<String>>(6)?,
- row.get::<_, Option<String>>(7)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<i64, _>(2)?,
+ row.try_get::<i64, _>(3)?,
+ row.try_get::<i64, _>(4)?,
+ row.try_get::<Option<String>, _>(5)?,
+ row.try_get::<Option<String>, _>(6)?,
+ row.try_get::<Option<String>, _>(7)?,
))
},
)
- .optional()
.map_err(|source| AppSqliteError::Query {
operation: "load account farm setup",
source,
@@ -96,7 +95,7 @@ impl<'a> AppFarmSetupRepository<'a> {
}
self.connection
- .execute(
+ .execute_statement(
"INSERT INTO account_farm_setups (
account_id,
farm_name,
@@ -119,7 +118,7 @@ impl<'a> AppFarmSetupRepository<'a> {
saved_farm_display_name = excluded.saved_farm_display_name,
saved_farm_readiness = excluded.saved_farm_readiness,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
account_id,
projection.draft.farm_name,
projection.draft.location_or_service_area,
@@ -165,9 +164,9 @@ impl<'a> AppFarmSetupRepository<'a> {
pub fn clear_farm_setup(&self, account_id: &str) -> Result<(), AppSqliteError> {
self.connection
- .execute(
+ .execute_statement(
"DELETE FROM account_farm_setups WHERE account_id = ?1",
- [account_id],
+ crate::app_sqlite_params![account_id],
)
.map_err(|source| AppSqliteError::Query {
operation: "clear account farm setup",
@@ -286,7 +285,7 @@ mod tests {
fn farm_setup_round_trips_saved_farm_state() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let saved_farm = FarmSummary {
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
display_name: "North field farm".to_owned(),
readiness: FarmReadiness::Ready,
};
diff --git a/crates/store/src/repo/order_detail.rs b/crates/store/src/repo/order_detail.rs
@@ -3,7 +3,9 @@ use radroots_studio_app_view::{
TradeValidationReceiptProjection, TradeValidationReceiptProofSystem,
TradeValidationReceiptResult, TradeValidationReceiptType,
};
-use rusqlite::{Connection, params};
+use sqlx::Row;
+
+use crate::AppSqliteDatabase;
use crate::AppSqliteError;
@@ -86,7 +88,7 @@ pub(super) fn order_detail_economics(
}
pub(super) fn order_validation_receipts(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
) -> Result<Vec<TradeValidationReceiptProjection>, AppSqliteError> {
let mut statement = connection
@@ -110,17 +112,17 @@ pub(super) fn order_validation_receipts(
source,
})?;
let rows = statement
- .query_map(params![order_id.to_string()], |row| {
+ .query_map(crate::app_sqlite_params![order_id.to_string()], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, String>(7)?,
- row.get::<_, i64>(8)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<String, _>(7)?,
+ row.try_get::<i64, _>(8)?,
))
})
.map_err(|source| AppSqliteError::Query {
diff --git a/crates/store/src/repo/orders.rs b/crates/store/src/repo/orders.rs
@@ -9,7 +9,9 @@ use radroots_studio_app_view::{
PackDayPackListRow, PackDayProductTotalRow, PackDayProjection, PackDayRosterRow,
PackDayScreenQueryState, ProductId, TradeAgreementStatus, TradeWorkflowProjection,
};
-use rusqlite::{Connection, OptionalExtension, params};
+use sqlx::Row;
+
+use crate::{AppSqliteDatabase, OptionalSqliteResult};
use super::{
order_detail::{order_detail_economics, order_detail_item_row, order_validation_receipts},
@@ -19,7 +21,7 @@ use super::{
use crate::AppSqliteError;
pub struct AppOrdersRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -40,7 +42,7 @@ pub struct SellerOrderDecisionLineExport {
}
impl<'a> AppOrdersRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
@@ -90,22 +92,22 @@ impl<'a> AppOrdersRepository<'a> {
left join pickup_locations pl on pl.id = fw.pickup_location_id
where o.farm_id = ?1 and o.id = ?2
limit 1",
- params![farm_id.to_string(), order_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string(), order_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, Option<String>>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, String>(7)?,
- row.get::<_, String>(8)?,
- row.get::<_, String>(9)?,
- row.get::<_, Option<String>>(10)?,
- row.get::<_, Option<String>>(11)?,
- row.get::<_, Option<String>>(12)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<Option<String>, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<String, _>(7)?,
+ row.try_get::<String, _>(8)?,
+ row.try_get::<String, _>(9)?,
+ row.try_get::<Option<String>, _>(10)?,
+ row.try_get::<Option<String>, _>(11)?,
+ row.try_get::<Option<String>, _>(12)?,
))
},
)
@@ -185,12 +187,12 @@ impl<'a> AppOrdersRepository<'a> {
from orders
where farm_id = ?1 and id = ?2
limit 1",
- params![farm_id.to_string(), order_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string(), order_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -305,25 +307,25 @@ impl<'a> AppOrdersRepository<'a> {
})?;
let rows = statement
.query_map(
- params![
+ crate::app_sqlite_params![
farm_id.to_string(),
fulfillment_window_id.map(|id| id.to_string())
],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, Option<String>>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, String>(7)?,
- row.get::<_, String>(8)?,
- row.get::<_, String>(9)?,
- row.get::<_, Option<String>>(10)?,
- row.get::<_, Option<String>>(11)?,
- row.get::<_, Option<String>>(12)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<Option<String>, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<String, _>(7)?,
+ row.try_get::<String, _>(8)?,
+ row.try_get::<String, _>(9)?,
+ row.try_get::<Option<String>, _>(10)?,
+ row.try_get::<Option<String>, _>(11)?,
+ row.try_get::<Option<String>, _>(12)?,
))
},
)
@@ -411,14 +413,14 @@ impl<'a> AppOrdersRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![order_id], |row| {
+ .query_map(crate::app_sqlite_params![order_id], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, i64>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, Option<u32>>(4)?,
- row.get::<_, Option<String>>(5)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<i64, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<Option<u32>, _>(4)?,
+ row.try_get::<Option<String>, _>(5)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -469,11 +471,11 @@ impl<'a> AppOrdersRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![order_id.to_string()], |row| {
+ .query_map(crate::app_sqlite_params![order_id.to_string()], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, i64>(1)?,
- row.get::<_, Option<String>>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<i64, _>(1)?,
+ row.try_get::<Option<String>, _>(2)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -518,8 +520,8 @@ impl<'a> AppOrdersRepository<'a> {
.connection
.query_row(
"select stock_count from products where id = ?1 limit 1",
- params![product_id.to_string()],
- |row| row.get::<_, Option<i64>>(0),
+ crate::app_sqlite_params![product_id.to_string()],
+ |row| row.try_get::<Option<i64>, _>(0),
)
.optional()
.map_err(|source| AppSqliteError::Query {
@@ -556,9 +558,10 @@ impl<'a> AppOrdersRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![excluding_order_id.to_string()], |row| {
- Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?))
- })
+ .query_map(
+ crate::app_sqlite_params![excluding_order_id.to_string()],
+ |row| Ok((row.try_get::<String, _>(0)?, row.try_get::<i64, _>(1)?)),
+ )
.map_err(|source| AppSqliteError::Query {
operation: "query seller order decision reservations",
source,
@@ -602,12 +605,12 @@ impl<'a> AppOrdersRepository<'a> {
from fulfillment_windows
where farm_id = ?1 and id = ?2
limit 1",
- params![farm_id.to_string(), fulfillment_window_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string(), fulfillment_window_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -641,12 +644,12 @@ impl<'a> AppOrdersRepository<'a> {
where farm_id = ?1 and starts_at >= strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
order by starts_at asc, id asc
limit 1",
- params![farm_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -682,12 +685,12 @@ impl<'a> AppOrdersRepository<'a> {
and o.status in ('needs_action', 'scheduled', 'packed')
order by fw.starts_at asc, fw.id asc
limit 1",
- params![farm_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -729,15 +732,15 @@ impl<'a> AppOrdersRepository<'a> {
left join pickup_locations pl on pl.id = fw.pickup_location_id
where fw.farm_id = ?1 and fw.id = ?2
limit 1",
- params![farm_id.to_string(), fulfillment_window_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string(), fulfillment_window_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, Option<String>>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<Option<String>, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
))
},
)
@@ -790,12 +793,12 @@ impl<'a> AppOrdersRepository<'a> {
})?;
let rows = statement
.query_map(
- params![farm_id.to_string(), fulfillment_window_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string(), fulfillment_window_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, u32>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<u32, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -847,14 +850,14 @@ impl<'a> AppOrdersRepository<'a> {
})?;
let rows = statement
.query_map(
- params![farm_id.to_string(), fulfillment_window_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string(), fulfillment_window_id.to_string()],
|row| {
Ok(PackDayPackListRow {
- title: row.get(1)?,
+ title: row.try_get(1)?,
quantity_display: format!(
"{}: {}",
- row.get::<_, String>(0)?,
- row.get::<_, String>(2)?
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(2)?
),
})
},
@@ -893,12 +896,12 @@ impl<'a> AppOrdersRepository<'a> {
})?;
let rows = statement
.query_map(
- params![farm_id.to_string(), fulfillment_window_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string(), fulfillment_window_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, u32>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<u32, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -957,16 +960,16 @@ impl<'a> AppOrdersRepository<'a> {
})?;
let rows = statement
.query_map(
- params![farm_id.to_string(), fulfillment_window_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string(), fulfillment_window_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, u32>(5)?,
- row.get::<_, String>(6)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<u32, _>(5)?,
+ row.try_get::<String, _>(6)?,
))
},
)
@@ -1023,12 +1026,12 @@ impl<'a> AppOrdersRepository<'a> {
})?;
let rows = statement
.query_map(
- params![farm_id.to_string(), fulfillment_window_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string(), fulfillment_window_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -1075,13 +1078,13 @@ impl<'a> AppOrdersRepository<'a> {
})?;
let rows = statement
.query_map(
- params![farm_id.to_string(), fulfillment_window_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string(), fulfillment_window_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
))
},
)
@@ -1273,24 +1276,22 @@ fn empty_string_to_none(value: Option<String>) -> Option<String> {
#[cfg(test)]
mod tests {
+ use crate::{AppSqliteDatabase, AppSqliteError, AppSqliteStore, DatabaseTarget};
use radroots_studio_app_view::{
FarmId, FulfillmentWindowId, OrderId, OrderPrimaryAction, OrderStatus, OrdersFilter,
OrdersScreenQueryState, PackDayOutputOrderState, PackDayProductTotalRow,
PackDayScreenQueryState, PickupLocationId, TradeAgreementStatus, TradeInventoryStatus,
TradeRevisionStatus, TradeWorkflowSource,
};
- use rusqlite::{Connection, params};
-
- use crate::{AppSqliteError, AppSqliteStore, DatabaseTarget};
#[test]
fn orders_list_loads_summary_rows_and_window_filter_truthfully() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let connection = store.connection();
- let farm_id = FarmId::new();
- let other_farm_id = FarmId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
- let other_window_id = FulfillmentWindowId::new();
+ let farm_id = FarmId::generate();
+ let other_farm_id = FarmId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let other_window_id = FulfillmentWindowId::generate();
insert_farm(
connection,
@@ -1330,7 +1331,7 @@ mod tests {
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
Some(fulfillment_window_id),
"R-100",
@@ -1340,7 +1341,7 @@ mod tests {
);
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
Some(fulfillment_window_id),
"R-101",
@@ -1350,7 +1351,7 @@ mod tests {
);
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
Some(fulfillment_window_id),
"R-102",
@@ -1360,7 +1361,7 @@ mod tests {
);
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
Some(other_window_id),
"R-103",
@@ -1370,7 +1371,7 @@ mod tests {
);
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
None,
"R-104",
@@ -1380,7 +1381,7 @@ mod tests {
);
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
other_farm_id,
Some(fulfillment_window_id),
"R-999",
@@ -1423,9 +1424,9 @@ mod tests {
fn order_detail_loads_items_and_context() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let connection = store.connection();
- let farm_id = FarmId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
- let order_id = OrderId::new();
+ let farm_id = FarmId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let order_id = OrderId::generate();
insert_farm(
connection,
@@ -1502,8 +1503,8 @@ mod tests {
fn seller_order_projections_fail_closed_for_invalid_workflow_revision() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let connection = store.connection();
- let farm_id = FarmId::new();
- let order_id = OrderId::new();
+ let farm_id = FarmId::generate();
+ let order_id = OrderId::generate();
insert_farm(
connection,
@@ -1568,8 +1569,8 @@ mod tests {
fn seller_order_projections_read_workflow_display_snapshot() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let connection = store.connection();
- let farm_id = FarmId::new();
- let order_id = OrderId::new();
+ let farm_id = FarmId::generate();
+ let order_id = OrderId::generate();
insert_farm(
connection,
@@ -1649,8 +1650,8 @@ mod tests {
fn seller_order_projections_fail_closed_for_invalid_workflow_snapshot_keys() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let connection = store.connection();
- let farm_id = FarmId::new();
- let order_id = OrderId::new();
+ let farm_id = FarmId::generate();
+ let order_id = OrderId::generate();
insert_farm(
connection,
@@ -1718,11 +1719,11 @@ mod tests {
fn pack_day_defaults_to_next_window_and_projects_totals_pack_list_and_roster() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let connection = store.connection();
- let farm_id = FarmId::new();
- let next_window_id = FulfillmentWindowId::new();
- let later_window_id = FulfillmentWindowId::new();
- let scheduled_order_id = OrderId::new();
- let packed_order_id = OrderId::new();
+ let farm_id = FarmId::generate();
+ let next_window_id = FulfillmentWindowId::generate();
+ let later_window_id = FulfillmentWindowId::generate();
+ let scheduled_order_id = OrderId::generate();
+ let packed_order_id = OrderId::generate();
insert_farm(
connection,
@@ -1774,7 +1775,7 @@ mod tests {
);
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
Some(next_window_id),
"R-102",
@@ -1784,7 +1785,7 @@ mod tests {
);
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
Some(later_window_id),
"R-200",
@@ -1862,10 +1863,10 @@ mod tests {
fn pack_day_output_source_projects_canonical_records_without_screen_strings() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let connection = store.connection();
- let farm_id = FarmId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
- let scheduled_order_id = OrderId::new();
- let packed_order_id = OrderId::new();
+ let farm_id = FarmId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let scheduled_order_id = OrderId::generate();
+ let packed_order_id = OrderId::generate();
insert_farm(
connection,
@@ -1907,7 +1908,7 @@ mod tests {
);
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
Some(fulfillment_window_id),
"R-102",
@@ -1996,8 +1997,8 @@ mod tests {
fn orders_list_stays_aligned_with_today_needs_action_order_boundary() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let connection = store.connection();
- let farm_id = FarmId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let farm_id = FarmId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
insert_farm(
connection,
@@ -2019,7 +2020,7 @@ mod tests {
for index in 0..5 {
insert_order(
connection,
- OrderId::new(),
+ OrderId::generate(),
farm_id,
Some(fulfillment_window_id),
&format!("R-10{index}"),
@@ -2059,7 +2060,7 @@ mod tests {
}
fn insert_farm(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
farm_id: FarmId,
display_name: &str,
readiness: &str,
@@ -2076,18 +2077,18 @@ mod tests {
created_at,
updated_at
) values (?1, ?2, ?3, 'UTC', 'USD', ?4, ?4)",
- params![farm_id.to_string(), display_name, readiness, created_at],
+ crate::app_sqlite_params![farm_id.to_string(), display_name, readiness, created_at],
)
.expect("farm insert should succeed");
}
fn insert_pickup_location(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
farm_id: FarmId,
label: &str,
is_default: bool,
) -> PickupLocationId {
- let pickup_location_id = PickupLocationId::new();
+ let pickup_location_id = PickupLocationId::generate();
connection
.execute(
@@ -2101,7 +2102,7 @@ mod tests {
created_at,
updated_at
) values (?1, ?2, ?3, '14 County Road', null, ?4, '2026-04-17T08:00:00Z', '2026-04-17T08:00:00Z')",
- params![
+ crate::app_sqlite_params![
pickup_location_id.to_string(),
farm_id.to_string(),
label,
@@ -2113,8 +2114,9 @@ mod tests {
pickup_location_id
}
+ #[expect(clippy::too_many_arguments)]
fn insert_window(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
fulfillment_window_id: FulfillmentWindowId,
farm_id: FarmId,
pickup_location_id: Option<PickupLocationId>,
@@ -2137,7 +2139,7 @@ mod tests {
label,
order_cutoff_at
) values (?1, ?2, ?3, ?4, null, ?3, ?3, ?5, ?6, ?7)",
- params![
+ crate::app_sqlite_params![
fulfillment_window_id.to_string(),
farm_id.to_string(),
starts_at,
@@ -2150,8 +2152,9 @@ mod tests {
.expect("fulfillment window insert should succeed");
}
+ #[expect(clippy::too_many_arguments)]
fn insert_order(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
farm_id: FarmId,
fulfillment_window_id: Option<FulfillmentWindowId>,
@@ -2171,7 +2174,7 @@ mod tests {
status,
updated_at
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
- params![
+ crate::app_sqlite_params![
order_id.to_string(),
farm_id.to_string(),
fulfillment_window_id.map(|id| id.to_string()),
@@ -2185,20 +2188,20 @@ mod tests {
}
fn set_order_workflow_revision(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
workflow_revision: &str,
) {
connection
.execute(
"update orders set workflow_revision = ?1 where id = ?2",
- params![workflow_revision, order_id.to_string()],
+ crate::app_sqlite_params![workflow_revision, order_id.to_string()],
)
.expect("order workflow revision update should succeed");
}
fn corrupt_order_workflow_revision(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
workflow_revision: &str,
) {
@@ -2212,7 +2215,7 @@ mod tests {
}
fn set_order_workflow_display_projection(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
agreement: &str,
inventory: &str,
@@ -2227,7 +2230,7 @@ mod tests {
workflow_provenance_source = ?3,
workflow_provenance_last_event_id = ?4
where id = ?5",
- params![
+ crate::app_sqlite_params![
agreement,
inventory,
provenance_source,
@@ -2239,7 +2242,7 @@ mod tests {
}
fn corrupt_order_workflow_display_projection(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
order_id: OrderId,
column: &str,
value: &str,
@@ -2256,7 +2259,10 @@ mod tests {
_ => panic!("unsupported workflow display projection column {column}"),
};
connection
- .execute(statement, params![value, order_id.to_string()])
+ .execute(
+ statement,
+ crate::app_sqlite_params![value, order_id.to_string()],
+ )
.expect("order workflow display projection corruption should succeed");
connection
.execute_batch("pragma ignore_check_constraints = off")
@@ -2273,8 +2279,9 @@ mod tests {
}
}
+ #[expect(clippy::too_many_arguments)]
fn insert_order_line(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
line_id: &str,
order_id: OrderId,
title: &str,
@@ -2296,7 +2303,7 @@ mod tests {
price_currency,
sort_index
) values (?1, ?2, ?3, ?4, ?5, ?6, 650, 'USD', ?7)",
- params![
+ crate::app_sqlite_params![
line_id,
order_id.to_string(),
title,
diff --git a/crates/store/src/repo/products.rs b/crates/store/src/repo/products.rs
@@ -6,18 +6,20 @@ use radroots_studio_app_view::{
ProductPublishBlocker, ProductStatus, ProductStockState, ProductStockSummary, ProductsFilter,
ProductsListProjection, ProductsListRow, ProductsListSummary, ProductsSort,
};
-use rusqlite::{Connection, OptionalExtension, params};
+use sqlx::Row;
+
+use crate::{AppSqliteDatabase, OptionalSqliteResult};
use crate::AppSqliteError;
const PRODUCTS_LOW_STOCK_THRESHOLD: u32 = 3;
pub struct AppProductsRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppProductsRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
@@ -58,7 +60,7 @@ impl<'a> AppProductsRepository<'a> {
}
pub fn create_product_draft(&self, farm_id: FarmId) -> Result<ProductId, AppSqliteError> {
- let product_id = ProductId::new();
+ let product_id = ProductId::generate();
self.connection
.execute(
@@ -89,7 +91,7 @@ impl<'a> AppProductsRepository<'a> {
null,
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
)",
- params![product_id.to_string(), farm_id.to_string()],
+ crate::app_sqlite_params![product_id.to_string(), farm_id.to_string()],
)
.map_err(|source| AppSqliteError::Query {
operation: "create product draft",
@@ -120,7 +122,7 @@ impl<'a> AppProductsRepository<'a> {
availability_window_id = ?10,
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
where id = ?1",
- params![
+ crate::app_sqlite_params![
product_id.to_string(),
draft.title.as_str(),
draft.subtitle.as_str(),
@@ -154,7 +156,7 @@ impl<'a> AppProductsRepository<'a> {
stock_count = ?2,
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
where id = ?1",
- params![product_id.to_string(), stock_quantity],
+ crate::app_sqlite_params![product_id.to_string(), stock_quantity],
)
.map_err(|source| AppSqliteError::Query {
operation: "update product stock",
@@ -175,9 +177,11 @@ impl<'a> AppProductsRepository<'a> {
fn current_utc_timestamp(&self) -> Result<String, AppSqliteError> {
self.connection
- .query_row("select strftime('%Y-%m-%dT%H:%M:%SZ', 'now')", [], |row| {
- row.get(0)
- })
+ .query_row(
+ "select strftime('%Y-%m-%dT%H:%M:%SZ', 'now')",
+ crate::empty_params(),
+ |row| row.try_get(0),
+ )
.map_err(|source| AppSqliteError::Query {
operation: "load current utc timestamp",
source,
@@ -212,22 +216,22 @@ impl<'a> AppProductsRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![farm_id.to_string()], |row| {
+ .query_map(crate::app_sqlite_params![farm_id.to_string()], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, Option<u32>>(7)?,
- row.get::<_, String>(8)?,
- row.get::<_, Option<u32>>(9)?,
- row.get::<_, Option<String>>(10)?,
- row.get::<_, Option<String>>(11)?,
- row.get::<_, Option<String>>(12)?,
- row.get::<_, String>(13)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<Option<u32>, _>(7)?,
+ row.try_get::<String, _>(8)?,
+ row.try_get::<Option<u32>, _>(9)?,
+ row.try_get::<Option<String>, _>(10)?,
+ row.try_get::<Option<String>, _>(11)?,
+ row.try_get::<Option<String>, _>(12)?,
+ row.try_get::<String, _>(13)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -307,23 +311,23 @@ impl<'a> AppProductsRepository<'a> {
left join fulfillment_windows fw on fw.id = p.availability_window_id
where p.id = ?1
limit 1",
- params![product_id.to_string()],
+ crate::app_sqlite_params![product_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, Option<u32>>(7)?,
- row.get::<_, String>(8)?,
- row.get::<_, Option<u32>>(9)?,
- row.get::<_, Option<String>>(10)?,
- row.get::<_, Option<String>>(11)?,
- row.get::<_, Option<String>>(12)?,
- row.get::<_, String>(13)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<Option<u32>, _>(7)?,
+ row.try_get::<String, _>(8)?,
+ row.try_get::<Option<u32>, _>(9)?,
+ row.try_get::<Option<String>, _>(10)?,
+ row.try_get::<Option<String>, _>(11)?,
+ row.try_get::<Option<String>, _>(12)?,
+ row.try_get::<String, _>(13)?,
))
},
)
@@ -728,14 +732,12 @@ fn parse_product_status(
#[cfg(test)]
mod tests {
+ use crate::{AppSqliteDatabase, AppSqliteStore, DatabaseTarget};
use radroots_studio_app_view::{
FarmId, FulfillmentWindowId, ProductAttentionState, ProductAvailabilityState,
ProductEditorDraft, ProductId, ProductPublishBlocker, ProductStatus, ProductStockState,
ProductsFilter, ProductsSort,
};
- use rusqlite::{Connection, params};
-
- use crate::{AppSqliteStore, DatabaseTarget};
use super::AppProductsRepository;
@@ -984,14 +986,14 @@ mod tests {
.expect("ready blockers should load"),
Some(Vec::new())
);
- let stale_window_id = FulfillmentWindowId::new();
+ let stale_window_id = FulfillmentWindowId::generate();
connection
.execute_batch("PRAGMA foreign_keys = OFF;")
.expect("foreign keys should disable for stale fixture");
connection
.execute(
"update products set availability_window_id = ?2 where id = ?1",
- params![product_id.to_string(), stale_window_id.to_string()],
+ crate::app_sqlite_params![product_id.to_string(), stale_window_id.to_string()],
)
.expect("stale availability id should write");
connection
@@ -1009,7 +1011,7 @@ mod tests {
connection
.execute(
"update products set availability_window_id = ?2 where id = ?1",
- params![product_id.to_string(), window_id.to_string()],
+ crate::app_sqlite_params![product_id.to_string(), window_id.to_string()],
)
.expect("ready availability id should restore");
connection
@@ -1054,14 +1056,14 @@ mod tests {
);
}
- fn insert_farm(connection: &Connection, display_name: &str) -> FarmId {
- let farm_id = FarmId::new();
+ fn insert_farm(connection: &AppSqliteDatabase, display_name: &str) -> FarmId {
+ let farm_id = FarmId::generate();
connection
.execute(
"insert into farms (id, display_name, readiness, created_at, updated_at)
values (?1, ?2, 'ready', '2026-04-18T08:00:00Z', '2026-04-18T08:00:00Z')",
- params![farm_id.to_string(), display_name],
+ crate::app_sqlite_params![farm_id.to_string(), display_name],
)
.expect("farm insert should succeed");
@@ -1069,18 +1071,18 @@ mod tests {
}
fn insert_window(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
farm_id: FarmId,
starts_at: &str,
ends_at: &str,
) -> FulfillmentWindowId {
- let window_id = FulfillmentWindowId::new();
+ let window_id = FulfillmentWindowId::generate();
connection
.execute(
"insert into fulfillment_windows (id, farm_id, starts_at, ends_at, capacity_limit, created_at, updated_at)
values (?1, ?2, ?3, ?4, null, ?3, ?3)",
- params![window_id.to_string(), farm_id.to_string(), starts_at, ends_at],
+ crate::app_sqlite_params![window_id.to_string(), farm_id.to_string(), starts_at, ends_at],
)
.expect("fulfillment window insert should succeed");
@@ -1088,11 +1090,11 @@ mod tests {
}
fn insert_product(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
farm_id: FarmId,
seed: SeedProduct<'_>,
) -> ProductId {
- let product_id = ProductId::new();
+ let product_id = ProductId::generate();
connection
.execute(
@@ -1110,7 +1112,7 @@ mod tests {
availability_window_id,
updated_at
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, 'USD', ?9, ?10, ?11)",
- params![
+ crate::app_sqlite_params![
product_id.to_string(),
farm_id.to_string(),
seed.title,
diff --git a/crates/store/src/repo/reminders.rs b/crates/store/src/repo/reminders.rs
@@ -3,18 +3,20 @@ use radroots_studio_app_view::{
ReminderKind, ReminderLogEntryProjection, ReminderLogProjection, ReminderSurface,
ReminderUrgency,
};
-use rusqlite::{Connection, params};
+use sqlx::Row;
+
+use crate::AppSqliteDatabase;
use std::str::FromStr;
use uuid::Uuid;
use crate::AppSqliteError;
pub struct AppRemindersRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppRemindersRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
@@ -47,21 +49,24 @@ impl<'a> AppRemindersRepository<'a> {
source,
})?;
let rows = statement
- .query_map(params![account_id, farm_id.to_string()], |row| {
- Ok((
- row.get::<_, String>(0)?,
- row.get::<_, Option<String>>(1)?,
- row.get::<_, Option<String>>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, String>(7)?,
- row.get::<_, String>(8)?,
- row.get::<_, Option<String>>(9)?,
- row.get::<_, String>(10)?,
- ))
- })
+ .query_map(
+ crate::app_sqlite_params![account_id, farm_id.to_string()],
+ |row| {
+ Ok((
+ row.try_get::<String, _>(0)?,
+ row.try_get::<Option<String>, _>(1)?,
+ row.try_get::<Option<String>, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<String, _>(7)?,
+ row.try_get::<String, _>(8)?,
+ row.try_get::<Option<String>, _>(9)?,
+ row.try_get::<String, _>(10)?,
+ ))
+ },
+ )
.map_err(|source| AppSqliteError::Query {
operation: "query reminder schedule",
source,
@@ -125,26 +130,26 @@ impl<'a> AppRemindersRepository<'a> {
projection: &ReminderFeedProjection,
log_entries: &[ReminderLogEntryProjection],
) -> Result<(), AppSqliteError> {
- let transaction =
+ self.connection
+ .execute_batch("BEGIN IMMEDIATE")
+ .map_err(|source| AppSqliteError::Query {
+ operation: "begin reminder schedule replacement",
+ source,
+ })?;
+
+ let result = (|| -> Result<(), AppSqliteError> {
self.connection
- .unchecked_transaction()
+ .execute(
+ "DELETE FROM reminder_schedules WHERE account_id = ?1 AND farm_id = ?2",
+ crate::app_sqlite_params![account_id, farm_id.to_string()],
+ )
.map_err(|source| AppSqliteError::Query {
- operation: "begin reminder schedule replacement",
+ operation: "clear reminder schedule",
source,
})?;
- transaction
- .execute(
- "DELETE FROM reminder_schedules WHERE account_id = ?1 AND farm_id = ?2",
- params![account_id, farm_id.to_string()],
- )
- .map_err(|source| AppSqliteError::Query {
- operation: "clear reminder schedule",
- source,
- })?;
-
- {
- let mut statement = transaction
+ let mut statement = self
+ .connection
.prepare(
"INSERT INTO reminder_schedules (
reminder_id,
@@ -169,7 +174,7 @@ impl<'a> AppRemindersRepository<'a> {
for reminder in &projection.items {
statement
- .execute(params![
+ .execute(crate::app_sqlite_params![
reminder.reminder_id.to_string(),
account_id,
reminder.farm_id.to_string(),
@@ -191,14 +196,13 @@ impl<'a> AppRemindersRepository<'a> {
source,
})?;
}
- }
- for entry in log_entries {
- let log_entry_id = Uuid::now_v7().to_string();
+ for entry in log_entries {
+ let log_entry_id = Uuid::now_v7().to_string();
- transaction
- .execute(
- "INSERT INTO reminder_log_entries (
+ self.connection
+ .execute(
+ "INSERT INTO reminder_log_entries (
log_entry_id,
account_id,
farm_id,
@@ -209,26 +213,34 @@ impl<'a> AppRemindersRepository<'a> {
delivery_state,
detail
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
- params![
- log_entry_id,
- account_id,
- farm_id.to_string(),
- entry.reminder_id.to_string(),
- entry.kind.storage_key(),
- entry.title,
- entry.recorded_at,
- entry.delivery_state.storage_key(),
- entry.detail,
- ],
- )
- .map_err(|source| AppSqliteError::Query {
- operation: "record reminder log entry",
- source,
- })?;
+ crate::app_sqlite_params![
+ log_entry_id,
+ account_id,
+ farm_id.to_string(),
+ entry.reminder_id.to_string(),
+ entry.kind.storage_key(),
+ entry.title,
+ entry.recorded_at,
+ entry.delivery_state.storage_key(),
+ entry.detail,
+ ],
+ )
+ .map_err(|source| AppSqliteError::Query {
+ operation: "record reminder log entry",
+ source,
+ })?;
+ }
+
+ Ok(())
+ })();
+
+ if let Err(error) = result {
+ let _ = self.connection.execute_batch("ROLLBACK");
+ return Err(error);
}
- transaction
- .commit()
+ self.connection
+ .execute_batch("COMMIT")
.map_err(|source| AppSqliteError::Query {
operation: "commit reminder schedule replacement",
source,
@@ -258,7 +270,7 @@ impl<'a> AppRemindersRepository<'a> {
delivery_state,
detail
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
- params![
+ crate::app_sqlite_params![
log_entry_id,
account_id,
farm_id.to_string(),
@@ -305,15 +317,15 @@ impl<'a> AppRemindersRepository<'a> {
})?;
let rows = statement
.query_map(
- params![account_id, farm_id.to_string(), limit as i64],
+ crate::app_sqlite_params![account_id, farm_id.to_string(), limit as i64],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, Option<String>>(5)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<Option<String>, _>(5)?,
))
},
)
@@ -425,11 +437,11 @@ mod tests {
fn reminder_schedule_round_trips_and_is_account_scoped() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let repository = AppRemindersRepository::new(store.connection());
- let farm_id = FarmId::new();
- let other_farm_id = FarmId::new();
- let order_id = OrderId::new();
+ let farm_id = FarmId::generate();
+ let other_farm_id = FarmId::generate();
+ let order_id = OrderId::generate();
let reminder = ReminderDeadlineProjection {
- reminder_id: ReminderId::new(),
+ reminder_id: ReminderId::generate(),
farm_id,
order_id: Some(order_id),
fulfillment_window_id: None,
@@ -482,9 +494,9 @@ mod tests {
fn reminder_log_records_and_loads_recent_entries() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let repository = AppRemindersRepository::new(store.connection());
- let farm_id = FarmId::new();
- let first_reminder_id = ReminderId::new();
- let second_reminder_id = ReminderId::new();
+ let farm_id = FarmId::generate();
+ let first_reminder_id = ReminderId::generate();
+ let second_reminder_id = ReminderId::generate();
repository
.record_reminder_log_entry(
diff --git a/crates/store/src/repo/today.rs b/crates/store/src/repo/today.rs
@@ -3,7 +3,9 @@ use radroots_studio_app_view::{
ProductListRow, ProductStatus, TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind,
TodaySummary,
};
-use rusqlite::{Connection, OptionalExtension, Params, params};
+use sqlx::Row;
+
+use crate::{AppSqliteDatabase, IntoAppSqliteParams, OptionalSqliteResult};
use crate::AppSqliteError;
@@ -11,11 +13,11 @@ pub const TODAY_AGENDA_LIST_LIMIT: i64 = 4;
pub const TODAY_AGENDA_LOW_STOCK_THRESHOLD: u32 = 3;
pub struct AppTodayAgendaRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppTodayAgendaRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
@@ -51,7 +53,7 @@ impl<'a> AppTodayAgendaRepository<'a> {
display_name = excluded.display_name,
readiness = excluded.readiness,
updated_at = excluded.updated_at",
- params![
+ crate::app_sqlite_params![
farm.farm_id.to_string(),
farm.display_name,
farm_readiness_storage_key(farm.readiness),
@@ -73,12 +75,12 @@ impl<'a> AppTodayAgendaRepository<'a> {
self.connection
.query_row(
"select id, display_name, readiness from farms where id = ?1 limit 1",
- params![farm_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -91,12 +93,12 @@ impl<'a> AppTodayAgendaRepository<'a> {
self.connection
.query_row(
"select id, display_name, readiness from farms order by created_at asc, id asc limit 1",
- [],
+ crate::empty_params(),
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -124,17 +126,17 @@ impl<'a> AppTodayAgendaRepository<'a> {
orders_needing_action: self.count_u32(
"count today orders needing action",
"select count(*) from orders where farm_id = ?1 and status = 'needs_action'",
- params![farm_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string()],
)?,
low_stock_products: self.count_u32(
"count today low-stock products",
"select count(*) from products where farm_id = ?1 and status = 'published' and stock_count <= ?2",
- params![farm_id.to_string(), TODAY_AGENDA_LOW_STOCK_THRESHOLD],
+ crate::app_sqlite_params![farm_id.to_string(), TODAY_AGENDA_LOW_STOCK_THRESHOLD],
)?,
draft_products: self.count_u32(
"count today draft products",
"select count(*) from products where farm_id = ?1 and status = 'draft'",
- params![farm_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string()],
)?,
reminders_due_soon: 0,
})
@@ -159,13 +161,13 @@ impl<'a> AppTodayAgendaRepository<'a> {
})?;
let rows = statement
.query_map(
- params![farm_id.to_string(), TODAY_AGENDA_LIST_LIMIT],
+ crate::app_sqlite_params![farm_id.to_string(), TODAY_AGENDA_LIST_LIMIT],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, Option<String>>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<Option<String>, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
))
},
)
@@ -217,16 +219,16 @@ impl<'a> AppTodayAgendaRepository<'a> {
})?;
let rows = statement
.query_map(
- params![
+ crate::app_sqlite_params![
farm_id.to_string(),
TODAY_AGENDA_LOW_STOCK_THRESHOLD,
TODAY_AGENDA_LIST_LIMIT
],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, u32>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<u32, _>(2)?,
))
},
)
@@ -270,12 +272,12 @@ impl<'a> AppTodayAgendaRepository<'a> {
})?;
let rows = statement
.query_map(
- params![farm_id.to_string(), TODAY_AGENDA_LIST_LIMIT],
+ crate::app_sqlite_params![farm_id.to_string(), TODAY_AGENDA_LIST_LIMIT],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, u32>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<u32, _>(2)?,
))
},
)
@@ -314,12 +316,12 @@ impl<'a> AppTodayAgendaRepository<'a> {
where farm_id = ?1 and starts_at >= strftime('%Y-%m-%dT%H:%M:%SZ', 'now') \
order by starts_at asc, id asc \
limit 1",
- params![farm_id.to_string()],
+ crate::app_sqlite_params![farm_id.to_string()],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
))
},
)
@@ -356,7 +358,7 @@ impl<'a> AppTodayAgendaRepository<'a> {
is_complete: self.exists(
"check today fulfillment window setup",
"select exists(select 1 from fulfillment_windows where farm_id = ?1)",
- params![farm.farm_id.to_string()],
+ crate::app_sqlite_params![farm.farm_id.to_string()],
)?,
},
TodaySetupTask {
@@ -364,31 +366,31 @@ impl<'a> AppTodayAgendaRepository<'a> {
is_complete: self.exists(
"check today published product setup",
"select exists(select 1 from products where farm_id = ?1 and status = 'published')",
- params![farm.farm_id.to_string()],
+ crate::app_sqlite_params![farm.farm_id.to_string()],
)?,
},
])
}
- fn count_u32<P: Params>(
+ fn count_u32<P: IntoAppSqliteParams>(
&self,
operation: &'static str,
sql: &'static str,
params: P,
) -> Result<u32, AppSqliteError> {
self.connection
- .query_row(sql, params, |row| row.get::<_, u32>(0))
+ .query_row(sql, params, |row| row.try_get::<u32, _>(0))
.map_err(|source| AppSqliteError::Query { operation, source })
}
- fn exists<P: Params>(
+ fn exists<P: IntoAppSqliteParams>(
&self,
operation: &'static str,
sql: &'static str,
params: P,
) -> Result<bool, AppSqliteError> {
self.connection
- .query_row(sql, params, |row| row.get::<_, i64>(0))
+ .query_row(sql, params, |row| row.try_get::<i64, _>(0))
.map(|value| value == 1)
.map_err(|source| AppSqliteError::Query { operation, source })
}
@@ -433,10 +435,8 @@ fn farm_readiness_storage_key(readiness: FarmReadiness) -> &'static str {
#[cfg(test)]
mod tests {
+ use crate::{AppSqliteDatabase, AppSqliteStore, DatabaseTarget};
use radroots_studio_app_view::{FarmId, FulfillmentWindowId, ProductId, TodaySetupTaskKind};
- use rusqlite::{Connection, params};
-
- use crate::{AppSqliteStore, DatabaseTarget};
use super::{TODAY_AGENDA_LIST_LIMIT, TODAY_AGENDA_LOW_STOCK_THRESHOLD};
@@ -458,10 +458,10 @@ mod tests {
fn today_agenda_loads_truthful_projection_for_selected_farm() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let connection = store.connection();
- let farm_id = FarmId::new();
- let other_farm_id = FarmId::new();
- let earliest_window_id = FulfillmentWindowId::new();
- let later_window_id = FulfillmentWindowId::new();
+ let farm_id = FarmId::generate();
+ let other_farm_id = FarmId::generate();
+ let earliest_window_id = FulfillmentWindowId::generate();
+ let later_window_id = FulfillmentWindowId::generate();
insert_farm(
connection,
@@ -493,7 +493,7 @@ mod tests {
);
insert_window(
connection,
- FulfillmentWindowId::new(),
+ FulfillmentWindowId::generate(),
other_farm_id,
"2099-04-17T10:00:00Z",
"2099-04-17T12:00:00Z",
@@ -608,8 +608,8 @@ mod tests {
fn today_agenda_uses_primary_farm_and_builds_setup_checklist_for_incomplete_farm() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let connection = store.connection();
- let primary_farm_id = FarmId::new();
- let secondary_farm_id = FarmId::new();
+ let primary_farm_id = FarmId::generate();
+ let secondary_farm_id = FarmId::generate();
insert_farm(
connection,
@@ -643,7 +643,7 @@ mod tests {
);
insert_window(
connection,
- FulfillmentWindowId::new(),
+ FulfillmentWindowId::generate(),
secondary_farm_id,
"2099-04-20T16:00:00Z",
"2099-04-20T18:00:00Z",
@@ -674,7 +674,7 @@ mod tests {
fn saved_farm_summary_round_trips_into_today_projection() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let farm = radroots_studio_app_view::FarmSummary {
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
display_name: "North field farm".to_owned(),
readiness: radroots_studio_app_view::FarmReadiness::Incomplete,
};
@@ -696,7 +696,7 @@ mod tests {
}
fn insert_farm(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
farm_id: FarmId,
display_name: &str,
readiness: &str,
@@ -706,13 +706,13 @@ mod tests {
.execute(
"insert into farms (id, display_name, readiness, created_at, updated_at) \
values (?1, ?2, ?3, ?4, ?4)",
- params![farm_id.to_string(), display_name, readiness, created_at],
+ crate::app_sqlite_params![farm_id.to_string(), display_name, readiness, created_at],
)
.expect("farm insert should succeed");
}
fn insert_window(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
fulfillment_window_id: FulfillmentWindowId,
farm_id: FarmId,
starts_at: &str,
@@ -722,7 +722,7 @@ mod tests {
.execute(
"insert into fulfillment_windows (id, farm_id, starts_at, ends_at, capacity_limit, created_at, updated_at) \
values (?1, ?2, ?3, ?4, null, ?3, ?3)",
- params![
+ crate::app_sqlite_params![
fulfillment_window_id.to_string(),
farm_id.to_string(),
starts_at,
@@ -733,20 +733,20 @@ mod tests {
}
fn insert_product(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
farm_id: FarmId,
title: &str,
status: &str,
stock_count: u32,
updated_at: &str,
) -> ProductId {
- let product_id = ProductId::new();
+ let product_id = ProductId::generate();
connection
.execute(
"insert into products (id, farm_id, title, status, stock_count, updated_at) \
values (?1, ?2, ?3, ?4, ?5, ?6)",
- params![
+ crate::app_sqlite_params![
product_id.to_string(),
farm_id.to_string(),
title,
@@ -761,7 +761,7 @@ mod tests {
}
fn insert_order(
- connection: &Connection,
+ connection: &AppSqliteDatabase,
farm_id: FarmId,
fulfillment_window_id: Option<FulfillmentWindowId>,
order_number: &str,
@@ -773,8 +773,8 @@ mod tests {
.execute(
"insert into orders (id, farm_id, fulfillment_window_id, order_number, customer_display_name, status, updated_at) \
values (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
- params![
- radroots_studio_app_view::OrderId::new().to_string(),
+ crate::app_sqlite_params![
+ radroots_studio_app_view::OrderId::generate().to_string(),
farm_id.to_string(),
fulfillment_window_id.map(|id| id.to_string()),
order_number,
diff --git a/crates/store/src/sdk_workflow_receipts.rs b/crates/store/src/sdk_workflow_receipts.rs
@@ -1,4 +1,6 @@
-use rusqlite::{Connection, OptionalExtension, params};
+use sqlx::Row;
+
+use crate::{AppSqliteDatabase, OptionalSqliteResult};
use serde_json::Value;
use uuid::Uuid;
@@ -111,11 +113,11 @@ pub struct AppSdkStoredWorkflowReceipt {
}
pub struct AppSdkWorkflowReceiptRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppSdkWorkflowReceiptRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
@@ -164,7 +166,7 @@ impl<'a> AppSdkWorkflowReceiptRepository<'a> {
workflow_state = excluded.workflow_state,
updated_at = excluded.updated_at,
detail_json = excluded.detail_json",
- params![
+ crate::app_sqlite_params![
receipt_id,
input.source_kind.storage_key(),
input.source_record_id.as_str(),
@@ -213,7 +215,7 @@ impl<'a> AppSdkWorkflowReceiptRepository<'a> {
WHERE source_kind = ?1
AND source_record_id = ?2
LIMIT 1",
- params![source_kind.storage_key(), source_record_id],
+ crate::app_sqlite_params![source_kind.storage_key(), source_record_id],
decode_receipt_row,
)
.optional()
@@ -224,30 +226,32 @@ impl<'a> AppSdkWorkflowReceiptRepository<'a> {
}
}
-fn decode_receipt_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<AppSdkStoredWorkflowReceipt> {
- let source_kind: String = row.get(1)?;
- let outbox_ids_json: String = row.get(4)?;
- let workflow_state: String = row.get(8)?;
- let detail_json: String = row.get(11)?;
+fn decode_receipt_row(
+ row: &sqlx::sqlite::SqliteRow,
+) -> Result<AppSdkStoredWorkflowReceipt, sqlx::Error> {
+ let source_kind: String = row.try_get(1)?;
+ let outbox_ids_json: String = row.try_get(4)?;
+ let workflow_state: String = row.try_get(8)?;
+ let detail_json: String = row.try_get(11)?;
Ok(AppSdkStoredWorkflowReceipt {
- id: row.get(0)?,
+ id: row.try_get(0)?,
source_kind: AppSdkWorkflowReceiptSourceKind::parse(source_kind.as_str())
.map_err(decode_app_error)?,
- source_record_id: row.get(2)?,
- sdk_operation_kind: row.get(3)?,
+ source_record_id: row.try_get(2)?,
+ sdk_operation_kind: row.try_get(3)?,
sdk_outbox_event_ids: serde_json::from_str(outbox_ids_json.as_str()).map_err(|source| {
decode_app_error(AppSqliteError::DecodeJson {
field: "app_sdk_workflow_receipts.sdk_outbox_event_ids_json",
source,
})
})?,
- expected_event_id: row.get(5)?,
- actor_pubkey: row.get(6)?,
- idempotency_digest_prefix: row.get(7)?,
+ expected_event_id: row.try_get(5)?,
+ actor_pubkey: row.try_get(6)?,
+ idempotency_digest_prefix: row.try_get(7)?,
workflow_state: AppSdkWorkflowReceiptState::parse(workflow_state.as_str())
.map_err(decode_app_error)?,
- created_at: row.get(9)?,
- updated_at: row.get(10)?,
+ created_at: row.try_get(9)?,
+ updated_at: row.try_get(10)?,
detail_json: serde_json::from_str(detail_json.as_str()).map_err(|source| {
decode_app_error(AppSqliteError::DecodeJson {
field: "app_sdk_workflow_receipts.detail_json",
@@ -257,8 +261,8 @@ fn decode_receipt_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<AppSdkStoredW
})
}
-fn decode_app_error(error: AppSqliteError) -> rusqlite::Error {
- rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(error))
+fn decode_app_error(error: AppSqliteError) -> sqlx::Error {
+ sqlx::Error::Decode(Box::new(error))
}
#[cfg(test)]
diff --git a/crates/store/src/source_guards.rs b/crates/store/src/source_guards.rs
@@ -0,0 +1,170 @@
+use std::{
+ fs,
+ path::{Path, PathBuf},
+};
+
+const WORKSPACE_SQLX_DEPENDENCY: &str = r#"sqlx = { version = "0.9.0", default-features = false, features = ["derive", "sqlite-bundled"] }"#;
+const WORKSPACE_LIBSQLITE3_PATCH: &str =
+ r#"libsqlite3-sys = { path = "../lib/crates/libsqlite3_sys_3_53_3" }"#;
+
+#[test]
+fn app_sqlite_runtime_uses_sqlx_bundled_sqlite_only() {
+ let app_root = app_root();
+ let workspace_manifest = read_source(app_root.join("Cargo.toml").as_path());
+
+ assert!(
+ workspace_manifest.contains(WORKSPACE_SQLX_DEPENDENCY),
+ "workspace SQLx dependency must stay pinned to SQLx 0.9.0 with sqlite-bundled"
+ );
+ assert!(
+ workspace_manifest.contains(WORKSPACE_LIBSQLITE3_PATCH),
+ "workspace must keep the approved SQLite 3.53.3 libsqlite3-sys patch"
+ );
+
+ let findings = sqlite_runtime_drift_findings(app_root.as_path());
+ assert!(
+ findings.is_empty(),
+ "app SQLite runtime drift findings:\n{}",
+ findings.join("\n")
+ );
+}
+
+fn sqlite_runtime_drift_findings(app_root: &Path) -> Vec<String> {
+ sqlite_guard_paths(app_root)
+ .into_iter()
+ .flat_map(|path| {
+ let source = read_source(path.as_path());
+ let relative_path = path
+ .strip_prefix(app_root)
+ .expect("guard path should be app-relative")
+ .to_string_lossy()
+ .replace('\\', "/");
+ forbidden_sqlite_findings(relative_path.as_str(), source.as_str())
+ })
+ .collect()
+}
+
+fn forbidden_sqlite_findings(path: &str, source: &str) -> Vec<String> {
+ let mut findings = Vec::new();
+
+ for pattern in ["rusqlite", "SqliteExecutor"] {
+ for line in token_match_lines(source, pattern) {
+ findings.push(format!(
+ "{path}:{line} contains forbidden SQLite runtime token `{pattern}`"
+ ));
+ }
+ }
+
+ for pattern in [
+ "sqlite-wasm-rs",
+ "rsqlite-vfs",
+ "bundled-sqlcipher",
+ "features = [\"bundled\"]",
+ "features = [\"bundled-full\"]",
+ "features = [\"bundled-sqlcipher\"]",
+ ] {
+ for line in literal_match_lines(source, pattern) {
+ findings.push(format!(
+ "{path}:{line} contains forbidden SQLite runtime literal `{pattern}`"
+ ));
+ }
+ }
+
+ let lowercase_source = source.to_lowercase();
+ for line in literal_match_lines(lowercase_source.as_str(), "sqlcipher") {
+ findings.push(format!(
+ "{path}:{line} contains forbidden SQLCipher runtime literal"
+ ));
+ }
+
+ findings
+}
+
+fn sqlite_guard_paths(app_root: &Path) -> Vec<PathBuf> {
+ let mut paths = vec![app_root.join("Cargo.toml"), app_root.join("Cargo.lock")];
+ collect_guard_paths(app_root.join("crates").as_path(), &mut paths);
+ paths.sort();
+ paths
+}
+
+fn collect_guard_paths(root: &Path, paths: &mut Vec<PathBuf>) {
+ for entry in fs::read_dir(root).unwrap_or_else(|error| {
+ panic!("failed to read guard directory {}: {error}", root.display())
+ }) {
+ let entry = entry.unwrap_or_else(|error| {
+ panic!(
+ "failed to inspect guard directory {}: {error}",
+ root.display()
+ )
+ });
+ let path = entry.path();
+
+ if path.is_dir() {
+ if path.file_name().and_then(|name| name.to_str()) != Some("target") {
+ collect_guard_paths(path.as_path(), paths);
+ }
+ continue;
+ }
+
+ if path.file_name().and_then(|name| name.to_str()) == Some("source_guards.rs") {
+ continue;
+ }
+
+ if matches!(
+ path.extension().and_then(|extension| extension.to_str()),
+ Some("rs" | "toml" | "lock")
+ ) {
+ paths.push(path);
+ }
+ }
+}
+
+fn token_match_lines(source: &str, pattern: &str) -> Vec<usize> {
+ source
+ .match_indices(pattern)
+ .filter_map(|(index, _)| {
+ let before = source[..index].chars().next_back();
+ let after = source[index + pattern.len()..].chars().next();
+
+ if before.is_some_and(is_rust_identifier_character)
+ || after.is_some_and(is_rust_identifier_character)
+ {
+ None
+ } else {
+ Some(line_number(source, index))
+ }
+ })
+ .collect()
+}
+
+fn literal_match_lines(source: &str, pattern: &str) -> Vec<usize> {
+ source
+ .match_indices(pattern)
+ .map(|(index, _)| line_number(source, index))
+ .collect()
+}
+
+fn read_source(path: &Path) -> String {
+ fs::read_to_string(path)
+ .unwrap_or_else(|error| panic!("failed to read source {}: {error}", path.display()))
+}
+
+fn app_root() -> PathBuf {
+ Path::new(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .and_then(Path::parent)
+ .expect("store crate should live under app crates directory")
+ .to_path_buf()
+}
+
+fn is_rust_identifier_character(character: char) -> bool {
+ character == '_' || character.is_ascii_alphanumeric()
+}
+
+fn line_number(source: &str, index: usize) -> usize {
+ source[..index]
+ .bytes()
+ .filter(|byte| *byte == b'\n')
+ .count()
+ + 1
+}
diff --git a/crates/store/src/sync.rs b/crates/store/src/sync.rs
@@ -5,7 +5,9 @@ use radroots_studio_app_sync::{
SyncConflictResolutionStatus, SyncConflictSeverity, SyncOperationKind,
};
use radroots_studio_app_view::{FarmId, FulfillmentWindowId, OrderId, ProductId};
-use rusqlite::{Connection, OptionalExtension, params};
+use sqlx::Row;
+
+use crate::{AppSqliteDatabase, OptionalSqliteResult};
use uuid::Uuid;
use crate::AppSqliteError;
@@ -28,12 +30,35 @@ pub struct StoredRelayIngestCursor {
pub cursor_since_unix_seconds: Option<i64>,
}
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct AppRelayIngestSuccessInput<'a> {
+ pub scope_key: &'a str,
+ pub relay_url: &'a str,
+ pub cursor_since_unix_seconds: i64,
+ pub last_event_created_at_unix_seconds: Option<i64>,
+ pub started_at: &'a str,
+ pub started_unix_seconds: i64,
+ pub completed_at: &'a str,
+ pub completed_unix_seconds: i64,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct AppRelayIngestFailureInput<'a> {
+ pub scope_key: &'a str,
+ pub relay_url: &'a str,
+ pub started_at: &'a str,
+ pub started_unix_seconds: i64,
+ pub completed_at: &'a str,
+ pub completed_unix_seconds: i64,
+ pub error_message: &'a str,
+}
+
pub struct AppSyncRepository<'a> {
- connection: &'a Connection,
+ connection: &'a AppSqliteDatabase,
}
impl<'a> AppSyncRepository<'a> {
- pub const fn new(connection: &'a Connection) -> Self {
+ pub(crate) const fn new(connection: &'a AppSqliteDatabase) -> Self {
Self { connection }
}
@@ -72,7 +97,7 @@ impl<'a> AppSyncRepository<'a> {
attempt_count = 0,
state = 'pending',
last_error_message = NULL",
- params![
+ crate::app_sqlite_params![
operation_id,
account_id,
operation.operation_key.as_str(),
@@ -100,8 +125,8 @@ impl<'a> AppSyncRepository<'a> {
AND operation_key = ?2
AND state IN ('pending', 'in_progress', 'failed', 'blocked', 'retryable')
LIMIT 1",
- params![account_id, operation.operation_key.as_str()],
- |row| row.get::<_, String>(0),
+ crate::app_sqlite_params![account_id, operation.operation_key.as_str()],
+ |row| row.try_get::<String, _>(0),
)
.map_err(|source| AppSqliteError::Query {
operation: "load pending sync operation id after enqueue",
@@ -140,17 +165,17 @@ impl<'a> AppSyncRepository<'a> {
let rows = statement
.query_map([account_id], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, String>(7)?,
- row.get::<_, u32>(8)?,
- row.get::<_, String>(9)?,
- row.get::<_, Option<String>>(10)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<String, _>(7)?,
+ row.try_get::<u32, _>(8)?,
+ row.try_get::<String, _>(9)?,
+ row.try_get::<Option<String>, _>(10)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -216,7 +241,7 @@ impl<'a> AppSyncRepository<'a> {
state = 'retryable',
last_error_message = ?5
WHERE account_id = ?1 AND id = ?2",
- params![
+ crate::app_sqlite_params![
account_id,
operation_id,
available_at,
@@ -241,7 +266,7 @@ impl<'a> AppSyncRepository<'a> {
.connection
.execute(
"DELETE FROM local_outbox WHERE account_id = ?1 AND id = ?2",
- params![account_id, operation_id],
+ crate::app_sqlite_params![account_id, operation_id],
)
.map_err(|source| AppSqliteError::Query {
operation: "dequeue pending sync operation",
@@ -270,11 +295,11 @@ impl<'a> AppSyncRepository<'a> {
[account_id],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, Option<String>>(1)?,
- row.get::<_, Option<String>>(2)?,
- row.get::<_, Option<String>>(3)?,
- row.get::<_, Option<String>>(4)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<Option<String>, _>(1)?,
+ row.try_get::<Option<String>, _>(2)?,
+ row.try_get::<Option<String>, _>(3)?,
+ row.try_get::<Option<String>, _>(4)?,
))
},
)
@@ -325,7 +350,7 @@ impl<'a> AppSyncRepository<'a> {
last_sync_completed_at = excluded.last_sync_completed_at,
last_remote_cursor = excluded.last_remote_cursor,
last_error_message = excluded.last_error_message",
- params![
+ crate::app_sqlite_params![
account_id,
sync_checkpoint_state_value(checkpoint.state),
checkpoint.last_sync_started_at,
@@ -357,8 +382,8 @@ impl<'a> AppSyncRepository<'a> {
FROM app_relay_ingest_freshness
WHERE scope_key = ?1 AND relay_url = ?2
LIMIT 1",
- params![scope_key, relay_url.as_str()],
- |row| row.get::<_, Option<i64>>(0),
+ crate::app_sqlite_params![scope_key, relay_url.as_str()],
+ |row| row.try_get::<Option<i64>, _>(0),
)
.optional()
.map_err(|source| AppSqliteError::Query {
@@ -404,14 +429,7 @@ impl<'a> AppSyncRepository<'a> {
pub fn record_relay_ingest_success(
&self,
- scope_key: &str,
- relay_url: &str,
- cursor_since_unix_seconds: i64,
- last_event_created_at_unix_seconds: Option<i64>,
- started_at: &str,
- started_unix_seconds: i64,
- completed_at: &str,
- completed_unix_seconds: i64,
+ input: AppRelayIngestSuccessInput<'_>,
) -> Result<(), AppSqliteError> {
self.connection
.execute(
@@ -442,15 +460,15 @@ impl<'a> AppSyncRepository<'a> {
last_success_unix_seconds = excluded.last_success_unix_seconds,
last_error_message = NULL,
updated_at = excluded.updated_at",
- params![
- scope_key,
- relay_url,
- cursor_since_unix_seconds,
- last_event_created_at_unix_seconds,
- started_at,
- started_unix_seconds,
- completed_at,
- completed_unix_seconds,
+ crate::app_sqlite_params![
+ input.scope_key,
+ input.relay_url,
+ input.cursor_since_unix_seconds,
+ input.last_event_created_at_unix_seconds,
+ input.started_at,
+ input.started_unix_seconds,
+ input.completed_at,
+ input.completed_unix_seconds,
],
)
.map_err(|source| AppSqliteError::Query {
@@ -463,13 +481,7 @@ impl<'a> AppSyncRepository<'a> {
pub fn record_relay_ingest_failure(
&self,
- scope_key: &str,
- relay_url: &str,
- started_at: &str,
- started_unix_seconds: i64,
- completed_at: &str,
- completed_unix_seconds: i64,
- error_message: &str,
+ input: AppRelayIngestFailureInput<'_>,
) -> Result<(), AppSqliteError> {
self.connection
.execute(
@@ -496,14 +508,14 @@ impl<'a> AppSyncRepository<'a> {
last_fetch_completed_unix_seconds = excluded.last_fetch_completed_unix_seconds,
last_error_message = excluded.last_error_message,
updated_at = excluded.updated_at",
- params![
- scope_key,
- relay_url,
- started_at,
- started_unix_seconds,
- completed_at,
- completed_unix_seconds,
- error_message,
+ crate::app_sqlite_params![
+ input.scope_key,
+ input.relay_url,
+ input.started_at,
+ input.started_unix_seconds,
+ input.completed_at,
+ input.completed_unix_seconds,
+ input.error_message,
],
)
.map_err(|source| AppSqliteError::Query {
@@ -536,17 +548,17 @@ impl<'a> AppSyncRepository<'a> {
FROM app_relay_ingest_freshness
WHERE scope_key = ?1 AND relay_url = ?2
LIMIT 1",
- params![scope_key, relay_url],
+ crate::app_sqlite_params![scope_key, relay_url],
|row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, Option<i64>>(1)?,
- row.get::<_, Option<i64>>(2)?,
- row.get::<_, Option<String>>(3)?,
- row.get::<_, Option<String>>(4)?,
- row.get::<_, Option<i64>>(5)?,
- row.get::<_, Option<String>>(6)?,
- row.get::<_, Option<String>>(7)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<Option<i64>, _>(1)?,
+ row.try_get::<Option<i64>, _>(2)?,
+ row.try_get::<Option<String>, _>(3)?,
+ row.try_get::<Option<String>, _>(4)?,
+ row.try_get::<Option<i64>, _>(5)?,
+ row.try_get::<Option<String>, _>(6)?,
+ row.try_get::<Option<String>, _>(7)?,
))
},
)
@@ -625,7 +637,7 @@ impl<'a> AppSyncRepository<'a> {
detected_at,
resolved_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
- params![
+ crate::app_sqlite_params![
conflict_id,
account_id,
conflict.aggregate.aggregate_kind(),
@@ -698,16 +710,16 @@ impl<'a> AppSyncRepository<'a> {
let rows = statement
.query_map([account_id], |row| {
Ok((
- row.get::<_, String>(0)?,
- row.get::<_, String>(1)?,
- row.get::<_, String>(2)?,
- row.get::<_, String>(3)?,
- row.get::<_, String>(4)?,
- row.get::<_, String>(5)?,
- row.get::<_, String>(6)?,
- row.get::<_, Option<String>>(7)?,
- row.get::<_, String>(8)?,
- row.get::<_, Option<String>>(9)?,
+ row.try_get::<String, _>(0)?,
+ row.try_get::<String, _>(1)?,
+ row.try_get::<String, _>(2)?,
+ row.try_get::<String, _>(3)?,
+ row.try_get::<String, _>(4)?,
+ row.try_get::<String, _>(5)?,
+ row.try_get::<String, _>(6)?,
+ row.try_get::<Option<String>, _>(7)?,
+ row.try_get::<String, _>(8)?,
+ row.try_get::<Option<String>, _>(9)?,
))
})
.map_err(|source| AppSqliteError::Query {
@@ -773,7 +785,7 @@ impl<'a> AppSyncRepository<'a> {
"UPDATE local_conflicts
SET resolution_status = ?3, resolved_at = ?4
WHERE account_id = ?1 AND id = ?2",
- params![
+ crate::app_sqlite_params![
account_id,
conflict_id,
sync_conflict_resolution_status_value(resolution),
@@ -1008,7 +1020,9 @@ mod tests {
};
use radroots_studio_app_view::{FarmId, ProductId};
- use crate::{AppSqliteStore, DatabaseTarget};
+ use crate::{
+ AppRelayIngestFailureInput, AppRelayIngestSuccessInput, AppSqliteStore, DatabaseTarget,
+ };
#[test]
fn checkpoints_are_selected_account_scoped() {
@@ -1064,27 +1078,27 @@ mod tests {
);
repository
- .record_relay_ingest_success(
- "direct_relay_ingest",
- "wss://relay-a.example",
- 1_010,
- Some(1_009),
- "2026-05-25T20:00:00Z",
- 1_000,
- "2026-05-25T20:00:02Z",
- 1_002,
- )
+ .record_relay_ingest_success(AppRelayIngestSuccessInput {
+ scope_key: "direct_relay_ingest",
+ relay_url: "wss://relay-a.example",
+ cursor_since_unix_seconds: 1_010,
+ last_event_created_at_unix_seconds: Some(1_009),
+ started_at: "2026-05-25T20:00:00Z",
+ started_unix_seconds: 1_000,
+ completed_at: "2026-05-25T20:00:02Z",
+ completed_unix_seconds: 1_002,
+ })
.expect("success should record");
repository
- .record_relay_ingest_failure(
- "direct_relay_ingest",
- "wss://relay-b.example",
- "2026-05-25T20:00:00Z",
- 1_000,
- "2026-05-25T20:00:02Z",
- 1_002,
- "relay timeout",
- )
+ .record_relay_ingest_failure(AppRelayIngestFailureInput {
+ scope_key: "direct_relay_ingest",
+ relay_url: "wss://relay-b.example",
+ started_at: "2026-05-25T20:00:00Z",
+ started_unix_seconds: 1_000,
+ completed_at: "2026-05-25T20:00:02Z",
+ completed_unix_seconds: 1_002,
+ error_message: "relay timeout",
+ })
.expect("failure should record");
let cursors = repository
@@ -1125,13 +1139,13 @@ mod tests {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let repository = store.sync_repository();
let first = PendingSyncOperation::new(
- SyncAggregateRef::Farm(FarmId::new()),
+ SyncAggregateRef::Farm(FarmId::generate()),
SyncOperationKind::Upsert,
"{\"farm\":\"a\"}",
"2026-04-20T18:00:00Z",
);
let second = PendingSyncOperation::new(
- SyncAggregateRef::Product(ProductId::new()),
+ SyncAggregateRef::Product(ProductId::generate()),
SyncOperationKind::Delete,
"{\"product\":\"b\"}",
"2026-04-20T18:05:00Z",
@@ -1211,7 +1225,7 @@ mod tests {
fn outbox_enqueue_upserts_active_operation_by_deterministic_key() {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let repository = store.sync_repository();
- let product_id = ProductId::new();
+ let product_id = ProductId::generate();
let first = PendingSyncOperation::new(
SyncAggregateRef::Product(product_id),
SyncOperationKind::Upsert,
@@ -1260,7 +1274,7 @@ mod tests {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let repository = store.sync_repository();
let first = SyncConflict {
- aggregate: SyncAggregateRef::Farm(FarmId::new()),
+ aggregate: SyncAggregateRef::Farm(FarmId::generate()),
kind: SyncConflictKind::RevisionMismatch,
severity: SyncConflictSeverity::Blocking,
resolution: SyncConflictResolutionStatus::Unresolved,
@@ -1270,7 +1284,7 @@ mod tests {
resolved_at: None,
};
let second = SyncConflict {
- aggregate: SyncAggregateRef::Product(ProductId::new()),
+ aggregate: SyncAggregateRef::Product(ProductId::generate()),
kind: SyncConflictKind::RemoteValidationReject,
severity: SyncConflictSeverity::ReviewRequired,
resolution: SyncConflictResolutionStatus::Unresolved,
@@ -1334,7 +1348,7 @@ mod tests {
let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open");
let repository = store.sync_repository();
let first = SyncConflict {
- aggregate: SyncAggregateRef::Farm(FarmId::new()),
+ aggregate: SyncAggregateRef::Farm(FarmId::generate()),
kind: SyncConflictKind::RevisionMismatch,
severity: SyncConflictSeverity::Blocking,
resolution: SyncConflictResolutionStatus::Unresolved,
@@ -1344,7 +1358,7 @@ mod tests {
resolved_at: None,
};
let second = SyncConflict {
- aggregate: SyncAggregateRef::Product(ProductId::new()),
+ aggregate: SyncAggregateRef::Product(ProductId::generate()),
kind: SyncConflictKind::RemoteValidationReject,
severity: SyncConflictSeverity::ReviewRequired,
resolution: SyncConflictResolutionStatus::Unresolved,
diff --git a/crates/sync/src/lib.rs b/crates/sync/src/lib.rs
@@ -586,7 +586,7 @@ mod tests {
fn conflict_status_counts_only_unresolved_conflicts() {
let conflicts = vec![
SyncConflict {
- aggregate: SyncAggregateRef::Product(ProductId::new()),
+ aggregate: SyncAggregateRef::Product(ProductId::generate()),
kind: SyncConflictKind::RevisionMismatch,
severity: SyncConflictSeverity::Blocking,
resolution: SyncConflictResolutionStatus::Unresolved,
@@ -596,7 +596,7 @@ mod tests {
resolved_at: None,
},
SyncConflict {
- aggregate: SyncAggregateRef::Farm(FarmId::new()),
+ aggregate: SyncAggregateRef::Farm(FarmId::generate()),
kind: SyncConflictKind::RemoteValidationReject,
severity: SyncConflictSeverity::ReviewRequired,
resolution: SyncConflictResolutionStatus::AcceptedRemote,
@@ -618,14 +618,14 @@ mod tests {
#[test]
fn request_and_result_surface_conflict_status_through_typed_contracts() {
let mut pending_operation = PendingSyncOperation::new(
- SyncAggregateRef::Product(ProductId::new()),
+ SyncAggregateRef::Product(ProductId::generate()),
SyncOperationKind::Upsert,
"{\"title\":\"greens\"}",
"2026-04-17T19:32:00Z",
);
pending_operation.attempt_count = 1;
let conflict = SyncConflict {
- aggregate: SyncAggregateRef::Product(ProductId::new()),
+ aggregate: SyncAggregateRef::Product(ProductId::generate()),
kind: SyncConflictKind::RevisionMismatch,
severity: SyncConflictSeverity::ReviewRequired,
resolution: SyncConflictResolutionStatus::Unresolved,
diff --git a/crates/sync/src/publish.rs b/crates/sync/src/publish.rs
@@ -681,7 +681,7 @@ mod tests {
#[test]
fn publish_payload_serializes_with_stable_kind_and_sdk_target() {
- let farm_id = FarmId::new();
+ let farm_id = FarmId::generate();
let payload = AppPublishPayload::FarmProfile(AppFarmProfilePublishPayload {
context: AppPublishContext::new("acct_local", "farm_setup")
.with_source_local_event_id("local-event-1"),
@@ -739,7 +739,7 @@ mod tests {
fn listing_publish_payload_reports_stable_validation_reason_codes() {
let payload = AppPublishPayload::Listing(AppListingPublishPayload {
context: AppPublishContext::new("", ""),
- product_id: ProductId::new(),
+ product_id: ProductId::generate(),
listing_d_tag: None,
farm_id: None,
farm_pubkey: None,
@@ -789,8 +789,8 @@ mod tests {
fn order_request_publish_payload_requires_sdk_publish_inputs() {
let payload = AppPublishPayload::OrderRequest(AppOrderRequestPublishPayload {
context: AppPublishContext::new("acct_buyer", "place_personal_order"),
- order_id: OrderId::new(),
- farm_id: FarmId::new(),
+ order_id: OrderId::generate(),
+ farm_id: FarmId::generate(),
status: Some("needs_action".to_owned()),
order_document_json: None,
listing_addr: Some(String::new()),
@@ -799,7 +799,7 @@ mod tests {
buyer_pubkey: None,
seller_pubkey: Some(" ".to_owned()),
items: vec![AppOrderRequestItemPayload {
- product_id: ProductId::new(),
+ product_id: ProductId::generate(),
quantity: 0,
}],
currency_code: None,
@@ -834,8 +834,8 @@ mod tests {
fn order_decision_publish_payload_reports_stable_validation_reason_codes() {
let payload = AppPublishPayload::OrderDecision(AppOrderDecisionPublishPayload {
context: AppPublishContext::new("", ""),
- app_order_id: OrderId::new(),
- farm_id: FarmId::new(),
+ app_order_id: OrderId::generate(),
+ farm_id: FarmId::generate(),
trade_order_id: " ".to_owned(),
request_event_id: String::new(),
listing_event_id: None,
@@ -876,8 +876,8 @@ mod tests {
#[test]
fn cancellation_publish_payload_reports_stable_validation_reason_codes() {
- let order_id = OrderId::new();
- let farm_id = FarmId::new();
+ let order_id = OrderId::generate();
+ let farm_id = FarmId::generate();
let cancellation =
AppPublishPayload::OrderCancellation(AppOrderCancellationPublishPayload {
context: AppPublishContext::new("", ""),
@@ -956,8 +956,8 @@ mod tests {
#[test]
fn order_revision_publish_payloads_report_stable_validation_reason_codes() {
- let order_id = OrderId::new();
- let farm_id = FarmId::new();
+ let order_id = OrderId::generate();
+ let farm_id = FarmId::generate();
let economics = revision_economics();
let valid_proposal =
AppPublishPayload::OrderRevisionProposal(AppOrderRevisionProposalPublishPayload {
@@ -1067,7 +1067,7 @@ mod tests {
fn existing_raw_payload_outbox_work_rejects_publish_payload() {
let pending_operation = PendingSyncOperation {
operation_key: "product:greens:upsert".to_owned(),
- aggregate: SyncAggregateRef::Product(ProductId::new()),
+ aggregate: SyncAggregateRef::Product(ProductId::generate()),
operation: SyncOperationKind::Upsert,
payload_json: "{\"title\":\"greens\"}".to_owned(),
created_at: "2026-04-17T19:32:00Z".to_owned(),
diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs
@@ -54,7 +54,7 @@ macro_rules! typed_id {
pub struct $name(Uuid);
impl $name {
- pub fn new() -> Self {
+ pub fn generate() -> Self {
Self(Uuid::now_v7())
}
diff --git a/crates/view/src/lib.rs b/crates/view/src/lib.rs
@@ -2292,7 +2292,7 @@ mod tests {
custody: AccountCustody::LocalManaged,
},
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
- FarmerActivationProjection::active(FarmId::new()),
+ FarmerActivationProjection::active(FarmId::generate()),
);
let activation = AccountSurfaceActivationProjection::from(&selected_account);
let restored = SelectedAccountProjection::from_surface_activation(
@@ -2315,7 +2315,7 @@ mod tests {
custody: AccountCustody::LocalManaged,
},
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
- FarmerActivationProjection::active(FarmId::new()),
+ FarmerActivationProjection::active(FarmId::generate()),
),
);
let personal_identity = AppIdentityProjection::ready(
@@ -2419,7 +2419,7 @@ mod tests {
custody: AccountCustody::LocalManaged,
},
SelectedSurfaceProjection::new(ActiveSurface::Farmer),
- FarmerActivationProjection::active(FarmId::new()),
+ FarmerActivationProjection::active(FarmId::generate()),
);
let signed_in_identity = AppIdentityProjection::ready(Vec::new(), selected_account.clone());
let blocked_identity = AppIdentityProjection::blocked_with_selection(
@@ -2663,8 +2663,8 @@ mod tests {
#[test]
fn product_attention_stock_and_projection_states_are_explicit() {
let row = ProductsListRow {
- product_id: super::ProductId::new(),
- farm_id: FarmId::new(),
+ product_id: super::ProductId::generate(),
+ farm_id: FarmId::generate(),
title: "Pea shoots".to_owned(),
subtitle: Some("Tray-grown".to_owned()),
status: ProductStatus::Draft,
@@ -2723,7 +2723,7 @@ mod tests {
price_minor_units: Some(450),
price_currency: "USD".to_owned(),
stock_quantity: Some(12),
- availability_window_id: Some(super::FulfillmentWindowId::new()),
+ availability_window_id: Some(super::FulfillmentWindowId::generate()),
status: ProductStatus::Draft,
};
@@ -2934,7 +2934,7 @@ mod tests {
"storage keys must parse exactly"
);
- let order_id = OrderId::new();
+ let order_id = OrderId::generate();
let active_order = test_active_order_projection(RadrootsTradeWorkflowState::Committed);
let projection = TradeWorkflowProjection::from_active_order_projection(
order_id,
@@ -3356,9 +3356,9 @@ mod tests {
#[test]
fn pack_day_output_source_keeps_export_truth_out_of_ui_display_strings() {
- let farm_id = FarmId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
- let order_id = OrderId::new();
+ let farm_id = FarmId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
+ let order_id = OrderId::generate();
let screen_row = PackDayPackListRow {
title: "Salad mix".to_owned(),
quantity_display: "Casey: 2 bags".to_owned(),
@@ -3405,10 +3405,10 @@ mod tests {
#[test]
fn pack_day_export_bundle_tracks_output_directory_and_artifacts() {
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let bundle = PackDayExportBundle {
fulfillment_window_id,
- export_instance_id: PackDayExportInstanceId::new(),
+ export_instance_id: PackDayExportInstanceId::generate(),
generated_at_utc: "2026-04-23T15:00:00Z".to_owned(),
bundle_directory: "exports/pack_day/window-1/20260423T150000Z".to_owned(),
artifacts: vec![
@@ -3432,9 +3432,9 @@ mod tests {
#[test]
fn orders_and_pack_day_projections_hold_truthful_execution_data() {
- let fulfillment_window_id = super::FulfillmentWindowId::new();
- let farm_id = FarmId::new();
- let order_id = super::OrderId::new();
+ let fulfillment_window_id = super::FulfillmentWindowId::generate();
+ let farm_id = FarmId::generate();
+ let order_id = super::OrderId::generate();
let order_economics = TradeEconomicsProjection {
subtotal_minor_units: Some(1300),
total_minor_units: Some(1300),
@@ -3530,9 +3530,9 @@ mod tests {
#[test]
fn buyer_marketplace_projections_hold_guest_capable_contract_data() {
- let farm_id = FarmId::new();
- let product_id = super::ProductId::new();
- let order_id = super::OrderId::new();
+ let farm_id = FarmId::generate();
+ let product_id = super::ProductId::generate();
+ let order_id = super::OrderId::generate();
let buyer_order_economics = TradeEconomicsProjection {
subtotal_minor_units: Some(1300),
total_minor_units: Some(1300),
@@ -3664,19 +3664,19 @@ mod tests {
fn today_agenda_stays_on_the_compact_order_row_contract() {
let today = TodayAgendaProjection {
orders_needing_action: vec![OrderListRow {
- order_id: super::OrderId::new(),
- farm_id: FarmId::new(),
- fulfillment_window_id: Some(super::FulfillmentWindowId::new()),
+ order_id: super::OrderId::generate(),
+ farm_id: FarmId::generate(),
+ fulfillment_window_id: Some(super::FulfillmentWindowId::generate()),
order_number: "R-1002".to_owned(),
customer_display_name: "Morgan".to_owned(),
status: OrderStatus::NeedsAction,
}],
..TodayAgendaProjection::default()
};
- let orders_row_id = super::OrderId::new();
+ let orders_row_id = super::OrderId::generate();
let orders_row = OrdersListRow {
order_id: orders_row_id,
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
fulfillment_window_id: None,
order_number: "R-2002".to_owned(),
customer_display_name: "Robin".to_owned(),
@@ -3702,14 +3702,14 @@ mod tests {
#[test]
fn today_summary_attention_state_is_explicit() {
let quiet = TodaySummary {
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
orders_needing_action: 0,
low_stock_products: 0,
draft_products: 0,
reminders_due_soon: 0,
};
let busy = TodaySummary {
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
orders_needing_action: 1,
low_stock_products: 0,
draft_products: 0,
@@ -3722,11 +3722,11 @@ mod tests {
#[test]
fn reminder_and_repeat_demand_contracts_are_explicit() {
- let farm_id = FarmId::new();
- let order_id = OrderId::new();
- let fulfillment_window_id = FulfillmentWindowId::new();
+ let farm_id = FarmId::generate();
+ let order_id = OrderId::generate();
+ let fulfillment_window_id = FulfillmentWindowId::generate();
let reminder = ReminderDeadlineProjection {
- reminder_id: ReminderId::new(),
+ reminder_id: ReminderId::generate(),
farm_id,
order_id: Some(order_id),
fulfillment_window_id: Some(fulfillment_window_id),
@@ -3781,8 +3781,8 @@ mod tests {
let calm = TodayAgendaProjection::default();
let with_attention = TodayAgendaProjection {
draft_products: vec![ProductListRow {
- product_id: super::ProductId::new(),
- farm_id: FarmId::new(),
+ product_id: super::ProductId::generate(),
+ farm_id: FarmId::generate(),
title: "Spring onions".to_owned(),
status: super::ProductStatus::Draft,
stock_count: 0,
@@ -3809,16 +3809,16 @@ mod tests {
fn today_agenda_projection_can_hold_truthful_lists() {
let projection = TodayAgendaProjection {
orders_needing_action: vec![OrderListRow {
- order_id: super::OrderId::new(),
- farm_id: FarmId::new(),
- fulfillment_window_id: Some(super::FulfillmentWindowId::new()),
+ order_id: super::OrderId::generate(),
+ farm_id: FarmId::generate(),
+ fulfillment_window_id: Some(super::FulfillmentWindowId::generate()),
order_number: "R-1001".to_owned(),
customer_display_name: "Casey".to_owned(),
status: super::OrderStatus::NeedsAction,
}],
low_stock_products: vec![ProductListRow {
- product_id: super::ProductId::new(),
- farm_id: FarmId::new(),
+ product_id: super::ProductId::generate(),
+ farm_id: FarmId::generate(),
title: "Carrots".to_owned(),
status: super::ProductStatus::Published,
stock_count: 2,
@@ -3893,7 +3893,7 @@ mod tests {
#[test]
fn saved_farm_projection_is_always_ready() {
let saved_farm = super::FarmSummary {
- farm_id: FarmId::new(),
+ farm_id: FarmId::generate(),
display_name: "North field farm".to_owned(),
readiness: super::FarmReadiness::Ready,
};
@@ -3927,8 +3927,8 @@ mod tests {
blockers: vec![FarmReadinessBlocker::MissingOperatingRules],
timing_conflicts: vec![FarmTimingConflict {
kind: FarmTimingConflictKind::BlackoutOverlapsFulfillmentWindow,
- fulfillment_window_id: Some(super::FulfillmentWindowId::new()),
- blackout_period_id: Some(BlackoutPeriodId::new()),
+ fulfillment_window_id: Some(super::FulfillmentWindowId::generate()),
+ blackout_period_id: Some(BlackoutPeriodId::generate()),
}],
};
@@ -3970,10 +3970,10 @@ mod tests {
#[test]
fn farm_rules_projection_represents_full_v1_inventory() {
- let farm_id = FarmId::new();
- let pickup_location_id = PickupLocationId::new();
- let fulfillment_window_id = super::FulfillmentWindowId::new();
- let blackout_period_id = BlackoutPeriodId::new();
+ let farm_id = FarmId::generate();
+ let pickup_location_id = PickupLocationId::generate();
+ let fulfillment_window_id = super::FulfillmentWindowId::generate();
+ let blackout_period_id = BlackoutPeriodId::generate();
let projection = super::FarmRulesProjection {
farm_profile: Some(super::FarmProfileRecord {
farm_id,
@@ -4088,12 +4088,12 @@ mod tests {
#[test]
fn activity_context_preserves_recent_event_order() {
let first = AppActivityEvent {
- activity_event_id: ActivityEventId::new(),
+ activity_event_id: ActivityEventId::generate(),
recorded_at: "2026-04-18T00:00:00.000Z".to_owned(),
kind: AppActivityKind::HomeOpened,
};
let second = AppActivityEvent {
- activity_event_id: ActivityEventId::new(),
+ activity_event_id: ActivityEventId::generate(),
recorded_at: "2026-04-18T00:01:00.000Z".to_owned(),
kind: AppActivityKind::SettingsOpened {
section: SettingsSection::About,