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 !c.nullable {
43 line.push_str(" NOT NULL");
44 }
45 if let Some(d) = &c.default_text {
46 line.push_str(&format!(" DEFAULT {d}"));
47 }
48 lines.push(line);
49 }
50 for uc in &schema.uniqueness_constraints {
51 let cols: Vec<String> = uc
52 .columns
53 .iter()
54 .filter_map(|&p| schema.columns.get(p))
55 .map(|c| quote_ident(&c.name))
56 .collect();
57 let kind = if uc.is_primary_key {
58 "PRIMARY KEY"
59 } else if uc.nulls_not_distinct {
60 "UNIQUE NULLS NOT DISTINCT"
61 } else {
62 "UNIQUE"
63 };
64 lines.push(format!(" {kind} ({})", cols.join(", ")));
65 }
66 out.push_str(&format!(
67 "CREATE TABLE {} (\n{}\n);\n",
68 quote_ident(name),
69 lines.join(",\n")
70 ));
71 }
72
73 for name in &tables {
76 let rows = match self.execute(&format!("SELECT * FROM {}", quote_ident(name)))? {
77 QueryResult::Rows { rows, .. } => rows,
78 _ => continue,
79 };
80 for chunk in rows.chunks(100) {
81 let tuples: Vec<String> = chunk
82 .iter()
83 .map(|r| {
84 let vals: Vec<String> = r
85 .values
86 .iter()
87 .map(|v| format!("{}", crate::clock::value_to_literal(v.clone())))
88 .collect();
89 format!("({})", vals.join(", "))
90 })
91 .collect();
92 out.push_str(&format!(
93 "INSERT INTO {} VALUES {};\n",
94 quote_ident(name),
95 tuples.join(", ")
96 ));
97 }
98 }
99
100 if let QueryResult::Rows { rows, .. } = self.execute(
102 "SELECT indexdef FROM pg_indexes WHERE schemaname = 'public' ORDER BY indexname",
103 )? {
104 for r in rows {
105 let def = crate::eval::value_to_text(&r.values[0]);
106 if def.contains("_pkey") || def.contains("_key\"") || def.contains("_key ") {
109 continue;
110 }
111 out.push_str(&format!("{def};\n"));
112 }
113 }
114
115 let mut views: Vec<(String, Vec<String>, String)> = Vec::new();
117 for (name, v) in self.active_catalog().views_all() {
118 if name.starts_with("__spg_") {
119 continue;
120 }
121 views.push((v.name.clone(), v.columns.clone(), v.body.clone()));
122 }
123 views.sort();
124 for (name, columns, body) in views {
125 let cols = if columns.is_empty() {
126 String::new()
127 } else {
128 format!(
129 " ({})",
130 columns
131 .iter()
132 .map(|c| quote_ident(c))
133 .collect::<Vec<_>>()
134 .join(", ")
135 )
136 };
137 out.push_str(&format!(
138 "CREATE VIEW {}{cols} AS {body};\n",
139 quote_ident(&name)
140 ));
141 }
142 Ok(out)
143 }
144}
145
146fn ddl_type(ty: spg_storage::DataType) -> String {
154 use spg_storage::DataType as T;
155 match ty {
156 T::Varchar(n) if n > 0 => format!("varchar({n})"),
157 T::Char(n) if n > 0 => format!("char({n})"),
158 T::Numeric { precision, scale } if precision > 0 => {
159 format!("numeric({precision},{scale})")
160 }
161 T::TextArray => "text[]".into(),
165 T::IntArray => "integer[]".into(),
166 T::BigIntArray => "bigint[]".into(),
167 T::SmallIntArray => "smallint[]".into(),
168 T::FloatArray => "double precision[]".into(),
169 T::BoolArray => "boolean[]".into(),
170 T::NumericArray => "numeric[]".into(),
171 T::DateArray => "date[]".into(),
172 T::TimestampArray => "timestamp without time zone[]".into(),
173 T::TimestamptzArray => "timestamp with time zone[]".into(),
174 T::UuidArray => "uuid[]".into(),
175 T::JsonArray => "json[]".into(),
176 T::JsonbArray => "jsonb[]".into(),
177 T::BytesArray => "bytea[]".into(),
178 T::VarcharArray => "varchar[]".into(),
179 T::CharArray => "char[]".into(),
180 T::IntervalArray => "interval[]".into(),
181 T::OidArray => "oid[]".into(),
182 T::MoneyArray => "money[]".into(),
183 other => crate::system_catalog::pg_data_type_text(other),
184 }
185}
186
187fn quote_ident(s: &str) -> String {
189 if !s.is_empty()
190 && s.chars()
191 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
192 && !s.starts_with(|c: char| c.is_ascii_digit())
193 {
194 s.into()
195 } else {
196 format!("\"{}\"", s.replace('"', "\"\""))
197 }
198}