0
0
mirror of https://github.com/tursodatabase/libsql.git synced 2025-05-31 00:03:47 +00:00

Fix namespace deletion ()

* fix schema deletion

* optional ns delete payload
This commit is contained in:
ad hoc
2024-03-14 12:35:20 +01:00
committed by GitHub
parent 42c25205ee
commit a2077c54ab
9 changed files with 139 additions and 9 deletions

@ -116,6 +116,8 @@ pub enum Error {
PendingMigrationOnSchema(NamespaceName),
#[error("couldn't find requested migration job")]
MigrationJobNotFound,
#[error("cannot delete `{0}` because databases are still refering to it")]
HasLinkedDbs(NamespaceName),
}
impl AsRef<Self> for Error {
@ -198,6 +200,7 @@ impl IntoResponse for &Error {
Migration(e) => e.into_response(),
PendingMigrationOnSchema(_) => self.format_err(StatusCode::BAD_REQUEST),
MigrationJobNotFound => self.format_err(StatusCode::NOT_FOUND),
HasLinkedDbs(_) => self.format_err(StatusCode::BAD_REQUEST),
}
}
}

@ -412,11 +412,16 @@ struct DeleteNamespaceReq {
async fn handle_delete_namespace<C>(
State(app_state): State<Arc<AppState<C>>>,
Path(namespace): Path<String>,
Json(req): Json<DeleteNamespaceReq>,
payload: Option<Json<DeleteNamespaceReq>>,
) -> crate::Result<()> {
let prune_all = match payload {
Some(req) => !req.keep_backup,
None => true,
};
app_state
.namespaces
.destroy(NamespaceName::from_string(namespace)?, !req.keep_backup)
.destroy(NamespaceName::from_string(namespace)?, prune_all)
.await?;
Ok(())
}

@ -431,12 +431,18 @@ impl MetaStore {
tracing::debug!("removed namespace `{}` from meta store", namespace);
let config = sender.borrow().clone();
let tx = guard.conn.transaction()?;
if config.config.is_shared_schema {
if crate::schema::db::schema_has_linked_dbs(&tx, &namespace)? {
return Err(crate::Error::HasLinkedDbs(namespace.clone()));
}
}
if let Some(ref shared_schema) = config.config.shared_schema_name {
if crate::schema::db::has_pending_migration_jobs(&tx, shared_schema)? {
return Err(crate::Error::PendingMigrationOnSchema(
shared_schema.clone(),
));
}
tx.execute(
"DELETE FROM shared_schema_links WHERE shared_schema_name = ? AND namespace = ?",
(shared_schema.as_str(), namespace.as_str()),

@ -95,6 +95,15 @@ impl NamespaceStore {
if self.inner.has_shutdown.load(Ordering::Relaxed) {
return Err(Error::NamespaceStoreShutdown);
}
// destroy on-disk database and backups
// FIXME: this is blocking
let db_config = self
.inner
.metadata
.remove(namespace.clone())?
.ok_or_else(|| crate::Error::NamespaceDoesntExist(namespace.to_string()))?;
let mut bottomless_db_id_init = NamespaceBottomlessDbIdInit::FetchFromConfig;
if let Some(ns) = self.inner.store.remove(&namespace).await {
// deallocate in-memory resources
@ -106,13 +115,6 @@ impl NamespaceStore {
}
}
// destroy on-disk database and backups
// FIXME: this is blocking
let db_config = self
.inner
.metadata
.remove(namespace.clone())?
.ok_or_else(|| crate::Error::NamespaceDoesntExist(namespace.to_string()))?;
Namespace::cleanup(
&self.inner.config,
&namespace,

@ -95,6 +95,19 @@ pub(crate) fn has_pending_migration_jobs(
Ok(has_pending)
}
pub(crate) fn schema_has_linked_dbs(
conn: &rusqlite::Connection,
schema: &NamespaceName,
) -> Result<bool, Error> {
let has_linked = conn.query_row(
"SELECT count(1) FROM (SELECT 0 FROM shared_schema_links WHERE shared_schema_name = ? LIMIT 1)",
[schema.as_str()],
|row| Ok(row.get::<_, usize>(0)? != 0),
)?;
Ok(has_linked)
}
/// Create a migration job, and returns the job_id
pub(super) fn register_schema_migration_job(
conn: &mut rusqlite::Connection,

@ -738,3 +738,89 @@ fn check_migration_perms() {
sim.run().unwrap();
}
#[test]
fn schema_deletion() {
let mut sim = Builder::new()
.simulation_duration(Duration::from_secs(100000))
.build();
let tmp = tempdir().unwrap();
make_primary(&mut sim, tmp.path().to_path_buf());
sim.client("client", async {
let client = Client::new();
client
.post(
"http://primary:9090/v1/namespaces/schema/create",
json!({"shared_schema": true }),
)
.await
.unwrap();
client
.post(
"http://primary:9090/v1/namespaces/ns1/create",
json!({"shared_schema_name": "schema" }),
)
.await
.unwrap();
client
.post(
"http://primary:9090/v1/namespaces/ns2/create",
json!({"shared_schema_name": "schema" }),
)
.await
.unwrap();
let resp = client
.delete("http://primary:9090/v1/namespaces/schema", json!({}))
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp = client
.delete("http://primary:9090/v1/namespaces/ns1", json!({}))
.await
.unwrap();
assert!(resp.status().is_success());
let resp = client
.delete("http://primary:9090/v1/namespaces/schema", json!({}))
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp = client
.delete("http://primary:9090/v1/namespaces/ns2", json!({}))
.await
.unwrap();
assert!(resp.status().is_success());
let resp = client
.delete("http://primary:9090/v1/namespaces/schema", json!({}))
.await
.unwrap();
assert!(resp.status().is_success());
let resp = client
.get("http://primary:9090/v1/namespaces/schema/config")
.await
.unwrap();
assert_debug_snapshot!(resp.body_string().await.unwrap());
let resp = client
.get("http://primary:9090/v1/namespaces/ns1/config")
.await
.unwrap();
assert_debug_snapshot!(resp.body_string().await.unwrap());
let resp = client
.get("http://primary:9090/v1/namespaces/ns2/config")
.await
.unwrap();
assert_debug_snapshot!(resp.body_string().await.unwrap());
Ok(())
});
sim.run().unwrap();
}

@ -0,0 +1,5 @@
---
source: libsql-server/tests/namespaces/shared_schema.rs
expression: resp.body_string().await.unwrap()
---
"{\"error\":\"Namespace `ns1` doesn't exist\"}"

@ -0,0 +1,5 @@
---
source: libsql-server/tests/namespaces/shared_schema.rs
expression: resp.body_string().await.unwrap()
---
"{\"error\":\"Namespace `ns2` doesn't exist\"}"

@ -0,0 +1,5 @@
---
source: libsql-server/tests/namespaces/shared_schema.rs
expression: resp.body_string().await.unwrap()
---
"{\"error\":\"Namespace `schema` doesn't exist\"}"