Skip to main content

common_meta/ddl/event/
table.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
32/// Current version of table DDL event payloads.
33pub(crate) const TABLE_DDL_PAYLOAD_VERSION: u8 = 1;
34
35/// A table DDL event type and its fixed domain schema.
36#[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    /// Returns the stable event type stored in the events table.
52    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/// Nullable table locator columns stored alongside a table DDL event.
73#[derive(Debug, Clone, Default, PartialEq, Eq)]
74pub(crate) struct TableDdlLocator {
75    /// Catalog containing the table.
76    pub(crate) catalog_name: Option<String>,
77    /// Schema containing the table.
78    pub(crate) schema_name: Option<String>,
79    /// Table name.
80    pub(crate) table_name: Option<String>,
81    /// Table ID when known at this lifecycle point.
82    pub(crate) table_id: Option<TableId>,
83    /// Physical table ID for a logical table event.
84    pub(crate) physical_table_id: Option<TableId>,
85}
86
87impl TableDdlLocator {
88    /// Creates a locator from a fully qualified table name.
89    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    /// Creates a locator containing only a table ID.
103    #[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    /// Adds a table ID to the locator.
112    pub(crate) fn with_table_id(mut self, table_id: TableId) -> Self {
113        self.table_id = Some(table_id);
114        self
115    }
116
117    /// Adds a physical table ID to a logical-table locator.
118    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
189/// Returns the stable kind stored in an Alter Table payload, if supported.
190pub(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        // Repartition is handled by RepartitionProcedure.
205        AlterTableKind::Repartition(_) => None,
206    }
207}
208
209/// Shared event representation used by table DDL procedures.
210#[derive(Debug)]
211pub(crate) struct TableDdlEvent {
212    event_type: TableDdlEventType,
213    locators: Vec<TableDdlLocator>,
214    payload: Option<TableDdlPayload>,
215}
216
217impl TableDdlEvent {
218    /// Builds the bounded event emitted when creating a table is submitted.
219    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    /// Builds the bounded event emitted when creating logical tables is submitted.
236    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    /// Builds the bounded event emitted when altering a table is submitted.
251    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    /// Builds the bounded event emitted when altering logical tables is submitted.
266    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    /// Builds the bounded event emitted when dropping a table is submitted.
288    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    /// Builds the bounded event emitted when restoring a dropped table is submitted.
300    #[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    /// Builds the bounded event emitted when purging a dropped table is submitted.
312    #[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    /// Builds the bounded event emitted when truncating a table is submitted.
324    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    /// Builds a lifecycle event with stable object locators and no intent payload.
339    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    /// Builds a Create Table success event containing the submitted locator and allocated ID.
351    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    /// Builds Create Logical Tables success rows from their allocated locators.
359    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}