use serde::{Deserialize, Serialize};
use crate::identifier::{Identifier, QualifiedName};
use crate::ir::cast::Cast;
use crate::ir::collation::Collation;
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;
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::text_search::configuration::TsConfiguration;
use crate::ir::text_search::dictionary::TsDictionary;
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, CatalogObjectRef};
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),
AlterViewSetCheckOption {
qname: QualifiedName,
new_value: Option<crate::ir::view::CheckOption>,
},
Mv(MvChange),
UserType(UserTypeChange),
Function(FunctionChange),
Procedure(ProcedureChange),
Extension(ExtensionChange),
Trigger(TriggerChange),
Table(TableChange),
GrantObjectPrivilege {
object: CatalogObjectRef,
grant: Grant,
},
RevokeObjectPrivilege {
object: CatalogObjectRef,
grant: Grant,
},
GrantColumnPrivilege {
qname: QualifiedName,
grant: Grant,
},
RevokeColumnPrivilege {
qname: QualifiedName,
grant: Grant,
},
AlterObjectOwner(AlterObjectOwner),
AlterDefaultPrivileges {
target_role: Identifier,
schema: Option<Identifier>,
object_type: DefaultPrivObjectType,
direction: GrantDirection,
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,
security: RowSecurity,
},
SetTableForceRowSecurity {
qname: QualifiedName,
force: ForceRowSecurity,
},
SetTableStorage {
qname: QualifiedName,
options: crate::ir::reloptions::TableStorageOptions,
},
SetIndexStorage {
qname: QualifiedName,
options: crate::ir::reloptions::IndexStorageOptions,
},
SetMaterializedViewStorage {
qname: QualifiedName,
options: crate::ir::reloptions::TableStorageOptions,
},
Publication(PublicationChange),
Subscription(SubscriptionChange),
EventTrigger(EventTriggerChange),
Statistic(StatisticChange),
Collation(CollationChange),
Aggregate(AggregateChange),
Cast(CastChange),
TsDictionary(TsDictionaryChange),
TsConfiguration(TsConfigurationChange),
UnsupportedDiff {
reason: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GrantDirection {
Grant,
Revoke,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RowSecurity {
Enable,
Disable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ForceRowSecurity {
Force,
NoForce,
}
#[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,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BodyReplaceStrategy {
InPlace,
Recreate,
}
#[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,
strategy: BodyReplaceStrategy,
},
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),
ReplaceViaDropCreate {
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>,
},
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum AggregateChange {
Create(crate::ir::aggregate::Aggregate),
Replace {
from: crate::ir::aggregate::Aggregate,
to: crate::ir::aggregate::Aggregate,
},
Drop {
qname: QualifiedName,
arg_types: Vec<ColumnType>,
},
AlterOwner {
qname: QualifiedName,
arg_types: Vec<ColumnType>,
owner: Identifier,
},
CommentOn {
qname: QualifiedName,
arg_types: Vec<ColumnType>,
comment: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum CastChange {
Create(Cast),
Replace {
from: Cast,
to: Cast,
},
Drop {
source: QualifiedName,
target: QualifiedName,
},
CommentOn {
source: QualifiedName,
target: QualifiedName,
comment: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum EventTriggerChange {
Create(crate::ir::event_trigger::EventTrigger),
Replace {
from: crate::ir::event_trigger::EventTrigger,
to: crate::ir::event_trigger::EventTrigger,
},
Drop {
name: Identifier,
},
AlterEnable {
name: Identifier,
enabled: crate::ir::event_trigger::EventTriggerEnabled,
},
AlterOwner {
name: Identifier,
owner: Identifier,
},
CommentOn {
name: Identifier,
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,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum PublicationChange {
Create(crate::ir::publication::Publication),
Drop {
name: Identifier,
},
Replace {
from: crate::ir::publication::Publication,
to: crate::ir::publication::Publication,
},
AddTable {
publication: Identifier,
table: crate::ir::publication::PublishedTable,
},
DropTable {
publication: Identifier,
qname: QualifiedName,
},
SetTable {
publication: Identifier,
table: crate::ir::publication::PublishedTable,
},
AddSchema {
publication: Identifier,
schema: Identifier,
},
DropSchema {
publication: Identifier,
schema: Identifier,
},
SetPublish {
publication: Identifier,
kinds: crate::ir::publication::PublishKinds,
},
SetViaRoot {
publication: Identifier,
value: bool,
},
CommentOn {
name: Identifier,
comment: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum SubscriptionChange {
Create(crate::ir::subscription::Subscription),
Drop {
name: Identifier,
},
AlterConnection {
name: Identifier,
new_connection: String,
},
AddPublication {
name: Identifier,
publication: Identifier,
},
DropPublication {
name: Identifier,
publication: Identifier,
},
SetOptions {
name: Identifier,
options: crate::ir::subscription::SubscriptionOptions,
},
CommentOn {
name: Identifier,
comment: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum StatisticChange {
Create(crate::ir::statistic::Statistic),
Drop {
qname: QualifiedName,
},
Replace {
from: crate::ir::statistic::Statistic,
to: crate::ir::statistic::Statistic,
},
AlterSetTarget {
qname: QualifiedName,
value: i32,
},
CommentOn {
qname: QualifiedName,
comment: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum TsDictionaryChange {
Create(TsDictionary),
Replace {
from: TsDictionary,
to: TsDictionary,
},
Drop {
qname: QualifiedName,
},
AlterOptions {
qname: QualifiedName,
options: Vec<(String, String)>,
},
AlterOwner {
qname: QualifiedName,
owner: Identifier,
},
CommentOn {
qname: QualifiedName,
comment: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum TsConfigurationChange {
Create(TsConfiguration),
Replace {
from: TsConfiguration,
to: TsConfiguration,
},
Drop {
qname: QualifiedName,
},
AddMapping {
qname: QualifiedName,
token_type: String,
dictionaries: Vec<QualifiedName>,
},
AlterMapping {
qname: QualifiedName,
token_type: String,
dictionaries: Vec<QualifiedName>,
},
DropMapping {
qname: QualifiedName,
token_type: String,
},
AlterOwner {
qname: QualifiedName,
owner: Identifier,
},
CommentOn {
qname: QualifiedName,
comment: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
pub enum CollationChange {
Create(Collation),
Drop {
qname: QualifiedName,
},
Rename {
from: QualifiedName,
to: Identifier,
},
Replace {
from: Collation,
to: Collation,
},
CommentOn {
qname: QualifiedName,
comment: Option<String>,
},
}
#[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(),
access_method: None,
tablespace: None,
}
}
#[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);
}
}