pushkin-compiler 0.1.0

Schema compiler for pushkin: canonical JSON Schema emission and generated bindings
Documentation
//! SQL DDL emission, contract-as-truth direction (spec §5.2 table).

use std::fmt::Write as _;

use crate::schema::{ContractSchema, PropertyKind};

pub fn emit(schema: &ContractSchema, epoch: u32) -> String {
    let mut columns = Vec::new();
    for property in &schema.properties {
        let PropertyKind::String {
            max_length,
            enum_values,
            default,
            ..
        } = &property.kind;

        let base_type = match max_length {
            Some(max) => format!("VARCHAR({max})"),
            None => "TEXT".to_owned(),
        };
        let mut column = format!("    {} {base_type}", identifier(&property.name));
        if property.required || default.is_some() {
            column.push_str(" NOT NULL");
        }
        if let Some(value) = default {
            let _ = write!(column, " DEFAULT {}", string_literal(value));
        }
        if let Some(values) = enum_values {
            let list = values
                .iter()
                .map(|value| string_literal(value))
                .collect::<Vec<_>>()
                .join(", ");
            let _ = write!(
                column,
                " CHECK ({} IN ({list}))",
                identifier(&property.name)
            );
        }
        columns.push(column);
    }

    format!(
        "-- GENERATED by pushkin compile from contract '{name}' — do not hand-edit.\n\
         -- pushkin-epoch: {epoch}\n\
         CREATE TABLE {table} (\n{columns}\n);\n",
        name = schema.contract_name,
        table = identifier(&schema.contract_name),
        columns = columns.join(",\n"),
    )
}

/// Every table/column identifier is double-quoted (S3 human decision, option
/// a): S2-valid names that collide with SQL reserved words (`user`, `order`)
/// stay executable, and quoting is semantics-preserving for lowercase names
/// Postgres would fold anyway. `"` doubling mirrors `string_literal`'s `''`.
fn identifier(name: &str) -> String {
    format!("\"{}\"", name.replace('"', "\"\""))
}

fn string_literal(value: &str) -> String {
    format!("'{}'", value.replace('\'', "''"))
}