Skip to content

Commit 82cd274

Browse files
authored
Shared schema (#1080)
* Rename SharedSchemaError to SharedSchemaCreationError Signed-off-by: Piotr Jastrzebski <piotr@chiselstrike.com> * Improve NamespaceStore::exists Signed-off-by: Piotr Jastrzebski <piotr@chiselstrike.com> * Metastore: Implement shared schema links Signed-off-by: Piotr Jastrzebski <piotr@chiselstrike.com> --------- Signed-off-by: Piotr Jastrzebski <piotr@chiselstrike.com>
1 parent e60a4e3 commit 82cd274

4 files changed

Lines changed: 61 additions & 22 deletions

File tree

libsql-server/src/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ pub enum Error {
102102
#[error("Unable to decode protobuf: {0}")]
103103
ProstDecode(#[from] prost::DecodeError),
104104
#[error("Shared schema error: {0}")]
105-
SharedSchemaError(String),
105+
SharedSchemaCreationError(String),
106106
}
107107

108108
impl AsRef<Self> for Error {
@@ -180,7 +180,7 @@ impl IntoResponse for &Error {
180180
MetaStoreUpdateFailure(_) => self.format_err(StatusCode::INTERNAL_SERVER_ERROR),
181181
Ref(this) => this.as_ref().into_response(),
182182
ProstDecode(_) => self.format_err(StatusCode::INTERNAL_SERVER_ERROR),
183-
SharedSchemaError(_) => self.format_err(StatusCode::BAD_REQUEST),
183+
SharedSchemaCreationError(_) => self.format_err(StatusCode::BAD_REQUEST),
184184
}
185185
}
186186
}

libsql-server/src/http/admin/mod.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ struct CreateNamespaceReq {
286286
shared_schema: bool,
287287
/// If some, this is a [NamespaceName] reference to a shared schema DB.
288288
#[serde(default)]
289-
shared_schema_name: Option<String>,
289+
shared_schema_name: Option<NamespaceName>,
290290
}
291291

292292
async fn handle_create_namespace<M: MakeNamespace, C: Connector>(
@@ -300,15 +300,15 @@ async fn handle_create_namespace<M: MakeNamespace, C: Connector>(
300300
}
301301
let shared_schema_name = if let Some(ns) = req.shared_schema_name {
302302
if req.shared_schema {
303-
return Err(Error::SharedSchemaError(
303+
return Err(Error::SharedSchemaCreationError(
304304
"shared schema database cannot reference another shared schema".to_string(),
305305
));
306306
}
307-
let namespace = NamespaceName::from_string(ns)?;
308-
if !app_state.namespaces.exists(&namespace).await {
309-
return Err(Error::NamespaceDoesntExist(namespace.to_string()));
307+
// TODO: move this check into meta store
308+
if !app_state.namespaces.exists(&ns) {
309+
return Err(Error::NamespaceDoesntExist(ns.to_string()));
310310
}
311-
Some(namespace.to_string())
311+
Some(ns)
312312
} else {
313313
None
314314
};
@@ -334,7 +334,7 @@ async fn handle_create_namespace<M: MakeNamespace, C: Connector>(
334334
let mut config = (*store.get()).clone();
335335

336336
config.is_shared_schema = req.shared_schema;
337-
config.shared_schema_name = shared_schema_name;
337+
config.shared_schema_name = shared_schema_name.as_ref().map(|x| x.to_string());
338338
if let Some(max_db_size) = req.max_db_size {
339339
config.max_db_pages = max_db_size.as_u64() / LIBSQL_PAGE_SIZE;
340340
}
@@ -379,6 +379,7 @@ async fn handle_fork_namespace<M: MakeNamespace, C>(
379379
let mut to_config = (*to_store.get()).clone();
380380
to_config.max_db_pages = from_config.max_db_pages;
381381
to_config.heartbeat_url = from_config.heartbeat_url.clone();
382+
to_config.shared_schema_name = from_config.shared_schema_name.clone();
382383
to_store.store(to_config).await?;
383384
Ok(())
384385
}

libsql-server/src/namespace/meta_store.rs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ impl MetaStoreInner {
120120
Sqlite3WalManager::default(),
121121
);
122122
let conn = open_conn_active_checkpoint(&db_path, wal_manager.clone(), None, 1000, None)?;
123+
conn.execute("PRAGMA foreign_keys=ON", ())?;
123124
conn.execute(
124125
"CREATE TABLE IF NOT EXISTS namespace_configs (
125126
namespace TEXT NOT NULL PRIMARY KEY,
@@ -128,6 +129,17 @@ impl MetaStoreInner {
128129
",
129130
(),
130131
)?;
132+
conn.execute(
133+
"CREATE TABLE IF NOT EXISTS shared_schema_links (
134+
shared_schema_name TEXT NOT NULL,
135+
namespace TEXT NOT NULL,
136+
PRIMARY KEY (shared_schema_name, namespace),
137+
FOREIGN KEY (shared_schema_name) REFERENCES namespace_configs (namespace) ON DELETE RESTRICT ON UPDATE RESTRICT,
138+
FOREIGN KEY (namespace) REFERENCES namespace_configs (namespace) ON DELETE RESTRICT ON UPDATE RESTRICT
139+
)
140+
",
141+
(),
142+
)?;
131143

132144
let mut this = MetaStoreInner {
133145
configs: Default::default(),
@@ -249,10 +261,27 @@ fn process(msg: ChangeMsg, inner: Arc<Mutex<MetaStoreInner>>) -> Result<()> {
249261

250262
let inner = &mut inner.lock();
251263

252-
inner.conn.execute(
253-
"INSERT OR REPLACE INTO namespace_configs (namespace, config) VALUES (?1, ?2)",
254-
rusqlite::params![namespace.as_str(), config_encoded],
255-
)?;
264+
if let Some(schema) = config.shared_schema_name.as_ref() {
265+
let tx = inner.conn.transaction()?;
266+
tx.execute(
267+
"INSERT OR REPLACE INTO namespace_configs (namespace, config) VALUES (?1, ?2)",
268+
rusqlite::params![namespace.as_str(), config_encoded],
269+
)?;
270+
tx.execute(
271+
"DELETE FROM shared_schema_links WHERE namespace = ?",
272+
rusqlite::params![namespace.as_str()],
273+
)?;
274+
tx.execute(
275+
"INSERT OR REPLACE INTO shared_schema_links (shared_schema_name, namespace) VALUES (?1, ?2)",
276+
rusqlite::params![schema.as_str(), namespace.as_str()],
277+
)?;
278+
tx.commit()?;
279+
} else {
280+
inner.conn.execute(
281+
"INSERT OR REPLACE INTO namespace_configs (namespace, config) VALUES (?1, ?2)",
282+
rusqlite::params![namespace.as_str(), config_encoded],
283+
)?;
284+
}
256285

257286
let configs = &mut inner.configs;
258287

@@ -322,18 +351,28 @@ impl MetaStore {
322351
tracing::debug!("removing namespace `{}` from meta store", namespace);
323352

324353
let mut guard = self.inner.lock();
325-
guard.conn.execute(
326-
"DELETE FROM namespace_configs WHERE namespace = ?",
327-
[namespace.as_str()],
328-
)?;
329-
if let Some(sender) = guard.configs.remove(&namespace) {
354+
let r = if let Some(sender) = guard.configs.get(&namespace) {
330355
tracing::debug!("removed namespace `{}` from meta store", namespace);
331356
let config = sender.borrow().clone();
357+
let tx = guard.conn.transaction()?;
358+
if let Some(shared_schema) = config.config.shared_schema_name.as_deref() {
359+
tx.execute(
360+
"DELETE FROM shared_schema_links WHERE shared_schema_name = ? AND namespace = ?",
361+
[shared_schema, namespace.as_str()],
362+
)?;
363+
}
364+
tx.execute(
365+
"DELETE FROM namespace_configs WHERE namespace = ?",
366+
[namespace.as_str()],
367+
)?;
368+
tx.commit()?;
332369
Ok(Some(config.config))
333370
} else {
334371
tracing::trace!("namespace `{}` not found in meta store", namespace);
335372
Ok(None)
336-
}
373+
};
374+
guard.configs.remove(&namespace);
375+
r
337376
}
338377

339378
// TODO: we need to either make sure that the metastore is restored

libsql-server/src/namespace/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -458,9 +458,8 @@ impl<M: MakeNamespace> NamespaceStore<M> {
458458
})
459459
}
460460

461-
pub async fn exists(&self, namespace: &NamespaceName) -> bool {
462-
let e = self.inner.store.get(namespace).await;
463-
e.is_some()
461+
pub fn exists(&self, namespace: &NamespaceName) -> bool {
462+
self.inner.metadata.exists(namespace)
464463
}
465464

466465
pub async fn destroy(&self, namespace: NamespaceName) -> crate::Result<()> {

0 commit comments

Comments
 (0)