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