1use crate::{Engine, EngineError, QueryResult};
13use alloc::format;
14use alloc::string::String;
15use alloc::vec::Vec;
16
17impl Engine {
18 pub fn dump_sql(&mut self) -> Result<String, EngineError> {
25 let mut out = String::from("-- spg dump (self-consistent form)\n");
26 let mut tables = self.active_catalog().table_names();
27 tables.retain(|t| !t.starts_with("__spg_"));
28 tables.sort();
29
30 for name in &tables {
32 let Some(t) = self.active_catalog().get(name) else {
33 continue;
34 };
35 let schema = t.schema().clone();
36 let mut lines: Vec<String> = Vec::new();
37 for c in &schema.columns {
38 let mut line = format!(" {} {}", quote_ident(&c.name), ddl_type(c.ty));
39 if let Some(e) = &c.user_enum_type {
40 line = format!(" {} {}", quote_ident(&c.name), quote_ident(e));
41 }
42 if let Some(coll) = &c.collation_name
53 && !coll.eq_ignore_ascii_case("C")
54 && !coll.eq_ignore_ascii_case("default")
55 {
56 line.push_str(&format!(" COLLATE {}", quote_ident(coll)));
57 }
58 if !c.nullable {
59 line.push_str(" NOT NULL");
60 }
61 if let Some(d) = &c.default_text {
62 line.push_str(&format!(" DEFAULT {d}"));
63 }
64 lines.push(line);
65 }
66 for uc in &schema.uniqueness_constraints {
67 let cols: Vec<String> = uc
68 .columns
69 .iter()
70 .filter_map(|&p| schema.columns.get(p))
71 .map(|c| quote_ident(&c.name))
72 .collect();
73 let kind = if uc.is_primary_key {
74 "PRIMARY KEY"
75 } else if uc.nulls_not_distinct {
76 "UNIQUE NULLS NOT DISTINCT"
77 } else {
78 "UNIQUE"
79 };
80 lines.push(format!(" {kind} ({})", cols.join(", ")));
81 }
82 out.push_str(&format!(
83 "CREATE TABLE {} (\n{}\n);\n",
84 quote_ident(name),
85 lines.join(",\n")
86 ));
87 }
88
89 for name in &tables {
92 let rows = match self.execute(&format!("SELECT * FROM {}", quote_ident(name)))? {
93 QueryResult::Rows { rows, .. } => rows,
94 _ => continue,
95 };
96 for chunk in rows.chunks(100) {
97 let tuples: Vec<String> = chunk
98 .iter()
99 .map(|r| {
100 let vals: Vec<String> = r
101 .values
102 .iter()
103 .map(|v| format!("{}", crate::clock::value_to_literal(v.clone())))
104 .collect();
105 format!("({})", vals.join(", "))
106 })
107 .collect();
108 out.push_str(&format!(
109 "INSERT INTO {} VALUES {};\n",
110 quote_ident(name),
111 tuples.join(", ")
112 ));
113 }
114 }
115
116 if let QueryResult::Rows { rows, .. } = self.execute(
118 "SELECT indexdef FROM pg_indexes WHERE schemaname = 'public' ORDER BY indexname",
119 )? {
120 for r in rows {
121 let def = crate::eval::value_to_text(&r.values[0]);
122 if def.contains("_pkey") || def.contains("_key\"") || def.contains("_key ") {
125 continue;
126 }
127 out.push_str(&format!("{def};\n"));
128 }
129 }
130
131 let mut views: Vec<(String, Vec<String>, String)> = Vec::new();
133 for (name, v) in self.active_catalog().views_all() {
134 if name.starts_with("__spg_") {
135 continue;
136 }
137 views.push((v.name.clone(), v.columns.clone(), v.body.clone()));
138 }
139 views.sort();
140 for (name, columns, body) in views {
141 let cols = if columns.is_empty() {
142 String::new()
143 } else {
144 format!(
145 " ({})",
146 columns
147 .iter()
148 .map(|c| quote_ident(c))
149 .collect::<Vec<_>>()
150 .join(", ")
151 )
152 };
153 out.push_str(&format!(
154 "CREATE VIEW {}{cols} AS {body};\n",
155 quote_ident(&name)
156 ));
157 }
158 Ok(out)
159 }
160}
161
162fn ddl_type(ty: spg_storage::DataType) -> String {
170 use spg_storage::DataType as T;
171 match ty {
172 T::Varchar(n) if n > 0 => format!("varchar({n})"),
173 T::Char(n) if n > 0 => format!("char({n})"),
174 T::Numeric { precision, scale } if precision > 0 => {
175 format!("numeric({precision},{scale})")
176 }
177 T::TextArray => "text[]".into(),
181 T::IntArray => "integer[]".into(),
182 T::BigIntArray => "bigint[]".into(),
183 T::SmallIntArray => "smallint[]".into(),
184 T::FloatArray => "double precision[]".into(),
185 T::BoolArray => "boolean[]".into(),
186 T::NumericArray => "numeric[]".into(),
187 T::DateArray => "date[]".into(),
188 T::TimestampArray => "timestamp without time zone[]".into(),
189 T::TimestamptzArray => "timestamp with time zone[]".into(),
190 T::UuidArray => "uuid[]".into(),
191 T::JsonArray => "json[]".into(),
192 T::JsonbArray => "jsonb[]".into(),
193 T::BytesArray => "bytea[]".into(),
194 T::VarcharArray => "varchar[]".into(),
195 T::CharArray => "char[]".into(),
196 T::IntervalArray => "interval[]".into(),
197 T::OidArray => "oid[]".into(),
198 T::MoneyArray => "money[]".into(),
199 other => crate::system_catalog::pg_data_type_text(other),
200 }
201}
202
203fn quote_ident(s: &str) -> String {
205 if !s.is_empty()
206 && s.chars()
207 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
208 && !s.starts_with(|c: char| c.is_ascii_digit())
209 {
210 s.into()
211 } else {
212 format!("\"{}\"", s.replace('"', "\"\""))
213 }
214}