1use std::any::Any;
16use std::collections::BTreeSet;
17
18use api::v1::alter_table_expr::Kind as AlterTableKind;
19use api::v1::value::ValueData;
20use api::v1::{ColumnSchema, Row};
21use common_event_recorder::Event;
22use common_event_recorder::error::{Result, SerializeEventSnafu};
23use common_event_recorder::event_table::{
24 CATALOG_NAME_COLUMN, PHYSICAL_TABLE_ID_COLUMN, SCHEMA_NAME_COLUMN, TABLE_ID_COLUMN,
25 TABLE_NAME_COLUMN, column_schemas, nullable_string, nullable_value,
26};
27use serde::Serialize;
28use serde_json::Value as JsonValue;
29use snafu::ResultExt;
30use store_api::storage::TableId;
31
32pub(crate) const TABLE_DDL_PAYLOAD_VERSION: u8 = 1;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub(crate) enum TableDdlEventType {
38 CreateTable,
39 CreateLogicalTables,
40 AlterTable,
41 AlterLogicalTables,
42 DropTable,
43 #[cfg(feature = "enterprise")]
44 UndropTable,
45 #[cfg(feature = "enterprise")]
46 PurgeDroppedTable,
47 TruncateTable,
48}
49
50impl TableDdlEventType {
51 pub(crate) const fn as_str(self) -> &'static str {
53 match self {
54 Self::CreateTable => "create_table",
55 Self::CreateLogicalTables => "create_logical_tables",
56 Self::AlterTable => "alter_table",
57 Self::AlterLogicalTables => "alter_logical_tables",
58 Self::DropTable => "drop_table",
59 #[cfg(feature = "enterprise")]
60 Self::UndropTable => "undrop_table",
61 #[cfg(feature = "enterprise")]
62 Self::PurgeDroppedTable => "purge_dropped_table",
63 Self::TruncateTable => "truncate_table",
64 }
65 }
66
67 const fn has_physical_table_id(self) -> bool {
68 matches!(self, Self::CreateLogicalTables | Self::AlterLogicalTables)
69 }
70}
71
72#[derive(Debug, Clone, Default, PartialEq, Eq)]
74pub(crate) struct TableDdlLocator {
75 pub(crate) catalog_name: Option<String>,
77 pub(crate) schema_name: Option<String>,
79 pub(crate) table_name: Option<String>,
81 pub(crate) table_id: Option<TableId>,
83 pub(crate) physical_table_id: Option<TableId>,
85}
86
87impl TableDdlLocator {
88 pub(crate) fn new(
90 catalog_name: impl Into<String>,
91 schema_name: impl Into<String>,
92 table_name: impl Into<String>,
93 ) -> Self {
94 Self {
95 catalog_name: Some(catalog_name.into()),
96 schema_name: Some(schema_name.into()),
97 table_name: Some(table_name.into()),
98 ..Default::default()
99 }
100 }
101
102 #[cfg(feature = "enterprise")]
104 pub(crate) fn from_table_id(table_id: TableId) -> Self {
105 Self {
106 table_id: Some(table_id),
107 ..Default::default()
108 }
109 }
110
111 pub(crate) fn with_table_id(mut self, table_id: TableId) -> Self {
113 self.table_id = Some(table_id);
114 self
115 }
116
117 pub(crate) fn with_physical_table_id(mut self, physical_table_id: TableId) -> Self {
119 self.physical_table_id = Some(physical_table_id);
120 self
121 }
122}
123
124#[derive(Debug, Serialize)]
125#[serde(untagged)]
126enum TableDdlPayload {
127 CreateTable(CreateTablePayload),
128 CreateLogicalTables(CreateLogicalTablesPayload),
129 AlterTable(AlterTablePayload),
130 AlterLogicalTables(AlterLogicalTablesPayload),
131 DropTable(DropTablePayload),
132 #[cfg(feature = "enterprise")]
133 UndropTable(UndropTablePayload),
134 #[cfg(feature = "enterprise")]
135 PurgeDroppedTable(PurgeDroppedTablePayload),
136 TruncateTable(TruncateTablePayload),
137}
138
139#[derive(Debug, Serialize)]
140struct CreateTablePayload {
141 version: u8,
142 create_if_not_exists: bool,
143 engine: String,
144}
145
146#[derive(Debug, Serialize)]
147struct CreateLogicalTablesPayload {
148 version: u8,
149 table_count: usize,
150}
151
152#[derive(Debug, Serialize)]
153struct AlterTablePayload {
154 version: u8,
155 kind: Option<&'static str>,
156}
157
158#[derive(Debug, Serialize)]
159struct AlterLogicalTablesPayload {
160 version: u8,
161 table_count: usize,
162 kinds: Vec<&'static str>,
163}
164
165#[derive(Debug, Serialize)]
166struct DropTablePayload {
167 version: u8,
168 drop_if_exists: bool,
169}
170
171#[cfg(feature = "enterprise")]
172#[derive(Debug, Serialize)]
173struct UndropTablePayload {
174 version: u8,
175}
176
177#[cfg(feature = "enterprise")]
178#[derive(Debug, Serialize)]
179struct PurgeDroppedTablePayload {
180 version: u8,
181}
182
183#[derive(Debug, Serialize)]
184struct TruncateTablePayload {
185 version: u8,
186 time_range_count: usize,
187}
188
189pub(crate) fn alter_table_kind_name(kind: &AlterTableKind) -> Option<&'static str> {
191 match kind {
192 AlterTableKind::AddColumns(_) => Some("add_columns"),
193 AlterTableKind::DropColumns(_) => Some("drop_columns"),
194 AlterTableKind::RenameTable(_) => Some("rename_table"),
195 AlterTableKind::ModifyColumnTypes(_) => Some("modify_column_types"),
196 AlterTableKind::SetTableOptions(_) => Some("set_table_options"),
197 AlterTableKind::UnsetTableOptions(_) => Some("unset_table_options"),
198 AlterTableKind::SetIndex(_) => Some("set_index"),
199 AlterTableKind::UnsetIndex(_) => Some("unset_index"),
200 AlterTableKind::DropDefaults(_) => Some("drop_defaults"),
201 AlterTableKind::SetIndexes(_) => Some("set_indexes"),
202 AlterTableKind::UnsetIndexes(_) => Some("unset_indexes"),
203 AlterTableKind::SetDefaults(_) => Some("set_defaults"),
204 AlterTableKind::Repartition(_) => None,
206 }
207}
208
209#[derive(Debug)]
211pub(crate) struct TableDdlEvent {
212 event_type: TableDdlEventType,
213 locators: Vec<TableDdlLocator>,
214 payload: Option<TableDdlPayload>,
215}
216
217impl TableDdlEvent {
218 pub(crate) fn create_table_submitted(
220 locator: TableDdlLocator,
221 create_if_not_exists: bool,
222 engine: &str,
223 ) -> Self {
224 Self::submitted(
225 TableDdlEventType::CreateTable,
226 [locator],
227 TableDdlPayload::CreateTable(CreateTablePayload {
228 version: TABLE_DDL_PAYLOAD_VERSION,
229 create_if_not_exists,
230 engine: engine.to_string(),
231 }),
232 )
233 }
234
235 pub(crate) fn create_logical_tables_submitted(
237 locators: impl IntoIterator<Item = TableDdlLocator>,
238 table_count: usize,
239 ) -> Self {
240 Self::submitted(
241 TableDdlEventType::CreateLogicalTables,
242 locators,
243 TableDdlPayload::CreateLogicalTables(CreateLogicalTablesPayload {
244 version: TABLE_DDL_PAYLOAD_VERSION,
245 table_count,
246 }),
247 )
248 }
249
250 pub(crate) fn alter_table_submitted(
252 locator: TableDdlLocator,
253 kind: Option<&'static str>,
254 ) -> Self {
255 Self::submitted(
256 TableDdlEventType::AlterTable,
257 [locator],
258 TableDdlPayload::AlterTable(AlterTablePayload {
259 version: TABLE_DDL_PAYLOAD_VERSION,
260 kind,
261 }),
262 )
263 }
264
265 pub(crate) fn alter_logical_tables_submitted(
267 locators: impl IntoIterator<Item = TableDdlLocator>,
268 table_count: usize,
269 kinds: impl IntoIterator<Item = &'static str>,
270 ) -> Self {
271 let kinds = kinds
272 .into_iter()
273 .collect::<BTreeSet<_>>()
274 .into_iter()
275 .collect();
276 Self::submitted(
277 TableDdlEventType::AlterLogicalTables,
278 locators,
279 TableDdlPayload::AlterLogicalTables(AlterLogicalTablesPayload {
280 version: TABLE_DDL_PAYLOAD_VERSION,
281 table_count,
282 kinds,
283 }),
284 )
285 }
286
287 pub(crate) fn drop_table_submitted(locator: TableDdlLocator, drop_if_exists: bool) -> Self {
289 Self::submitted(
290 TableDdlEventType::DropTable,
291 [locator],
292 TableDdlPayload::DropTable(DropTablePayload {
293 version: TABLE_DDL_PAYLOAD_VERSION,
294 drop_if_exists,
295 }),
296 )
297 }
298
299 #[cfg(feature = "enterprise")]
301 pub(crate) fn undrop_table_submitted(locator: TableDdlLocator) -> Self {
302 Self::submitted(
303 TableDdlEventType::UndropTable,
304 [locator],
305 TableDdlPayload::UndropTable(UndropTablePayload {
306 version: TABLE_DDL_PAYLOAD_VERSION,
307 }),
308 )
309 }
310
311 #[cfg(feature = "enterprise")]
313 pub(crate) fn purge_dropped_table_submitted(locator: TableDdlLocator) -> Self {
314 Self::submitted(
315 TableDdlEventType::PurgeDroppedTable,
316 [locator],
317 TableDdlPayload::PurgeDroppedTable(PurgeDroppedTablePayload {
318 version: TABLE_DDL_PAYLOAD_VERSION,
319 }),
320 )
321 }
322
323 pub(crate) fn truncate_table_submitted(
325 locator: TableDdlLocator,
326 time_range_count: usize,
327 ) -> Self {
328 Self::submitted(
329 TableDdlEventType::TruncateTable,
330 [locator],
331 TableDdlPayload::TruncateTable(TruncateTablePayload {
332 version: TABLE_DDL_PAYLOAD_VERSION,
333 time_range_count,
334 }),
335 )
336 }
337
338 pub(crate) fn lifecycle(
340 event_type: TableDdlEventType,
341 locators: impl IntoIterator<Item = TableDdlLocator>,
342 ) -> Self {
343 Self {
344 event_type,
345 locators: locators.into_iter().collect(),
346 payload: None,
347 }
348 }
349
350 pub(crate) fn create_table_succeeded(locator: TableDdlLocator, table_id: TableId) -> Self {
352 Self::lifecycle(
353 TableDdlEventType::CreateTable,
354 [locator.with_table_id(table_id)],
355 )
356 }
357
358 pub(crate) fn create_logical_tables_succeeded(
360 locators: impl IntoIterator<Item = TableDdlLocator>,
361 ) -> Self {
362 Self::lifecycle(TableDdlEventType::CreateLogicalTables, locators)
363 }
364
365 fn submitted(
366 event_type: TableDdlEventType,
367 locators: impl IntoIterator<Item = TableDdlLocator>,
368 payload: TableDdlPayload,
369 ) -> Self {
370 Self {
371 event_type,
372 locators: locators.into_iter().collect(),
373 payload: Some(payload),
374 }
375 }
376
377 fn schema() -> Vec<ColumnSchema> {
378 column_schemas([
379 &CATALOG_NAME_COLUMN,
380 &SCHEMA_NAME_COLUMN,
381 &TABLE_NAME_COLUMN,
382 &TABLE_ID_COLUMN,
383 ])
384 }
385
386 fn locator_row(&self, locator: &TableDdlLocator) -> Row {
387 let mut values = vec![
388 nullable_string(locator.catalog_name.as_deref()),
389 nullable_string(locator.schema_name.as_deref()),
390 nullable_string(locator.table_name.as_deref()),
391 nullable_table_id(locator.table_id),
392 ];
393 if self.event_type.has_physical_table_id() {
394 values.push(nullable_table_id(locator.physical_table_id));
395 }
396 Row { values }
397 }
398}
399
400impl Event for TableDdlEvent {
401 fn event_type(&self) -> &str {
402 self.event_type.as_str()
403 }
404
405 fn json_payload(&self) -> Result<JsonValue> {
406 match &self.payload {
407 Some(payload) => serde_json::to_value(payload).context(SerializeEventSnafu),
408 None => Ok(JsonValue::Null),
409 }
410 }
411
412 fn extra_schema(&self) -> Vec<ColumnSchema> {
413 let mut schema = Self::schema();
414 if self.event_type.has_physical_table_id() {
415 schema.push(PHYSICAL_TABLE_ID_COLUMN.column_schema());
416 }
417 schema
418 }
419
420 fn extra_rows(&self) -> Result<Vec<Row>> {
421 Ok(self
422 .locators
423 .iter()
424 .map(|locator| self.locator_row(locator))
425 .collect())
426 }
427
428 fn as_any(&self) -> &dyn Any {
429 self
430 }
431}
432
433fn nullable_table_id(value: Option<TableId>) -> api::v1::Value {
434 nullable_value(value.map(ValueData::U32Value))
435}