1use api::v1::meta::reconcile_request::Target;
16use api::v1::meta::{ReconcileRequest, ReconcileTable, ResolveStrategy};
17use arrow::datatypes::DataType as ArrowDataType;
18use common_catalog::format_full_table_name;
19use common_error::ext::BoxedError;
20use common_macro::admin_fn;
21use common_query::error::{
22 MissingProcedureServiceHandlerSnafu, Result, TableMutationSnafu, UnsupportedInputDataTypeSnafu,
23};
24use common_telemetry::info;
25use datafusion_expr::{Signature, TypeSignature, Volatility};
26use datatypes::prelude::*;
27use session::context::QueryContextRef;
28use session::table_name::table_name_to_full_name;
29use snafu::ResultExt;
30
31use crate::handlers::ProcedureServiceHandlerRef;
32use crate::helper::parse_resolve_strategy;
33
34const FN_NAME: &str = "reconcile_table";
35
36#[admin_fn(
45 name = ReconcileTableFunction,
46 display_name = reconcile_table,
47 sig_fn = signature,
48 ret = string
49)]
50pub(crate) async fn reconcile_table(
51 procedure_service_handler: &ProcedureServiceHandlerRef,
52 query_ctx: &QueryContextRef,
53 params: &[ValueRef<'_>],
54) -> Result<Value> {
55 let (table_name, resolve_strategy) = match params {
56 [ValueRef::String(table_name)] => (table_name, ResolveStrategy::UseLatest),
57 [
58 ValueRef::String(table_name),
59 ValueRef::String(resolve_strategy),
60 ] => (table_name, parse_resolve_strategy(resolve_strategy)?),
61 _ => {
62 return UnsupportedInputDataTypeSnafu {
63 function: FN_NAME,
64 datatypes: params.iter().map(|v| v.data_type()).collect::<Vec<_>>(),
65 }
66 .fail();
67 }
68 };
69 let (catalog_name, schema_name, table_name) = table_name_to_full_name(table_name, query_ctx)
70 .map_err(BoxedError::new)
71 .context(TableMutationSnafu)?;
72 info!(
73 "Reconciling table: {} with resolve_strategy: {:?}",
74 format_full_table_name(&catalog_name, &schema_name, &table_name),
75 resolve_strategy
76 );
77 let pid = procedure_service_handler
78 .reconcile(ReconcileRequest {
79 target: Some(Target::ReconcileTable(ReconcileTable {
80 catalog_name,
81 schema_name,
82 table_name,
83 resolve_strategy: resolve_strategy as i32,
84 })),
85 ..Default::default()
86 })
87 .await?;
88 match pid {
89 Some(pid) => {
90 query_ctx.set_last_procedure_id(pid.clone());
91 Ok(Value::from(pid))
92 }
93 None => Ok(Value::Null),
94 }
95}
96
97fn signature() -> Signature {
98 Signature::one_of(
99 vec![
100 TypeSignature::Exact(vec![ArrowDataType::Utf8]),
102 TypeSignature::Exact(vec![ArrowDataType::Utf8, ArrowDataType::Utf8]),
104 ],
105 Volatility::Immutable,
106 )
107}
108
109#[cfg(test)]
110mod tests {
111 use std::sync::Arc;
112
113 use arrow::array::StringArray;
114 use arrow::datatypes::{DataType, Field};
115 use datafusion_expr::ColumnarValue;
116
117 use crate::admin::reconcile_table::ReconcileTableFunction;
118 use crate::function::FunctionContext;
119 use crate::function_factory::ScalarFunctionFactory;
120
121 #[tokio::test]
122 async fn test_reconcile_table() {
123 common_telemetry::init_default_ut_logging();
124
125 let factory: ScalarFunctionFactory = ReconcileTableFunction::factory().into();
127 let context = FunctionContext::mock();
128 let query_ctx = context.query_ctx.clone();
129 query_ctx.set_last_procedure_id("previous-id".to_string());
130 let provider = factory.provide(context);
131 let f = provider.as_async().unwrap();
132
133 let func_args = datafusion::logical_expr::ScalarFunctionArgs {
134 args: vec![ColumnarValue::Array(Arc::new(StringArray::from(vec![
135 "test",
136 ])))],
137 arg_fields: vec![Arc::new(Field::new("arg_0", DataType::Utf8, false))],
138 return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
139 number_rows: 1,
140 config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
141 };
142 let result = f.invoke_async_with_args(func_args).await.unwrap();
143 assert_eq!(query_ctx.last_procedure_id().as_deref(), Some("test_pid"));
144 match result {
145 ColumnarValue::Array(array) => {
146 let result_array = array.as_any().downcast_ref::<StringArray>().unwrap();
147 assert_eq!(result_array.value(0), "test_pid");
148 }
149 ColumnarValue::Scalar(scalar) => {
150 assert_eq!(
151 scalar,
152 datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string()))
153 );
154 }
155 }
156
157 let factory: ScalarFunctionFactory = ReconcileTableFunction::factory().into();
159 let provider = factory.provide(FunctionContext::mock());
160 let f = provider.as_async().unwrap();
161
162 let func_args = datafusion::logical_expr::ScalarFunctionArgs {
163 args: vec![
164 ColumnarValue::Array(Arc::new(StringArray::from(vec!["test"]))),
165 ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseMetasrv"]))),
166 ],
167 arg_fields: vec![
168 Arc::new(Field::new("arg_0", DataType::Utf8, false)),
169 Arc::new(Field::new("arg_1", DataType::Utf8, false)),
170 ],
171 return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
172 number_rows: 1,
173 config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
174 };
175 let result = f.invoke_async_with_args(func_args).await.unwrap();
176 match result {
177 ColumnarValue::Array(array) => {
178 let result_array = array.as_any().downcast_ref::<StringArray>().unwrap();
179 assert_eq!(result_array.value(0), "test_pid");
180 }
181 ColumnarValue::Scalar(scalar) => {
182 assert_eq!(
183 scalar,
184 datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string()))
185 );
186 }
187 }
188
189 let factory: ScalarFunctionFactory = ReconcileTableFunction::factory().into();
191 let provider = factory.provide(FunctionContext::mock());
192 let f = provider.as_async().unwrap();
193
194 let func_args = datafusion::logical_expr::ScalarFunctionArgs {
195 args: vec![
196 ColumnarValue::Array(Arc::new(StringArray::from(vec!["test"]))),
197 ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseMetasrv"]))),
198 ColumnarValue::Array(Arc::new(StringArray::from(vec!["10"]))),
199 ],
200 arg_fields: vec![
201 Arc::new(Field::new("arg_0", DataType::Utf8, false)),
202 Arc::new(Field::new("arg_1", DataType::Utf8, false)),
203 Arc::new(Field::new("arg_2", DataType::Utf8, false)),
204 ],
205 return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
206 number_rows: 1,
207 config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
208 };
209 let _err = f.invoke_async_with_args(func_args).await.unwrap_err();
210 }
212}