#![cfg(feature = "sqlite")]
mod advisory;
mod lease;
mod outbox;
use std::error::Error;
use std::num::NonZeroUsize;
use ruststream_sqlx_dialect::{
ClaimShape, Column, Dialect, Form, Isolation, Lease, Mode, Opening, Opens, Param, Sqlite,
StatementError, TableName, TableSpec, level,
};
const EXPIRY: Column<'static> = Column::new("locked_until");
const LEASED: TableSpec<'static> =
TableSpec::new("email_jobs", Column::new("job_id"), Form::Lease(EXPIRY))
.group(Column::new("name"))
.retry_after(Column::new("retry_after"))
.attempt(Column::new("attempt"))
.processed_at(Column::new("processed_at"))
.payload(Column::new("payload"));
const BARE: TableSpec<'static> = TableSpec::new("jobs", Column::new("job_id"), Form::Lease(EXPIRY))
.payload(Column::new("payload"));
const LOCKED: TableSpec<'static> =
TableSpec::new("email_jobs", Column::new("job_id"), Form::RowLock)
.group(Column::new("name"))
.retry_after(Column::new("retry_after"))
.attempt(Column::new("attempt"))
.payload(Column::new("payload"));
fn quoted(ident: &str) -> String {
let mut out = String::new();
Sqlite.quote_into(ident, &mut out);
out
}
#[test]
fn names_are_quoted_with_backticks_and_keep_their_case() {
assert_eq!(quoted("email_jobs"), "`email_jobs`");
assert_eq!(quoted("EmailJobs"), "`EmailJobs`");
assert_eq!(quoted("email jobs"), "`email jobs`");
assert_eq!(quoted("a`b"), "`a``b`");
assert_eq!(quoted("``"), "``````");
assert_eq!(quoted(r#"a"b"#), r#"`a"b`"#);
}
#[test]
fn every_placeholder_is_a_question_mark() {
let mut out = String::new();
Sqlite.placeholder_into(NonZeroUsize::MIN, &mut out);
out.push(' ');
Sqlite.placeholder_into(NonZeroUsize::MIN.saturating_add(11), &mut out);
assert_eq!(out, "? ?");
assert_eq!(Sqlite.name(), "sqlite");
}
#[test]
fn a_claim_of_the_services_own_opens_its_transaction_for_writing() {
assert_eq!(Sqlite.begin_lease_claim(), Some("BEGIN IMMEDIATE"));
}
#[test]
fn transactions_open_in_the_declared_mode() -> Result<(), Box<dyn Error>> {
assert_eq!(Sqlite.begin(Opening::Default)?, None);
assert_eq!(
Sqlite.begin(Opening::Mode(Mode::Immediate))?,
Some("BEGIN IMMEDIATE")
);
assert_eq!(
Sqlite.begin(Opening::Mode(Mode::Exclusive))?,
Some("BEGIN EXCLUSIVE")
);
assert_eq!(
Sqlite.begin(Opening::Mode(Mode::Deferred))?,
Some("BEGIN DEFERRED")
);
assert_eq!(
Sqlite.begin(Opening::Isolation(Isolation::Serializable)),
Err(StatementError::UnsupportedOpening {
dialect: "sqlite",
opening: Opening::Isolation(Isolation::Serializable).name(),
})
);
assert!(
Sqlite
.begin(Opening::Isolation(Isolation::ReadUncommitted))
.is_err()
);
assert_eq!(Sqlite.savepoint(), "SAVEPOINT ruststream_claim");
assert_eq!(
Sqlite.rollback_to_savepoint(),
"ROLLBACK TO SAVEPOINT ruststream_claim"
);
Ok(())
}
fn begin_at<Level, D: Opens<Level>>(
dialect: &D,
opening: Opening,
) -> Result<Option<&'static str>, StatementError> {
dialect.begin(opening)
}
#[test]
fn every_mode_sqlite_opens_is_one_its_begin_accepts() {
let opened = [
begin_at::<(), _>(&Sqlite, Opening::Default),
begin_at::<level::Deferred, _>(&Sqlite, Opening::Mode(Mode::Deferred)),
begin_at::<level::Immediate, _>(&Sqlite, Opening::Mode(Mode::Immediate)),
begin_at::<level::Exclusive, _>(&Sqlite, Opening::Mode(Mode::Exclusive)),
];
assert!(opened.iter().all(Result::is_ok), "{opened:?}");
}
#[test]
fn the_insert_writes_every_column_the_database_does_not_fill() -> Result<(), StatementError> {
let data = [
Column::new("subject"),
Column::new("created_at").generated(),
];
let spec = TableSpec::new(
"email_jobs",
Column::new("job_id").generated(),
Form::RowLock,
)
.group(Column::new("name"))
.retry_after(Column::new("retry_after"))
.attempt(Column::new("attempt").generated())
.payload(Column::new("payload"))
.data(&data);
let insert = Sqlite.insert(&spec)?;
assert_eq!(
insert.sql(),
"INSERT INTO `email_jobs` (`name`, `retry_after`, `payload`, `subject`) VALUES (?, ?, ?, ?)",
);
assert_eq!(
insert.params(),
[
Param::Column(1),
Param::Column(2),
Param::Column(4),
Param::Column(5)
]
);
Ok(())
}
#[test]
fn an_insert_of_only_generated_columns_writes_the_defaults() -> Result<(), StatementError> {
let spec = TableSpec::new(
"jobs",
Column::new("id").generated(),
Form::Lease(EXPIRY.generated()),
);
let insert = Sqlite.insert(&spec)?;
assert_eq!(insert.sql(), "INSERT INTO `jobs` DEFAULT VALUES");
assert_eq!(insert.params(), []);
assert_eq!(
Sqlite.insert(&BARE.selecting_all()),
Err(StatementError::Flattened {
statement: "insert"
})
);
Ok(())
}
#[test]
fn a_name_of_any_length_is_kept() -> Result<(), Box<dyn Error>> {
let long = "n".repeat(1000);
let spec =
TableSpec::new(&long, Column::new(&long), Form::Lease(Column::new(&long))).within(&long);
let claim = Sqlite.lease_claim(&spec, ClaimShape::Ids)?;
assert!(claim.sql().contains("ed(&long)), "{}", claim.sql());
let target = format!("{long}.{long}");
assert_eq!(
Sqlite
.dead_letter_table(&spec, TableName::parse(&target)?)?
.len(),
2
);
Ok(())
}
#[test]
fn the_row_lock_form_is_refused_for_every_statement() -> Result<(), Box<dyn Error>> {
let refused = StatementError::UnsupportedForm {
dialect: "sqlite",
form: "row lock",
};
let other = |statement| StatementError::FormMismatch {
statement,
form: "row lock",
};
assert_eq!(
Sqlite.lease_claim(&LOCKED, ClaimShape::Rows),
Err(other("lease_claim"))
);
assert_eq!(
Sqlite.lease_claim(&LOCKED, ClaimShape::Roles),
Err(other("lease_claim"))
);
assert_eq!(Sqlite.ack(&LOCKED), Err(refused.clone()));
assert_eq!(Sqlite.retry(&LOCKED), Err(refused.clone()));
assert_eq!(Sqlite.retry_after(&LOCKED), Err(refused.clone()));
assert_eq!(Sqlite.discard(&LOCKED), Err(refused.clone()));
assert_eq!(Sqlite.dead_letter_group(&LOCKED), Err(refused.clone()));
assert_eq!(
Sqlite.dead_letter_table(&LOCKED, TableName::parse("jobs_dead")?),
Err(refused.clone())
);
assert_eq!(Sqlite.extend(&LOCKED), Err(other("extend")));
assert_eq!(Sqlite.stamp(&LOCKED), Err(other("stamp")));
assert_eq!(Sqlite.ack(&LOCKED.database_clock()), Err(refused));
Ok(())
}
#[test]
fn a_fetch_by_a_list_of_ids_is_refused() {
assert_eq!(
Sqlite.fetch(&LEASED),
Err(StatementError::UnsupportedFetch { dialect: "sqlite" })
);
}
#[test]
fn sqlite_asks_nothing_of_its_server() -> Result<(), StatementError> {
assert_eq!(Sqlite.server_version(), None);
Sqlite.check_server(&LEASED, "3.50.4")?;
Ok(())
}