use crate::pg_common::{qualified_table, quote_identifier};
pub(super) fn bootstrap_ddl(schema: &str) -> String {
let schema_q = quote_identifier(schema);
let table = qualified_table(schema, "blobs");
format!(
"CREATE SCHEMA IF NOT EXISTS {schema_q};
CREATE TABLE IF NOT EXISTS {table} (
\"key\" bytea PRIMARY KEY,
value bytea NOT NULL,
size bigint NOT NULL
) WITH (fillfactor = 90);
"
)
}
pub(super) fn gc_ddl(schema: &str) -> String {
let schema_q = quote_identifier(schema);
let pins = qualified_table(schema, "gc_pins");
let window = qualified_table(schema, "gc_window");
format!(
"CREATE SCHEMA IF NOT EXISTS {schema_q};
CREATE TABLE IF NOT EXISTS {pins} (
key bytea NOT NULL,
owner bytea NOT NULL,
expiry bigint NOT NULL,
PRIMARY KEY (key, owner)
);
CREATE TABLE IF NOT EXISTS {window} (
key bytea PRIMARY KEY,
visible_after bigint NOT NULL
);
"
)
}
#[cfg(test)]
mod tests {
use crate::backend::{Backend, StagingCursor};
use crate::Error;
use crate::pg_common::test_support::SchemaJanitor;
use crate::pg_common::PgOpts;
use crate::postgres::testing::{
fresh_schema, harness_dsn, harness_schemaless, skip_note, stored_value, CONTENT,
};
use crate::postgres::PostgresBackend;
#[tokio::test]
async fn virgin_store_no_schema_yet_reads_are_noops() {
let Some(backend) = harness_schemaless(" virgin").await else {
return;
};
let key = b"\x01opaque-key".to_vec();
assert!(
!backend.has(&key).await.unwrap(),
"has on a schema-less database is a no-op read"
);
assert!(
backend.get(&key).await.unwrap().is_none(),
"get is a no-op read"
);
assert_eq!(
backend.size(&key).await.unwrap(),
None,
"size is a no-op read"
);
assert!(
backend.list().await.unwrap().is_empty(),
"list is empty on a schema-less database"
);
assert_eq!(
backend.delete(&key).await.unwrap_err(),
Error::Missing,
"delete on a schema-less database reports Missing"
);
assert_eq!(
backend
.delete_batch(std::slice::from_ref(&key))
.await
.unwrap(),
0,
"the batch-delete helper on a schema-less database removes nothing"
);
assert!(
backend.put(&key, CONTENT).await.is_err(),
"the tolerance is read-only: writes surface the engine error"
);
let mut staging = backend.put_staged().await.unwrap();
staging.append(CONTENT).await.unwrap();
let err = staging.commit(&key).await;
assert!(
err.is_err(),
"a staged commit on a schema-less database surfaces the engine error"
);
assert!(
backend.list().await.unwrap().is_empty(),
"the failed writes left zero residue"
);
}
#[tokio::test]
async fn bootstrap_creates_the_b5_row_shape_idempotently() {
let Some(dsn) = harness_dsn() else {
skip_note();
return;
};
let schema = fresh_schema("bootstrap");
let opts = PgOpts {
schema: schema.clone(),
..Default::default()
};
let first = PostgresBackend::new(&dsn, opts.clone()).await.unwrap();
let second = PostgresBackend::new(&dsn, opts).await.unwrap();
let _janitor = SchemaJanitor::new(&dsn, &schema);
let client = first.pool.get().await.unwrap();
let columns: Vec<String> = client
.query(
"SELECT data_type::text FROM information_schema.columns \
WHERE table_schema = $1 AND table_name = 'blobs' \
ORDER BY ordinal_position",
&[&schema],
)
.await
.unwrap()
.iter()
.map(|row| row.get::<_, String>(0))
.collect();
assert_eq!(
columns,
["bytea", "bytea", "bigint"],
"the B5 row shape: key/value/size column types"
);
let reloptions: String = client
.query_one(
"SELECT reloptions::text FROM pg_class \
WHERE relname = 'blobs' \
AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)",
&[&schema],
)
.await
.unwrap()
.get(0);
assert_eq!(
reloptions, "{fillfactor=90}",
"the measured fillfactor posture is stamped on the table"
);
let key = b"\x01bootstrap-key".to_vec();
let backend = &first;
backend.put(&key, CONTENT).await.unwrap();
assert!(second.has(&key).await.unwrap(), "the second open sees it");
}
#[tokio::test]
async fn hostile_schema_name_is_quoted_safely() {
let Some(dsn) = harness_dsn() else {
skip_note();
return;
};
let schema = fresh_schema("we\"ird");
let backend = PostgresBackend::new(
&dsn,
PgOpts {
schema: schema.clone(),
..Default::default()
},
)
.await
.expect("open with a hostile schema name");
let _janitor = SchemaJanitor::new(&dsn, &schema);
let key = b"\x01hostile-schema-key".to_vec();
backend.put(&key, CONTENT).await.unwrap();
assert!(backend.has(&key).await.unwrap());
assert_eq!(stored_value(&backend, &key).await.as_deref(), Some(CONTENT));
assert_eq!(
backend.size(&key).await.unwrap(),
Some(CONTENT.len() as u64)
);
assert_eq!(backend.list().await.unwrap(), vec![key.clone()]);
backend.delete(&key).await.unwrap();
assert!(!backend.has(&key).await.unwrap());
}
}