use crate::SQLConstraintKind;
use crate::prelude::*;
use crate::{Param, Placeholder, SQLParam, sql::tokens::Token};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColumnDialect {
SQLite {
autoincrement: bool,
},
PostgreSQL {
postgres_type: &'static str,
is_serial: bool,
is_bigserial: bool,
is_generated_identity: bool,
is_identity_always: bool,
generated_expression: Option<&'static str>,
generated_stored: bool,
collate: Option<&'static str>,
},
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TableDialect {
#[default]
PostgreSQL,
SQLite {
without_rowid: bool,
strict: bool,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ForeignKeyRef {
pub target_table: &'static str,
pub source_columns: &'static [&'static str],
pub target_columns: &'static [&'static str],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PrimaryKeyRef {
pub columns: &'static [&'static str],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ConstraintRef {
pub name: Option<&'static str>,
pub kind: SQLConstraintKind,
pub columns: &'static [&'static str],
pub check_expression: Option<&'static str>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TableRef {
pub name: &'static str,
pub column_names: &'static [&'static str],
pub schema: Option<&'static str>,
pub qualified_name: &'static str,
pub columns: &'static [ColumnRef],
pub primary_key: Option<PrimaryKeyRef>,
pub foreign_keys: &'static [ForeignKeyRef],
pub constraints: &'static [ConstraintRef],
pub dependency_names: &'static [&'static str],
pub dialect: TableDialect,
}
impl TableRef {
#[must_use]
pub const fn sql(name: &'static str, column_names: &'static [&'static str]) -> Self {
Self {
name,
column_names,
schema: None,
qualified_name: "",
columns: &[],
primary_key: None,
foreign_keys: &[],
constraints: &[],
dependency_names: &[],
dialect: TableDialect::PostgreSQL,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct ColumnFlags(u8);
impl ColumnFlags {
pub const NOT_NULL: Self = Self(1 << 0);
pub const PRIMARY_KEY: Self = Self(1 << 1);
pub const UNIQUE: Self = Self(1 << 2);
pub const HAS_DEFAULT: Self = Self(1 << 3);
#[must_use]
pub const fn empty() -> Self {
Self(0)
}
#[must_use]
pub const fn from_bits(bits: u8) -> Self {
Self(bits)
}
#[must_use]
pub const fn bits(self) -> u8 {
self.0
}
#[must_use]
pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
#[must_use]
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
}
impl core::ops::BitOr for ColumnFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
impl core::ops::BitOrAssign for ColumnFlags {
fn bitor_assign(&mut self, rhs: Self) {
*self = self.union(rhs);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ColumnRef {
pub table: &'static str,
pub name: &'static str,
pub sql_type: &'static str,
pub flags: ColumnFlags,
pub dialect: ColumnDialect,
}
impl ColumnRef {
#[must_use]
pub const fn sql(table: &'static str, name: &'static str) -> Self {
Self {
table,
name,
sql_type: "",
flags: ColumnFlags::empty(),
dialect: ColumnDialect::SQLite {
autoincrement: false,
},
}
}
#[must_use]
pub const fn not_null(&self) -> bool {
self.flags.contains(ColumnFlags::NOT_NULL)
}
#[must_use]
pub const fn primary_key(&self) -> bool {
self.flags.contains(ColumnFlags::PRIMARY_KEY)
}
#[must_use]
pub const fn unique(&self) -> bool {
self.flags.contains(ColumnFlags::UNIQUE)
}
#[must_use]
pub const fn has_default(&self) -> bool {
self.flags.contains(ColumnFlags::HAS_DEFAULT)
}
}
#[inline]
pub fn write_quoted_ident(buf: &mut impl core::fmt::Write, name: &str) {
let _ = buf.write_char('"');
if name.contains('"') {
for ch in name.chars() {
if ch == '"' {
let _ = buf.write_str("\"\"");
} else {
let _ = buf.write_char(ch);
}
}
} else {
let _ = buf.write_str(name);
}
let _ = buf.write_char('"');
}
#[derive(Clone)]
pub enum SQLChunk<'a, V: SQLParam> {
Token(Token),
Ident(Cow<'a, str>),
Raw(Cow<'a, str>),
Number(usize),
Param(Param<'a, V>),
Table(TableRef),
Column(ColumnRef),
}
impl<'a, V: SQLParam> SQLChunk<'a, V> {
#[inline]
#[must_use]
pub const fn token(t: Token) -> Self {
Self::Token(t)
}
#[inline]
#[must_use]
pub const fn ident_static(name: &'static str) -> Self {
Self::Ident(Cow::Borrowed(name))
}
#[inline]
#[must_use]
pub const fn raw_static(text: &'static str) -> Self {
Self::Raw(Cow::Borrowed(text))
}
#[inline]
#[must_use]
pub const fn table(table: TableRef) -> Self {
Self::Table(table)
}
#[inline]
#[must_use]
pub const fn column(column: ColumnRef) -> Self {
Self::Column(column)
}
#[inline]
pub const fn param_borrowed(value: &'a V, placeholder: Placeholder) -> Self {
Self::Param(Param {
value: Some(Cow::Borrowed(value)),
placeholder,
})
}
#[inline]
pub fn ident(name: impl Into<Cow<'a, str>>) -> Self {
Self::Ident(name.into())
}
#[inline]
pub fn raw(text: impl Into<Cow<'a, str>>) -> Self {
Self::Raw(text.into())
}
#[inline]
#[must_use]
pub const fn number(value: usize) -> Self {
Self::Number(value)
}
#[inline]
pub fn param(value: impl Into<Cow<'a, V>>, placeholder: Placeholder) -> Self {
Self::Param(Param {
value: Some(value.into()),
placeholder,
})
}
pub(crate) fn write(&self, buf: &mut impl core::fmt::Write) {
match self {
SQLChunk::Token(token) => {
let _ = buf.write_str(token.as_str());
}
SQLChunk::Ident(name) => {
write_quoted_ident(buf, name);
}
SQLChunk::Raw(text) => {
let _ = buf.write_str(text);
}
SQLChunk::Number(value) => {
let _ = write!(buf, "{value}");
}
SQLChunk::Param(Param { placeholder, .. }) => {
let _ = write!(buf, "{placeholder}");
}
SQLChunk::Table(t) => {
write_quoted_ident(buf, t.name);
}
SQLChunk::Column(c) => {
write_quoted_ident(buf, c.table);
let _ = buf.write_char('.');
write_quoted_ident(buf, c.name);
}
}
}
#[inline]
pub(crate) const fn is_word_like(&self) -> bool {
match self {
SQLChunk::Token(t) => !t.is_punctuation() && !t.is_operator(),
SQLChunk::Ident(_)
| SQLChunk::Raw(_)
| SQLChunk::Number(_)
| SQLChunk::Param(_)
| SQLChunk::Table(_)
| SQLChunk::Column(_) => true,
}
}
}
impl<V: SQLParam + core::fmt::Debug> core::fmt::Debug for SQLChunk<'_, V> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SQLChunk::Token(token) => f.debug_tuple("Token").field(token).finish(),
SQLChunk::Ident(name) => f.debug_tuple("Ident").field(name).finish(),
SQLChunk::Raw(text) => f.debug_tuple("Raw").field(text).finish(),
SQLChunk::Number(value) => f.debug_tuple("Number").field(value).finish(),
SQLChunk::Param(param) => f.debug_tuple("Param").field(param).finish(),
SQLChunk::Table(t) => f.debug_tuple("Table").field(&t.name).finish(),
SQLChunk::Column(c) => f
.debug_tuple("Column")
.field(&format!("{}.{}", c.table, c.name))
.finish(),
}
}
}
impl<V: SQLParam> From<Token> for SQLChunk<'_, V> {
#[inline]
fn from(value: Token) -> Self {
Self::Token(value)
}
}
impl<V: SQLParam> From<TableRef> for SQLChunk<'_, V> {
#[inline]
fn from(value: TableRef) -> Self {
Self::Table(value)
}
}
impl<V: SQLParam> From<ColumnRef> for SQLChunk<'_, V> {
#[inline]
fn from(value: ColumnRef) -> Self {
Self::Column(value)
}
}
impl<'a, V: SQLParam> From<Param<'a, V>> for SQLChunk<'a, V> {
#[inline]
fn from(value: Param<'a, V>) -> Self {
Self::Param(value)
}
}