Skip to main content
Glama

Convex MCP server

Official
by get-convex
table_mapping.rs15.9 kB
use std::collections::BTreeMap; use anyhow::Context; use errors::ErrorMetadata; use imbl::OrdMap; use serde::Serialize; use crate::{ DeveloperDocumentId, TableName, TableNumber, TabletId, TabletIdAndTableNumber, }; #[cfg_attr(any(test, feature = "testing"), derive(proptest_derive::Arbitrary))] #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)] pub enum TableNamespace { /// For tables that have a single global namespace, e.g. _tables, _index, /// _db. /// Also for tables in the root component, including system tables. Global, /// Some tables are namespaced by component, like user tables, /// _file_storage, etc. ByComponent(DeveloperDocumentId), } impl TableNamespace { /// Default namespace for user tables in tests. /// Ideally we should be able to change this to a different namespace /// without any test failures. #[cfg(any(test, feature = "testing"))] pub const fn test_user() -> Self { Self::Global } #[cfg(any(test, feature = "testing"))] pub const fn test_component() -> Self { Self::ByComponent(DeveloperDocumentId::MIN) } /// Use this to make it clear that a table pertains to the root component. /// It doesn't extend between components like a plain Global. /// This is useful for code searching. pub const fn root_component() -> Self { Self::Global } /// Namespace that should be replaced with RootComponent or ByComponent, /// but for now uses Global. For easy searching. #[allow(non_snake_case)] pub const fn by_component_TODO() -> Self { Self::Global } /// Namespace that should be passed down, and could be Global, ByComponent, /// or ByComponentDefinition. #[allow(non_snake_case)] pub const fn TODO() -> Self { Self::Global } } // This TableMapping contains the mapping between TableNames and // TabletIdAndTableNumber. This only includes active tables and hidden tables // (i.e. not deleted tables). // Use is_active to determine if a table is active. #[derive(Clone, Debug, PartialEq, Eq)] pub struct TableMapping { /// Maps from tablet to number and name exist for all tablets. tablet_to_table: OrdMap<TabletId, (TableNamespace, TableNumber, TableName)>, /// Maps from number and name only exist for active tablets, /// because other tablets might have conflicting numbers/names. table_name_to_canonical_tablet: OrdMap<TableNamespace, OrdMap<TableName, TabletId>>, table_number_to_canonical_tablet: OrdMap<TableNamespace, OrdMap<TableNumber, TabletId>>, } #[derive(Clone, Debug, PartialEq)] pub struct NamespacedTableMapping { namespace: TableNamespace, /// Maps from tablet to number and name exist for all tablets. tablet_to_table: OrdMap<TabletId, (TableNamespace, TableNumber, TableName)>, /// Maps from number and name only exist for active tablets, /// because other tablets might have conflicting numbers/names. table_name_to_canonical_tablet: OrdMap<TableName, TabletId>, table_number_to_canonical_tablet: OrdMap<TableNumber, TabletId>, } impl TableMapping { pub fn new() -> Self { Self { tablet_to_table: Default::default(), table_name_to_canonical_tablet: Default::default(), table_number_to_canonical_tablet: Default::default(), } } pub fn iter( &self, ) -> impl Iterator<Item = (TabletId, TableNamespace, TableNumber, &TableName)> { self.tablet_to_table .iter() .map(|(table_id, (namespace, table_number, table_name))| { (*table_id, *namespace, *table_number, table_name) }) } pub fn iter_active_user_tables( &self, ) -> impl Iterator<Item = (TabletId, TableNamespace, TableNumber, &TableName)> { self.tablet_to_table .iter() .filter(|(tablet_id, (_, _, name))| !name.is_system() && self.is_active(**tablet_id)) .map(|(table_id, (namespace, table_number, table_name))| { (*table_id, *namespace, *table_number, table_name) }) } pub fn iter_active_namespaces( &self, ) -> impl Iterator<Item = (&TableNamespace, &OrdMap<TableName, TabletId>)> { self.table_name_to_canonical_tablet.iter() } pub fn namespaces_for_name(&self, name: &TableName) -> Vec<TableNamespace> { self.iter() .filter(|(_, _, _, table_name)| *table_name == name) .map(|(_, namespace, ..)| namespace) .collect() } pub fn insert( &mut self, tablet_id: TabletId, namespace: TableNamespace, table_number: TableNumber, name: TableName, ) { self.insert_tablet(tablet_id, namespace, table_number, name.clone()); self.table_name_to_canonical_tablet .entry(namespace) .or_default() .insert(name, tablet_id); self.table_number_to_canonical_tablet .entry(namespace) .or_default() .insert(table_number, tablet_id); } /// A tablet has mappings ID -> number, and ID -> name but not necessarily /// the reverse mappings. pub fn insert_tablet( &mut self, tablet_id: TabletId, namespace: TableNamespace, table_number: TableNumber, name: TableName, ) { self.tablet_to_table .insert(tablet_id, (namespace, table_number, name)); } /// Removes tablet for active table. pub fn remove(&mut self, tablet_id: TabletId) { let Some((namespace, number, name)) = self.tablet_to_table.remove(&tablet_id) else { panic!("{tablet_id} does not exist"); }; if self .table_name_to_canonical_tablet .get(&namespace) .and_then(|m| m.get(&name)) == Some(&tablet_id) { self.table_name_to_canonical_tablet .entry(namespace) .or_default() .remove(&name); } if self .table_number_to_canonical_tablet .get(&namespace) .and_then(|m| m.get(&number)) == Some(&tablet_id) { self.table_number_to_canonical_tablet .entry(namespace) .or_default() .remove(&number); } } pub fn id_exists(&self, id: TabletId) -> bool { self.tablet_to_table.contains_key(&id) } pub fn tablet_id_exists(&self, id: TabletId) -> bool { self.tablet_to_table.contains_key(&id) } pub fn tablet_name(&self, id: TabletId) -> anyhow::Result<TableName> { self.tablet_to_table .get(&id) .map(|(_, _, name)| name.clone()) .with_context(|| format!("cannot find table {id:?}")) } pub fn tablet_number(&self, id: TabletId) -> anyhow::Result<TableNumber> { self.tablet_to_table .get(&id) .map(|(_, number, ..)| *number) .with_context(|| format!("cannot find table {id:?}")) } pub fn tablet_namespace(&self, id: TabletId) -> anyhow::Result<TableNamespace> { self.tablet_to_table .get(&id) .map(|(namespace, ..)| *namespace) .with_context(|| format!("cannot find table {id:?}")) } pub fn get_table_metadata( &self, id: TabletId, ) -> anyhow::Result<&(TableNamespace, TableNumber, TableName)> { self.tablet_to_table .get(&id) .with_context(|| format!("cannot find table {id:?}")) } pub fn tablet_to_name(&self) -> impl Fn(TabletId) -> anyhow::Result<TableName> + '_ { |id| self.tablet_name(id) } /// Assuming all system tables are in the table mapping, /// does a table id correspond to a system table? pub fn is_system_tablet(&self, tablet_id: TabletId) -> bool { match self.tablet_to_table.get(&tablet_id) { Some((_, _, t)) => t.is_system(), None => false, } } pub fn update(&mut self, other: TableMapping) { for (table_id, (namespace, table_number, table_name)) in other.tablet_to_table.into_iter() { self.insert(table_id, namespace, table_number, table_name); } } pub fn is_active(&self, tablet_id: TabletId) -> bool { let Some((namespace, table_number, _)) = self.tablet_to_table.get(&tablet_id) else { return false; }; let Some(active_tablet_id) = self .table_number_to_canonical_tablet .get(namespace) .and_then(|m| m.get(table_number)) else { return false; }; tablet_id == *active_tablet_id } pub fn len(&self) -> usize { self.tablet_to_table.len() } pub fn is_empty(&self) -> bool { self.tablet_to_table.is_empty() } pub fn namespace(&self, namespace: TableNamespace) -> NamespacedTableMapping { NamespacedTableMapping { namespace, tablet_to_table: self.tablet_to_table.clone(), table_name_to_canonical_tablet: self .table_name_to_canonical_tablet .get(&namespace) .cloned() .unwrap_or_default(), table_number_to_canonical_tablet: self .table_number_to_canonical_tablet .get(&namespace) .cloned() .unwrap_or_default(), } } } impl NamespacedTableMapping { pub fn namespace(&self) -> TableNamespace { self.namespace } pub fn iter(&self) -> impl Iterator<Item = (TabletId, TableNumber, &TableName)> { self.tablet_to_table .iter() .filter(|(_, (namespace, ..))| namespace == &self.namespace) .map(|(table_id, (_, table_number, table_name))| (*table_id, *table_number, table_name)) } pub fn iter_active_user_tables( &self, ) -> impl Iterator<Item = (TabletId, TableNumber, &TableName)> { self.iter() .filter(|(tablet_id, _, name)| !name.is_system() && self.is_active(*tablet_id)) } pub fn id(&self, name: &TableName) -> anyhow::Result<TabletIdAndTableNumber> { self.id_and_number_if_exists(name) .ok_or_else(|| anyhow::anyhow!("cannot find table {name:?}")) } pub fn id_if_exists(&self, name: &TableName) -> Option<TabletId> { self.table_name_to_canonical_tablet.get(name).cloned() } pub fn id_and_number_if_exists(&self, name: &TableName) -> Option<TabletIdAndTableNumber> { let table_id = self.id_if_exists(name)?; let (_, number, _) = self.tablet_to_table.get(&table_id)?; Some(TabletIdAndTableNumber { tablet_id: table_id, table_number: *number, }) } pub fn id_exists(&self, id: TabletId) -> bool { self.tablet_to_table.contains_key(&id) } pub fn tablet_id_exists(&self, id: TabletId) -> bool { self.tablet_to_table.contains_key(&id) } pub fn table_number_exists(&self) -> impl Fn(TableNumber) -> bool + '_ { |n| self.table_number_to_canonical_tablet.contains_key(&n) } pub fn name_by_number_if_exists(&self, number: TableNumber) -> Option<&TableName> { self.table_number_to_canonical_tablet .get(&number) .and_then(|id| self.tablet_to_table.get(id)) .map(|(_, _, name)| name) } pub fn name_exists(&self, name: &TableName) -> bool { self.table_name_to_canonical_tablet.contains_key(name) } pub fn name_to_id(&self) -> impl Fn(TableName) -> anyhow::Result<TabletIdAndTableNumber> + '_ { |name| self.id(&name) } pub fn name_to_tablet(&self) -> impl Fn(TableName) -> anyhow::Result<TabletId> + '_ { |name| self.id(&name).map(|id| id.tablet_id) } pub fn tablet_name(&self, id: TabletId) -> anyhow::Result<TableName> { self.tablet_to_table .get(&id) .map(|(_, _, name)| name.clone()) .ok_or_else(|| anyhow::anyhow!("cannot find table {id:?}")) } pub fn tablet_to_name(&self) -> impl Fn(TabletId) -> anyhow::Result<TableName> + '_ { |id| self.tablet_name(id) } /// When the user inputs a TableName and we don't know whether it exists, /// throw a developer error if it doesn't exist. pub fn name_to_id_user_input( &self, ) -> impl Fn(TableName) -> anyhow::Result<TabletIdAndTableNumber> + '_ { |name| { let Some(id) = self.id_and_number_if_exists(&name) else { anyhow::bail!(table_does_not_exist(&name)); }; Ok(id) } } /// When the user inputs a TableName and we don't know whether it exists, /// throw a developer error if it doesn't exist. pub fn name_to_number_user_input( &self, ) -> impl Fn(TableName) -> anyhow::Result<TableNumber> + '_ { |name| { let table_id = self.name_to_id_user_input()(name)?; Ok(table_id.table_number) } } /// Assuming all system tables are in the table mapping, /// does a table id correspond to a system table? pub fn is_system_tablet(&self, tablet_id: TabletId) -> bool { match self.tablet_to_table.get(&tablet_id) { Some((_, _, t)) => t.is_system(), None => false, } } pub fn is_active(&self, tablet_id: TabletId) -> bool { let Some((_, table_number, _)) = self.tablet_to_table.get(&tablet_id) else { return false; }; let Some(active_tablet_id) = self.table_number_to_canonical_tablet.get(table_number) else { return false; }; tablet_id == *active_tablet_id } pub fn tablet_matches_name(&self, tablet_id: TabletId, name: &TableName) -> bool { match self.tablet_to_table.get(&tablet_id) { Some((_, _, table_name)) => name == table_name, None => false, } } pub fn tablet_number(&self, id: TabletId) -> anyhow::Result<TableNumber> { self.tablet_to_table .get(&id) .map(|(_, number, ..)| *number) .ok_or_else(|| anyhow::anyhow!("cannot find table {id:?}")) } pub fn number_to_name(&self) -> impl Fn(TableNumber) -> anyhow::Result<TableName> + '_ { |table_number| { self.table_number_to_canonical_tablet .get(&table_number) .and_then(|id| self.tablet_to_table.get(id)) .map(|(_, _, name)| name.clone()) .ok_or_else(|| anyhow::anyhow!("cannot find table {table_number:?}")) } } pub fn number_to_tablet(&self) -> impl Fn(TableNumber) -> anyhow::Result<TabletId> + '_ { |table_number| { self.table_number_to_canonical_tablet .get(&table_number) .ok_or_else(|| anyhow::anyhow!("cannot find table with id {table_number}")) .copied() } } pub fn to_value(self, with_system_tables: bool) -> TableMappingValue { TableMappingValue( self.iter() .filter(|(tablet_id, ..)| self.is_active(*tablet_id)) .filter(|(_, _, name)| with_system_tables || !name.is_system()) .map(|(_, number, name)| (number, name.clone())) .collect(), ) } } fn table_does_not_exist(table: &TableName) -> ErrorMetadata { ErrorMetadata::bad_request("TableDoesNotExist", format!("Table '{table}' not found")) } /// The table mapping that is sent to the dashboard through the /// `getTableMapping` operation. It omits system tables. #[derive(Serialize)] pub struct TableMappingValue(BTreeMap<TableNumber, TableName>);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server