use mongreldb_core::memtable::Value as CoreValue;
use mongreldb_core::schema::{ColumnFlags, TypeId};
use mongreldb_core::MongrelError as CoreError;
use mongreldb_kit::{
AggFunc, Aggregate, AggregateQuery, Column, ColumnType, Cte, Database, Delete, Direction, Expr,
ForeignKey, ForeignKeyAction, Index, Join, JoinKind, JoinQuery, KitError, Literal, Migration,
MigrationOp, OnConflict, OrderBy, Query, Row, Schema, Select, Table, Transaction,
UniqueConstraint, Update,
};
use serde_json::{json, Map, Value};
use std::path::PathBuf;
fn temp_dir() -> PathBuf {
tempfile::tempdir().unwrap().keep()
}
fn users_table() -> Table {
Table {
id: 1,
name: "users".into(),
columns: vec![
Column::new(1, "id", ColumnType::Int64),
Column::new(2, "email", ColumnType::Text),
Column {
nullable: true,
..Column::new(3, "name", ColumnType::Text)
},
],
primary_key: vec!["id".into()],
indexes: vec![Index {
name: "idx_email".into(),
columns: vec!["email".into()],
unique: true,
kind: Default::default(),
}],
foreign_keys: vec![],
unique_constraints: vec![UniqueConstraint {
name: "uq_email".into(),
columns: vec!["email".into()],
}],
check_constraints: vec![],
}
}
fn orders_table() -> Table {
let mut t = Table {
id: 2,
name: "orders".into(),
columns: vec![
Column::new(1, "id", ColumnType::Int64),
Column::new(2, "user_id", ColumnType::Int64),
Column::new(3, "total", ColumnType::Float64),
],
primary_key: vec!["id".into()],
indexes: vec![],
foreign_keys: vec![ForeignKey {
name: "fk_orders_user".into(),
columns: vec!["user_id".into()],
references_table: "users".into(),
references_columns: vec!["id".into()],
on_delete: ForeignKeyAction::Restrict,
}],
unique_constraints: vec![],
check_constraints: vec![],
};
t.columns[2].nullable = true;
t
}
fn items_table() -> Table {
Table {
id: 3,
name: "items".into(),
columns: vec![
Column::new(1, "id", ColumnType::Int64),
Column::new(2, "order_id", ColumnType::Int64),
Column::new(3, "sku", ColumnType::Text),
],
primary_key: vec!["id".into()],
indexes: vec![],
foreign_keys: vec![ForeignKey {
name: "fk_items_order".into(),
columns: vec!["order_id".into()],
references_table: "orders".into(),
references_columns: vec!["id".into()],
on_delete: ForeignKeyAction::Cascade,
}],
unique_constraints: vec![],
check_constraints: vec![],
}
}
fn make_schema() -> Schema {
Schema::new(vec![users_table(), orders_table(), items_table()]).unwrap()
}
fn insert_user(txn: &mut Transaction, id: i64, email: &str) -> Row {
let mut row = Map::new();
row.insert("id".into(), json!(id));
row.insert("email".into(), json!(email));
txn.insert("users", row).unwrap()
}
fn insert_named_user(txn: &mut Transaction, id: i64, email: &str, name: &str) -> Row {
let mut row = Map::new();
row.insert("id".into(), json!(id));
row.insert("email".into(), json!(email));
row.insert("name".into(), json!(name));
txn.insert("users", row).unwrap()
}
fn insert_order(txn: &mut Transaction, id: i64, user_id: i64, total: f64) {
let mut row = Map::new();
row.insert("id".into(), json!(id));
row.insert("user_id".into(), json!(user_id));
row.insert("total".into(), json!(total));
txn.insert("orders", row).unwrap();
}
fn col(name: &str) -> Expr {
Expr::Column(name.into())
}
fn select_all(table: &str, filter: Option<Expr>) -> Select {
Select {
table: table.into(),
columns: vec![],
filter,
order_by: vec![],
limit: None,
offset: None,
}
}
#[test]
fn count_star_native_delegation_matches_scan() {
fn count_star(txn: &Transaction, table: &str, filter: Option<Expr>) -> i64 {
let q = AggregateQuery {
table: table.into(),
filter,
group_by: vec![],
aggregates: vec![Aggregate {
func: AggFunc::Count,
column: None,
alias: "n".into(),
distinct: false,
}],
having: None,
};
let rows = txn.aggregate(&q).unwrap();
assert_eq!(rows.len(), 1);
rows[0].values.get("n").unwrap().as_i64().unwrap()
}
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "a@example.com");
insert_user(&mut txn, 2, "b@example.com");
insert_order(&mut txn, 1, 1, 10.0);
insert_order(&mut txn, 2, 1, 30.0);
insert_order(&mut txn, 3, 2, 5.0);
txn.commit().unwrap();
{
let txn = db.begin().unwrap();
assert_eq!(count_star(&txn, "orders", None), 3);
}
{
let txn = db.begin().unwrap();
let f = Expr::Eq(
Box::new(col("id")),
Box::new(Expr::Literal(Literal::Int(2))),
);
assert_eq!(count_star(&txn, "orders", Some(f)), 1);
}
{
let mut txn = db.begin().unwrap();
txn.execute(&Query::Delete(Delete {
table: "orders".into(),
filter: Some(Expr::Eq(
Box::new(col("id")),
Box::new(Expr::Literal(Literal::Int(3))),
)),
returning: vec![],
pk: None,
}))
.unwrap();
txn.commit().unwrap();
let txn = db.begin().unwrap();
assert_eq!(count_star(&txn, "orders", None), 2);
}
{
let mut txn = db.begin().unwrap();
insert_order(&mut txn, 4, 1, 7.0);
assert_eq!(count_star(&txn, "orders", None), 3);
txn.rollback();
}
}
#[test]
fn column_aggregate_native_delegation_matches_scan() {
fn agg1(txn: &Transaction, func: AggFunc, col: &str) -> Value {
let q = AggregateQuery {
table: "orders".into(),
filter: None,
group_by: vec![],
aggregates: vec![Aggregate {
func,
column: Some(col.into()),
alias: "v".into(),
distinct: false,
}],
having: None,
};
let rows = txn.aggregate(&q).unwrap();
assert_eq!(rows.len(), 1);
rows[0].values.get("v").cloned().unwrap()
}
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "a@example.com");
insert_order(&mut txn, 1, 1, 10.0);
insert_order(&mut txn, 2, 1, 30.0);
insert_order(&mut txn, 3, 1, 5.0);
{
let mut row = Map::new();
row.insert("id".into(), json!(4));
row.insert("user_id".into(), json!(1));
txn.insert("orders", row).unwrap();
}
txn.commit().unwrap();
let txn = db.begin().unwrap();
assert_eq!(agg1(&txn, AggFunc::Sum, "total"), json!(45.0));
assert_eq!(agg1(&txn, AggFunc::Min, "total"), json!(5.0));
assert_eq!(agg1(&txn, AggFunc::Max, "total"), json!(30.0));
assert_eq!(agg1(&txn, AggFunc::Avg, "total"), json!(15.0));
assert_eq!(agg1(&txn, AggFunc::Count, "total"), json!(3)); }
#[test]
fn count_distinct_matches_scan() {
fn cdistinct(txn: &Transaction, col: &str, filter: Option<Expr>) -> i64 {
let q = AggregateQuery {
table: "orders".into(),
filter,
group_by: vec![],
aggregates: vec![Aggregate {
func: AggFunc::Count,
column: Some(col.into()),
alias: "d".into(),
distinct: true,
}],
having: None,
};
txn.aggregate(&q).unwrap()[0]
.values
.get("d")
.unwrap()
.as_i64()
.unwrap()
}
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
for id in 1..=3i64 {
insert_user(&mut txn, id, &format!("u{id}@x.com"));
}
insert_order(&mut txn, 1, 1, 10.0);
insert_order(&mut txn, 2, 1, 20.0); insert_order(&mut txn, 3, 2, 30.0);
insert_order(&mut txn, 4, 2, 30.0); insert_order(&mut txn, 5, 3, 40.0);
txn.commit().unwrap();
let txn = db.begin().unwrap();
assert_eq!(cdistinct(&txn, "user_id", None), 3);
assert_eq!(cdistinct(&txn, "total", None), 4);
let f = Expr::Gte(
Box::new(col("user_id")),
Box::new(Expr::Literal(Literal::Int(2))),
);
assert_eq!(cdistinct(&txn, "user_id", Some(f)), 2);
}
#[test]
fn aggregates_group_by_and_having() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "a@example.com");
insert_user(&mut txn, 2, "b@example.com");
insert_order(&mut txn, 1, 1, 10.0);
insert_order(&mut txn, 2, 1, 30.0);
insert_order(&mut txn, 3, 2, 5.0);
txn.commit().unwrap();
let txn = db.begin().unwrap();
let q = AggregateQuery {
table: "orders".into(),
filter: None,
group_by: vec![],
aggregates: vec![
Aggregate {
func: AggFunc::Count,
column: None,
alias: "n".into(),
distinct: false,
},
Aggregate {
func: AggFunc::Sum,
column: Some("total".into()),
alias: "s".into(),
distinct: false,
},
Aggregate {
func: AggFunc::Min,
column: Some("total".into()),
alias: "mn".into(),
distinct: false,
},
Aggregate {
func: AggFunc::Max,
column: Some("total".into()),
alias: "mx".into(),
distinct: false,
},
Aggregate {
func: AggFunc::Avg,
column: Some("total".into()),
alias: "av".into(),
distinct: false,
},
],
having: None,
};
let rows = txn.aggregate(&q).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].values.get("n"), Some(&json!(3)));
assert_eq!(rows[0].values.get("s"), Some(&json!(45.0)));
assert_eq!(rows[0].values.get("mn"), Some(&json!(5.0)));
assert_eq!(rows[0].values.get("mx"), Some(&json!(30.0)));
assert_eq!(rows[0].values.get("av"), Some(&json!(15.0)));
let q = AggregateQuery {
table: "orders".into(),
filter: None,
group_by: vec!["user_id".into()],
aggregates: vec![
Aggregate {
func: AggFunc::Count,
column: None,
alias: "n".into(),
distinct: false,
},
Aggregate {
func: AggFunc::Sum,
column: Some("total".into()),
alias: "s".into(),
distinct: false,
},
],
having: Some(Expr::Gt(
Box::new(col("n")),
Box::new(Expr::Literal(Literal::Int(1))),
)),
};
let rows = txn.aggregate(&q).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].values.get("user_id"), Some(&json!(1)));
assert_eq!(rows[0].values.get("n"), Some(&json!(2)));
assert_eq!(rows[0].values.get("s"), Some(&json!(40.0)));
}
#[test]
fn joins_inner_left_cross() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "a@example.com");
insert_user(&mut txn, 2, "b@example.com");
insert_user(&mut txn, 3, "c@example.com");
insert_order(&mut txn, 1, 1, 10.0);
insert_order(&mut txn, 2, 1, 30.0);
txn.commit().unwrap();
let txn = db.begin().unwrap();
let on = Some(Expr::Eq(Box::new(col("u.id")), Box::new(col("o.user_id"))));
let inner = JoinQuery {
table: "users".into(),
alias: Some("u".into()),
joins: vec![Join {
kind: JoinKind::Inner,
table: "orders".into(),
alias: Some("o".into()),
on: on.clone(),
}],
filter: None,
order_by: vec![],
limit: None,
offset: None,
};
let rows = txn.join(&inner).unwrap();
assert_eq!(rows.len(), 2);
for r in &rows {
assert_eq!(r["o"]["user_id"], json!(1));
}
let left = JoinQuery {
joins: vec![Join {
kind: JoinKind::Left,
table: "orders".into(),
alias: Some("o".into()),
on: on.clone(),
}],
..inner.clone()
};
let rows = txn.join(&left).unwrap();
assert_eq!(rows.len(), 4);
let nulls = rows.iter().filter(|r| r["o"].is_null()).count();
assert_eq!(nulls, 2);
let cross = JoinQuery {
joins: vec![Join {
kind: JoinKind::Cross,
table: "orders".into(),
alias: Some("o".into()),
on: None,
}],
..inner
};
let rows = txn.join(&cross).unwrap();
assert_eq!(rows.len(), 6); }
#[test]
fn select_distinct_dedupes_projection() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_named_user(&mut txn, 1, "a@example.com", "Dup");
insert_named_user(&mut txn, 2, "b@example.com", "Dup");
insert_named_user(&mut txn, 3, "c@example.com", "Other");
txn.commit().unwrap();
let txn = db.begin().unwrap();
let q = Query::Select(Select {
table: "users".into(),
columns: vec![col("name")],
filter: None,
order_by: vec![],
limit: None,
offset: None,
});
let rows = txn.select_distinct(&q).unwrap();
let names: Vec<&Value> = rows.iter().map(|r| r.values.get("name").unwrap()).collect();
assert_eq!(names, vec![&json!("Dup"), &json!("Other")]);
assert!(rows.iter().all(|r| r.values.len() == 1));
}
#[test]
fn like_contains_and_not_in() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
insert_user(&mut txn, 2, "bob@test.com");
insert_user(&mut txn, 3, "carol@example.com");
txn.commit().unwrap();
let txn = db.begin().unwrap();
let like = txn
.select(&Query::Select(select_all(
"users",
Some(Expr::Like(Box::new(col("email")), "%@example.com".into())),
)))
.unwrap();
assert_eq!(like.len(), 2);
let contains = txn
.select(&Query::Select(select_all(
"users",
Some(Expr::Contains(Box::new(col("email")), "bob".into())),
)))
.unwrap();
assert_eq!(contains.len(), 1);
assert_eq!(contains[0].values.get("id"), Some(&json!(2)));
let not_in = txn
.select(&Query::Select(select_all(
"users",
Some(Expr::NotIn(
Box::new(col("id")),
vec![Literal::Int(1), Literal::Int(2)],
)),
)))
.unwrap();
assert_eq!(not_in.len(), 1);
assert_eq!(not_in[0].values.get("id"), Some(&json!(3)));
}
#[test]
fn exists_and_in_subquery() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "a@example.com");
insert_user(&mut txn, 2, "b@example.com");
insert_user(&mut txn, 3, "c@example.com");
insert_order(&mut txn, 1, 1, 150.0);
insert_order(&mut txn, 2, 2, 50.0);
txn.commit().unwrap();
let txn = db.begin().unwrap();
let sub = Select {
table: "orders".into(),
columns: vec![col("user_id")],
filter: Some(Expr::Gt(
Box::new(col("total")),
Box::new(Expr::Literal(Literal::Float(100.0))),
)),
order_by: vec![],
limit: None,
offset: None,
};
let rows = txn
.select(&Query::Select(select_all(
"users",
Some(Expr::InSubquery(Box::new(col("id")), Box::new(sub.clone()))),
)))
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].values.get("id"), Some(&json!(1)));
let rows = txn
.select(&Query::Select(select_all(
"users",
Some(Expr::Exists(Box::new(sub))),
)))
.unwrap();
assert_eq!(rows.len(), 3);
let none = Select {
table: "orders".into(),
columns: vec![],
filter: Some(Expr::Gt(
Box::new(col("total")),
Box::new(Expr::Literal(Literal::Float(1000.0))),
)),
order_by: vec![],
limit: None,
offset: None,
};
let rows = txn
.select(&Query::Select(select_all(
"users",
Some(Expr::NotExists(Box::new(none))),
)))
.unwrap();
assert_eq!(rows.len(), 3);
}
#[test]
fn cte_materializes_and_reads() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "a@example.com");
insert_user(&mut txn, 2, "b@example.com");
insert_order(&mut txn, 1, 1, 150.0);
insert_order(&mut txn, 2, 2, 50.0);
txn.commit().unwrap();
let txn = db.begin().unwrap();
let cte = Cte {
name: "big_orders".into(),
query: Box::new(Select {
table: "orders".into(),
columns: vec![],
filter: Some(Expr::Gt(
Box::new(col("total")),
Box::new(Expr::Literal(Literal::Float(100.0))),
)),
order_by: vec![OrderBy {
expr: col("id"),
direction: Direction::Asc,
}],
limit: None,
offset: None,
}),
};
let body = select_all("big_orders", None);
let rows = txn.select_with(&[cte], &body).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].values.get("user_id"), Some(&json!(1)));
assert_eq!(rows[0].values.get("total"), Some(&json!(150.0)));
}
#[test]
fn create_open_and_crud() {
let dir = temp_dir();
let schema = make_schema();
{
let db = Database::create(&dir, schema.clone()).unwrap();
assert!(db.table("users").is_some());
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
let row = txn.get_by_pk("users", &json!(1)).unwrap().unwrap();
assert_eq!(row.values.get("email"), Some(&json!("alice@example.com")));
let mut patch = Map::new();
patch.insert("name".into(), json!("Alice"));
txn.update("users", &json!(1), patch).unwrap();
txn.commit().unwrap();
let txn = db.begin().unwrap();
let row = txn.get_by_pk("users", &json!(1)).unwrap().unwrap();
assert_eq!(row.values.get("name"), Some(&json!("Alice")));
}
let db = Database::open(&dir).unwrap();
let txn = db.begin().unwrap();
let row = txn.get_by_pk("users", &json!(1)).unwrap().unwrap();
assert_eq!(row.values.get("name"), Some(&json!("Alice")));
}
#[test]
fn insert_returning_projects_requested_columns() {
let dir = temp_dir();
let db = Database::create(&dir, Schema::new(vec![users_table()]).unwrap()).unwrap();
let mut row = Map::new();
row.insert("id".into(), json!(1));
row.insert("email".into(), json!("alice@example.com"));
row.insert("name".into(), json!("Alice"));
let mut txn = db.begin().unwrap();
let returned = txn
.insert_returning("users", row, vec!["id".into(), "name".into()])
.unwrap();
assert_eq!(returned, json!({"id": 1, "name": "Alice"}));
txn.commit().unwrap();
}
#[test]
fn truncate_clears_rows_and_unique_guards() {
let dir = temp_dir();
let db = Database::create(&dir, Schema::new(vec![users_table()]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
insert_user(&mut txn, 2, "bob@example.com");
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
txn.truncate("users").unwrap();
let rows = txn
.select(&Query::Select(select_all("users", None)))
.unwrap();
assert!(rows.is_empty());
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
let rows = txn
.select(&Query::Select(select_all("users", None)))
.unwrap();
assert!(rows.is_empty());
insert_user(&mut txn, 3, "alice@example.com");
txn.commit().unwrap();
let txn = db.begin().unwrap();
let rows = txn
.select(&Query::Select(select_all("users", None)))
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(
rows[0].values.get("email"),
Some(&json!("alice@example.com"))
);
}
#[test]
fn truncate_rejects_referenced_table() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
let err = txn.truncate("users").unwrap_err();
assert!(matches!(err, KitError::Restrict(_)));
}
#[test]
fn truncate_allows_repeat_in_same_transaction() {
let dir = temp_dir();
let db = Database::create(&dir, Schema::new(vec![users_table()]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
txn.truncate("users").unwrap();
txn.truncate("users").unwrap(); txn.commit().unwrap();
}
#[test]
fn truncate_rejects_prior_data_write_in_same_transaction() {
let dir = temp_dir();
let db = Database::create(&dir, Schema::new(vec![users_table()]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "a@example.com");
let err = txn.truncate("users").unwrap_err();
assert!(matches!(err, KitError::Validation(_)));
}
#[test]
fn upsert_insert_do_nothing_and_do_update() {
let dir = temp_dir();
let db = Database::create(&dir, Schema::new(vec![users_table()]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
let mut row = Map::new();
row.insert("id".into(), json!(1));
row.insert("email".into(), json!("alice@example.com"));
row.insert("name".into(), json!("Alice"));
let inserted = txn
.upsert(
"users",
row,
OnConflict::DoNothing,
vec!["id".into(), "name".into()],
)
.unwrap();
assert_eq!(inserted, json!({"id": 1, "name": "Alice"}));
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
let mut duplicate = Map::new();
duplicate.insert("id".into(), json!(1));
duplicate.insert("email".into(), json!("changed@example.com"));
duplicate.insert("name".into(), json!("Ignored"));
let unchanged = txn
.upsert(
"users",
duplicate,
OnConflict::DoNothing,
vec!["email".into(), "name".into()],
)
.unwrap();
assert_eq!(
unchanged,
json!({"email": "alice@example.com", "name": "Alice"})
);
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
let mut incoming = Map::new();
incoming.insert("id".into(), json!(1));
incoming.insert("email".into(), json!("alice@example.com"));
incoming.insert("name".into(), json!("Incoming"));
let mut patch = Map::new();
patch.insert("name".into(), json!("Updated"));
let updated = txn
.upsert(
"users",
incoming,
OnConflict::DoUpdate(patch),
vec!["id".into(), "name".into()],
)
.unwrap();
assert_eq!(updated, json!({"id": 1, "name": "Updated"}));
txn.commit().unwrap();
let txn = db.begin().unwrap();
let row = txn.get_by_pk("users", &json!(1)).unwrap().unwrap();
assert_eq!(row.values.get("email"), Some(&json!("alice@example.com")));
assert_eq!(row.values.get("name"), Some(&json!("Updated")));
}
#[test]
fn update_where_returns_post_images() {
let dir = temp_dir();
let db = Database::create(&dir, Schema::new(vec![users_table()]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
insert_user(&mut txn, 2, "bob@example.com");
insert_user(&mut txn, 3, "carol@example.com");
txn.commit().unwrap();
let mut patch = Map::new();
patch.insert("name".into(), json!("Updated"));
let mut txn = db.begin().unwrap();
let mut returned = txn
.update_where(
"users",
Some(Expr::Gt(
Box::new(col("id")),
Box::new(Expr::Literal(Literal::Int(1))),
)),
patch,
vec!["id".into(), "name".into()],
)
.unwrap();
returned.sort_by_key(|v| v["id"].as_i64().unwrap());
assert_eq!(
returned,
vec![
json!({"id": 2, "name": "Updated"}),
json!({"id": 3, "name": "Updated"})
]
);
txn.commit().unwrap();
let txn = db.begin().unwrap();
assert_eq!(
txn.get_by_pk("users", &json!(1))
.unwrap()
.unwrap()
.values
.get("name"),
Some(&Value::Null)
);
assert_eq!(
txn.get_by_pk("users", &json!(2))
.unwrap()
.unwrap()
.values
.get("name"),
Some(&json!("Updated"))
);
}
#[test]
fn delete_where_returns_preimages_and_cascades() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
insert_order(&mut txn, 10, 1, 100.0);
insert_order(&mut txn, 11, 1, 25.0);
let mut item = Map::new();
item.insert("id".into(), json!(1));
item.insert("order_id".into(), json!(10));
item.insert("sku".into(), json!("ABC"));
txn.insert("items", item).unwrap();
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
let returned = txn
.delete_where(
"orders",
Some(Expr::Eq(
Box::new(col("id")),
Box::new(Expr::Literal(Literal::Int(10))),
)),
vec!["id".into(), "total".into()],
)
.unwrap();
assert_eq!(returned, vec![json!({"id": 10, "total": 100.0})]);
txn.commit().unwrap();
let txn = db.begin().unwrap();
assert!(txn.get_by_pk("orders", &json!(10)).unwrap().is_none());
assert!(txn.get_by_pk("items", &json!(1)).unwrap().is_none());
assert!(txn.get_by_pk("orders", &json!(11)).unwrap().is_some());
}
#[test]
fn select_filters_and_orders() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
insert_user(&mut txn, 2, "bob@example.com");
insert_user(&mut txn, 3, "carol@example.com");
txn.commit().unwrap();
let txn = db.begin().unwrap();
let q = mongreldb_kit::Query::Select(mongreldb_kit::Select {
table: "users".into(),
columns: vec![mongreldb_kit::Expr::Column("id".into())],
filter: Some(mongreldb_kit::Expr::Gt(
Box::new(mongreldb_kit::Expr::Column("id".into())),
Box::new(mongreldb_kit::Expr::Literal(mongreldb_kit::Literal::Int(1))),
)),
order_by: vec![mongreldb_kit::OrderBy {
expr: mongreldb_kit::Expr::Column("id".into()),
direction: mongreldb_kit::Direction::Desc,
}],
limit: Some(2),
offset: None,
});
let rows = txn.select(&q).unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].values.get("id"), Some(&json!(3)));
assert_eq!(rows[1].values.get("id"), Some(&json!(2)));
}
#[test]
fn unique_constraint_violation() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
let mut row = Map::new();
row.insert("id".into(), json!(2));
row.insert("email".into(), json!("alice@example.com"));
let err = txn.insert("users", row).unwrap_err();
assert!(matches!(err, KitError::Duplicate(_)));
}
#[test]
fn foreign_key_violation() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
let mut order = Map::new();
order.insert("id".into(), json!(1));
order.insert("user_id".into(), json!(99));
order.insert("total".into(), json!(10.0));
let err = txn.insert("orders", order).unwrap_err();
assert!(matches!(err, KitError::ForeignKey(_)));
}
#[test]
fn restrict_delete_blocks() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
let mut order = Map::new();
order.insert("id".into(), json!(1));
order.insert("user_id".into(), json!(1));
order.insert("total".into(), json!(10.0));
txn.insert("orders", order).unwrap();
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
let err = txn.delete("users", &json!(1)).unwrap_err();
assert!(matches!(err, KitError::Restrict(_)));
}
#[test]
fn cascade_delete_removes_children() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
let mut order = Map::new();
order.insert("id".into(), json!(1));
order.insert("user_id".into(), json!(1));
order.insert("total".into(), json!(10.0));
txn.insert("orders", order).unwrap();
let mut item = Map::new();
item.insert("id".into(), json!(1));
item.insert("order_id".into(), json!(1));
item.insert("sku".into(), json!("ABC"));
txn.insert("items", item).unwrap();
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
txn.delete("orders", &json!(1)).unwrap();
txn.commit().unwrap();
let txn = db.begin().unwrap();
assert!(txn.get_by_pk("orders", &json!(1)).unwrap().is_none());
assert!(txn.get_by_pk("items", &json!(1)).unwrap().is_none());
}
#[test]
fn migrate_records_versions() {
let dir = temp_dir();
let schema = Schema::new(vec![users_table()]).unwrap();
let mut db = Database::create(&dir, schema).unwrap();
let migrations = vec![
Migration {
version: 1,
name: "init".into(),
ops: vec![MigrationOp::CreateTable {
name: "users".into(),
}],
},
Migration {
version: 2,
name: "add_orders".into(),
ops: vec![MigrationOp::CreateTable {
name: "orders".into(),
}],
},
];
let schema2 = make_schema();
db.set_schema(schema2);
mongreldb_kit::migrate(&mut db, &migrations).unwrap();
let txn = db.begin().unwrap();
assert!(txn.get_by_pk("users", &json!(1)).unwrap().is_none());
}
fn people_table(with_unique: bool) -> Table {
Table {
id: 20,
name: "people".into(),
columns: vec![
Column::new(1, "id", ColumnType::Int64),
Column::new(2, "email", ColumnType::Text),
],
primary_key: vec!["id".into()],
indexes: vec![],
foreign_keys: vec![],
unique_constraints: if with_unique {
vec![UniqueConstraint {
name: "uq_people_email".into(),
columns: vec!["email".into()],
}]
} else {
vec![]
},
check_constraints: vec![],
}
}
fn people_table_with_index(index: Option<Index>) -> Table {
Table {
indexes: index.into_iter().collect(),
..people_table(false)
}
}
fn people_table_without_email() -> Table {
Table {
columns: vec![Column::new(1, "id", ColumnType::Int64)],
primary_key: vec!["id".into()],
..people_table(false)
}
}
fn insert_person(txn: &mut Transaction, id: i64, email: &str) -> Result<Row, KitError> {
let mut row = Map::new();
row.insert("id".into(), json!(id));
row.insert("email".into(), json!(email));
txn.insert("people", row)
}
fn widgets_table(value_name: &str, value_type: ColumnType, nullable: bool) -> Table {
Table {
id: 30,
name: "widgets".into(),
columns: vec![
Column::new(1, "id", ColumnType::Int64),
Column {
nullable,
..Column::new(2, value_name, value_type)
},
],
primary_key: vec!["id".into()],
indexes: vec![],
foreign_keys: vec![],
unique_constraints: vec![],
check_constraints: vec![],
}
}
fn alter_widget_value_migration(column: &str) -> Vec<Migration> {
vec![Migration {
version: 1,
name: "alter_widget_value".into(),
ops: vec![MigrationOp::AlterColumn {
table: "widgets".into(),
column: column.into(),
}],
}]
}
fn core_widget_column(db: &Database, name: &str) -> mongreldb_core::schema::ColumnDef {
let handle = db.raw().table("widgets").unwrap();
let guard = handle.lock();
guard.schema().column(name).unwrap().clone()
}
#[test]
fn migrate_add_unique_backfills_and_enforces() {
let dir = temp_dir();
let mut db = Database::create(&dir, Schema::new(vec![people_table(false)]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_person(&mut txn, 1, "a@example.com").unwrap();
insert_person(&mut txn, 2, "b@example.com").unwrap();
txn.commit().unwrap();
db.set_schema(Schema::new(vec![people_table(true)]).unwrap());
let migrations = vec![Migration {
version: 1,
name: "add_people_unique".into(),
ops: vec![MigrationOp::AddUnique {
table: "people".into(),
constraint: "uq_people_email".into(),
}],
}];
mongreldb_kit::migrate(&mut db, &migrations).unwrap();
let mut txn = db.begin().unwrap();
let err = insert_person(&mut txn, 3, "a@example.com").unwrap_err();
assert!(matches!(err, KitError::Duplicate(_)));
txn.rollback();
let mut txn = db.begin().unwrap();
insert_person(&mut txn, 4, "c@example.com").unwrap();
txn.commit().unwrap();
}
#[test]
fn migrate_add_unique_rejects_existing_duplicates() {
let dir = temp_dir();
let mut db = Database::create(&dir, Schema::new(vec![people_table(false)]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_person(&mut txn, 1, "dup@example.com").unwrap();
insert_person(&mut txn, 2, "dup@example.com").unwrap();
txn.commit().unwrap();
db.set_schema(Schema::new(vec![people_table(true)]).unwrap());
let migrations = vec![Migration {
version: 1,
name: "add_people_unique".into(),
ops: vec![MigrationOp::AddUnique {
table: "people".into(),
constraint: "uq_people_email".into(),
}],
}];
let err = mongreldb_kit::migrate(&mut db, &migrations).unwrap_err();
assert!(matches!(err, KitError::Migration(_)));
}
#[test]
fn migrate_alter_column_renames_native_column() {
let dir = temp_dir();
let v1 = widgets_table("label", ColumnType::Text, false);
let v2 = widgets_table("name", ColumnType::Text, false);
let mut db = Database::create(&dir, Schema::new(vec![v1]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
let mut row = Map::new();
row.insert("id".into(), json!(1));
row.insert("label".into(), json!("one"));
txn.insert("widgets", row).unwrap();
txn.commit().unwrap();
db.set_schema(Schema::new(vec![v2]).unwrap());
mongreldb_kit::migrate(&mut db, &alter_widget_value_migration("label")).unwrap();
assert!(db
.raw()
.table("widgets")
.unwrap()
.lock()
.schema()
.column("label")
.is_none());
assert_eq!(core_widget_column(&db, "name").id, 2);
let txn = db.begin().unwrap();
let row = txn.get_by_pk("widgets", &json!(1)).unwrap().unwrap();
assert_eq!(row.values.get("name"), Some(&json!("one")));
}
#[test]
fn migrate_alter_column_changes_native_type_on_empty_table() {
let dir = temp_dir();
let v1 = widgets_table("qty", ColumnType::Int64, false);
let v2 = widgets_table("qty", ColumnType::Float64, false);
let mut db = Database::create(&dir, Schema::new(vec![v1]).unwrap()).unwrap();
db.set_schema(Schema::new(vec![v2]).unwrap());
mongreldb_kit::migrate(&mut db, &alter_widget_value_migration("qty")).unwrap();
assert_eq!(core_widget_column(&db, "qty").ty, TypeId::Float64);
let mut txn = db.begin().unwrap();
let mut row = Map::new();
row.insert("id".into(), json!(1));
row.insert("qty".into(), json!(1.5));
txn.insert("widgets", row).unwrap();
txn.commit().unwrap();
}
#[test]
fn migrate_alter_column_drops_not_null() {
let dir = temp_dir();
let v1 = widgets_table("name", ColumnType::Text, false);
let v2 = widgets_table("name", ColumnType::Text, true);
let mut db = Database::create(&dir, Schema::new(vec![v1]).unwrap()).unwrap();
db.set_schema(Schema::new(vec![v2]).unwrap());
mongreldb_kit::migrate(&mut db, &alter_widget_value_migration("name")).unwrap();
assert!(core_widget_column(&db, "name")
.flags
.contains(ColumnFlags::NULLABLE));
let mut txn = db.begin().unwrap();
let mut row = Map::new();
row.insert("id".into(), json!(1));
row.insert("name".into(), Value::Null);
txn.insert("widgets", row).unwrap();
txn.commit().unwrap();
}
#[test]
fn migrate_alter_column_rejects_set_not_null_with_existing_nulls() {
let dir = temp_dir();
let v1 = widgets_table("name", ColumnType::Text, true);
let v2 = widgets_table("name", ColumnType::Text, false);
let mut db = Database::create(&dir, Schema::new(vec![v1]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
let mut row = Map::new();
row.insert("id".into(), json!(1));
row.insert("name".into(), Value::Null);
txn.insert("widgets", row).unwrap();
txn.commit().unwrap();
db.set_schema(Schema::new(vec![v2]).unwrap());
let err = mongreldb_kit::migrate(&mut db, &alter_widget_value_migration("name")).unwrap_err();
assert!(matches!(err, KitError::Validation(_)));
}
fn owners_table() -> Table {
Table {
id: 21,
name: "owners".into(),
columns: vec![
Column::new(1, "id", ColumnType::Int64),
Column::new(2, "name", ColumnType::Text),
],
primary_key: vec!["id".into()],
indexes: vec![],
foreign_keys: vec![],
unique_constraints: vec![],
check_constraints: vec![],
}
}
fn pets_table(with_fk: bool) -> Table {
Table {
id: 22,
name: "pets".into(),
columns: vec![
Column::new(1, "id", ColumnType::Int64),
Column::new(2, "owner_id", ColumnType::Int64),
Column::new(3, "name", ColumnType::Text),
],
primary_key: vec!["id".into()],
indexes: vec![],
foreign_keys: if with_fk {
vec![ForeignKey {
name: "fk_pets_owner".into(),
columns: vec!["owner_id".into()],
references_table: "owners".into(),
references_columns: vec!["id".into()],
on_delete: ForeignKeyAction::Restrict,
}]
} else {
vec![]
},
unique_constraints: vec![],
check_constraints: vec![],
}
}
fn insert_pet(txn: &mut Transaction, id: i64, owner_id: i64, name: &str) -> Result<Row, KitError> {
let mut row = Map::new();
row.insert("id".into(), json!(id));
row.insert("owner_id".into(), json!(owner_id));
row.insert("name".into(), json!(name));
txn.insert("pets", row)
}
#[test]
fn migrate_add_foreign_key_backfills_and_enforces() {
let dir = temp_dir();
let mut db = Database::create(
&dir,
Schema::new(vec![owners_table(), pets_table(false)]).unwrap(),
)
.unwrap();
let mut txn = db.begin().unwrap();
let mut owner = Map::new();
owner.insert("id".into(), json!(1));
owner.insert("name".into(), json!("Ada"));
txn.insert("owners", owner).unwrap();
insert_pet(&mut txn, 1, 1, "Rex").unwrap();
txn.commit().unwrap();
db.set_schema(Schema::new(vec![owners_table(), pets_table(true)]).unwrap());
let migrations = vec![Migration {
version: 1,
name: "add_pets_fk".into(),
ops: vec![MigrationOp::AddForeignKey {
table: "pets".into(),
constraint: "fk_pets_owner".into(),
}],
}];
mongreldb_kit::migrate(&mut db, &migrations).unwrap();
let mut txn = db.begin().unwrap();
let err = insert_pet(&mut txn, 2, 99, "Lost").unwrap_err();
assert!(matches!(err, KitError::ForeignKey(_)));
txn.rollback();
let mut txn = db.begin().unwrap();
insert_pet(&mut txn, 3, 1, "Spot").unwrap();
txn.commit().unwrap();
}
#[test]
fn migrate_add_foreign_key_rejects_orphans() {
let dir = temp_dir();
let mut db = Database::create(
&dir,
Schema::new(vec![owners_table(), pets_table(false)]).unwrap(),
)
.unwrap();
let mut txn = db.begin().unwrap();
let mut owner = Map::new();
owner.insert("id".into(), json!(1));
owner.insert("name".into(), json!("Ada"));
txn.insert("owners", owner).unwrap();
insert_pet(&mut txn, 1, 42, "Orphan").unwrap();
txn.commit().unwrap();
db.set_schema(Schema::new(vec![owners_table(), pets_table(true)]).unwrap());
let migrations = vec![Migration {
version: 1,
name: "add_pets_fk".into(),
ops: vec![MigrationOp::AddForeignKey {
table: "pets".into(),
constraint: "fk_pets_owner".into(),
}],
}];
let err = mongreldb_kit::migrate(&mut db, &migrations).unwrap_err();
assert!(matches!(err, KitError::ForeignKey(_)));
}
#[test]
fn migrate_drop_table_removes_table() {
let dir = temp_dir();
let mut db = Database::create(&dir, Schema::new(vec![people_table(false)]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_person(&mut txn, 1, "a@example.com").unwrap();
txn.commit().unwrap();
assert!(db.raw().table_id("people").is_ok());
db.set_schema(Schema::new(vec![]).unwrap());
let migrations = vec![Migration {
version: 1,
name: "drop_people".into(),
ops: vec![MigrationOp::DropTable {
name: "people".into(),
}],
}];
mongreldb_kit::migrate(&mut db, &migrations).unwrap();
assert!(db.raw().table_id("people").is_err());
assert!(db.table("people").is_none());
}
#[test]
fn migrate_add_index_rebuilds_table_and_preserves_rows() {
let dir = temp_dir();
let mut db = Database::create(&dir, Schema::new(vec![people_table(false)]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_person(&mut txn, 1, "a@example.com").unwrap();
insert_person(&mut txn, 2, "b@example.com").unwrap();
txn.commit().unwrap();
db.set_schema(
Schema::new(vec![people_table_with_index(Some(Index {
name: "idx_people_email".into(),
columns: vec!["email".into()],
unique: false,
kind: Default::default(),
}))])
.unwrap(),
);
let migrations = vec![Migration {
version: 1,
name: "add_email_index".into(),
ops: vec![MigrationOp::AddIndex {
table: "people".into(),
index: "idx_people_email".into(),
}],
}];
mongreldb_kit::migrate(&mut db, &migrations).unwrap();
let schema = db.raw().table("people").unwrap().lock().schema().clone();
assert_eq!(schema.indexes.len(), 1);
assert_eq!(schema.indexes[0].name, "idx_people_email_email");
let txn = db.begin().unwrap();
let row = txn.get_by_pk("people", &json!(1)).unwrap().unwrap();
assert_eq!(row.values.get("email"), Some(&json!("a@example.com")));
}
#[test]
fn migrate_drop_index_rebuilds_table_and_preserves_rows() {
let dir = temp_dir();
let indexed = people_table_with_index(Some(Index {
name: "idx_people_email".into(),
columns: vec!["email".into()],
unique: false,
kind: Default::default(),
}));
let mut db = Database::create(&dir, Schema::new(vec![indexed]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_person(&mut txn, 1, "a@example.com").unwrap();
txn.commit().unwrap();
db.set_schema(Schema::new(vec![people_table(false)]).unwrap());
let migrations = vec![Migration {
version: 1,
name: "drop_email_index".into(),
ops: vec![MigrationOp::DropIndex {
table: "people".into(),
index: "idx_people_email".into(),
}],
}];
mongreldb_kit::migrate(&mut db, &migrations).unwrap();
let schema = db.raw().table("people").unwrap().lock().schema().clone();
assert!(schema.indexes.is_empty());
let txn = db.begin().unwrap();
let row = txn.get_by_pk("people", &json!(1)).unwrap().unwrap();
assert_eq!(row.values.get("email"), Some(&json!("a@example.com")));
}
#[test]
fn migrate_drop_column_rebuilds_table_and_removes_stale_values() {
let dir = temp_dir();
let mut db = Database::create(&dir, Schema::new(vec![people_table(false)]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_person(&mut txn, 1, "a@example.com").unwrap();
txn.commit().unwrap();
db.set_schema(Schema::new(vec![people_table_without_email()]).unwrap());
let migrations = vec![Migration {
version: 1,
name: "drop_email".into(),
ops: vec![MigrationOp::DropColumn {
table: "people".into(),
column: "email".into(),
}],
}];
mongreldb_kit::migrate(&mut db, &migrations).unwrap();
let schema = db.raw().table("people").unwrap().lock().schema().clone();
assert!(schema.column("email").is_none());
let txn = db.begin().unwrap();
let row = txn.get_by_pk("people", &json!(1)).unwrap().unwrap();
assert_eq!(row.values.get("id"), Some(&json!(1)));
assert!(row.values.get("email").is_none());
}
#[test]
fn migrate_raw_sql_errors_clearly() {
let dir = temp_dir();
let mut db = Database::create(&dir, Schema::new(vec![people_table(false)]).unwrap()).unwrap();
let migrations = vec![Migration {
version: 1,
name: "raw".into(),
ops: vec![MigrationOp::RawSql("VACUUM".into())],
}];
let err = mongreldb_kit::migrate(&mut db, &migrations).unwrap_err();
match err {
KitError::Migration(msg) => assert!(msg.contains("RawSql")),
other => panic!("expected migration error, got {other:?}"),
}
}
#[test]
fn truncate_then_reuse_pk() {
let dir = temp_dir();
let db = Database::create(&dir, Schema::new(vec![users_table()]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
txn.truncate("users").unwrap();
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "alice@example.com");
txn.commit().unwrap();
let txn = db.begin().unwrap();
let rows = txn
.select(&Query::Select(select_all("users", None)))
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].values.get("id"), Some(&json!(1)));
}
#[test]
fn truncate_conflicts_with_concurrent_insert() {
let dir = temp_dir();
let db = Database::create(&dir, Schema::new(vec![users_table()]).unwrap()).unwrap();
let core_db = db.raw();
let mut seed = core_db.begin();
seed.put(
"users",
vec![
(1, CoreValue::Int64(1)),
(2, CoreValue::Bytes(b"alice@example.com".to_vec())),
],
)
.unwrap();
seed.commit().unwrap();
let mut insert = core_db.begin();
insert
.put(
"users",
vec![
(1, CoreValue::Int64(1)),
(2, CoreValue::Bytes(b"bob@example.com".to_vec())),
],
)
.unwrap();
let mut truncate = core_db.begin();
truncate.truncate("users").unwrap();
truncate.commit().unwrap();
let err = insert.commit().unwrap_err();
assert!(
matches!(err, CoreError::Conflict(_)),
"expected conflict, got {err:?}"
);
}
#[test]
fn update_where_multi_row_returning() {
let dir = temp_dir();
let db = Database::create(&dir, Schema::new(vec![users_table()]).unwrap()).unwrap();
let mut txn = db.begin().unwrap();
insert_named_user(&mut txn, 1, "a@example.com", "A");
insert_named_user(&mut txn, 2, "b@example.com", "B");
insert_named_user(&mut txn, 3, "c@example.com", "C");
txn.commit().unwrap();
let mut patch = Map::new();
patch.insert("name".into(), json!("Updated"));
let mut txn = db.begin().unwrap();
let returned = txn
.execute(&Query::Update(Update {
table: "users".into(),
set: patch,
filter: Some(Expr::Gt(
Box::new(col("id")),
Box::new(Expr::Literal(Literal::Int(1))),
)),
returning: vec!["id".into(), "name".into()],
pk: None,
}))
.unwrap();
assert_eq!(returned.len(), 2);
let mut returned = returned;
returned.sort_by_key(|v| v["id"].as_i64().unwrap());
assert_eq!(
returned,
vec![
json!({"id": 2, "name": "Updated"}),
json!({"id": 3, "name": "Updated"})
]
);
txn.commit().unwrap();
}
#[test]
fn delete_where_multi_row_cascade() {
let dir = temp_dir();
let db = Database::create(&dir, make_schema()).unwrap();
let mut txn = db.begin().unwrap();
insert_user(&mut txn, 1, "user@example.com");
insert_order(&mut txn, 10, 1, 100.0);
insert_order(&mut txn, 11, 1, 50.0);
let mut item = Map::new();
item.insert("id".into(), json!(1));
item.insert("order_id".into(), json!(10));
item.insert("sku".into(), json!("ABC"));
txn.insert("items", item).unwrap();
let mut item = Map::new();
item.insert("id".into(), json!(2));
item.insert("order_id".into(), json!(11));
item.insert("sku".into(), json!("DEF"));
txn.insert("items", item).unwrap();
txn.commit().unwrap();
let mut txn = db.begin().unwrap();
let returned = txn
.execute(&Query::Delete(Delete {
table: "orders".into(),
filter: Some(Expr::Gte(
Box::new(col("id")),
Box::new(Expr::Literal(Literal::Int(10))),
)),
returning: vec!["id".into()],
pk: None,
}))
.unwrap();
assert_eq!(returned.len(), 2);
let mut returned = returned;
returned.sort_by_key(|v| v["id"].as_i64().unwrap());
assert_eq!(returned, vec![json!({"id": 10}), json!({"id": 11})]);
txn.commit().unwrap();
let txn = db.begin().unwrap();
assert!(txn.get_by_pk("orders", &json!(10)).unwrap().is_none());
assert!(txn.get_by_pk("orders", &json!(11)).unwrap().is_none());
assert!(txn.get_by_pk("items", &json!(1)).unwrap().is_none());
assert!(txn.get_by_pk("items", &json!(2)).unwrap().is_none());
}