1#[cfg(feature = "enterprise")]
16pub mod trigger;
17
18use std::collections::{HashMap, HashSet};
19use std::result;
20use std::time::Duration;
21
22use api::helper::{from_pb_time_ranges, to_pb_time_ranges};
23use api::v1::alter_database_expr::Kind as PbAlterDatabaseKind;
24use api::v1::meta::ddl_task_request::Task;
25use api::v1::meta::{
26 AlterDatabaseTask as PbAlterDatabaseTask, AlterTableTask as PbAlterTableTask,
27 AlterTableTasks as PbAlterTableTasks, CommentOnTask as PbCommentOnTask,
28 CreateDatabaseTask as PbCreateDatabaseTask, CreateFlowTask as PbCreateFlowTask,
29 CreateTableTask as PbCreateTableTask, CreateTableTasks as PbCreateTableTasks,
30 CreateViewTask as PbCreateViewTask, DdlTaskRequest as PbDdlTaskRequest,
31 DdlTaskResponse as PbDdlTaskResponse, DropDatabaseTask as PbDropDatabaseTask,
32 DropFlowTask as PbDropFlowTask, DropTableTask as PbDropTableTask,
33 DropTableTasks as PbDropTableTasks, DropViewTask as PbDropViewTask, Partition, ProcedureId,
34 PurgeDroppedTableTask as PbPurgeDroppedTableTask, TruncateTableTask as PbTruncateTableTask,
35 UndropTableTask as PbUndropTableTask,
36};
37use api::v1::{
38 AlterDatabaseExpr, AlterTableExpr, CommentObjectType as PbCommentObjectType, CommentOnExpr,
39 CreateDatabaseExpr, CreateFlowExpr, CreateTableExpr, CreateViewExpr, DropDatabaseExpr,
40 DropFlowExpr, DropTableExpr, DropViewExpr, EvalInterval, ExpireAfter, Option as PbOption,
41 QueryContext as PbQueryContext, TruncateTableExpr,
42};
43use base64::Engine as _;
44use base64::engine::general_purpose;
45use common_catalog::{format_full_flow_name, format_full_table_name};
46use common_error::ext::BoxedError;
47use common_time::{DatabaseTimeToLive, Timestamp};
48use prost::Message;
49use serde::{Deserialize, Serialize};
50use serde_with::{DefaultOnNull, serde_as};
51use snafu::{OptionExt, ResultExt};
52use table::metadata::{TableId, TableInfo};
53use table::requests::validate_database_option;
54use table::table_name::TableName;
55use table::table_reference::TableReference;
56
57use crate::error::{
58 self, ConvertTimeRangesSnafu, ExternalSnafu, InvalidSetDatabaseOptionSnafu,
59 InvalidUnsetDatabaseOptionSnafu, Result,
60};
61use crate::flow_name::FlowName;
62use crate::instruction::CacheIdent;
63use crate::key::FlowId;
64use crate::key::flow::flow_name::FlowNameManager;
65use crate::key::table_name::{TableNameKey, TableNameManager};
66
67pub const ORIGIN_FRONTEND_ADDR_EXTENSION_KEY: &str = "__greptime_origin_frontend.addr";
69
70pub const CREATE_DATABASE_CREATOR_EXTENSION_KEY: &str = "__greptime_create_database.creator";
72pub const CREATE_DATABASE_CREATOR_METADATA_KEY: &str =
74 "x-greptime-internal-create-database-creator-bin";
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct CreatorGrantIntent {
78 pub username: String,
79 pub created_at_ns: i64,
80}
81
82#[derive(Debug, Clone)]
84pub enum DdlTask {
85 CreateTable(CreateTableTask),
86 DropTable(DropTableTask),
87 UndropTable(UndropTableTask),
88 PurgeDroppedTable(PurgeDroppedTableTask),
89 AlterTable(AlterTableTask),
90 TruncateTable(TruncateTableTask),
91 CreateLogicalTables(Vec<CreateTableTask>),
92 DropLogicalTables(Vec<DropTableTask>),
93 AlterLogicalTables(Vec<AlterTableTask>),
94 CreateDatabase(CreateDatabaseTask),
95 DropDatabase(DropDatabaseTask),
96 AlterDatabase(AlterDatabaseTask),
97 CreateFlow(CreateFlowTask),
98 DropFlow(DropFlowTask),
99 #[cfg(feature = "enterprise")]
100 DropTrigger(trigger::DropTriggerTask),
101 CreateView(CreateViewTask),
102 DropView(DropViewTask),
103 #[cfg(feature = "enterprise")]
104 CreateTrigger(trigger::CreateTriggerTask),
105 CommentOn(CommentOnTask),
106}
107
108impl DdlTask {
109 pub fn new_create_flow(expr: CreateFlowTask) -> Self {
111 DdlTask::CreateFlow(expr)
112 }
113
114 pub fn new_drop_flow(expr: DropFlowTask) -> Self {
116 DdlTask::DropFlow(expr)
117 }
118
119 pub fn new_drop_view(expr: DropViewTask) -> Self {
121 DdlTask::DropView(expr)
122 }
123
124 pub fn new_create_table(
126 expr: CreateTableExpr,
127 partitions: Vec<Partition>,
128 table_info: TableInfo,
129 ) -> Self {
130 DdlTask::CreateTable(CreateTableTask::new(expr, partitions, table_info))
131 }
132
133 pub fn new_create_logical_tables(table_data: Vec<(CreateTableExpr, TableInfo)>) -> Self {
135 DdlTask::CreateLogicalTables(
136 table_data
137 .into_iter()
138 .map(|(expr, table_info)| CreateTableTask::new(expr, Vec::new(), table_info))
139 .collect(),
140 )
141 }
142
143 pub fn new_alter_logical_tables(table_data: Vec<AlterTableExpr>) -> Self {
145 DdlTask::AlterLogicalTables(
146 table_data
147 .into_iter()
148 .map(|alter_table| AlterTableTask { alter_table })
149 .collect(),
150 )
151 }
152
153 pub fn new_drop_table(
155 catalog: String,
156 schema: String,
157 table: String,
158 table_id: TableId,
159 drop_if_exists: bool,
160 ) -> Self {
161 DdlTask::DropTable(DropTableTask {
162 catalog,
163 schema,
164 table,
165 table_id,
166 drop_if_exists,
167 })
168 }
169
170 pub fn new_undrop_table(table_id: TableId) -> Self {
172 DdlTask::UndropTable(UndropTableTask { table_id })
173 }
174
175 pub fn new_purge_dropped_table(table_id: TableId) -> Self {
177 DdlTask::PurgeDroppedTable(PurgeDroppedTableTask { table_id })
178 }
179
180 pub fn new_create_database(
182 catalog: String,
183 schema: String,
184 create_if_not_exists: bool,
185 options: HashMap<String, String>,
186 creator: Option<CreatorGrantIntent>,
187 ) -> Self {
188 DdlTask::CreateDatabase(CreateDatabaseTask {
189 catalog,
190 schema,
191 create_if_not_exists,
192 options,
193 creator,
194 })
195 }
196
197 pub fn new_drop_database(catalog: String, schema: String, drop_if_exists: bool) -> Self {
199 DdlTask::DropDatabase(DropDatabaseTask {
200 catalog,
201 schema,
202 drop_if_exists,
203 })
204 }
205
206 pub fn new_alter_database(alter_expr: AlterDatabaseExpr) -> Self {
208 DdlTask::AlterDatabase(AlterDatabaseTask { alter_expr })
209 }
210
211 pub fn new_alter_table(alter_table: AlterTableExpr) -> Self {
213 DdlTask::AlterTable(AlterTableTask { alter_table })
214 }
215
216 pub fn new_truncate_table(
218 catalog: String,
219 schema: String,
220 table: String,
221 table_id: TableId,
222 time_ranges: Vec<(Timestamp, Timestamp)>,
223 ) -> Self {
224 DdlTask::TruncateTable(TruncateTableTask {
225 catalog,
226 schema,
227 table,
228 table_id,
229 time_ranges,
230 })
231 }
232
233 pub fn new_create_view(create_view: CreateViewExpr, view_info: TableInfo) -> Self {
235 DdlTask::CreateView(CreateViewTask {
236 create_view,
237 view_info,
238 })
239 }
240
241 pub fn new_comment_on(task: CommentOnTask) -> Self {
243 DdlTask::CommentOn(task)
244 }
245}
246
247impl TryFrom<Task> for DdlTask {
248 type Error = error::Error;
249 fn try_from(task: Task) -> Result<Self> {
250 match task {
251 Task::CreateTableTask(create_table) => {
252 Ok(DdlTask::CreateTable(create_table.try_into()?))
253 }
254 Task::DropTableTask(drop_table) => Ok(DdlTask::DropTable(drop_table.try_into()?)),
255 Task::UndropTableTask(undrop_table) => {
256 Ok(DdlTask::UndropTable(undrop_table.try_into()?))
257 }
258 Task::PurgeDroppedTableTask(purge_dropped_table) => {
259 Ok(DdlTask::PurgeDroppedTable(purge_dropped_table.try_into()?))
260 }
261 Task::AlterTableTask(alter_table) => Ok(DdlTask::AlterTable(alter_table.try_into()?)),
262 Task::TruncateTableTask(truncate_table) => {
263 Ok(DdlTask::TruncateTable(truncate_table.try_into()?))
264 }
265 Task::CreateTableTasks(create_tables) => {
266 let tasks = create_tables
267 .tasks
268 .into_iter()
269 .map(|task| task.try_into())
270 .collect::<Result<Vec<_>>>()?;
271
272 Ok(DdlTask::CreateLogicalTables(tasks))
273 }
274 Task::DropTableTasks(drop_tables) => {
275 let tasks = drop_tables
276 .tasks
277 .into_iter()
278 .map(|task| task.try_into())
279 .collect::<Result<Vec<_>>>()?;
280
281 Ok(DdlTask::DropLogicalTables(tasks))
282 }
283 Task::AlterTableTasks(alter_tables) => {
284 let tasks = alter_tables
285 .tasks
286 .into_iter()
287 .map(|task| task.try_into())
288 .collect::<Result<Vec<_>>>()?;
289
290 Ok(DdlTask::AlterLogicalTables(tasks))
291 }
292 Task::CreateDatabaseTask(create_database) => {
293 Ok(DdlTask::CreateDatabase(create_database.try_into()?))
294 }
295 Task::DropDatabaseTask(drop_database) => {
296 Ok(DdlTask::DropDatabase(drop_database.try_into()?))
297 }
298 Task::AlterDatabaseTask(alter_database) => {
299 Ok(DdlTask::AlterDatabase(alter_database.try_into()?))
300 }
301 Task::CreateFlowTask(create_flow) => Ok(DdlTask::CreateFlow(create_flow.try_into()?)),
302 Task::DropFlowTask(drop_flow) => Ok(DdlTask::DropFlow(drop_flow.try_into()?)),
303 Task::CreateViewTask(create_view) => Ok(DdlTask::CreateView(create_view.try_into()?)),
304 Task::DropViewTask(drop_view) => Ok(DdlTask::DropView(drop_view.try_into()?)),
305 Task::CreateTriggerTask(create_trigger) => {
306 #[cfg(feature = "enterprise")]
307 return Ok(DdlTask::CreateTrigger(create_trigger.try_into()?));
308 #[cfg(not(feature = "enterprise"))]
309 {
310 let _ = create_trigger;
311 crate::error::UnsupportedSnafu {
312 operation: "create trigger",
313 }
314 .fail()
315 }
316 }
317 Task::DropTriggerTask(drop_trigger) => {
318 #[cfg(feature = "enterprise")]
319 return Ok(DdlTask::DropTrigger(drop_trigger.try_into()?));
320 #[cfg(not(feature = "enterprise"))]
321 {
322 let _ = drop_trigger;
323 crate::error::UnsupportedSnafu {
324 operation: "drop trigger",
325 }
326 .fail()
327 }
328 }
329 Task::CommentOnTask(comment_on) => Ok(DdlTask::CommentOn(comment_on.try_into()?)),
330 }
331 }
332}
333
334#[derive(Clone)]
335pub struct SubmitDdlTaskRequest {
336 pub query_context: QueryContext,
337 pub wait: bool,
338 pub timeout: Duration,
339 pub task: DdlTask,
340}
341
342impl SubmitDdlTaskRequest {
343 pub fn new(query_context: QueryContext, task: DdlTask) -> Self {
345 Self {
346 query_context,
347 wait: Self::default_wait(),
348 timeout: Self::default_timeout(),
349 task,
350 }
351 }
352
353 pub fn default_timeout() -> Duration {
355 Duration::from_secs(60)
356 }
357
358 pub fn default_wait() -> bool {
360 true
361 }
362}
363
364fn ddl_timeout_secs(timeout: Duration) -> u32 {
365 timeout
366 .as_nanos()
367 .div_ceil(Duration::from_secs(1).as_nanos())
368 .try_into()
369 .unwrap_or(u32::MAX)
370}
371
372impl TryFrom<SubmitDdlTaskRequest> for PbDdlTaskRequest {
373 type Error = error::Error;
374
375 fn try_from(request: SubmitDdlTaskRequest) -> Result<Self> {
376 let SubmitDdlTaskRequest {
377 mut query_context,
378 wait,
379 timeout,
380 task,
381 } = request;
382
383 query_context
384 .extensions
385 .remove(CREATE_DATABASE_CREATOR_EXTENSION_KEY);
386 if let DdlTask::CreateDatabase(CreateDatabaseTask {
387 creator: Some(creator),
388 ..
389 }) = &task
390 {
391 query_context.extensions.insert(
392 CREATE_DATABASE_CREATOR_EXTENSION_KEY.to_string(),
393 serde_json::to_string(creator).context(error::SerdeJsonSnafu)?,
394 );
395 }
396
397 let task = match task {
398 DdlTask::CreateTable(task) => Task::CreateTableTask(task.try_into()?),
399 DdlTask::DropTable(task) => Task::DropTableTask(task.into()),
400 DdlTask::UndropTable(task) => Task::UndropTableTask(task.into()),
401 DdlTask::PurgeDroppedTable(task) => Task::PurgeDroppedTableTask(task.into()),
402 DdlTask::AlterTable(task) => Task::AlterTableTask(task.try_into()?),
403 DdlTask::TruncateTable(task) => Task::TruncateTableTask(task.try_into()?),
404 DdlTask::CreateLogicalTables(tasks) => {
405 let tasks = tasks
406 .into_iter()
407 .map(|task| task.try_into())
408 .collect::<Result<Vec<_>>>()?;
409
410 Task::CreateTableTasks(PbCreateTableTasks { tasks })
411 }
412 DdlTask::DropLogicalTables(tasks) => {
413 let tasks = tasks
414 .into_iter()
415 .map(|task| task.into())
416 .collect::<Vec<_>>();
417
418 Task::DropTableTasks(PbDropTableTasks { tasks })
419 }
420 DdlTask::AlterLogicalTables(tasks) => {
421 let tasks = tasks
422 .into_iter()
423 .map(|task| task.try_into())
424 .collect::<Result<Vec<_>>>()?;
425
426 Task::AlterTableTasks(PbAlterTableTasks { tasks })
427 }
428 DdlTask::CreateDatabase(task) => Task::CreateDatabaseTask(task.try_into()?),
429 DdlTask::DropDatabase(task) => Task::DropDatabaseTask(task.try_into()?),
430 DdlTask::AlterDatabase(task) => Task::AlterDatabaseTask(task.try_into()?),
431 DdlTask::CreateFlow(task) => Task::CreateFlowTask(task.into()),
432 DdlTask::DropFlow(task) => Task::DropFlowTask(task.into()),
433 DdlTask::CreateView(task) => Task::CreateViewTask(task.try_into()?),
434 DdlTask::DropView(task) => Task::DropViewTask(task.into()),
435 #[cfg(feature = "enterprise")]
436 DdlTask::CreateTrigger(task) => Task::CreateTriggerTask(task.try_into()?),
437 #[cfg(feature = "enterprise")]
438 DdlTask::DropTrigger(task) => Task::DropTriggerTask(task.into()),
439 DdlTask::CommentOn(task) => Task::CommentOnTask(task.into()),
440 };
441
442 Ok(Self {
443 header: None,
444 query_context: Some(query_context.into()),
445 timeout_secs: ddl_timeout_secs(timeout),
446 wait,
447 task: Some(task),
448 })
449 }
450}
451
452#[derive(Debug, Default)]
453pub struct SubmitDdlTaskResponse {
454 pub key: Vec<u8>,
455 pub table_ids: Vec<TableId>,
457}
458
459impl TryFrom<PbDdlTaskResponse> for SubmitDdlTaskResponse {
460 type Error = error::Error;
461
462 fn try_from(resp: PbDdlTaskResponse) -> Result<Self> {
463 let table_ids = resp.table_ids.into_iter().map(|t| t.id).collect();
464 Ok(Self {
465 key: resp.pid.map(|pid| pid.key).unwrap_or_default(),
466 table_ids,
467 })
468 }
469}
470
471impl From<SubmitDdlTaskResponse> for PbDdlTaskResponse {
472 fn from(val: SubmitDdlTaskResponse) -> Self {
473 Self {
474 pid: Some(ProcedureId { key: val.key }),
475 table_ids: val
476 .table_ids
477 .into_iter()
478 .map(|id| api::v1::TableId { id })
479 .collect(),
480 ..Default::default()
481 }
482 }
483}
484
485#[derive(Debug, PartialEq, Clone)]
487pub struct CreateViewTask {
488 pub create_view: CreateViewExpr,
489 pub view_info: TableInfo,
490}
491
492impl CreateViewTask {
493 pub fn table_ref(&self) -> TableReference<'_> {
495 TableReference {
496 catalog: &self.create_view.catalog_name,
497 schema: &self.create_view.schema_name,
498 table: &self.create_view.view_name,
499 }
500 }
501
502 pub fn raw_logical_plan(&self) -> &Vec<u8> {
504 &self.create_view.logical_plan
505 }
506
507 pub fn view_definition(&self) -> &str {
509 &self.create_view.definition
510 }
511
512 pub fn table_names(&self) -> HashSet<TableName> {
514 self.create_view
515 .table_names
516 .iter()
517 .map(|t| t.clone().into())
518 .collect()
519 }
520
521 pub fn columns(&self) -> &Vec<String> {
523 &self.create_view.columns
524 }
525
526 pub fn plan_columns(&self) -> &Vec<String> {
528 &self.create_view.plan_columns
529 }
530}
531
532impl TryFrom<PbCreateViewTask> for CreateViewTask {
533 type Error = error::Error;
534
535 fn try_from(pb: PbCreateViewTask) -> Result<Self> {
536 let view_info = serde_json::from_slice(&pb.view_info).context(error::SerdeJsonSnafu)?;
537
538 Ok(CreateViewTask {
539 create_view: pb.create_view.context(error::InvalidProtoMsgSnafu {
540 err_msg: "expected create view",
541 })?,
542 view_info,
543 })
544 }
545}
546
547impl TryFrom<CreateViewTask> for PbCreateViewTask {
548 type Error = error::Error;
549
550 fn try_from(task: CreateViewTask) -> Result<PbCreateViewTask> {
551 Ok(PbCreateViewTask {
552 create_view: Some(task.create_view),
553 view_info: serde_json::to_vec(&task.view_info).context(error::SerdeJsonSnafu)?,
554 })
555 }
556}
557
558impl Serialize for CreateViewTask {
559 fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
560 where
561 S: serde::Serializer,
562 {
563 let view_info = serde_json::to_vec(&self.view_info)
564 .map_err(|err| serde::ser::Error::custom(err.to_string()))?;
565
566 let pb = PbCreateViewTask {
567 create_view: Some(self.create_view.clone()),
568 view_info,
569 };
570 let buf = pb.encode_to_vec();
571 let encoded = general_purpose::STANDARD_NO_PAD.encode(buf);
572 serializer.serialize_str(&encoded)
573 }
574}
575
576impl<'de> Deserialize<'de> for CreateViewTask {
577 fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
578 where
579 D: serde::Deserializer<'de>,
580 {
581 let encoded = String::deserialize(deserializer)?;
582 let buf = general_purpose::STANDARD_NO_PAD
583 .decode(encoded)
584 .map_err(|err| serde::de::Error::custom(err.to_string()))?;
585 let expr: PbCreateViewTask = PbCreateViewTask::decode(&*buf)
586 .map_err(|err| serde::de::Error::custom(err.to_string()))?;
587
588 let expr = CreateViewTask::try_from(expr)
589 .map_err(|err| serde::de::Error::custom(err.to_string()))?;
590
591 Ok(expr)
592 }
593}
594
595#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
597pub struct DropViewTask {
598 pub catalog: String,
599 pub schema: String,
600 pub view: String,
601 pub view_id: TableId,
602 pub drop_if_exists: bool,
603}
604
605impl DropViewTask {
606 pub fn table_ref(&self) -> TableReference<'_> {
608 TableReference {
609 catalog: &self.catalog,
610 schema: &self.schema,
611 table: &self.view,
612 }
613 }
614}
615
616impl TryFrom<PbDropViewTask> for DropViewTask {
617 type Error = error::Error;
618
619 fn try_from(pb: PbDropViewTask) -> Result<Self> {
620 let expr = pb.drop_view.context(error::InvalidProtoMsgSnafu {
621 err_msg: "expected drop view",
622 })?;
623
624 Ok(DropViewTask {
625 catalog: expr.catalog_name,
626 schema: expr.schema_name,
627 view: expr.view_name,
628 view_id: expr
629 .view_id
630 .context(error::InvalidProtoMsgSnafu {
631 err_msg: "expected view_id",
632 })?
633 .id,
634 drop_if_exists: expr.drop_if_exists,
635 })
636 }
637}
638
639impl From<DropViewTask> for PbDropViewTask {
640 fn from(task: DropViewTask) -> Self {
641 PbDropViewTask {
642 drop_view: Some(DropViewExpr {
643 catalog_name: task.catalog,
644 schema_name: task.schema,
645 view_name: task.view,
646 view_id: Some(api::v1::TableId { id: task.view_id }),
647 drop_if_exists: task.drop_if_exists,
648 }),
649 }
650 }
651}
652
653#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
654pub struct DropTableTask {
655 pub catalog: String,
656 pub schema: String,
657 pub table: String,
658 pub table_id: TableId,
659 #[serde(default)]
660 pub drop_if_exists: bool,
661}
662
663#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
664pub struct UndropTableTask {
665 pub table_id: TableId,
666}
667
668#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
669pub struct PurgeDroppedTableTask {
670 pub table_id: TableId,
671}
672
673impl TryFrom<PbUndropTableTask> for UndropTableTask {
674 type Error = error::Error;
675
676 fn try_from(pb: PbUndropTableTask) -> Result<Self> {
677 Ok(Self {
678 table_id: pb
679 .table_id
680 .context(error::InvalidProtoMsgSnafu {
681 err_msg: "expected table_id",
682 })?
683 .id,
684 })
685 }
686}
687
688impl From<UndropTableTask> for PbUndropTableTask {
689 fn from(task: UndropTableTask) -> Self {
690 Self {
691 table_id: Some(api::v1::TableId { id: task.table_id }),
692 }
693 }
694}
695
696impl TryFrom<PbPurgeDroppedTableTask> for PurgeDroppedTableTask {
697 type Error = error::Error;
698
699 fn try_from(pb: PbPurgeDroppedTableTask) -> Result<Self> {
700 Ok(Self {
701 table_id: pb
702 .table_id
703 .context(error::InvalidProtoMsgSnafu {
704 err_msg: "expected table_id",
705 })?
706 .id,
707 })
708 }
709}
710
711impl From<PurgeDroppedTableTask> for PbPurgeDroppedTableTask {
712 fn from(task: PurgeDroppedTableTask) -> Self {
713 Self {
714 table_id: Some(api::v1::TableId { id: task.table_id }),
715 }
716 }
717}
718
719impl DropTableTask {
720 pub fn table_ref(&self) -> TableReference<'_> {
721 TableReference {
722 catalog: &self.catalog,
723 schema: &self.schema,
724 table: &self.table,
725 }
726 }
727
728 pub fn table_name(&self) -> TableName {
729 TableName {
730 catalog_name: self.catalog.clone(),
731 schema_name: self.schema.clone(),
732 table_name: self.table.clone(),
733 }
734 }
735}
736
737impl TryFrom<PbDropTableTask> for DropTableTask {
738 type Error = error::Error;
739
740 fn try_from(pb: PbDropTableTask) -> Result<Self> {
741 let drop_table = pb.drop_table.context(error::InvalidProtoMsgSnafu {
742 err_msg: "expected drop table",
743 })?;
744
745 Ok(Self {
746 catalog: drop_table.catalog_name,
747 schema: drop_table.schema_name,
748 table: drop_table.table_name,
749 table_id: drop_table
750 .table_id
751 .context(error::InvalidProtoMsgSnafu {
752 err_msg: "expected table_id",
753 })?
754 .id,
755 drop_if_exists: drop_table.drop_if_exists,
756 })
757 }
758}
759
760impl From<DropTableTask> for PbDropTableTask {
761 fn from(task: DropTableTask) -> Self {
762 PbDropTableTask {
763 drop_table: Some(DropTableExpr {
764 catalog_name: task.catalog,
765 schema_name: task.schema,
766 table_name: task.table,
767 table_id: Some(api::v1::TableId { id: task.table_id }),
768 drop_if_exists: task.drop_if_exists,
769 }),
770 }
771 }
772}
773
774#[derive(Debug, PartialEq, Clone)]
775pub struct CreateTableTask {
776 pub create_table: CreateTableExpr,
777 pub partitions: Vec<Partition>,
778 pub table_info: TableInfo,
779}
780
781impl TryFrom<PbCreateTableTask> for CreateTableTask {
782 type Error = error::Error;
783
784 fn try_from(pb: PbCreateTableTask) -> Result<Self> {
785 let table_info = serde_json::from_slice(&pb.table_info).context(error::SerdeJsonSnafu)?;
786
787 Ok(CreateTableTask::new(
788 pb.create_table.context(error::InvalidProtoMsgSnafu {
789 err_msg: "expected create table",
790 })?,
791 pb.partitions,
792 table_info,
793 ))
794 }
795}
796
797impl TryFrom<CreateTableTask> for PbCreateTableTask {
798 type Error = error::Error;
799
800 fn try_from(task: CreateTableTask) -> Result<Self> {
801 Ok(PbCreateTableTask {
802 table_info: serde_json::to_vec(&task.table_info).context(error::SerdeJsonSnafu)?,
803 create_table: Some(task.create_table),
804 partitions: task.partitions,
805 })
806 }
807}
808
809impl CreateTableTask {
810 pub fn new(
811 expr: CreateTableExpr,
812 partitions: Vec<Partition>,
813 table_info: TableInfo,
814 ) -> CreateTableTask {
815 CreateTableTask {
816 create_table: expr,
817 partitions,
818 table_info,
819 }
820 }
821
822 pub fn table_name(&self) -> TableName {
823 let table = &self.create_table;
824
825 TableName {
826 catalog_name: table.catalog_name.clone(),
827 schema_name: table.schema_name.clone(),
828 table_name: table.table_name.clone(),
829 }
830 }
831
832 pub fn table_ref(&self) -> TableReference<'_> {
833 let table = &self.create_table;
834
835 TableReference {
836 catalog: &table.catalog_name,
837 schema: &table.schema_name,
838 table: &table.table_name,
839 }
840 }
841
842 pub fn set_table_id(&mut self, table_id: TableId) {
844 self.table_info.ident.table_id = table_id;
845 }
846
847 pub fn sort_columns(&mut self) {
852 self.create_table
855 .column_defs
856 .sort_unstable_by(|a, b| a.name.cmp(&b.name));
857
858 self.table_info.sort_columns();
859 }
860}
861
862impl Serialize for CreateTableTask {
863 fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
864 where
865 S: serde::Serializer,
866 {
867 let table_info = serde_json::to_vec(&self.table_info)
868 .map_err(|err| serde::ser::Error::custom(err.to_string()))?;
869
870 let pb = PbCreateTableTask {
871 create_table: Some(self.create_table.clone()),
872 partitions: self.partitions.clone(),
873 table_info,
874 };
875 let buf = pb.encode_to_vec();
876 let encoded = general_purpose::STANDARD_NO_PAD.encode(buf);
877 serializer.serialize_str(&encoded)
878 }
879}
880
881impl<'de> Deserialize<'de> for CreateTableTask {
882 fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
883 where
884 D: serde::Deserializer<'de>,
885 {
886 let encoded = String::deserialize(deserializer)?;
887 let buf = general_purpose::STANDARD_NO_PAD
888 .decode(encoded)
889 .map_err(|err| serde::de::Error::custom(err.to_string()))?;
890 let expr: PbCreateTableTask = PbCreateTableTask::decode(&*buf)
891 .map_err(|err| serde::de::Error::custom(err.to_string()))?;
892
893 let expr = CreateTableTask::try_from(expr)
894 .map_err(|err| serde::de::Error::custom(err.to_string()))?;
895
896 Ok(expr)
897 }
898}
899
900#[derive(Debug, PartialEq, Clone)]
901pub struct AlterTableTask {
902 pub alter_table: AlterTableExpr,
904}
905
906impl AlterTableTask {
907 pub fn validate(&self) -> Result<()> {
908 self.alter_table
909 .kind
910 .as_ref()
911 .context(error::UnexpectedSnafu {
912 err_msg: "'kind' is absent",
913 })?;
914 Ok(())
915 }
916
917 pub fn table_ref(&self) -> TableReference<'_> {
918 TableReference {
919 catalog: &self.alter_table.catalog_name,
920 schema: &self.alter_table.schema_name,
921 table: &self.alter_table.table_name,
922 }
923 }
924
925 pub fn table_name(&self) -> TableName {
926 let table = &self.alter_table;
927
928 TableName {
929 catalog_name: table.catalog_name.clone(),
930 schema_name: table.schema_name.clone(),
931 table_name: table.table_name.clone(),
932 }
933 }
934}
935
936impl TryFrom<PbAlterTableTask> for AlterTableTask {
937 type Error = error::Error;
938
939 fn try_from(pb: PbAlterTableTask) -> Result<Self> {
940 let alter_table = pb.alter_table.context(error::InvalidProtoMsgSnafu {
941 err_msg: "expected alter_table",
942 })?;
943
944 Ok(AlterTableTask { alter_table })
945 }
946}
947
948impl TryFrom<AlterTableTask> for PbAlterTableTask {
949 type Error = error::Error;
950
951 fn try_from(task: AlterTableTask) -> Result<Self> {
952 Ok(PbAlterTableTask {
953 alter_table: Some(task.alter_table),
954 })
955 }
956}
957
958impl Serialize for AlterTableTask {
959 fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
960 where
961 S: serde::Serializer,
962 {
963 let pb = PbAlterTableTask {
964 alter_table: Some(self.alter_table.clone()),
965 };
966 let buf = pb.encode_to_vec();
967 let encoded = general_purpose::STANDARD_NO_PAD.encode(buf);
968 serializer.serialize_str(&encoded)
969 }
970}
971
972impl<'de> Deserialize<'de> for AlterTableTask {
973 fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
974 where
975 D: serde::Deserializer<'de>,
976 {
977 let encoded = String::deserialize(deserializer)?;
978 let buf = general_purpose::STANDARD_NO_PAD
979 .decode(encoded)
980 .map_err(|err| serde::de::Error::custom(err.to_string()))?;
981 let expr: PbAlterTableTask = PbAlterTableTask::decode(&*buf)
982 .map_err(|err| serde::de::Error::custom(err.to_string()))?;
983
984 let expr = AlterTableTask::try_from(expr)
985 .map_err(|err| serde::de::Error::custom(err.to_string()))?;
986
987 Ok(expr)
988 }
989}
990
991#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
992pub struct TruncateTableTask {
993 pub catalog: String,
994 pub schema: String,
995 pub table: String,
996 pub table_id: TableId,
997 pub time_ranges: Vec<(Timestamp, Timestamp)>,
998}
999
1000impl TruncateTableTask {
1001 pub fn table_ref(&self) -> TableReference<'_> {
1002 TableReference {
1003 catalog: &self.catalog,
1004 schema: &self.schema,
1005 table: &self.table,
1006 }
1007 }
1008
1009 pub fn table_name(&self) -> TableName {
1010 TableName {
1011 catalog_name: self.catalog.clone(),
1012 schema_name: self.schema.clone(),
1013 table_name: self.table.clone(),
1014 }
1015 }
1016}
1017
1018impl TryFrom<PbTruncateTableTask> for TruncateTableTask {
1019 type Error = error::Error;
1020
1021 fn try_from(pb: PbTruncateTableTask) -> Result<Self> {
1022 let truncate_table = pb.truncate_table.context(error::InvalidProtoMsgSnafu {
1023 err_msg: "expected truncate table",
1024 })?;
1025
1026 Ok(Self {
1027 catalog: truncate_table.catalog_name,
1028 schema: truncate_table.schema_name,
1029 table: truncate_table.table_name,
1030 table_id: truncate_table
1031 .table_id
1032 .context(error::InvalidProtoMsgSnafu {
1033 err_msg: "expected table_id",
1034 })?
1035 .id,
1036 time_ranges: truncate_table
1037 .time_ranges
1038 .map(from_pb_time_ranges)
1039 .transpose()
1040 .map_err(BoxedError::new)
1041 .context(ExternalSnafu)?
1042 .unwrap_or_default(),
1043 })
1044 }
1045}
1046
1047impl TryFrom<TruncateTableTask> for PbTruncateTableTask {
1048 type Error = error::Error;
1049
1050 fn try_from(task: TruncateTableTask) -> Result<Self> {
1051 Ok(PbTruncateTableTask {
1052 truncate_table: Some(TruncateTableExpr {
1053 catalog_name: task.catalog,
1054 schema_name: task.schema,
1055 table_name: task.table,
1056 table_id: Some(api::v1::TableId { id: task.table_id }),
1057 time_ranges: Some(
1058 to_pb_time_ranges(&task.time_ranges).context(ConvertTimeRangesSnafu)?,
1059 ),
1060 }),
1061 })
1062 }
1063}
1064
1065#[serde_as]
1066#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
1067pub struct CreateDatabaseTask {
1068 pub catalog: String,
1069 pub schema: String,
1070 pub create_if_not_exists: bool,
1071 #[serde_as(deserialize_as = "DefaultOnNull")]
1072 pub options: HashMap<String, String>,
1073 #[serde(default)]
1074 pub creator: Option<CreatorGrantIntent>,
1075}
1076
1077impl TryFrom<PbCreateDatabaseTask> for CreateDatabaseTask {
1078 type Error = error::Error;
1079
1080 fn try_from(pb: PbCreateDatabaseTask) -> Result<Self> {
1081 let CreateDatabaseExpr {
1082 catalog_name,
1083 schema_name,
1084 create_if_not_exists,
1085 options,
1086 } = pb.create_database.context(error::InvalidProtoMsgSnafu {
1087 err_msg: "expected create database",
1088 })?;
1089
1090 Ok(CreateDatabaseTask {
1091 catalog: catalog_name,
1092 schema: schema_name,
1093 create_if_not_exists,
1094 options,
1095 creator: None,
1096 })
1097 }
1098}
1099
1100impl TryFrom<CreateDatabaseTask> for PbCreateDatabaseTask {
1101 type Error = error::Error;
1102
1103 fn try_from(
1104 CreateDatabaseTask {
1105 catalog,
1106 schema,
1107 create_if_not_exists,
1108 options,
1109 creator: _,
1110 }: CreateDatabaseTask,
1111 ) -> Result<Self> {
1112 Ok(PbCreateDatabaseTask {
1113 create_database: Some(CreateDatabaseExpr {
1114 catalog_name: catalog,
1115 schema_name: schema,
1116 create_if_not_exists,
1117 options,
1118 }),
1119 })
1120 }
1121}
1122
1123#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
1124pub struct DropDatabaseTask {
1125 pub catalog: String,
1126 pub schema: String,
1127 pub drop_if_exists: bool,
1128}
1129
1130impl TryFrom<PbDropDatabaseTask> for DropDatabaseTask {
1131 type Error = error::Error;
1132
1133 fn try_from(pb: PbDropDatabaseTask) -> Result<Self> {
1134 let DropDatabaseExpr {
1135 catalog_name,
1136 schema_name,
1137 drop_if_exists,
1138 } = pb.drop_database.context(error::InvalidProtoMsgSnafu {
1139 err_msg: "expected drop database",
1140 })?;
1141
1142 Ok(DropDatabaseTask {
1143 catalog: catalog_name,
1144 schema: schema_name,
1145 drop_if_exists,
1146 })
1147 }
1148}
1149
1150impl TryFrom<DropDatabaseTask> for PbDropDatabaseTask {
1151 type Error = error::Error;
1152
1153 fn try_from(
1154 DropDatabaseTask {
1155 catalog,
1156 schema,
1157 drop_if_exists,
1158 }: DropDatabaseTask,
1159 ) -> Result<Self> {
1160 Ok(PbDropDatabaseTask {
1161 drop_database: Some(DropDatabaseExpr {
1162 catalog_name: catalog,
1163 schema_name: schema,
1164 drop_if_exists,
1165 }),
1166 })
1167 }
1168}
1169
1170#[derive(Debug, PartialEq, Clone)]
1171pub struct AlterDatabaseTask {
1172 pub alter_expr: AlterDatabaseExpr,
1173}
1174
1175impl TryFrom<AlterDatabaseTask> for PbAlterDatabaseTask {
1176 type Error = error::Error;
1177
1178 fn try_from(task: AlterDatabaseTask) -> Result<Self> {
1179 Ok(PbAlterDatabaseTask {
1180 task: Some(task.alter_expr),
1181 })
1182 }
1183}
1184
1185impl TryFrom<PbAlterDatabaseTask> for AlterDatabaseTask {
1186 type Error = error::Error;
1187
1188 fn try_from(pb: PbAlterDatabaseTask) -> Result<Self> {
1189 let alter_expr = pb.task.context(error::InvalidProtoMsgSnafu {
1190 err_msg: "expected alter database",
1191 })?;
1192
1193 Ok(AlterDatabaseTask { alter_expr })
1194 }
1195}
1196
1197impl TryFrom<PbAlterDatabaseKind> for AlterDatabaseKind {
1198 type Error = error::Error;
1199
1200 fn try_from(pb: PbAlterDatabaseKind) -> Result<Self> {
1201 match pb {
1202 PbAlterDatabaseKind::SetDatabaseOptions(options) => {
1203 Ok(AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions(
1204 options
1205 .set_database_options
1206 .into_iter()
1207 .map(SetDatabaseOption::try_from)
1208 .collect::<Result<Vec<_>>>()?,
1209 )))
1210 }
1211 PbAlterDatabaseKind::UnsetDatabaseOptions(options) => Ok(
1212 AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions(
1213 options
1214 .keys
1215 .iter()
1216 .map(|key| UnsetDatabaseOption::try_from(key.as_str()))
1217 .collect::<Result<Vec<_>>>()?,
1218 )),
1219 ),
1220 }
1221 }
1222}
1223
1224const TTL_KEY: &str = "ttl";
1225
1226impl TryFrom<PbOption> for SetDatabaseOption {
1227 type Error = error::Error;
1228
1229 fn try_from(PbOption { key, value }: PbOption) -> Result<Self> {
1230 let key_lower = key.to_ascii_lowercase();
1231 match key_lower.as_str() {
1232 TTL_KEY => {
1233 let ttl = DatabaseTimeToLive::from_humantime_or_str(&value)
1234 .map_err(|_| InvalidSetDatabaseOptionSnafu { key, value }.build())?;
1235
1236 Ok(SetDatabaseOption::Ttl(ttl))
1237 }
1238 _ => {
1239 if validate_database_option(&key_lower) {
1240 Ok(SetDatabaseOption::Other(key_lower, value))
1241 } else {
1242 InvalidSetDatabaseOptionSnafu { key, value }.fail()
1243 }
1244 }
1245 }
1246 }
1247}
1248
1249#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1250pub enum SetDatabaseOption {
1251 Ttl(DatabaseTimeToLive),
1252 Other(String, String),
1253}
1254
1255#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1256pub enum UnsetDatabaseOption {
1257 Ttl,
1258 Other(String),
1259}
1260
1261impl TryFrom<&str> for UnsetDatabaseOption {
1262 type Error = error::Error;
1263
1264 fn try_from(key: &str) -> Result<Self> {
1265 let key_lower = key.to_ascii_lowercase();
1266 match key_lower.as_str() {
1267 TTL_KEY => Ok(UnsetDatabaseOption::Ttl),
1268 _ => {
1269 if validate_database_option(&key_lower) {
1270 Ok(UnsetDatabaseOption::Other(key_lower))
1271 } else {
1272 InvalidUnsetDatabaseOptionSnafu { key }.fail()
1273 }
1274 }
1275 }
1276 }
1277}
1278
1279#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1280pub struct SetDatabaseOptions(pub Vec<SetDatabaseOption>);
1281
1282#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1283pub struct UnsetDatabaseOptions(pub Vec<UnsetDatabaseOption>);
1284
1285#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1286pub enum AlterDatabaseKind {
1287 SetDatabaseOptions(SetDatabaseOptions),
1288 UnsetDatabaseOptions(UnsetDatabaseOptions),
1289}
1290
1291impl AlterDatabaseTask {
1292 pub fn catalog(&self) -> &str {
1293 &self.alter_expr.catalog_name
1294 }
1295
1296 pub fn schema(&self) -> &str {
1297 &self.alter_expr.catalog_name
1298 }
1299}
1300
1301#[derive(Debug, Clone, Serialize, Deserialize)]
1303pub struct CreateFlowTask {
1304 pub catalog_name: String,
1305 pub flow_name: String,
1306 pub source_table_names: Vec<TableName>,
1307 pub sink_table_name: TableName,
1308 pub or_replace: bool,
1309 pub create_if_not_exists: bool,
1310 pub expire_after: Option<i64>,
1312 pub eval_interval_secs: Option<i64>,
1313 pub comment: String,
1314 pub sql: String,
1315 pub flow_options: HashMap<String, String>,
1316 #[serde(default)]
1320 pub eval_schedule: Option<crate::key::flow::flow_info::FlowScheduleConfig>,
1321}
1322
1323impl TryFrom<PbCreateFlowTask> for CreateFlowTask {
1324 type Error = error::Error;
1325
1326 fn try_from(pb: PbCreateFlowTask) -> Result<Self> {
1327 let CreateFlowExpr {
1328 catalog_name,
1329 flow_name,
1330 source_table_names,
1331 sink_table_name,
1332 or_replace,
1333 create_if_not_exists,
1334 expire_after,
1335 eval_interval,
1336 comment,
1337 sql,
1338 flow_options,
1339 } = pb.create_flow.context(error::InvalidProtoMsgSnafu {
1340 err_msg: "expected create_flow",
1341 })?;
1342
1343 Ok(CreateFlowTask {
1344 catalog_name,
1345 flow_name,
1346 source_table_names: source_table_names.into_iter().map(Into::into).collect(),
1347 sink_table_name: sink_table_name
1348 .context(error::InvalidProtoMsgSnafu {
1349 err_msg: "expected sink_table_name",
1350 })?
1351 .into(),
1352 or_replace,
1353 create_if_not_exists,
1354 expire_after: expire_after.map(|e| e.value),
1355 eval_interval_secs: eval_interval.map(|e| e.seconds),
1356 comment,
1357 sql,
1358 flow_options,
1359 eval_schedule: None,
1360 })
1361 }
1362}
1363
1364impl From<CreateFlowTask> for PbCreateFlowTask {
1365 fn from(
1366 CreateFlowTask {
1367 catalog_name,
1368 flow_name,
1369 source_table_names,
1370 sink_table_name,
1371 or_replace,
1372 create_if_not_exists,
1373 expire_after,
1374 eval_interval_secs: eval_interval,
1375 comment,
1376 sql,
1377 flow_options,
1378 ..
1379 }: CreateFlowTask,
1380 ) -> Self {
1381 PbCreateFlowTask {
1382 create_flow: Some(CreateFlowExpr {
1383 catalog_name,
1384 flow_name,
1385 source_table_names: source_table_names.into_iter().map(Into::into).collect(),
1386 sink_table_name: Some(sink_table_name.into()),
1387 or_replace,
1388 create_if_not_exists,
1389 expire_after: expire_after.map(|value| ExpireAfter { value }),
1390 eval_interval: eval_interval.map(|seconds| EvalInterval { seconds }),
1391 comment,
1392 sql,
1393 flow_options,
1394 }),
1395 }
1396 }
1397}
1398
1399#[derive(Debug, Clone, Serialize, Deserialize)]
1401pub struct DropFlowTask {
1402 pub catalog_name: String,
1403 pub flow_name: String,
1404 pub flow_id: FlowId,
1405 pub drop_if_exists: bool,
1406}
1407
1408impl TryFrom<PbDropFlowTask> for DropFlowTask {
1409 type Error = error::Error;
1410
1411 fn try_from(pb: PbDropFlowTask) -> Result<Self> {
1412 let DropFlowExpr {
1413 catalog_name,
1414 flow_name,
1415 flow_id,
1416 drop_if_exists,
1417 } = pb.drop_flow.context(error::InvalidProtoMsgSnafu {
1418 err_msg: "expected drop_flow",
1419 })?;
1420 let flow_id = flow_id
1421 .context(error::InvalidProtoMsgSnafu {
1422 err_msg: "expected flow_id",
1423 })?
1424 .id;
1425 Ok(DropFlowTask {
1426 catalog_name,
1427 flow_name,
1428 flow_id,
1429 drop_if_exists,
1430 })
1431 }
1432}
1433
1434impl From<DropFlowTask> for PbDropFlowTask {
1435 fn from(
1436 DropFlowTask {
1437 catalog_name,
1438 flow_name,
1439 flow_id,
1440 drop_if_exists,
1441 }: DropFlowTask,
1442 ) -> Self {
1443 PbDropFlowTask {
1444 drop_flow: Some(DropFlowExpr {
1445 catalog_name,
1446 flow_name,
1447 flow_id: Some(api::v1::FlowId { id: flow_id }),
1448 drop_if_exists,
1449 }),
1450 }
1451 }
1452}
1453
1454#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1456pub enum CommentObjectId {
1457 Table(TableId),
1458 Flow(FlowId),
1459}
1460
1461#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1463pub struct CommentOnTask {
1464 pub catalog_name: String,
1465 pub schema_name: String,
1466 pub object_type: CommentObjectType,
1467 pub object_name: String,
1468 pub column_name: Option<String>,
1470 pub object_id: Option<CommentObjectId>,
1472 pub comment: Option<String>,
1473}
1474
1475#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1476pub enum CommentObjectType {
1477 Table,
1478 Column,
1479 Flow,
1480}
1481
1482impl CommentOnTask {
1483 pub fn table_id(&self) -> Option<TableId> {
1484 match self.object_id.as_ref() {
1485 Some(CommentObjectId::Table(table_id)) => Some(*table_id),
1486 _ => None,
1487 }
1488 }
1489
1490 pub fn flow_id(&self) -> Option<FlowId> {
1491 match self.object_id.as_ref() {
1492 Some(CommentObjectId::Flow(flow_id)) => Some(*flow_id),
1493 _ => None,
1494 }
1495 }
1496
1497 fn set_table_id(&mut self, table_id: TableId) {
1498 self.object_id = Some(CommentObjectId::Table(table_id));
1499 }
1500
1501 fn set_flow_id(&mut self, flow_id: FlowId) {
1502 self.object_id = Some(CommentObjectId::Flow(flow_id));
1503 }
1504
1505 pub fn cache_idents(&self) -> Vec<CacheIdent> {
1507 match self.object_type {
1508 CommentObjectType::Table | CommentObjectType::Column => {
1509 let mut cache_idents = Vec::with_capacity(2);
1510 if let Some(CommentObjectId::Table(table_id)) = self.object_id.as_ref() {
1511 cache_idents.push(CacheIdent::TableId(*table_id));
1512 }
1513 cache_idents.push(CacheIdent::TableName(TableName {
1514 catalog_name: self.catalog_name.clone(),
1515 schema_name: self.schema_name.clone(),
1516 table_name: self.object_name.clone(),
1517 }));
1518 cache_idents
1519 }
1520 CommentObjectType::Flow => {
1521 let mut cache_idents = Vec::with_capacity(2);
1522 if let Some(CommentObjectId::Flow(flow_id)) = self.object_id.as_ref() {
1523 cache_idents.push(CacheIdent::FlowId(*flow_id));
1524 }
1525 cache_idents.push(CacheIdent::FlowName(FlowName {
1526 catalog_name: self.catalog_name.clone(),
1527 flow_name: self.object_name.clone(),
1528 }));
1529 cache_idents
1530 }
1531 }
1532 }
1533
1534 pub async fn enrich_object_id(
1537 &mut self,
1538 table_name_manager: &TableNameManager,
1539 flow_name_manager: &FlowNameManager,
1540 ) -> Result<()> {
1541 match self.object_type {
1542 CommentObjectType::Table | CommentObjectType::Column => {
1543 let table_id = table_name_manager
1544 .get(TableNameKey::new(
1545 &self.catalog_name,
1546 &self.schema_name,
1547 &self.object_name,
1548 ))
1549 .await?
1550 .with_context(|| error::TableNotFoundSnafu {
1551 table_name: format_full_table_name(
1552 &self.catalog_name,
1553 &self.schema_name,
1554 &self.object_name,
1555 ),
1556 })?
1557 .table_id();
1558
1559 self.set_table_id(table_id);
1560 }
1561 CommentObjectType::Flow => {
1562 let flow_id = flow_name_manager
1563 .get(&self.catalog_name, &self.object_name)
1564 .await?
1565 .with_context(|| error::FlowNotFoundSnafu {
1566 flow_name: format_full_flow_name(&self.catalog_name, &self.object_name),
1567 })?
1568 .flow_id();
1569
1570 self.set_flow_id(flow_id);
1571 }
1572 }
1573
1574 Ok(())
1575 }
1576}
1577
1578impl From<CommentObjectType> for PbCommentObjectType {
1580 fn from(object_type: CommentObjectType) -> Self {
1581 match object_type {
1582 CommentObjectType::Table => PbCommentObjectType::Table,
1583 CommentObjectType::Column => PbCommentObjectType::Column,
1584 CommentObjectType::Flow => PbCommentObjectType::Flow,
1585 }
1586 }
1587}
1588
1589impl TryFrom<i32> for CommentObjectType {
1590 type Error = error::Error;
1591
1592 fn try_from(value: i32) -> Result<Self> {
1593 match value {
1594 0 => Ok(CommentObjectType::Table),
1595 1 => Ok(CommentObjectType::Column),
1596 2 => Ok(CommentObjectType::Flow),
1597 _ => error::InvalidProtoMsgSnafu {
1598 err_msg: format!(
1599 "Invalid CommentObjectType value: {}. Valid values are: 0 (Table), 1 (Column), 2 (Flow)",
1600 value
1601 ),
1602 }
1603 .fail(),
1604 }
1605 }
1606}
1607
1608impl TryFrom<PbCommentOnTask> for CommentOnTask {
1610 type Error = error::Error;
1611
1612 fn try_from(pb: PbCommentOnTask) -> Result<Self> {
1613 let comment_on = pb.comment_on.context(error::InvalidProtoMsgSnafu {
1614 err_msg: "expected comment_on",
1615 })?;
1616
1617 Ok(CommentOnTask {
1618 catalog_name: comment_on.catalog_name,
1619 schema_name: comment_on.schema_name,
1620 object_type: comment_on.object_type.try_into()?,
1621 object_name: comment_on.object_name,
1622 column_name: if comment_on.column_name.is_empty() {
1623 None
1624 } else {
1625 Some(comment_on.column_name)
1626 },
1627 comment: if comment_on.comment.is_empty() {
1628 None
1629 } else {
1630 Some(comment_on.comment)
1631 },
1632 object_id: None,
1633 })
1634 }
1635}
1636
1637impl From<CommentOnTask> for PbCommentOnTask {
1638 fn from(task: CommentOnTask) -> Self {
1639 let pb_object_type: PbCommentObjectType = task.object_type.into();
1640 PbCommentOnTask {
1641 comment_on: Some(CommentOnExpr {
1642 catalog_name: task.catalog_name,
1643 schema_name: task.schema_name,
1644 object_type: pb_object_type as i32,
1645 object_name: task.object_name,
1646 column_name: task.column_name.unwrap_or_default(),
1647 comment: task.comment.unwrap_or_default(),
1648 }),
1649 }
1650 }
1651}
1652
1653#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
1654pub struct QueryContext {
1655 pub current_catalog: String,
1656 pub current_schema: String,
1657 pub timezone: String,
1658 pub extensions: HashMap<String, String>,
1659 pub channel: u8,
1660 #[serde(default)]
1662 pub snapshot_seqs: HashMap<u64, u64>,
1663 #[serde(default)]
1665 pub sst_min_sequences: HashMap<u64, u64>,
1666}
1667
1668impl QueryContext {
1669 pub fn current_catalog(&self) -> &str {
1671 &self.current_catalog
1672 }
1673
1674 pub fn current_schema(&self) -> &str {
1676 &self.current_schema
1677 }
1678
1679 pub fn timezone(&self) -> &str {
1681 &self.timezone
1682 }
1683
1684 pub fn extensions(&self) -> &HashMap<String, String> {
1686 &self.extensions
1687 }
1688
1689 pub fn channel(&self) -> u8 {
1691 self.channel
1692 }
1693
1694 pub fn snapshot_seqs(&self) -> &HashMap<u64, u64> {
1695 &self.snapshot_seqs
1696 }
1697
1698 pub fn sst_min_sequences(&self) -> &HashMap<u64, u64> {
1699 &self.sst_min_sequences
1700 }
1701}
1702
1703#[derive(Debug, Clone, Serialize, PartialEq)]
1707pub struct FlowQueryContext {
1708 pub catalog: String,
1710 pub schema: String,
1712 pub timezone: String,
1714 #[serde(default)]
1716 pub extensions: HashMap<String, String>,
1717 #[serde(default)]
1719 pub channel: u8,
1720 #[serde(default)]
1722 pub snapshot_seqs: HashMap<u64, u64>,
1723 #[serde(default)]
1725 pub sst_min_sequences: HashMap<u64, u64>,
1726}
1727
1728impl<'de> Deserialize<'de> for FlowQueryContext {
1729 fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
1730 where
1731 D: serde::Deserializer<'de>,
1732 {
1733 #[derive(Deserialize)]
1735 #[serde(untagged)]
1736 enum ContextCompat {
1737 Flow(FlowQueryContextHelper),
1738 Full(QueryContext),
1739 }
1740
1741 #[derive(Deserialize)]
1742 struct FlowQueryContextHelper {
1743 catalog: String,
1744 schema: String,
1745 timezone: String,
1746 #[serde(default)]
1747 extensions: HashMap<String, String>,
1748 #[serde(default)]
1749 channel: u8,
1750 #[serde(default)]
1751 snapshot_seqs: HashMap<u64, u64>,
1752 #[serde(default)]
1753 sst_min_sequences: HashMap<u64, u64>,
1754 }
1755
1756 match ContextCompat::deserialize(deserializer)? {
1757 ContextCompat::Flow(helper) => Ok(FlowQueryContext {
1758 catalog: helper.catalog,
1759 schema: helper.schema,
1760 timezone: helper.timezone,
1761 extensions: helper.extensions,
1762 channel: helper.channel,
1763 snapshot_seqs: helper.snapshot_seqs,
1764 sst_min_sequences: helper.sst_min_sequences,
1765 }),
1766 ContextCompat::Full(full_ctx) => Ok(full_ctx.into()),
1767 }
1768 }
1769}
1770
1771impl From<PbQueryContext> for QueryContext {
1772 fn from(pb_ctx: PbQueryContext) -> Self {
1773 let (snapshot_seqs, sst_min_sequences) = pb_ctx
1774 .snapshot_seqs
1775 .map(|seqs| (seqs.snapshot_seqs, seqs.sst_min_sequences))
1776 .unwrap_or_default();
1777
1778 Self {
1779 current_catalog: pb_ctx.current_catalog,
1780 current_schema: pb_ctx.current_schema,
1781 timezone: pb_ctx.timezone,
1782 extensions: pb_ctx.extensions,
1783 channel: pb_ctx.channel as u8,
1784 snapshot_seqs,
1785 sst_min_sequences,
1786 }
1787 }
1788}
1789
1790impl From<QueryContext> for PbQueryContext {
1791 fn from(
1792 QueryContext {
1793 current_catalog,
1794 current_schema,
1795 timezone,
1796 extensions,
1797 channel,
1798 snapshot_seqs,
1799 sst_min_sequences,
1800 }: QueryContext,
1801 ) -> Self {
1802 PbQueryContext {
1803 current_catalog,
1804 current_schema,
1805 timezone,
1806 extensions,
1807 channel: channel as u32,
1808 snapshot_seqs: (!snapshot_seqs.is_empty() || !sst_min_sequences.is_empty()).then_some(
1809 api::v1::SnapshotSequences {
1810 snapshot_seqs,
1811 sst_min_sequences,
1812 },
1813 ),
1814 explain: None,
1815 }
1816 }
1817}
1818
1819impl From<QueryContext> for FlowQueryContext {
1820 fn from(ctx: QueryContext) -> Self {
1821 Self {
1822 catalog: ctx.current_catalog,
1823 schema: ctx.current_schema,
1824 timezone: ctx.timezone,
1825 extensions: ctx.extensions,
1826 channel: ctx.channel,
1827 snapshot_seqs: ctx.snapshot_seqs,
1828 sst_min_sequences: ctx.sst_min_sequences,
1829 }
1830 }
1831}
1832
1833impl From<FlowQueryContext> for QueryContext {
1834 fn from(flow_ctx: FlowQueryContext) -> Self {
1835 Self {
1836 current_catalog: flow_ctx.catalog,
1837 current_schema: flow_ctx.schema,
1838 timezone: flow_ctx.timezone,
1839 extensions: flow_ctx.extensions,
1840 channel: flow_ctx.channel,
1841 snapshot_seqs: flow_ctx.snapshot_seqs,
1842 sst_min_sequences: flow_ctx.sst_min_sequences,
1843 }
1844 }
1845}
1846
1847impl From<FlowQueryContext> for PbQueryContext {
1848 fn from(flow_ctx: FlowQueryContext) -> Self {
1849 let query_ctx: QueryContext = flow_ctx.into();
1850 query_ctx.into()
1851 }
1852}
1853
1854#[cfg(test)]
1855mod tests {
1856 use std::sync::Arc;
1857
1858 use api::v1::{AlterTableExpr, ColumnDef, CreateTableExpr, SemanticType};
1859 use datatypes::schema::{ColumnSchema, Schema, SchemaBuilder};
1860 use store_api::metric_engine_consts::METRIC_ENGINE_NAME;
1861 use store_api::storage::ConcreteDataType;
1862 use table::metadata::{TableInfo, TableMeta, TableType};
1863 use table::test_util::table_info::test_table_info;
1864
1865 use super::{AlterTableTask, CreateTableTask, *};
1866
1867 #[test]
1868 fn test_ddl_timeout_secs() {
1869 assert_eq!(ddl_timeout_secs(Duration::ZERO), 0);
1870 assert_eq!(ddl_timeout_secs(Duration::from_nanos(1)), 1);
1871 assert_eq!(ddl_timeout_secs(Duration::from_secs(1)), 1);
1872 assert_eq!(ddl_timeout_secs(Duration::from_millis(1500)), 2);
1873 assert_eq!(
1874 ddl_timeout_secs(Duration::from_secs(u32::MAX as u64 + 1)),
1875 u32::MAX
1876 );
1877 }
1878
1879 #[test]
1880 fn test_basic_ser_de_create_table_task() {
1881 let schema = SchemaBuilder::default().build().unwrap();
1882 let table_info = test_table_info(1025, "foo", "bar", "baz", Arc::new(schema));
1883 let task = CreateTableTask::new(CreateTableExpr::default(), Vec::new(), table_info);
1884
1885 let output = serde_json::to_vec(&task).unwrap();
1886
1887 let de = serde_json::from_slice(&output).unwrap();
1888 assert_eq!(task, de);
1889 }
1890
1891 #[test]
1892 fn test_basic_ser_de_alter_table_task() {
1893 let task = AlterTableTask {
1894 alter_table: AlterTableExpr::default(),
1895 };
1896
1897 let output = serde_json::to_vec(&task).unwrap();
1898
1899 let de = serde_json::from_slice(&output).unwrap();
1900 assert_eq!(task, de);
1901 }
1902
1903 #[test]
1904 fn test_undrop_table_task_pb_roundtrip() {
1905 let expected = UndropTableTask { table_id: 1024 };
1906 let request = SubmitDdlTaskRequest::new(
1907 QueryContext::default(),
1908 DdlTask::UndropTable(expected.clone()),
1909 );
1910
1911 let pb = PbDdlTaskRequest::try_from(request).unwrap();
1912 let pb_task = pb.task.unwrap();
1913 let de = DdlTask::try_from(pb_task).unwrap();
1914
1915 assert!(matches!(de, DdlTask::UndropTable(task) if task == expected));
1916 }
1917
1918 #[test]
1919 fn test_purge_dropped_table_task_pb_roundtrip() {
1920 let expected = PurgeDroppedTableTask { table_id: 1024 };
1921 let request = SubmitDdlTaskRequest::new(
1922 QueryContext::default(),
1923 DdlTask::PurgeDroppedTable(expected.clone()),
1924 );
1925
1926 let pb = PbDdlTaskRequest::try_from(request).unwrap();
1927 let pb_task = pb.task.unwrap();
1928 let de = DdlTask::try_from(pb_task).unwrap();
1929
1930 assert!(matches!(de, DdlTask::PurgeDroppedTable(task) if task == expected));
1931 }
1932
1933 #[test]
1934 fn test_create_database_creator_transport_sanitizes_extension() {
1935 let request = |creator| {
1936 let mut query_context = QueryContext::default();
1937 query_context.extensions.insert(
1938 CREATE_DATABASE_CREATOR_EXTENSION_KEY.to_string(),
1939 "spoofed".to_string(),
1940 );
1941 SubmitDdlTaskRequest::new(
1942 query_context,
1943 DdlTask::new_create_database(
1944 "greptime".to_string(),
1945 "test".to_string(),
1946 false,
1947 HashMap::new(),
1948 creator,
1949 ),
1950 )
1951 };
1952 let creator = CreatorGrantIntent {
1953 username: "alice".to_string(),
1954 created_at_ns: 42,
1955 };
1956
1957 let mut pb = PbDdlTaskRequest::try_from(request(Some(creator.clone()))).unwrap();
1958 let encoded = pb
1959 .query_context
1960 .as_ref()
1961 .unwrap()
1962 .extensions
1963 .get(CREATE_DATABASE_CREATOR_EXTENSION_KEY)
1964 .unwrap();
1965 assert_eq!(creator, serde_json::from_str(encoded).unwrap());
1966 assert!(matches!(
1967 DdlTask::try_from(pb.task.take().unwrap()).unwrap(),
1968 DdlTask::CreateDatabase(CreateDatabaseTask { creator: None, .. })
1969 ));
1970
1971 let pb = PbDdlTaskRequest::try_from(request(None)).unwrap();
1972 assert!(
1973 !pb.query_context
1974 .unwrap()
1975 .extensions
1976 .contains_key(CREATE_DATABASE_CREATOR_EXTENSION_KEY)
1977 );
1978 }
1979
1980 #[test]
1981 fn test_undrop_table_task_json_roundtrip() {
1982 let task = UndropTableTask { table_id: 1024 };
1983
1984 let output = serde_json::to_vec(&task).unwrap();
1985
1986 let de = serde_json::from_slice(&output).unwrap();
1987 assert_eq!(task, de);
1988 }
1989
1990 #[test]
1991 fn test_purge_dropped_table_task_json_roundtrip() {
1992 let task = PurgeDroppedTableTask { table_id: 1024 };
1993
1994 let output = serde_json::to_vec(&task).unwrap();
1995
1996 let de = serde_json::from_slice(&output).unwrap();
1997 assert_eq!(task, de);
1998 }
1999
2000 #[test]
2001 fn test_sort_columns() {
2002 let schema = Arc::new(Schema::new(vec![
2004 ColumnSchema::new(
2005 "column3".to_string(),
2006 ConcreteDataType::string_datatype(),
2007 true,
2008 ),
2009 ColumnSchema::new(
2010 "column1".to_string(),
2011 ConcreteDataType::timestamp_millisecond_datatype(),
2012 false,
2013 )
2014 .with_time_index(true),
2015 ColumnSchema::new(
2016 "column2".to_string(),
2017 ConcreteDataType::float64_datatype(),
2018 true,
2019 ),
2020 ]));
2021
2022 let meta = TableMeta {
2024 schema,
2025 primary_key_indices: vec![0],
2026 value_indices: vec![2],
2027 engine: METRIC_ENGINE_NAME.to_string(),
2028 next_column_id: 0,
2029 options: Default::default(),
2030 created_on: Default::default(),
2031 updated_on: Default::default(),
2032 partition_key_indices: Default::default(),
2033 column_ids: Default::default(),
2034 };
2035
2036 let raw_table_info = TableInfo {
2038 ident: Default::default(),
2039 meta,
2040 name: Default::default(),
2041 desc: Default::default(),
2042 catalog_name: Default::default(),
2043 schema_name: Default::default(),
2044 table_type: TableType::Base,
2045 };
2046
2047 let create_table_expr = CreateTableExpr {
2049 column_defs: vec![
2050 ColumnDef {
2051 name: "column3".to_string(),
2052 semantic_type: SemanticType::Tag as i32,
2053 ..Default::default()
2054 },
2055 ColumnDef {
2056 name: "column1".to_string(),
2057 semantic_type: SemanticType::Timestamp as i32,
2058 ..Default::default()
2059 },
2060 ColumnDef {
2061 name: "column2".to_string(),
2062 semantic_type: SemanticType::Field as i32,
2063 ..Default::default()
2064 },
2065 ],
2066 primary_keys: vec!["column3".to_string()],
2067 ..Default::default()
2068 };
2069
2070 let mut create_table_task =
2071 CreateTableTask::new(create_table_expr, Vec::new(), raw_table_info);
2072
2073 create_table_task.sort_columns();
2075
2076 assert_eq!(
2078 create_table_task.create_table.column_defs[0].name,
2079 "column1".to_string()
2080 );
2081 assert_eq!(
2082 create_table_task.create_table.column_defs[1].name,
2083 "column2".to_string()
2084 );
2085 assert_eq!(
2086 create_table_task.create_table.column_defs[2].name,
2087 "column3".to_string()
2088 );
2089
2090 assert_eq!(
2092 create_table_task.table_info.meta.schema.timestamp_index(),
2093 Some(0)
2094 );
2095 assert_eq!(
2096 create_table_task.table_info.meta.primary_key_indices,
2097 vec![2]
2098 );
2099 assert_eq!(create_table_task.table_info.meta.value_indices, vec![0, 1]);
2100 }
2101
2102 #[test]
2103 fn test_flow_query_context_conversion_from_query_context() {
2104 use std::collections::HashMap;
2105 let mut extensions = HashMap::new();
2106 extensions.insert("key1".to_string(), "value1".to_string());
2107 extensions.insert("key2".to_string(), "value2".to_string());
2108
2109 let query_ctx = QueryContext {
2110 current_catalog: "test_catalog".to_string(),
2111 current_schema: "test_schema".to_string(),
2112 timezone: "UTC".to_string(),
2113 extensions,
2114 channel: 5,
2115 snapshot_seqs: HashMap::from([(10, 100)]),
2116 sst_min_sequences: HashMap::from([(10, 90)]),
2117 };
2118
2119 let flow_ctx: FlowQueryContext = query_ctx.into();
2120
2121 assert_eq!(flow_ctx.catalog, "test_catalog");
2122 assert_eq!(flow_ctx.schema, "test_schema");
2123 assert_eq!(flow_ctx.timezone, "UTC");
2124 assert_eq!(flow_ctx.channel, 5);
2125 assert_eq!(flow_ctx.snapshot_seqs, HashMap::from([(10, 100)]));
2126 assert_eq!(flow_ctx.sst_min_sequences, HashMap::from([(10, 90)]));
2127 }
2128
2129 #[test]
2130 fn test_flow_query_context_conversion_to_query_context() {
2131 let flow_ctx = FlowQueryContext {
2132 catalog: "prod_catalog".to_string(),
2133 schema: "public".to_string(),
2134 timezone: "America/New_York".to_string(),
2135 extensions: HashMap::from([("k".to_string(), "v".to_string())]),
2136 channel: 7,
2137 snapshot_seqs: HashMap::from([(11, 111)]),
2138 sst_min_sequences: HashMap::from([(11, 101)]),
2139 };
2140
2141 let query_ctx: QueryContext = flow_ctx.clone().into();
2142
2143 assert_eq!(query_ctx.current_catalog, "prod_catalog");
2144 assert_eq!(query_ctx.current_schema, "public");
2145 assert_eq!(query_ctx.timezone, "America/New_York");
2146 assert_eq!(
2147 query_ctx.extensions,
2148 HashMap::from([("k".to_string(), "v".to_string())])
2149 );
2150 assert_eq!(query_ctx.channel, 7);
2151 assert_eq!(query_ctx.snapshot_seqs, HashMap::from([(11, 111)]));
2152 assert_eq!(query_ctx.sst_min_sequences, HashMap::from([(11, 101)]));
2153
2154 let flow_ctx_roundtrip: FlowQueryContext = query_ctx.into();
2156 assert_eq!(flow_ctx, flow_ctx_roundtrip);
2157 }
2158
2159 #[test]
2160 fn test_flow_query_context_serialization() {
2161 let flow_ctx = FlowQueryContext {
2162 catalog: "test_catalog".to_string(),
2163 schema: "test_schema".to_string(),
2164 timezone: "UTC".to_string(),
2165 extensions: HashMap::new(),
2166 channel: 0,
2167 snapshot_seqs: HashMap::new(),
2168 sst_min_sequences: HashMap::new(),
2169 };
2170
2171 let serialized = serde_json::to_string(&flow_ctx).unwrap();
2172 let deserialized: FlowQueryContext = serde_json::from_str(&serialized).unwrap();
2173
2174 assert_eq!(flow_ctx, deserialized);
2175
2176 let json_value: serde_json::Value = serde_json::from_str(&serialized).unwrap();
2178 assert_eq!(json_value["catalog"], "test_catalog");
2179 assert_eq!(json_value["schema"], "test_schema");
2180 assert_eq!(json_value["timezone"], "UTC");
2181 }
2182
2183 #[test]
2184 fn test_flow_query_context_conversion_to_pb() {
2185 let flow_ctx = FlowQueryContext {
2186 catalog: "pb_catalog".to_string(),
2187 schema: "pb_schema".to_string(),
2188 timezone: "Asia/Tokyo".to_string(),
2189 extensions: HashMap::from([("x".to_string(), "y".to_string())]),
2190 channel: 6,
2191 snapshot_seqs: HashMap::from([(3, 30)]),
2192 sst_min_sequences: HashMap::from([(3, 21)]),
2193 };
2194
2195 let pb_ctx: PbQueryContext = flow_ctx.into();
2196
2197 assert_eq!(pb_ctx.current_catalog, "pb_catalog");
2198 assert_eq!(pb_ctx.current_schema, "pb_schema");
2199 assert_eq!(pb_ctx.timezone, "Asia/Tokyo");
2200 assert_eq!(
2201 pb_ctx.extensions,
2202 HashMap::from([("x".to_string(), "y".to_string())])
2203 );
2204 assert_eq!(pb_ctx.channel, 6);
2205 assert_eq!(
2206 pb_ctx.snapshot_seqs,
2207 Some(api::v1::SnapshotSequences {
2208 snapshot_seqs: HashMap::from([(3, 30)]),
2209 sst_min_sequences: HashMap::from([(3, 21)]),
2210 })
2211 );
2212 assert!(pb_ctx.explain.is_none());
2213 }
2214
2215 #[test]
2216 fn test_pb_query_context_roundtrip_with_snapshot_sequences() {
2217 let pb = PbQueryContext {
2218 current_catalog: "c1".to_string(),
2219 current_schema: "s1".to_string(),
2220 timezone: "UTC".to_string(),
2221 extensions: HashMap::from([("flow.return_region_seq".to_string(), "true".to_string())]),
2222 channel: 3,
2223 snapshot_seqs: Some(api::v1::SnapshotSequences {
2224 snapshot_seqs: HashMap::from([(1, 100)]),
2225 sst_min_sequences: HashMap::from([(1, 90)]),
2226 }),
2227 explain: None,
2228 };
2229
2230 let query_ctx: QueryContext = pb.clone().into();
2231 let pb_roundtrip: PbQueryContext = query_ctx.into();
2232
2233 assert_eq!(pb_roundtrip.current_catalog, pb.current_catalog);
2234 assert_eq!(pb_roundtrip.current_schema, pb.current_schema);
2235 assert_eq!(pb_roundtrip.timezone, pb.timezone);
2236 assert_eq!(pb_roundtrip.extensions, pb.extensions);
2237 assert_eq!(pb_roundtrip.channel, pb.channel);
2238 assert_eq!(pb_roundtrip.snapshot_seqs, pb.snapshot_seqs);
2239 }
2240}