#![cfg(feature = "c-sqlite-tests")]
use asupersync::runtime::RuntimeBuilder;
use asupersync::{Cx, Outcome};
use serde::{Deserialize, Serialize};
use sqlmodel::SchemaBuilder;
use sqlmodel::prelude::*;
use sqlmodel_query::{DeleteBuilder, UpdateBuilder};
use sqlmodel_sqlite::SqliteConnection;
fn unwrap_outcome<T>(outcome: Outcome<T, Error>) -> T {
match outcome {
Outcome::Ok(v) => v,
Outcome::Err(e) => panic!("unexpected error: {e}"),
Outcome::Cancelled(r) => panic!("cancelled: {r:?}"),
Outcome::Panicked(p) => panic!("panicked: {p:?}"),
}
}
#[derive(sqlmodel::Model, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[sqlmodel(table, inheritance = "joined")]
struct Person {
#[sqlmodel(primary_key, auto_increment)]
id: Option<i64>,
name: String,
}
#[derive(sqlmodel::Model, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[sqlmodel(table, inherits = "Person")]
struct Student {
#[sqlmodel(parent)]
person: Person,
#[sqlmodel(primary_key)]
id: Option<i64>,
grade: String,
}
#[test]
fn sqlite_joined_inheritance_dml_inserts_updates_deletes_base_and_child() {
let rt = RuntimeBuilder::current_thread()
.build()
.expect("create asupersync runtime");
let cx = Cx::for_testing();
rt.block_on(async {
let conn = SqliteConnection::open_memory().expect("open sqlite memory db");
let stmts = SchemaBuilder::new()
.create_table::<Person>()
.create_table::<Student>()
.build();
for stmt in stmts {
unwrap_outcome(conn.execute(&cx, &stmt, &[]).await);
}
let student0 = Student {
person: Person {
id: None,
name: "Alice".to_string(),
},
id: None,
grade: "A".to_string(),
};
let id = unwrap_outcome(insert!(&student0).execute(&cx, &conn).await);
assert!(id > 0);
let person_table = sqlmodel_core::quote_ident(<Person as Model>::TABLE_NAME);
let student_table = sqlmodel_core::quote_ident(<Student as Model>::TABLE_NAME);
let people = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT id, name FROM {person_table} WHERE id = ?1"),
&[Value::BigInt(id)],
)
.await,
);
assert_eq!(people.len(), 1);
assert_eq!(people[0].get_as::<i64>(0).unwrap(), id);
assert_eq!(people[0].get_named::<String>("name").unwrap(), "Alice");
let student_rows = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT id, grade FROM {student_table} WHERE id = ?1"),
&[Value::BigInt(id)],
)
.await,
);
assert_eq!(student_rows.len(), 1);
assert_eq!(student_rows[0].get_as::<i64>(0).unwrap(), id);
assert_eq!(student_rows[0].get_named::<String>("grade").unwrap(), "A");
let student1 = Student {
person: Person {
id: Some(id),
name: "Alice2".to_string(),
},
id: Some(id),
grade: "B".to_string(),
};
let updated = unwrap_outcome(update!(&student1).execute(&cx, &conn).await);
assert_eq!(updated, 2);
let people2 = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT name FROM {person_table} WHERE id = ?1"),
&[Value::BigInt(id)],
)
.await,
);
assert_eq!(people2[0].get_as::<String>(0).unwrap(), "Alice2");
let students2 = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT grade FROM {student_table} WHERE id = ?1"),
&[Value::BigInt(id)],
)
.await,
);
assert_eq!(students2[0].get_as::<String>(0).unwrap(), "B");
let updated_explicit = unwrap_outcome(
UpdateBuilder::<Student>::empty()
.set(&format!("{}.name", <Person as Model>::TABLE_NAME), "Alice3")
.set("grade", "A+")
.filter(Expr::qualified(<Student as Model>::TABLE_NAME, "id").eq(id))
.execute(&cx, &conn)
.await,
);
assert_eq!(updated_explicit, 2);
let explicit_rows = unwrap_outcome(
UpdateBuilder::<Student>::empty()
.set("grade", "A++")
.filter(Expr::qualified(<Student as Model>::TABLE_NAME, "id").eq(id))
.returning()
.execute_returning(&cx, &conn)
.await,
);
assert_eq!(explicit_rows.len(), 1);
assert_eq!(
explicit_rows[0]
.get_named::<String>(&format!("{}__name", <Person as Model>::TABLE_NAME))
.unwrap(),
"Alice3"
);
assert_eq!(
explicit_rows[0]
.get_named::<String>(&format!("{}__grade", <Student as Model>::TABLE_NAME))
.unwrap(),
"A++"
);
let upsert_model = Student {
person: Person {
id: Some(id),
name: "Alice4".to_string(),
},
id: Some(id),
grade: "A*".to_string(),
};
let upsert_id = unwrap_outcome(
insert!(&upsert_model)
.on_conflict_do_update(&["name", "grade"])
.execute(&cx, &conn)
.await,
);
assert_eq!(upsert_id, id);
let people_after_upsert = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT name FROM {person_table} WHERE id = ?1"),
&[Value::BigInt(id)],
)
.await,
);
assert_eq!(
people_after_upsert[0].get_as::<String>(0).unwrap(),
"Alice4"
);
let students_after_upsert = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT grade FROM {student_table} WHERE id = ?1"),
&[Value::BigInt(id)],
)
.await,
);
assert_eq!(students_after_upsert[0].get_as::<String>(0).unwrap(), "A*");
let ambiguous_update = UpdateBuilder::<Student>::empty()
.set("id", 123_i64)
.filter(Expr::qualified(<Student as Model>::TABLE_NAME, "id").eq(id))
.execute(&cx, &conn)
.await;
match ambiguous_update {
Outcome::Err(e) => assert!(
e.to_string()
.contains("ambiguous joined-table inheritance column 'id'"),
"unexpected error: {e}"
),
other => panic!("expected ambiguity error, got {other:?}"),
}
let conflict_returning = insert!(&upsert_model)
.on_conflict_do_nothing()
.execute_returning(&cx, &conn)
.await;
match conflict_returning {
Outcome::Err(e) => assert!(
e.to_string()
.contains("insert_returning does not support ON CONFLICT"),
"unexpected error: {e}"
),
other => panic!("expected insert_returning ON CONFLICT error, got {other:?}"),
}
let student_c = Student {
person: Person {
id: None,
name: "Bob".to_string(),
},
id: None,
grade: "C".to_string(),
};
let id2 = unwrap_outcome(insert!(&student_c).execute(&cx, &conn).await);
let student_d = Student {
person: Person {
id: None,
name: "Dana".to_string(),
},
id: None,
grade: "D".to_string(),
};
let id3 = unwrap_outcome(insert!(&student_d).execute(&cx, &conn).await);
let deleted_explicit = unwrap_outcome(
DeleteBuilder::<Student>::new()
.filter(Expr::qualified(<Student as Model>::TABLE_NAME, "grade").eq("C"))
.execute(&cx, &conn)
.await,
);
assert_eq!(deleted_explicit, 2);
let people_deleted_explicit = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT id FROM {person_table} WHERE id = ?1"),
&[Value::BigInt(id2)],
)
.await,
);
assert_eq!(people_deleted_explicit.len(), 0);
let students_deleted_explicit = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT id FROM {student_table} WHERE id = ?1"),
&[Value::BigInt(id2)],
)
.await,
);
assert_eq!(students_deleted_explicit.len(), 0);
let deleted_rows = unwrap_outcome(
DeleteBuilder::<Student>::new()
.filter(Expr::qualified(<Student as Model>::TABLE_NAME, "grade").eq("D"))
.returning()
.execute_returning(&cx, &conn)
.await,
);
assert_eq!(deleted_rows.len(), 1);
assert_eq!(
deleted_rows[0]
.get_named::<String>(&format!("{}__name", <Person as Model>::TABLE_NAME))
.unwrap(),
"Dana"
);
assert_eq!(
deleted_rows[0]
.get_named::<String>(&format!("{}__grade", <Student as Model>::TABLE_NAME))
.unwrap(),
"D"
);
let people_after_returning_delete = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT id FROM {person_table} WHERE id = ?1"),
&[Value::BigInt(id3)],
)
.await,
);
assert_eq!(people_after_returning_delete.len(), 0);
let students_after_returning_delete = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT id FROM {student_table} WHERE id = ?1"),
&[Value::BigInt(id3)],
)
.await,
);
assert_eq!(students_after_returning_delete.len(), 0);
let deleted = unwrap_outcome(
DeleteBuilder::from_model(&student1)
.execute(&cx, &conn)
.await,
);
assert_eq!(deleted, 2);
let people3 = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT id FROM {person_table} WHERE id = ?1"),
&[Value::BigInt(id)],
)
.await,
);
assert_eq!(people3.len(), 0);
let deleted_student_rows = unwrap_outcome(
conn.query(
&cx,
&format!("SELECT id FROM {student_table} WHERE id = ?1"),
&[Value::BigInt(id)],
)
.await,
);
assert_eq!(deleted_student_rows.len(), 0);
});
}