// file: kb_lib/src/db/queries/db_metadata.rs //! Queries for `kb_db_metadata`. /// Inserts or updates one metadata row. pub async fn upsert_db_metadata( database: &crate::KbDatabase, dto: &crate::KbDbMetadataDto, ) -> Result<(), crate::KbError> { let entity = crate::KbDbMetadataEntity::from(dto.clone()); match database.connection() { crate::KbDatabaseConnection::Sqlite(pool) => { let query_result = sqlx::query( r#" INSERT INTO kb_db_metadata ( key, value, updated_at ) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at "#, ) .bind(entity.key) .bind(entity.value) .bind(entity.updated_at) .execute(pool) .await; match query_result { Ok(_) => return Ok(()), Err(error) => { return Err(crate::KbError::Db(format!( "cannot upsert kb_db_metadata on sqlite: {}", error ))); }, } }, } } /// Reads one metadata row by key. pub async fn get_db_metadata( database: &crate::KbDatabase, key: &str, ) -> Result, crate::KbError> { match database.connection() { crate::KbDatabaseConnection::Sqlite(pool) => { let query_result = sqlx::query_as::( r#" SELECT key, value, updated_at FROM kb_db_metadata WHERE key = ? LIMIT 1 "#, ) .bind(key) .fetch_optional(pool) .await; let entity_option = match query_result { Ok(entity_option) => entity_option, Err(error) => { return Err(crate::KbError::Db(format!( "cannot read kb_db_metadata '{}' on sqlite: {}", key, error ))); }, }; match entity_option { Some(entity) => { let dto_result = crate::KbDbMetadataDto::try_from(entity); match dto_result { Ok(dto) => return Ok(Some(dto)), Err(error) => return Err(error), } }, None => return Ok(None), } }, } } /// Lists all metadata rows. pub async fn list_db_metadata( database: &crate::KbDatabase, ) -> Result, crate::KbError> { match database.connection() { crate::KbDatabaseConnection::Sqlite(pool) => { let query_result = sqlx::query_as::( r#" SELECT key, value, updated_at FROM kb_db_metadata ORDER BY key ASC "#, ) .fetch_all(pool) .await; let entities = match query_result { Ok(entities) => entities, Err(error) => { return Err(crate::KbError::Db(format!( "cannot list kb_db_metadata on sqlite: {}", error ))); }, }; let mut dtos = std::vec::Vec::new(); for entity in entities { let dto_result = crate::KbDbMetadataDto::try_from(entity); let dto = match dto_result { Ok(dto) => dto, Err(error) => return Err(error), }; dtos.push(dto); } return Ok(dtos); }, } } #[cfg(test)] mod tests { #[tokio::test] async fn db_metadata_roundtrip_works() { let tempdir = tempfile::tempdir().expect("tempdir must succeed"); let database_path = tempdir.path().join("roundtrip.sqlite3"); let config = crate::KbDatabaseConfig { enabled: true, backend: crate::KbDatabaseBackend::Sqlite, sqlite: crate::KbSqliteDatabaseConfig { path: database_path.to_string_lossy().to_string(), create_if_missing: true, busy_timeout_ms: 5000, max_connections: 1, auto_initialize_schema: true, use_wal: true, }, }; let database = crate::KbDatabase::connect_and_initialize(&config) .await .expect("database init must succeed"); let dto = crate::KbDbMetadataDto::new("schema_version".to_string(), "0.5.0".to_string()); crate::upsert_db_metadata(&database, &dto).await.expect("upsert must succeed"); let fetched = crate::get_db_metadata(&database, "schema_version") .await .expect("fetch must succeed"); assert!(fetched.is_some()); let fetched = fetched.expect("metadata must exist"); assert_eq!(fetched.key, "schema_version"); assert_eq!(fetched.value, "0.5.0"); let listed = crate::list_db_metadata(&database).await.expect("list must succeed"); assert!(!listed.is_empty()); } }