Skip to content

Commit 6d2cdbb

Browse files
committed
adapter: colocate temporary schemas with ephemeral owner registration (SQL-150)
1 parent 03d1fdc commit 6d2cdbb

7 files changed

Lines changed: 254 additions & 214 deletions

File tree

src/adapter/src/catalog.rs

Lines changed: 35 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,9 @@ use mz_license_keys::ValidatedLicenseKey;
5656
use mz_ore::metrics::MetricsRegistry;
5757
use mz_ore::now::{EpochMillis, NowFn, SYSTEM_TIME};
5858
use mz_ore::result::ResultExt as _;
59-
use mz_ore::soft_assert_or_log;
6059
use mz_persist_client::PersistClient;
6160
use mz_repr::adt::mz_acl_item::{AclMode, PrivilegeMap};
6261
use mz_repr::explain::ExprHumanizer;
63-
use mz_repr::namespaces::MZ_TEMP_SCHEMA;
6462
use mz_repr::network_policy_id::NetworkPolicyId;
6563
use mz_repr::optimize::OptimizerFeatures;
6664
use mz_repr::role_id::RoleId;
@@ -1164,75 +1162,42 @@ impl Catalog {
11641162
self.state.try_get_role_auth_by_id(id)
11651163
}
11661164

1167-
/// Creates a new schema in the `Catalog` for temporary items
1168-
/// indicated by the TEMPORARY or TEMP keywords.
1169-
pub fn create_temporary_schema(
1170-
&mut self,
1171-
conn_id: &ConnectionId,
1172-
owner_id: RoleId,
1173-
) -> Result<(), Error> {
1174-
self.state.create_temporary_schema(conn_id, owner_id)
1165+
/// Registers the connection's temporary namespace: the `uuid` <->
1166+
/// `conn_id` mapping used to stamp and apply durable temporary items
1167+
/// owned by the session. The `mz_temp` schema itself materializes when
1168+
/// the first temporary item is applied.
1169+
///
1170+
/// The coordinator calls this at a session's first temporary-item
1171+
/// creation, strictly before the transaction that persists the item, and
1172+
/// guards on [`CatalogState::has_temporary_namespace`], so registering
1173+
/// an already-registered namespace is a bug.
1174+
pub fn register_temporary_namespace(&mut self, conn_id: &ConnectionId, uuid: Uuid) {
1175+
self.state
1176+
.temporary_namespaces
1177+
.register(conn_id.clone(), uuid);
11751178
}
11761179

11771180
fn item_exists_in_temp_schemas(&self, conn_id: &ConnectionId, item_name: &str) -> bool {
1178-
// Temporary schemas are created lazily, so it's valid for one to not exist yet.
1181+
// A temporary namespace is registered at the connection's first
1182+
// temporary-item creation, so it's valid for one to not exist yet.
11791183
self.state
1180-
.temporary_schemas
1181-
.get(conn_id)
1184+
.temporary_namespaces
1185+
.schema(conn_id)
11821186
.map(|schema| schema.items.contains_key(item_name))
11831187
.unwrap_or(false)
11841188
}
11851189

1186-
/// Drops schema for connection if it exists. Returns an error if it exists and has items.
1187-
/// Returns Ok if conn_id's temp schema does not exist.
1188-
pub fn drop_temporary_schema(&mut self, conn_id: &ConnectionId) -> Result<(), Error> {
1189-
let Some(schema) = self.state.temporary_schemas.remove(conn_id) else {
1190-
return Ok(());
1191-
};
1192-
if !schema.items.is_empty() {
1193-
return Err(Error::new(ErrorKind::SchemaNotEmpty(MZ_TEMP_SCHEMA.into())));
1194-
}
1195-
Ok(())
1196-
}
1197-
1198-
/// Registers the session as an ephemeral owner: the `uuid` <-> `conn_id`
1199-
/// mapping used to stamp and apply durable temporary items owned by the
1200-
/// session.
1201-
///
1202-
/// The coordinator calls this at a session's first temporary-item
1203-
/// creation, strictly before the transaction that persists the item, and
1204-
/// guards on [`CatalogState::is_ephemeral_owner`], so registering an
1205-
/// already-registered connection is a bug.
1206-
pub fn register_ephemeral_owner(&mut self, uuid: Uuid, conn_id: ConnectionId) {
1207-
let prev = self
1208-
.state
1209-
.ephemeral_owner_conns_by_uuid
1210-
.insert(uuid, conn_id.clone());
1211-
soft_assert_or_log!(
1212-
prev.is_none(),
1213-
"duplicate ephemeral owner registration for {uuid}"
1214-
);
1215-
self.state
1216-
.ephemeral_owner_uuids_by_conn
1217-
.insert(conn_id, uuid);
1218-
}
1219-
1220-
/// Removes the ephemeral-owner registration for `conn_id`.
1190+
/// Removes the connection's temporary namespace, if it has one. Returns
1191+
/// Ok if none exists.
12211192
///
1222-
/// Callers guard on [`CatalogState::is_ephemeral_owner`], so
1223-
/// unregistering an unregistered connection is a bug. They must also
1224-
/// only do this after the transaction dropping the session's temporary
1225-
/// items has been applied, since applying an ephemeral item update
1226-
/// resolves the owning connection through this mapping.
1227-
pub fn unregister_ephemeral_owner(&mut self, conn_id: &ConnectionId) {
1228-
let uuid = self.state.ephemeral_owner_uuids_by_conn.remove(conn_id);
1229-
soft_assert_or_log!(
1230-
uuid.is_some(),
1231-
"no ephemeral owner registration for {conn_id}"
1232-
);
1233-
if let Some(uuid) = uuid {
1234-
self.state.ephemeral_owner_conns_by_uuid.remove(&uuid);
1235-
}
1193+
/// Callers must only do this after the transaction dropping the
1194+
/// session's temporary items has been applied, since applying an
1195+
/// ephemeral item update resolves the owning connection through the
1196+
/// namespace. If the schema still contains items (the drop transaction
1197+
/// failed, e.g. because this process is being fenced out), this returns
1198+
/// an error and leaves the namespace fully in place.
1199+
pub fn drop_temporary_namespace(&mut self, conn_id: &ConnectionId) -> Result<(), Error> {
1200+
self.state.temporary_namespaces.unregister(conn_id)
12361201
}
12371202

12381203
pub(crate) fn object_dependents(
@@ -2077,7 +2042,7 @@ impl SessionCatalog for ConnCatalog<'_> {
20772042
self.state
20782043
.ambient_schemas_by_id
20792044
.values()
2080-
.chain(self.state.temporary_schemas.values())
2045+
.chain(self.state.temporary_namespaces.schemas())
20812046
.map(|schema| schema as &dyn CatalogSchema),
20822047
)
20832048
.collect()
@@ -2811,6 +2776,10 @@ mod tests {
28112776
]
28122777
);
28132778

2779+
// A session's temporary namespace is registered at its first
2780+
// temporary-item creation, so until then `mz_temp` drops out of
2781+
// the resolved search path and temporary resolution comes only
2782+
// from the implicit leading temporary schema.
28142783
let mut session = Session::dummy();
28152784
session
28162785
.vars_mut()
@@ -2822,25 +2791,14 @@ mod tests {
28222791
)
28232792
.expect("failed to set search_path");
28242793
let conn_catalog = catalog.for_session(&session);
2825-
assert_ne!(
2826-
conn_catalog.effective_search_path(false),
2827-
conn_catalog.search_path
2828-
);
2829-
assert_ne!(
2830-
conn_catalog.effective_search_path(true),
2831-
conn_catalog.search_path
2832-
);
2794+
assert_eq!(conn_catalog.search_path, vec![]);
28332795
assert_eq!(
28342796
conn_catalog.effective_search_path(false),
2835-
vec![
2836-
mz_catalog_schema.clone(),
2837-
pg_catalog_schema.clone(),
2838-
mz_temp_schema.clone()
2839-
]
2797+
vec![mz_catalog_schema.clone(), pg_catalog_schema.clone()]
28402798
);
28412799
assert_eq!(
28422800
conn_catalog.effective_search_path(true),
2843-
vec![mz_catalog_schema, pg_catalog_schema, mz_temp_schema]
2801+
vec![mz_temp_schema, mz_catalog_schema, pg_catalog_schema]
28442802
);
28452803
catalog.expire().await;
28462804
})

src/adapter/src/catalog/apply.rs

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,7 +1166,7 @@ impl CatalogState {
11661166
};
11671167
match update.diff {
11681168
// An addition is local when the owning session is connected here.
1169-
StateDiff::Addition => !self.ephemeral_owner_conns_by_uuid.contains_key(&owner),
1169+
StateDiff::Addition => !self.temporary_namespaces.contains_uuid(&owner),
11701170
// A retraction must be applied iff the matching addition was
11711171
// applied here, and entry presence records exactly that. A
11721172
// read-only catalog never reaches this arm: it only applies its
@@ -1207,32 +1207,25 @@ impl CatalogState {
12071207
// session's connection, not in the schema named by
12081208
// `schema_id` (which is the temporary schema sentinel).
12091209
// Updates for sessions not connected to this process are
1210-
// filtered out in `apply_updates_inner`, so the mapping must
1211-
// exist here.
1210+
// filtered out in `apply_updates_inner`, so the namespace
1211+
// (registered before the transaction that created the item)
1212+
// must exist here. Its schema materializes in `insert_entry`.
12121213
let conn_id = ephemeral_owner_session.map(|owner| {
1213-
self.ephemeral_owner_conns_by_uuid
1214-
.get(&owner)
1214+
self.temporary_namespaces
1215+
.conn_for_uuid(&owner)
12151216
.cloned()
12161217
.unwrap_or_else(|| {
12171218
panic!("no session record applied for temporary item owner {owner}")
12181219
})
12191220
});
12201221
let name = match &conn_id {
1221-
Some(conn_id) => {
1222-
// Lazily create the temporary schema if it doesn't
1223-
// exist yet.
1224-
if !self.temporary_schemas.contains_key(conn_id) {
1225-
self.create_temporary_schema(conn_id, owner_id)
1226-
.expect("failed to create temporary schema");
1227-
}
1228-
QualifiedItemName {
1229-
qualifiers: ItemQualifiers {
1230-
database_spec: ResolvedDatabaseSpecifier::Ambient,
1231-
schema_spec: SchemaSpecifier::Temporary,
1232-
},
1233-
item: name.clone(),
1234-
}
1235-
}
1222+
Some(_) => QualifiedItemName {
1223+
qualifiers: ItemQualifiers {
1224+
database_spec: ResolvedDatabaseSpecifier::Ambient,
1225+
schema_spec: SchemaSpecifier::Temporary,
1226+
},
1227+
item: name.clone(),
1228+
},
12361229
None => {
12371230
let schema = self.find_non_temp_schema(&schema_id);
12381231
QualifiedItemName {
@@ -1698,8 +1691,8 @@ impl CatalogState {
16981691
// Keep in sync with `get_schemas`
16991692
match (database_spec, schema_spec) {
17001693
(ResolvedDatabaseSpecifier::Ambient, SchemaSpecifier::Temporary) => self
1701-
.temporary_schemas
1702-
.get_mut(conn_id)
1694+
.temporary_namespaces
1695+
.schema_mut(conn_id)
17031696
.expect("catalog out of sync"),
17041697
(ResolvedDatabaseSpecifier::Ambient, SchemaSpecifier::Id(id)) => self
17051698
.ambient_schemas_by_id
@@ -2004,13 +1997,12 @@ impl CatalogState {
20041997
self.entry_by_global_id.insert(gid, entry.id());
20051998
}
20061999
let conn_id = entry.item().conn_id().unwrap_or(&SYSTEM_CONN_ID);
2007-
// Lazily create the temporary schema if this is a temporary item and the schema
2008-
// doesn't exist yet.
2009-
if entry.name().qualifiers.schema_spec == SchemaSpecifier::Temporary
2010-
&& !self.temporary_schemas.contains_key(conn_id)
2011-
{
2012-
self.create_temporary_schema(conn_id, entry.owner_id)
2013-
.expect("failed to create temporary schema");
2000+
// Materialize the temporary schema at the first applied temporary
2001+
// item. The owning session's namespace was registered before the
2002+
// transaction that created the item.
2003+
if entry.name().qualifiers.schema_spec == SchemaSpecifier::Temporary {
2004+
self.temporary_namespaces
2005+
.ensure_schema(conn_id, entry.owner_id);
20142006
}
20152007
let schema = self.get_schema_mut(
20162008
&entry.name().qualifiers.database_spec,

src/adapter/src/catalog/open.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use mz_audit_log::{
2525
CreateOrDropClusterReplicaReasonV1, EventDetails, EventType, ObjectType, VersionedEvent,
2626
};
2727
use mz_auth::hash::scram256_hash;
28-
use mz_catalog::SYSTEM_CONN_ID;
2928
use mz_catalog::builtin::{
3029
BUILTIN_CLUSTERS, BUILTIN_PREFIXES, BUILTIN_ROLES, BUILTINS, Builtin, Fingerprint,
3130
MZ_CATALOG_RAW, RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL,
@@ -155,9 +154,7 @@ impl Catalog {
155154
comments: Arc::new(CommentsMap::default()),
156155
source_references: imbl::OrdMap::new(),
157156
storage_metadata: Arc::new(StorageMetadata::default()),
158-
temporary_schemas: imbl::OrdMap::new(),
159-
ephemeral_owner_conns_by_uuid: imbl::OrdMap::new(),
160-
ephemeral_owner_uuids_by_conn: imbl::OrdMap::new(),
157+
temporary_namespaces: Default::default(),
161158
mock_authentication_nonce: Default::default(),
162159
config: mz_sql::catalog::CatalogConfig {
163160
start_time: to_datetime((config.now)()),
@@ -251,7 +248,6 @@ impl Catalog {
251248
Err(e) => return Err(e.into()),
252249
};
253250
}
254-
state.create_temporary_schema(&SYSTEM_CONN_ID, MZ_SYSTEM_ROLE_ID)?;
255251
}
256252

257253
// Make life easier by consolidating all updates, so that we end up with only positive

0 commit comments

Comments
 (0)