frontend/instance/
export_database.rs1use std::sync::Arc;
16
17use auth::{
18 PermissionAction, PermissionChecker, PermissionCheckerRef, PermissionReq,
19 PermissionTableTarget, PermissionTableTargets,
20};
21use common_error::ext::BoxedError;
22use common_query::Output;
23use common_recordbatch::RecordBatches;
24use common_telemetry::error;
25use datatypes::prelude::ConcreteDataType;
26use datatypes::schema::{ColumnSchema, Schema};
27use datatypes::vectors::StringVector;
28use operator::statement::export_database::{DatabaseExportSummary, PreparedDatabaseExport};
29use session::context::QueryContextRef;
30use snafu::ResultExt;
31use sql::ast::{Ident, ObjectName};
32use sql::statements::OptionMap;
33use sql::statements::copy::{Copy, CopyDatabase, CopyDatabaseArgument};
34use sql::statements::statement::Statement;
35use table::requests::CopyDatabaseRequest;
36use tokio_util::sync::CancellationToken;
37
38use crate::error::{PermissionSnafu, Result};
39use crate::instance::Instance;
40
41pub(crate) fn parse_metric_export_requested(options: &OptionMap) -> Result<bool> {
42 match options.get("experimental_metric_export") {
43 None | Some("false") => Ok(false),
44 Some("true") => Ok(true),
45 Some(_) => Err(operator::error::InvalidDatabaseExportSnafu {
46 reason: "experimental_metric_export must be true or false",
47 }
48 .build()
49 .into()),
50 }
51}
52
53impl Instance {
54 pub(crate) fn show_metric_export_capability(&self) -> Result<Output> {
55 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
56 "EXPERIMENTAL_METRIC_EXPORT",
57 ConcreteDataType::string_datatype(),
58 false,
59 )]));
60 let batches = RecordBatches::try_from_columns(
61 schema,
62 vec![Arc::new(StringVector::from(vec![
63 self.experimental_metric_export.to_string(),
64 ])) as datatypes::vectors::VectorRef],
65 )
66 .map_err(BoxedError::new)
67 .context(crate::error::ExternalSnafu)?;
68 Ok(Output::new_with_record_batches(batches))
69 }
70
71 pub(crate) async fn copy_metric_database(
72 &self,
73 arg: CopyDatabaseArgument,
74 ctx: QueryContextRef,
75 ) -> Result<Output> {
76 if !self.experimental_metric_export {
77 return Err(operator::error::InvalidDatabaseExportSnafu {
78 reason: "experimental_metric_export is disabled on this frontend",
79 }
80 .build()
81 .into());
82 }
83 let req = operator::statement::to_copy_database_request(arg, &ctx)?;
84 let plan = self.prepare_database_export(req, None, &ctx).await?;
85 let cancellation = CancellationToken::new();
86 let _guard = cancellation.clone().drop_guard();
87 let executor = self.statement_executor.clone();
88 let task = common_runtime::spawn_query(async move {
90 let result = executor.export_database(plan, &cancellation, ctx).await;
91 if let Err(err) = &result {
92 error!(err; "Experimental database export failed after draining started work");
93 }
94 result
95 });
96 let summary = task.await.context(operator::error::JoinTaskSnafu)??;
97 Ok(Output::new_with_affected_rows(summary.rows))
98 }
99
100 #[allow(dead_code)]
101 async fn export_database(
102 &self,
103 req: CopyDatabaseRequest,
104 names: Option<&[String]>,
105 cancellation: &CancellationToken,
106 ctx: QueryContextRef,
107 ) -> Result<DatabaseExportSummary> {
108 if cancellation.is_cancelled() {
109 return Err(operator::error::DatabaseExportCancelledSnafu.build().into());
110 }
111 let plan = self.prepare_database_export(req, names, &ctx).await?;
112 Ok(self
113 .statement_executor
114 .export_database(plan, cancellation, ctx)
115 .await?)
116 }
117
118 #[cfg(any(test, feature = "testing"))]
120 pub async fn export_database_for_test(
121 &self,
122 req: CopyDatabaseRequest,
123 names: Option<&[String]>,
124 cancellation: &CancellationToken,
125 ctx: QueryContextRef,
126 ) -> Result<DatabaseExportSummary> {
127 self.export_database(req, names, cancellation, ctx).await
128 }
129
130 async fn prepare_database_export(
131 &self,
132 req: CopyDatabaseRequest,
133 names: Option<&[String]>,
134 ctx: &QueryContextRef,
135 ) -> Result<PreparedDatabaseExport> {
136 let stmt = Statement::Copy(Copy::CopyDatabase(CopyDatabase::To(CopyDatabaseArgument {
137 database_name: ObjectName::from(vec![
138 Ident::new(&req.catalog_name),
139 Ident::new(&req.schema_name),
140 ]),
141 with: req.with.clone().into(),
142 connection: req.connection.clone().into(),
143 location: req.location.clone(),
144 })));
145 self.plugins
146 .get::<PermissionCheckerRef>()
147 .as_ref()
148 .check_permission_with_context(
149 ctx.current_user(),
150 PermissionReq::SqlStatement(&stmt),
151 Some(&ctx.current_schema()),
152 )
153 .context(PermissionSnafu)?;
154 let tables = self
155 .statement_executor
156 .capture_database_export_tables(&req, names, ctx)
157 .await?;
158 let targets = PermissionTableTargets::resolved(
159 tables
160 .iter()
161 .map(|table| {
162 let info = table.table_info();
163 PermissionTableTarget::new(&info.catalog_name, &info.schema_name, &info.name)
164 })
165 .collect(),
166 );
167 self.check_table_permission(ctx, PermissionReq::SqlStatement(&stmt), targets.clone())
168 .context(PermissionSnafu)?;
169 self.check_table_permission(
171 ctx,
172 PermissionReq::Action(PermissionAction::read("database.export")),
173 targets,
174 )
175 .context(PermissionSnafu)?;
176 Ok(self
177 .statement_executor
178 .prepare_database_export(req, tables)
179 .await?)
180 }
181}