11// SPDX-License-Identifier: BUSL-1.1
22
3- //! `DROP TENANT [IF EXISTS] <id>` handler. Migrated to
3+ //! `DROP TENANT [IF EXISTS] <id|name >` handler. Migrated to
44//! `CatalogEntry::DeleteTenant` in phase 1k.6.
5+ //!
6+ //! Accepts either a numeric tenant id or a tenant name (single-quoted
7+ //! optional), parallel to the `CREATE TENANT <name>` and
8+ //! `SHOW TENANT <name|id>` paths.
59
610use pgwire:: api:: results:: { Response , Tag } ;
711use pgwire:: error:: PgWireResult ;
@@ -11,28 +15,10 @@ use crate::control::metadata_proposer::propose_catalog_entry;
1115use crate :: control:: security:: audit:: AuditEvent ;
1216use crate :: control:: security:: identity:: AuthenticatedIdentity ;
1317use crate :: control:: state:: SharedState ;
14- use crate :: types:: TenantId ;
1518
1619use super :: super :: super :: types:: sqlstate_error;
1720use super :: super :: parse_utils:: strip_if_exists;
18-
19- /// Whether a tenant id currently exists, consulting the redb catalog when
20- /// one is wired up and falling back to the in-memory quota table otherwise.
21- fn tenant_exists ( state : & SharedState , tenant_id : TenantId ) -> PgWireResult < bool > {
22- if let Some ( catalog) = state. credentials . catalog ( ) {
23- let present = catalog
24- . load_all_tenants ( )
25- . map_err ( |e| sqlstate_error ( "XX000" , & format ! ( "catalog read: {e}" ) ) ) ?
26- . iter ( )
27- . any ( |t| t. tenant_id == tenant_id. as_u64 ( ) ) ;
28- return Ok ( present) ;
29- }
30- let tenants = match state. tenants . lock ( ) {
31- Ok ( t) => t,
32- Err ( p) => p. into_inner ( ) ,
33- } ;
34- Ok ( tenants. has_quota ( tenant_id) )
35- }
21+ use super :: tenant_exists;
3622
3723pub fn drop_tenant (
3824 state : & SharedState ,
@@ -51,22 +37,44 @@ pub fn drop_tenant(
5137 if parts. len ( ) < 3 {
5238 return Err ( sqlstate_error (
5339 "42601" ,
54- "syntax: DROP TENANT [IF EXISTS] <id>" ,
40+ "syntax: DROP TENANT [IF EXISTS] <id|name >" ,
5541 ) ) ;
5642 }
5743
58- let tid: u64 = parts[ 2 ]
59- . parse ( )
60- . map_err ( |_| sqlstate_error ( "42601" , "TENANT ID must be a numeric value" ) ) ?;
61- let tenant_id = TenantId :: new ( tid) ;
44+ // Accept either a numeric id or a tenant name; mirror the existing
45+ // CREATE TENANT name-resolution path. A name that matches no tenant yields
46+ // `None` here; an unknown numeric id resolves to a candidate that the
47+ // existence gate below rejects, so both forms behave identically.
48+ let tenant_id = match super :: resolve_tenant_ref ( state, parts[ 2 ] ) ? {
49+ Some ( tid) => tid,
50+ None => {
51+ // Name token did not resolve to any tenant.
52+ if if_exists {
53+ return Ok ( vec ! [ Response :: Execution ( Tag :: new( "DROP TENANT" ) ) ] ) ;
54+ }
55+ return Err ( sqlstate_error (
56+ "42704" ,
57+ & format ! ( "tenant '{}' does not exist" , parts[ 2 ] ) ,
58+ ) ) ;
59+ }
60+ } ;
61+ let tid = tenant_id. as_u64 ( ) ;
6262
6363 if tid == 0 {
6464 return Err ( sqlstate_error ( "42501" , "cannot drop system tenant (0)" ) ) ;
6565 }
6666
67- // `IF EXISTS`: dropping a tenant that does not exist is a no-op success.
68- if if_exists && !tenant_exists ( state, tenant_id) ? {
69- return Ok ( vec ! [ Response :: Execution ( Tag :: new( "DROP TENANT" ) ) ] ) ;
67+ // Existence gate, uniform across numeric ids and resolved names: an unknown
68+ // tenant is a no-op under `IF EXISTS`, otherwise `42704` — never a silent
69+ // delete proposal for a tenant that does not exist.
70+ if !tenant_exists ( state, tenant_id) ? {
71+ if if_exists {
72+ return Ok ( vec ! [ Response :: Execution ( Tag :: new( "DROP TENANT" ) ) ] ) ;
73+ }
74+ return Err ( sqlstate_error (
75+ "42704" ,
76+ & format ! ( "tenant '{}' does not exist" , parts[ 2 ] ) ,
77+ ) ) ;
7078 }
7179
7280 let entry = CatalogEntry :: DeleteTenant { tenant_id : tid } ;
0 commit comments