mirror of
https://github.com/tursodatabase/libsql.git
synced 2025-05-31 00:03:47 +00:00
Fix namespace deletion (#1211)
* fix schema deletion * optional ns delete payload
This commit is contained in:
libsql-server
@ -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();
|
||||
}
|
||||
|
5
libsql-server/tests/namespaces/snapshots/tests__namespaces__shared_schema__schema_deletion-2.snap
Normal file
5
libsql-server/tests/namespaces/snapshots/tests__namespaces__shared_schema__schema_deletion-2.snap
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: libsql-server/tests/namespaces/shared_schema.rs
|
||||
expression: resp.body_string().await.unwrap()
|
||||
---
|
||||
"{\"error\":\"Namespace `ns1` doesn't exist\"}"
|
5
libsql-server/tests/namespaces/snapshots/tests__namespaces__shared_schema__schema_deletion-3.snap
Normal file
5
libsql-server/tests/namespaces/snapshots/tests__namespaces__shared_schema__schema_deletion-3.snap
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: libsql-server/tests/namespaces/shared_schema.rs
|
||||
expression: resp.body_string().await.unwrap()
|
||||
---
|
||||
"{\"error\":\"Namespace `ns2` doesn't exist\"}"
|
5
libsql-server/tests/namespaces/snapshots/tests__namespaces__shared_schema__schema_deletion.snap
Normal file
5
libsql-server/tests/namespaces/snapshots/tests__namespaces__shared_schema__schema_deletion.snap
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: libsql-server/tests/namespaces/shared_schema.rs
|
||||
expression: resp.body_string().await.unwrap()
|
||||
---
|
||||
"{\"error\":\"Namespace `schema` doesn't exist\"}"
|
Reference in New Issue
Block a user