use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CopyFormat {
#[default]
Text,
Csv,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct CopyOptions {
pub format: CopyFormat,
pub header: bool,
pub delimiter: Option<char>,
pub null_str: Option<String>,
pub quote: Option<char>,
pub escape: Option<char>,
pub force_quote: Option<Vec<String>>,
pub force_not_null: Option<Vec<String>>,
pub force_null: Option<Vec<String>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorDirection {
Next,
Prior,
First,
Last,
Absolute(i64),
Relative(i64),
Count(i64),
All,
Backward(i64),
BackwardAll,
}
impl fmt::Display for CursorDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Next => f.write_str("NEXT"),
Self::Prior => f.write_str("PRIOR"),
Self::First => f.write_str("FIRST"),
Self::Last => f.write_str("LAST"),
Self::Absolute(n) => write!(f, "ABSOLUTE {n}"),
Self::Relative(n) => write!(f, "RELATIVE {n}"),
Self::Count(n) => write!(f, "FORWARD {n}"),
Self::All => f.write_str("ALL"),
Self::Backward(n) => write!(f, "BACKWARD {n}"),
Self::BackwardAll => f.write_str("BACKWARD ALL"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiscardTarget {
All,
Plans,
Sequences,
Temp,
}
impl fmt::Display for DiscardTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::All => "ALL",
Self::Plans => "PLANS",
Self::Sequences => "SEQUENCES",
Self::Temp => "TEMP",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MaintainKind {
ReindexRelation,
ReindexSchema,
Whole,
ClusterRelation,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SetDbRoleSettingStatement {
pub database: Option<String>,
pub role: Option<String>,
pub param: Option<String>,
pub value: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidateOnlyKind {
LockTable,
RoleName,
SecurityLabel,
ExtensionAvailable,
TypeName,
AggregateName,
ConversionName,
LanguageName,
CollationName,
TsConfigName,
EventTriggerName,
TablespaceName,
LargeObjectOid,
ForeignInfra,
ExtensionInstalled,
}
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::large_enum_variant)] pub enum Statement {
AlterSystem {
parameter: Option<String>,
},
DropDatabase {
name: String,
if_exists: bool,
},
NoOpPreventedInTransaction {
what: String,
},
DropAggregate {
if_exists: bool,
items: Vec<(String, Option<Vec<String>>)>,
},
AlterRolePassword {
name: String,
password: Option<String>,
},
ValidateOnly {
kind: ValidateOnlyKind,
names: Vec<String>,
},
SetDbRoleSetting(Box<SetDbRoleSettingStatement>),
SetConstraints {
names: Vec<String>,
deferred: bool,
},
DropTable {
names: Vec<String>,
if_exists: bool,
},
DropIndex {
name: String,
if_exists: bool,
},
Prepare {
name: String,
param_types: Vec<String>,
body: alloc::boxed::Box<Statement>,
source: String,
},
Execute {
name: String,
args: Vec<Expr>,
},
Deallocate(Option<String>),
CreateStatistics {
name: String,
if_not_exists: bool,
kinds: Vec<String>,
columns: Vec<String>,
table: String,
},
DropStatistics {
name: String,
if_exists: bool,
},
Call(String),
PrepareTransaction(String),
Empty,
DeclareCursor {
name: String,
scroll: Option<bool>,
hold: bool,
query: Box<Statement>,
},
FetchCursor {
name: String,
direction: CursorDirection,
},
MoveCursor {
name: String,
direction: CursorDirection,
},
CloseCursor {
name: Option<String>,
},
Listen(String),
Notify {
channel: String,
payload: Option<String>,
},
Unlisten(Option<String>),
CopyTo {
table: String,
columns: Option<Vec<String>>,
query: Option<Box<Statement>>,
options: CopyOptions,
},
CopyFromFile {
table: String,
columns: Option<Vec<String>>,
path: String,
options: CopyOptions,
},
CopyToFile {
table: String,
columns: Option<Vec<String>>,
query: Option<Box<Statement>>,
path: String,
options: CopyOptions,
},
Select(SelectStatement),
CreateTable(CreateTableStatement),
CreateExtension(String),
DoBlock(PlPgSqlBlock),
CreateIndex(CreateIndexStatement),
Insert(InsertStatement),
Update(UpdateStatement),
Delete(DeleteStatement),
Merge(MergeStatement),
Vacuum {
table: Option<String>,
analyze: bool,
},
Begin(Option<IsolationLevel>),
Commit,
Rollback,
Savepoint(String),
RollbackToSavepoint(String),
ReleaseSavepoint(String),
ShowTables,
ShowDatabases,
ShowCreateTable(String),
ShowIndexes(String),
ShowStatus,
ShowVariables,
ShowProcesslist,
Discard(DiscardTarget),
Kill {
query_only: bool,
id: Box<Expr>,
},
ShowColumns(String),
CreateUser(CreateUserStatement),
DropUser {
name: String,
if_exists: bool,
},
SetRole(Option<String>),
Grant(GrantStatement),
Revoke(GrantStatement),
CreatePolicy(CreatePolicyStatement),
AlterPolicy(AlterPolicyStatement),
DropPolicy(DropPolicyStatement),
ShowUsers,
Explain(ExplainStatement),
AlterIndex(AlterIndexStatement),
AlterTable(AlterTableStatement),
CreatePublication(CreatePublicationStatement),
DropPublication {
name: String,
if_exists: bool,
},
ShowPublications,
CreateSubscription(CreateSubscriptionStatement),
DropSubscription {
name: String,
if_exists: bool,
},
ShowSubscriptions,
WaitForWalPosition {
pos: u64,
timeout_ms: Option<u64>,
},
Analyze(Option<String>),
Maintain {
kind: MaintainKind,
concurrently: bool,
target: Option<String>,
},
Truncate {
tables: Vec<String>,
restart_identity: bool,
cascade: bool,
only: bool,
},
CompactColdSegments,
SetParameter {
name: String,
value: SetValue,
local: bool,
},
SetParameterList(Vec<(String, SetValue)>),
SetUserVars(Vec<(String, Expr)>, Vec<(String, Expr)>),
SetTransaction {
isolation: IsolationLevel,
},
ShowParameter(String),
ResetParameter(Option<String>),
CreateFunction(CreateFunctionStatement),
CreateTrigger(CreateTriggerStatement),
CreateRule(CreateRuleStatement),
DropRule {
name: String,
table: String,
if_exists: bool,
},
DropTrigger {
name: String,
table: String,
if_exists: bool,
},
DropFunction {
name: String,
args: Option<Vec<String>>,
if_exists: bool,
},
CreateSequence(CreateSequenceStatement),
AlterSequence(AlterSequenceStatement),
DropSequence {
names: Vec<String>,
if_exists: bool,
},
CreateView(CreateViewStatement),
DropView {
names: Vec<String>,
if_exists: bool,
},
CreateMaterializedView(CreateMaterializedViewStatement),
RefreshMaterializedView {
name: String,
with_data: bool,
},
DropMaterializedView {
names: Vec<String>,
if_exists: bool,
},
CreateType(CreateTypeStatement),
CommentOn {
kind: String,
name: String,
comment: Option<String>,
},
AlterTypeRenameValue {
type_name: String,
old: String,
new: String,
},
AlterTypeAddValue {
type_name: String,
label: String,
if_not_exists: bool,
position: Option<(bool, String)>,
},
DropType {
names: Vec<String>,
if_exists: bool,
},
CreateDomain(CreateDomainStatement),
AlterDomain {
name: String,
action: AlterDomainAction,
},
DropDomain {
names: Vec<String>,
if_exists: bool,
},
CreateSchema {
name: String,
if_not_exists: bool,
},
DropSchema {
names: Vec<String>,
if_exists: bool,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum AlterDomainAction {
AddConstraint { name: Option<String>, check: Expr },
DropConstraint { name: String, if_exists: bool },
SetDefault(Expr),
DropDefault,
SetNotNull,
DropNotNull,
RenameTo(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateDomainStatement {
pub name: String,
pub base_type: ColumnTypeName,
pub base_domain: Option<String>,
pub default: Option<Expr>,
pub not_null: bool,
pub checks: Vec<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateTypeStatement {
pub name: String,
pub kind: TypeKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeKind {
Enum { labels: Vec<String> },
Composite {
fields: Vec<(String, ColumnTypeName)>,
field_user_types: Vec<Option<String>>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum SetValue {
String(String),
Ident(String),
Number(String),
Default,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum IsolationLevel {
ReadUncommitted,
#[default]
ReadCommitted,
RepeatableRead,
Serializable,
}
impl IsolationLevel {
pub fn as_pg_str(self) -> &'static str {
match self {
Self::ReadUncommitted => "read uncommitted",
Self::ReadCommitted => "read committed",
Self::RepeatableRead => "repeatable read",
Self::Serializable => "serializable",
}
}
}
impl core::fmt::Display for IsolationLevel {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_pg_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateSubscriptionStatement {
pub name: String,
pub conn_str: String,
pub publications: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateSequenceStatement {
pub name: String,
pub if_not_exists: bool,
pub temporary: bool,
pub data_type: Option<SequenceDataType>,
pub options: SequenceOptions,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SequenceDataType {
SmallInt,
Int,
BigInt,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SequenceOptions {
pub increment: Option<i64>,
pub min_value: Option<SeqBound>,
pub max_value: Option<SeqBound>,
pub start: Option<i64>,
pub restart: Option<Option<i64>>,
pub cache: Option<i64>,
pub cycle: Option<bool>,
pub owned_by: Option<SequenceOwnedBy>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SeqBound {
Value(i64),
NoBound,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SequenceOwnedBy {
None,
Column { table: String, column: String },
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateMaterializedViewStatement {
pub name: String,
pub if_not_exists: bool,
pub columns: Vec<String>,
pub body: SelectStatement,
pub with_data: bool,
pub as_plain_table: bool,
pub temporary: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ViewCheckOption {
Local,
Cascaded,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateViewStatement {
pub name: String,
pub or_replace: bool,
pub if_not_exists: bool,
pub temporary: bool,
pub columns: Vec<String>,
pub body: SelectStatement,
pub check_option: Option<ViewCheckOption>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AlterSequenceStatement {
pub name: String,
pub if_exists: bool,
pub options: SequenceOptions,
pub rename_to: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreatePublicationStatement {
pub name: String,
pub scope: PublicationScope,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PublicationScope {
AllTables,
ForTables(Vec<String>),
AllTablesExcept(Vec<String>),
TablesInSchema(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AlterIndexStatement {
pub name: String,
pub target: AlterIndexTarget,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AlterIndexTarget {
Rebuild { encoding: Option<VecEncoding> },
Rename { new: String, if_exists: bool },
StorageParams,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AlterTableStatement {
pub name: String,
pub targets: Vec<AlterTableTarget>,
}
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum AlterTableTarget {
Inherit { parent: String, detach: bool },
SetHotTierBytes(u64),
AddForeignKey(ForeignKeyConstraint),
DropForeignKey { name: String, if_exists: bool },
DropIndex { name: String, if_exists: bool },
AddColumn {
column: ColumnDef,
if_not_exists: bool,
},
AlterColumnType {
column: String,
new_type: ColumnTypeName,
using: Option<Expr>,
collation: Option<(Collation, String)>,
},
DropColumn {
column: String,
if_exists: bool,
cascade: bool,
},
AddTableConstraint(TableConstraint),
OwnerTo { role: String },
ClusterOn { index: Option<String> },
ValidateConstraint { name: String },
RenameColumn { old: String, new: String },
RenameConstraint { old: String, new: String },
SetColumnAutoIncrement {
column: String,
seq_name: Option<String>,
},
RenameTable { new: String },
SetTriggerEnabled {
which: TriggerSelector,
enabled: bool,
},
SetRowSecurity {
enabled: Option<bool>,
force: Option<bool>,
},
AttachPartition {
child: String,
bounds: PartitionOfBoundsAst,
},
DetachPartition {
child: String,
concurrently: bool,
finalize: bool,
},
AlterColumnSetDefault { column: String, default_expr: Expr },
AlterColumnDropDefault { column: String },
AlterColumnSetNotNull { column: String },
AlterColumnDropNotNull { column: String },
AlterColumnRestart { column: String, with: Option<i64> },
AlterColumnDropExpression { column: String, if_exists: bool },
AlterColumnDropIdentity { column: String, if_exists: bool },
AlterColumnSetExpression { column: String, expr: Expr },
OfType { type_name: String },
ReplicaIdentityUsingIndex { index: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TriggerSelector {
All,
Named(String),
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, PartialEq)]
pub struct ExplainStatement {
pub analyze: bool,
pub inner: Box<Statement>,
pub suggest: bool,
pub costs_off: bool,
pub buffers: bool,
pub timing_off: bool,
pub settings: bool,
pub wal: bool,
pub summary_off: bool,
pub format: ExplainFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ExplainFormat {
#[default]
Text,
Json,
Xml,
Yaml,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PolicyCmd {
All,
Select,
Insert,
Update,
Delete,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreatePolicyStatement {
pub name: String,
pub table: String,
pub permissive: bool,
pub cmd: PolicyCmd,
pub roles: Vec<String>,
pub using: Option<Expr>,
pub with_check: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AlterPolicyStatement {
pub name: String,
pub table: String,
pub rename_to: Option<String>,
pub roles: Option<Vec<String>>,
pub using: Option<Expr>,
pub with_check: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DropPolicyStatement {
pub name: String,
pub table: String,
pub if_exists: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateUserStatement {
pub name: String,
pub password: String,
pub role: String,
pub login: Option<bool>,
pub inherit: Option<bool>,
pub superuser: Option<bool>,
pub is_user: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FunctionVolatility {
Immutable,
Stable,
#[default]
Volatile,
}
impl FunctionVolatility {
#[must_use]
pub const fn as_pg_char(self) -> &'static str {
match self {
Self::Immutable => "i",
Self::Stable => "s",
Self::Volatile => "v",
}
}
#[must_use]
pub const fn as_sql(self) -> &'static str {
match self {
Self::Immutable => "IMMUTABLE",
Self::Stable => "STABLE",
Self::Volatile => "VOLATILE",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FunctionParallel {
#[default]
Unsafe,
Restricted,
Safe,
}
impl FunctionParallel {
#[must_use]
pub const fn as_pg_char(self) -> &'static str {
match self {
Self::Unsafe => "u",
Self::Restricted => "r",
Self::Safe => "s",
}
}
#[must_use]
pub const fn as_sql(self) -> &'static str {
match self {
Self::Unsafe => "PARALLEL UNSAFE",
Self::Restricted => "PARALLEL RESTRICTED",
Self::Safe => "PARALLEL SAFE",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct FunctionAttrs {
pub volatility: FunctionVolatility,
pub strict: bool,
pub security_definer: bool,
pub leakproof: bool,
pub parallel: FunctionParallel,
pub cost: Option<f64>,
pub rows: Option<f64>,
}
impl FunctionAttrs {
#[must_use]
pub fn render_words(&self) -> alloc::vec::Vec<alloc::string::String> {
let mut out = alloc::vec::Vec::new();
if self.volatility != FunctionVolatility::Volatile {
out.push(alloc::string::String::from(self.volatility.as_sql()));
}
if self.parallel != FunctionParallel::Unsafe {
out.push(alloc::string::String::from(self.parallel.as_sql()));
}
if self.strict {
out.push(alloc::string::String::from("STRICT"));
}
if self.security_definer {
out.push(alloc::string::String::from("SECURITY DEFINER"));
}
if self.leakproof {
out.push(alloc::string::String::from("LEAKPROOF"));
}
if let Some(c) = self.cost {
out.push(alloc::format!("COST {}", render_attr_number(c)));
}
if let Some(r) = self.rows {
out.push(alloc::format!("ROWS {}", render_attr_number(r)));
}
out
}
}
fn render_attr_number(v: f64) -> alloc::string::String {
let whole = v as i64;
if v.abs() < 1e15 && (whole as f64) == v {
alloc::format!("{whole}")
} else {
alloc::format!("{v}")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateFunctionStatement {
pub name: String,
pub or_replace: bool,
pub args: Vec<FunctionArg>,
pub returns: FunctionReturn,
pub language: String,
pub body: FunctionBody,
pub attrs: FunctionAttrs,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionArg {
pub mode: FunctionArgMode,
pub name: Option<String>,
pub ty: FunctionArgType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FunctionArgMode {
In,
Out,
InOut,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FunctionArgType {
Typed(ColumnTypeName),
Raw(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum FunctionReturn {
Trigger,
Void,
Type(ColumnTypeName),
Other(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum FunctionBody {
PlPgSql(PlPgSqlBlock),
Raw(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct PlPgSqlBlock {
pub declarations: Vec<PlPgSqlDeclare>,
pub statements: Vec<PlPgSqlStmt>,
pub exception_handlers: Vec<ExceptionHandler>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExceptionHandler {
pub conditions: Vec<String>,
pub body: Vec<PlPgSqlStmt>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PlPgSqlDeclare {
pub name: String,
pub ty: FunctionArgType,
pub default: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PlPgSqlStmt {
Assign { target: AssignTarget, value: Expr },
SelectInto {
var: String,
body: Box<SelectStatement>,
},
Return(ReturnTarget),
ReturnNext(Expr),
ReturnQuery(Box<SelectStatement>),
ReturnQueryExecute { sql: Expr },
If {
branches: Vec<(Expr, Vec<PlPgSqlStmt>)>,
else_branch: Vec<PlPgSqlStmt>,
},
Raise {
level: RaiseLevel,
message: String,
args: Vec<Expr>,
},
EmbeddedSql(Box<Statement>),
Assert {
condition: Expr,
message: Option<Expr>,
},
While {
condition: Expr,
body: Vec<PlPgSqlStmt>,
},
ForRange {
var: String,
start: Expr,
end: Expr,
reverse: bool,
body: Vec<PlPgSqlStmt>,
},
Loop { body: Vec<PlPgSqlStmt> },
Exit { when: Option<Expr> },
Continue { when: Option<Expr> },
ExecuteDynamic { sql: Expr },
ForQuery {
var: String,
query: Box<SelectStatement>,
body: Vec<PlPgSqlStmt>,
},
ForExecute {
var: String,
sql_expr: Expr,
body: Vec<PlPgSqlStmt>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RaiseLevel {
Notice,
Warning,
Info,
Log,
Debug,
Exception,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AssignTarget {
NewColumn(String),
OldColumn(String),
Local(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ReturnTarget {
New,
Old,
Null,
Expr(Expr),
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateTriggerStatement {
pub name: String,
pub or_replace: bool,
pub timing: TriggerTiming,
pub events: Vec<TriggerEvent>,
pub table: String,
pub for_each: TriggerForEach,
pub function: String,
pub update_columns: Vec<String>,
pub when_condition: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateRuleStatement {
pub name: String,
pub or_replace: bool,
pub event: String,
pub table: String,
pub instead: bool,
pub when_condition: Option<Expr>,
pub commands: Vec<Statement>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TriggerTiming {
Before,
After,
InsteadOf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TriggerEvent {
Insert,
Update,
Delete,
Truncate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TriggerForEach {
Row,
Statement,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct IndexColumnOrder {
pub descending: bool,
pub nulls_first: Option<bool>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateIndexStatement {
pub name: String,
pub concurrently: bool,
pub key_order: IndexColumnOrder,
pub key_collation: Option<String>,
pub table: String,
pub column: String,
pub nulls_not_distinct: bool,
pub method: IndexMethod,
pub if_not_exists: bool,
pub included_columns: Vec<String>,
pub partial_predicate: Option<Expr>,
pub expression: Option<Expr>,
pub extra_columns: Vec<String>,
pub is_unique: bool,
pub opclass: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexMethod {
BTree,
Hnsw,
Brin,
Gin,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LikeSpec {
pub source: String,
pub at: usize,
pub options: LikeOptions,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LikeOptions {
pub defaults: bool,
pub constraints: bool,
pub identity: bool,
pub generated: bool,
pub indexes: bool,
pub comments: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateTableStatement {
pub temporary: bool,
pub name: String,
pub columns: Vec<ColumnDef>,
pub like_specs: Vec<LikeSpec>,
pub inherits: Vec<String>,
pub if_not_exists: bool,
pub foreign_keys: Vec<ForeignKeyConstraint>,
pub table_constraints: Vec<TableConstraint>,
pub partition_by: Option<PartitionBySpec>,
pub partition_of: Option<PartitionOfSpec>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PartitionBySpec {
pub kind: PartitionKindAst,
pub key_columns: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartitionKindAst {
Range,
List,
Hash,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PartitionOfSpec {
pub parent_name: String,
pub bounds: PartitionOfBoundsAst,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PartitionOfBoundsAst {
Range {
lower: Box<Expr>,
upper: Box<Expr>,
},
List {
values: Vec<Expr>,
},
Hash {
modulus: u32,
remainder: u32,
},
Default,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TableConstraint {
PrimaryKey {
name: Option<String>,
columns: Vec<String>,
deferrable: bool,
initially_deferred: bool,
},
Unique {
name: Option<String>,
columns: Vec<String>,
nulls_not_distinct: bool,
deferrable: bool,
initially_deferred: bool,
},
Check {
name: Option<String>,
expr: Expr,
not_valid: bool,
},
Exclude {
name: Option<String>,
method: Option<String>,
elements: Vec<(String, String)>,
},
Index {
name: Option<String>,
columns: Vec<String>,
},
FulltextIndex {
name: Option<String>,
columns: Vec<String>,
},
}
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::struct_excessive_bools)] pub struct ColumnDef {
pub name: String,
pub ty: ColumnTypeName,
pub nullable: bool,
pub default: Option<Expr>,
pub auto_increment: bool,
pub is_primary_key: bool,
pub is_unique: bool,
pub unique_nulls_not_distinct: bool,
pub constraint_deferrable: bool,
pub constraint_initially_deferred: bool,
pub check: Option<Expr>,
pub user_type_ref: Option<String>,
pub on_update_runtime: Option<Expr>,
pub collation: Collation,
pub collation_explicit: bool,
pub collation_name: Option<String>,
pub is_unsigned: bool,
pub inline_enum_variants: Option<Vec<String>>,
pub inline_set_variants: Option<Vec<String>>,
pub generated_stored_expr: Option<Box<Expr>>,
pub identity_always: bool,
pub mysql_int_width: Option<MysqlIntWidth>,
pub mysql_fsp: Option<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Collation {
Binary,
CaseInsensitive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MysqlIntWidth {
Tiny,
Small,
Medium,
Int,
Big,
}
#[allow(clippy::derivable_impls)]
impl Default for Collation {
fn default() -> Self {
Self::Binary
}
}
impl Collation {
#[must_use]
pub fn from_collation_name(name: &str) -> Self {
let lc = name.trim().to_ascii_lowercase();
let bare = lc
.trim_matches(|c: char| c == '"' || c == '\'')
.rsplit('.')
.next()
.unwrap_or("");
if bare.is_empty() {
return Self::Binary;
}
if bare == "case_insensitive" || bare == "nocase" {
return Self::CaseInsensitive;
}
if bare.ends_with("_ci") {
return Self::CaseInsensitive;
}
Self::Binary
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ForeignKeyConstraint {
pub name: Option<String>,
pub columns: Vec<String>,
pub parent_table: String,
pub parent_columns: Vec<String>,
pub on_delete: FkAction,
pub on_update: FkAction,
pub match_type: MatchType,
pub deferrable: bool,
pub initially_deferred: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MatchType {
#[default]
Simple,
Full,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FkAction {
Restrict,
Cascade,
SetNull,
SetDefault,
NoAction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VecEncoding {
#[default]
F32,
Sq8,
F16,
}
impl fmt::Display for VecEncoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::F32 => f.write_str("F32"),
Self::Sq8 => f.write_str("SQ8"),
Self::F16 => f.write_str("HALF"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColumnTypeName {
Name,
Xid,
Xid8,
Oid,
SmallInt,
Int,
BigInt,
Float,
Real,
Text,
Varchar(u32),
Char(u32),
Bool,
Vector {
dim: u32,
encoding: VecEncoding,
},
Numeric(u16, i16),
Date,
Timestamp,
Timestamptz,
Json,
Jsonb,
Bytes,
TextArray,
IntArray,
BigIntArray,
TsVector,
TsQuery,
Uuid,
Time,
Year,
TimeTz,
Money,
Range(RangeKindAst),
Hstore,
IntArray2D,
BigIntArray2D,
TextArray2D,
BoolArray2D,
Interval,
IntervalArray,
BoolArray,
SmallIntArray,
FloatArray,
NumericArray,
DateArray,
TimestampArray,
TimestamptzArray,
UuidArray,
JsonArray,
JsonbArray,
BytesArray,
VarcharArray,
CharArray,
Multirange(RangeKindAst),
Point,
Lseg,
Path,
PgBox,
Polygon,
Line,
Circle,
Inet,
Cidr,
Macaddr,
Macaddr8,
Bit(u32),
BitVarying(u32),
Xml,
Char1,
MoneyArray,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum RangeKindAst {
Int4,
Int8,
Num,
Ts,
TsTz,
Date,
}
impl fmt::Display for ColumnTypeName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SmallInt => f.write_str("SMALLINT"),
Self::Int => f.write_str("INT"),
Self::BigInt => f.write_str("BIGINT"),
Self::Float => f.write_str("FLOAT"),
Self::Real => f.write_str("REAL"),
Self::Text => f.write_str("TEXT"),
Self::Name => f.write_str("name"),
Self::Xid => f.write_str("xid"),
Self::Xid8 => f.write_str("xid8"),
Self::Oid => f.write_str("oid"),
Self::Varchar(n) => write!(f, "VARCHAR({n})"),
Self::Char(n) => write!(f, "CHAR({n})"),
Self::Bool => f.write_str("BOOL"),
Self::Vector { dim, encoding } => match encoding {
VecEncoding::F32 => write!(f, "VECTOR({dim})"),
VecEncoding::Sq8 => write!(f, "VECTOR({dim}) USING SQ8"),
VecEncoding::F16 => write!(f, "VECTOR({dim}) USING HALF"),
},
Self::Json => f.write_str("JSON"),
Self::Jsonb => f.write_str("JSONB"),
Self::Bytes => f.write_str("BYTEA"),
Self::TextArray => f.write_str("TEXT[]"),
Self::IntArray => f.write_str("INT[]"),
Self::BigIntArray => f.write_str("BIGINT[]"),
Self::TsVector => f.write_str("TSVECTOR"),
Self::TsQuery => f.write_str("TSQUERY"),
Self::Uuid => f.write_str("UUID"),
Self::Numeric(p, s) => {
if *s == 0 {
write!(f, "NUMERIC({p})")
} else {
write!(f, "NUMERIC({p}, {s})")
}
}
Self::Date => f.write_str("DATE"),
Self::Timestamp => f.write_str("TIMESTAMP"),
Self::Timestamptz => f.write_str("TIMESTAMPTZ"),
Self::Time => f.write_str("TIME"),
Self::Year => f.write_str("YEAR"),
Self::TimeTz => f.write_str("TIMETZ"),
Self::Money => f.write_str("MONEY"),
Self::Range(k) => f.write_str(match k {
RangeKindAst::Int4 => "INT4RANGE",
RangeKindAst::Int8 => "INT8RANGE",
RangeKindAst::Num => "NUMRANGE",
RangeKindAst::Ts => "TSRANGE",
RangeKindAst::TsTz => "TSTZRANGE",
RangeKindAst::Date => "DATERANGE",
}),
Self::Hstore => f.write_str("HSTORE"),
Self::Interval => f.write_str("INTERVAL"),
Self::IntervalArray => f.write_str("INTERVAL[]"),
Self::BoolArray => f.write_str("BOOL[]"),
Self::SmallIntArray => f.write_str("SMALLINT[]"),
Self::FloatArray => f.write_str("FLOAT[]"),
Self::NumericArray => f.write_str("NUMERIC[]"),
Self::DateArray => f.write_str("DATE[]"),
Self::TimestampArray => f.write_str("TIMESTAMP[]"),
Self::TimestamptzArray => f.write_str("TIMESTAMPTZ[]"),
Self::UuidArray => f.write_str("UUID[]"),
Self::JsonArray => f.write_str("JSON[]"),
Self::JsonbArray => f.write_str("JSONB[]"),
Self::BytesArray => f.write_str("BYTEA[]"),
Self::VarcharArray => f.write_str("VARCHAR[]"),
Self::CharArray => f.write_str("CHAR[]"),
Self::Multirange(k) => f.write_str(match k {
RangeKindAst::Int4 => "INT4MULTIRANGE",
RangeKindAst::Int8 => "INT8MULTIRANGE",
RangeKindAst::Num => "NUMMULTIRANGE",
RangeKindAst::Ts => "TSMULTIRANGE",
RangeKindAst::TsTz => "TSTZMULTIRANGE",
RangeKindAst::Date => "DATEMULTIRANGE",
}),
Self::Point => f.write_str("POINT"),
Self::Lseg => f.write_str("LSEG"),
Self::Path => f.write_str("PATH"),
Self::PgBox => f.write_str("BOX"),
Self::Polygon => f.write_str("POLYGON"),
Self::Line => f.write_str("LINE"),
Self::Circle => f.write_str("CIRCLE"),
Self::Inet => f.write_str("INET"),
Self::Cidr => f.write_str("CIDR"),
Self::Macaddr => f.write_str("MACADDR"),
Self::Macaddr8 => f.write_str("MACADDR8"),
Self::Bit(0) => f.write_str("BIT"),
Self::Bit(n) => write!(f, "BIT({n})"),
Self::BitVarying(0) => f.write_str("VARBIT"),
Self::BitVarying(n) => write!(f, "VARBIT({n})"),
Self::Xml => f.write_str("XML"),
Self::Char1 => f.write_str("\"char\""),
Self::MoneyArray => f.write_str("MONEY[]"),
Self::IntArray2D => f.write_str("INT[][]"),
Self::BigIntArray2D => f.write_str("BIGINT[][]"),
Self::TextArray2D => f.write_str("TEXT[][]"),
Self::BoolArray2D => f.write_str("BOOL[][]"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DmlOrderLimit {
pub order_by: Vec<OrderBy>,
pub limit: Option<u32>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UpdateFromSources {
pub from: FromClause,
pub sub_where: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UpdateStatement {
pub ctes: Vec<Cte>,
pub table: String,
pub only: bool,
pub alias: Option<String>,
pub assignments: Vec<(String, Expr)>,
pub from_sources: Option<alloc::boxed::Box<UpdateFromSources>>,
pub where_: Option<Expr>,
pub order_limit: Option<alloc::boxed::Box<DmlOrderLimit>>,
pub returning: Option<Vec<SelectItem>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeleteStatement {
pub ctes: Vec<Cte>,
pub table: String,
pub only: bool,
pub alias: Option<String>,
pub where_: Option<Expr>,
pub order_limit: Option<alloc::boxed::Box<DmlOrderLimit>>,
pub returning: Option<Vec<SelectItem>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MergeStatement {
pub ctes: Vec<Cte>,
pub target: String,
pub target_alias: Option<String>,
pub source: String,
pub source_alias: Option<String>,
pub source_select: Option<Box<SelectStatement>>,
pub source_column_aliases: Vec<String>,
pub on: Expr,
pub clauses: Vec<MergeWhenClause>,
pub returning: Option<Vec<SelectItem>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MergeWhenClause {
pub matched: MergeMatched,
pub condition: Option<Expr>,
pub action: MergeAction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MergeMatched {
Matched,
NotMatched,
NotMatchedBySource,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MergeAction {
Insert {
columns: Vec<String>,
values: Vec<Expr>,
},
Update { assignments: Vec<(String, Expr)> },
Delete,
DoNothing,
}
#[derive(Debug, Clone, PartialEq)]
pub struct InsertStatement {
pub ctes: Vec<Cte>,
pub table: String,
pub alias: Option<String>,
pub columns: Option<Vec<String>>,
pub rows: Vec<Vec<Expr>>,
pub select_source: Option<Box<SelectStatement>>,
pub on_conflict: Option<OnConflictClause>,
pub returning: Option<Vec<SelectItem>>,
pub overriding: Overriding,
pub mysql_ignore: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Overriding {
#[default]
None,
System,
User,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OnConflictClause {
pub target_columns: Vec<String>,
pub index_where: Option<Expr>,
pub constraint_name: Option<String>,
pub mysql_lowered: bool,
pub action: OnConflictAction,
}
#[derive(Debug, Clone, PartialEq)]
pub enum OnConflictAction {
Nothing,
Update {
assignments: Vec<(String, Expr)>,
where_: Option<Expr>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LockingClause {
pub strength: LockStrength,
pub of_tables: Vec<String>,
pub policy: LockWait,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockStrength {
KeyShare,
Share,
NoKeyUpdate,
Update,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LockWait {
#[default]
Wait,
NoWait,
SkipLocked,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct SelectStatement {
pub locking: Option<alloc::boxed::Box<LockingClause>>,
pub ctes: Vec<Cte>,
pub distinct: bool,
pub distinct_on: Vec<Expr>,
pub items: Vec<SelectItem>,
pub from: Option<FromClause>,
pub where_: Option<Expr>,
pub group_by: Option<Vec<Expr>>,
pub group_by_all: bool,
pub having: Option<Expr>,
pub unions: Vec<(UnionKind, SelectStatement)>,
pub order_by: Vec<OrderBy>,
pub limit: Option<LimitExpr>,
pub offset: Option<LimitExpr>,
pub limit_with_ties: bool,
pub window_check_exprs: Vec<Expr>,
}
impl Expr {
pub fn for_each_subquery_mut<E>(
&mut self,
f: &mut impl FnMut(&mut SelectStatement) -> Result<(), E>,
) -> Result<(), E> {
let mut stack: Vec<&mut Self> = alloc::vec![self];
while let Some(e) = stack.pop() {
match e {
Self::Literal(_) | Self::Column(_) | Self::Placeholder(_) => {}
Self::NamedArg { expr, .. }
| Self::Variadic(expr)
| Self::Unary { expr, .. }
| Self::Cast { expr, .. }
| Self::FieldAccess { base: expr, .. }
| Self::IsNull { expr, .. }
| Self::BoolTest { expr, .. }
| Self::Extract { source: expr, .. } => stack.push(expr),
Self::Binary { lhs, rhs, .. } => {
stack.push(lhs);
stack.push(rhs);
}
Self::Like { expr, pattern, .. } => {
stack.push(expr);
stack.push(pattern);
}
Self::ArraySubscript { target, index } => {
stack.push(target);
stack.push(index);
}
Self::ArraySlice { target, lo, hi } => {
stack.push(target);
stack.extend(lo.iter_mut().chain(hi.iter_mut()).map(|b| &mut **b));
}
Self::AnyAll { expr, array, .. } => {
stack.push(expr);
stack.push(array);
}
Self::FunctionCall { args, .. } | Self::Array(args) => {
stack.extend(args.iter_mut());
}
Self::AggregateOrdered {
call,
order_by,
filter,
..
} => {
stack.push(call);
stack.extend(order_by.iter_mut().map(|o| &mut o.expr));
stack.extend(filter.iter_mut().map(|b| &mut **b));
}
Self::WindowFunction {
args,
partition_by,
order_by,
filter,
..
} => {
stack.extend(args.iter_mut().chain(partition_by.iter_mut()));
stack.extend(order_by.iter_mut().map(|(e, _, _)| e));
stack.extend(filter.iter_mut().map(|b| &mut **b));
}
Self::InList { expr, list, .. } => {
stack.push(expr);
stack.extend(list.iter_mut());
}
Self::Case {
operand,
branches,
else_branch,
} => {
stack.extend(
operand
.iter_mut()
.chain(else_branch.iter_mut())
.map(|b| &mut **b),
);
for (when, then) in branches.iter_mut() {
stack.push(when);
stack.push(then);
}
}
Self::ScalarSubquery(s) | Self::Exists { subquery: s, .. } => f(s)?,
Self::InSubquery { expr, subquery, .. } => {
stack.push(expr);
f(subquery)?;
}
Self::RowInSubquery { row, subquery, .. }
| Self::RowCmpSubquery { row, subquery, .. } => {
stack.extend(row.iter_mut());
f(subquery)?;
}
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LimitExpr {
Literal(u32),
Placeholder(u16),
Expr(alloc::boxed::Box<Expr>),
}
impl fmt::Display for LimitExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Literal(n) => write!(f, "{n}"),
Self::Placeholder(n) => write!(f, "${n}"),
Self::Expr(e) => write!(f, "({e})"),
}
}
}
impl LimitExpr {
#[must_use]
pub fn as_literal(&self) -> Option<u32> {
match self {
Self::Literal(n) => Some(*n),
Self::Placeholder(_) => None,
Self::Expr(_) => {
debug_assert!(
false,
"LimitExpr::Expr reached execution — resolve_limit_exprs \
missed a nesting site; treating it as `no limit` would \
return every row"
);
None
}
}
}
}
impl SelectStatement {
#[must_use]
pub fn limit_literal(&self) -> Option<u32> {
self.limit.as_ref().and_then(LimitExpr::as_literal)
}
#[must_use]
pub fn offset_literal(&self) -> Option<u32> {
self.offset.as_ref().and_then(LimitExpr::as_literal)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Cte {
pub name: String,
pub body: CteBody,
pub recursive: bool,
pub column_overrides: Vec<String>,
pub search: Option<SearchClause>,
pub cycle: Option<CycleClause>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SearchClause {
pub depth_first: bool,
pub by_columns: Vec<String>,
pub set_column: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CycleClause {
pub columns: Vec<String>,
pub mark_column: String,
pub mark_value: Option<Literal>,
pub default_value: Option<Literal>,
pub path_column: String,
}
#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq)]
pub enum CteBody {
Select(SelectStatement),
Insert(Box<InsertStatement>),
Update(Box<UpdateStatement>),
Delete(Box<DeleteStatement>),
Merge(Box<MergeStatement>),
}
impl CteBody {
#[must_use]
pub fn as_select(&self) -> Option<&SelectStatement> {
match self {
Self::Select(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_select_mut(&mut self) -> Option<&mut SelectStatement> {
match self {
Self::Select(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn is_modifying(&self) -> bool {
!matches!(self, Self::Select(_))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct OrderBy {
pub expr: Expr,
pub desc: bool,
pub nulls_first: Option<bool>,
pub collation: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnionKind {
Distinct,
All,
Intersect,
IntersectAll,
Except,
ExceptAll,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SelectItem {
Wildcard,
QualifiedWildcard(String),
Expr {
expr: Expr,
alias: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct TableRef {
pub name: String,
pub alias: Option<String>,
pub only: bool,
pub as_of_segment: Option<u32>,
pub unnest_expr: Option<Box<Expr>>,
pub unnest_column_aliases: Vec<String>,
pub with_ordinality: bool,
pub generate_series_args: Option<Vec<Expr>>,
pub lateral_subquery: Option<Box<SelectStatement>>,
pub jsonb_each_text_arg: Option<(String, Box<Expr>)>,
pub table_fn_call: Option<Box<(String, Vec<Expr>)>>,
pub scalar_fn_item: bool,
pub rows_from: Option<Vec<(String, Vec<Expr>)>>,
pub json_table: Option<Box<JsonTable>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct JsonTable {
pub doc: Box<Expr>,
pub row_path: String,
pub columns: Vec<JsonTableColumn>,
pub passing: Vec<(String, Expr)>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum JsonTableColumn {
Ordinality { name: String },
Regular {
name: String,
ty: ColumnTypeName,
path: String,
exists: bool,
format_json: bool,
wrapper: bool,
on_empty: JsonTableOnBehavior,
on_error: JsonTableOnBehavior,
},
Nested {
path: String,
columns: Vec<JsonTableColumn>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum JsonTableOnBehavior {
Null,
Error,
Default(Box<Expr>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct FromClause {
pub primary: TableRef,
pub joins: Vec<FromJoin>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FromJoin {
pub kind: JoinKind,
pub table: TableRef,
pub on: Option<Expr>,
pub using_cols: Option<Vec<String>>,
pub natural: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JoinKind {
Inner,
Left,
Cross,
Right,
FullOuter,
Semi,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Literal(Literal),
Column(ColumnName),
NamedArg {
name: String,
expr: Box<Expr>,
},
Variadic(Box<Expr>),
Placeholder(u16),
Binary {
lhs: Box<Expr>,
op: BinOp,
rhs: Box<Expr>,
},
Unary {
op: UnOp,
expr: Box<Expr>,
},
Cast {
expr: Box<Expr>,
target: CastTarget,
},
FieldAccess {
base: Box<Expr>,
field: String,
},
IsNull {
expr: Box<Expr>,
negated: bool,
},
BoolTest {
expr: Box<Expr>,
value: Option<bool>,
negated: bool,
},
FunctionCall {
name: String,
args: Vec<Expr>,
},
AggregateOrdered {
call: Box<Expr>,
order_by: Vec<OrderBy>,
distinct: bool,
filter: Option<Box<Expr>>,
},
Like {
expr: Box<Expr>,
pattern: Box<Expr>,
negated: bool,
case_insensitive: bool,
},
WindowFunction {
name: String,
args: Vec<Expr>,
partition_by: Vec<Expr>,
order_by: Vec<(
Expr,
bool, /* desc */
Option<bool>, /* nulls_first */
)>,
frame: Option<WindowFrame>,
null_treatment: NullTreatment,
filter: Option<Box<Expr>>,
},
ScalarSubquery(Box<SelectStatement>),
Exists {
subquery: Box<SelectStatement>,
negated: bool,
},
InSubquery {
expr: Box<Expr>,
subquery: Box<SelectStatement>,
negated: bool,
},
RowInSubquery {
row: Vec<Expr>,
subquery: Box<SelectStatement>,
negated: bool,
},
RowCmpSubquery {
row: Vec<Expr>,
op: BinOp,
subquery: Box<SelectStatement>,
},
InList {
expr: Box<Expr>,
list: Vec<Expr>,
negated: bool,
},
Extract {
field: ExtractField,
source: Box<Expr>,
},
Array(Vec<Expr>),
ArraySubscript {
target: Box<Expr>,
index: Box<Expr>,
},
ArraySlice {
target: Box<Expr>,
lo: Option<Box<Expr>>,
hi: Option<Box<Expr>>,
},
AnyAll {
expr: Box<Expr>,
op: BinOp,
array: Box<Expr>,
is_any: bool,
},
Case {
operand: Option<Box<Expr>>,
branches: Vec<(Expr, Expr)>,
else_branch: Option<Box<Expr>>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NullTreatment {
#[default]
Respect,
Ignore,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WindowFrame {
pub kind: FrameKind,
pub start: FrameBound,
pub end: Option<FrameBound>,
pub exclude: FrameExclusion,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FrameExclusion {
#[default]
NoOthers,
CurrentRow,
Group,
Ties,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameKind {
Rows,
Range,
Groups,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrameBound {
UnboundedPreceding,
OffsetPreceding(u64),
CurrentRow,
OffsetFollowing(u64),
UnboundedFollowing,
IntervalPreceding {
months: i32,
days: i32,
micros: i64,
},
IntervalFollowing {
months: i32,
days: i32,
micros: i64,
},
}
impl fmt::Display for FrameBound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnboundedPreceding => f.write_str("UNBOUNDED PRECEDING"),
Self::OffsetPreceding(n) => write!(f, "{n} PRECEDING"),
Self::CurrentRow => f.write_str("CURRENT ROW"),
Self::OffsetFollowing(n) => write!(f, "{n} FOLLOWING"),
Self::UnboundedFollowing => f.write_str("UNBOUNDED FOLLOWING"),
Self::IntervalPreceding { .. } => f.write_str("INTERVAL PRECEDING"),
Self::IntervalFollowing { .. } => f.write_str("INTERVAL FOLLOWING"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtractField {
Year,
Month,
Day,
Hour,
Minute,
Second,
Microsecond,
Epoch,
Dow,
Isodow,
Doy,
Week,
Isoyear,
Quarter,
Decade,
Century,
Millennium,
Julian,
Millisecond,
Timezone,
TimezoneHour,
TimezoneMinute,
Other(String),
}
impl fmt::Display for ExtractField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Year => "YEAR",
Self::Month => "MONTH",
Self::Day => "DAY",
Self::Hour => "HOUR",
Self::Minute => "MINUTE",
Self::Second => "SECOND",
Self::Microsecond => "MICROSECOND",
Self::Epoch => "EPOCH",
Self::Dow => "DOW",
Self::Isodow => "ISODOW",
Self::Doy => "DOY",
Self::Week => "WEEK",
Self::Isoyear => "ISOYEAR",
Self::Quarter => "QUARTER",
Self::Decade => "DECADE",
Self::Century => "CENTURY",
Self::Millennium => "MILLENNIUM",
Self::Julian => "JULIAN",
Self::Millisecond => "MILLISECOND",
Self::Timezone => "TIMEZONE",
Self::TimezoneHour => "TIMEZONE_HOUR",
Self::TimezoneMinute => "TIMEZONE_MINUTE",
Self::Other(name) => return f.write_str(name),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CastTarget {
Int,
BigInt,
Float,
Text,
Bool,
Vector,
Date,
Timestamp,
Interval,
Timestamptz,
Json,
Jsonb,
RegType,
RegClass,
TextArray,
IntArray,
BigIntArray,
TsVector,
TsQuery,
Uuid,
Bytea,
Named(String),
}
impl fmt::Display for CastTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Int => "int",
Self::BigInt => "bigint",
Self::Float => "float",
Self::Text => "text",
Self::Bool => "bool",
Self::Vector => "vector",
Self::Interval => "interval",
Self::Timestamptz => "timestamptz",
Self::Json => "json",
Self::Jsonb => "jsonb",
Self::RegType => "regtype",
Self::RegClass => "regclass",
Self::Date => "date",
Self::Timestamp => "timestamp",
Self::TextArray => "TEXT[]",
Self::IntArray => "INT[]",
Self::BigIntArray => "BIGINT[]",
Self::TsVector => "tsvector",
Self::TsQuery => "tsquery",
Self::Uuid => "uuid",
Self::Bytea => "bytea",
Self::Named(name) => return f.write_str(name),
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
Integer(i64),
Float(f64),
Numeric {
unscaled: i128,
scale: u16,
},
NumericBig(String),
String(String),
Bool(bool),
Null,
Vector(Vec<f32>),
TextArray(Vec<Option<String>>),
IntArray(Vec<Option<i32>>),
BigIntArray(Vec<Option<i64>>),
Interval {
months: i32,
days: i32,
micros: i64,
text: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColumnName {
pub qualifier: Option<String>,
pub name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
Or,
And,
Eq,
NotEq,
IsDistinctFrom,
IsNotDistinctFrom,
IntDiv,
Lt,
LtEq,
Gt,
GtEq,
Add,
Sub,
Mul,
Div,
Mod,
L2Distance,
GeomParallel,
OverLeft,
OverRight,
GeomPerp,
GeomSameAs,
ClosestPoint,
GeomHoriz,
InnerProduct,
CosineDistance,
Concat,
BitOr,
BitAnd,
BitXor,
LogicalXor,
JsonGet,
JsonGetText,
JsonGetPath,
JsonGetPathText,
JsonContains,
JsonPathExists,
JsonContainedBy,
JsonKeyExists,
JsonKeysAny,
JsonKeysAll,
JsonDeletePath,
TsMatch,
InetContainedBy,
InetContainedByEq,
InetContains,
InetContainsEq,
InetOverlap,
Intersects,
IsBelow,
IsAbove,
PatternLt,
PatternLtEq,
PatternGt,
PatternGtEq,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnOp {
Not,
Neg,
BitNot,
Plus,
}
impl Statement {
#[must_use]
pub fn mysql_implicit_commit(&self) -> bool {
match self {
Self::CreateTable(c) => !c.temporary,
Self::Begin { .. }
| Self::DropTable { .. }
| Self::DropIndex { .. }
| Self::CreateIndex(_)
| Self::AlterIndex { .. }
| Self::AlterTable(_)
| Self::Truncate { .. }
| Self::Analyze { .. }
| Self::CreateStatistics { .. }
| Self::DropStatistics { .. }
| Self::CreateView { .. }
| Self::DropView { .. }
| Self::CreateMaterializedView { .. }
| Self::RefreshMaterializedView { .. }
| Self::DropMaterializedView { .. }
| Self::CreateSequence(_)
| Self::AlterSequence { .. }
| Self::DropSequence { .. }
| Self::CreateFunction(_)
| Self::DropFunction { .. }
| Self::CreateTrigger(_)
| Self::DropTrigger { .. }
| Self::CreateRule(_)
| Self::DropRule { .. }
| Self::CreateType(_)
| Self::DropType { .. }
| Self::AlterTypeAddValue { .. }
| Self::AlterTypeRenameValue { .. }
| Self::CreateDomain(_)
| Self::AlterDomain { .. }
| Self::DropDomain { .. }
| Self::CreateSchema { .. }
| Self::DropSchema { .. }
| Self::CreateUser { .. }
| Self::DropUser { .. }
| Self::Grant { .. }
| Self::Revoke { .. }
| Self::CreatePolicy(_)
| Self::AlterPolicy(_)
| Self::DropPolicy { .. }
| Self::CommentOn { .. }
| Self::CreateExtension { .. } => true,
_ => false,
}
}
#[must_use]
pub fn is_readonly(&self) -> bool {
match self {
Statement::SetConstraints { .. } => false,
Statement::AlterSystem { .. } => false,
Statement::NoOpPreventedInTransaction { .. } => false,
Statement::DropDatabase { .. } => false,
Statement::ValidateOnly { .. } => false,
Statement::AlterRolePassword { .. } => true,
Statement::DropAggregate { .. } => false,
Statement::SetDbRoleSetting(_) => false,
Statement::Maintain { .. } => false,
Statement::Prepare { .. }
| Statement::Execute { .. }
| Statement::Deallocate(_)
| Statement::Call(_)
| Statement::PrepareTransaction(_)
| Statement::CreateStatistics { .. }
| Statement::DropStatistics { .. }
| Statement::Kill { .. }
| Statement::Discard(_) => false,
Statement::Select(s) if s.locking.is_some() => false,
Statement::Select(_)
| Statement::CopyTo { .. }
| Statement::CopyToFile { .. }
| Statement::Explain(_)
| Statement::ShowTables
| Statement::ShowDatabases
| Statement::ShowCreateTable(_)
| Statement::ShowIndexes(_)
| Statement::ShowStatus
| Statement::ShowVariables
| Statement::ShowProcesslist
| Statement::ShowColumns(_)
| Statement::ShowUsers
| Statement::ShowPublications
| Statement::ShowSubscriptions
| Statement::WaitForWalPosition { .. } => true,
Statement::Empty
| Statement::Vacuum { .. }
| Statement::DropTable { .. }
| Statement::DropIndex { .. }
| Statement::CreateTable(_)
| Statement::CreateExtension(_)
| Statement::DoBlock(_)
| Statement::CreateIndex(_)
| Statement::Insert(_)
| Statement::Update(_)
| Statement::Delete(_)
| Statement::Merge(_)
| Statement::Begin(_)
| Statement::Commit
| Statement::Rollback
| Statement::Savepoint(_)
| Statement::RollbackToSavepoint(_)
| Statement::ReleaseSavepoint(_)
| Statement::CreateUser(_)
| Statement::DropUser { .. }
| Statement::SetRole(_)
| Statement::Grant(_)
| Statement::Revoke(_)
| Statement::CreatePolicy(_)
| Statement::AlterPolicy(_)
| Statement::DropPolicy(_)
| Statement::AlterIndex(_)
| Statement::AlterTable(_)
| Statement::CreatePublication(_)
| Statement::DropPublication { .. }
| Statement::CreateSubscription(_)
| Statement::DropSubscription { .. }
| Statement::Analyze(_)
| Statement::Truncate { .. }
| Statement::CompactColdSegments
| Statement::SetParameter { .. }
| Statement::SetParameterList(_)
| Statement::SetUserVars(..)
| Statement::SetTransaction { .. }
| Statement::ShowParameter(_)
| Statement::ResetParameter(_)
| Statement::CreateFunction(_)
| Statement::CreateTrigger(_)
| Statement::DropTrigger { .. }
| Statement::CreateRule(_)
| Statement::DropRule { .. }
| Statement::DropFunction { .. }
| Statement::CreateSequence(_)
| Statement::AlterSequence(_)
| Statement::DropSequence { .. }
| Statement::CreateView(_)
| Statement::DropView { .. }
| Statement::CreateMaterializedView(_)
| Statement::RefreshMaterializedView { .. }
| Statement::DropMaterializedView { .. }
| Statement::CreateType(_)
| Statement::AlterTypeAddValue { .. }
| Statement::AlterTypeRenameValue { .. }
| Statement::CommentOn { .. }
| Statement::DropType { .. }
| Statement::CreateDomain(_)
| Statement::DropDomain { .. }
| Statement::CreateSchema { .. }
| Statement::DropSchema { .. }
| Statement::DeclareCursor { .. }
| Statement::FetchCursor { .. }
| Statement::MoveCursor { .. }
| Statement::CloseCursor { .. }
| Statement::Listen(_)
| Statement::Notify { .. }
| Statement::Unlisten(_)
| Statement::CopyFromFile { .. }
| Statement::AlterDomain { .. } => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GrantStatement {
pub privileges: Vec<GrantPriv>,
pub object: GrantObject,
pub grantees: Vec<String>,
pub grant_option: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GrantPriv {
pub word: String,
pub columns: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GrantObject {
Tables(Vec<String>),
Roles(Vec<String>),
Sequences(Vec<String>),
Schemas(Vec<String>),
Databases(Vec<String>),
Functions(Vec<(String, Option<Vec<String>>)>),
AllTablesInSchema,
Other(String),
}
impl GrantStatement {
fn render(&self, grant: bool) -> alloc::string::String {
use core::fmt::Write as _;
let mut s = alloc::string::String::new();
let privs = if self.privileges.is_empty() {
alloc::string::String::from("ALL")
} else {
let parts: Vec<_> = self
.privileges
.iter()
.map(|p| {
if p.columns.is_empty() {
p.word.clone()
} else {
let cols: Vec<_> = p.columns.iter().map(|c| quote_ident(c)).collect();
alloc::format!("{} ({})", p.word, cols.join(", "))
}
})
.collect();
parts.join(", ")
};
let obj = match &self.object {
GrantObject::Tables(t) => {
let names: Vec<_> = t.iter().map(|n| quote_ident(n)).collect();
alloc::format!("TABLE {}", names.join(", "))
}
GrantObject::Roles(r) => {
let names: Vec<_> = r.iter().map(|n| quote_ident(n)).collect();
names.join(", ")
}
GrantObject::Sequences(n) => {
let names: Vec<_> = n.iter().map(|x| quote_ident(x)).collect();
alloc::format!("SEQUENCE {}", names.join(", "))
}
GrantObject::Schemas(n) => {
let names: Vec<_> = n.iter().map(|x| quote_ident(x)).collect();
alloc::format!("SCHEMA {}", names.join(", "))
}
GrantObject::Databases(n) => {
let names: Vec<_> = n.iter().map(|x| quote_ident(x)).collect();
alloc::format!("DATABASE {}", names.join(", "))
}
GrantObject::Functions(n) => {
let names: Vec<_> = n
.iter()
.map(|(name, args)| match args {
Some(a) => alloc::format!("{}({})", quote_ident(name), a.join(", ")),
None => quote_ident(name),
})
.collect();
alloc::format!("FUNCTION {}", names.join(", "))
}
GrantObject::AllTablesInSchema => "ALL TABLES IN SCHEMA public".into(),
GrantObject::Other(k) => k.clone(),
};
let who: Vec<_> = self
.grantees
.iter()
.map(|g| {
if g.is_empty() {
"PUBLIC".into()
} else {
quote_ident(g)
}
})
.collect();
if let GrantObject::Roles(_) = &self.object {
let _ = if grant {
write!(s, "GRANT {obj} TO {}", who.join(", "))
} else {
write!(s, "REVOKE {obj} FROM {}", who.join(", "))
};
return s;
}
if grant {
let _ = write!(s, "GRANT {privs} ON {obj} TO {}", who.join(", "));
if self.grant_option {
s.push_str(" WITH GRANT OPTION");
}
} else {
s.push_str("REVOKE ");
if self.grant_option {
s.push_str("GRANT OPTION FOR ");
}
let _ = write!(s, "{privs} ON {obj} FROM {}", who.join(", "));
}
s
}
}
impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => Ok(()),
Self::DropAggregate { if_exists, items } => {
f.write_str("DROP AGGREGATE ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
for (i, (name, args)) in items.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
match args {
Some(a) => write!(f, "{name}({})", a.join(", "))?,
None => write!(f, "{name}(*)")?,
}
}
Ok(())
}
Self::AlterRolePassword { name, password } => {
write!(f, "ALTER ROLE {}", quote_ident(name))?;
match password {
Some(_) => f.write_str(" PASSWORD '<redacted>'"),
None => f.write_str(" PASSWORD NULL"),
}
}
Self::ValidateOnly { kind, names } => match kind {
ValidateOnlyKind::LockTable => write!(f, "LOCK TABLE {}", names.join(", ")),
ValidateOnlyKind::RoleName => {
write!(f, "DROP OWNED BY {}", names.join(", "))
}
ValidateOnlyKind::SecurityLabel => f.write_str("SECURITY LABEL"),
ValidateOnlyKind::ExtensionAvailable => {
write!(f, "CREATE EXTENSION {}", names.join(", "))
}
ValidateOnlyKind::ForeignInfra => f.write_str("CREATE SERVER"),
ValidateOnlyKind::CollationName => {
write!(f, "DROP COLLATION {}", names.join(", "))
}
ValidateOnlyKind::TsConfigName => {
write!(f, "DROP TEXT SEARCH CONFIGURATION {}", names.join(", "))
}
ValidateOnlyKind::EventTriggerName => {
write!(f, "DROP EVENT TRIGGER {}", names.join(", "))
}
ValidateOnlyKind::TablespaceName => {
write!(f, "DROP TABLESPACE {}", names.join(", "))
}
ValidateOnlyKind::LargeObjectOid => {
write!(f, "ALTER LARGE OBJECT {}", names.join(", "))
}
ValidateOnlyKind::TypeName => write!(f, "ALTER TYPE {}", names.join(", ")),
ValidateOnlyKind::AggregateName => {
write!(f, "ALTER AGGREGATE {}", names.join(", "))
}
ValidateOnlyKind::ConversionName => {
write!(f, "DROP CONVERSION {}", names.join(", "))
}
ValidateOnlyKind::LanguageName => {
write!(f, "DROP LANGUAGE {}", names.join(", "))
}
ValidateOnlyKind::ExtensionInstalled => {
write!(f, "DROP EXTENSION {}", names.join(", "))
}
},
Self::AlterSystem { parameter } => match parameter {
Some(p) => write!(f, "ALTER SYSTEM RESET {p}"),
None => f.write_str("ALTER SYSTEM RESET ALL"),
},
Self::SetDbRoleSetting(st) => {
match (&st.database, &st.role) {
(Some(d), None) => write!(f, "ALTER DATABASE {d}")?,
(_, Some(r)) => write!(f, "ALTER ROLE {r}")?,
(None, None) => f.write_str("ALTER ROLE ALL")?,
}
if let (Some(d), Some(_)) = (&st.database, &st.role) {
write!(f, " IN DATABASE {d}")?;
}
match (&st.param, &st.value) {
(None, _) => f.write_str(" RESET ALL"),
(Some(p), None) => write!(f, " RESET {p}"),
(Some(p), Some(v)) => write!(f, " SET {p} = '{v}'"),
}
}
Self::Maintain {
kind,
concurrently,
target,
} => {
f.write_str(match kind {
crate::ast::MaintainKind::ClusterRelation => "CLUSTER ",
_ => "REINDEX ",
})?;
if *concurrently {
f.write_str("CONCURRENTLY ")?;
}
if let Some(t) = target {
f.write_str(t)?;
}
Ok(())
}
Self::DropDatabase { name, if_exists } => {
f.write_str("DROP DATABASE ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
f.write_str(name)
}
Self::NoOpPreventedInTransaction { what } => f.write_str(what),
Self::SetConstraints { names, deferred } => {
f.write_str("SET CONSTRAINTS ")?;
if names.is_empty() {
f.write_str("ALL")?;
} else {
for (i, n) in names.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str(n)?;
}
}
f.write_str(if *deferred { " DEFERRED" } else { " IMMEDIATE" })
}
Self::Prepare { source, .. } => f.write_str(source),
Self::Execute { name, args } => {
write!(f, "EXECUTE {}", quote_ident(name))?;
if !args.is_empty() {
f.write_str("(")?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{a}")?;
}
f.write_str(")")?;
}
Ok(())
}
Self::CreateStatistics {
name,
if_not_exists,
kinds,
columns,
table,
} => {
f.write_str("CREATE STATISTICS ")?;
if *if_not_exists {
f.write_str("IF NOT EXISTS ")?;
}
write!(f, "{}", quote_ident(name))?;
if !kinds.is_empty() {
write!(f, " ({})", kinds.join(", "))?;
}
write!(f, " ON {} FROM {}", columns.join(", "), quote_ident(table))
}
Self::DropStatistics { name, if_exists } => {
f.write_str("DROP STATISTICS ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{}", quote_ident(name))
}
Self::Call(n) => write!(f, "CALL {}()", quote_ident(n)),
Self::PrepareTransaction(gid) => write!(f, "PREPARE TRANSACTION '{gid}'"),
Self::Deallocate(None) => f.write_str("DEALLOCATE ALL"),
Self::Deallocate(Some(n)) => write!(f, "DEALLOCATE {}", quote_ident(n)),
Self::DeclareCursor {
name,
scroll,
hold,
query,
} => {
write!(f, "DECLARE {} ", quote_ident(name))?;
match scroll {
Some(true) => f.write_str("SCROLL ")?,
Some(false) => f.write_str("NO SCROLL ")?,
None => {}
}
f.write_str("CURSOR ")?;
if *hold {
f.write_str("WITH HOLD ")?;
}
write!(f, "FOR {query}")
}
Self::FetchCursor { name, direction } => {
write!(f, "FETCH {direction} FROM {}", quote_ident(name))
}
Self::MoveCursor { name, direction } => {
write!(f, "MOVE {direction} FROM {}", quote_ident(name))
}
Self::CloseCursor { name } => match name {
Some(n) => write!(f, "CLOSE {}", quote_ident(n)),
None => f.write_str("CLOSE ALL"),
},
Self::Listen(ch) => write!(f, "LISTEN {}", quote_ident(ch)),
Self::Notify { channel, payload } => {
write!(f, "NOTIFY {}", quote_ident(channel))?;
if let Some(p) = payload {
write!(f, ", '{}'", p.replace('\'', "''"))?;
}
Ok(())
}
Self::Unlisten(ch) => match ch {
Some(c) => write!(f, "UNLISTEN {}", quote_ident(c)),
None => f.write_str("UNLISTEN *"),
},
Self::CopyTo {
table,
columns,
query,
options,
} => {
if let Some(q) = query {
write!(f, "COPY ({q})")?;
} else {
write!(f, "COPY {table}")?;
if let Some(cols) = columns {
write!(f, " ({})", cols.join(", "))?;
}
}
write!(f, " TO STDOUT")?;
let mut parts: Vec<String> = Vec::new();
if options.format == CopyFormat::Csv {
parts.push("FORMAT csv".to_string());
}
if options.header {
parts.push("HEADER true".to_string());
}
if let Some(d) = options.delimiter {
parts.push(alloc::format!("DELIMITER '{d}'"));
}
if let Some(n) = &options.null_str {
parts.push(alloc::format!("NULL '{n}'"));
}
if let Some(q) = options.quote {
parts.push(alloc::format!("QUOTE '{q}'"));
}
if !parts.is_empty() {
write!(f, " WITH ({})", parts.join(", "))?;
}
Ok(())
}
Self::CopyFromFile {
table,
columns,
path,
options,
} => {
write!(f, "COPY {table}")?;
if let Some(cols) = columns {
write!(f, " ({})", cols.join(", "))?;
}
write!(f, " FROM '{path}'")?;
let mut parts: Vec<String> = Vec::new();
if options.format == CopyFormat::Csv {
parts.push("FORMAT csv".to_string());
}
if options.header {
parts.push("HEADER true".to_string());
}
if let Some(d) = options.delimiter {
parts.push(alloc::format!("DELIMITER '{d}'"));
}
if let Some(n) = &options.null_str {
parts.push(alloc::format!("NULL '{n}'"));
}
if let Some(q) = options.quote {
parts.push(alloc::format!("QUOTE '{q}'"));
}
if !parts.is_empty() {
write!(f, " WITH ({})", parts.join(", "))?;
}
Ok(())
}
Self::CopyToFile {
table,
columns,
query,
path,
options,
} => {
if let Some(q) = query {
write!(f, "COPY ({q})")?;
} else {
write!(f, "COPY {table}")?;
if let Some(cols) = columns {
write!(f, " ({})", cols.join(", "))?;
}
}
write!(f, " TO '{path}'")?;
let mut parts: Vec<String> = Vec::new();
if options.format == CopyFormat::Csv {
parts.push("FORMAT csv".to_string());
}
if options.header {
parts.push("HEADER true".to_string());
}
if let Some(d) = options.delimiter {
parts.push(alloc::format!("DELIMITER '{d}'"));
}
if let Some(n) = &options.null_str {
parts.push(alloc::format!("NULL '{n}'"));
}
if let Some(q) = options.quote {
parts.push(alloc::format!("QUOTE '{q}'"));
}
if !parts.is_empty() {
write!(f, " WITH ({})", parts.join(", "))?;
}
Ok(())
}
Self::AlterDomain { name, action } => {
write!(f, "ALTER DOMAIN {name} ")?;
match action {
AlterDomainAction::AddConstraint { name: cn, check } => match cn {
Some(cn) => write!(f, "ADD CONSTRAINT {cn} CHECK ({check})"),
None => write!(f, "ADD CHECK ({check})"),
},
AlterDomainAction::DropConstraint {
name: cn,
if_exists,
} => {
if *if_exists {
write!(f, "DROP CONSTRAINT IF EXISTS {cn}")
} else {
write!(f, "DROP CONSTRAINT {cn}")
}
}
AlterDomainAction::SetDefault(e) => write!(f, "SET DEFAULT {e}"),
AlterDomainAction::DropDefault => f.write_str("DROP DEFAULT"),
AlterDomainAction::SetNotNull => f.write_str("SET NOT NULL"),
AlterDomainAction::DropNotNull => f.write_str("DROP NOT NULL"),
AlterDomainAction::RenameTo(n) => write!(f, "RENAME TO {n}"),
}
}
Self::Truncate {
tables,
restart_identity,
cascade,
only,
} => {
f.write_str("TRUNCATE TABLE ")?;
if *only {
f.write_str("ONLY ")?;
}
for (i, t) in tables.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str(t)?;
}
if *restart_identity {
f.write_str(" RESTART IDENTITY")?;
}
if *cascade {
f.write_str(" CASCADE")?;
}
Ok(())
}
Self::DropTable { names, if_exists } => {
f.write_str("DROP TABLE ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
for (i, n) in names.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(n))?;
}
Ok(())
}
Self::DropIndex { name, if_exists } => {
f.write_str("DROP INDEX ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{}", quote_ident(name))
}
Self::Select(s) => s.fmt(f),
Self::CreateTable(s) => s.fmt(f),
Self::CreateIndex(s) => s.fmt(f),
Self::Insert(s) => s.fmt(f),
Self::Update(s) => s.fmt(f),
Self::Delete(s) => s.fmt(f),
Self::Merge(s) => s.fmt(f),
Self::Vacuum { table, analyze } => {
f.write_str("VACUUM")?;
if *analyze {
f.write_str(" ANALYZE")?;
}
if let Some(t) = table {
write!(f, " {}", quote_ident(t))?;
}
Ok(())
}
Self::Begin(None) => f.write_str("BEGIN"),
Self::Begin(Some(level)) => write!(f, "BEGIN ISOLATION LEVEL {level}"),
Self::Commit => f.write_str("COMMIT"),
Self::Rollback => f.write_str("ROLLBACK"),
Self::Savepoint(n) => write!(f, "SAVEPOINT {}", quote_ident(n)),
Self::RollbackToSavepoint(n) => write!(f, "ROLLBACK TO SAVEPOINT {}", quote_ident(n)),
Self::ReleaseSavepoint(n) => write!(f, "RELEASE SAVEPOINT {}", quote_ident(n)),
Self::ShowTables => f.write_str("SHOW TABLES"),
Self::ShowDatabases => f.write_str("SHOW DATABASES"),
Self::ShowCreateTable(t) => write!(f, "SHOW CREATE TABLE {}", quote_ident(t)),
Self::ShowIndexes(t) => write!(f, "SHOW INDEXES FROM {}", quote_ident(t)),
Self::ShowStatus => f.write_str("SHOW STATUS"),
Self::ShowVariables => f.write_str("SHOW VARIABLES"),
Self::ShowProcesslist => f.write_str("SHOW PROCESSLIST"),
Self::Discard(t) => write!(f, "DISCARD {t}"),
Self::Kill { query_only, id } => {
if *query_only {
write!(f, "KILL QUERY {id}")
} else {
write!(f, "KILL CONNECTION {id}")
}
}
Self::ShowColumns(t) => write!(f, "SHOW COLUMNS FROM {}", quote_ident(t)),
Self::CreateUser(s) => write!(
f,
"CREATE USER {} WITH PASSWORD '<redacted>' ROLE '{}'",
quote_ident(&s.name),
s.role
),
Self::DropUser { name, if_exists } => {
let ie = if *if_exists { "IF EXISTS " } else { "" };
write!(f, "DROP USER {ie}{}", quote_ident(name))
}
Self::SetRole(Some(r)) => write!(f, "SET ROLE {}", quote_ident(r)),
Self::SetRole(None) => f.write_str("RESET ROLE"),
Self::Grant(g) => write!(f, "{}", g.render(true)),
Self::Revoke(g) => write!(f, "{}", g.render(false)),
Self::CreatePolicy(s) => {
write!(
f,
"CREATE POLICY {} ON {}",
quote_ident(&s.name),
quote_ident(&s.table)
)?;
if !s.permissive {
f.write_str(" AS RESTRICTIVE")?;
}
if !matches!(s.cmd, PolicyCmd::All) {
let w = match s.cmd {
PolicyCmd::Select => "SELECT",
PolicyCmd::Insert => "INSERT",
PolicyCmd::Update => "UPDATE",
PolicyCmd::Delete => "DELETE",
PolicyCmd::All => unreachable!(),
};
write!(f, " FOR {w}")?;
}
if !s.roles.is_empty() {
write!(f, " TO {}", s.roles.join(", "))?;
}
if let Some(u) = &s.using {
write!(f, " USING ({u})")?;
}
if let Some(c) = &s.with_check {
write!(f, " WITH CHECK ({c})")?;
}
Ok(())
}
Self::AlterPolicy(s) => {
write!(
f,
"ALTER POLICY {} ON {}",
quote_ident(&s.name),
quote_ident(&s.table)
)?;
if let Some(nn) = &s.rename_to {
return write!(f, " RENAME TO {}", quote_ident(nn));
}
if let Some(roles) = &s.roles {
write!(f, " TO {}", roles.join(", "))?;
}
if let Some(u) = &s.using {
write!(f, " USING ({u})")?;
}
if let Some(c) = &s.with_check {
write!(f, " WITH CHECK ({c})")?;
}
Ok(())
}
Self::DropPolicy(s) => {
f.write_str("DROP POLICY ")?;
if s.if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{} ON {}", quote_ident(&s.name), quote_ident(&s.table))
}
Self::ShowUsers => f.write_str("SHOW USERS"),
Self::ShowPublications => f.write_str("SHOW PUBLICATIONS"),
Self::ShowSubscriptions => f.write_str("SHOW SUBSCRIPTIONS"),
Self::CreateSubscription(s) => {
write!(
f,
"CREATE SUBSCRIPTION {} CONNECTION '{}' PUBLICATION ",
quote_ident(&s.name),
s.conn_str.replace('\'', "''")
)?;
for (i, p) in s.publications.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(p))?;
}
Ok(())
}
Self::DropSubscription { name, if_exists } => {
let opt = if *if_exists { "IF EXISTS " } else { "" };
write!(f, "DROP SUBSCRIPTION {opt}{}", quote_ident(name))
}
Self::WaitForWalPosition { pos, timeout_ms } => {
write!(f, "WAIT FOR WAL POSITION {pos}")?;
if let Some(ms) = timeout_ms {
write!(f, " WITH TIMEOUT {ms}")?;
}
Ok(())
}
Self::Analyze(None) => f.write_str("ANALYZE"),
Self::Analyze(Some(t)) => write!(f, "ANALYZE {}", quote_ident(t)),
Self::CompactColdSegments => f.write_str("COMPACT COLD SEGMENTS"),
Self::Explain(e) => {
if e.suggest {
write!(f, "EXPLAIN (SUGGEST) {}", e.inner)
} else if e.analyze {
write!(f, "EXPLAIN ANALYZE {}", e.inner)
} else {
write!(f, "EXPLAIN {}", e.inner)
}
}
Self::AlterIndex(a) => {
write!(f, "ALTER INDEX ")?;
match &a.target {
AlterIndexTarget::StorageParams => {
write!(f, "{} SET ()", quote_ident(&a.name))
}
AlterIndexTarget::Rebuild { encoding } => {
write!(f, "{} REBUILD", quote_ident(&a.name))?;
if let Some(enc) = encoding {
write!(f, " WITH (encoding = {enc})")?;
}
Ok(())
}
AlterIndexTarget::Rename { new, if_exists } => {
if *if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{} RENAME TO {}", quote_ident(&a.name), quote_ident(new))
}
}
}
Self::AlterTable(a) => {
write!(f, "ALTER TABLE {} ", quote_ident(&a.name))?;
for (i, t) in a.targets.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
fmt_alter_target(f, t)?;
}
Ok(())
}
Self::CreatePublication(p) => {
write!(f, "CREATE PUBLICATION {}", quote_ident(&p.name))?;
match &p.scope {
PublicationScope::AllTables => f.write_str(" FOR ALL TABLES"),
PublicationScope::ForTables(ts) => {
f.write_str(" FOR TABLE ")?;
for (i, t) in ts.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(t))?;
}
Ok(())
}
PublicationScope::TablesInSchema(schema) => {
write!(f, " FOR TABLES IN SCHEMA {}", quote_ident(schema))?;
Ok(())
}
PublicationScope::AllTablesExcept(ts) => {
f.write_str(" FOR ALL TABLES EXCEPT ")?;
for (i, t) in ts.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(t))?;
}
Ok(())
}
}
}
Self::CreateExtension(name) => {
write!(f, "CREATE EXTENSION IF NOT EXISTS {}", quote_ident(name))
}
Self::DoBlock(body) => write!(f, "DO $$ {body} $$"),
Self::DropPublication { name, if_exists } => {
let opt = if *if_exists { "IF EXISTS " } else { "" };
write!(f, "DROP PUBLICATION {opt}{}", quote_ident(name))
}
Self::SetParameter { name, value, local } => {
write!(f, "SET {}{name} = ", if *local { "LOCAL " } else { "" })?;
match value {
SetValue::String(s) => write!(f, "'{}'", s.replace('\'', "''")),
SetValue::Ident(s) | SetValue::Number(s) => f.write_str(s),
SetValue::Default => f.write_str("DEFAULT"),
}
}
Self::SetTransaction { isolation } => {
write!(f, "SET TRANSACTION ISOLATION LEVEL ")?;
let name = match isolation {
IsolationLevel::ReadUncommitted => "READ UNCOMMITTED",
IsolationLevel::ReadCommitted => "READ COMMITTED",
IsolationLevel::RepeatableRead => "REPEATABLE READ",
IsolationLevel::Serializable => "SERIALIZABLE",
};
f.write_str(name)
}
Self::ShowParameter(name) => write!(f, "SHOW {name}"),
Self::SetUserVars(assigns, _) => {
f.write_str("SET ")?;
for (i, (name, value)) in assigns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "@{name} = {value}")?;
}
Ok(())
}
Self::SetParameterList(pairs) => {
f.write_str("SET ")?;
for (i, (name, value)) in pairs.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{name} = ")?;
match value {
SetValue::String(s) => write!(f, "'{}'", s.replace('\'', "''"))?,
SetValue::Ident(s) | SetValue::Number(s) => f.write_str(s)?,
SetValue::Default => f.write_str("DEFAULT")?,
}
}
Ok(())
}
Self::ResetParameter(None) => f.write_str("RESET ALL"),
Self::ResetParameter(Some(name)) => write!(f, "RESET {name}"),
Self::CreateFunction(s) => s.fmt(f),
Self::CreateTrigger(s) => s.fmt(f),
Self::DropTrigger {
name,
table,
if_exists,
} => {
f.write_str("DROP TRIGGER ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{} ON {}", quote_ident(name), quote_ident(table))
}
Self::DropFunction {
name,
args,
if_exists,
} => {
f.write_str("DROP FUNCTION ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{}", quote_ident(name))?;
if let Some(a) = args {
write!(f, "({})", a.join(", "))?;
}
Ok(())
}
Self::CreateSequence(s) => s.fmt(f),
Self::AlterSequence(s) => s.fmt(f),
Self::DropSequence { names, if_exists } => {
f.write_str("DROP SEQUENCE ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
for (i, n) in names.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(n))?;
}
Ok(())
}
Self::CreateView(v) => v.fmt(f),
Self::DropView { names, if_exists } => {
f.write_str("DROP VIEW ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
for (i, n) in names.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(n))?;
}
Ok(())
}
Self::CreateMaterializedView(v) => v.fmt(f),
Self::RefreshMaterializedView { name, with_data } => {
write!(f, "REFRESH MATERIALIZED VIEW {}", quote_ident(name))?;
if !*with_data {
f.write_str(" WITH NO DATA")?;
}
Ok(())
}
Self::DropMaterializedView { names, if_exists } => {
f.write_str("DROP MATERIALIZED VIEW ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
for (i, n) in names.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(n))?;
}
Ok(())
}
Self::CreateType(t) => t.fmt(f),
Self::CommentOn {
kind,
name,
comment,
} => {
let body = match comment {
Some(c) => alloc::format!("'{}'", c.replace('\'', "''")),
None => "NULL".into(),
};
write!(f, "COMMENT ON {} {name} IS {body}", kind.to_uppercase())
}
Self::AlterTypeRenameValue {
type_name,
old,
new,
} => write!(
f,
"ALTER TYPE {} RENAME VALUE '{}' TO '{}'",
quote_ident(type_name),
old.replace('\'', "''"),
new.replace('\'', "''")
),
Self::AlterTypeAddValue {
type_name,
label,
if_not_exists,
position,
} => {
write!(f, "ALTER TYPE {type_name} ADD VALUE ")?;
if *if_not_exists {
write!(f, "IF NOT EXISTS ")?;
}
write!(f, "'{label}'")?;
if let Some((is_before, anchor)) = position {
write!(
f,
" {} '{anchor}'",
if *is_before { "BEFORE" } else { "AFTER" }
)?;
}
Ok(())
}
Self::DropType { names, if_exists } => {
f.write_str("DROP TYPE ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
for (i, n) in names.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(n))?;
}
Ok(())
}
Self::CreateDomain(d) => d.fmt(f),
Self::DropDomain { names, if_exists } => {
f.write_str("DROP DOMAIN ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
for (i, n) in names.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(n))?;
}
Ok(())
}
Self::CreateSchema {
name,
if_not_exists,
} => {
f.write_str("CREATE SCHEMA ")?;
if *if_not_exists {
f.write_str("IF NOT EXISTS ")?;
}
write!(f, "{}", quote_ident(name))
}
Self::DropSchema { names, if_exists } => {
f.write_str("DROP SCHEMA ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
for (i, n) in names.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(n))?;
}
Ok(())
}
Self::CreateRule(r) => {
f.write_str("CREATE ")?;
if r.or_replace {
f.write_str("OR REPLACE ")?;
}
write!(
f,
"RULE {} AS ON {} TO {}",
quote_ident(&r.name),
r.event,
quote_ident(&r.table)
)?;
if let Some(w) = &r.when_condition {
write!(f, " WHERE {w}")?;
}
f.write_str(if r.instead {
" DO INSTEAD "
} else {
" DO ALSO "
})?;
if r.commands.is_empty() {
f.write_str("NOTHING")?;
} else if r.commands.len() == 1 {
write!(f, "{}", r.commands[0])?;
} else {
f.write_str("(")?;
for (i, c) in r.commands.iter().enumerate() {
if i > 0 {
f.write_str("; ")?;
}
write!(f, "{c}")?;
}
f.write_str(")")?;
}
Ok(())
}
Self::DropRule {
name,
table,
if_exists,
} => {
f.write_str("DROP RULE ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{} ON {}", quote_ident(name), quote_ident(table))
}
}
}
}
impl fmt::Display for CreateDomainStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"CREATE DOMAIN {} AS {}",
quote_ident(&self.name),
self.base_type
)?;
if let Some(d) = &self.default {
write!(f, " DEFAULT {d}")?;
}
if self.not_null {
f.write_str(" NOT NULL")?;
}
for c in &self.checks {
write!(f, " CHECK ({c})")?;
}
Ok(())
}
}
impl fmt::Display for CreateTypeStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CREATE TYPE {} AS ", quote_ident(&self.name))?;
match &self.kind {
TypeKind::Enum { labels } => {
f.write_str("ENUM (")?;
for (i, l) in labels.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "'{}'", l.replace('\'', "''"))?;
}
f.write_str(")")
}
TypeKind::Composite { fields, .. } => {
f.write_str("(")?;
for (i, (n, t)) in fields.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{} {}", quote_ident(n), t)?;
}
f.write_str(")")
}
}
}
}
impl fmt::Display for CreateMaterializedViewStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CREATE MATERIALIZED VIEW ")?;
if self.if_not_exists {
f.write_str("IF NOT EXISTS ")?;
}
write!(f, "{}", quote_ident(&self.name))?;
if !self.columns.is_empty() {
f.write_str(" (")?;
for (i, c) in self.columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(c))?;
}
f.write_str(")")?;
}
write!(f, " AS {}", self.body)?;
if !self.with_data {
f.write_str(" WITH NO DATA")?;
}
Ok(())
}
}
impl fmt::Display for CreateViewStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CREATE ")?;
if self.or_replace {
f.write_str("OR REPLACE ")?;
}
if self.temporary {
f.write_str("TEMPORARY ")?;
}
f.write_str("VIEW ")?;
if self.if_not_exists {
f.write_str("IF NOT EXISTS ")?;
}
write!(f, "{}", quote_ident(&self.name))?;
if !self.columns.is_empty() {
f.write_str(" (")?;
for (i, c) in self.columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(c))?;
}
f.write_str(")")?;
}
write!(f, " AS {}", self.body)?;
match self.check_option {
Some(ViewCheckOption::Local) => f.write_str(" WITH LOCAL CHECK OPTION"),
Some(ViewCheckOption::Cascaded) => f.write_str(" WITH CASCADED CHECK OPTION"),
None => Ok(()),
}
}
}
impl fmt::Display for CreateSequenceStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CREATE ")?;
if self.temporary {
f.write_str("TEMPORARY ")?;
}
f.write_str("SEQUENCE ")?;
if self.if_not_exists {
f.write_str("IF NOT EXISTS ")?;
}
write!(f, "{}", quote_ident(&self.name))?;
if let Some(dt) = self.data_type {
write!(f, " AS {dt}")?;
}
write_sequence_options(f, &self.options)
}
}
impl fmt::Display for AlterSequenceStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ALTER SEQUENCE ")?;
if self.if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{}", quote_ident(&self.name))?;
write_sequence_options(f, &self.options)
}
}
impl fmt::Display for SequenceDataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::SmallInt => "smallint",
Self::Int => "integer",
Self::BigInt => "bigint",
})
}
}
fn write_sequence_options(f: &mut fmt::Formatter<'_>, o: &SequenceOptions) -> fmt::Result {
if let Some(n) = o.increment {
write!(f, " INCREMENT BY {n}")?;
}
match o.min_value {
Some(SeqBound::Value(n)) => write!(f, " MINVALUE {n}")?,
Some(SeqBound::NoBound) => f.write_str(" NO MINVALUE")?,
None => {}
}
match o.max_value {
Some(SeqBound::Value(n)) => write!(f, " MAXVALUE {n}")?,
Some(SeqBound::NoBound) => f.write_str(" NO MAXVALUE")?,
None => {}
}
if let Some(n) = o.start {
write!(f, " START WITH {n}")?;
}
match o.restart {
Some(Some(n)) => write!(f, " RESTART WITH {n}")?,
Some(None) => f.write_str(" RESTART")?,
None => {}
}
if let Some(n) = o.cache {
write!(f, " CACHE {n}")?;
}
match o.cycle {
Some(true) => f.write_str(" CYCLE")?,
Some(false) => f.write_str(" NO CYCLE")?,
None => {}
}
if let Some(ob) = &o.owned_by {
match ob {
SequenceOwnedBy::None => f.write_str(" OWNED BY NONE")?,
SequenceOwnedBy::Column { table, column } => {
write!(
f,
" OWNED BY {}.{}",
quote_ident(table),
quote_ident(column)
)?;
}
}
}
Ok(())
}
impl fmt::Display for CreateFunctionStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CREATE ")?;
if self.or_replace {
f.write_str("OR REPLACE ")?;
}
write!(f, "FUNCTION {}(", quote_ident(&self.name))?;
for (i, arg) in self.args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
match arg.mode {
FunctionArgMode::In => {}
FunctionArgMode::Out => f.write_str("OUT ")?,
FunctionArgMode::InOut => f.write_str("INOUT ")?,
}
if let Some(name) = &arg.name {
write!(f, "{} ", quote_ident(name))?;
}
match &arg.ty {
FunctionArgType::Typed(t) => write!(f, "{t}")?,
FunctionArgType::Raw(s) => f.write_str(s)?,
}
}
f.write_str(") RETURNS ")?;
match &self.returns {
FunctionReturn::Trigger => f.write_str("TRIGGER")?,
FunctionReturn::Void => f.write_str("VOID")?,
FunctionReturn::Type(t) => write!(f, "{t}")?,
FunctionReturn::Other(s) => f.write_str(s)?,
}
write!(f, " LANGUAGE {} AS $$", self.language)?;
match &self.body {
FunctionBody::PlPgSql(b) => write!(f, "\n{b}\n")?,
FunctionBody::Raw(s) => f.write_str(s)?,
}
f.write_str("$$")
}
}
impl fmt::Display for PlPgSqlBlock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.declarations.is_empty() {
f.write_str("DECLARE\n")?;
for d in &self.declarations {
write!(f, " {} ", quote_ident(&d.name))?;
match &d.ty {
FunctionArgType::Typed(t) => write!(f, "{t}")?,
FunctionArgType::Raw(s) => f.write_str(s)?,
}
if let Some(e) = &d.default {
write!(f, " := {e}")?;
}
f.write_str(";\n")?;
}
}
f.write_str("BEGIN\n")?;
for stmt in &self.statements {
writeln!(f, " {stmt};")?;
}
if !self.exception_handlers.is_empty() {
f.write_str("EXCEPTION\n")?;
for h in &self.exception_handlers {
writeln!(f, " WHEN {} THEN", h.conditions.join(" OR "))?;
for stmt in &h.body {
writeln!(f, " {stmt};")?;
}
}
}
f.write_str("END")
}
}
impl fmt::Display for PlPgSqlStmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Assign { target, value } => write!(f, "{target} := {value}"),
Self::SelectInto { var, body } => write!(f, "{body} INTO {var}"),
Self::ReturnNext(e) => write!(f, "RETURN NEXT {e}"),
Self::ReturnQuery(s) => write!(f, "RETURN QUERY {s}"),
Self::ReturnQueryExecute { sql } => write!(f, "RETURN QUERY EXECUTE {sql}"),
Self::Return(t) => match t {
ReturnTarget::New => f.write_str("RETURN NEW"),
ReturnTarget::Old => f.write_str("RETURN OLD"),
ReturnTarget::Null => f.write_str("RETURN NULL"),
ReturnTarget::Expr(e) => write!(f, "RETURN {e}"),
},
Self::If {
branches,
else_branch,
} => {
for (i, (cond, body)) in branches.iter().enumerate() {
if i == 0 {
write!(f, "IF {cond} THEN ")?;
} else {
write!(f, " ELSIF {cond} THEN ")?;
}
for (j, s) in body.iter().enumerate() {
if j > 0 {
f.write_str("; ")?;
}
write!(f, "{s}")?;
}
}
if !else_branch.is_empty() {
f.write_str(" ELSE ")?;
for (j, s) in else_branch.iter().enumerate() {
if j > 0 {
f.write_str("; ")?;
}
write!(f, "{s}")?;
}
}
f.write_str(" END IF")
}
Self::Raise {
level,
message,
args,
} => {
let lvl = match level {
RaiseLevel::Notice => "NOTICE",
RaiseLevel::Warning => "WARNING",
RaiseLevel::Info => "INFO",
RaiseLevel::Log => "LOG",
RaiseLevel::Debug => "DEBUG",
RaiseLevel::Exception => "EXCEPTION",
};
write!(f, "RAISE {lvl} '{}'", message.replace('\'', "''"))?;
for a in args {
write!(f, ", {a}")?;
}
Ok(())
}
Self::EmbeddedSql(s) => write!(f, "{s}"),
Self::Assert { condition, message } => {
write!(f, "ASSERT {condition}")?;
if let Some(m) = message {
write!(f, ", {m}")?;
}
Ok(())
}
Self::While { condition, body } => {
writeln!(f, "WHILE {condition} LOOP")?;
for s in body {
writeln!(f, " {s};")?;
}
f.write_str("END LOOP")
}
Self::ForRange {
var,
start,
end,
reverse,
body,
} => {
write!(f, "FOR {var} IN ")?;
if *reverse {
f.write_str("REVERSE ")?;
}
writeln!(f, "{start}..{end} LOOP")?;
for s in body {
writeln!(f, " {s};")?;
}
f.write_str("END LOOP")
}
Self::Loop { body } => {
writeln!(f, "LOOP")?;
for s in body {
writeln!(f, " {s};")?;
}
f.write_str("END LOOP")
}
Self::Exit { when } => {
f.write_str("EXIT")?;
if let Some(c) = when {
write!(f, " WHEN {c}")?;
}
Ok(())
}
Self::Continue { when } => {
f.write_str("CONTINUE")?;
if let Some(c) = when {
write!(f, " WHEN {c}")?;
}
Ok(())
}
Self::ExecuteDynamic { sql } => write!(f, "EXECUTE {sql}"),
Self::ForQuery { var, query, body } => {
writeln!(f, "FOR {var} IN ({query}) LOOP")?;
for s in body {
writeln!(f, " {s};")?;
}
f.write_str("END LOOP")
}
Self::ForExecute {
var,
sql_expr,
body,
} => {
writeln!(f, "FOR {var} IN EXECUTE {sql_expr} LOOP")?;
for s in body {
writeln!(f, " {s};")?;
}
f.write_str("END LOOP")
}
}
}
}
impl fmt::Display for AssignTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NewColumn(c) => write!(f, "NEW.{}", quote_ident(c)),
Self::OldColumn(c) => write!(f, "OLD.{}", quote_ident(c)),
Self::Local(n) => f.write_str(n),
}
}
}
impl fmt::Display for CreateTriggerStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CREATE ")?;
if self.or_replace {
f.write_str("OR REPLACE ")?;
}
write!(f, "TRIGGER {} ", quote_ident(&self.name))?;
match self.timing {
TriggerTiming::Before => f.write_str("BEFORE")?,
TriggerTiming::After => f.write_str("AFTER")?,
TriggerTiming::InsteadOf => f.write_str("INSTEAD OF")?,
}
for (i, e) in self.events.iter().enumerate() {
if i == 0 {
f.write_str(" ")?;
} else {
f.write_str(" OR ")?;
}
match e {
TriggerEvent::Insert => f.write_str("INSERT")?,
TriggerEvent::Update => {
f.write_str("UPDATE")?;
if !self.update_columns.is_empty() {
f.write_str(" OF ")?;
for (j, col) in self.update_columns.iter().enumerate() {
if j > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(col))?;
}
}
}
TriggerEvent::Delete => f.write_str("DELETE")?,
TriggerEvent::Truncate => f.write_str("TRUNCATE")?,
}
}
write!(f, " ON {} FOR EACH ", quote_ident(&self.table))?;
match self.for_each {
TriggerForEach::Row => f.write_str("ROW")?,
TriggerForEach::Statement => f.write_str("STATEMENT")?,
}
write!(f, " EXECUTE FUNCTION {}()", quote_ident(&self.function))
}
}
impl fmt::Display for CreateIndexStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_unique {
f.write_str("CREATE UNIQUE INDEX ")?;
} else {
f.write_str("CREATE INDEX ")?;
}
if self.if_not_exists {
f.write_str("IF NOT EXISTS ")?;
}
write!(
f,
"{} ON {} ",
quote_ident(&self.name),
quote_ident(&self.table)
)?;
match self.method {
IndexMethod::Hnsw => f.write_str("USING hnsw ")?,
IndexMethod::Brin => f.write_str("USING brin ")?,
IndexMethod::Gin => f.write_str("USING gin ")?,
IndexMethod::BTree => {}
}
if let Some(expr) = &self.expression {
write!(f, "({})", expr)?;
} else if self.extra_columns.is_empty() {
if let Some(op) = &self.opclass {
write!(f, "({} {})", quote_ident(&self.column), op)?;
} else {
write!(f, "({})", quote_ident(&self.column))?;
}
} else {
f.write_str("(")?;
write!(f, "{}", quote_ident(&self.column))?;
for c in &self.extra_columns {
write!(f, ", {}", quote_ident(c))?;
}
f.write_str(")")?;
}
if !self.included_columns.is_empty() {
f.write_str(" INCLUDE (")?;
for (i, c) in self.included_columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(c))?;
}
f.write_str(")")?;
}
if let Some(pred) = &self.partial_predicate {
write!(f, " WHERE {}", pred)?;
}
Ok(())
}
}
impl fmt::Display for CreateTableStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CREATE TABLE ")?;
if self.if_not_exists {
f.write_str("IF NOT EXISTS ")?;
}
write!(f, "{}", quote_ident(&self.name))?;
if let Some(spec) = &self.partition_of {
write!(f, " PARTITION OF {} ", quote_ident(&spec.parent_name))?;
return match &spec.bounds {
PartitionOfBoundsAst::Range { lower, upper } => {
write!(f, "FOR VALUES FROM ({}) TO ({})", *lower, *upper)
}
PartitionOfBoundsAst::List { values } => {
f.write_str("FOR VALUES IN (")?;
for (i, v) in values.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", v)?;
}
f.write_str(")")
}
PartitionOfBoundsAst::Hash { modulus, remainder } => {
write!(
f,
"FOR VALUES WITH (MODULUS {}, REMAINDER {})",
modulus, remainder
)
}
PartitionOfBoundsAst::Default => f.write_str("DEFAULT"),
};
}
f.write_str(" (")?;
for (i, col) in self.columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{col}")?;
}
for fk in &self.foreign_keys {
f.write_str(", ")?;
write!(f, "{fk}")?;
}
for tc in &self.table_constraints {
f.write_str(", ")?;
write!(f, "{tc}")?;
}
f.write_str(")")?;
if let Some(spec) = &self.partition_by {
f.write_str(" PARTITION BY ")?;
match spec.kind {
PartitionKindAst::Range => f.write_str("RANGE ")?,
PartitionKindAst::List => f.write_str("LIST ")?,
PartitionKindAst::Hash => f.write_str("HASH ")?,
}
f.write_str("(")?;
for (i, col) in spec.key_columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(col))?;
}
f.write_str(")")?;
}
Ok(())
}
}
fn fmt_alter_target(f: &mut fmt::Formatter<'_>, t: &AlterTableTarget) -> fmt::Result {
match t {
AlterTableTarget::OfType { type_name } => write!(f, "OF {type_name}"),
AlterTableTarget::ReplicaIdentityUsingIndex { index } => {
write!(f, "REPLICA IDENTITY USING INDEX {index}")
}
AlterTableTarget::Inherit { parent, detach } => {
if *detach {
write!(f, "NO INHERIT {parent}")
} else {
write!(f, "INHERIT {parent}")
}
}
AlterTableTarget::SetHotTierBytes(n) => {
write!(f, "SET hot_tier_bytes = {n}")
}
AlterTableTarget::AddForeignKey(fk) => write!(f, "ADD {fk}"),
AlterTableTarget::DropForeignKey { name, if_exists } => {
f.write_str("DROP CONSTRAINT ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{}", quote_ident(name))
}
AlterTableTarget::DropIndex { name, if_exists } => {
f.write_str("DROP INDEX ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{}", quote_ident(name))
}
AlterTableTarget::AddColumn {
column,
if_not_exists,
} => {
f.write_str("ADD COLUMN ")?;
if *if_not_exists {
f.write_str("IF NOT EXISTS ")?;
}
write!(f, "{} {}", quote_ident(&column.name), column.ty)?;
if !column.nullable {
f.write_str(" NOT NULL")?;
}
if let Some(d) = &column.default {
write!(f, " DEFAULT {d}")?;
}
if column.auto_increment {
f.write_str(" AUTO_INCREMENT")?;
}
if column.is_primary_key {
f.write_str(" PRIMARY KEY")?;
}
Ok(())
}
AlterTableTarget::AlterColumnType {
column,
new_type,
using,
collation,
} => {
write!(f, "ALTER COLUMN {} TYPE {new_type}", quote_ident(column))?;
if let Some((_, name)) = collation {
write!(f, " COLLATE {}", quote_ident(name))?;
}
if let Some(u) = using {
write!(f, " USING {u}")?;
}
Ok(())
}
AlterTableTarget::DropColumn {
column,
if_exists,
cascade,
} => {
f.write_str("DROP COLUMN ")?;
if *if_exists {
f.write_str("IF EXISTS ")?;
}
write!(f, "{}", quote_ident(column))?;
if *cascade {
f.write_str(" CASCADE")?;
}
Ok(())
}
AlterTableTarget::AddTableConstraint(tc) => {
write!(f, "ADD {tc}")
}
AlterTableTarget::ValidateConstraint { name } => {
write!(f, "VALIDATE CONSTRAINT {}", quote_ident(name))
}
AlterTableTarget::OwnerTo { role } => write!(f, "OWNER TO {}", quote_ident(role)),
AlterTableTarget::ClusterOn { index } => match index {
Some(i) => write!(f, "CLUSTER ON {}", quote_ident(i)),
None => f.write_str("SET WITHOUT CLUSTER"),
},
AlterTableTarget::SetColumnAutoIncrement { column, seq_name } => {
let seq = seq_name
.clone()
.unwrap_or_else(|| alloc::format!("{column}_seq"));
write!(
f,
"ALTER COLUMN {} SET DEFAULT nextval('{seq}')",
quote_ident(column)
)
}
AlterTableTarget::RenameColumn { old, new } => {
write!(
f,
"RENAME COLUMN {} TO {}",
quote_ident(old),
quote_ident(new)
)
}
AlterTableTarget::RenameConstraint { old, new } => {
write!(
f,
"RENAME CONSTRAINT {} TO {}",
quote_ident(old),
quote_ident(new)
)
}
AlterTableTarget::RenameTable { new } => {
write!(f, "RENAME TO {}", quote_ident(new))
}
AlterTableTarget::SetTriggerEnabled { which, enabled } => {
f.write_str(if *enabled {
"ENABLE TRIGGER "
} else {
"DISABLE TRIGGER "
})?;
match which {
TriggerSelector::All => f.write_str("ALL"),
TriggerSelector::Named(n) => f.write_str("e_ident(n)),
}
}
AlterTableTarget::SetRowSecurity { enabled, force } => match (enabled, force) {
(Some(true), _) => f.write_str("ENABLE ROW LEVEL SECURITY"),
(Some(false), _) => f.write_str("DISABLE ROW LEVEL SECURITY"),
(_, Some(true)) => f.write_str("FORCE ROW LEVEL SECURITY"),
(_, Some(false)) => f.write_str("NO FORCE ROW LEVEL SECURITY"),
(None, None) => Ok(()),
},
AlterTableTarget::AttachPartition { child, bounds } => {
write!(f, "ATTACH PARTITION {} ", quote_ident(child))?;
match bounds {
PartitionOfBoundsAst::Range { lower, upper } => {
write!(f, "FOR VALUES FROM ({}) TO ({})", *lower, *upper)
}
PartitionOfBoundsAst::List { values } => {
f.write_str("FOR VALUES IN (")?;
for (i, v) in values.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", v)?;
}
f.write_str(")")
}
PartitionOfBoundsAst::Hash { modulus, remainder } => {
write!(
f,
"FOR VALUES WITH (MODULUS {}, REMAINDER {})",
modulus, remainder
)
}
PartitionOfBoundsAst::Default => f.write_str("DEFAULT"),
}
}
AlterTableTarget::DetachPartition {
child,
concurrently,
finalize,
} => {
write!(f, "DETACH PARTITION {}", quote_ident(child))?;
if *concurrently {
f.write_str(" CONCURRENTLY")?;
}
if *finalize {
f.write_str(" FINALIZE")?;
}
Ok(())
}
AlterTableTarget::AlterColumnSetDefault {
column,
default_expr,
} => write!(
f,
"ALTER COLUMN {} SET DEFAULT {}",
quote_ident(column),
default_expr
),
AlterTableTarget::AlterColumnDropDefault { column } => {
write!(f, "ALTER COLUMN {} DROP DEFAULT", quote_ident(column))
}
AlterTableTarget::AlterColumnSetNotNull { column } => {
write!(f, "ALTER COLUMN {} SET NOT NULL", quote_ident(column))
}
AlterTableTarget::AlterColumnDropNotNull { column } => {
write!(f, "ALTER COLUMN {} DROP NOT NULL", quote_ident(column))
}
AlterTableTarget::AlterColumnRestart { column, with } => {
write!(f, "ALTER COLUMN {} RESTART", quote_ident(column))?;
if let Some(n) = with {
write!(f, " WITH {n}")?;
}
Ok(())
}
AlterTableTarget::AlterColumnDropExpression { column, if_exists } => {
write!(
f,
"ALTER COLUMN {} DROP EXPRESSION{}",
quote_ident(column),
if *if_exists { " IF EXISTS" } else { "" }
)
}
AlterTableTarget::AlterColumnDropIdentity { column, if_exists } => {
write!(
f,
"ALTER COLUMN {} DROP IDENTITY{}",
quote_ident(column),
if *if_exists { " IF EXISTS" } else { "" }
)
}
AlterTableTarget::AlterColumnSetExpression { column, expr } => {
write!(
f,
"ALTER COLUMN {} SET EXPRESSION AS ({expr})",
quote_ident(column)
)
}
}
}
impl fmt::Display for TableConstraint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PrimaryKey { name, columns, .. } => {
if let Some(n) = name {
write!(f, "CONSTRAINT {} ", quote_ident(n))?;
}
f.write_str("PRIMARY KEY (")?;
for (i, c) in columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
f.write_str(")")
}
Self::Unique {
name,
columns,
nulls_not_distinct,
..
} => {
if let Some(n) = name {
write!(f, "CONSTRAINT {} ", quote_ident(n))?;
}
f.write_str("UNIQUE ")?;
if *nulls_not_distinct {
f.write_str("NULLS NOT DISTINCT ")?;
}
f.write_str("(")?;
for (i, c) in columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
f.write_str(")")
}
Self::Check {
name,
expr,
not_valid,
} => {
if let Some(n) = name {
write!(f, "CONSTRAINT {} ", quote_ident(n))?;
}
write!(f, "CHECK ({expr})")?;
if *not_valid {
write!(f, " NOT VALID")?;
}
Ok(())
}
Self::Index { name, columns } => {
f.write_str("KEY ")?;
if let Some(n) = name {
write!(f, "{} ", quote_ident(n))?;
}
f.write_str("(")?;
for (i, c) in columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
f.write_str(")")
}
Self::FulltextIndex { name, columns } => {
f.write_str("FULLTEXT KEY ")?;
if let Some(n) = name {
write!(f, "{} ", quote_ident(n))?;
}
f.write_str("(")?;
for (i, c) in columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
f.write_str(")")
}
Self::Exclude {
name,
method,
elements,
} => {
if let Some(n) = name {
write!(f, "CONSTRAINT {} ", quote_ident(n))?;
}
f.write_str("EXCLUDE ")?;
if let Some(m) = method {
write!(f, "USING {m} ")?;
}
f.write_str("(")?;
for (i, (col, op)) in elements.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{} WITH {op}", quote_ident(col))?;
}
f.write_str(")")
}
}
}
}
impl fmt::Display for ForeignKeyConstraint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(name) = &self.name {
write!(f, "CONSTRAINT {} ", quote_ident(name))?;
}
f.write_str("FOREIGN KEY (")?;
for (i, c) in self.columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
write!(f, ") REFERENCES {}", quote_ident(&self.parent_table))?;
if !self.parent_columns.is_empty() {
f.write_str(" (")?;
for (i, c) in self.parent_columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
f.write_str(")")?;
}
if self.on_delete != FkAction::Restrict {
write!(f, " ON DELETE {}", self.on_delete)?;
}
if self.on_update != FkAction::Restrict {
write!(f, " ON UPDATE {}", self.on_update)?;
}
Ok(())
}
}
impl fmt::Display for FkAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Restrict => f.write_str("RESTRICT"),
Self::Cascade => f.write_str("CASCADE"),
Self::SetNull => f.write_str("SET NULL"),
Self::SetDefault => f.write_str("SET DEFAULT"),
Self::NoAction => f.write_str("NO ACTION"),
}
}
}
impl fmt::Display for ColumnDef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", quote_ident(&self.name))?;
if let Some(ut) = &self.user_type_ref {
write!(f, " {}", quote_ident(ut))?;
} else if let Some(variants) = &self.inline_enum_variants {
write_variant_list(f, "ENUM", variants)?;
} else if let Some(variants) = &self.inline_set_variants {
write_variant_list(f, "SET", variants)?;
} else {
write!(f, " {}", self.ty)?;
}
if self.is_unsigned {
f.write_str(" UNSIGNED")?;
}
match self.collation {
Collation::Binary => {}
Collation::CaseInsensitive => f.write_str(" COLLATE \"case_insensitive\"")?,
}
if let Some(d) = &self.default {
write!(f, " DEFAULT {d}")?;
}
if self.auto_increment {
f.write_str(" AUTO_INCREMENT")?;
}
if !self.nullable {
f.write_str(" NOT NULL")?;
}
if self.is_primary_key {
f.write_str(" PRIMARY KEY")?;
}
if self.on_update_runtime.is_some() {
f.write_str(" ON UPDATE CURRENT_TIMESTAMP")?;
}
if let Some(gen_expr) = &self.generated_stored_expr {
write!(f, " GENERATED ALWAYS AS ({gen_expr}) STORED")?;
}
Ok(())
}
}
fn write_variant_list(f: &mut fmt::Formatter<'_>, kw: &str, variants: &[String]) -> fmt::Result {
write!(f, " {kw}(")?;
for (i, v) in variants.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "'{}'", v.replace('\'', "''"))?;
}
f.write_str(")")
}
impl fmt::Display for InsertStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "INSERT INTO {}", quote_ident(&self.table))?;
if let Some(cols) = &self.columns {
f.write_str(" (")?;
for (i, c) in cols.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
f.write_str(")")?;
}
if let Some(sel) = &self.select_source {
write!(f, " {sel}")?;
} else {
f.write_str(" VALUES ")?;
for (ri, row) in self.rows.iter().enumerate() {
if ri > 0 {
f.write_str(", ")?;
}
f.write_str("(")?;
for (i, v) in row.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{v}")?;
}
f.write_str(")")?;
}
}
if let Some(oc) = &self.on_conflict {
write!(f, " {oc}")?;
}
write_returning(self.returning.as_deref(), f)?;
Ok(())
}
}
impl fmt::Display for OnConflictClause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ON CONFLICT")?;
if let Some(name) = &self.constraint_name {
write!(f, " ON CONSTRAINT {name}")?;
}
if !self.target_columns.is_empty() {
f.write_str(" (")?;
for (i, c) in self.target_columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
f.write_str(")")?;
}
if let Some(w) = &self.index_where {
write!(f, " WHERE {w}")?;
}
match &self.action {
OnConflictAction::Nothing => f.write_str(" DO NOTHING"),
OnConflictAction::Update {
assignments,
where_,
} => {
f.write_str(" DO UPDATE SET ")?;
for (i, (col, expr)) in assignments.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{} = {expr}", quote_ident(col))?;
}
if let Some(w) = where_ {
write!(f, " WHERE {w}")?;
}
Ok(())
}
}
}
}
fn write_returning(ret: Option<&[SelectItem]>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Some(items) = ret else {
return Ok(());
};
f.write_str(" RETURNING ")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{item}")?;
}
Ok(())
}
impl fmt::Display for UpdateStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "UPDATE {} SET ", quote_ident(&self.table))?;
for (i, (col, expr)) in self.assignments.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{} = {expr}", quote_ident(col))?;
}
if let Some(w) = &self.where_ {
write!(f, " WHERE {w}")?;
}
if let Some(ol) = self.order_limit.as_deref() {
if !ol.order_by.is_empty() {
f.write_str(" ORDER BY ")?;
for (i, o) in ol.order_by.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", o.expr)?;
if o.desc {
f.write_str(" DESC")?;
}
match o.nulls_first {
Some(true) => f.write_str(" NULLS FIRST")?,
Some(false) => f.write_str(" NULLS LAST")?,
None => {}
}
}
}
if let Some(n) = ol.limit {
write!(f, " LIMIT {n}")?;
}
}
write_returning(self.returning.as_deref(), f)?;
Ok(())
}
}
impl fmt::Display for DeleteStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DELETE FROM {}", quote_ident(&self.table))?;
if let Some(w) = &self.where_ {
write!(f, " WHERE {w}")?;
}
write_returning(self.returning.as_deref(), f)?;
Ok(())
}
}
impl fmt::Display for CteBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Select(s) => write!(f, "{s}"),
Self::Insert(s) => write!(f, "{s}"),
Self::Update(s) => write!(f, "{s}"),
Self::Delete(s) => write!(f, "{s}"),
Self::Merge(s) => write!(f, "{s}"),
}
}
}
impl fmt::Display for MergeStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_with_clause(&self.ctes, f)?;
f.write_str("MERGE INTO ")?;
write!(f, "{}", quote_ident(&self.target))?;
if let Some(a) = &self.target_alias {
write!(f, " {}", quote_ident(a))?;
}
f.write_str(" USING ")?;
if let Some(sub) = &self.source_select {
write!(f, "({sub})")?;
} else {
write!(f, "{}", quote_ident(&self.source))?;
}
if let Some(a) = &self.source_alias {
write!(f, " {}", quote_ident(a))?;
}
if !self.source_column_aliases.is_empty() {
f.write_str("(")?;
for (i, c) in self.source_column_aliases.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(c))?;
}
f.write_str(")")?;
}
write!(f, " ON {}", self.on)?;
for clause in &self.clauses {
f.write_str(" WHEN ")?;
f.write_str(match clause.matched {
MergeMatched::Matched => "MATCHED",
MergeMatched::NotMatched => "NOT MATCHED",
MergeMatched::NotMatchedBySource => "NOT MATCHED BY SOURCE",
})?;
if let Some(c) = &clause.condition {
write!(f, " AND {c}")?;
}
f.write_str(" THEN ")?;
match &clause.action {
MergeAction::Insert { columns, values } => {
f.write_str("INSERT ")?;
if !columns.is_empty() {
f.write_str("(")?;
for (i, c) in columns.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", quote_ident(c))?;
}
f.write_str(") ")?;
}
f.write_str("VALUES (")?;
for (i, v) in values.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{v}")?;
}
f.write_str(")")?;
}
MergeAction::Update { assignments } => {
f.write_str("UPDATE SET ")?;
for (i, (c, e)) in assignments.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{} = {e}", quote_ident(c))?;
}
}
MergeAction::Delete => f.write_str("DELETE")?,
MergeAction::DoNothing => f.write_str("DO NOTHING")?,
}
}
if let Some(items) = &self.returning {
f.write_str(" RETURNING ")?;
for (i, it) in items.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{it}")?;
}
}
Ok(())
}
}
fn fmt_with_clause(ctes: &[Cte], f: &mut fmt::Formatter<'_>) -> fmt::Result {
if ctes.is_empty() {
return Ok(());
}
f.write_str("WITH ")?;
if ctes.iter().any(|c| c.recursive) {
f.write_str("RECURSIVE ")?;
}
for (i, cte) in ctes.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(&cte.name))?;
if !cte.column_overrides.is_empty() {
f.write_str(" (")?;
for (ci, c) in cte.column_overrides.iter().enumerate() {
if ci > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
f.write_str(")")?;
}
write!(f, " AS ({})", cte.body)?;
}
f.write_str(" ")
}
impl fmt::Display for SelectStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_with_clause(&self.ctes, f)?;
write_bare_select(self, f)?;
for (kind, peer) in &self.unions {
f.write_str(match kind {
UnionKind::Distinct => " UNION ",
UnionKind::All => " UNION ALL ",
UnionKind::Intersect => " INTERSECT ",
UnionKind::IntersectAll => " INTERSECT ALL ",
UnionKind::Except => " EXCEPT ",
UnionKind::ExceptAll => " EXCEPT ALL ",
})?;
write_bare_select(peer, f)?;
}
if !self.order_by.is_empty() {
f.write_str(" ORDER BY ")?;
for (i, o) in self.order_by.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", o.expr)?;
if o.desc {
f.write_str(" DESC")?;
}
match o.nulls_first {
Some(true) => f.write_str(" NULLS FIRST")?,
Some(false) => f.write_str(" NULLS LAST")?,
None => {}
}
}
}
if self.limit_with_ties {
if let Some(o) = &self.offset {
write!(f, " OFFSET {o}")?;
}
if let Some(n) = &self.limit {
write!(f, " FETCH FIRST {n} ROWS WITH TIES")?;
}
} else {
if let Some(n) = &self.limit {
write!(f, " LIMIT {n}")?;
}
if let Some(o) = &self.offset {
write!(f, " OFFSET {o}")?;
}
}
Ok(())
}
}
fn write_bare_select(s: &SelectStatement, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SELECT ")?;
if s.distinct {
f.write_str("DISTINCT ")?;
}
write_bare_select_body(s, f)
}
fn write_bare_select_body(s: &SelectStatement, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, item) in s.items.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{item}")?;
}
if let Some(t) = &s.from {
write!(f, " FROM {t}")?;
}
if let Some(e) = &s.where_ {
write!(f, " WHERE {e}")?;
}
if let Some(gs) = &s.group_by {
f.write_str(" GROUP BY ")?;
for (i, g) in gs.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{g}")?;
}
} else if s.group_by_all {
f.write_str(" GROUP BY ALL")?;
}
if let Some(h) = &s.having {
write!(f, " HAVING {h}")?;
}
Ok(())
}
impl fmt::Display for SelectItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Wildcard => f.write_str("*"),
Self::QualifiedWildcard(q) => write!(f, "{}.*", quote_ident(q)),
Self::Expr { expr, alias } => {
write!(f, "{expr}")?;
if let Some(a) = alias {
write!(f, " AS {}", quote_ident(a))?;
}
Ok(())
}
}
}
}
impl fmt::Display for FromClause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.primary)?;
for j in &self.joins {
match j.kind {
JoinKind::Inner => write!(f, " INNER JOIN {}", j.table)?,
JoinKind::Left => write!(f, " LEFT JOIN {}", j.table)?,
JoinKind::Cross => write!(f, " CROSS JOIN {}", j.table)?,
JoinKind::Right => write!(f, " RIGHT JOIN {}", j.table)?,
JoinKind::FullOuter => write!(f, " FULL OUTER JOIN {}", j.table)?,
JoinKind::Semi => write!(f, " SEMI JOIN {}", j.table)?,
}
if let Some(on) = &j.on {
write!(f, " ON {on}")?;
}
}
Ok(())
}
}
fn fmt_json_table_columns(f: &mut fmt::Formatter<'_>, cols: &[JsonTableColumn]) -> fmt::Result {
for (i, c) in cols.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
match c {
JsonTableColumn::Ordinality { name } => {
write!(f, "{} FOR ORDINALITY", quote_ident(name))?;
}
JsonTableColumn::Nested { path, columns } => {
write!(f, "NESTED PATH '{path}' COLUMNS (")?;
fmt_json_table_columns(f, columns)?;
f.write_str(")")?;
}
JsonTableColumn::Regular {
name,
ty,
path,
exists,
format_json,
wrapper,
on_empty,
on_error,
} => {
write!(f, "{} {ty}", quote_ident(name))?;
if *format_json {
f.write_str(" FORMAT JSON")?;
}
if *exists {
write!(f, " EXISTS PATH '{path}'")?;
} else {
write!(f, " PATH '{path}'")?;
}
if *wrapper {
f.write_str(" WITH WRAPPER")?;
}
if let JsonTableOnBehavior::Error = on_empty {
f.write_str(" ERROR ON EMPTY")?;
} else if let JsonTableOnBehavior::Default(e) = on_empty {
write!(f, " DEFAULT {e} ON EMPTY")?;
}
if let JsonTableOnBehavior::Error = on_error {
f.write_str(" ERROR ON ERROR")?;
} else if let JsonTableOnBehavior::Default(e) = on_error {
write!(f, " DEFAULT {e} ON ERROR")?;
}
}
}
}
Ok(())
}
impl fmt::Display for TableRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(jt) = &self.json_table {
write!(f, "JSON_TABLE({}, '{}'", jt.doc, jt.row_path)?;
if !jt.passing.is_empty() {
f.write_str(" PASSING ")?;
for (i, (n, e)) in jt.passing.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{e} AS {}", quote_ident(n))?;
}
}
f.write_str(" COLUMNS (")?;
fmt_json_table_columns(f, &jt.columns)?;
f.write_str(")")?;
if let Some(a) = &self.alias {
write!(f, " AS {}", quote_ident(a))?;
}
return Ok(());
}
if let Some(inner) = &self.lateral_subquery {
write!(f, "LATERAL ({inner})")?;
if let Some(a) = &self.alias {
write!(f, " AS {}", quote_ident(a))?;
if !self.unnest_column_aliases.is_empty() {
f.write_str(" (")?;
for (i, c) in self.unnest_column_aliases.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
f.write_str(")")?;
}
}
return Ok(());
}
if let Some(expr) = &self.unnest_expr {
write!(f, "UNNEST({expr})")?;
if let Some(a) = &self.alias {
write!(f, " AS {}", quote_ident(a))?;
if !self.unnest_column_aliases.is_empty() {
f.write_str(" (")?;
for (i, c) in self.unnest_column_aliases.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str("e_ident(c))?;
}
f.write_str(")")?;
}
}
return Ok(());
}
if let Some(args) = &self.generate_series_args {
f.write_str("generate_series(")?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{a}")?;
}
f.write_str(")")?;
if let Some(a) = &self.alias {
write!(f, " AS {}", quote_ident(a))?;
}
return Ok(());
}
write!(f, "{}", quote_ident(&self.name))?;
if let Some(seg) = self.as_of_segment {
write!(f, " AS OF SEGMENT {seg}")?;
}
if let Some(a) = &self.alias {
write!(f, " AS {}", quote_ident(a))?;
}
Ok(())
}
}
impl fmt::Display for ColumnName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(q) = &self.qualifier {
write!(f, "{}.{}", quote_ident(q), quote_ident(&self.name))
} else {
write!(f, "{}", quote_ident(&self.name))
}
}
}
fn write_bool_chain(f: &mut fmt::Formatter<'_>, e: &Expr, op: BinOp) -> fmt::Result {
if let Expr::Binary {
lhs,
op: inner,
rhs,
} = e
&& *inner == op
{
write_bool_chain(f, lhs, op)?;
return write!(f, " {op} {rhs}");
}
write!(f, "{e}")
}
#[must_use]
pub fn pretty_expr(e: &Expr) -> String {
let mut out = String::new();
write_pretty(&mut out, e, PrettyParent::None, false, false);
out
}
#[must_use]
pub fn pretty_expr_mysql(e: &Expr) -> String {
let mut out = String::new();
write_pretty(&mut out, e, PrettyParent::None, false, true);
out
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum NameStrength {
None,
Weak,
Strong,
}
#[must_use]
pub fn figure_column_name(expr: &Expr) -> Option<String> {
let (name, _) = figure_name_inner(expr);
name
}
fn canonical_function_name(name: &str) -> String {
match name {
"count_star" => "count".to_string(),
other => other.to_ascii_lowercase(),
}
}
fn figure_name_inner(expr: &Expr) -> (Option<String>, NameStrength) {
let strong = |n: String| (Some(n), NameStrength::Strong);
match expr {
Expr::Column(c) => strong(c.name.clone()),
Expr::FunctionCall { name, .. } | Expr::WindowFunction { name, .. } => {
strong(canonical_function_name(name))
}
Expr::AggregateOrdered { call, .. } => figure_name_inner(call),
Expr::Extract { .. } => strong("extract".to_string()),
Expr::Exists { .. } => strong("exists".to_string()),
Expr::Array(_) => strong("array".to_string()),
Expr::FieldAccess { field, .. } => strong(field.clone()),
Expr::Cast {
expr: inner,
target,
} => match figure_name_inner(inner) {
(Some(n), NameStrength::Strong) => strong(n),
_ => (Some(target.to_string()), NameStrength::Weak),
},
Expr::ScalarSubquery(sel) => scalar_subquery_name(sel),
Expr::Case { .. } => (Some("case".to_string()), NameStrength::Weak),
Expr::Literal(Literal::Interval { .. }) => {
(Some("interval".to_string()), NameStrength::Weak)
}
Expr::Variadic(inner) => figure_name_inner(inner),
Expr::NamedArg { expr: inner, .. } => figure_name_inner(inner),
_ => (None, NameStrength::None),
}
}
fn scalar_subquery_name(sel: &SelectStatement) -> (Option<String>, NameStrength) {
match sel.items.as_slice() {
[SelectItem::Expr { alias: Some(a), .. }] => (Some(a.clone()), NameStrength::Strong),
[SelectItem::Expr { expr, alias: None }] => figure_name_inner(expr),
_ => (None, NameStrength::None),
}
}
fn pretty_prec(e: &Expr) -> u8 {
match e {
Expr::Binary { op, .. } => match op {
BinOp::Or => 1,
BinOp::LogicalXor => 2,
BinOp::And => 3,
BinOp::Add | BinOp::Sub | BinOp::Concat => 6,
BinOp::Mul | BinOp::Div | BinOp::Mod => 7,
_ => 5,
},
Expr::Unary { op, .. } => match op {
UnOp::Not => 4,
UnOp::Neg | UnOp::BitNot | UnOp::Plus => 8,
},
_ => u8::MAX,
}
}
fn pretty_is_compound(e: &Expr) -> bool {
match e {
Expr::Binary { .. } | Expr::Unary { .. } => true,
Expr::Cast { expr, .. } => pretty_is_compound(expr),
_ => false,
}
}
#[derive(Clone, Copy, PartialEq)]
enum PrettyParent {
None,
Comparison,
Arith(u8),
Bool(u8),
Not,
}
fn write_pretty(out: &mut String, e: &Expr, parent: PrettyParent, is_rhs: bool, mysql: bool) {
let prec = pretty_prec(e);
let is_unary_sign = matches!(
e,
Expr::Unary {
op: UnOp::Neg | UnOp::BitNot | UnOp::Plus,
..
}
);
let needs = match parent {
PrettyParent::None => false,
PrettyParent::Comparison => pretty_is_compound(e),
PrettyParent::Arith(p) => {
is_unary_sign
|| (matches!(e, Expr::Binary { .. } | Expr::Unary { .. })
&& (prec < p || (prec == p && is_rhs)))
}
PrettyParent::Bool(p) => matches!(e, Expr::Binary { .. } | Expr::Unary { .. }) && prec < p,
PrettyParent::Not => {
matches!(e, Expr::Binary { .. } | Expr::Unary { .. }) && prec <= pretty_prec_not()
}
};
if needs {
out.push('(');
}
match e {
Expr::Binary { lhs, op, rhs } => {
let child = match op {
BinOp::And | BinOp::Or => PrettyParent::Bool(prec),
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::Concat => {
PrettyParent::Arith(prec)
}
_ => PrettyParent::Comparison,
};
write_pretty(out, lhs, child, false, mysql);
out.push(' ');
out.push_str(&alloc::format!("{op}"));
out.push(' ');
let rhs_is_rhs = !matches!(op, BinOp::And | BinOp::Or);
write_pretty(out, rhs, child, rhs_is_rhs, mysql);
}
Expr::Unary { op, expr } => match op {
UnOp::Not => {
out.push_str("NOT ");
write_pretty(out, expr, PrettyParent::Not, false, mysql);
}
UnOp::Neg => {
out.push_str("- ");
write_pretty(out, expr, PrettyParent::Comparison, false, mysql);
}
UnOp::Plus => {
out.push_str("+ ");
write_pretty(out, expr, PrettyParent::Comparison, false, mysql);
}
UnOp::BitNot => {
out.push('~');
write_pretty(out, expr, PrettyParent::Comparison, false, mysql);
}
},
Expr::Cast { expr, target } => {
if mysql {
out.push_str("cast(");
write_pretty(out, expr, PrettyParent::None, false, mysql);
out.push_str(&alloc::format!(
" as {})",
target.to_string().to_lowercase()
));
} else {
write_pretty(out, expr, PrettyParent::Comparison, false, mysql);
out.push_str(&alloc::format!("::{target}"));
}
}
Expr::IsNull { expr, negated } => {
write_pretty(out, expr, PrettyParent::Comparison, false, mysql);
out.push_str(if *negated { " IS NOT NULL" } else { " IS NULL" });
}
other => out.push_str(&alloc::format!("{other}")),
}
if needs {
out.push(')');
}
}
const fn pretty_prec_not() -> u8 {
4
}
impl fmt::Display for Expr {
#[allow(clippy::too_many_lines)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Literal(l) => write!(f, "{l}"),
Self::Column(c) => write!(f, "{c}"),
Self::Placeholder(n) => write!(f, "${n}"),
Self::NamedArg { name, expr } => write!(f, "{} := {expr}", quote_ident(name)),
Self::Variadic(expr) => write!(f, "VARIADIC {expr}"),
Self::Binary { lhs, op, rhs } if matches!(op, BinOp::And | BinOp::Or) => {
f.write_str("(")?;
write_bool_chain(f, lhs, *op)?;
write!(f, " {op} {rhs}")?;
f.write_str(")")
}
Self::Binary { lhs, op, rhs } => write!(f, "({lhs} {op} {rhs})"),
Self::Unary { op, expr } => match op {
UnOp::Not => write!(f, "(NOT {expr})"),
UnOp::Neg => write!(f, "(- {expr})"),
UnOp::Plus => write!(f, "(+ {expr})"),
UnOp::BitNot => write!(f, "(~{expr})"),
},
Self::Cast { expr, target } => write!(f, "({expr})::{target}"),
Self::FieldAccess { base, field } => write!(f, "({base}).{field}"),
Self::AggregateOrdered {
call,
order_by,
distinct,
filter,
} => {
let fmt_order_by = |f: &mut fmt::Formatter<'_>| -> fmt::Result {
for (i, o) in order_by.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", o.expr)?;
if o.desc {
f.write_str(" DESC")?;
}
match o.nulls_first {
Some(true) => f.write_str(" NULLS FIRST")?,
Some(false) => f.write_str(" NULLS LAST")?,
None => {}
}
}
Ok(())
};
let ordered_set = matches!(
call.as_ref(),
Expr::FunctionCall { name, .. }
if matches!(
name.to_ascii_lowercase().as_str(),
"percentile_cont" | "percentile_disc" | "mode"
)
);
if ordered_set {
write!(f, "{call} WITHIN GROUP (ORDER BY ")?;
fmt_order_by(f)?;
f.write_str(")")?;
} else {
let inner = alloc::format!("{call}");
let body = inner.strip_suffix(')').unwrap_or(&inner);
let (head, args_part) = body.split_once('(').unwrap_or((body, ""));
write!(f, "{head}(")?;
if *distinct {
f.write_str("DISTINCT ")?;
}
write!(f, "{args_part}")?;
if !order_by.is_empty() {
f.write_str(" ORDER BY ")?;
fmt_order_by(f)?;
}
f.write_str(")")?;
}
if let Some(cond) = filter {
write!(f, " FILTER (WHERE {cond})")?;
}
Ok(())
}
Self::IsNull { expr, negated } => {
if *negated {
write!(f, "({expr} IS NOT NULL)")
} else {
write!(f, "({expr} IS NULL)")
}
}
Self::BoolTest {
expr,
value,
negated,
} => {
let word = match value {
Some(true) => "TRUE",
Some(false) => "FALSE",
None => "UNKNOWN",
};
if *negated {
write!(f, "({expr} IS NOT {word})")
} else {
write!(f, "({expr} IS {word})")
}
}
Self::FunctionCall { name, args } => {
write!(f, "{name}(")?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{a}")?;
}
f.write_str(")")
}
Self::Like {
expr,
pattern,
negated,
case_insensitive,
} => {
let op = match (negated, case_insensitive) {
(false, false) => "LIKE",
(true, false) => "NOT LIKE",
(false, true) => "ILIKE",
(true, true) => "NOT ILIKE",
};
write!(f, "({expr} {op} {pattern})")
}
Self::Extract { field, source } => write!(f, "EXTRACT({field} FROM {source})"),
Self::WindowFunction {
name,
args,
partition_by,
order_by,
frame,
null_treatment,
filter,
} => {
write!(f, "{name}(")?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{a}")?;
}
f.write_str(")")?;
if let Some(cond) = filter {
write!(f, " FILTER (WHERE {cond})")?;
}
if matches!(null_treatment, NullTreatment::Ignore) {
f.write_str(" IGNORE NULLS")?;
}
f.write_str(" OVER (")?;
if !partition_by.is_empty() {
f.write_str("PARTITION BY ")?;
for (i, p) in partition_by.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{p}")?;
}
}
if !order_by.is_empty() {
if !partition_by.is_empty() {
f.write_str(" ")?;
}
f.write_str("ORDER BY ")?;
for (i, (e, desc, nulls_first)) in order_by.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{e}")?;
if *desc {
f.write_str(" DESC")?;
}
match nulls_first {
Some(true) => f.write_str(" NULLS FIRST")?,
Some(false) => f.write_str(" NULLS LAST")?,
None => {}
}
}
}
if let Some(fr) = frame {
if !partition_by.is_empty() || !order_by.is_empty() {
f.write_str(" ")?;
}
let k = match fr.kind {
FrameKind::Rows => "ROWS",
FrameKind::Range => "RANGE",
FrameKind::Groups => "GROUPS",
};
if let Some(end) = &fr.end {
write!(f, "{k} BETWEEN {} AND {}", fr.start, end)?;
} else {
write!(f, "{k} {}", fr.start)?;
}
}
f.write_str(")")
}
Self::ScalarSubquery(s) => write!(f, "({s})"),
Self::Exists { subquery, negated } => {
if *negated {
write!(f, "NOT EXISTS ({subquery})")
} else {
write!(f, "EXISTS ({subquery})")
}
}
Self::InSubquery {
expr,
subquery,
negated,
} => {
if *negated {
write!(f, "({expr} NOT IN ({subquery}))")
} else {
write!(f, "({expr} IN ({subquery}))")
}
}
Self::RowInSubquery {
row,
subquery,
negated,
} => {
write!(f, "(")?;
for (i, e) in row.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{e}")?;
}
let kw = if *negated { ") NOT IN (" } else { ") IN (" };
write!(f, "{kw}{subquery})")
}
Self::RowCmpSubquery { row, op, subquery } => {
write!(f, "(")?;
for (i, e) in row.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{e}")?;
}
write!(f, ") {op} ({subquery})")
}
Self::InList {
expr,
list,
negated,
} => {
let kw = if *negated { " NOT IN (" } else { " IN (" };
write!(f, "({expr}{kw}")?;
for (i, e) in list.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{e}")?;
}
f.write_str("))")
}
Self::Array(items) => {
f.write_str("ARRAY[")?;
for (i, e) in items.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{e}")?;
}
f.write_str("]")
}
Self::ArraySubscript { target, index } => write!(f, "({target}[{index}])"),
Self::ArraySlice { target, lo, hi } => {
write!(f, "({target}[")?;
if let Some(l) = lo {
write!(f, "{l}")?;
}
write!(f, ":")?;
if let Some(h) = hi {
write!(f, "{h}")?;
}
write!(f, "])")
}
Self::AnyAll {
expr,
op,
array,
is_any,
} => {
let kw = if *is_any { "ANY" } else { "ALL" };
write!(f, "({expr} {op} {kw}({array}))")
}
Self::Case {
operand,
branches,
else_branch,
} => {
f.write_str("CASE")?;
if let Some(op) = operand {
write!(f, " {op}")?;
}
for (w, t) in branches {
write!(f, " WHEN {w} THEN {t}")?;
}
if let Some(e) = else_branch {
write!(f, " ELSE {e}")?;
}
f.write_str(" END")
}
}
}
}
pub fn render_exact_decimal(unscaled: i128, scale: u16) -> alloc::string::String {
use alloc::string::ToString;
if scale == 0 {
return alloc::format!("{unscaled}");
}
let neg = unscaled < 0;
let digits = alloc::format!("{}", unscaled.unsigned_abs());
let scale = scale as usize;
let (int_part, frac_part) = if digits.len() > scale {
(
digits[..digits.len() - scale].to_string(),
digits[digits.len() - scale..].to_string(),
)
} else {
("0".to_string(), alloc::format!("{digits:0>scale$}"))
};
alloc::format!("{}{int_part}.{frac_part}", if neg { "-" } else { "" })
}
impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Integer(n) => write!(f, "{n}"),
Self::Float(x) => {
let s = format!("{x}");
if s.contains('.') || s.contains('e') || s.contains('E') {
f.write_str(&s)
} else {
write!(f, "{s}.0")
}
}
Self::Numeric { unscaled, scale } => {
f.write_str(&render_exact_decimal(*unscaled, *scale))
}
Self::NumericBig(s) => f.write_str(s),
Self::String(s) => {
f.write_str("'")?;
for c in s.chars() {
if c == '\'' {
f.write_str("''")?;
} else {
write!(f, "{c}")?;
}
}
f.write_str("'")
}
Self::Bool(b) => f.write_str(if *b { "TRUE" } else { "FALSE" }),
Self::Null => f.write_str("NULL"),
Self::TextArray(items) => {
f.write_str("'{")?;
for (i, it) in items.iter().enumerate() {
if i > 0 {
f.write_str(",")?;
}
match it {
None => f.write_str("NULL")?,
Some(s) => {
f.write_str("\"")?;
for c in s.chars() {
match c {
'"' | '\\' => write!(f, "\\{c}")?,
'\'' => f.write_str("''")?,
_ => write!(f, "{c}")?,
}
}
f.write_str("\"")?;
}
}
}
f.write_str("}'")
}
Self::IntArray(items) => {
f.write_str("'{")?;
for (i, it) in items.iter().enumerate() {
if i > 0 {
f.write_str(",")?;
}
match it {
None => f.write_str("NULL")?,
Some(n) => write!(f, "{n}")?,
}
}
f.write_str("}'")
}
Self::BigIntArray(items) => {
f.write_str("'{")?;
for (i, it) in items.iter().enumerate() {
if i > 0 {
f.write_str(",")?;
}
match it {
None => f.write_str("NULL")?,
Some(n) => write!(f, "{n}")?,
}
}
f.write_str("}'")
}
Self::Vector(v) => {
f.write_str("[")?;
for (i, x) in v.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
let s = format!("{x}");
if s.contains('.') || s.contains('e') || s.contains('E') {
f.write_str(&s)?;
} else {
write!(f, "{s}.0")?;
}
}
f.write_str("]")
}
Self::Interval { text, .. } => {
f.write_str("INTERVAL '")?;
for c in text.chars() {
if c == '\'' {
f.write_str("''")?;
} else {
write!(f, "{c}")?;
}
}
f.write_str("'")
}
}
}
}
impl fmt::Display for BinOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Or => "OR",
Self::And => "AND",
Self::Eq => "=",
Self::NotEq => "<>",
Self::IsDistinctFrom => "IS DISTINCT FROM",
Self::IsNotDistinctFrom => "IS NOT DISTINCT FROM",
Self::IntDiv => "DIV",
Self::Lt => "<",
Self::LtEq => "<=",
Self::Gt => ">",
Self::GtEq => ">=",
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
Self::Mod => "%",
Self::L2Distance => "<->",
Self::GeomParallel => "?||",
Self::OverLeft => "&<",
Self::OverRight => "&>",
Self::GeomPerp => "?-|",
Self::GeomSameAs => "~=",
Self::ClosestPoint => "##",
Self::GeomHoriz => "?-",
Self::InnerProduct => "<#>",
Self::CosineDistance => "<=>",
Self::Concat => "||",
Self::BitOr => "|",
Self::BitAnd => "&",
Self::BitXor => "#",
Self::LogicalXor => "xor",
Self::JsonGet => "->",
Self::JsonGetText => "->>",
Self::JsonGetPath => "#>",
Self::JsonGetPathText => "#>>",
Self::JsonContains => "@>",
Self::JsonPathExists => "@?",
Self::JsonContainedBy => "<@",
Self::JsonKeyExists => "?",
Self::JsonKeysAny => "?|",
Self::JsonKeysAll => "?&",
Self::JsonDeletePath => "#-",
Self::TsMatch => "@@",
Self::InetContainedBy => "<<",
Self::InetContainedByEq => "<<=",
Self::InetContains => ">>",
Self::InetContainsEq => ">>=",
Self::InetOverlap => "&&",
Self::Intersects => "?#",
Self::IsBelow => "<^",
Self::IsAbove => ">^",
Self::PatternLt => "~<~",
Self::PatternLtEq => "~<=~",
Self::PatternGt => "~>~",
Self::PatternGtEq => "~>=~",
})
}
}
pub(crate) fn quote_ident(s: &str) -> String {
let needs_quote = match s.chars().next() {
None => true,
Some(c) if !c.is_ascii_alphabetic() && c != '_' => true,
_ => {
s.chars().any(|c| !(c.is_ascii_alphanumeric() || c == '_'))
|| s.chars().any(|c| c.is_ascii_uppercase())
|| is_keyword(s)
}
};
if !needs_quote {
return s.to_string();
}
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
if c == '"' {
out.push_str("\"\"");
} else {
out.push(c);
}
}
out.push('"');
out
}
fn is_keyword(s: &str) -> bool {
matches!(
&*s.to_ascii_lowercase(),
"select"
| "from"
| "where"
| "as"
| "null"
| "true"
| "false"
| "and"
| "or"
| "not"
| "create"
| "table"
| "insert"
| "into"
| "values"
| "index"
| "on"
| "begin"
| "commit"
| "rollback"
| "is"
| "between"
| "in"
| "like"
| "group"
| "distinct"
| "union"
| "all"
| "join"
| "inner"
| "left"
| "cross"
| "outer"
| "default"
| "savepoint"
| "release"
| "to"
| "having"
| "show"
| "extract"
| "offset"
| "asc"
| "desc"
| "interval"
)
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn integer_literal_renders_without_dot() {
assert_eq!(Literal::Integer(42).to_string(), "42");
}
#[test]
fn integral_float_keeps_dot() {
assert_eq!(Literal::Float(1.0).to_string(), "1.0");
assert_eq!(Literal::Float(1.5).to_string(), "1.5");
assert_eq!(Literal::Float(2.5e-3).to_string(), "0.0025");
}
#[test]
fn string_literal_doubles_quote() {
assert_eq!(Literal::String("it's".into()).to_string(), "'it''s'");
}
#[test]
fn bool_and_null_render_uppercase() {
assert_eq!(Literal::Bool(true).to_string(), "TRUE");
assert_eq!(Literal::Bool(false).to_string(), "FALSE");
assert_eq!(Literal::Null.to_string(), "NULL");
}
#[test]
fn binary_op_always_parenthesised() {
let e = Expr::Binary {
lhs: Box::new(Expr::Literal(Literal::Integer(1))),
op: BinOp::Add,
rhs: Box::new(Expr::Literal(Literal::Integer(2))),
};
assert_eq!(e.to_string(), "(1 + 2)");
}
#[test]
fn select_star_from_table() {
let s = SelectStatement {
locking: None,
items: vec![SelectItem::Wildcard],
from: Some(FromClause {
primary: TableRef {
name: "users".into(),
alias: None,
only: false,
as_of_segment: None,
unnest_expr: None,
unnest_column_aliases: Vec::new(),
with_ordinality: false,
generate_series_args: None,
lateral_subquery: None,
jsonb_each_text_arg: None,
table_fn_call: None,
rows_from: None,
json_table: None,
scalar_fn_item: false,
},
joins: vec![],
}),
where_: None,
group_by: None,
group_by_all: false,
having: None,
unions: vec![],
order_by: Vec::new(),
limit: None,
offset: None,
limit_with_ties: false,
window_check_exprs: Vec::new(),
distinct: false,
distinct_on: Vec::new(),
ctes: vec![],
};
assert_eq!(s.to_string(), "SELECT * FROM users");
}
#[test]
fn quote_ident_for_uppercase_and_keyword() {
assert_eq!(quote_ident("foo"), "foo");
assert_eq!(quote_ident("Foo"), "\"Foo\"");
assert_eq!(quote_ident("select"), "\"select\"");
assert_eq!(quote_ident(""), "\"\"");
assert_eq!(quote_ident("a\"b"), "\"a\"\"b\"");
}
}