1pub mod mysql;
12
13use crate::error::MigrationError;
14use crate::operation::Operation;
15use crate::state::{
16 ConstraintState, DbDefault, FieldState, ForeignKeyState, ModelState, OnDelete, ProjectState,
17 SqlType, auto_index_name,
18};
19use siderite_orm::BackendKind;
20
21pub fn quote_ident(ident: &str) -> String {
23 let mut out = String::with_capacity(ident.len() + 2);
24 out.push('"');
25 for ch in ident.chars() {
26 if ch == '"' {
27 out.push('"');
28 }
29 out.push(ch);
30 }
31 out.push('"');
32 out
33}
34
35pub fn quote_identifier(kind: BackendKind, ident: &str) -> String {
38 match kind {
39 BackendKind::MySql => mysql::quote_ident(ident),
40 _ => quote_ident(ident),
41 }
42}
43
44pub fn quote_string(value: &str) -> String {
46 let mut out = String::with_capacity(value.len() + 2);
47 out.push('\'');
48 for ch in value.chars() {
49 if ch == '\'' {
50 out.push('\'');
51 }
52 out.push(ch);
53 }
54 out.push('\'');
55 out
56}
57
58pub fn statements(
65 kind: BackendKind,
66 state: &ProjectState,
67 operations: &[Operation],
68) -> Result<Vec<String>, MigrationError> {
69 require_sql(kind)?;
70 let mut current = state.clone();
71 let mut out = Vec::new();
72 for op in operations {
73 out.extend(render(kind, op, ¤t)?);
74 op.apply_to_state(&mut current)?;
75 }
76 Ok(out)
77}
78
79pub fn render(
84 kind: BackendKind,
85 op: &Operation,
86 state: &ProjectState,
87) -> Result<Vec<String>, MigrationError> {
88 require_sql(kind)?;
89 if kind == BackendKind::MySql {
90 return mysql::render(op, state);
91 }
92 match op {
93 Operation::CreateModel { model } => Ok(create_model_sql(kind, model)),
94 Operation::DeleteModel { name } => {
95 let model = state.require(name)?;
96 Ok(vec![format!("DROP TABLE {}", quote_ident(&model.table))])
97 }
98 Operation::RenameModel {
99 old_name, table, ..
100 } => {
101 let model = state.require(old_name)?;
102 if model.table == *table {
103 Ok(Vec::new())
104 } else {
105 Ok(vec![format!(
106 "ALTER TABLE {} RENAME TO {}",
107 quote_ident(&model.table),
108 quote_ident(table)
109 )])
110 }
111 }
112 Operation::AddField { model, field } => add_field_sql(kind, state.require(model)?, field),
113 Operation::RemoveField { model, name } => {
114 remove_field_sql(kind, state.require(model)?, name)
115 }
116 Operation::AlterField { model, name, field } => {
117 alter_field_sql(kind, state.require(model)?, name, field)
118 }
119 Operation::RenameField {
120 model,
121 old_name,
122 new_name,
123 } => rename_field_sql(kind, state.require(model)?, old_name, new_name),
124 Operation::CreateIndex { model, index } => {
125 let m = state.require(model)?;
126 Ok(vec![create_index_sql(&m.table, index)])
127 }
128 Operation::DeleteIndex { model, name } => {
129 state.require(model)?;
130 Ok(vec![format!("DROP INDEX {}", quote_ident(name))])
131 }
132 Operation::AddConstraint { model, constraint } => {
133 add_constraint_sql(kind, state.require(model)?, constraint)
134 }
135 Operation::DeleteConstraint { model, name } => {
136 delete_constraint_sql(kind, state.require(model)?, name)
137 }
138 Operation::RunSQL { sql, .. } => Ok(vec![sql.clone()]),
139 Operation::RunRust { .. } => Ok(Vec::new()),
140 }
141}
142
143fn require_sql(kind: BackendKind) -> Result<(), MigrationError> {
144 match kind {
145 BackendKind::Postgres | BackendKind::Sqlite | BackendKind::MySql => Ok(()),
146 other => Err(MigrationError::UnsupportedBackend(other)),
147 }
148}
149
150fn create_model_sql(kind: BackendKind, model: &ModelState) -> Vec<String> {
151 let mut stmts = vec![create_table_sql(kind, model, &model.table)];
152 stmts.extend(index_sqls(model));
153 stmts
154}
155
156fn create_table_sql(kind: BackendKind, model: &ModelState, table: &str) -> String {
157 let mut parts: Vec<String> = model.fields.iter().map(|f| column_def(kind, f)).collect();
158 for constraint in &model.constraints {
159 parts.push(table_constraint_sql(constraint));
160 }
161 let body = parts.join(",\n ");
162 format!("CREATE TABLE {} (\n {}\n)", quote_ident(table), body)
163}
164
165fn table_constraint_sql(constraint: &ConstraintState) -> String {
166 match constraint {
167 ConstraintState::Unique { name, columns } => {
168 format!(
169 "CONSTRAINT {} UNIQUE ({})",
170 quote_ident(name),
171 columns
172 .iter()
173 .map(|c| quote_ident(c))
174 .collect::<Vec<_>>()
175 .join(", ")
176 )
177 }
178 ConstraintState::Check { name, sql } => {
179 format!("CONSTRAINT {} CHECK ({sql})", quote_ident(name))
180 }
181 }
182}
183
184fn column_def(kind: BackendKind, field: &FieldState) -> String {
185 let mut s = format!("{} {}", quote_ident(&field.column), type_sql(kind, field));
186 if field.auto && field.primary_key {
187 if let Some(fk) = &field.fk {
188 s.push(' ');
189 s.push_str(&references_sql(fk));
190 }
191 return s;
192 }
193 if !field.nullable {
194 s.push_str(" NOT NULL");
195 }
196 if let Some(default) = &field.default {
197 s.push_str(" DEFAULT ");
198 s.push_str(&default_sql(kind, default));
199 }
200 if field.unique && !field.primary_key {
201 s.push_str(" UNIQUE");
202 }
203 if field.primary_key {
204 s.push_str(" PRIMARY KEY");
205 }
206 if let Some(fk) = &field.fk {
207 s.push(' ');
208 s.push_str(&references_sql(fk));
209 }
210 s
211}
212
213fn type_sql(kind: BackendKind, field: &FieldState) -> String {
214 if field.auto && field.primary_key {
215 return match kind {
216 BackendKind::Sqlite => "INTEGER PRIMARY KEY AUTOINCREMENT".to_owned(),
217 BackendKind::Postgres => {
218 let base = match field.sql_type {
219 SqlType::SmallInt | SqlType::Integer => "INTEGER",
220 _ => "BIGINT",
221 };
222 format!("{base} GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY")
223 }
224 _ => sql_type_name(kind, field),
225 };
226 }
227 sql_type_name(kind, field)
228}
229
230fn sql_type_name(kind: BackendKind, field: &FieldState) -> String {
231 match (kind, field.sql_type) {
232 (_, SqlType::Decimal) => match (field.max_digits, field.decimal_places) {
233 (Some(p), Some(s)) => format!("NUMERIC({p},{s})"),
234 _ => "NUMERIC".to_owned(),
235 },
236 (_, SqlType::Text) => match field.max_length {
237 Some(n) => format!("VARCHAR({n})"),
238 None => "TEXT".to_owned(),
239 },
240 (_, SqlType::Duration) => "BIGINT".to_owned(),
241 (_, SqlType::IpAddr) => "TEXT".to_owned(),
242 (BackendKind::Postgres, SqlType::SmallInt) => "SMALLINT".to_owned(),
243 (BackendKind::Postgres, SqlType::Integer) => "INTEGER".to_owned(),
244 (BackendKind::Postgres, SqlType::BigInt) => "BIGINT".to_owned(),
245 (BackendKind::Postgres, SqlType::Real) => "REAL".to_owned(),
246 (BackendKind::Postgres, SqlType::Double) => "DOUBLE PRECISION".to_owned(),
247 (BackendKind::Postgres, SqlType::Bool) => "BOOLEAN".to_owned(),
248 (BackendKind::Postgres, SqlType::Binary) => "BYTEA".to_owned(),
249 (BackendKind::Postgres, SqlType::Date) => "DATE".to_owned(),
250 (BackendKind::Postgres, SqlType::Time) => "TIME".to_owned(),
251 (BackendKind::Postgres, SqlType::Timestamp) => "TIMESTAMPTZ".to_owned(),
252 (BackendKind::Postgres, SqlType::Uuid) => "UUID".to_owned(),
253 (BackendKind::Postgres, SqlType::Json) => "jsonb".to_owned(),
254 (BackendKind::Sqlite, SqlType::SmallInt | SqlType::Integer) => "INTEGER".to_owned(),
255 (BackendKind::Sqlite, SqlType::BigInt) => "BIGINT".to_owned(),
256 (BackendKind::Sqlite, SqlType::Real | SqlType::Double) => "REAL".to_owned(),
257 (BackendKind::Sqlite, SqlType::Bool) => "INTEGER".to_owned(),
258 (BackendKind::Sqlite, SqlType::Binary) => "BLOB".to_owned(),
259 (BackendKind::Sqlite, SqlType::Date | SqlType::Time | SqlType::Timestamp) => {
260 "TEXT".to_owned()
261 }
262 (BackendKind::Sqlite, SqlType::Uuid | SqlType::Json) => "TEXT".to_owned(),
263 _ => "TEXT".to_owned(),
264 }
265}
266
267fn default_sql(kind: BackendKind, default: &DbDefault) -> String {
268 match default {
269 DbDefault::Now => match kind {
270 BackendKind::Postgres => "CURRENT_TIMESTAMP".to_owned(),
271 BackendKind::Sqlite => {
272 "(strftime('%Y-%m-%dT%H:%M:%fZ','now'))".to_owned()
275 }
276 _ => "CURRENT_TIMESTAMP".to_owned(),
277 },
278 DbDefault::Int(i) => i.to_string(),
279 DbDefault::Bool(true) => match kind {
280 BackendKind::Sqlite => "1".to_owned(),
281 _ => "TRUE".to_owned(),
282 },
283 DbDefault::Bool(false) => match kind {
284 BackendKind::Sqlite => "0".to_owned(),
285 _ => "FALSE".to_owned(),
286 },
287 DbDefault::Text(s) => quote_string(s),
288 }
289}
290
291fn references_sql(fk: &ForeignKeyState) -> String {
292 format!(
293 "REFERENCES {} ({}) ON DELETE {}",
294 quote_ident(&fk.target_table),
295 quote_ident(&fk.target_column),
296 on_delete_sql(fk.on_delete)
297 )
298}
299
300fn on_delete_sql(policy: OnDelete) -> &'static str {
301 match policy {
302 OnDelete::Cascade => "CASCADE",
303 OnDelete::Protect => "RESTRICT",
304 OnDelete::SetNull => "SET NULL",
305 OnDelete::SetDefault => "SET DEFAULT",
306 OnDelete::DoNothing => "NO ACTION",
307 }
308}
309
310fn index_sqls(model: &ModelState) -> Vec<String> {
311 let mut stmts = Vec::new();
312 let mut seen = std::collections::BTreeSet::new();
313 for field in &model.fields {
314 if field.index && !field.unique && !field.primary_key {
315 let name = auto_index_name(&model.table, &field.column);
316 if seen.insert(name.clone()) {
317 stmts.push(create_index_sql(
318 &model.table,
319 &crate::state::IndexState {
320 name,
321 columns: vec![field.column.clone()],
322 unique: false,
323 },
324 ));
325 }
326 }
327 }
328 for index in &model.indexes {
329 if seen.insert(index.name.clone()) {
330 stmts.push(create_index_sql(&model.table, index));
331 }
332 }
333 stmts
334}
335
336fn create_index_sql(table: &str, index: &crate::state::IndexState) -> String {
337 let unique = if index.unique { "UNIQUE " } else { "" };
338 format!(
339 "CREATE {unique}INDEX {} ON {} ({})",
340 quote_ident(&index.name),
341 quote_ident(table),
342 index
343 .columns
344 .iter()
345 .map(|c| quote_ident(c))
346 .collect::<Vec<_>>()
347 .join(", ")
348 )
349}
350
351fn add_field_sql(
352 kind: BackendKind,
353 model: &ModelState,
354 field: &FieldState,
355) -> Result<Vec<String>, MigrationError> {
356 if kind == BackendKind::Sqlite && (field.unique || field.primary_key) {
357 let mut new_model = model.clone();
358 new_model.fields.push(field.clone());
359 return Ok(sqlite_rebuild(model, &new_model));
360 }
361 let def = column_def(kind, field);
362 let mut stmts = vec![format!(
363 "ALTER TABLE {} ADD COLUMN {def}",
364 quote_ident(&model.table)
365 )];
366 if field.index && !field.unique && !field.primary_key {
367 stmts.push(create_index_sql(
368 &model.table,
369 &crate::state::IndexState {
370 name: auto_index_name(&model.table, &field.column),
371 columns: vec![field.column.clone()],
372 unique: false,
373 },
374 ));
375 }
376 Ok(stmts)
377}
378
379fn remove_field_sql(
380 kind: BackendKind,
381 model: &ModelState,
382 name: &str,
383) -> Result<Vec<String>, MigrationError> {
384 let field = model.field(name).ok_or_else(|| {
385 MigrationError::state(format!("field `{name}` not on model `{}`", model.name))
386 })?;
387 if kind == BackendKind::Sqlite {
388 let mut new_model = model.clone();
389 new_model.fields.retain(|f| f.name != name);
390 new_model
391 .indexes
392 .retain(|i| !i.columns.iter().any(|c| c == &field.column));
393 return Ok(sqlite_rebuild(model, &new_model));
394 }
395 Ok(vec![format!(
396 "ALTER TABLE {} DROP COLUMN {}",
397 quote_ident(&model.table),
398 quote_ident(&field.column)
399 )])
400}
401
402fn alter_field_sql(
403 kind: BackendKind,
404 model: &ModelState,
405 name: &str,
406 field: &FieldState,
407) -> Result<Vec<String>, MigrationError> {
408 let old = model.field(name).ok_or_else(|| {
409 MigrationError::state(format!("field `{name}` not on model `{}`", model.name))
410 })?;
411 if kind == BackendKind::Sqlite {
412 let mut new_model = model.clone();
413 if let Some(existing) = new_model.field_mut(name) {
414 *existing = field.clone();
415 }
416 return Ok(sqlite_rebuild_mapped(
419 model,
420 &new_model,
421 &[(name, field.name.as_str())],
422 ));
423 }
424 let mut stmts = Vec::new();
425 let table = quote_ident(&model.table);
426 if old.column != field.column {
427 stmts.push(format!(
428 "ALTER TABLE {table} RENAME COLUMN {} TO {}",
429 quote_ident(&old.column),
430 quote_ident(&field.column)
431 ));
432 }
433 let col = quote_ident(&field.column);
434 if old.sql_type != field.sql_type
435 || old.max_length != field.max_length
436 || old.max_digits != field.max_digits
437 || old.decimal_places != field.decimal_places
438 {
439 if kind == BackendKind::Postgres && old.sql_type != field.sql_type && old.default.is_some()
443 {
444 stmts.push(format!(
445 "ALTER TABLE {table} ALTER COLUMN {col} DROP DEFAULT"
446 ));
447 }
448 let new_type = sql_type_name(kind, field);
449 let mut stmt = format!("ALTER TABLE {table} ALTER COLUMN {col} TYPE {new_type}");
450 if kind == BackendKind::Postgres && old.sql_type != field.sql_type {
454 stmt.push_str(&format!(" USING {col}::{new_type}"));
455 }
456 stmts.push(stmt);
457 if kind == BackendKind::Postgres
460 && old.sql_type != field.sql_type
461 && let Some(d) = &field.default
462 {
463 stmts.push(format!(
464 "ALTER TABLE {table} ALTER COLUMN {col} SET DEFAULT {}",
465 default_sql(kind, d)
466 ));
467 }
468 }
469 if old.nullable != field.nullable {
470 if field.nullable {
471 stmts.push(format!(
472 "ALTER TABLE {table} ALTER COLUMN {col} DROP NOT NULL"
473 ));
474 } else {
475 stmts.push(format!(
476 "ALTER TABLE {table} ALTER COLUMN {col} SET NOT NULL"
477 ));
478 }
479 }
480 let pg_type_changed = kind == BackendKind::Postgres && old.sql_type != field.sql_type;
483 if !pg_type_changed && old.default != field.default {
484 match &field.default {
485 Some(d) => stmts.push(format!(
486 "ALTER TABLE {table} ALTER COLUMN {col} SET DEFAULT {}",
487 default_sql(kind, d)
488 )),
489 None => stmts.push(format!(
490 "ALTER TABLE {table} ALTER COLUMN {col} DROP DEFAULT"
491 )),
492 }
493 }
494 Ok(stmts)
495}
496
497fn rename_field_sql(
498 _kind: BackendKind,
499 model: &ModelState,
500 old_name: &str,
501 new_name: &str,
502) -> Result<Vec<String>, MigrationError> {
503 let field = model.field(old_name).ok_or_else(|| {
504 MigrationError::state(format!("field `{old_name}` not on model `{}`", model.name))
505 })?;
506 if field.column == old_name && old_name != new_name {
507 return Ok(vec![format!(
508 "ALTER TABLE {} RENAME COLUMN {} TO {}",
509 quote_ident(&model.table),
510 quote_ident(&field.column),
511 quote_ident(new_name)
512 )]);
513 }
514 Ok(Vec::new())
515}
516
517fn add_constraint_sql(
518 kind: BackendKind,
519 model: &ModelState,
520 constraint: &ConstraintState,
521) -> Result<Vec<String>, MigrationError> {
522 if kind == BackendKind::Sqlite {
523 let mut new_model = model.clone();
524 new_model.constraints.push(constraint.clone());
525 return Ok(sqlite_rebuild(model, &new_model));
526 }
527 Ok(vec![format!(
528 "ALTER TABLE {} ADD {}",
529 quote_ident(&model.table),
530 table_constraint_sql(constraint)
531 )])
532}
533
534fn delete_constraint_sql(
535 kind: BackendKind,
536 model: &ModelState,
537 name: &str,
538) -> Result<Vec<String>, MigrationError> {
539 if kind == BackendKind::Sqlite {
540 let mut new_model = model.clone();
541 new_model.constraints.retain(|c| c.name() != name);
542 return Ok(sqlite_rebuild(model, &new_model));
543 }
544 Ok(vec![format!(
545 "ALTER TABLE {} DROP CONSTRAINT {}",
546 quote_ident(&model.table),
547 quote_ident(name)
548 )])
549}
550
551pub struct SqliteCastCheck {
554 pub table: String,
556 pub column: String,
558 pub sql: String,
560}
561
562pub fn sqlite_lossy_cast_checks(state: &ProjectState, op: &Operation) -> Vec<SqliteCastCheck> {
568 let Operation::AlterField { model, name, field } = op else {
569 return Vec::new();
570 };
571 let Some(current) = state.model(model) else {
572 return Vec::new();
573 };
574 let Some(old) = current.field(name) else {
575 return Vec::new();
576 };
577 if !sqlite_cast_needs_check(old.sql_type, field.sql_type) {
578 return Vec::new();
579 }
580 let sql = format!(
581 "SELECT 1 AS siderite_lossy FROM {} WHERE {} IS NOT NULL AND CAST({} AS {}) IS NOT {} LIMIT 1",
582 quote_ident(¤t.table),
583 quote_ident(&old.column),
584 quote_ident(&old.column),
585 sql_type_name(BackendKind::Sqlite, field),
586 quote_ident(&old.column),
587 );
588 vec![SqliteCastCheck {
589 table: current.table.clone(),
590 column: field.column.clone(),
591 sql,
592 }]
593}
594
595fn sqlite_cast_needs_check(old: SqlType, new: SqlType) -> bool {
596 if old == new {
597 return false;
598 }
599 if is_sqlite_texty(new) || new == SqlType::Binary {
601 return false;
602 }
603 if is_sqlite_int(old) && is_sqlite_real(new) {
605 return false;
606 }
607 true
608}
609
610fn is_sqlite_texty(t: SqlType) -> bool {
611 matches!(
612 t,
613 SqlType::Text
614 | SqlType::Date
615 | SqlType::Time
616 | SqlType::Timestamp
617 | SqlType::Uuid
618 | SqlType::Json
619 | SqlType::IpAddr
620 )
621}
622
623fn is_sqlite_int(t: SqlType) -> bool {
624 matches!(
625 t,
626 SqlType::SmallInt | SqlType::Integer | SqlType::BigInt | SqlType::Bool | SqlType::Duration
627 )
628}
629
630fn is_sqlite_real(t: SqlType) -> bool {
631 matches!(t, SqlType::Real | SqlType::Double)
632}
633
634fn sqlite_rebuild(old: &ModelState, new: &ModelState) -> Vec<String> {
650 sqlite_rebuild_mapped(old, new, &[])
651}
652
653fn sqlite_rebuild_mapped(
656 old: &ModelState,
657 new: &ModelState,
658 field_renames: &[(&str, &str)],
659) -> Vec<String> {
660 let tmp = format!("{}__siderite_new", new.table);
661 let mut stmts = vec![create_table_sql(BackendKind::Sqlite, new, &tmp)];
662
663 let copied: Vec<(&FieldState, &FieldState)> = new
664 .fields
665 .iter()
666 .filter_map(|nf| {
667 if let Some(of) = old.field(&nf.name) {
668 return Some((of, nf));
669 }
670 let old_name = field_renames
672 .iter()
673 .find(|(_, new_name)| *new_name == nf.name)
674 .map(|(old_name, _)| *old_name)?;
675 old.field(old_name).map(|of| (of, nf))
676 })
677 .collect();
678 if !copied.is_empty() {
679 let dest = copied
680 .iter()
681 .map(|(_, nf)| quote_ident(&nf.column))
682 .collect::<Vec<_>>()
683 .join(", ");
684 let src = copied
685 .iter()
686 .map(|(of, nf)| {
687 if of.sql_type == nf.sql_type {
688 quote_ident(&of.column)
689 } else {
690 format!(
691 "CAST({} AS {})",
692 quote_ident(&of.column),
693 sql_type_name(BackendKind::Sqlite, nf)
694 )
695 }
696 })
697 .collect::<Vec<_>>()
698 .join(", ");
699 stmts.push(format!(
700 "INSERT INTO {} ({dest}) SELECT {src} FROM {}",
701 quote_ident(&tmp),
702 quote_ident(&old.table)
703 ));
704 }
705
706 stmts.push(format!("DROP TABLE {}", quote_ident(&old.table)));
707 stmts.push(format!(
708 "ALTER TABLE {} RENAME TO {}",
709 quote_ident(&tmp),
710 quote_ident(&new.table)
711 ));
712 stmts.extend(index_sqls(new));
713 stmts
714}
715
716pub struct SqliteRebuildTarget {
723 pub table: String,
725 pub old_columns: Vec<String>,
727 pub new_columns: Vec<String>,
729 pub recreated_indexes: std::collections::HashSet<String>,
731}
732
733pub fn sqlite_rebuild_target(state: &ProjectState, op: &Operation) -> Option<SqliteRebuildTarget> {
738 let (old, new) = match op {
739 Operation::AddField { model, field } if field.unique || field.primary_key => {
740 let old = state.model(model)?;
741 let mut new = old.clone();
742 new.fields.push(field.clone());
743 (old.clone(), new)
744 }
745 Operation::RemoveField { model, name } => {
746 let old = state.model(model)?;
747 let mut new = old.clone();
748 new.fields.retain(|f| f.name != *name);
749 if let Some(dropped) = old.field(name).map(|f| f.column.clone()) {
752 new.indexes
753 .retain(|i| !i.columns.iter().any(|c| c == &dropped));
754 }
755 (old.clone(), new)
756 }
757 Operation::AlterField { model, name, field } => {
758 let old = state.model(model)?;
759 let mut new = old.clone();
760 *new.field_mut(name)? = field.clone();
761 (old.clone(), new)
762 }
763 Operation::AddConstraint { model, constraint } => {
764 let old = state.model(model)?;
765 let mut new = old.clone();
766 new.constraints.push(constraint.clone());
767 (old.clone(), new)
768 }
769 Operation::DeleteConstraint { model, name } => {
770 let old = state.model(model)?;
771 let mut new = old.clone();
772 new.constraints.retain(|c| c.name() != *name);
773 (old.clone(), new)
774 }
775 _ => return None,
776 };
777 let table = old.table.clone();
778 let old_columns = old.fields.iter().map(|f| f.column.clone()).collect();
779 let new_columns = new.fields.iter().map(|f| f.column.clone()).collect();
780 Some(SqliteRebuildTarget {
781 table,
782 old_columns,
783 new_columns,
784 recreated_indexes: rebuilt_index_names(&new),
785 })
786}
787
788fn rebuilt_index_names(new: &ModelState) -> std::collections::HashSet<String> {
793 let mut seen = std::collections::HashSet::new();
794 for field in &new.fields {
795 if field.index && !field.unique && !field.primary_key {
796 seen.insert(auto_index_name(&new.table, &field.column));
797 }
798 }
799 for index in &new.indexes {
800 seen.insert(index.name.clone());
801 }
802 seen
803}
804
805pub fn sqlite_sql_mentions_ident(sql: &str, ident: &str) -> bool {
811 if ident.is_empty() {
812 return false;
813 }
814 let lower_sql = sql.to_lowercase();
815 let lower = ident.to_lowercase();
816 for (open, close) in [("\"", "\""), ("`", "`"), ("[", "]")] {
817 if lower_sql.contains(&format!("{open}{lower}{close}")) {
818 return true;
819 }
820 }
821 let stripped = strip_single_quoted(&lower_sql);
822 stripped
823 .split(|c: char| !c.is_ascii_alphanumeric() && c != '_')
824 .any(|token| token == lower)
825}
826
827fn strip_single_quoted(sql: &str) -> String {
829 let mut out = String::with_capacity(sql.len());
830 let mut chars = sql.chars().peekable();
831 while let Some(ch) = chars.next() {
832 if ch != '\'' {
833 out.push(ch);
834 continue;
835 }
836 loop {
838 match chars.next() {
839 None => break,
840 Some('\'') => {
841 if chars.peek() == Some(&'\'') {
842 chars.next();
843 } else {
844 break;
845 }
846 }
847 Some(_) => {}
848 }
849 }
850 out.push(' ');
851 }
852 out
853}