tank-sqlite 0.32.0

SQLite driver implementation for Tank: the Rust data layer
Documentation
use std::{
    collections::{BTreeMap, HashMap},
    fmt::Write,
    mem,
};
use tank_core::{
    ColumnDef, ColumnRef, Context, DynQuery, Entity, Expression, GenericSqlWriter, SqlWriter,
    TableRef, Value, write_escaped,
};

/// SQL writer for SQLite dialect.
///
/// Emits SQLite specific SQL syntax to mantain compatibility with tank operations.
pub struct SQLiteSqlWriter {}

impl SqlWriter for SQLiteSqlWriter {
    fn as_dyn(&self) -> &dyn SqlWriter {
        self
    }

    fn write_column_overridden_type(
        &self,
        _context: &mut Context,
        out: &mut DynQuery,
        _column: &ColumnDef,
        types: &BTreeMap<&'static str, &'static str>,
    ) {
        if let Some(t) = types
            .iter()
            .find_map(|(k, v)| if *k == "sqlite" { Some(v) } else { None })
        {
            out.push_str(t);
        }
    }

    fn write_column_ref(&self, context: &mut Context, out: &mut DynQuery, value: &ColumnRef) {
        if context.qualify_columns {
            let table_ref = mem::take(&mut context.table_ref);
            let mut schema = &table_ref.schema;
            if schema.is_empty() {
                schema = &value.schema;
            }
            let mut table = &table_ref.alias;
            if table.is_empty() {
                table = &table_ref.name;
            }
            if table.is_empty() {
                table = &value.table;
            }
            if !table.is_empty() {
                out.push('"');
                if !schema.is_empty() {
                    write_escaped(out, schema, '"', "\"\"");
                    out.push_str(self.separator());
                }
                write_escaped(out, table, '"', "\"\"");
                out.push('"');
                out.push_str(self.separator());
            }
            context.table_ref = table_ref;
        }
        self.write_identifier(context, out, &value.name, true);
    }

    fn write_table_ref(&self, context: &mut Context, out: &mut DynQuery, value: &TableRef) {
        if self.is_alias_declaration(context) || value.alias.is_empty() {
            out.push('"');
            if !value.schema.is_empty() {
                write_escaped(out, &value.schema, '"', "\"\"");
                out.push_str(self.separator());
            }
            write_escaped(out, &value.name, '"', "\"\"");
            out.push('"');
        }
        if !value.alias.is_empty() {
            let _ = write!(out, " {}", value.alias);
        }
    }

    fn write_column_type(&self, _context: &mut Context, out: &mut DynQuery, value: &Value) {
        match value {
            Value::Boolean(..) => out.push_str("INTEGER"),
            Value::Int8(..) => out.push_str("INTEGER"),
            Value::Int16(..) => out.push_str("INTEGER"),
            Value::Int32(..) => out.push_str("INTEGER"),
            Value::Int64(..) => out.push_str("INTEGER"),
            Value::UInt8(..) => out.push_str("INTEGER"),
            Value::UInt16(..) => out.push_str("INTEGER"),
            Value::UInt32(..) => out.push_str("INTEGER"),
            Value::UInt64(..) => out.push_str("INTEGER"),
            Value::Float32(..) => out.push_str("REAL"),
            Value::Float64(..) => out.push_str("REAL"),
            Value::Decimal(.., precision, scale) => {
                out.push_str("REAL");
                if (precision, scale) != (&0, &0) {
                    let _ = write!(out, "({precision},{scale})");
                }
            }
            Value::Char(..) => out.push_str("TEXT"),
            Value::Varchar(..) => out.push_str("TEXT"),
            Value::Blob(..) => out.push_str("BLOB"),
            Value::Date(..) => out.push_str("TEXT"),
            Value::Time(..) => out.push_str("TEXT"),
            Value::Timestamp(..) => out.push_str("TEXT"),
            Value::TimestampWithTimezone(..) => out.push_str("TEXT"),
            Value::Uuid(..) => out.push_str("TEXT"),
            _ => log::error!("Unexpected tank::Value, SQLite does not support {value:?}"),
        };
    }

    fn write_value_f32(&self, context: &mut Context, out: &mut DynQuery, value: f32) {
        if value.is_infinite() {
            if value.is_sign_negative() {
                out.push('-');
            }
            out.push_str("1.0e+10000");
            return;
        }
        if value.is_nan() {
            log::warn!("SQLite does not support float NaN values, will write NULL instead");
            self.write_null(context, out);
            return;
        }
        GenericSqlWriter::new().write_value_f32(context, out, value);
    }

    fn write_value_f64(&self, context: &mut Context, out: &mut DynQuery, value: f64) {
        if value.is_infinite() {
            if value.is_sign_negative() {
                out.push('-');
            }
            out.push_str("1.0e+10000");
            return;
        }
        if value.is_nan() {
            log::warn!("SQLite does not support float NaN values, will write NULL instead");
            self.write_null(context, out);
            return;
        }
        GenericSqlWriter::new().write_value_f64(context, out, value);
    }

    fn write_blob(&self, _context: &mut Context, out: &mut DynQuery, value: &[u8]) {
        out.push_str("X'");
        for b in value {
            let _ = write!(out, "{:02X}", b);
        }
        out.push('\'');
    }

    fn write_current_timestamp_ms(&self, _context: &mut Context, out: &mut DynQuery) {
        out.push_str("(unixepoch('subsec') * 1000)");
    }

    fn write_list(
        &self,
        _context: &mut Context,
        _out: &mut DynQuery,
        _value: &mut dyn Iterator<Item = &dyn Expression>,
        _ty: Option<&Value>,
        _is_array: bool,
    ) {
        log::error!("SQLite does not support arrays or lists");
    }

    fn write_map(
        &self,
        _context: &mut Context,
        _out: &mut DynQuery,
        _value: &HashMap<Value, Value>,
    ) {
        log::error!("SQLite does not support maps");
    }

    fn write_create_schema<E>(&self, _out: &mut DynQuery, _if_not_exists: bool)
    where
        Self: Sized,
        E: Entity,
    {
        // SQLite does not support schema
    }

    fn write_drop_schema<E>(&self, _out: &mut DynQuery, _if_exists: bool)
    where
        Self: Sized,
        E: Entity,
    {
        // SQLite does not support schema
    }

    fn write_column_comments_statements<E>(&self, _context: &mut Context, _out: &mut DynQuery)
    where
        Self: Sized,
        E: Entity,
    {
    }
}