pg2sqlite_core/transform/
index.rs1use crate::diagnostics::warning::{self, Severity, Warning};
3use crate::ir::{Index, IndexColumn, IndexMethod, SchemaModel};
4use crate::transform::expr_map;
5
6pub fn transform_indexes(model: &mut SchemaModel, warnings: &mut Vec<Warning>) {
8 let mut kept = Vec::new();
9
10 for index in &model.indexes {
11 if let Some(idx) = transform_index(index, warnings) {
12 kept.push(idx);
13 }
14 }
15
16 model.indexes = kept;
17}
18
19fn transform_index(index: &Index, warnings: &mut Vec<Warning>) -> Option<Index> {
20 let obj = index.name.normalized.clone();
21
22 if let Some(method) = &index.method
24 && *method != IndexMethod::Btree
25 {
26 warnings.push(
27 Warning::new(
28 warning::INDEX_METHOD_IGNORED,
29 Severity::Info,
30 format!("index method '{method}' ignored; SQLite only supports btree"),
31 )
32 .with_object(&obj),
33 );
34 }
35
36 let where_clause = if let Some(where_expr) = &index.where_clause {
38 match expr_map::map_expr(where_expr, &obj, warnings) {
39 Some(mapped) => Some(mapped),
40 None => {
41 warnings.push(
42 Warning::new(
43 warning::PARTIAL_INDEX_UNSUPPORTED,
44 Severity::Unsupported,
45 "partial index WHERE clause uses unsupported PG features; index skipped",
46 )
47 .with_object(&obj),
48 );
49 return None;
50 }
51 }
52 } else {
53 None
54 };
55
56 let mut columns = Vec::new();
58 for col in &index.columns {
59 match col {
60 IndexColumn::Column(ident) => {
61 columns.push(IndexColumn::Column(ident.clone()));
62 }
63 IndexColumn::Expression(expr) => match expr_map::map_expr(expr, &obj, warnings) {
64 Some(mapped) => {
65 columns.push(IndexColumn::Expression(mapped));
66 }
67 None => {
68 warnings.push(
69 Warning::new(
70 warning::EXPRESSION_INDEX_UNSUPPORTED,
71 Severity::Unsupported,
72 "expression index uses unsupported PG features; index skipped",
73 )
74 .with_object(&obj),
75 );
76 return None;
77 }
78 },
79 }
80 }
81
82 Some(Index {
83 name: index.name.clone(),
84 table: index.table.clone(),
85 columns,
86 unique: index.unique,
87 method: None, where_clause,
89 })
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::ir::{Expr, Ident, QualifiedName};
96
97 fn make_index(name: &str, table: &str, cols: Vec<&str>) -> Index {
98 Index {
99 name: Ident::new(name),
100 table: QualifiedName::new(Ident::new(table)),
101 columns: cols
102 .into_iter()
103 .map(|c| IndexColumn::Column(Ident::new(c)))
104 .collect(),
105 unique: false,
106 method: None,
107 where_clause: None,
108 }
109 }
110
111 #[test]
112 fn test_simple_index_passthrough() {
113 let mut model = SchemaModel {
114 indexes: vec![make_index("idx_name", "users", vec!["name"])],
115 ..Default::default()
116 };
117 let mut w = Vec::new();
118 transform_indexes(&mut model, &mut w);
119 assert_eq!(model.indexes.len(), 1);
120 assert!(w.is_empty());
121 }
122
123 #[test]
124 fn test_gin_method_warned() {
125 let mut idx = make_index("idx_data", "t", vec!["data"]);
126 idx.method = Some(IndexMethod::Gin);
127 let mut model = SchemaModel {
128 indexes: vec![idx],
129 ..Default::default()
130 };
131 let mut w = Vec::new();
132 transform_indexes(&mut model, &mut w);
133 assert_eq!(model.indexes.len(), 1);
134 assert!(w.iter().any(|w| w.code == warning::INDEX_METHOD_IGNORED));
135 assert!(model.indexes[0].method.is_none());
136 }
137
138 #[test]
139 fn test_partial_index_with_compatible_where() {
140 let mut idx = make_index("idx_active", "users", vec!["email"]);
141 idx.where_clause = Some(Expr::IsNull {
142 expr: Box::new(Expr::ColumnRef("deleted_at".to_string())),
143 negated: false,
144 });
145 let mut model = SchemaModel {
146 indexes: vec![idx],
147 ..Default::default()
148 };
149 let mut w = Vec::new();
150 transform_indexes(&mut model, &mut w);
151 assert_eq!(model.indexes.len(), 1);
152 assert!(model.indexes[0].where_clause.is_some());
153 }
154}