1mod admin;
16mod comment;
17mod copy_database;
18mod copy_query_to;
19mod copy_table_from;
20mod copy_table_to;
21mod cursor;
22pub mod ddl;
23mod describe;
24mod dml;
25mod kill;
26mod set;
27mod show;
28mod tql;
29
30use std::collections::HashMap;
31use std::sync::Arc;
32
33use api::v1::RowInsertRequests;
34use catalog::CatalogManagerRef;
35use catalog::kvbackend::KvBackendCatalogManager;
36use catalog::process_manager::ProcessManagerRef;
37use client::RecordBatches;
38use client::error::{ExternalSnafu as ClientExternalSnafu, Result as ClientResult};
39use client::inserter::{InsertOptions, Inserter};
40use common_error::ext::BoxedError;
41use common_meta::cache_invalidator::CacheInvalidatorRef;
42use common_meta::key::flow::{FlowMetadataManager, FlowMetadataManagerRef};
43use common_meta::key::schema_name::SchemaNameKey;
44use common_meta::key::view_info::{ViewInfoManager, ViewInfoManagerRef};
45use common_meta::key::{TableMetadataManager, TableMetadataManagerRef};
46use common_meta::kv_backend::KvBackendRef;
47use common_meta::procedure_executor::ProcedureExecutorRef;
48use common_query::Output;
49use common_telemetry::{debug, tracing, warn};
50use common_time::Timestamp;
51use common_time::range::TimestampRange;
52use datafusion_expr::LogicalPlan;
53use datatypes::prelude::ConcreteDataType;
54use datatypes::schema::ColumnSchema;
55use humantime::format_duration;
56use itertools::Itertools;
57use partition::manager::PartitionRuleManagerRef;
58use query::QueryEngineRef;
59use query::parser::QueryStatement;
60use session::context::{Channel, QueryContextBuilder, QueryContextRef};
61use session::table_name::table_idents_to_full_name;
62use set::{set_query_timeout, set_read_preference};
63use snafu::{OptionExt, ResultExt, ensure};
64use sql::ast::ObjectNamePartExt;
65use sql::statements::OptionMap;
66use sql::statements::copy::{
67 CopyDatabase, CopyDatabaseArgument, CopyQueryToArgument, CopyTable, CopyTableArgument,
68};
69use sql::statements::set_variables::SetVariables;
70use sql::statements::show::ShowCreateTableVariant;
71use sql::statements::statement::Statement;
72use sql::util::format_raw_object_name;
73use sqlparser::ast::ObjectName;
74use store_api::mito_engine_options::{APPEND_MODE_KEY, TTL_KEY};
75use table::TableRef;
76use table::requests::{CopyDatabaseRequest, CopyDirection, CopyQueryToRequest, CopyTableRequest};
77use table::table_name::TableName;
78use table::table_reference::TableReference;
79
80use self::set::{
81 set_bytea_output, set_datestyle, set_intervalstyle, set_timezone, validate_client_encoding,
82};
83use crate::error::{
84 self, CatalogSnafu, ExecLogicalPlanSnafu, ExternalSnafu, InvalidSqlSnafu, NotSupportedSnafu,
85 PlanStatementSnafu, Result, SchemaNotFoundSnafu, SqlCommonSnafu, TableMetadataManagerSnafu,
86 TableNotFoundSnafu, UnexpectedSnafu, UpgradeCatalogManagerRefSnafu,
87};
88use crate::insert::InserterRef;
89use crate::statement::copy_database::{COPY_DATABASE_TIME_END_KEY, COPY_DATABASE_TIME_START_KEY};
90use crate::statement::set::set_allow_query_fallback;
91
92#[async_trait::async_trait]
94pub trait StatementExecutorConfigurator: Send + Sync {
95 async fn configure(
96 &self,
97 executor: StatementExecutor,
98 ctx: ExecutorConfigureContext,
99 ) -> std::result::Result<StatementExecutor, BoxedError>;
100}
101
102pub type StatementExecutorConfiguratorRef = Arc<dyn StatementExecutorConfigurator>;
103
104pub struct ExecutorConfigureContext {
105 pub kv_backend: KvBackendRef,
106}
107
108#[derive(Clone)]
109pub struct StatementExecutor {
110 catalog_manager: CatalogManagerRef,
111 query_engine: QueryEngineRef,
112 procedure_executor: ProcedureExecutorRef,
113 table_metadata_manager: TableMetadataManagerRef,
114 flow_metadata_manager: FlowMetadataManagerRef,
115 view_info_manager: ViewInfoManagerRef,
116 partition_manager: PartitionRuleManagerRef,
117 cache_invalidator: CacheInvalidatorRef,
118 inserter: InserterRef,
119 process_manager: Option<ProcessManagerRef>,
120 origin_frontend_addr: String,
121 #[cfg(feature = "enterprise")]
122 trigger_querier: Option<TriggerQuerierRef>,
123}
124
125pub type StatementExecutorRef = Arc<StatementExecutor>;
126
127#[cfg(feature = "enterprise")]
129#[async_trait::async_trait]
130pub trait TriggerQuerier: Send + Sync {
131 async fn show_create_trigger(
133 &self,
134 catalog: &str,
135 trigger: &str,
136 query_ctx: &QueryContextRef,
137 ) -> std::result::Result<Output, BoxedError>;
138
139 fn as_any(&self) -> &dyn std::any::Any;
140}
141
142#[cfg(feature = "enterprise")]
143pub type TriggerQuerierRef = Arc<dyn TriggerQuerier>;
144
145impl StatementExecutor {
146 #[allow(clippy::too_many_arguments)]
147 pub fn new(
148 catalog_manager: CatalogManagerRef,
149 query_engine: QueryEngineRef,
150 procedure_executor: ProcedureExecutorRef,
151 kv_backend: KvBackendRef,
152 cache_invalidator: CacheInvalidatorRef,
153 inserter: InserterRef,
154 partition_manager: PartitionRuleManagerRef,
155 process_manager: Option<ProcessManagerRef>,
156 origin_frontend_addr: String,
157 ) -> Self {
158 Self {
159 catalog_manager,
160 query_engine,
161 procedure_executor,
162 table_metadata_manager: Arc::new(TableMetadataManager::new(kv_backend.clone())),
163 flow_metadata_manager: Arc::new(FlowMetadataManager::new(kv_backend.clone())),
164 view_info_manager: Arc::new(ViewInfoManager::new(kv_backend.clone())),
165 partition_manager,
166 cache_invalidator,
167 inserter,
168 process_manager,
169 origin_frontend_addr,
170 #[cfg(feature = "enterprise")]
171 trigger_querier: None,
172 }
173 }
174
175 #[cfg(feature = "enterprise")]
176 pub fn with_trigger_querier(mut self, querier: TriggerQuerierRef) -> Self {
177 self.trigger_querier = Some(querier);
178 self
179 }
180
181 #[cfg(feature = "testing")]
182 pub async fn execute_stmt(
183 &self,
184 stmt: QueryStatement,
185 query_ctx: QueryContextRef,
186 ) -> Result<Output> {
187 match stmt {
188 QueryStatement::Sql(stmt) => self.execute_sql(stmt, query_ctx).await,
189 QueryStatement::Promql(_, _) => self.plan_exec(stmt, query_ctx).await,
190 }
191 }
192
193 #[tracing::instrument(skip_all)]
194 pub async fn execute_sql(&self, stmt: Statement, query_ctx: QueryContextRef) -> Result<Output> {
195 match stmt {
196 Statement::Query(_) | Statement::Explain(_) | Statement::Delete(_) => {
197 self.plan_exec(QueryStatement::Sql(stmt), query_ctx).await
198 }
199
200 Statement::DeclareCursor(declare_cursor) => {
201 self.declare_cursor(declare_cursor, query_ctx).await
202 }
203 Statement::FetchCursor(fetch_cursor) => {
204 self.fetch_cursor(fetch_cursor, query_ctx).await
205 }
206 Statement::CloseCursor(close_cursor) => {
207 self.close_cursor(close_cursor, query_ctx).await
208 }
209
210 Statement::Insert(insert) => self.insert(insert, query_ctx).await,
211
212 Statement::Tql(tql) => self.execute_tql(tql, query_ctx).await,
213
214 Statement::DescribeTable(stmt) => self.describe_table(stmt, query_ctx).await,
215
216 Statement::ShowDatabases(stmt) => self.show_databases(stmt, query_ctx).await,
217
218 Statement::ShowTables(stmt) => self.show_tables(stmt, query_ctx).await,
219
220 Statement::ShowTableStatus(stmt) => self.show_table_status(stmt, query_ctx).await,
221
222 Statement::ShowCollation(kind) => self.show_collation(kind, query_ctx).await,
223
224 Statement::ShowCharset(kind) => self.show_charset(kind, query_ctx).await,
225
226 Statement::ShowViews(stmt) => self.show_views(stmt, query_ctx).await,
227
228 Statement::ShowFlows(stmt) => self.show_flows(stmt, query_ctx).await,
229
230 #[cfg(feature = "enterprise")]
231 Statement::ShowTriggers(stmt) => self.show_triggers(stmt, query_ctx).await,
232
233 Statement::Copy(sql::statements::copy::Copy::CopyQueryTo(stmt)) => {
234 let query_output = self
235 .plan_exec(QueryStatement::Sql(*stmt.query), query_ctx)
236 .await?;
237 let req = to_copy_query_request(stmt.arg)?;
238
239 self.copy_query_to(req, query_output)
240 .await
241 .map(Output::new_with_affected_rows)
242 }
243
244 Statement::Copy(sql::statements::copy::Copy::CopyTable(stmt)) => {
245 let req = to_copy_table_request(stmt, query_ctx.clone())?;
246 match req.direction {
247 CopyDirection::Export => self
248 .copy_table_to(req, query_ctx)
249 .await
250 .map(Output::new_with_affected_rows),
251 CopyDirection::Import => self.copy_table_from(req, query_ctx).await,
252 }
253 }
254
255 Statement::Copy(sql::statements::copy::Copy::CopyDatabase(copy_database)) => {
256 match copy_database {
257 CopyDatabase::To(arg) => {
258 self.copy_database_to(
259 to_copy_database_request(arg, &query_ctx)?,
260 query_ctx.clone(),
261 )
262 .await
263 }
264 CopyDatabase::From(arg) => {
265 self.copy_database_from(
266 to_copy_database_request(arg, &query_ctx)?,
267 query_ctx,
268 )
269 .await
270 }
271 }
272 }
273
274 Statement::CreateTable(stmt) => {
275 let _ = self.create_table(stmt, query_ctx).await?;
276 Ok(Output::new_with_affected_rows(0))
277 }
278 Statement::CreateTableLike(stmt) => {
279 let _ = self.create_table_like(stmt, query_ctx).await?;
280 Ok(Output::new_with_affected_rows(0))
281 }
282 Statement::CreateExternalTable(stmt) => {
283 let _ = self.create_external_table(stmt, query_ctx).await?;
284 Ok(Output::new_with_affected_rows(0))
285 }
286 Statement::CreateFlow(stmt) => self.create_flow(stmt, query_ctx).await,
287 #[cfg(feature = "enterprise")]
288 Statement::CreateTrigger(stmt) => self.create_trigger(stmt, query_ctx).await,
289 Statement::DropFlow(stmt) => {
290 self.drop_flow(
291 query_ctx.current_catalog().to_string(),
292 format_raw_object_name(stmt.flow_name()),
293 stmt.drop_if_exists(),
294 query_ctx,
295 )
296 .await
297 }
298 #[cfg(feature = "enterprise")]
299 Statement::DropTrigger(stmt) => {
300 self.drop_trigger(
301 query_ctx.current_catalog().to_string(),
302 format_raw_object_name(stmt.trigger_name()),
303 stmt.drop_if_exists(),
304 query_ctx,
305 )
306 .await
307 }
308 Statement::CreateView(stmt) => {
309 let _ = self.create_view(stmt, query_ctx).await?;
310 Ok(Output::new_with_affected_rows(0))
311 }
312 Statement::DropView(stmt) => {
313 let (catalog_name, schema_name, view_name) =
314 table_idents_to_full_name(&stmt.view_name, &query_ctx)
315 .map_err(BoxedError::new)
316 .context(ExternalSnafu)?;
317
318 self.drop_view(
319 catalog_name,
320 schema_name,
321 view_name,
322 stmt.drop_if_exists,
323 query_ctx,
324 )
325 .await
326 }
327 Statement::AlterTable(alter_table) => self.alter_table(alter_table, query_ctx).await,
328
329 Statement::AlterDatabase(alter_database) => {
330 self.alter_database(alter_database, query_ctx).await
331 }
332
333 #[cfg(feature = "enterprise")]
334 Statement::AlterTrigger(alter_trigger) => {
335 self.alter_trigger(alter_trigger, query_ctx).await
336 }
337
338 Statement::DropTable(stmt) => {
339 let mut table_names = Vec::with_capacity(stmt.table_names().len());
340 for table_name_stmt in stmt.table_names() {
341 let (catalog, schema, table) =
342 table_idents_to_full_name(table_name_stmt, &query_ctx)
343 .map_err(BoxedError::new)
344 .context(ExternalSnafu)?;
345 table_names.push(TableName::new(catalog, schema, table));
346 }
347 self.drop_tables(&table_names[..], stmt.drop_if_exists(), query_ctx.clone())
348 .await
349 }
350 Statement::UndropTable(stmt) => {
351 let (catalog, schema, table) =
352 table_idents_to_full_name(stmt.table_name(), &query_ctx)
353 .map_err(BoxedError::new)
354 .context(ExternalSnafu)?;
355 self.undrop_table(TableName::new(catalog, schema, table), query_ctx)
356 .await
357 }
358 Statement::DropDatabase(stmt) => {
359 self.drop_database(
360 query_ctx.current_catalog().to_string(),
361 format_raw_object_name(stmt.name()),
362 stmt.drop_if_exists(),
363 query_ctx,
364 )
365 .await
366 }
367 Statement::TruncateTable(stmt) => {
368 let (catalog, schema, table) =
369 table_idents_to_full_name(stmt.table_name(), &query_ctx)
370 .map_err(BoxedError::new)
371 .context(ExternalSnafu)?;
372 let table_name = TableName::new(catalog, schema, table);
373 let time_ranges = self
374 .convert_truncate_time_ranges(&table_name, stmt.time_ranges(), &query_ctx)
375 .await?;
376 self.truncate_table(table_name, time_ranges, query_ctx)
377 .await
378 }
379 Statement::CreateDatabase(stmt) => {
380 self.create_database(
381 &format_raw_object_name(&stmt.name),
382 stmt.if_not_exists,
383 stmt.options.into_map(),
384 query_ctx,
385 )
386 .await
387 }
388 Statement::ShowCreateDatabase(show) => {
389 let (catalog, database) =
390 idents_to_full_database_name(&show.database_name, &query_ctx)
391 .map_err(BoxedError::new)
392 .context(ExternalSnafu)?;
393 let table_metadata_manager = self
394 .catalog_manager
395 .as_any()
396 .downcast_ref::<KvBackendCatalogManager>()
397 .map(|manager| manager.table_metadata_manager_ref().clone())
398 .context(UpgradeCatalogManagerRefSnafu)?;
399 let opts: HashMap<String, String> = table_metadata_manager
400 .schema_manager()
401 .get(SchemaNameKey::new(&catalog, &database))
402 .await
403 .context(TableMetadataManagerSnafu)?
404 .context(SchemaNotFoundSnafu {
405 schema_info: &database,
406 })?
407 .into_inner()
408 .into();
409
410 self.show_create_database(&database, opts.into()).await
411 }
412 Statement::ShowCreateTable(show) => {
413 let (catalog, schema, table) =
414 table_idents_to_full_name(&show.table_name, &query_ctx)
415 .map_err(BoxedError::new)
416 .context(ExternalSnafu)?;
417
418 let table_ref = self
419 .catalog_manager
420 .table(&catalog, &schema, &table, Some(&query_ctx))
421 .await
422 .context(CatalogSnafu)?
423 .context(TableNotFoundSnafu { table_name: &table })?;
424 let table_name = TableName::new(catalog, schema, table);
425
426 match show.variant {
427 ShowCreateTableVariant::Original => {
428 self.show_create_table(table_name, table_ref, query_ctx)
429 .await
430 }
431 ShowCreateTableVariant::PostgresForeignTable => {
432 self.show_create_table_for_pg(table_name, table_ref, query_ctx)
433 .await
434 }
435 }
436 }
437 Statement::ShowCreateFlow(show) => self.show_create_flow(show, query_ctx).await,
438 Statement::ShowCreateView(show) => self.show_create_view(show, query_ctx).await,
439 #[cfg(feature = "enterprise")]
440 Statement::ShowCreateTrigger(show) => self.show_create_trigger(show, query_ctx).await,
441 Statement::SetVariables(set_var) => self.set_variables(set_var, query_ctx),
442 Statement::ShowVariables(show_variable) => self.show_variable(show_variable, query_ctx),
443 Statement::Comment(stmt) => self.comment(stmt, query_ctx).await,
444 Statement::ShowColumns(show_columns) => {
445 self.show_columns(show_columns, query_ctx).await
446 }
447 Statement::ShowIndex(show_index) => self.show_index(show_index, query_ctx).await,
448 Statement::ShowRegion(show_region) => self.show_region(show_region, query_ctx).await,
449 Statement::ShowStatus(_) => self.show_status(query_ctx).await,
450 Statement::ShowSearchPath(_) => self.show_search_path(query_ctx).await,
451 Statement::Use(db) => self.use_database(db, query_ctx).await,
452 Statement::Admin(admin) => self.execute_admin_command(admin, query_ctx).await,
453 Statement::Kill(kill) => self.execute_kill(query_ctx, kill).await,
454 Statement::ShowProcesslist(show) => self.show_processlist(show, query_ctx).await,
455 }
456 }
457
458 pub async fn use_database(&self, db: String, query_ctx: QueryContextRef) -> Result<Output> {
459 let catalog = query_ctx.current_catalog();
460 ensure!(
461 self.catalog_manager
462 .schema_exists(catalog, db.as_ref(), Some(&query_ctx))
463 .await
464 .context(CatalogSnafu)?,
465 SchemaNotFoundSnafu { schema_info: &db }
466 );
467
468 query_ctx.set_current_schema(&db);
469
470 Ok(Output::new_with_record_batches(RecordBatches::empty()))
471 }
472
473 fn set_variables(&self, set_var: SetVariables, query_ctx: QueryContextRef) -> Result<Output> {
474 let var_name = set_var.variable.to_string().to_uppercase();
475
476 debug!(
477 "Trying to set {}={} for session: {} ",
478 var_name,
479 set_var.value.iter().map(|e| e.to_string()).join(", "),
480 query_ctx.conn_info()
481 );
482
483 match var_name.as_str() {
484 "READ_PREFERENCE" => set_read_preference(set_var.value, query_ctx)?,
485
486 "@@TIME_ZONE" | "@@SESSION.TIME_ZONE" | "TIMEZONE" | "TIME_ZONE" => {
487 set_timezone(set_var.value, query_ctx)?
488 }
489
490 "BYTEA_OUTPUT" => set_bytea_output(set_var.value, query_ctx)?,
491
492 "DATESTYLE" => set_datestyle(set_var.value, query_ctx)?,
496 "INTERVALSTYLE" => set_intervalstyle(set_var.value, query_ctx)?,
497
498 "ALLOW_QUERY_FALLBACK" => set_allow_query_fallback(set_var.value, query_ctx)?,
500
501 "CLIENT_ENCODING" => validate_client_encoding(set_var)?,
502 "@@SESSION.MAX_EXECUTION_TIME" | "MAX_EXECUTION_TIME" => match query_ctx.channel() {
503 Channel::Mysql => set_query_timeout(set_var.value, query_ctx)?,
504 Channel::Postgres => {
505 warn!(
506 "Unsupported set variable {} for channel {:?}",
507 var_name,
508 query_ctx.channel()
509 );
510 query_ctx.set_warning(format!("Unsupported set variable {}", var_name))
511 }
512 _ => {
513 return NotSupportedSnafu {
514 feat: format!("Unsupported set variable {}", var_name),
515 }
516 .fail();
517 }
518 },
519 "STATEMENT_TIMEOUT" => match query_ctx.channel() {
520 Channel::Postgres => set_query_timeout(set_var.value, query_ctx)?,
521 Channel::Mysql => {
522 warn!(
523 "Unsupported set variable {} for channel {:?}",
524 var_name,
525 query_ctx.channel()
526 );
527 query_ctx.set_warning(format!("Unsupported set variable {}", var_name));
528 }
529 _ => {
530 return NotSupportedSnafu {
531 feat: format!("Unsupported set variable {}", var_name),
532 }
533 .fail();
534 }
535 },
536 "SEARCH_PATH" => {
537 if query_ctx.channel() == Channel::Postgres {
538 let search_path = set_var.search_path().context(NotSupportedSnafu {
539 feat: "Unsupported search path in set variable statement",
540 })?;
541 query_ctx.set_current_schema(search_path);
542 } else {
543 return NotSupportedSnafu {
544 feat: format!("Unsupported set variable {}", var_name),
545 }
546 .fail();
547 }
548 }
549 _ => {
550 if query_ctx.channel() == Channel::Postgres || query_ctx.channel() == Channel::Mysql
551 {
552 warn!(
556 "Unsupported set variable {} for channel {:?}",
557 var_name,
558 query_ctx.channel()
559 );
560 query_ctx.set_warning(format!("Unsupported set variable {}", var_name));
561 } else {
562 return NotSupportedSnafu {
563 feat: format!("Unsupported set variable {}", var_name),
564 }
565 .fail();
566 }
567 }
568 }
569 Ok(Output::new_with_affected_rows(0))
570 }
571
572 #[tracing::instrument(skip_all)]
573 pub async fn plan(
574 &self,
575 stmt: &QueryStatement,
576 query_ctx: QueryContextRef,
577 ) -> Result<LogicalPlan> {
578 self.query_engine
579 .planner()
580 .plan(stmt, query_ctx)
581 .await
582 .context(PlanStatementSnafu)
583 }
584
585 #[tracing::instrument(skip_all)]
587 pub async fn exec_plan(&self, plan: LogicalPlan, query_ctx: QueryContextRef) -> Result<Output> {
588 self.query_engine
589 .execute(plan, query_ctx)
590 .await
591 .context(ExecLogicalPlanSnafu)
592 }
593
594 pub fn optimize_logical_plan(&self, plan: LogicalPlan) -> Result<LogicalPlan> {
595 self.query_engine
596 .planner()
597 .optimize(plan)
598 .context(PlanStatementSnafu)
599 }
600
601 #[tracing::instrument(skip_all)]
602 async fn plan_exec(&self, stmt: QueryStatement, query_ctx: QueryContextRef) -> Result<Output> {
603 let plan = self.plan(&stmt, query_ctx.clone()).await?;
604 self.exec_plan(plan, query_ctx).await
605 }
606
607 async fn get_table(&self, table_ref: &TableReference<'_>) -> Result<TableRef> {
608 let TableReference {
609 catalog,
610 schema,
611 table,
612 } = table_ref;
613 self.catalog_manager
614 .table(catalog, schema, table, None)
615 .await
616 .context(CatalogSnafu)?
617 .with_context(|| TableNotFoundSnafu {
618 table_name: table_ref.to_string(),
619 })
620 }
621
622 pub fn procedure_executor(&self) -> &ProcedureExecutorRef {
623 &self.procedure_executor
624 }
625
626 pub fn cache_invalidator(&self) -> &CacheInvalidatorRef {
627 &self.cache_invalidator
628 }
629
630 pub async fn convert_truncate_time_ranges(
633 &self,
634 table_name: &TableName,
635 sql_values_time_range: &[(sqlparser::ast::Value, sqlparser::ast::Value)],
636 query_ctx: &QueryContextRef,
637 ) -> Result<Vec<(Timestamp, Timestamp)>> {
638 if sql_values_time_range.is_empty() {
639 return Ok(vec![]);
640 }
641 let table = self.get_table(&table_name.table_ref()).await?;
642 let info = table.table_info();
643 let time_index_dt = info
644 .meta
645 .schema
646 .timestamp_column()
647 .context(UnexpectedSnafu {
648 violated: "Table must have a timestamp column",
649 })?;
650
651 let time_unit = time_index_dt
652 .data_type
653 .as_timestamp()
654 .with_context(|| UnexpectedSnafu {
655 violated: format!(
656 "Table {}'s time index column must be a timestamp type, found: {:?}",
657 table_name, time_index_dt
658 ),
659 })?
660 .unit();
661
662 let start_column = ColumnSchema::new(
663 "range_start",
664 ConcreteDataType::timestamp_datatype(time_unit),
665 false,
666 );
667 let end_column = ColumnSchema::new(
668 "range_end",
669 ConcreteDataType::timestamp_datatype(time_unit),
670 false,
671 );
672 let mut time_ranges = Vec::with_capacity(sql_values_time_range.len());
673 for (start, end) in sql_values_time_range {
674 let start = common_sql::convert::sql_value_to_value(
675 &start_column,
676 start,
677 Some(&query_ctx.timezone()),
678 None,
679 false,
680 )
681 .context(SqlCommonSnafu)
682 .and_then(|v| {
683 if let datatypes::value::Value::Timestamp(t) = v {
684 Ok(t)
685 } else {
686 error::InvalidSqlSnafu {
687 err_msg: format!("Expected a timestamp value, found {v:?}"),
688 }
689 .fail()
690 }
691 })?;
692
693 let end = common_sql::convert::sql_value_to_value(
694 &end_column,
695 end,
696 Some(&query_ctx.timezone()),
697 None,
698 false,
699 )
700 .context(SqlCommonSnafu)
701 .and_then(|v| {
702 if let datatypes::value::Value::Timestamp(t) = v {
703 Ok(t)
704 } else {
705 error::InvalidSqlSnafu {
706 err_msg: format!("Expected a timestamp value, found {v:?}"),
707 }
708 .fail()
709 }
710 })?;
711 time_ranges.push((start, end));
712 }
713 Ok(time_ranges)
714 }
715
716 pub(crate) fn inserter(&self) -> &InserterRef {
718 &self.inserter
719 }
720}
721
722fn to_copy_query_request(stmt: CopyQueryToArgument) -> Result<CopyQueryToRequest> {
723 let CopyQueryToArgument {
724 with,
725 connection,
726 location,
727 } = stmt;
728
729 Ok(CopyQueryToRequest {
730 location,
731 with: with.into_map(),
732 connection: connection.into_map(),
733 })
734}
735
736fn verify_time_related_format(with: &OptionMap) -> Result<()> {
738 let time_format = with.get(common_datasource::file_format::TIME_FORMAT);
739 let date_format = with.get(common_datasource::file_format::DATE_FORMAT);
740 let timestamp_format = with.get(common_datasource::file_format::TIMESTAMP_FORMAT);
741 let file_format = with.get(common_datasource::file_format::FORMAT_TYPE);
742
743 if !matches!(file_format, Some(f) if f.eq_ignore_ascii_case("csv")) {
744 ensure!(
745 time_format.is_none() && date_format.is_none() && timestamp_format.is_none(),
746 error::TimestampFormatNotSupportedSnafu {
747 format: "<unknown>".to_string(),
748 file_format: file_format.unwrap_or_default(),
749 }
750 );
751 }
752
753 for (key, format_opt) in [
754 (common_datasource::file_format::TIME_FORMAT, time_format),
755 (common_datasource::file_format::DATE_FORMAT, date_format),
756 (
757 common_datasource::file_format::TIMESTAMP_FORMAT,
758 timestamp_format,
759 ),
760 ] {
761 if let Some(format) = format_opt {
762 chrono::format::strftime::StrftimeItems::new(format)
763 .parse()
764 .map_err(|_| error::InvalidCopyParameterSnafu { key, value: format }.build())?;
765 }
766 }
767
768 Ok(())
769}
770
771fn to_copy_table_request(stmt: CopyTable, query_ctx: QueryContextRef) -> Result<CopyTableRequest> {
772 let direction = match stmt {
773 CopyTable::To(_) => CopyDirection::Export,
774 CopyTable::From(_) => CopyDirection::Import,
775 };
776
777 let CopyTableArgument {
778 location,
779 connection,
780 with,
781 table_name,
782 limit,
783 ..
784 } = match stmt {
785 CopyTable::To(arg) => arg,
786 CopyTable::From(arg) => arg,
787 };
788 let (catalog_name, schema_name, table_name) =
789 table_idents_to_full_name(&table_name, &query_ctx)
790 .map_err(BoxedError::new)
791 .context(ExternalSnafu)?;
792
793 let timestamp_range = timestamp_range_from_option_map(&with, &query_ctx)?;
794
795 verify_time_related_format(&with)?;
796
797 let pattern = with
798 .get(common_datasource::file_format::FILE_PATTERN)
799 .map(|x| x.to_string());
800
801 Ok(CopyTableRequest {
802 catalog_name,
803 schema_name,
804 table_name,
805 location,
806 with: with.into_map(),
807 connection: connection.into_map(),
808 pattern,
809 direction,
810 timestamp_range,
811 limit,
812 })
813}
814
815fn to_copy_database_request(
818 arg: CopyDatabaseArgument,
819 query_ctx: &QueryContextRef,
820) -> Result<CopyDatabaseRequest> {
821 let (catalog_name, database_name) = idents_to_full_database_name(&arg.database_name, query_ctx)
822 .map_err(BoxedError::new)
823 .context(ExternalSnafu)?;
824 let time_range = timestamp_range_from_option_map(&arg.with, query_ctx)?;
825
826 Ok(CopyDatabaseRequest {
827 catalog_name,
828 schema_name: database_name,
829 location: arg.location,
830 with: arg.with.into_map(),
831 connection: arg.connection.into_map(),
832 time_range,
833 })
834}
835
836fn timestamp_range_from_option_map(
840 options: &OptionMap,
841 query_ctx: &QueryContextRef,
842) -> Result<Option<TimestampRange>> {
843 let start_timestamp = extract_timestamp(options, COPY_DATABASE_TIME_START_KEY, query_ctx)?;
844 let end_timestamp = extract_timestamp(options, COPY_DATABASE_TIME_END_KEY, query_ctx)?;
845 let time_range = match (start_timestamp, end_timestamp) {
846 (Some(start), Some(end)) => Some(TimestampRange::new(start, end).with_context(|| {
847 error::InvalidTimestampRangeSnafu {
848 start: start.to_iso8601_string(),
849 end: end.to_iso8601_string(),
850 }
851 })?),
852 (Some(start), None) => Some(TimestampRange::from_start(start)),
853 (None, Some(end)) => Some(TimestampRange::until_end(end, false)), (None, None) => None,
855 };
856 Ok(time_range)
857}
858
859fn extract_timestamp(
861 map: &OptionMap,
862 key: &str,
863 query_ctx: &QueryContextRef,
864) -> Result<Option<Timestamp>> {
865 map.get(key)
866 .map(|v| {
867 Timestamp::from_str(v, Some(&query_ctx.timezone()))
868 .map_err(|_| error::InvalidCopyParameterSnafu { key, value: v }.build())
869 })
870 .transpose()
871}
872
873fn idents_to_full_database_name(
874 obj_name: &ObjectName,
875 query_ctx: &QueryContextRef,
876) -> Result<(String, String)> {
877 match &obj_name.0[..] {
878 [database] => Ok((
879 query_ctx.current_catalog().to_owned(),
880 database.to_string_unquoted(),
881 )),
882 [catalog, database] => Ok((catalog.to_string_unquoted(), database.to_string_unquoted())),
883 _ => InvalidSqlSnafu {
884 err_msg: format!(
885 "expect database name to be <catalog>.<database>, <database>, found: {obj_name}",
886 ),
887 }
888 .fail(),
889 }
890}
891
892pub struct InserterImpl {
894 statement_executor: StatementExecutorRef,
895 options: Option<InsertOptions>,
896}
897
898impl InserterImpl {
899 pub fn new(statement_executor: StatementExecutorRef, options: Option<InsertOptions>) -> Self {
900 Self {
901 statement_executor,
902 options,
903 }
904 }
905}
906
907#[async_trait::async_trait]
908impl Inserter for InserterImpl {
909 async fn insert_rows(
910 &self,
911 context: &client::inserter::Context<'_>,
912 requests: RowInsertRequests,
913 ) -> ClientResult<()> {
914 let mut ctx_builder = QueryContextBuilder::default()
915 .current_catalog(context.catalog.to_string())
916 .current_schema(context.schema.to_string());
917 if let Some(options) = self.options.as_ref() {
918 ctx_builder = ctx_builder
919 .set_extension(
920 TTL_KEY.to_string(),
921 format_duration(options.ttl).to_string(),
922 )
923 .set_extension(APPEND_MODE_KEY.to_string(), options.append_mode.to_string());
924 }
925 let query_ctx = ctx_builder.build().into();
926
927 self.statement_executor
928 .inserter()
929 .handle_row_inserts(
930 requests,
931 query_ctx,
932 self.statement_executor.as_ref(),
933 false,
934 false,
935 )
936 .await
937 .map_err(BoxedError::new)
938 .context(ClientExternalSnafu)
939 .map(|_| ())
940 }
941
942 fn set_options(&mut self, options: &InsertOptions) {
943 self.options = Some(*options);
944 }
945}
946
947#[cfg(test)]
948mod tests {
949 use std::assert_matches;
950 use std::collections::HashMap;
951
952 use common_time::range::TimestampRange;
953 use common_time::{Timestamp, Timezone};
954 use session::context::QueryContextBuilder;
955 use sql::statements::OptionMap;
956
957 use crate::error;
958 use crate::statement::copy_database::{
959 COPY_DATABASE_TIME_END_KEY, COPY_DATABASE_TIME_START_KEY,
960 };
961 use crate::statement::{timestamp_range_from_option_map, verify_time_related_format};
962
963 fn check_timestamp_range((start, end): (&str, &str)) -> error::Result<Option<TimestampRange>> {
964 let query_ctx = QueryContextBuilder::default()
965 .timezone(Timezone::from_tz_string("Asia/Shanghai").unwrap())
966 .build()
967 .into();
968 let map = OptionMap::from(
969 [
970 (COPY_DATABASE_TIME_START_KEY.to_string(), start.to_string()),
971 (COPY_DATABASE_TIME_END_KEY.to_string(), end.to_string()),
972 ]
973 .into_iter()
974 .collect::<HashMap<_, _>>(),
975 );
976 timestamp_range_from_option_map(&map, &query_ctx)
977 }
978
979 #[test]
980 fn test_timestamp_range_from_option_map() {
981 assert_eq!(
982 Some(
983 TimestampRange::new(
984 Timestamp::new_second(1649635200),
985 Timestamp::new_second(1649664000),
986 )
987 .unwrap(),
988 ),
989 check_timestamp_range(("2022-04-11 08:00:00", "2022-04-11 16:00:00"),).unwrap()
990 );
991
992 assert_matches!(
993 check_timestamp_range(("2022-04-11 08:00:00", "2022-04-11 07:00:00")).unwrap_err(),
994 error::Error::InvalidTimestampRange { .. }
995 );
996 }
997
998 #[test]
999 fn test_verify_timestamp_format() {
1000 let map = OptionMap::from(
1001 [
1002 (
1003 common_datasource::file_format::TIMESTAMP_FORMAT.to_string(),
1004 "%Y-%m-%d %H:%M:%S".to_string(),
1005 ),
1006 (
1007 common_datasource::file_format::FORMAT_TYPE.to_string(),
1008 "csv".to_string(),
1009 ),
1010 ]
1011 .into_iter()
1012 .collect::<HashMap<_, _>>(),
1013 );
1014 assert!(verify_time_related_format(&map).is_ok());
1015
1016 let map = OptionMap::from(
1017 [
1018 (
1019 common_datasource::file_format::TIMESTAMP_FORMAT.to_string(),
1020 "%Y-%m-%d %H:%M:%S".to_string(),
1021 ),
1022 (
1023 common_datasource::file_format::FORMAT_TYPE.to_string(),
1024 "json".to_string(),
1025 ),
1026 ]
1027 .into_iter()
1028 .collect::<HashMap<_, _>>(),
1029 );
1030
1031 assert_matches!(
1032 verify_time_related_format(&map).unwrap_err(),
1033 error::Error::TimestampFormatNotSupported { .. }
1034 );
1035 let map = OptionMap::from(
1036 [
1037 (
1038 common_datasource::file_format::TIMESTAMP_FORMAT.to_string(),
1039 "%111112".to_string(),
1040 ),
1041 (
1042 common_datasource::file_format::FORMAT_TYPE.to_string(),
1043 "csv".to_string(),
1044 ),
1045 ]
1046 .into_iter()
1047 .collect::<HashMap<_, _>>(),
1048 );
1049
1050 assert_matches!(
1051 verify_time_related_format(&map).unwrap_err(),
1052 error::Error::InvalidCopyParameter { .. }
1053 );
1054 }
1055}