use nanosql::{Result, Query, Connection, ConnectionExt, TransactionExt};
use nanosql::{Param, ResultRecord, Table, table::{CreateTable, TableConstraint}};
#[derive(Clone, Debug, Param, ResultRecord, Table)]
struct Department {
#[nanosql(pk)]
department_id: i64,
title: String,
established_year: u16,
}
#[derive(Clone, Debug, Param, ResultRecord, Table)]
#[nanosql(fk("bogus_table" => ("some_fk_2" = "some_col", "some_fk_1" = "other_col")))]
struct Employee {
#[nanosql(pk)]
employee_id: i64,
full_name: String,
#[nanosql(fk("Employee" => "employee_id"))]
boss_employee_id: Option<i64>,
#[nanosql(foreign_key = Department::department_id)]
employing_department_id: i64,
#[nanosql(unique)]
some_fk_1: u32,
some_fk_2: bool,
}
#[derive(Clone, Debug, Param, ResultRecord, Table)]
#[nanosql(pk = [some_col, other_col], rename = bogus_table)]
struct BogusTable {
some_col: bool,
other_col: u32,
}
nanosql::define_query!{
#[derive(Clone, Copy, Default, Debug)]
ListTableIndexes<'p>: &'p str => Vec<Option<String>> {
"SELECT sql FROM sqlite_schema WHERE type = 'index' AND tbl_name = ?"
}
}
fn main() -> Result<()> {
let query = CreateTable::<Employee>::default();
let sql_disp = query.display_sql();
let sql = sql_disp.to_string();
println!("{sql_disp}");
assert!(
sql.contains(
r#""employing_department_id" INTEGER NOT NULL REFERENCES "Department"("department_id") DEFERRABLE INITIALLY DEFERRED"#
)
);
assert!(
sql.contains(
r#""boss_employee_id" INTEGER NULL REFERENCES "Employee"("employee_id") DEFERRABLE INITIALLY DEFERRED"#
)
);
let desc = Employee::description();
let fk_sql = desc.constraints
.iter()
.find(|c| matches!(c, TableConstraint::ForeignKey { .. }))
.unwrap()
.to_string();
assert!(fk_sql.contains(
r#"FOREIGN KEY("some_fk_2", "some_fk_1") REFERENCES "bogus_table"("some_col", "other_col") DEFERRABLE INITIALLY DEFERRED"#
));
let mut conn = Connection::connect_in_memory()?;
conn.create_table::<Department>()?;
conn.create_table::<BogusTable>()?;
conn.create_table::<Employee>()?;
let mut stmt = conn.compile(ListTableIndexes)?;
let index_sqls = stmt.invoke("Employee")?;
for sql in index_sqls {
let Some(sql) = sql else {
println!("Found SQLite auto-index, skipping");
continue;
};
println!("{sql}");
assert!(sql.contains("CREATE INDEX \"__nanosql_index_Employee_"));
assert!(
(
sql.contains(r#""boss_employee_id" ASC"#)
&&
!sql.contains("employing_department_id")
&&
!sql.contains("some_fk_1")
&&
!sql.contains("some_fk_2")
) || (
sql.contains(r#""employing_department_id" ASC"#)
&&
!sql.contains("boss_employee_id")
&&
!sql.contains("some_fk_1")
&&
!sql.contains("some_fk_2")
) || (
sql.contains(r#""some_fk_1" ASC"#)
&&
sql.contains(r#""some_fk_2" ASC"#)
&&
!sql.contains("boss_employee_id")
&&
!sql.contains("employing_department_id")
)
);
}
test_runtime_enforcement_of_fk()
}
fn test_runtime_enforcement_of_fk() -> Result<()> {
let mut conn = Connection::connect_in_memory()?;
conn.create_table::<Employee>()?;
conn.create_table::<Department>()?;
conn.create_table::<BogusTable>()?;
let employee = Employee {
employee_id: 42,
full_name: "Jane Doe".into(),
boss_employee_id: None,
employing_department_id: 13,
some_fk_1: 37,
some_fk_2: true,
};
let error = conn.insert_batch([employee.clone()]).unwrap_err();
let err_msg = error.to_string();
assert!(err_msg.contains("FOREIGN KEY"));
let txn = conn.transaction()?;
txn.insert_batch([employee])?;
txn.insert_batch([
BogusTable {
some_col: true,
other_col: 37,
},
])?;
txn.insert_batch([
Department {
department_id: 13,
title: "Department of Redundancy Department".into(),
established_year: 1984,
},
])?;
txn.commit()?;
Ok(())
}