pg2sqlite_core/transform/
planner.rs1use crate::diagnostics::warning::{self, Severity, Warning};
3use crate::ir::{Expr, PgType, SchemaModel, TableConstraint};
4
5pub fn plan(model: &mut SchemaModel, warnings: &mut Vec<Warning>) {
7 merge_alter_constraints(model, warnings);
8 resolve_serials(model, warnings);
9 resolve_enums(model, warnings);
10}
11
12fn merge_alter_constraints(model: &mut SchemaModel, warnings: &mut Vec<Warning>) {
14 let alters = std::mem::take(&mut model.alter_constraints);
15
16 for alter in alters {
17 let target_table = model.tables.iter_mut().find(|t| t.name == alter.table);
18
19 match target_table {
20 Some(table) => {
21 table.constraints.push(alter.constraint);
22 }
23 None => {
24 warnings.push(
25 Warning::new(
26 warning::ALTER_TARGET_MISSING,
27 Severity::Unsupported,
28 format!(
29 "ALTER TABLE target '{}' not found; constraint skipped",
30 alter.table.name.normalized
31 ),
32 )
33 .with_object(&alter.table.name.normalized),
34 );
35 }
36 }
37 }
38}
39
40fn resolve_serials(model: &mut SchemaModel, warnings: &mut Vec<Warning>) {
44 let _sequence_names: Vec<String> = model
46 .sequences
47 .iter()
48 .map(|s| s.name.name.normalized.clone())
49 .collect();
50
51 for table in &mut model.tables {
52 let table_pk_columns: Vec<String> = table
54 .constraints
55 .iter()
56 .filter_map(|c| match c {
57 TableConstraint::PrimaryKey { columns, .. } => Some(
58 columns
59 .iter()
60 .map(|c| c.normalized.clone())
61 .collect::<Vec<_>>(),
62 ),
63 _ => None,
64 })
65 .flatten()
66 .collect();
67
68 for col in &mut table.columns {
69 let is_serial = matches!(
70 col.pg_type,
71 PgType::Serial | PgType::BigSerial | PgType::SmallSerial
72 );
73
74 let has_nextval = matches!(&col.default, Some(Expr::NextVal(_)));
76
77 if !is_serial && !has_nextval {
78 continue;
79 }
80
81 let obj = format!("{}.{}", table.name.name.normalized, col.name.normalized);
82
83 let is_sole_pk = col.is_primary_key
85 || (table_pk_columns.len() == 1 && table_pk_columns[0] == col.name.normalized);
86
87 if is_sole_pk {
88 col.pg_type = PgType::Integer;
89 col.is_primary_key = true;
90 col.default = None;
91
92 warnings.push(
94 Warning::new(
95 warning::SERIAL_TO_ROWID,
96 Severity::Lossy,
97 "SERIAL column mapped to INTEGER PRIMARY KEY (rowid alias)",
98 )
99 .with_object(&obj),
100 );
101 } else {
102 col.pg_type = PgType::Integer;
103 col.default = None;
104
105 warnings.push(
106 Warning::new(
107 warning::SERIAL_NOT_PRIMARY_KEY,
108 Severity::Lossy,
109 "SERIAL column is not the sole primary key; mapped to INTEGER without auto-increment",
110 )
111 .with_object(&obj),
112 );
113 }
114 }
115 }
116
117 for seq in &model.sequences {
119 warnings.push(
120 Warning::new(
121 warning::SEQUENCE_IGNORED,
122 Severity::Info,
123 format!(
124 "sequence '{}' ignored (absorbed into SERIAL handling or unused)",
125 seq.name.name.normalized
126 ),
127 )
128 .with_object(&seq.name.name.normalized),
129 );
130 }
131}
132
133fn resolve_enums(model: &mut SchemaModel, _warnings: &mut [Warning]) {
135 let enum_names: std::collections::HashSet<String> = model
136 .enums
137 .iter()
138 .map(|e| e.name.name.normalized.clone())
139 .collect();
140
141 for table in &mut model.tables {
142 for col in &mut table.columns {
143 if let PgType::Other { name } = &col.pg_type
144 && enum_names.contains(name)
145 {
146 col.pg_type = PgType::Enum { name: name.clone() };
147 }
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::ir::{AlterConstraint, Column, FkAction, Ident, QualifiedName, Table};
156
157 fn make_table(name: &str, columns: Vec<Column>, constraints: Vec<TableConstraint>) -> Table {
158 Table {
159 name: QualifiedName::new(Ident::new(name)),
160 columns,
161 constraints,
162 }
163 }
164
165 fn make_column(name: &str, pg_type: PgType) -> Column {
166 Column {
167 name: Ident::new(name),
168 pg_type,
169 sqlite_type: None,
170 not_null: false,
171 default: None,
172 is_primary_key: false,
173 is_unique: false,
174 references: None,
175 check: None,
176 }
177 }
178
179 #[test]
180 fn test_merge_alter_constraints() {
181 let mut model = SchemaModel {
182 tables: vec![make_table(
183 "orders",
184 vec![
185 make_column("id", PgType::Integer),
186 make_column("user_id", PgType::Integer),
187 ],
188 vec![],
189 )],
190 alter_constraints: vec![AlterConstraint {
191 table: QualifiedName::new(Ident::new("orders")),
192 constraint: TableConstraint::ForeignKey {
193 name: Some(Ident::new("fk_user")),
194 columns: vec![Ident::new("user_id")],
195 ref_table: QualifiedName::new(Ident::new("users")),
196 ref_columns: vec![Ident::new("id")],
197 on_delete: Some(FkAction::Cascade),
198 on_update: None,
199 deferrable: false,
200 },
201 }],
202 ..Default::default()
203 };
204 let mut w = Vec::new();
205 plan(&mut model, &mut w);
206 assert_eq!(model.tables[0].constraints.len(), 1);
207 }
208
209 #[test]
210 fn test_alter_target_missing() {
211 let mut model = SchemaModel {
212 tables: vec![],
213 alter_constraints: vec![AlterConstraint {
214 table: QualifiedName::new(Ident::new("nonexistent")),
215 constraint: TableConstraint::Check {
216 name: None,
217 expr: Expr::Raw("true".to_string()),
218 },
219 }],
220 ..Default::default()
221 };
222 let mut w = Vec::new();
223 plan(&mut model, &mut w);
224 assert!(w.iter().any(|w| w.code == warning::ALTER_TARGET_MISSING));
225 }
226
227 #[test]
228 fn test_serial_sole_pk() {
229 let mut col = make_column("id", PgType::Serial);
230 col.is_primary_key = true;
231 let mut model = SchemaModel {
232 tables: vec![make_table("users", vec![col], vec![])],
233 ..Default::default()
234 };
235 let mut w = Vec::new();
236 plan(&mut model, &mut w);
237 assert_eq!(model.tables[0].columns[0].pg_type, PgType::Integer);
238 assert!(model.tables[0].columns[0].is_primary_key);
239 assert!(w.iter().any(|w| w.code == warning::SERIAL_TO_ROWID));
240 }
241
242 #[test]
243 fn test_serial_not_pk() {
244 let col = make_column("counter", PgType::Serial);
245 let mut model = SchemaModel {
246 tables: vec![make_table("t", vec![col], vec![])],
247 ..Default::default()
248 };
249 let mut w = Vec::new();
250 plan(&mut model, &mut w);
251 assert_eq!(model.tables[0].columns[0].pg_type, PgType::Integer);
252 assert!(w.iter().any(|w| w.code == warning::SERIAL_NOT_PRIMARY_KEY));
253 }
254}