use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::large_enum_variant)] pub enum Statement {
Select(SelectStatement),
CreateTable(CreateTableStatement),
CreateExtension(String),
CreateIndex(CreateIndexStatement),
Insert(InsertStatement),
Update(UpdateStatement),
Delete(DeleteStatement),
Begin,
Commit,
Rollback,
Savepoint(String),
RollbackToSavepoint(String),
ReleaseSavepoint(String),
ShowTables,
ShowColumns(String),
CreateUser(CreateUserStatement),
DropUser(String),
ShowUsers,
Explain(ExplainStatement),
AlterIndex(AlterIndexStatement),
AlterTable(AlterTableStatement),
CreatePublication(CreatePublicationStatement),
DropPublication(String),
ShowPublications,
CreateSubscription(CreateSubscriptionStatement),
DropSubscription(String),
ShowSubscriptions,
WaitForWalPosition {
pos: u64,
timeout_ms: Option<u64>,
},
Analyze(Option<String>),
CompactColdSegments,
}
#[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 CreatePublicationStatement {
pub name: String,
pub scope: PublicationScope,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PublicationScope {
AllTables,
ForTables(Vec<String>),
AllTablesExcept(Vec<String>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AlterIndexStatement {
pub name: String,
pub target: AlterIndexTarget,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlterIndexTarget {
Rebuild { encoding: Option<VecEncoding> },
}
#[derive(Debug, Clone, PartialEq)]
pub struct AlterTableStatement {
pub name: String,
pub target: AlterTableTarget,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AlterTableTarget {
SetHotTierBytes(u64),
AddForeignKey(ForeignKeyConstraint),
DropForeignKey(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExplainStatement {
pub analyze: bool,
pub inner: Box<SelectStatement>,
pub suggest: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateUserStatement {
pub name: String,
pub password: String,
pub role: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateIndexStatement {
pub name: String,
pub table: String,
pub column: String,
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>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexMethod {
BTree,
Hnsw,
Brin,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateTableStatement {
pub name: String,
pub columns: Vec<ColumnDef>,
pub if_not_exists: bool,
pub foreign_keys: Vec<ForeignKeyConstraint>,
pub table_constraints: Vec<TableConstraint>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TableConstraint {
PrimaryKey {
name: Option<String>,
columns: Vec<String>,
},
Unique {
name: Option<String>,
columns: Vec<String>,
},
}
#[derive(Debug, Clone, PartialEq)]
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,
}
#[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,
}
#[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 {
SmallInt,
Int,
BigInt,
Float,
Text,
Varchar(u32),
Char(u32),
Bool,
Vector {
dim: u32,
encoding: VecEncoding,
},
Numeric(u8, u8),
Date,
Timestamp,
Timestamptz,
Json,
Jsonb,
}
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::Text => f.write_str("TEXT"),
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::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"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct UpdateStatement {
pub table: String,
pub assignments: Vec<(String, Expr)>,
pub where_: Option<Expr>,
pub returning: Option<Vec<SelectItem>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeleteStatement {
pub table: String,
pub where_: Option<Expr>,
pub returning: Option<Vec<SelectItem>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct InsertStatement {
pub table: String,
pub columns: Option<Vec<String>>,
pub rows: Vec<Vec<Expr>>,
pub on_conflict: Option<OnConflictClause>,
pub returning: Option<Vec<SelectItem>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OnConflictClause {
pub target_columns: Vec<String>,
pub action: OnConflictAction,
}
#[derive(Debug, Clone, PartialEq)]
pub enum OnConflictAction {
Nothing,
Update {
assignments: Vec<(String, Expr)>,
where_: Option<Expr>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct SelectStatement {
pub ctes: Vec<Cte>,
pub distinct: bool,
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<u32>,
pub offset: Option<u32>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Cte {
pub name: String,
pub body: SelectStatement,
pub recursive: bool,
pub column_overrides: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OrderBy {
pub expr: Expr,
pub desc: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnionKind {
Distinct,
All,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SelectItem {
Wildcard,
Expr { expr: Expr, alias: Option<String> },
}
#[derive(Debug, Clone, PartialEq)]
pub struct TableRef {
pub name: String,
pub alias: Option<String>,
pub as_of_segment: Option<u32>,
}
#[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>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JoinKind {
Inner,
Left,
Cross,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Literal(Literal),
Column(ColumnName),
Placeholder(u16),
Binary {
lhs: Box<Expr>,
op: BinOp,
rhs: Box<Expr>,
},
Unary {
op: UnOp,
expr: Box<Expr>,
},
Cast {
expr: Box<Expr>,
target: CastTarget,
},
IsNull {
expr: Box<Expr>,
negated: bool,
},
FunctionCall {
name: String,
args: Vec<Expr>,
},
Like {
expr: Box<Expr>,
pattern: Box<Expr>,
negated: bool,
},
WindowFunction {
name: String,
args: Vec<Expr>,
partition_by: Vec<Expr>,
order_by: Vec<(Expr, bool /* desc */)>,
frame: Option<WindowFrame>,
null_treatment: NullTreatment,
},
ScalarSubquery(Box<SelectStatement>),
Exists {
subquery: Box<SelectStatement>,
negated: bool,
},
InSubquery {
expr: Box<Expr>,
subquery: Box<SelectStatement>,
negated: bool,
},
Extract {
field: ExtractField,
source: 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>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameKind {
Rows,
Range,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrameBound {
UnboundedPreceding,
OffsetPreceding(u64),
CurrentRow,
OffsetFollowing(u64),
UnboundedFollowing,
}
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"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtractField {
Year,
Month,
Day,
Hour,
Minute,
Second,
Microsecond,
}
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",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CastTarget {
Int,
BigInt,
Float,
Text,
Bool,
Vector,
Date,
Timestamp,
}
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::Date => "date",
Self::Timestamp => "timestamp",
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
Integer(i64),
Float(f64),
String(String),
Bool(bool),
Null,
Vector(Vec<f32>),
Interval {
months: 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,
Lt,
LtEq,
Gt,
GtEq,
Add,
Sub,
Mul,
Div,
L2Distance,
InnerProduct,
CosineDistance,
Concat,
JsonGet,
JsonGetText,
JsonGetPath,
JsonGetPathText,
JsonContains,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnOp {
Not,
Neg,
}
impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
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::Begin => f.write_str("BEGIN"),
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::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(n) => write!(f, "DROP USER {}", quote_ident(n)),
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) => {
write!(f, "DROP SUBSCRIPTION {}", 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 {} ", quote_ident(&a.name))?;
match a.target {
AlterIndexTarget::Rebuild { encoding } => {
f.write_str("REBUILD")?;
if let Some(enc) = encoding {
write!(f, " WITH (encoding = {enc})")?;
}
Ok(())
}
}
}
Self::AlterTable(a) => {
write!(f, "ALTER TABLE {} ", quote_ident(&a.name))?;
match &a.target {
AlterTableTarget::SetHotTierBytes(n) => {
write!(f, "SET hot_tier_bytes = {n}")
}
AlterTableTarget::AddForeignKey(fk) => write!(f, "ADD {fk}"),
AlterTableTarget::DropForeignKey(name) => {
write!(f, "DROP CONSTRAINT {}", quote_ident(name))
}
}
}
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::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::DropPublication(name) => {
write!(f, "DROP PUBLICATION {}", quote_ident(name))
}
}
}
}
impl fmt::Display for CreateIndexStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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::BTree => {}
}
if let Some(expr) = &self.expression {
write!(f, "({})", expr)?;
} else {
write!(f, "({})", quote_ident(&self.column))?;
}
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))?;
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}")?;
}
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), self.ty)?;
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")?;
}
Ok(())
}
}
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(")")?;
}
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(")")?;
}
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}")?;
}
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}")?;
}
Ok(())
}
}
impl fmt::Display for SelectStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_bare_select(self, f)?;
for (kind, peer) in &self.unions {
f.write_str(match kind {
UnionKind::Distinct => " UNION ",
UnionKind::All => " UNION 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")?;
}
}
}
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}")?;
}
}
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::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)?,
}
if let Some(on) = &j.on {
write!(f, " ON {on}")?;
}
}
Ok(())
}
}
impl fmt::Display for TableRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", quote_ident(&self.name))?;
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))
}
}
}
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::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})"),
},
Self::Cast { expr, target } => write!(f, "({expr}::{target})"),
Self::IsNull { expr, negated } => {
if *negated {
write!(f, "({expr} IS NOT NULL)")
} else {
write!(f, "({expr} IS NULL)")
}
}
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,
} => {
if *negated {
write!(f, "({expr} NOT LIKE {pattern})")
} else {
write!(f, "({expr} LIKE {pattern})")
}
}
Self::Extract { field, source } => write!(f, "EXTRACT({field} FROM {source})"),
Self::WindowFunction {
name,
args,
partition_by,
order_by,
frame,
null_treatment: _,
} => {
write!(f, "{name}(")?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{a}")?;
}
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)) in order_by.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{e}")?;
if *desc {
f.write_str(" DESC")?;
}
}
}
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",
};
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}))")
}
}
}
}
}
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::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::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::Lt => "<",
Self::LtEq => "<=",
Self::Gt => ">",
Self::GtEq => ">=",
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
Self::L2Distance => "<->",
Self::InnerProduct => "<#>",
Self::CosineDistance => "<=>",
Self::Concat => "||",
Self::JsonGet => "->",
Self::JsonGetText => "->>",
Self::JsonGetPath => "#>",
Self::JsonGetPathText => "#>>",
Self::JsonContains => "@>",
})
}
}
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 {
items: vec![SelectItem::Wildcard],
from: Some(FromClause {
primary: TableRef {
name: "users".into(),
alias: None,
as_of_segment: None,
},
joins: vec![],
}),
where_: None,
group_by: None,
group_by_all: false,
having: None,
unions: vec![],
order_by: Vec::new(),
limit: None,
offset: None,
distinct: false,
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\"");
}
}