use std::collections::HashMap;
use sea_orm::sea_query::{ColumnType, Expr, ExprTrait, Order, UnionType, Value};
use sea_orm::{
ActiveModelTrait, ColumnTrait, Condition, ConnectionTrait, DatabaseConnection, EntityTrait,
IntoActiveModel, Iterable, ModelTrait, Paginator, PaginatorTrait, PrimaryKeyToColumn,
QueryFilter, QueryOrder, QuerySelect, QueryTrait, Select, SelectModel, TryGetable,
};
use crate::db::DbError;
#[derive(Debug, Clone)]
pub struct QuerySet<E: EntityTrait> {
select: Select<E>,
orderings: Vec<(sea_orm::sea_query::Expr, Order)>,
}
impl<E: EntityTrait> Default for QuerySet<E> {
fn default() -> Self {
Self::new()
}
}
impl<E: EntityTrait> QuerySet<E> {
fn with_state(select: Select<E>, orderings: Vec<(sea_orm::sea_query::Expr, Order)>) -> Self {
Self { select, orderings }
}
fn reverse_order(order: Order) -> Order {
match order {
Order::Asc => Order::Desc,
Order::Desc => Order::Asc,
other => other,
}
}
fn primary_key_column() -> Result<E::Column, DbError> {
let mut primary_keys = E::PrimaryKey::iter().map(|key| key.into_column());
match (primary_keys.next(), primary_keys.next()) {
(Some(column), None) => Ok(column),
_ => Err(DbError::Connection(
"queryset helpers currently support only single-column primary keys".to_owned(),
)),
}
}
fn select_primary_keys(
select: Select<E>,
) -> Result<sea_orm::sea_query::SelectStatement, DbError> {
let pk = Self::primary_key_column()?;
Ok(select.select_only().column(pk).into_query())
}
async fn fetch_optional<T>(
query: sea_orm::sea_query::SelectStatement,
alias: &str,
db: &DatabaseConnection,
) -> Result<Option<T>, DbError>
where
T: TryGetable,
{
let row = match db.query_one(&query).await? {
Some(row) => row,
None => return Ok(None),
};
row.try_get::<Option<T>>("", alias).map_err(Into::into)
}
async fn fetch_value(
query: sea_orm::sea_query::SelectStatement,
alias: &str,
column_type: &ColumnType,
db: &DatabaseConnection,
) -> Result<Option<Value>, DbError> {
let row = match db.query_one(&query).await? {
Some(row) => row,
None => return Ok(None),
};
match column_type {
ColumnType::Boolean => row
.try_get::<Option<bool>>("", alias)
.map(|value| value.map(|v| Value::Bool(Some(v))))
.map_err(Into::into),
ColumnType::TinyInteger => row
.try_get::<Option<i8>>("", alias)
.map(|value| value.map(|v| Value::TinyInt(Some(v))))
.map_err(Into::into),
ColumnType::SmallInteger => row
.try_get::<Option<i16>>("", alias)
.map(|value| value.map(|v| Value::SmallInt(Some(v))))
.map_err(Into::into),
ColumnType::Integer => row
.try_get::<Option<i32>>("", alias)
.map(|value| value.map(|v| Value::Int(Some(v))))
.map_err(Into::into),
ColumnType::BigInteger => row
.try_get::<Option<i64>>("", alias)
.map(|value| value.map(|v| Value::BigInt(Some(v))))
.map_err(Into::into),
ColumnType::TinyUnsigned => row
.try_get::<Option<u8>>("", alias)
.map(|value| value.map(|v| Value::TinyUnsigned(Some(v))))
.map_err(Into::into),
ColumnType::SmallUnsigned => row
.try_get::<Option<u16>>("", alias)
.map(|value| value.map(|v| Value::SmallUnsigned(Some(v))))
.map_err(Into::into),
ColumnType::Unsigned => row
.try_get::<Option<u32>>("", alias)
.map(|value| value.map(|v| Value::Unsigned(Some(v))))
.map_err(Into::into),
ColumnType::BigUnsigned => row
.try_get::<Option<u64>>("", alias)
.map(|value| value.map(|v| Value::BigUnsigned(Some(v))))
.map_err(Into::into),
ColumnType::Float => row
.try_get::<Option<f32>>("", alias)
.map(|value| value.map(|v| Value::Float(Some(v))))
.map_err(Into::into),
ColumnType::Double | ColumnType::Decimal(_) | ColumnType::Money(_) => row
.try_get::<Option<f64>>("", alias)
.map(|value| value.map(|v| Value::Double(Some(v))))
.map_err(Into::into),
ColumnType::Char(_) => row
.try_get::<Option<String>>("", alias)
.map(|value| {
value
.and_then(|v| v.chars().next())
.map(|v| Value::Char(Some(v)))
})
.map_err(Into::into),
ColumnType::String(_)
| ColumnType::Text
| ColumnType::Uuid
| ColumnType::Custom(_)
| ColumnType::Enum { .. } => row
.try_get::<Option<String>>("", alias)
.map(|value| value.map(|v| Value::String(Some(v))))
.map_err(Into::into),
_ => Err(DbError::Connection(format!(
"aggregate min/max is unsupported for column type {column_type:?}"
))),
}
}
pub fn new() -> Self {
Self::with_state(E::find(), Vec::new())
}
pub fn from_select(select: Select<E>) -> Self {
Self::with_state(select, Vec::new())
}
pub fn filter(self, condition: Condition) -> Self {
Self::with_state(self.select.filter(condition), self.orderings)
}
pub fn exclude(self, condition: Condition) -> Self {
Self::with_state(self.select.filter(condition.not()), self.orderings)
}
pub fn order_by<C: ColumnTrait>(mut self, col: C, asc: bool) -> Self {
let order = if asc { Order::Asc } else { Order::Desc };
let expr = Expr::col((E::default(), col));
self.select = self.select.order_by(expr.clone(), order.clone());
self.orderings.push((expr, order));
self
}
pub fn distinct(self) -> Self {
Self::with_state(self.select.distinct(), self.orderings)
}
pub fn select_for_update(self) -> Self {
Self::with_state(self.select.lock_exclusive(), self.orderings)
}
pub fn reverse(mut self) -> Self {
if self.orderings.is_empty() {
return self;
}
self.orderings = self
.orderings
.into_iter()
.map(|(expr, order)| (expr, Self::reverse_order(order)))
.collect();
let query = QueryTrait::query(&mut self.select);
query.clear_order_by();
for (expr, order) in &self.orderings {
query.order_by_expr(expr.clone(), order.clone());
}
self
}
pub fn values<C: ColumnTrait + Copy>(self, columns: &[C]) -> Select<E> {
columns
.iter()
.fold(self.select.select_only(), |select, column| {
select.column(*column)
})
}
pub fn values_list<C: ColumnTrait + Copy>(self, columns: &[C]) -> Select<E> {
self.values(columns)
}
pub fn limit(self, n: u64) -> Self {
Self::with_state(self.select.limit(n), self.orderings)
}
pub fn offset(self, n: u64) -> Self {
Self::with_state(self.select.offset(n), self.orderings)
}
pub(crate) async fn all_async(self, db: &DatabaseConnection) -> Result<Vec<E::Model>, DbError> {
self.select.all(db).await.map_err(Into::into)
}
pub fn all(self, db: &DatabaseConnection) -> Result<Vec<E::Model>, DbError> {
crate::runtime::block_on(self.all_async(db))
}
pub(crate) async fn first_async(
self,
db: &DatabaseConnection,
) -> Result<Option<E::Model>, DbError> {
self.select.one(db).await.map_err(Into::into)
}
pub fn first(self, db: &DatabaseConnection) -> Result<Option<E::Model>, DbError> {
crate::runtime::block_on(self.first_async(db))
}
pub(crate) async fn get_async(self, db: &DatabaseConnection) -> Result<E::Model, DbError> {
let model = E::default().table_name();
let models = self.select.limit(2).all(db).await?;
match models.len() {
0 => Err(DbError::DoesNotExist {
model,
lookup: "provided query criteria".to_owned(),
}),
1 => Ok(models
.into_iter()
.next()
.expect("query limited to two rows should contain one model")),
_ => Err(DbError::MultipleObjectsReturned { model }),
}
}
pub fn get(self, db: &DatabaseConnection) -> Result<E::Model, DbError> {
crate::runtime::block_on(self.get_async(db))
}
pub(crate) async fn get_or_create_async<F>(
self,
create_model: F,
db: &DatabaseConnection,
) -> Result<(E::Model, bool), DbError>
where
F: FnOnce() -> E::ActiveModel,
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
E::Model: IntoActiveModel<E::ActiveModel>,
{
match self.clone().get_async(db).await {
Ok(model) => Ok((model, false)),
Err(DbError::DoesNotExist { .. }) => match create_model().insert(db).await {
Ok(model) => Ok((model, true)),
Err(insert_err) => match self.get_async(db).await {
Ok(model) => Ok((model, false)),
Err(DbError::DoesNotExist { .. }) => Err(insert_err.into()),
Err(err) => Err(err),
},
},
Err(err) => Err(err),
}
}
pub fn get_or_create<F>(
self,
create_model: F,
db: &DatabaseConnection,
) -> Result<(E::Model, bool), DbError>
where
F: FnOnce() -> E::ActiveModel,
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
E::Model: IntoActiveModel<E::ActiveModel>,
{
crate::runtime::block_on(self.get_or_create_async(create_model, db))
}
pub(crate) async fn update_or_create_async<F, U>(
self,
create_model: F,
update_model: U,
db: &DatabaseConnection,
) -> Result<(E::Model, bool), DbError>
where
F: FnOnce() -> E::ActiveModel,
U: FnOnce(&mut E::ActiveModel),
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
E::Model: IntoActiveModel<E::ActiveModel>,
{
match self.clone().get_async(db).await {
Ok(model) => {
let mut active_model = model.into_active_model();
update_model(&mut active_model);
let model = active_model.update(db).await?;
Ok((model, false))
}
Err(DbError::DoesNotExist { .. }) => self.get_or_create_async(create_model, db).await,
Err(err) => Err(err),
}
}
pub fn update_or_create<F, U>(
self,
create_model: F,
update_model: U,
db: &DatabaseConnection,
) -> Result<(E::Model, bool), DbError>
where
F: FnOnce() -> E::ActiveModel,
U: FnOnce(&mut E::ActiveModel),
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
E::Model: IntoActiveModel<E::ActiveModel>,
{
crate::runtime::block_on(self.update_or_create_async(create_model, update_model, db))
}
pub(crate) async fn count_async(self, db: &DatabaseConnection) -> Result<u64, DbError>
where
E::Model: Send + Sync,
{
self.select
.paginate(db, 1)
.num_items()
.await
.map_err(Into::into)
}
pub fn count(self, db: &DatabaseConnection) -> Result<u64, DbError>
where
E::Model: Send + Sync,
{
crate::runtime::block_on(self.count_async(db))
}
pub(crate) async fn aggregate_count_async(self, db: &DatabaseConnection) -> Result<u64, DbError>
where
E::Model: Send + Sync,
{
self.count_async(db).await
}
pub fn aggregate_count(self, db: &DatabaseConnection) -> Result<u64, DbError>
where
E::Model: Send + Sync,
{
crate::runtime::block_on(self.aggregate_count_async(db))
}
pub(crate) async fn aggregate_sum_async<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<f64>, DbError> {
let query = self
.select
.select_only()
.expr_as(Expr::col((E::default(), col)).sum(), "aggregate")
.into_query();
Self::fetch_optional::<f64>(query, "aggregate", db).await
}
pub fn aggregate_sum<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<f64>, DbError> {
crate::runtime::block_on(self.aggregate_sum_async(col, db))
}
pub(crate) async fn aggregate_avg_async<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<f64>, DbError> {
let query = self
.select
.select_only()
.expr_as(Expr::col((E::default(), col)).avg(), "aggregate")
.into_query();
Self::fetch_optional::<f64>(query, "aggregate", db).await
}
pub fn aggregate_avg<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<f64>, DbError> {
crate::runtime::block_on(self.aggregate_avg_async(col, db))
}
pub(crate) async fn aggregate_min_async<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<Value>, DbError> {
let column_def = col.def();
let query = self
.select
.select_only()
.expr_as(Expr::col((E::default(), col)).min(), "aggregate")
.into_query();
Self::fetch_value(query, "aggregate", column_def.get_column_type(), db).await
}
pub fn aggregate_min<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<Value>, DbError> {
crate::runtime::block_on(self.aggregate_min_async(col, db))
}
pub(crate) async fn aggregate_max_async<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<Value>, DbError> {
let column_def = col.def();
let query = self
.select
.select_only()
.expr_as(Expr::col((E::default(), col)).max(), "aggregate")
.into_query();
Self::fetch_value(query, "aggregate", column_def.get_column_type(), db).await
}
pub fn aggregate_max<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<Value>, DbError> {
crate::runtime::block_on(self.aggregate_max_async(col, db))
}
pub(crate) async fn exists_async(self, db: &DatabaseConnection) -> Result<bool, DbError> {
self.select
.limit(1)
.one(db)
.await
.map(|model| model.is_some())
.map_err(Into::into)
}
pub fn exists(self, db: &DatabaseConnection) -> Result<bool, DbError> {
crate::runtime::block_on(self.exists_async(db))
}
pub(crate) async fn update_async<C>(
self,
col: C,
value: impl Into<Value>,
db: &DatabaseConnection,
) -> Result<u64, DbError>
where
C: ColumnTrait,
{
let pk = Self::primary_key_column()?;
let subquery = Self::select_primary_keys(self.select)?;
Ok(E::update_many()
.col_expr(col, Expr::val(value.into()))
.filter(pk.in_subquery(subquery))
.exec(db)
.await?
.rows_affected)
}
pub fn update<C>(
self,
col: C,
value: impl Into<Value>,
db: &DatabaseConnection,
) -> Result<u64, DbError>
where
C: ColumnTrait,
{
crate::runtime::block_on(self.update_async(col, value, db))
}
pub(crate) async fn delete_async(self, db: &DatabaseConnection) -> Result<u64, DbError> {
let pk = Self::primary_key_column()?;
let subquery = Self::select_primary_keys(self.select)?;
Ok(E::delete_many()
.filter(pk.in_subquery(subquery))
.exec(db)
.await?
.rows_affected)
}
pub fn delete(self, db: &DatabaseConnection) -> Result<u64, DbError> {
crate::runtime::block_on(self.delete_async(db))
}
pub(crate) async fn earliest_async<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<E::Model>, DbError> {
self.order_by(col, true).limit(1).first_async(db).await
}
pub fn earliest<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<E::Model>, DbError> {
crate::runtime::block_on(self.earliest_async(col, db))
}
pub(crate) async fn latest_async<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<E::Model>, DbError> {
self.order_by(col, false).limit(1).first_async(db).await
}
pub fn latest<C: ColumnTrait>(
self,
col: C,
db: &DatabaseConnection,
) -> Result<Option<E::Model>, DbError> {
crate::runtime::block_on(self.latest_async(col, db))
}
pub(crate) async fn in_bulk_async(
self,
ids: &[i32],
db: &DatabaseConnection,
) -> Result<HashMap<i32, E::Model>, DbError> {
if ids.is_empty() {
return Ok(HashMap::new());
}
let pk = Self::primary_key_column()?;
let models = self
.filter(Condition::all().add(pk.is_in(ids.iter().copied())))
.all_async(db)
.await?;
Ok(models
.into_iter()
.filter_map(|model| match model.get(pk) {
Value::Int(Some(id)) => Some((id, model)),
Value::BigInt(Some(id)) => i32::try_from(id).ok().map(|id| (id, model)),
Value::TinyInt(Some(id)) => Some((i32::from(id), model)),
Value::SmallInt(Some(id)) => Some((i32::from(id), model)),
_ => None,
})
.collect())
}
pub fn in_bulk(
self,
ids: &[i32],
db: &DatabaseConnection,
) -> Result<HashMap<i32, E::Model>, DbError> {
crate::runtime::block_on(self.in_bulk_async(ids, db))
}
pub fn iterator<'db>(
self,
chunk_size: u64,
db: &'db DatabaseConnection,
) -> Paginator<'db, DatabaseConnection, SelectModel<E::Model>>
where
E::Model: Send + Sync + 'db,
{
self.select.paginate(db, chunk_size)
}
pub fn none() -> Self {
Self::with_state(
E::find().filter(Condition::all().add(Expr::val(1).eq(0))),
Vec::new(),
)
}
pub fn union(mut self, other: Self) -> Self {
QueryTrait::query(&mut self.select).union(UnionType::Distinct, other.select.into_query());
self.orderings.clear();
self
}
pub fn intersection(mut self, other: Self) -> Self {
QueryTrait::query(&mut self.select).union(UnionType::Intersect, other.select.into_query());
self.orderings.clear();
self
}
pub fn difference(mut self, other: Self) -> Self {
QueryTrait::query(&mut self.select).union(UnionType::Except, other.select.into_query());
self.orderings.clear();
self
}
pub(crate) async fn explain_async(self, db: &DatabaseConnection) -> Result<String, DbError> {
let backend = db.get_database_backend();
let sql = format!("EXPLAIN QUERY PLAN {}", self.select.build(backend));
let stmt = sea_orm::Statement::from_string(backend, sql);
let rows = db.query_all_raw(stmt).await?;
let mut lines = Vec::with_capacity(rows.len());
for row in rows {
if let Ok(detail) = row.try_get::<String>("", "detail") {
lines.push(detail);
} else {
lines.push(format!("row columns: {:?}", row.column_names()));
}
}
Ok(lines.join("\n"))
}
pub fn explain(self, db: &DatabaseConnection) -> Result<String, DbError> {
crate::runtime::block_on(self.explain_async(db))
}
pub fn to_sql(self) -> String {
self.select.build(sea_orm::DbBackend::Sqlite).to_string()
}
pub(crate) async fn bulk_create_async(
models: Vec<E::ActiveModel>,
db: &DatabaseConnection,
) -> Result<Vec<E::Model>, DbError>
where
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
E::Model: sea_orm::IntoActiveModel<E::ActiveModel>,
{
if models.is_empty() {
return Ok(Vec::new());
}
E::insert_many(models)
.exec_with_returning(db)
.await
.map_err(Into::into)
}
pub fn bulk_create(
models: Vec<E::ActiveModel>,
db: &DatabaseConnection,
) -> Result<Vec<E::Model>, DbError>
where
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
E::Model: sea_orm::IntoActiveModel<E::ActiveModel>,
{
crate::runtime::block_on(Self::bulk_create_async(models, db))
}
pub(crate) async fn bulk_update_async(
models: Vec<E::ActiveModel>,
db: &DatabaseConnection,
) -> Result<u64, DbError>
where
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
E::Model: sea_orm::IntoActiveModel<E::ActiveModel>,
{
let mut updated = 0_u64;
for model in models {
E::update(model).exec(db).await?;
updated += 1;
}
Ok(updated)
}
pub fn bulk_update(models: Vec<E::ActiveModel>, db: &DatabaseConnection) -> Result<u64, DbError>
where
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
E::Model: sea_orm::IntoActiveModel<E::ActiveModel>,
{
crate::runtime::block_on(Self::bulk_update_async(models, db))
}
pub fn into_select(self) -> Select<E> {
self.select
}
}
#[cfg(test)]
mod tests {
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveValue, Condition, ConnectionTrait, DbBackend, QueryTrait};
use super::QuerySet;
use crate::db::{DbError, connect_test};
mod widget {
use sea_orm::entity::prelude::*;
#[sea_orm::model]
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "widgets")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
}
impl ActiveModelBehavior for ActiveModel {}
}
fn setup_db() -> DatabaseConnection {
let db = connect_test().expect("connect_test should create an in-memory database");
crate::runtime::block_on(db.execute_unprepared(
"CREATE TABLE widgets (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL)",
))
.expect("should create widgets table");
crate::runtime::block_on(db.execute_unprepared(
"INSERT INTO widgets (id, name) VALUES (1, 'alpha'), (2, 'Beta'), (3, 'gamma')",
))
.expect("should seed widgets table");
db
}
#[test]
fn queryset_chain_methods_build_expected_sql() {
let sql = QuerySet::<widget::Entity>::new()
.filter(Condition::all().add(widget::Column::Id.gt(1)))
.exclude(Condition::all().add(widget::Column::Name.contains("mm")))
.order_by(widget::Column::Id, false)
.limit(5)
.offset(2)
.into_select()
.build(DbBackend::Sqlite)
.to_string();
assert!(
sql.contains("WHERE \"widgets\".\"id\" > 1"),
"unexpected SQL: {sql}"
);
assert!(
sql.contains("NOT"),
"exclude should negate the condition: {sql}"
);
assert!(
sql.contains("ORDER BY \"widgets\".\"id\" DESC"),
"unexpected SQL: {sql}"
);
assert!(sql.contains("LIMIT 5"), "unexpected SQL: {sql}");
assert!(sql.contains("OFFSET 2"), "unexpected SQL: {sql}");
}
#[test]
fn none_builds_always_false_condition() {
let sql = QuerySet::<widget::Entity>::none()
.into_select()
.build(DbBackend::Sqlite)
.to_string();
assert!(
sql.contains("1 = 0"),
"none queryset should be empty: {sql}"
);
}
#[test]
fn distinct_and_to_sql_build_expected_sql() {
let sql = QuerySet::<widget::Entity>::new().distinct().to_sql();
assert!(sql.contains("SELECT DISTINCT"), "unexpected SQL: {sql}");
assert!(sql.contains("FROM \"widgets\""), "unexpected SQL: {sql}");
}
#[test]
fn terminal_methods_execute_against_sqlite() {
let db = setup_db();
let all = QuerySet::<widget::Entity>::new()
.order_by(widget::Column::Id, true)
.all(&db)
.expect("all should return every row");
assert_eq!(all.len(), 3);
assert_eq!(all[0].id, 1);
assert_eq!(all[2].name, "gamma");
let first = QuerySet::<widget::Entity>::new()
.order_by(widget::Column::Id, false)
.first(&db)
.expect("first should succeed")
.expect("first should return a row");
assert_eq!(first.id, 3);
let found = QuerySet::<widget::Entity>::new()
.filter(Condition::all().add(widget::Column::Id.eq(2)))
.get(&db)
.expect("get should return one matching row");
assert_eq!(found.name, "Beta");
let missing = QuerySet::<widget::Entity>::new()
.filter(Condition::all().add(widget::Column::Id.eq(99)))
.get(&db)
.expect_err("missing row should raise DoesNotExist");
assert!(matches!(
missing,
DbError::DoesNotExist {
model: "widgets",
..
}
));
let multiple = QuerySet::<widget::Entity>::new()
.filter(Condition::all().add(widget::Column::Name.contains("a")))
.get(&db)
.expect_err("multiple rows should raise MultipleObjectsReturned");
assert!(matches!(
multiple,
DbError::MultipleObjectsReturned { model: "widgets" }
));
let count = QuerySet::<widget::Entity>::new()
.filter(Condition::all().add(widget::Column::Id.gte(2)))
.count(&db)
.expect("count should succeed");
assert_eq!(count, 2);
let exists = QuerySet::<widget::Entity>::new()
.filter(Condition::all().add(widget::Column::Name.eq("alpha")))
.exists(&db)
.expect("exists should succeed");
assert!(exists);
let none_exists = QuerySet::<widget::Entity>::none()
.exists(&db)
.expect("exists on empty queryset should succeed");
assert!(!none_exists);
}
#[test]
fn queryset_bulk_operations_execute_against_sqlite() {
let db = setup_db();
let updated = QuerySet::<widget::Entity>::new()
.filter(Condition::all().add(widget::Column::Id.eq(1)))
.update(widget::Column::Name, "ALPHA", &db)
.expect("update should succeed");
assert_eq!(updated, 1);
let refreshed = QuerySet::<widget::Entity>::new()
.filter(Condition::all().add(widget::Column::Id.eq(1)))
.get(&db)
.expect("updated row should still exist");
assert_eq!(refreshed.name, "ALPHA");
let deleted = QuerySet::<widget::Entity>::new()
.filter(Condition::all().add(widget::Column::Id.gte(2)))
.delete(&db)
.expect("delete should succeed");
assert_eq!(deleted, 2);
let remaining = QuerySet::<widget::Entity>::new()
.count(&db)
.expect("count should succeed after delete");
assert_eq!(remaining, 1);
}
#[test]
fn queryset_convenience_methods_handle_empty_and_non_empty_results() {
let db = setup_db();
let earliest = QuerySet::<widget::Entity>::new()
.earliest(widget::Column::Id, &db)
.expect("earliest should succeed")
.expect("earliest should return a row");
assert_eq!(earliest.id, 1);
let latest = QuerySet::<widget::Entity>::new()
.latest(widget::Column::Id, &db)
.expect("latest should succeed")
.expect("latest should return a row");
assert_eq!(latest.id, 3);
let empty_earliest = QuerySet::<widget::Entity>::none()
.earliest(widget::Column::Id, &db)
.expect("earliest on empty queryset should succeed");
assert!(empty_earliest.is_none());
let empty_latest = QuerySet::<widget::Entity>::none()
.latest(widget::Column::Id, &db)
.expect("latest on empty queryset should succeed");
assert!(empty_latest.is_none());
}
#[test]
fn queryset_bulk_create_and_empty_queryset_no_ops_work() {
let db = setup_db();
let inserted = QuerySet::<widget::Entity>::bulk_create(
vec![
widget::ActiveModel {
id: ActiveValue::set(4),
name: ActiveValue::set("delta".to_owned()),
},
widget::ActiveModel {
id: ActiveValue::set(5),
name: ActiveValue::set("epsilon".to_owned()),
},
],
&db,
)
.expect("bulk_create should succeed");
assert_eq!(inserted.len(), 2);
assert_eq!(inserted[0].id, 4);
assert_eq!(inserted[1].name, "epsilon");
let empty_created = QuerySet::<widget::Entity>::bulk_create(Vec::new(), &db)
.expect("bulk_create should allow empty input");
assert!(empty_created.is_empty());
let empty_updated = QuerySet::<widget::Entity>::none()
.update(widget::Column::Name, "unused", &db)
.expect("update on none queryset should succeed");
assert_eq!(empty_updated, 0);
let empty_deleted = QuerySet::<widget::Entity>::none()
.delete(&db)
.expect("delete on none queryset should succeed");
assert_eq!(empty_deleted, 0);
}
}