Skip to content

Commit cb7b237

Browse files
ldanilekConvex, Inc.
authored andcommitted
allow virtual table and system table to share table numbers (#28008)
lift the requirement that virtual and physical tables have different table numbers, in one case: a virtual table and its corresponding physical table are allowed to have the same table number. this does not affect table number assignment, so new virtual tables and system tables will still have distinct table numbers. one potential concern is that `db.system.get` could see a virtual table id and think it's a physical system table id, because they have the same table number. This doesn't end up being a problem though because we always check if an ID is virtual before we try to look it up in the physical TableMapping. tested as part of #27538 which includes the migration to make the table numbers match. GitOrigin-RevId: 1558ece1048a9ea19abd1d87fdbfafc7fd424a3b
1 parent d05d85f commit cb7b237

File tree

7 files changed

+210
-13
lines changed

7 files changed

+210
-13
lines changed

crates/database/src/bootstrap_model/table.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,21 @@ impl<'a, RT: Runtime> TableModel<'a, RT> {
316316
let Some(table_number) = table_number else {
317317
return Ok(());
318318
};
319-
if self
319+
if let Ok(existing_virtual_table) = self
320320
.tx
321321
.virtual_table_mapping()
322322
.namespace(namespace)
323-
.table_number_exists()(table_number)
323+
.name(table_number)
324324
{
325+
let existing_system_table = self
326+
.tx
327+
.virtual_system_mapping()
328+
.virtual_to_system_table(&existing_virtual_table)?;
329+
if existing_system_table == table {
330+
// Setting physical table to have the same table number as its virtual table is
331+
// allowed.
332+
return Ok(());
333+
}
325334
anyhow::bail!(ErrorMetadata::bad_request(
326335
"TableConflict",
327336
format!("New table {table} has IDs that conflict with existing system table")

crates/database/src/database.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ impl<RT: Runtime> DatabaseSnapshot<RT> {
470470
table_mapping: TableMapping,
471471
table_states: OrdMap<TabletId, TableState>,
472472
index_registry: &IndexRegistry,
473+
virtual_system_mapping: VirtualSystemMapping,
473474
) -> anyhow::Result<TableRegistry> {
474475
let load_virtual_tables_timer = load_virtual_table_metadata_timer();
475476
let virtual_tables_table = table_mapping
@@ -492,6 +493,7 @@ impl<RT: Runtime> DatabaseSnapshot<RT> {
492493
table_states,
493494
persistence_snapshot.persistence().version(),
494495
virtual_table_mapping,
496+
virtual_system_mapping,
495497
)?;
496498
Self::verify_invariants(&table_registry, index_registry)?;
497499
Ok(table_registry)
@@ -564,6 +566,7 @@ impl<RT: Runtime> DatabaseSnapshot<RT> {
564566
persistence: Arc<dyn PersistenceReader>,
565567
snapshot: RepeatableTimestamp,
566568
retention_validator: Arc<dyn RetentionValidator>,
569+
virtual_system_mapping: VirtualSystemMapping,
567570
) -> anyhow::Result<Self> {
568571
let repeatable_persistence: RepeatablePersistence =
569572
RepeatablePersistence::new(persistence.clone(), snapshot, retention_validator.clone());
@@ -624,6 +627,7 @@ impl<RT: Runtime> DatabaseSnapshot<RT> {
624627
table_mapping,
625628
table_states,
626629
&index_registry,
630+
virtual_system_mapping,
627631
)
628632
.await?;
629633

@@ -795,6 +799,7 @@ impl<RT: Runtime> Database<RT> {
795799
reader.clone(),
796800
snapshot_ts,
797801
Arc::new(follower_retention_manager.clone()),
802+
virtual_system_mapping.clone(),
798803
)
799804
.await?;
800805
let max_ts = DatabaseSnapshot::<RT>::max_ts(&*reader).await?;

crates/database/src/table_registry.rs

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use value::{
3030
use crate::{
3131
defaults::bootstrap_system_tables,
3232
metrics::bootstrap_table_registry_timer,
33+
VirtualSystemMapping,
3334
VirtualTableMetadata,
3435
VIRTUAL_TABLES_TABLE,
3536
};
@@ -45,7 +46,10 @@ pub struct TableRegistry {
4546
table_mapping: TableMapping,
4647
persistence_version: PersistenceVersion,
4748

49+
/// Mapping from virtual table number to name.
4850
virtual_table_mapping: VirtualTableMapping,
51+
/// Mapping from virtual table name to corresponding system table name.
52+
virtual_system_mapping: VirtualSystemMapping,
4953
}
5054

5155
impl TableRegistry {
@@ -57,13 +61,15 @@ impl TableRegistry {
5761
table_states: OrdMap<TabletId, TableState>,
5862
persistence_version: PersistenceVersion,
5963
virtual_table_mapping: VirtualTableMapping,
64+
virtual_system_mapping: VirtualSystemMapping,
6065
) -> anyhow::Result<Self> {
6166
let _timer = bootstrap_table_registry_timer();
6267
Ok(Self {
6368
table_mapping,
6469
tablet_states: table_states,
6570
persistence_version,
6671
virtual_table_mapping,
72+
virtual_system_mapping,
6773
})
6874
}
6975

@@ -107,7 +113,11 @@ impl TableRegistry {
107113
if self.table_exists(metadata.namespace, &metadata.name) {
108114
anyhow::bail!("Tried to create duplicate table {new_value}");
109115
}
110-
self.validate_table_number(metadata.namespace, metadata.number)?;
116+
self.validate_table_number(
117+
metadata.namespace,
118+
metadata.number,
119+
&metadata.name,
120+
)?;
111121
}
112122
Some(TableUpdate {
113123
namespace: metadata.namespace,
@@ -195,7 +205,11 @@ impl TableRegistry {
195205
{
196206
anyhow::bail!("Tried to create duplicate virtual table {new_value}");
197207
}
198-
self.validate_table_number(metadata.namespace, metadata.number)?;
208+
self.validate_table_number(
209+
metadata.namespace,
210+
metadata.number,
211+
&metadata.name,
212+
)?;
199213
virtual_table_creation =
200214
Some((metadata.namespace, metadata.number, metadata.name));
201215
},
@@ -215,6 +229,7 @@ impl TableRegistry {
215229
&self,
216230
namespace: TableNamespace,
217231
table_number: TableNumber,
232+
table_name: &TableName,
218233
) -> anyhow::Result<()> {
219234
anyhow::ensure!(
220235
!self
@@ -224,14 +239,25 @@ impl TableRegistry {
224239
"Cannot add a table with table number {table_number} since it already exists in the \
225240
table mapping"
226241
);
227-
anyhow::ensure!(
228-
!self
229-
.virtual_table_mapping
230-
.namespace(namespace)
231-
.number_exists(table_number),
232-
"Cannot add a table with table number {table_number} since it already exists in the \
233-
virtual table mapping"
234-
);
242+
if let Ok(existing_virtual_table) = self
243+
.virtual_table_mapping
244+
.namespace(namespace)
245+
.name(table_number)
246+
{
247+
if self
248+
.virtual_system_mapping
249+
.virtual_to_system_table(&existing_virtual_table)?
250+
== table_name
251+
{
252+
// A virtual table can share a table number with its physical
253+
// table.
254+
} else {
255+
anyhow::bail!(
256+
"Cannot add a table with table number {table_number} since it already exists \
257+
in the virtual table mapping"
258+
);
259+
}
260+
}
235261
Ok(())
236262
}
237263

crates/database/src/tests/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ use crate::{
122122
TestFacingModel,
123123
Transaction,
124124
UserFacingModel,
125+
VirtualSystemMapping,
125126
};
126127

127128
mod randomized_search_tests;
@@ -1887,6 +1888,7 @@ async fn test_index_write(rt: TestRuntime) -> anyhow::Result<()> {
18871888
tp.reader(),
18881889
unchecked_repeatable_ts(ts),
18891890
retention_validator,
1891+
VirtualSystemMapping::default(),
18901892
)
18911893
.await?;
18921894
let index_metadata = database_snapshot.index_registry().clone();

crates/database/src/virtual_tables/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,22 @@ pub struct VirtualSystemMapping {
120120
system_to_virtual_doc_mapper: OrdMap<TableName, Arc<dyn VirtualSystemDocMapper>>,
121121
}
122122

123+
impl std::fmt::Debug for VirtualSystemMapping {
124+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125+
f.debug_struct("VirtualSystemMapping")
126+
.field("virtual_to_system", &self.virtual_to_system)
127+
.field("virtual_to_system_indexes", &self.virtual_to_system_indexes)
128+
.finish()
129+
}
130+
}
131+
132+
impl PartialEq for VirtualSystemMapping {
133+
fn eq(&self, other: &Self) -> bool {
134+
self.virtual_to_system == other.virtual_to_system
135+
&& self.virtual_to_system_indexes == other.virtual_to_system_indexes
136+
}
137+
}
138+
123139
impl VirtualSystemMapping {
124140
pub fn add_table(
125141
&mut self,

crates/function_runner/src/in_memory_indexes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ impl<RT: Runtime> InMemoryIndexCache<RT> {
334334
table_states,
335335
persistence_snapshot.persistence().version(),
336336
virtual_table_mapping,
337+
virtual_system_mapping(),
337338
)?;
338339
DatabaseSnapshot::<RT>::verify_invariants(&table_registry, &index_registry)?;
339340
let in_memory_indexes = FunctionRunnerInMemoryIndexes {

crates/model/src/file_storage/mod.rs

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,11 +355,51 @@ impl<'a, RT: Runtime> FileStorageModel<'a, RT> {
355355

356356
#[cfg(test)]
357357
mod tests {
358+
use std::collections::{
359+
BTreeMap,
360+
BTreeSet,
361+
};
362+
363+
use common::{
364+
document::ID_FIELD,
365+
query::{
366+
Order,
367+
Query,
368+
},
369+
};
370+
use database::{
371+
query::TableFilter,
372+
test_helpers::DbFixtures,
373+
DeveloperQuery,
374+
ImportFacingModel,
375+
ResolvedQuery,
376+
TableModel,
377+
};
358378
use pb::storage::FileStorageId as FileStorageIdProto;
359379
use proptest::prelude::*;
380+
use runtime::testing::TestRuntime;
360381
use sync_types::testing::assert_roundtrips;
382+
use uuid::Uuid;
383+
use value::{
384+
sha256::Sha256,
385+
val,
386+
ConvexObject,
387+
DeveloperDocumentId,
388+
TableNamespace,
389+
};
361390

362-
use super::FileStorageId;
391+
use super::{
392+
FileStorageId,
393+
FileStorageModel,
394+
};
395+
use crate::{
396+
file_storage::{
397+
types::FileStorageEntry,
398+
FILE_STORAGE_TABLE,
399+
FILE_STORAGE_VIRTUAL_TABLE,
400+
},
401+
test_helpers::DbFixturesWithModel,
402+
};
363403

364404
proptest! {
365405
#![proptest_config(
@@ -371,4 +411,102 @@ mod tests {
371411
assert_roundtrips::<FileStorageId, FileStorageIdProto>(left);
372412
}
373413
}
414+
415+
#[convex_macro::test_runtime]
416+
async fn test_file_storage_change_number_to_virtual(rt: TestRuntime) -> anyhow::Result<()> {
417+
let fixtures = DbFixtures::new_with_model(&rt).await?;
418+
let db = fixtures.db;
419+
let mut tx = db.begin_system().await?;
420+
FileStorageModel::new(&mut tx, TableNamespace::test_user())
421+
.store_file(FileStorageEntry {
422+
storage_id: Uuid::new_v4().into(),
423+
storage_key: "abc".try_into()?,
424+
sha256: Sha256::new().finalize(),
425+
size: 0,
426+
content_type: None,
427+
})
428+
.await?;
429+
db.commit_with_write_source(tx, "test").await?;
430+
431+
let mut tx = db.begin_system().await?;
432+
let virtual_table_number = tx
433+
.virtual_table_mapping()
434+
.namespace(TableNamespace::Global)
435+
.number(&FILE_STORAGE_VIRTUAL_TABLE)?;
436+
let table_id = TableModel::new(&mut tx)
437+
.insert_table_for_import(
438+
TableNamespace::test_user(),
439+
&FILE_STORAGE_TABLE,
440+
Some(virtual_table_number),
441+
&BTreeSet::new(),
442+
)
443+
.await?;
444+
let table_mapping_for_schema = tx.table_mapping().clone();
445+
let mut query = ResolvedQuery::new(
446+
&mut tx,
447+
TableNamespace::test_user(),
448+
Query::full_table_scan(FILE_STORAGE_TABLE.clone(), Order::Asc),
449+
)?;
450+
while let Some(doc) = query.next(&mut tx, None).await? {
451+
let physical_id = doc.developer_id();
452+
let virtual_id =
453+
DeveloperDocumentId::new(table_id.table_number, physical_id.internal_id());
454+
let mut object: BTreeMap<_, _> = doc.into_value().0.into();
455+
object.insert(ID_FIELD.clone().into(), val!(virtual_id.to_string()));
456+
457+
ImportFacingModel::new(&mut tx)
458+
.insert(
459+
table_id,
460+
&FILE_STORAGE_TABLE,
461+
ConvexObject::try_from(object)?,
462+
&table_mapping_for_schema,
463+
)
464+
.await?;
465+
}
466+
TableModel::new(&mut tx)
467+
.activate_table(
468+
table_id.tablet_id,
469+
&FILE_STORAGE_TABLE,
470+
table_id.table_number,
471+
&BTreeSet::new(),
472+
)
473+
.await?;
474+
db.commit_with_write_source(tx, "migrate_file_storage_table")
475+
.await?;
476+
477+
let mut tx = db.begin_system().await?;
478+
let mut count = 0;
479+
let virtual_table_number = tx
480+
.virtual_table_mapping()
481+
.namespace(TableNamespace::test_user())
482+
.number(&FILE_STORAGE_VIRTUAL_TABLE)?;
483+
let system_table_number = tx
484+
.table_mapping()
485+
.namespace(TableNamespace::test_user())
486+
.name_to_id()(FILE_STORAGE_TABLE.clone())?
487+
.table_number;
488+
assert_eq!(virtual_table_number, system_table_number);
489+
let mut query = ResolvedQuery::new(
490+
&mut tx,
491+
TableNamespace::test_user(),
492+
Query::full_table_scan(FILE_STORAGE_TABLE.clone(), Order::Asc),
493+
)?;
494+
while let Some(doc) = query.next(&mut tx, None).await? {
495+
let physical_id = doc.developer_id();
496+
let mut get_query = DeveloperQuery::new_with_version(
497+
&mut tx,
498+
TableNamespace::test_user(),
499+
Query::get(FILE_STORAGE_VIRTUAL_TABLE.clone(), physical_id),
500+
Some("1.7.0".parse()?),
501+
TableFilter::ExcludePrivateSystemTables,
502+
)?;
503+
let Some(virtual_doc) = get_query.next(&mut tx, None).await? else {
504+
anyhow::bail!("Virtual document {physical_id} not found");
505+
};
506+
assert_eq!(virtual_doc.id(), physical_id);
507+
count += 1;
508+
}
509+
assert_eq!(count, 1);
510+
Ok(())
511+
}
374512
}

0 commit comments

Comments
 (0)