use serde::{Deserialize, Serialize};
use crate::identifier::{Identifier, QualifiedName};
use crate::ir::column_type::ColumnType;
use crate::ir::default_expr::NormalizedExpr;
use crate::ir::default_privileges::DefaultPrivObjectType;
use crate::ir::extension::Extension;
use crate::ir::function::{Function, NormalizedArgTypes};
use crate::ir::grant::{Grant, GrantTarget};
use crate::ir::index::Index;
use crate::ir::partition::PartitionBounds;
use crate::ir::procedure::Procedure;
use crate::ir::schema::Schema;
use crate::ir::sequence::Sequence;
use crate::ir::table::Table;
use crate::ir::trigger::Trigger;
use crate::ir::user_type::{CompositeAttribute, DomainCheck, UserType};
use crate::ir::view::{MaterializedView, View};
use super::destructiveness::Destructiveness;
use super::owner_op::{AlterObjectOwner, OwnerObjectKind};
use super::sequence_op::SequenceOpEntry;
use super::table_op::TableOpEntry;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChangeEntry {
pub change: Change,
pub destructiveness: Destructiveness,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum Change {
CreateSchema(Schema),
DropSchema(Identifier),
AlterSchema {
name: Identifier,
comment: Option<String>,
},
CreateTable(Table),
DropTable {
qname: QualifiedName,
row_count_estimate: Option<i64>,
},
AlterTable {
qname: QualifiedName,
ops: Vec<TableOpEntry>,
},
CreateIndex(Index),
DropIndex(QualifiedName),
ReplaceIndex {
from: Index,
to: Index,
},
CreateSequence(Sequence),
DropSequence(QualifiedName),
AlterSequence {
qname: QualifiedName,
ops: Vec<SequenceOpEntry>,
},
ValidateConstraint {
table: QualifiedName,
constraint: Identifier,
},
RecreateIndex {
qname: QualifiedName,
},
View(ViewChange),
Mv(MvChange),
UserType(UserTypeChange),
Function(FunctionChange),
Procedure(ProcedureChange),
Extension(ExtensionChange),
Trigger(TriggerChange),
Table(TableChange),
GrantObjectPrivilege {
qname: QualifiedName,
kind: OwnerObjectKind,
#[serde(default)]
signature: String,
grant: Grant,
},
RevokeObjectPrivilege {
qname: QualifiedName,
kind: OwnerObjectKind,
#[serde(default)]
signature: String,
grant: Grant,
},
GrantColumnPrivilege {
qname: QualifiedName,
grant: Grant,
},
RevokeColumnPrivilege {
qname: QualifiedName,
grant: Grant,
},
AlterObjectOwner(AlterObjectOwner),
AlterDefaultPrivileges {
target_role: Identifier,
schema: Option<Identifier>,
object_type: DefaultPrivObjectType,
is_grant: bool,
grant: Grant,
},
CreatePolicy {
table: QualifiedName,
policy: crate::ir::policy::Policy,
},
DropPolicy {
table: QualifiedName,
name: Identifier,
},
AlterPolicy {
table: QualifiedName,
policy: crate::ir::policy::Policy,
},
SetTableRowSecurity {
qname: QualifiedName,
enable: bool,
},
SetTableForceRowSecurity {
qname: QualifiedName,
force: bool,
},
SetTableStorage {
qname: QualifiedName,
options: crate::ir::reloptions::TableStorageOptions,
},
SetIndexStorage {
qname: QualifiedName,
options: crate::ir::reloptions::IndexStorageOptions,
},
SetMaterializedViewStorage {
qname: QualifiedName,
options: crate::ir::reloptions::TableStorageOptions,
},
CreatePublication(crate::ir::publication::Publication),
DropPublication {
name: crate::identifier::Identifier,
},
ReplacePublication {
from: crate::ir::publication::Publication,
to: crate::ir::publication::Publication,
},
AlterPublicationAddTable {
publication: crate::identifier::Identifier,
table: crate::ir::publication::PublishedTable,
},
AlterPublicationDropTable {
publication: crate::identifier::Identifier,
qname: crate::identifier::QualifiedName,
},
AlterPublicationSetTable {
publication: crate::identifier::Identifier,
table: crate::ir::publication::PublishedTable,
},
AlterPublicationAddSchema {
publication: crate::identifier::Identifier,
schema: crate::identifier::Identifier,
},
AlterPublicationDropSchema {
publication: crate::identifier::Identifier,
schema: crate::identifier::Identifier,
},
AlterPublicationSetPublish {
publication: crate::identifier::Identifier,
kinds: crate::ir::publication::PublishKinds,
},
AlterPublicationSetViaRoot {
publication: crate::identifier::Identifier,
value: bool,
},
CommentOnPublication {
name: crate::identifier::Identifier,
comment: Option<String>,
},
UnsupportedDiff {
reason: String,
},
}
const _: () = {
let _ = core::mem::size_of::<OwnerObjectKind>();
let _ = core::mem::size_of::<GrantTarget>();
};
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum UserTypeChange {
Create(UserType),
Drop(QualifiedName),
EnumAddValue {
qname: QualifiedName,
value: String,
before: Option<String>,
after: Option<String>,
},
EnumRenameValue {
qname: QualifiedName,
from: String,
to: String,
},
DomainAddCheck {
qname: QualifiedName,
constraint: DomainCheck,
},
DomainDropCheck {
qname: QualifiedName,
name: Identifier,
},
DomainSetDefault {
qname: QualifiedName,
default: Option<NormalizedExpr>,
},
DomainSetNotNull {
qname: QualifiedName,
not_null: bool,
},
CompositeAddAttribute {
qname: QualifiedName,
attribute: CompositeAttribute,
},
CompositeDropAttribute {
qname: QualifiedName,
name: Identifier,
},
CompositeAlterAttributeType {
qname: QualifiedName,
attribute: Identifier,
new_type: ColumnType,
},
SetComment {
qname: QualifiedName,
comment: Option<String>,
},
ReplaceWithCascade {
source: UserType,
catalog: UserType,
},
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum ViewChange {
Create(View),
Drop(QualifiedName),
ReplaceBody {
source: View,
catalog: View,
compatible: bool,
},
SetReloption {
qname: QualifiedName,
security_barrier: Option<bool>,
security_invoker: Option<bool>,
},
SetComment {
qname: QualifiedName,
comment: Option<String>,
},
SetColumnComment {
qname: QualifiedName,
column: Identifier,
comment: Option<String>,
},
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum MvChange {
Create(MaterializedView),
Drop(QualifiedName),
ReplaceBody {
source: MaterializedView,
catalog: MaterializedView,
},
SetComment {
qname: QualifiedName,
comment: Option<String>,
},
SetColumnComment {
qname: QualifiedName,
column: Identifier,
comment: Option<String>,
},
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum FunctionChange {
Create(Function),
Drop {
qname: QualifiedName,
args: NormalizedArgTypes,
},
CreateOrReplace(Function),
ReplaceWithCascade {
source: Function,
catalog: Function,
},
SetComment {
qname: QualifiedName,
args: NormalizedArgTypes,
comment: Option<String>,
},
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum ProcedureChange {
Create(Procedure),
Drop(QualifiedName),
CreateOrReplace(Procedure),
SetComment {
qname: QualifiedName,
comment: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum ExtensionChange {
Create(Extension),
Drop(Identifier),
AlterUpdate {
name: Identifier,
to_version: String,
},
ReplaceWithCascade(Extension),
CommentOn {
name: Identifier,
comment: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum TriggerChange {
Create(Trigger),
Drop {
qname: QualifiedName,
table: QualifiedName,
},
Replace(Trigger),
CommentOn {
qname: QualifiedName,
table: QualifiedName,
comment: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum TableChange {
AttachPartition {
parent: QualifiedName,
child: QualifiedName,
bounds: PartitionBounds,
},
DetachPartition {
parent: QualifiedName,
child: QualifiedName,
},
}
#[cfg(test)]
mod tests {
use super::*;
use crate::identifier::Identifier;
use crate::ir::column::Column;
use crate::ir::column_type::ColumnType;
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
fn qn(schema: &str, name: &str) -> QualifiedName {
QualifiedName::new(id(schema), id(name))
}
fn table_users() -> Table {
Table {
qname: qn("app", "users"),
columns: vec![Column {
name: id("id"),
ty: ColumnType::BigInt,
nullable: false,
default: None,
identity: None,
generated: None,
collation: None,
storage: None,
compression: None,
comment: None,
}],
constraints: vec![],
partition_by: None,
partition_of: None,
comment: None,
owner: None,
grants: vec![],
rls_enabled: false,
rls_forced: false,
policies: vec![],
storage: crate::ir::reloptions::TableStorageOptions::default(),
}
}
#[test]
fn create_table_entry_serde_round_trip() {
let entry = ChangeEntry {
change: Change::CreateTable(table_users()),
destructiveness: Destructiveness::Safe,
};
let json = serde_json::to_string(&entry).unwrap();
let back: ChangeEntry = serde_json::from_str(&json).unwrap();
assert_eq!(entry, back);
}
#[test]
fn drop_table_entry_serde_round_trip() {
let entry = ChangeEntry {
change: Change::DropTable {
qname: qn("app", "users"),
row_count_estimate: None,
},
destructiveness: Destructiveness::RequiresApprovalAndDataLossWarning {
reason: "drops table app.users".into(),
},
};
let json = serde_json::to_string(&entry).unwrap();
let back: ChangeEntry = serde_json::from_str(&json).unwrap();
assert_eq!(entry, back);
}
#[test]
fn equal_create_table_changes_compare_equal() {
let a = Change::CreateTable(table_users());
let b = Change::CreateTable(table_users());
assert_eq!(a, b);
}
}