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(), 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 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}