use std::collections::HashMap;
use drizzle_types::Dialect;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParsedDefault {
Int(String),
Float(String),
Bool(bool),
Str(String),
Unsupported(String),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ParsedReference {
pub table: String,
pub column: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedGenerated {
pub expression: String,
pub stored: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ParsedIdentity {
pub always: bool,
pub start: Option<String>,
pub increment: Option<String>,
pub min_value: Option<String>,
pub max_value: Option<String>,
pub cache: Option<i32>,
pub cycle: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SerialKind {
Smallserial,
Serial,
Bigserial,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ColumnSpec {
pub primary: bool,
pub unique: bool,
pub autoincrement: bool,
pub json: bool,
pub jsonb: bool,
pub enum_marker: bool,
pub serial: Option<SerialKind>,
pub identity: Option<ParsedIdentity>,
pub generated: Option<ParsedGenerated>,
pub default: Option<ParsedDefault>,
pub default_sql: Option<String>,
pub has_default_fn: bool,
pub check: Option<String>,
pub references: Option<ParsedReference>,
pub on_delete: Option<String>,
pub on_update: Option<String>,
pub on_delete_raw: Option<String>,
pub on_update_raw: Option<String>,
pub deferrable: bool,
pub initially_deferred: bool,
pub explicit_name: Option<String>,
pub collate: Option<String>,
pub relation: Option<String>,
pub nullable: bool,
pub comment: Option<String>,
pub named_values: Vec<(String, String)>,
pub sqlite_type: Option<String>,
pub pg_type: Option<String>,
pub pg_dimensions: Option<i32>,
pub is_custom_type: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CompositeFkSpec {
pub source_columns: Vec<String>,
pub target_table: String,
pub target_columns: Vec<String>,
pub on_delete: Option<String>,
pub on_update: Option<String>,
pub deferrable: bool,
pub initially_deferred: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TableUniqueSpec {
pub columns: Vec<String>,
pub name: Option<String>,
pub nulls_not_distinct: bool,
pub deferrable: bool,
pub initially_deferred: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TableCheckSpec {
pub name: Option<String>,
pub expr: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TableSpec {
pub explicit_name: Option<String>,
pub schema: Option<String>,
pub strict: bool,
pub without_rowid: bool,
pub unlogged: bool,
pub temporary: bool,
pub rls: bool,
pub inherits: Option<String>,
pub tablespace: Option<String>,
pub composite_fks: Vec<CompositeFkSpec>,
pub unique_constraints: Vec<TableUniqueSpec>,
pub check_constraints: Vec<TableCheckSpec>,
pub comment: Option<String>,
pub named_values: Vec<(String, String)>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct IndexSpec {
pub unique: bool,
pub concurrent: bool,
pub method: Option<String>,
pub where_clause: Option<String>,
pub tablespace: Option<String>,
pub explicit_name: Option<String>,
pub column_refs: Vec<(String, String)>,
}
#[derive(Debug, Clone, Default)]
pub struct ParsedTable {
pub name: String,
pub attr: String,
pub fields: Vec<ParsedField>,
pub dialect: Dialect,
pub spec: TableSpec,
pub(crate) order: usize,
}
#[derive(Debug, Clone, Default)]
pub struct ParsedIndex {
pub name: String,
pub attr: String,
pub columns: Vec<String>,
pub dialect: Dialect,
pub spec: IndexSpec,
pub(crate) order: usize,
}
#[derive(Debug, Clone, Default)]
pub struct ParsedSchema {
pub name: String,
pub members: HashMap<String, String>,
pub dialect: Dialect,
pub(crate) member_types: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct ParsedField {
pub name: String,
pub ty: String,
pub attrs: Vec<String>,
pub spec: ColumnSpec,
}
#[derive(Debug, Clone, Default)]
pub struct ParsedEnum {
pub name: String,
pub variants: Vec<String>,
pub dialect: Dialect,
pub schema: Option<String>,
pub(crate) order: usize,
}
#[derive(Debug, Clone, Default)]
pub struct ParsedView {
pub name: String,
pub attr: String,
pub dialect: Dialect,
pub explicit_name: Option<String>,
pub schema: Option<String>,
pub definition: Option<String>,
pub has_opaque_definition: bool,
pub materialized: bool,
pub existing: bool,
pub with_no_data: bool,
pub using: Option<String>,
pub tablespace: Option<String>,
pub(crate) order: usize,
}
#[derive(Debug, Clone, Default)]
pub struct ParsedPolicy {
pub name: String,
pub attr: String,
pub dialect: Dialect,
pub table: String,
pub explicit_name: Option<String>,
pub as_clause: Option<String>,
pub for_clause: Option<String>,
pub to: Vec<String>,
pub using: Option<String>,
pub with_check: Option<String>,
pub(crate) order: usize,
}
#[derive(Debug, Clone, Default)]
pub struct ParseResult {
pub tables: HashMap<String, ParsedTable>,
pub indexes: HashMap<String, ParsedIndex>,
pub schema: Option<ParsedSchema>,
pub dialect: Dialect,
pub enums: HashMap<String, ParsedEnum>,
pub views: HashMap<String, ParsedView>,
pub policies: HashMap<String, ParsedPolicy>,
pub warnings: Vec<String>,
pub errors: Vec<String>,
}
impl ParsedTable {
#[must_use]
pub fn field(&self, name: &str) -> Option<&ParsedField> {
self.fields.iter().find(|f| f.name == name)
}
#[must_use]
pub fn has_table_attr(&self, attr: &str) -> bool {
self.attr.contains(attr)
}
#[must_use]
pub fn attr_value(&self, key: &str) -> Option<String> {
self.spec
.named_values
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(key))
.map(|(_, v)| v.clone())
}
#[must_use]
pub fn schema_name(&self) -> Option<String> {
self.spec.schema.clone()
}
#[must_use]
pub const fn is_strict(&self) -> bool {
self.spec.strict
}
#[must_use]
pub const fn is_without_rowid(&self) -> bool {
self.spec.without_rowid
}
#[must_use]
pub fn field_names(&self) -> Vec<&str> {
self.fields.iter().map(|f| f.name.as_str()).collect()
}
}
impl ParsedIndex {
#[must_use]
pub const fn is_unique(&self) -> bool {
self.spec.unique
}
#[must_use]
pub const fn is_concurrent(&self) -> bool {
self.spec.concurrent
}
#[must_use]
pub fn method(&self) -> Option<String> {
self.spec.method.clone()
}
#[must_use]
pub fn where_clause(&self) -> Option<String> {
self.spec.where_clause.clone()
}
#[must_use]
pub fn table_name(&self) -> Option<&str> {
self.spec
.column_refs
.first()
.map(|(table, _)| table.as_str())
}
}
impl ParsedField {
#[must_use]
pub fn has_attr(&self, attr: &str) -> bool {
self.attrs.iter().any(|a| a.contains(attr))
}
#[must_use]
pub fn attr_value(&self, key: &str) -> Option<String> {
self.spec
.named_values
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(key))
.map(|(_, v)| v.clone())
}
#[must_use]
pub fn attr_values(&self) -> HashMap<String, String> {
self.spec.named_values.iter().cloned().collect()
}
#[must_use]
pub fn column_attr(&self) -> String {
self.attrs.join(", ")
}
#[must_use]
pub const fn is_nullable(&self) -> bool {
self.spec.nullable
}
#[must_use]
pub const fn is_primary_key(&self) -> bool {
self.spec.primary
}
#[must_use]
pub const fn is_autoincrement(&self) -> bool {
self.spec.autoincrement
}
#[must_use]
pub const fn is_unique(&self) -> bool {
self.spec.unique
}
#[must_use]
pub fn default_value(&self) -> Option<String> {
self.attr_value("default")
.or_else(|| match self.spec.default.as_ref()? {
ParsedDefault::Int(s) | ParsedDefault::Float(s) => Some(s.clone()),
ParsedDefault::Bool(b) => Some(b.to_string()),
ParsedDefault::Str(s) => Some(format!("{s:?}")),
ParsedDefault::Unsupported(raw) => Some(raw.clone()),
})
}
#[must_use]
pub fn references(&self) -> Option<String> {
self.spec
.references
.as_ref()
.map(|r| format!("{}::{}", r.table, r.column))
}
#[must_use]
pub fn on_delete(&self) -> Option<String> {
self.spec.on_delete_raw.clone()
}
#[must_use]
pub fn on_update(&self) -> Option<String> {
self.spec.on_update_raw.clone()
}
}
impl ParseResult {
#[must_use]
pub fn table(&self, name: &str, dialect: Dialect) -> Option<&ParsedTable> {
self.tables.get(&entity_key(dialect, name))
}
#[must_use]
pub fn index(&self, name: &str, dialect: Dialect) -> Option<&ParsedIndex> {
self.indexes.get(&entity_key(dialect, name))
}
#[must_use]
pub fn parsed_enum(&self, name: &str, dialect: Dialect) -> Option<&ParsedEnum> {
self.enums.get(&entity_key(dialect, name))
}
#[must_use]
pub fn view(&self, name: &str, dialect: Dialect) -> Option<&ParsedView> {
self.views.get(&entity_key(dialect, name))
}
#[must_use]
pub fn policy(&self, name: &str, dialect: Dialect) -> Option<&ParsedPolicy> {
self.policies.get(&entity_key(dialect, name))
}
pub fn tables_for_dialect(&self, dialect: Dialect) -> impl Iterator<Item = &ParsedTable> {
let prefix = format!("{}:", dialect_key(dialect));
self.tables
.iter()
.filter(move |(k, _)| k.starts_with(&prefix))
.map(|(_, v)| v)
}
pub fn indexes_for_dialect(&self, dialect: Dialect) -> impl Iterator<Item = &ParsedIndex> {
let prefix = format!("{}:", dialect_key(dialect));
self.indexes
.iter()
.filter(move |(k, _)| k.starts_with(&prefix))
.map(|(_, v)| v)
}
#[must_use]
pub fn table_names(&self) -> Vec<&str> {
self.tables
.keys()
.filter_map(|s| s.split(':').nth(1))
.collect()
}
#[must_use]
pub fn index_names(&self) -> Vec<&str> {
self.indexes
.keys()
.filter_map(|s| s.split(':').nth(1))
.collect()
}
}
pub(crate) const fn dialect_key(dialect: Dialect) -> &'static str {
match dialect {
Dialect::SQLite => "sqlite",
Dialect::PostgreSQL => "postgres",
Dialect::MySQL => "mysql",
}
}
pub(crate) fn entity_key(dialect: Dialect, name: &str) -> String {
format!("{}:{}", dialect_key(dialect), name)
}