endpoint_gen/
sql.rs

1use crate::model::{Field, ProceduralFunction, Type};
2use crate::Data;
3use convert_case::{Case, Casing};
4use itertools::Itertools;
5use std::fs::File;
6use std::io::Write;
7
8pub const PARAM_PREFIX: &str = "a_";
9
10pub trait ToSql {
11    fn to_sql(&self) -> String;
12}
13
14impl ToSql for Type {
15    fn to_sql(&self) -> String {
16        match self {
17            Type::Date => "int".to_owned(), // TODO: fix things
18            Type::Int => "int".to_owned(),
19            Type::BigInt => "bigint".to_owned(),
20            Type::TimeStampMs => "bigint".to_owned(),
21            Type::Numeric => "double precision".to_owned(),
22            Type::Struct { fields, .. } => {
23                let fields = fields.iter().map(|x| format!("\"{}\" {}", x.name, x.ty.to_sql()));
24                format!("table (\n{}\n)", fields.map(|x| format!("    {}", x)).join(",\n"))
25            }
26            Type::StructRef(_name) => "jsonb".to_owned(),
27            Type::Object => "jsonb".to_owned(),
28            Type::DataTable { .. } => {
29                todo!()
30            }
31            Type::Vec(fields) => {
32                format!("{}[]", fields.to_sql())
33            }
34            Type::Unit => "void".to_owned(),
35            Type::Optional(t) => t.to_sql().to_string(),
36            Type::Boolean => "boolean".to_owned(),
37            Type::String => "varchar".to_owned(),
38            Type::Bytea => "bytea".to_owned(),
39            Type::UUID => "uuid".to_owned(),
40            Type::Inet => "inet".to_owned(),
41            Type::Enum { name, .. } => format!("enum_{}", name),
42            Type::EnumRef(name) => format!("enum_{}", name),
43            // 38 digits in total, with 4-18 decimal digits. So to be exact we need 38+18 digits
44            Type::BlockchainDecimal => "decimal(56, 18)".to_owned(),
45            Type::BlockchainAddress => "varchar".to_owned(),
46            Type::BlockchainTransactionHash => "varchar".to_owned(),
47        }
48    }
49}
50impl ToSql for ProceduralFunction {
51    fn to_sql(&self) -> String {
52        let params = self
53            .parameters
54            .iter()
55            .map(|x| match &x.ty {
56                Type::Optional(y) => {
57                    format!("{}{} {} DEFAULT NULL", PARAM_PREFIX, x.name, y.to_sql())
58                }
59                y => format!("{}{} {}", PARAM_PREFIX, x.name, y.to_sql()),
60            })
61            .join(", ");
62        format!(
63            "
64CREATE OR REPLACE FUNCTION api.{name}({params})
65RETURNS {returns}
66LANGUAGE plpgsql
67AS $$
68    {body}
69$$;
70        ",
71            name = self.name,
72            params = params,
73            returns = match &self.return_row_type {
74                Type::Struct { fields, .. } if fields.is_empty() => "void".to_owned(),
75                x => x.to_sql(),
76            },
77            body = self.body
78        )
79    }
80}
81
82pub fn gen_db_sql(data: &Data) -> eyre::Result<()> {
83    let funcs = &data.pg_funcs;
84
85    let db_filename = data.project_root.join("db/api.sql");
86    let mut f = File::create(db_filename)?;
87    writeln!(&mut f, "CREATE SCHEMA IF NOT EXISTS api;")?;
88    for func in funcs {
89        writeln!(&mut f, "{}", func.to_sql())?;
90    }
91    for srv in &data.services {
92        writeln!(
93            &mut f,
94            "{}",
95            ProceduralFunction::new(
96                format!("{}_SERVICE", srv.name.to_case(Case::ScreamingSnake)),
97                vec![],
98                vec![Field::new("code", Type::Int)],
99                format!("BEGIN RETURN QUERY SELECT {}; END", srv.id),
100            )
101            .to_sql()
102        )?;
103    }
104    f.flush()?;
105    drop(f);
106
107    Ok(())
108}
109
110pub fn gen_model_sql(data: &Data) -> eyre::Result<()> {
111    let db_filename = data.project_root.join("db/model.sql");
112    let mut f = File::create(db_filename)?;
113
114    for e in &data.enums {
115        match e {
116            Type::Enum { name, variants } => {
117                writeln!(
118                    &mut f,
119                    "CREATE TYPE enum_{} AS ENUM ({});",
120                    name,
121                    variants.iter().map(|x| format!("'{}'", x.name)).join(", ")
122                )?;
123            }
124            _ => unreachable!(),
125        }
126    }
127    f.flush()?;
128    drop(f);
129    Ok(())
130}