refactor(data): add select query abstraction

This commit is contained in:
Kayphoon
2026-05-17 15:04:35 +08:00
parent f0a6fffa87
commit 77640d51a6
6 changed files with 445 additions and 76 deletions

View File

@@ -3,6 +3,8 @@ mod mysql;
mod postgres;
mod sqlite;
use aether_data_query::{DialectSql, SelectColumn, SelectQuery};
#[allow(unused_imports)]
pub(crate) use aether_data_contracts::repository::quota::{
ProviderQuotaReadRepository, ProviderQuotaRepository, ProviderQuotaWriteRepository,
@@ -12,3 +14,39 @@ pub use memory::InMemoryProviderQuotaRepository;
pub use mysql::MysqlProviderQuotaRepository;
pub use postgres::SqlxProviderQuotaRepository;
pub use sqlite::SqliteProviderQuotaRepository;
fn quota_snapshot_select() -> SelectQuery<'static> {
SelectQuery::new("providers").select_columns([
SelectColumn::expr("id").alias("provider_id"),
SelectColumn::expr(
DialectSql::common("billing_type").with_postgres("CAST(billing_type AS TEXT)"),
)
.alias("billing_type"),
SelectColumn::expr(DialectSql::dialect(
"CAST(monthly_quota_usd AS DOUBLE PRECISION)",
"CAST(monthly_quota_usd AS REAL)",
"monthly_quota_usd",
))
.alias("monthly_quota_usd"),
SelectColumn::expr(DialectSql::dialect(
"CAST(COALESCE(monthly_used_usd, 0) AS DOUBLE PRECISION)",
"CAST(COALESCE(monthly_used_usd, 0) AS REAL)",
"COALESCE(monthly_used_usd, 0)",
))
.alias("monthly_used_usd"),
SelectColumn::expr("quota_reset_day"),
SelectColumn::expr(DialectSql::dialect(
"CAST(EXTRACT(EPOCH FROM quota_last_reset_at) AS BIGINT)",
"quota_last_reset_at",
"quota_last_reset_at",
))
.alias("quota_last_reset_at_unix_secs"),
SelectColumn::expr(DialectSql::dialect(
"CAST(EXTRACT(EPOCH FROM quota_expires_at) AS BIGINT)",
"quota_expires_at",
"quota_expires_at",
))
.alias("quota_expires_at_unix_secs"),
SelectColumn::expr("is_active"),
])
}

View File

@@ -1,26 +1,14 @@
use async_trait::async_trait;
use sqlx::{mysql::MySqlRow, MySql, QueryBuilder, Row};
use sqlx::{mysql::MySqlRow, MySql, Row};
use super::{
ProviderQuotaReadRepository, ProviderQuotaWriteRepository, StoredProviderQuotaSnapshot,
quota_snapshot_select, ProviderQuotaReadRepository, ProviderQuotaWriteRepository,
StoredProviderQuotaSnapshot,
};
use crate::driver::mysql::MysqlPool;
use crate::error::SqlResultExt;
use crate::DataLayerError;
use aether_data_query::{push_in, WhereClause};
const QUOTA_COLUMNS: &str = r#"
SELECT
id AS provider_id,
billing_type,
monthly_quota_usd,
COALESCE(monthly_used_usd, 0) AS monthly_used_usd,
quota_reset_day,
quota_last_reset_at AS quota_last_reset_at_unix_secs,
quota_expires_at AS quota_expires_at_unix_secs,
is_active
FROM providers
"#;
use aether_data_query::SqlDialect;
#[derive(Debug, Clone)]
pub struct MysqlProviderQuotaRepository {
@@ -39,8 +27,11 @@ impl ProviderQuotaReadRepository for MysqlProviderQuotaRepository {
&self,
provider_id: &str,
) -> Result<Option<StoredProviderQuotaSnapshot>, DataLayerError> {
let row = sqlx::query(&format!("{QUOTA_COLUMNS} WHERE id = ? LIMIT 1"))
.bind(provider_id)
let mut statement = quota_snapshot_select().statement::<MySql>(SqlDialect::Mysql);
statement.where_eq("id", provider_id.to_string()).limit(1);
let row = statement
.finish()
.build()
.fetch_optional(&self.pool)
.await
.map_sql_err()?;
@@ -55,11 +46,16 @@ impl ProviderQuotaReadRepository for MysqlProviderQuotaRepository {
return Ok(Vec::new());
}
let mut builder = QueryBuilder::<MySql>::new(QUOTA_COLUMNS);
let mut where_clause = WhereClause::new();
push_in(&mut builder, &mut where_clause, "id", provider_ids);
builder.push(" ORDER BY id ASC");
let rows = builder.build().fetch_all(&self.pool).await.map_sql_err()?;
let mut statement = quota_snapshot_select().statement::<MySql>(SqlDialect::Mysql);
statement
.where_in("id", provider_ids)
.order_by_sql("id ASC");
let rows = statement
.finish()
.build()
.fetch_all(&self.pool)
.await
.map_sql_err()?;
rows.iter().map(map_row).collect()
}
}

View File

@@ -1,26 +1,12 @@
use async_trait::async_trait;
use sqlx::{PgPool, Postgres, QueryBuilder, Row};
use sqlx::{PgPool, Postgres, Row};
use super::{
ProviderQuotaReadRepository, ProviderQuotaWriteRepository, StoredProviderQuotaSnapshot,
quota_snapshot_select, ProviderQuotaReadRepository, ProviderQuotaWriteRepository,
StoredProviderQuotaSnapshot,
};
use crate::{error::SqlxResultExt, DataLayerError};
use aether_data_query::{push_in, WhereClause};
const FIND_BY_PROVIDER_ID_SQL: &str = r#"
SELECT
id AS provider_id,
CAST(billing_type AS TEXT) AS billing_type,
CAST(monthly_quota_usd AS DOUBLE PRECISION) AS monthly_quota_usd,
CAST(COALESCE(monthly_used_usd, 0) AS DOUBLE PRECISION) AS monthly_used_usd,
quota_reset_day,
CAST(EXTRACT(EPOCH FROM quota_last_reset_at) AS BIGINT) AS quota_last_reset_at_unix_secs,
CAST(EXTRACT(EPOCH FROM quota_expires_at) AS BIGINT) AS quota_expires_at_unix_secs,
is_active
FROM providers
WHERE id = $1
LIMIT 1
"#;
use aether_data_query::SqlDialect;
const RESET_DUE_SQL: &str = r#"
UPDATE providers
@@ -54,8 +40,11 @@ impl ProviderQuotaReadRepository for SqlxProviderQuotaRepository {
&self,
provider_id: &str,
) -> Result<Option<StoredProviderQuotaSnapshot>, DataLayerError> {
let row = sqlx::query(FIND_BY_PROVIDER_ID_SQL)
.bind(provider_id)
let mut statement = quota_snapshot_select().statement::<Postgres>(SqlDialect::Postgres);
statement.where_eq("id", provider_id.to_string()).limit(1);
let row = statement
.finish()
.build()
.fetch_optional(&self.pool)
.await
.map_postgres_err()?;
@@ -69,13 +58,13 @@ impl ProviderQuotaReadRepository for SqlxProviderQuotaRepository {
if provider_ids.is_empty() {
return Ok(Vec::new());
}
let mut builder = QueryBuilder::<Postgres>::new(
"SELECT id AS provider_id, CAST(billing_type AS TEXT) AS billing_type, CAST(monthly_quota_usd AS DOUBLE PRECISION) AS monthly_quota_usd, CAST(COALESCE(monthly_used_usd, 0) AS DOUBLE PRECISION) AS monthly_used_usd, quota_reset_day, CAST(EXTRACT(EPOCH FROM quota_last_reset_at) AS BIGINT) AS quota_last_reset_at_unix_secs, CAST(EXTRACT(EPOCH FROM quota_expires_at) AS BIGINT) AS quota_expires_at_unix_secs, is_active FROM providers",
);
let mut where_clause = WhereClause::new();
push_in(&mut builder, &mut where_clause, "id", provider_ids);
builder.push(" ORDER BY id ASC");
builder
let mut statement = quota_snapshot_select().statement::<Postgres>(SqlDialect::Postgres);
statement
.where_in("id", provider_ids)
.order_by_sql("id ASC");
statement
.finish()
.build()
.fetch_all(&self.pool)
.await

View File

@@ -1,26 +1,14 @@
use async_trait::async_trait;
use sqlx::{sqlite::SqliteRow, QueryBuilder, Row, Sqlite};
use sqlx::{sqlite::SqliteRow, Row, Sqlite};
use super::{
ProviderQuotaReadRepository, ProviderQuotaWriteRepository, StoredProviderQuotaSnapshot,
quota_snapshot_select, ProviderQuotaReadRepository, ProviderQuotaWriteRepository,
StoredProviderQuotaSnapshot,
};
use crate::driver::sqlite::{sqlite_optional_real, sqlite_real, SqlitePool};
use crate::error::SqlResultExt;
use crate::DataLayerError;
use aether_data_query::{push_in, WhereClause};
const QUOTA_COLUMNS: &str = r#"
SELECT
id AS provider_id,
billing_type,
CAST(monthly_quota_usd AS REAL) AS monthly_quota_usd,
CAST(COALESCE(monthly_used_usd, 0) AS REAL) AS monthly_used_usd,
quota_reset_day,
quota_last_reset_at AS quota_last_reset_at_unix_secs,
quota_expires_at AS quota_expires_at_unix_secs,
is_active
FROM providers
"#;
use aether_data_query::SqlDialect;
#[derive(Debug, Clone)]
pub struct SqliteProviderQuotaRepository {
@@ -39,8 +27,11 @@ impl ProviderQuotaReadRepository for SqliteProviderQuotaRepository {
&self,
provider_id: &str,
) -> Result<Option<StoredProviderQuotaSnapshot>, DataLayerError> {
let row = sqlx::query(&format!("{QUOTA_COLUMNS} WHERE id = ? LIMIT 1"))
.bind(provider_id)
let mut statement = quota_snapshot_select().statement::<Sqlite>(SqlDialect::Sqlite);
statement.where_eq("id", provider_id.to_string()).limit(1);
let row = statement
.finish()
.build()
.fetch_optional(&self.pool)
.await
.map_sql_err()?;
@@ -55,11 +46,16 @@ impl ProviderQuotaReadRepository for SqliteProviderQuotaRepository {
return Ok(Vec::new());
}
let mut builder = QueryBuilder::<Sqlite>::new(QUOTA_COLUMNS);
let mut where_clause = WhereClause::new();
push_in(&mut builder, &mut where_clause, "id", provider_ids);
builder.push(" ORDER BY id ASC");
let rows = builder.build().fetch_all(&self.pool).await.map_sql_err()?;
let mut statement = quota_snapshot_select().statement::<Sqlite>(SqlDialect::Sqlite);
statement
.where_in("id", provider_ids)
.order_by_sql("id ASC");
let rows = statement
.finish()
.build()
.fetch_all(&self.pool)
.await
.map_sql_err()?;
rows.iter().map(map_row).collect()
}
}