Skip to content

Commit 215abce

Browse files
committed
Enhance database default storage validation
1 parent 2892bf6 commit 215abce

File tree

11 files changed

+214
-68
lines changed

11 files changed

+214
-68
lines changed

src/query/service/src/interpreters/access/privilege_access.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ impl PrivilegeAccess {
246246
) -> Result<()> {
247247
self.access_system_history(Some(catalog_name), Some(db_name), None, privileges)?;
248248
let tenant = self.ctx.get_tenant();
249+
let catalog = self.ctx.get_catalog(catalog_name).await?;
250+
if if_exists && !catalog.exists_database(&tenant, db_name).await? {
251+
return Ok(());
252+
}
249253
let check_current_role_only = match privileges {
250254
// create table/stream need check db's Create Privilege
251255
UserPrivilegeType::Create => true,
@@ -264,7 +268,6 @@ impl PrivilegeAccess {
264268
return Ok(());
265269
}
266270
Err(_err) => {
267-
let catalog = self.ctx.get_catalog(catalog_name).await?;
268271
match self
269272
.convert_to_id(&tenant, &catalog, db_name, None, false)
270273
.await
@@ -1694,7 +1697,14 @@ impl AccessChecker for PrivilegeAccess {
16941697
Plan::SetWorkloadGroupQuotas(_) => {}
16951698
Plan::UnsetWorkloadGroupQuotas(_) => {}
16961699
Plan::AlterDatabase(plan) => {
1697-
self.validate_db_access(&plan.catalog, &plan.database, UserPrivilegeType::Alter, false).await?;
1700+
self
1701+
.validate_db_access(
1702+
&plan.catalog,
1703+
&plan.database,
1704+
UserPrivilegeType::Alter,
1705+
plan.if_exists,
1706+
)
1707+
.await?;
16981708
}
16991709
}
17001710

src/query/service/src/interpreters/interpreter_alter_database.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use std::sync::Arc;
1616

1717
use databend_common_catalog::table_context::TableContext;
1818
use databend_common_exception::Result;
19+
use databend_common_sql::planner::binder::ddl::database::DEFAULT_STORAGE_CONNECTION;
20+
use databend_common_sql::planner::binder::ddl::database::DEFAULT_STORAGE_PATH;
1921
use databend_common_sql::plans::AlterDatabasePlan;
2022
use log::debug;
2123

@@ -49,20 +51,30 @@ impl Interpreter for AlterDatabaseInterpreter {
4951
#[async_backtrace::framed]
5052
async fn execute2(&self) -> Result<PipelineBuildResult> {
5153
debug!("ctx.id" = self.ctx.get_id().as_str(); "alter_database_execute");
52-
5354
let catalog = self.ctx.get_catalog(&self.plan.catalog).await?;
54-
let database = catalog
55+
let database = match catalog
5556
.get_database(&self.plan.tenant, &self.plan.database)
56-
.await?;
57+
.await
58+
{
59+
Ok(db) => db,
60+
Err(err) => {
61+
if self.plan.if_exists
62+
&& err.code() == databend_common_exception::ErrorCode::UNKNOWN_DATABASE
63+
{
64+
return Ok(PipelineBuildResult::create());
65+
}
66+
return Err(err);
67+
}
68+
};
5769

5870
// Merge provided options with the existing database options
5971
let mut merged_options = database.options().clone();
6072
for (key, value) in &self.plan.options {
6173
merged_options.insert(key.clone(), value.clone());
6274
}
6375

64-
let connection_value = merged_options.get("DEFAULT_STORAGE_CONNECTION").cloned();
65-
let path_value = merged_options.get("DEFAULT_STORAGE_PATH").cloned();
76+
let connection_value = merged_options.get(DEFAULT_STORAGE_CONNECTION).cloned();
77+
let path_value = merged_options.get(DEFAULT_STORAGE_PATH).cloned();
6678

6779
// Check if both options are present together in the final merged state
6880
// This ensures that after ALTER, the database still has both options configured

src/query/service/src/interpreters/interpreter_database_show_create.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl Interpreter for ShowCreateDatabaseInterpreter {
7575
.iter()
7676
.map(|(k, v)| format!("{}='{}'", k, v))
7777
.collect::<Vec<_>>()
78-
.join(" ");
78+
.join(", ");
7979
if !db.options().is_empty() {
8080
write!(info, " OPTIONS ({})", options).expect("failed to format database options");
8181
}

src/query/sql/src/planner/binder/ddl/database.rs

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ use crate::plans::UndropDatabasePlan;
4949
use crate::BindContext;
5050
use crate::SelectBuilder;
5151

52+
pub const DEFAULT_STORAGE_CONNECTION: &str = "DEFAULT_STORAGE_CONNECTION";
53+
pub const DEFAULT_STORAGE_PATH: &str = "DEFAULT_STORAGE_PATH";
54+
5255
impl Binder {
5356
#[async_backtrace::framed]
5457
pub(in crate::planner::binder) async fn bind_show_databases(
@@ -217,20 +220,33 @@ impl Binder {
217220
))),
218221

219222
AlterDatabaseAction::SetOptions { options } => {
220-
// Validate database options before processing
221-
// For ALTER DATABASE, allow modifying single option (the other already exists)
222-
self.validate_database_options(options, false).await?;
223+
let catalog_arc = self.ctx.get_catalog(&catalog).await?;
224+
let db_exists = catalog_arc.exists_database(&tenant, &database).await?;
225+
if !db_exists && !*if_exists {
226+
return Err(ErrorCode::UnknownDatabase(format!(
227+
"Unknown database '{}'",
228+
database
229+
)));
230+
}
223231

224-
// Convert SQLProperty to BTreeMap for database options
225-
let db_options = options
226-
.iter()
227-
.map(|property| (property.name.clone(), property.value.clone()))
228-
.collect::<BTreeMap<String, String>>();
232+
// Validate database options only when the database exists.
233+
let db_options = if db_exists {
234+
// For ALTER DATABASE, allow modifying single option (the other already exists)
235+
self.validate_database_options(options, false).await?;
236+
237+
options
238+
.iter()
239+
.map(|property| (property.name.clone(), property.value.clone()))
240+
.collect::<BTreeMap<String, String>>()
241+
} else {
242+
BTreeMap::new()
243+
};
229244

230245
Ok(Plan::AlterDatabase(Box::new(AlterDatabasePlan {
231246
tenant,
232247
catalog,
233248
database,
249+
if_exists: *if_exists,
234250
options: db_options,
235251
})))
236252
}
@@ -331,8 +347,7 @@ impl Binder {
331347
require_both: bool,
332348
) -> Result<()> {
333349
// Validate database options - only allow specific connection-related options
334-
const VALID_DATABASE_OPTIONS: &[&str] =
335-
&["DEFAULT_STORAGE_CONNECTION", "DEFAULT_STORAGE_PATH"];
350+
const VALID_DATABASE_OPTIONS: &[&str] = &[DEFAULT_STORAGE_CONNECTION, DEFAULT_STORAGE_PATH];
336351

337352
// Check for duplicate options
338353
let mut seen_options = std::collections::HashSet::new();
@@ -354,33 +369,31 @@ impl Binder {
354369
}
355370

356371
// Validate pairing requirement based on operation type
357-
let has_connection = options
358-
.iter()
359-
.any(|p| p.name == "DEFAULT_STORAGE_CONNECTION");
360-
let has_path = options.iter().any(|p| p.name == "DEFAULT_STORAGE_PATH");
372+
let has_connection = options.iter().any(|p| p.name == DEFAULT_STORAGE_CONNECTION);
373+
let has_path = options.iter().any(|p| p.name == DEFAULT_STORAGE_PATH);
361374

362375
if require_both {
363376
// For CREATE DATABASE: both options must be specified together
364377
if has_connection && !has_path {
365-
return Err(ErrorCode::BadArguments(
366-
"DEFAULT_STORAGE_CONNECTION requires DEFAULT_STORAGE_PATH to be specified"
367-
.to_string(),
368-
));
378+
return Err(ErrorCode::BadArguments(format!(
379+
"{} requires {} to be specified",
380+
DEFAULT_STORAGE_CONNECTION, DEFAULT_STORAGE_PATH
381+
)));
369382
}
370383

371384
if has_path && !has_connection {
372-
return Err(ErrorCode::BadArguments(
373-
"DEFAULT_STORAGE_PATH requires DEFAULT_STORAGE_CONNECTION to be specified"
374-
.to_string(),
375-
));
385+
return Err(ErrorCode::BadArguments(format!(
386+
"{} requires {} to be specified",
387+
DEFAULT_STORAGE_PATH, DEFAULT_STORAGE_CONNECTION
388+
)));
376389
}
377390
}
378391
// For ALTER DATABASE: allow modifying single option (the other one already exists in database)
379392

380393
// Validate that the specified connection exists
381394
if let Some(connection_property) = options
382395
.iter()
383-
.find(|p| p.name == "DEFAULT_STORAGE_CONNECTION")
396+
.find(|p| p.name == DEFAULT_STORAGE_CONNECTION)
384397
{
385398
let connection_name = &connection_property.value;
386399

@@ -402,15 +415,46 @@ impl Binder {
402415
if let (Some(connection_prop), Some(path_prop)) = (
403416
options
404417
.iter()
405-
.find(|p| p.name == "DEFAULT_STORAGE_CONNECTION"),
406-
options.iter().find(|p| p.name == "DEFAULT_STORAGE_PATH"),
418+
.find(|p| p.name == DEFAULT_STORAGE_CONNECTION),
419+
options.iter().find(|p| p.name == DEFAULT_STORAGE_PATH),
407420
) {
408-
// Validate the storage path is accessible
421+
// Validate the storage path is accessible and matches the connection protocol
409422
let connection = self.ctx.get_connection(&connection_prop.value).await?;
423+
424+
let uri_for_scheme = databend_common_ast::ast::UriLocation::from_uri(
425+
path_prop.value.clone(),
426+
BTreeMap::new(),
427+
)
428+
.map_err(|e| {
429+
ErrorCode::BadArguments(format!(
430+
"Invalid storage path '{}': {}",
431+
path_prop.value, e
432+
))
433+
})?;
434+
435+
let path_protocol = normalize_storage_protocol(&uri_for_scheme.protocol);
436+
let connection_protocol = normalize_storage_protocol(&connection.storage_type);
437+
438+
if path_protocol != connection_protocol {
439+
return Err(ErrorCode::BadArguments(format!(
440+
"{} protocol '{}' does not match connection '{}' protocol '{}'",
441+
DEFAULT_STORAGE_PATH,
442+
uri_for_scheme.protocol,
443+
connection_prop.value,
444+
connection.storage_type
445+
)));
446+
}
447+
410448
let mut uri_location = databend_common_ast::ast::UriLocation::from_uri(
411449
path_prop.value.clone(),
412-
connection.storage_params,
413-
)?;
450+
connection.storage_params.clone(),
451+
)
452+
.map_err(|e| {
453+
ErrorCode::BadArguments(format!(
454+
"Invalid storage path '{}': {}",
455+
path_prop.value, e
456+
))
457+
})?;
414458

415459
// Parse and validate the URI location using parse_storage_params_from_uri
416460
// This enforces that the path must end with '/' (directory requirement)
@@ -490,3 +534,11 @@ impl Binder {
490534
})
491535
}
492536
}
537+
538+
fn normalize_storage_protocol(protocol: &str) -> String {
539+
let mut lower = protocol.to_ascii_lowercase();
540+
if lower == "file" {
541+
lower = "fs".to_string();
542+
}
543+
lower
544+
}

src/query/sql/src/planner/binder/ddl/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod catalog;
1717
mod column;
1818
mod connection;
1919
mod data_mask;
20-
mod database;
20+
pub mod database;
2121
mod dictionary;
2222
mod dynamic_table;
2323
mod index;

src/query/sql/src/planner/binder/ddl/table.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ use crate::binder::ConstraintExprBinder;
117117
use crate::binder::Visibility;
118118
use crate::optimizer::ir::SExpr;
119119
use crate::parse_computed_expr_to_string;
120+
use crate::planner::binder::ddl::database::DEFAULT_STORAGE_CONNECTION;
121+
use crate::planner::binder::ddl::database::DEFAULT_STORAGE_PATH;
120122
use crate::planner::semantic::normalize_identifier;
121123
use crate::planner::semantic::resolve_type_name;
122124
use crate::planner::semantic::IdentifierNormalizer;
@@ -562,8 +564,8 @@ impl Binder {
562564
{
563565
// Extract database-level default connection options
564566
let default_connection_name =
565-
database_info.options().get("DEFAULT_STORAGE_CONNECTION");
566-
let default_path = database_info.options().get("DEFAULT_STORAGE_PATH");
567+
database_info.options().get(DEFAULT_STORAGE_CONNECTION);
568+
let default_path = database_info.options().get(DEFAULT_STORAGE_PATH);
567569

568570
// If both database defaults exist, construct UriLocation
569571
if let (Some(connection_name), Some(path)) = (default_connection_name, default_path)

src/query/sql/src/planner/binder/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ mod column_binding;
2727
mod constraint_expr;
2828
mod copy_into_location;
2929
mod copy_into_table;
30-
mod ddl;
30+
pub mod ddl;
3131
mod default_expr;
3232
mod distinct;
3333
mod explain;
@@ -78,6 +78,8 @@ pub use constraint_expr::ConstraintExprBinder;
7878
pub use copy_into_table::resolve_file_location;
7979
pub use copy_into_table::resolve_stage_location;
8080
pub use copy_into_table::resolve_stage_locations;
81+
pub use ddl::database::DEFAULT_STORAGE_CONNECTION;
82+
pub use ddl::database::DEFAULT_STORAGE_PATH;
8183
pub use ddl::table::verify_external_location_privileges;
8284
pub use default_expr::DefaultExprBinder;
8385
pub use explain::ExplainConfig;

src/query/sql/src/planner/plans/ddl/database.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ pub struct AlterDatabasePlan {
161161
pub tenant: Tenant,
162162
pub catalog: String,
163163
pub database: String,
164+
pub if_exists: bool,
164165
pub options: BTreeMap<String, String>,
165166
}
166167

0 commit comments

Comments
 (0)