1
//! File that contains all the datatypes and logic to perform the migrations
2
//! over a target database
3
use async_trait::async_trait;
4
use canyon_crud::DatabaseType;
5
use regex::Regex;
6
use std::collections::HashMap;
7
use std::fmt::Debug;
8
use std::ops::Not;
9

            
10
use crate::canyon_crud::{crud::Transaction, DatasourceConfig};
11
use crate::constants::regex_patterns;
12
use crate::save_migrations_query_to_execute;
13

            
14
use super::information_schema::{ColumnMetadata, TableMetadata};
15
use super::memory::CanyonMemory;
16
#[cfg(feature = "postgres")]
17
use crate::migrations::transforms::{to_postgres_alter_syntax, to_postgres_syntax};
18
#[cfg(feature = "mssql")]
19
use crate::migrations::transforms::{to_sqlserver_alter_syntax, to_sqlserver_syntax};
20
use canyon_entities::register_types::{CanyonRegisterEntity, CanyonRegisterEntityField};
21

            
22
/// Responsible of generating the queries to sync the database status with the
23
/// Rust source code managed by Canyon, for successfully make the migrations
24
#[derive(Debug, Default)]
25
pub struct MigrationsProcessor {
26
    operations: Vec<Box<dyn DatabaseOperation>>,
27
    set_primary_key_operations: Vec<Box<dyn DatabaseOperation>>,
28
    drop_primary_key_operations: Vec<Box<dyn DatabaseOperation>>,
29
    constraints_operations: Vec<Box<dyn DatabaseOperation>>,
30
}
31
impl Transaction<Self> for MigrationsProcessor {}
32

            
33
impl MigrationsProcessor {
34
    pub async fn process<'a>(
35
        &'a mut self,
36
        canyon_memory: CanyonMemory,
37
        canyon_entities: Vec<CanyonRegisterEntity<'a>>,
38
        database_tables: Vec<&'a TableMetadata>,
39
        datasource: &'_ DatasourceConfig,
40
    ) {
41
        // The database type formally represented in Canyon
42
        let db_type = datasource.get_db_type();
43
        // For each entity (table) on the register (Rust structs)
44
        for canyon_register_entity in canyon_entities {
45
            let entity_name = canyon_register_entity.entity_db_table_name;
46
            println!("Processing migrations for entity: {entity_name}");
47

            
48
            // 1st operation ->
49
            self.create_or_rename_tables(
50
                &canyon_memory,
51
                entity_name,
52
                canyon_register_entity.entity_fields.clone(),
53
                &database_tables,
54
            );
55

            
56
            let current_table_metadata = MigrationsHelper::get_current_table_metadata(
57
                &canyon_memory,
58
                entity_name,
59
                &database_tables,
60
            );
61

            
62
            self.delete_fields(
63
                entity_name,
64
                canyon_register_entity.entity_fields.clone(),
65
                current_table_metadata,
66
                db_type,
67
            );
68

            
69
            // For each field (column) on the this canyon register entity
70
            for canyon_register_field in canyon_register_entity.entity_fields {
71
                let current_column_metadata = MigrationsHelper::get_current_column_metadata(
72
                    canyon_register_field.field_name.clone(),
73
                    current_table_metadata,
74
                );
75

            
76
                // We only create or modify (right now only datatype)
77
                // the column when the database already contains the table,
78
                // if not, the columns are already create in the previous operation (create table)
79
                if current_table_metadata.is_some() {
80
                    self.create_or_modify_field(
81
                        entity_name,
82
                        db_type,
83
                        canyon_register_field.clone(),
84
                        current_column_metadata,
85
                    )
86
                }
87

            
88
                // Time to check annotations for the current column
89
                // Case when  we only need to add constraints
90
                if (current_table_metadata.is_none()
91
                    && !canyon_register_field.annotations.is_empty())
92
                    || (current_table_metadata.is_some() && current_column_metadata.is_none())
93
                {
94
                    self.add_constraints(entity_name, canyon_register_field.clone())
95
                }
96

            
97
                // Case when we need to compare the entity with the database contain
98
                #[allow(clippy::unnecessary_unwrap)]
99
                if current_table_metadata.is_some() && current_column_metadata.is_some() {
100
                    self.add_modify_or_remove_constraints(
101
                        entity_name,
102
                        canyon_register_field,
103
                        current_column_metadata.unwrap(),
104
                    )
105
                }
106
            }
107
        }
108

            
109
        for operation in &self.operations {
110
            operation.generate_sql(datasource).await; // This should be moved again to runtime
111
        }
112
        for operation in &self.drop_primary_key_operations {
113
            operation.generate_sql(datasource).await; // This should be moved again to runtime
114
        }
115
        for operation in &self.set_primary_key_operations {
116
            operation.generate_sql(datasource).await; // This should be moved again to runtime
117
        }
118
        for operation in &self.constraints_operations {
119
            operation.generate_sql(datasource).await; // This should be moved again to runtime
120
        }
121
        // TODO Still pending to decouple de executions of cargo check to skip the process if this
122
        // code is not processed by cargo build or cargo run
123
        // Self::from_query_register(datasource_name).await;
124
    }
125

            
126
    /// The operation that checks if an entity must be update is name in the database
127
    fn create_or_rename_tables<'a>(
128
        &mut self,
129
        canyon_memory: &'_ CanyonMemory,
130
        entity_name: &'a str,
131
        entity_fields: Vec<CanyonRegisterEntityField>,
132
        database_tables: &'a [&'a TableMetadata],
133
    ) {
134
        // 1st operation -> Check if the current entity is already on the target database.
135
        if !MigrationsHelper::entity_already_on_database(entity_name, database_tables) {
136
            // [`CanyonMemory`] holds a HashMap with the tables who changed their name in
137
            // the Rust side. If this table name is present, we don't create a new table,
138
            // just rename the already known one
139
            if canyon_memory.renamed_entities.contains_key(entity_name) {
140
                self.table_rename(
141
                    canyon_memory
142
                        .renamed_entities
143
                        .get(entity_name) // Get the old entity name (the value)
144
                        .unwrap()
145
                        .to_owned(),
146
                    entity_name.to_string(), // Set the new table name
147
                )
148
            } else {
149
                self.create_table(entity_name.to_string(), entity_fields)
150
            }
151
        }
152
    }
153

            
154
    /// Generates a database agnostic query to change the name of a table
155
    fn create_table(&mut self, table_name: String, entity_fields: Vec<CanyonRegisterEntityField>) {
156
        self.operations.push(Box::new(TableOperation::CreateTable(
157
            table_name,
158
            entity_fields,
159
        )));
160
    }
161

            
162
    /// Generates a database agnostic query to change the name of a table
163
    fn table_rename(&mut self, old_table_name: String, new_table_name: String) {
164
        self.operations
165
            .push(Box::new(TableOperation::AlterTableName(
166
                old_table_name,
167
                new_table_name,
168
            )));
169
    }
170

            
171
    // Creates or modify (currently only datatype) a column for a given canyon register entity field
172
    fn delete_fields<'a>(
173
        &mut self,
174
        entity_name: &'a str,
175
        entity_fields: Vec<CanyonRegisterEntityField>,
176
        current_table_metadata: Option<&'a TableMetadata>,
177
        _db_type: DatabaseType,
178
    ) {
179
        if current_table_metadata.is_none() {
180
            return;
181
        }
182
        let columns_name_to_delete: Vec<&ColumnMetadata> = current_table_metadata
183
            .unwrap()
184
            .columns
185
            .iter()
186
            .filter(|db_column| {
187
                entity_fields
188
                    .iter()
189
                    .map(|canyon_field| canyon_field.field_name.to_string())
190
                    .any(|canyon_field| canyon_field == db_column.column_name)
191
                    .not()
192
            })
193
            .collect();
194

            
195
        for column_metadata in columns_name_to_delete {
196
            #[cfg(feature = "mssql")]
197
            {
198
                if _db_type == DatabaseType::SqlServer && !column_metadata.is_nullable {
199
                    self.drop_column_not_null(
200
                        entity_name,
201
                        column_metadata.column_name.clone(),
202
                        MigrationsHelper::get_datatype_from_column_metadata(column_metadata),
203
                    )
204
                }
205
            }
206
            self.delete_column(entity_name, column_metadata.column_name.clone());
207
        }
208
    }
209

            
210
    // Creates or modify (currently only datatype and nullability) a column for a given canyon register entity field
211
    fn create_or_modify_field(
212
        &mut self,
213
        entity_name: &str,
214
        db_type: DatabaseType,
215
        canyon_register_entity_field: CanyonRegisterEntityField,
216
        current_column_metadata: Option<&ColumnMetadata>,
217
    ) {
218
        // If we do not retrieve data for this database column, it does not exist yet
219
        // and therefore it has to be created
220
        if current_column_metadata.is_none() {
221
            self.create_column(
222
                entity_name.to_string(),
223
                canyon_register_entity_field.clone(),
224
            )
225
        } else if !MigrationsHelper::is_same_datatype(
226
            db_type,
227
            &canyon_register_entity_field,
228
            current_column_metadata.unwrap(),
229
        ) {
230
            self.change_column_datatype(
231
                entity_name.to_string(),
232
                canyon_register_entity_field.clone(),
233
            )
234
        }
235

            
236
        if let Some(column_metadata) = current_column_metadata {
237
            if canyon_register_entity_field.is_nullable() != column_metadata.is_nullable {
238
                if column_metadata.is_nullable {
239
                    self.set_not_null(entity_name.to_string(), canyon_register_entity_field)
240
                } else {
241
                    self.drop_not_null(entity_name.to_string(), canyon_register_entity_field)
242
                }
243
            }
244
        }
245
    }
246

            
247
    fn delete_column(&mut self, table_name: &str, column_name: String) {
248
        self.operations.push(Box::new(ColumnOperation::DeleteColumn(
249
            table_name.to_string(),
250
            column_name,
251
        )));
252
    }
253

            
254
    #[cfg(feature = "mssql")]
255
    fn drop_column_not_null(
256
        &mut self,
257
        table_name: &str,
258
        column_name: String,
259
        column_datatype: String,
260
    ) {
261
        self.operations
262
            .push(Box::new(ColumnOperation::DropNotNullBeforeDropColumn(
263
                table_name.to_string(),
264
                column_name,
265
                column_datatype,
266
            )));
267
    }
268

            
269
    fn create_column(&mut self, table_name: String, field: CanyonRegisterEntityField) {
270
        self.operations
271
            .push(Box::new(ColumnOperation::CreateColumn(table_name, field)));
272
    }
273

            
274
    fn change_column_datatype(&mut self, table_name: String, field: CanyonRegisterEntityField) {
275
        self.operations
276
            .push(Box::new(ColumnOperation::AlterColumnType(
277
                table_name, field,
278
            )));
279
    }
280

            
281
    fn set_not_null(&mut self, table_name: String, field: CanyonRegisterEntityField) {
282
        self.operations
283
            .push(Box::new(ColumnOperation::AlterColumnSetNotNull(
284
                table_name, field,
285
            )));
286
    }
287

            
288
    fn drop_not_null(&mut self, table_name: String, field: CanyonRegisterEntityField) {
289
        self.operations
290
            .push(Box::new(ColumnOperation::AlterColumnDropNotNull(
291
                table_name, field,
292
            )));
293
    }
294

            
295
    fn add_constraints(
296
        &mut self,
297
        entity_name: &str,
298
        canyon_register_entity_field: CanyonRegisterEntityField,
299
    ) {
300
        for attr in &canyon_register_entity_field.annotations {
301
            if attr.starts_with("Annotation: ForeignKey") {
302
                let annotation_data = MigrationsHelper::extract_foreign_key_annotation(
303
                    &canyon_register_entity_field.annotations,
304
                );
305

            
306
                let table_to_reference = annotation_data.0;
307
                let column_to_reference = annotation_data.1;
308

            
309
                let foreign_key_name = format!(
310
                    "{entity_name}_{}_fkey",
311
                    &canyon_register_entity_field.field_name
312
                );
313

            
314
                Self::add_foreign_key(
315
                    self,
316
                    entity_name,
317
                    foreign_key_name,
318
                    table_to_reference,
319
                    column_to_reference,
320
                    &canyon_register_entity_field,
321
                );
322
            }
323
            if attr.starts_with("Annotation: PrimaryKey") {
324
                Self::add_primary_key(self, entity_name, canyon_register_entity_field.clone());
325

            
326
                #[cfg(feature = "postgres")]
327
                {
328
                    if canyon_register_entity_field.is_autoincremental() {
329
                        Self::add_identity(self, entity_name, canyon_register_entity_field.clone());
330
                    }
331
                }
332
            }
333
        }
334
    }
335

            
336
    fn add_foreign_key(
337
        &mut self,
338
        entity_name: &'_ str,
339
        foreign_key_name: String,
340
        table_to_reference: String,
341
        column_to_reference: String,
342
        canyon_register_entity_field: &CanyonRegisterEntityField,
343
    ) {
344
        self.constraints_operations
345
            .push(Box::new(TableOperation::AddTableForeignKey(
346
                entity_name.to_string(),
347
                foreign_key_name,
348
                canyon_register_entity_field.field_name.clone(),
349
                table_to_reference,
350
                column_to_reference,
351
            )));
352
    }
353

            
354
    fn add_primary_key(
355
        &mut self,
356
        entity_name: &str,
357
        canyon_register_entity_field: CanyonRegisterEntityField,
358
    ) {
359
        self.set_primary_key_operations
360
            .push(Box::new(TableOperation::AddTablePrimaryKey(
361
                entity_name.to_string(),
362
                canyon_register_entity_field,
363
            )));
364
    }
365

            
366
    #[cfg(feature = "postgres")]
367
    fn add_identity(&mut self, entity_name: &str, field: CanyonRegisterEntityField) {
368
        self.constraints_operations
369
            .push(Box::new(ColumnOperation::AlterColumnAddIdentity(
370
                entity_name.to_string(),
371
                field.clone(),
372
            )));
373

            
374
        self.constraints_operations
375
            .push(Box::new(SequenceOperation::ModifySequence(
376
                entity_name.to_string(),
377
                field,
378
            )));
379
    }
380

            
381
    fn add_modify_or_remove_constraints(
382
        &mut self,
383
        entity_name: &str,
384
        canyon_register_entity_field: CanyonRegisterEntityField,
385
        current_column_metadata: &ColumnMetadata,
386
    ) {
387
        let field_is_primary_key = canyon_register_entity_field
388
            .annotations
389
            .iter()
390
            .any(|anno| anno.starts_with("Annotation: PrimaryKey"));
391

            
392
        let field_is_foreign_key = canyon_register_entity_field
393
            .annotations
394
            .iter()
395
            .any(|anno| anno.starts_with("Annotation: ForeignKey"));
396

            
397
        // ------------ PRIMARY KEY ---------------
398
        // Case when field contains a primary key annotation, and it's not already on database, add it to constrains_operations
399
        if field_is_primary_key && current_column_metadata.primary_key_info.is_none() {
400
            Self::add_primary_key(self, entity_name, canyon_register_entity_field.clone());
401

            
402
            #[cfg(feature = "postgres")]
403
            {
404
                if canyon_register_entity_field.is_autoincremental() {
405
                    Self::add_identity(self, entity_name, canyon_register_entity_field.clone());
406
                }
407
            }
408
        }
409
        // Case when the field contains a primary key annotation, and it's already on the database
410
        else if field_is_primary_key && current_column_metadata.primary_key_info.is_some() {
411
            #[cfg(feature = "postgres")]
412
            {
413
                let is_autoincr_rust = canyon_register_entity_field.is_autoincremental();
414
                let is_autoincr_in_db = current_column_metadata.is_identity;
415
                if !is_autoincr_rust && is_autoincr_in_db {
416
                    Self::drop_identity(self, entity_name, canyon_register_entity_field.clone())
417
                } else if is_autoincr_rust && !is_autoincr_in_db {
418
                    Self::add_identity(self, entity_name, canyon_register_entity_field.clone())
419
                }
420
            }
421
        }
422
        // Case when field doesn't contains a primary key annotation, but there is one in the database column
423
        else if !field_is_primary_key && current_column_metadata.primary_key_info.is_some() {
424
            Self::drop_primary_key(
425
                self,
426
                entity_name,
427
                current_column_metadata
428
                    .primary_key_name
429
                    .as_ref()
430
                    .expect("PrimaryKey constrain name not found")
431
                    .to_string(),
432
            );
433

            
434
            #[cfg(feature = "postgres")]
435
            {
436
                if current_column_metadata.is_identity {
437
                    Self::drop_identity(self, entity_name, canyon_register_entity_field.clone());
438
                }
439
            }
440
        }
441

            
442
        // -------------------- FOREIGN KEY CASE ----------------------------
443
        // Case when field contains a foreign key annotation, and it's not already on database, add it to constraints_operations
444
        if field_is_foreign_key && current_column_metadata.foreign_key_name.is_none() {
445
            if current_column_metadata.foreign_key_name.is_none() {
446
                let annotation_data = MigrationsHelper::extract_foreign_key_annotation(
447
                    &canyon_register_entity_field.annotations,
448
                );
449

            
450
                let foreign_key_name = format!(
451
                    "{entity_name}_{}_fkey",
452
                    &canyon_register_entity_field.field_name
453
                );
454

            
455
                Self::add_foreign_key(
456
                    self,
457
                    entity_name,
458
                    foreign_key_name,
459
                    annotation_data.0,
460
                    annotation_data.1,
461
                    &canyon_register_entity_field,
462
                );
463
            }
464
        }
465
        // Case when field contains a foreign key annotation, and there is already one in the database
466
        else if field_is_foreign_key && current_column_metadata.foreign_key_name.is_some() {
467
            // Will contain the table name (on index 0) and column name (on index 1) pointed to by the foreign key
468
            let annotation_data = MigrationsHelper::extract_foreign_key_annotation(
469
                &canyon_register_entity_field.annotations,
470
            );
471

            
472
            let foreign_key_name = format!(
473
                "{entity_name}_{}_fkey",
474
                &canyon_register_entity_field.field_name
475
            );
476

            
477
            // Example of information in foreign_key_info: FOREIGN KEY (league) REFERENCES leagues(id)
478
            let references_regex = Regex::new(regex_patterns::EXTRACT_FOREIGN_KEY_INFO).unwrap();
479

            
480
            let captures_references = references_regex
481
                .captures(
482
                    current_column_metadata
483
                        .foreign_key_info
484
                        .as_ref()
485
                        .expect("Regex - foreign key info"),
486
                )
487
                .expect("Regex - foreign key info not found");
488

            
489
            let current_column = captures_references
490
                .name("current_column")
491
                .expect("Regex - Current column not found")
492
                .as_str()
493
                .to_string();
494
            let ref_table = captures_references
495
                .name("ref_table")
496
                .expect("Regex - Ref tablenot found")
497
                .as_str()
498
                .to_string();
499
            let ref_column = captures_references
500
                .name("ref_column")
501
                .expect("Regex - Ref column not found")
502
                .as_str()
503
                .to_string();
504

            
505
            // If entity foreign key is not equal to the one on database, a constrains_operations is added to delete it and add a new one.
506
            if canyon_register_entity_field.field_name != current_column
507
                || annotation_data.0 != ref_table
508
                || annotation_data.1 != ref_column
509
            {
510
                Self::delete_foreign_key(
511
                    self,
512
                    entity_name,
513
                    current_column_metadata
514
                        .foreign_key_name
515
                        .as_ref()
516
                        .expect("Annotation foreign key constrain name not found")
517
                        .to_string(),
518
                );
519

            
520
                Self::add_foreign_key(
521
                    self,
522
                    entity_name,
523
                    foreign_key_name,
524
                    annotation_data.0,
525
                    annotation_data.1,
526
                    &canyon_register_entity_field,
527
                )
528
            }
529
        } else if !field_is_foreign_key && current_column_metadata.foreign_key_name.is_some() {
530
            // Case when field don't contains a foreign key annotation, but there is already one in the database column
531
            Self::delete_foreign_key(
532
                self,
533
                entity_name,
534
                current_column_metadata
535
                    .foreign_key_name
536
                    .as_ref()
537
                    .expect("ForeignKey constrain name not found")
538
                    .to_string(),
539
            );
540
        }
541
    }
542

            
543
    fn drop_primary_key(&mut self, entity_name: &str, primary_key_name: String) {
544
        self.drop_primary_key_operations
545
            .push(Box::new(TableOperation::DeleteTablePrimaryKey(
546
                entity_name.to_string(),
547
                primary_key_name,
548
            )));
549
    }
550

            
551
    #[cfg(feature = "postgres")]
552
    fn drop_identity(
553
        &mut self,
554
        entity_name: &str,
555
        canyon_register_entity_field: CanyonRegisterEntityField,
556
    ) {
557
        self.constraints_operations
558
            .push(Box::new(ColumnOperation::AlterColumnDropIdentity(
559
                entity_name.to_string(),
560
                canyon_register_entity_field,
561
            )));
562
    }
563

            
564
    fn delete_foreign_key(&mut self, entity_name: &str, constrain_name: String) {
565
        self.constraints_operations
566
            .push(Box::new(TableOperation::DeleteTableForeignKey(
567
                // table_with_foreign_key,constrain_name
568
                entity_name.to_string(),
569
                constrain_name,
570
            )));
571
    }
572

            
573
    /// Make the detected migrations for the next Canyon-SQL run
574
    #[allow(clippy::await_holding_lock)]
575
    pub async fn from_query_register(queries_to_execute: &HashMap<&str, Vec<&str>>) {
576
        for datasource in queries_to_execute.iter() {
577
            for query_to_execute in datasource.1 {
578
                let res = Self::query(query_to_execute, [], datasource.0).await;
579

            
580
                match res {
581
                    Ok(_) => println!(
582
                        "\t[OK] - {:?} - Query: {:?}",
583
                        datasource.0, &query_to_execute
584
                    ),
585
                    Err(e) => println!(
586
                        "\t[ERR] - {:?} - Query: {:?}\nCause: {:?}",
587
                        datasource.0, &query_to_execute, e
588
                    ),
589
                }
590
                // TODO Ask for user input?
591
            }
592
        }
593
    }
594
}
595

            
596
/// Contains helper methods to parse and process the external and internal input data
597
/// for the migrations
598
struct MigrationsHelper;
599
impl MigrationsHelper {
600
    /// Checks if a tracked Canyon entity is already present in the database
601
    fn entity_already_on_database<'a>(
602
        entity_name: &'a str,
603
        database_tables: &'a [&'_ TableMetadata],
604
    ) -> bool {
605
        database_tables
606
            .iter()
607
            .any(|db_table_data| db_table_data.table_name == entity_name)
608
    }
609
    /// Get the table metadata for a given entity name or his old entity name if the table was renamed.
610
    fn get_current_table_metadata<'a>(
611
        canyon_memory: &'_ CanyonMemory,
612
        entity_name: &'a str,
613
        database_tables: &'a [&'_ TableMetadata],
614
    ) -> Option<&'a TableMetadata> {
615
        let correct_entity_name = canyon_memory
616
            .renamed_entities
617
            .get(&entity_name.to_lowercase())
618
            .map(|e| e.to_owned())
619
            .unwrap_or_else(|| entity_name.to_string());
620

            
621
        database_tables
622
            .iter()
623
            .find(|table_metadata| {
624
                table_metadata.table_name.to_lowercase() == *correct_entity_name.to_lowercase()
625
            })
626
            .map(|e| e.to_owned())
627
    }
628

            
629
    /// Get the column metadata for a given column name
630
    fn get_current_column_metadata(
631
        column_name: String,
632
        current_table_metadata: Option<&TableMetadata>,
633
    ) -> Option<&ColumnMetadata> {
634
        if let Some(metadata_table) = current_table_metadata {
635
            metadata_table
636
                .columns
637
                .iter()
638
                .find(|column| column.column_name == column_name)
639
        } else {
640
            None
641
        }
642
    }
643

            
644
    #[cfg(feature = "mssql")]
645
    fn get_datatype_from_column_metadata(current_column_metadata: &ColumnMetadata) -> String {
646
        // TODO Add all SQL Server text datatypes
647
        if ["nvarchar", "varchar"]
648
            .contains(&current_column_metadata.datatype.to_lowercase().as_str())
649
        {
650
            let varchar_len = match &current_column_metadata.character_maximum_length {
651
                Some(v) => v.to_string(),
652
                None => "max".to_string(),
653
            };
654

            
655
            format!("{}({})", current_column_metadata.datatype, varchar_len)
656
        } else {
657
            current_column_metadata.datatype.to_string()
658
        }
659
    }
660

            
661
    fn is_same_datatype(
662
        db_type: DatabaseType,
663
        canyon_register_entity_field: &CanyonRegisterEntityField,
664
        current_column_metadata: &ColumnMetadata,
665
    ) -> bool {
666
        #[cfg(feature = "postgres")]
667
        {
668
            if db_type == DatabaseType::PostgreSql {
669
                return to_postgres_alter_syntax(canyon_register_entity_field).to_lowercase()
670
                    == current_column_metadata.datatype;
671
            }
672
        }
673
        #[cfg(feature = "mssql")]
674
        {
675
            if db_type == DatabaseType::SqlServer {
676
                // TODO Search a better way to get the datatype without useless info (like "VARCHAR(MAX)")
677
                return to_sqlserver_alter_syntax(canyon_register_entity_field).to_lowercase()
678
                    == current_column_metadata.datatype;
679
            }
680
        }
681

            
682
        false
683
    }
684

            
685
    fn extract_foreign_key_annotation(field_annotations: &[String]) -> (String, String) {
686
        let opt_fk_annotation = field_annotations
687
            .iter()
688
            .find(|anno| anno.starts_with("Annotation: ForeignKey"));
689
        if let Some(fk_annotation) = opt_fk_annotation {
690
            let annotation_data = fk_annotation
691
                .split(',')
692
                .filter(|x| !x.contains("Annotation: ForeignKey")) // After here, we only have the "table" and the "column" attribute values
693
                .map(|x| {
694
                    x.split(':')
695
                        .collect::<Vec<&str>>()
696
                        .get(1)
697
                        .expect("Error. Unable to split annotations")
698
                        .trim()
699
                        .to_string()
700
                })
701
                .collect::<Vec<String>>();
702

            
703
            let table_to_reference = annotation_data
704
                .get(0)
705
                .expect("Error extracting table ref from FK annotation")
706
                .to_string();
707
            let column_to_reference = annotation_data
708
                .get(1)
709
                .expect("Error extracting column ref from FK annotation")
710
                .to_string();
711

            
712
            (table_to_reference, column_to_reference)
713
        } else {
714
            panic!("Detected a Foreign Key attribute when does not exists on the user's code");
715
        }
716
    }
717
}
718

            
719
#[cfg(test)]
720
mod migrations_helper_tests {
721
    use super::*;
722
    use crate::constants;
723

            
724
    const MOCKED_ENTITY_NAME: &str = "league";
725

            
726
    #[test]
727
    fn test_entity_already_on_database() {
728
        let parse_result_empty_db_tables =
729
            MigrationsHelper::entity_already_on_database(MOCKED_ENTITY_NAME, &[]);
730
        // Always should be false
731
        assert!(!parse_result_empty_db_tables);
732

            
733
        // Rust has a League entity. Database has a `league` entity. Case should be normalized
734
        // and a match must raise
735
        let mocked_league_entity_on_database = MigrationsHelper::entity_already_on_database(
736
            MOCKED_ENTITY_NAME,
737
            &[&constants::mocked_data::TABLE_METADATA_LEAGUE_EX],
738
        );
739
        assert!(mocked_league_entity_on_database);
740

            
741
        let mocked_league_entity_on_database = MigrationsHelper::entity_already_on_database(
742
            MOCKED_ENTITY_NAME,
743
            &[&constants::mocked_data::NON_MATCHING_TABLE_METADATA],
744
        );
745
        assert!(!mocked_league_entity_on_database)
746
    }
747
}
748

            
749
/// Trait that enables implementors to generate the migration queries
750
#[async_trait]
751
trait DatabaseOperation: Debug {
752
    async fn generate_sql(&self, datasource: &DatasourceConfig);
753
}
754

            
755
/// Helper to relate the operations that Canyon should do when it's managing a schema
756
#[derive(Debug)]
757
#[allow(dead_code)]
758
enum TableOperation {
759
    CreateTable(String, Vec<CanyonRegisterEntityField>),
760
    // old table_name, new table_name
761
    AlterTableName(String, String),
762
    // table_name, foreign_key_name, column_foreign_key, table_to_reference, column_to_reference
763
    AddTableForeignKey(String, String, String, String, String),
764
    // table_with_foreign_key, constraint_name
765
    DeleteTableForeignKey(String, String),
766
    // table_name, entity_field, column_name
767
    AddTablePrimaryKey(String, CanyonRegisterEntityField),
768
    // table_name, constraint_name
769
    DeleteTablePrimaryKey(String, String),
770
}
771

            
772
impl<T: Debug> Transaction<T> for TableOperation {}
773

            
774
#[async_trait]
775
impl DatabaseOperation for TableOperation {
776
    async fn generate_sql(&self, datasource: &DatasourceConfig) {
777
        let db_type = datasource.get_db_type();
778

            
779
        let stmt = match self {
780
            TableOperation::CreateTable(table_name, table_fields) => {
781
                match db_type {
782
                    #[cfg(feature = "postgres")] DatabaseType::PostgreSql => {
783
                        format!(
784
                            "CREATE TABLE \"{table_name}\" ({});",
785
                            table_fields
786
                                .iter()
787
                                .map(|entity_field| format!(
788
                                    "\"{}\" {}",
789
                                    entity_field.field_name,
790
                                    to_postgres_syntax(entity_field)
791
                                ))
792
                                .collect::<Vec<String>>()
793
                                .join(", ")
794
                        )
795
                    }
796
                    #[cfg(feature = "mssql")] DatabaseType::SqlServer => {
797
                        format!(
798
                            "CREATE TABLE {:?} ({:?});",
799
                            table_name,
800
                            table_fields
801
                                .iter()
802
                                .map(|entity_field| format!(
803
                                    "{} {}",
804
                                    entity_field.field_name,
805
                                    to_sqlserver_syntax(entity_field)
806
                                ))
807
                                .collect::<Vec<String>>()
808
                                .join(", ")
809
                        )
810
                            .replace('"', "")
811
                    },
812
                    #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!()
813

            
814
                }
815
            }
816

            
817
            TableOperation::AlterTableName(old_table_name, new_table_name) => {
818
                match db_type {
819
                    #[cfg(feature = "postgres")] DatabaseType::PostgreSql =>
820
                        format!("ALTER TABLE {old_table_name} RENAME TO {new_table_name};"),
821
                    #[cfg(feature = "mssql")] DatabaseType::SqlServer =>
822
                        /*
823
                            Notes: Brackets around `old_table_name`, p.e.
824
                                exec sp_rename ['league'], 'leagues'  // NOT VALID!
825
                            is only allowed for compound names split by a dot.
826
                                exec sp_rename ['random.league'], 'leagues'  // OK
827

            
828
                            CARE! This doesn't mean that we are including the schema.
829
                                exec sp_rename ['dbo.random.league'], 'leagues' // OK
830
                                exec sp_rename 'dbo.league', 'leagues' // OK - Schema doesn't need brackets
831

            
832
                            Due to the automatic mapped name from Rust to DB and vice-versa, this won't
833
                            be an allowed behaviour for now, only with the table_name parameter on the
834
                            CanyonEntity annotation.
835
                        */
836
                        format!("exec sp_rename '{old_table_name}', '{new_table_name}';"),
837
                    #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!()
838

            
839
                }
840
            }
841

            
842
            TableOperation::AddTableForeignKey(
843
                _table_name,
844
                _foreign_key_name,
845
                _column_foreign_key,
846
                _table_to_reference,
847
                _column_to_reference,
848
            ) => {
849
                match db_type {
850
                    #[cfg(feature = "postgres")] DatabaseType::PostgreSql =>
851
                        format!(
852
                            "ALTER TABLE {_table_name} ADD CONSTRAINT {_foreign_key_name} \
853
                            FOREIGN KEY ({_column_foreign_key}) REFERENCES {_table_to_reference} ({_column_to_reference});"
854
                        ),
855
                    #[cfg(feature = "mssql")] DatabaseType::SqlServer =>
856
                        todo!("[MS-SQL -> Operation still won't supported by Canyon for Sql Server]"),
857
                    #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!()
858

            
859
                }
860
            }
861

            
862
            TableOperation::DeleteTableForeignKey(_table_with_foreign_key, _constraint_name) => {
863
                match db_type {
864
                    #[cfg(feature = "postgres")] DatabaseType::PostgreSql =>
865
                        format!(
866
                            "ALTER TABLE {_table_with_foreign_key} DROP CONSTRAINT {_constraint_name};",
867
                        ),
868
                    #[cfg(feature = "mssql")] DatabaseType::SqlServer =>
869
                        todo!("[MS-SQL -> Operation still won't supported by Canyon for Sql Server]"),
870
                    #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!()
871

            
872
                }
873
            }
874

            
875
            TableOperation::AddTablePrimaryKey(_table_name, _entity_field) => {
876
                match db_type {
877
                    #[cfg(feature = "postgres")] DatabaseType::PostgreSql =>
878
                        format!(
879
                            "ALTER TABLE \"{_table_name}\" ADD PRIMARY KEY (\"{}\");",
880
                            _entity_field.field_name
881
                        ),
882
                    #[cfg(feature = "mssql")] DatabaseType::SqlServer =>
883
                        todo!("[MS-SQL -> Operation still won't supported by Canyon for Sql Server]"),
884
                    #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!()
885

            
886
                }
887
            }
888

            
889
            TableOperation::DeleteTablePrimaryKey(table_name, primary_key_name) => {
890
                match db_type {
891
                    #[cfg(feature = "postgres")] DatabaseType::PostgreSql =>
892
                        format!("ALTER TABLE {table_name} DROP CONSTRAINT {primary_key_name} CASCADE;"),
893
                    #[cfg(feature = "mssql")] DatabaseType::SqlServer =>
894
                        format!("ALTER TABLE {table_name} DROP CONSTRAINT {primary_key_name} CASCADE;"),
895
                    #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!()
896

            
897
                }
898
            }
899
        };
900

            
901
        save_migrations_query_to_execute(stmt, &datasource.name);
902
    }
903
}
904

            
905
/// Helper to relate the operations that Canyon should do when a change on a field should
906
#[derive(Debug)]
907
#[allow(dead_code)]
908
enum ColumnOperation {
909
    CreateColumn(String, CanyonRegisterEntityField),
910
    DeleteColumn(String, String),
911
    // AlterColumnName,
912
    AlterColumnType(String, CanyonRegisterEntityField),
913
    AlterColumnDropNotNull(String, CanyonRegisterEntityField),
914
    AlterColumnSetNotNull(String, CanyonRegisterEntityField),
915

            
916
    #[cfg(feature = "mssql")]
917
    // SQL server specific operation - SQL server can't drop a NOT NULL column
918
    DropNotNullBeforeDropColumn(String, String, String),
919
    #[cfg(feature = "postgres")]
920
    AlterColumnAddIdentity(String, CanyonRegisterEntityField),
921
    #[cfg(feature = "postgres")]
922
    AlterColumnDropIdentity(String, CanyonRegisterEntityField),
923
}
924

            
925
impl Transaction<Self> for ColumnOperation {}
926

            
927
#[async_trait]
928
impl DatabaseOperation for ColumnOperation {
929
    async fn generate_sql(&self, datasource: &DatasourceConfig) {
930
        let db_type = datasource.get_db_type();
931

            
932
        let stmt = match self {
933
            ColumnOperation::CreateColumn(table_name, entity_field) =>
934
                match db_type {
935
                    #[cfg(feature = "postgres")] DatabaseType::PostgreSql =>
936
                        format!(
937
                            "ALTER TABLE \"{}\" ADD COLUMN \"{}\" {};",
938
                            table_name,
939
                            entity_field.field_name,
940
                            to_postgres_syntax(entity_field)
941
                        ),
942
                    #[cfg(feature = "mssql")] DatabaseType::SqlServer =>
943
                        format!(
944
                            "ALTER TABLE {} ADD \"{}\" {};",
945
                            table_name,
946
                            entity_field.field_name,
947
                            to_sqlserver_syntax(entity_field)
948
                        ),
949
                    #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!()
950

            
951
                }
952
            ColumnOperation::DeleteColumn(table_name, column_name) => {
953
                // TODO Check if operation for SQL server is different
954
                format!("ALTER TABLE \"{table_name}\" DROP COLUMN \"{column_name}\";")
955
            },
956
            ColumnOperation::AlterColumnType(_table_name, _entity_field) =>
957
                match db_type {
958
                    #[cfg(feature = "postgres")] DatabaseType::PostgreSql =>
959
                        format!(
960
                            "ALTER TABLE \"{_table_name}\" ALTER COLUMN \"{}\" TYPE {};",
961
                            _entity_field.field_name, to_postgres_alter_syntax(_entity_field)
962
                        ),
963
                    #[cfg(feature = "mssql")] DatabaseType::SqlServer =>
964
                        todo!("[MS-SQL -> Operation still won't supported by Canyon for Sql Server]"),
965
                    #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!()
966

            
967

            
968
                }
969
            ColumnOperation::AlterColumnDropNotNull(table_name, entity_field) =>
970
                match db_type {
971
                    #[cfg(feature = "postgres")] DatabaseType::PostgreSql =>
972
                        format!("ALTER TABLE \"{table_name}\" ALTER COLUMN \"{}\" DROP NOT NULL;", entity_field.field_name),
973
                    #[cfg(feature = "mssql")] DatabaseType::SqlServer =>
974
                        format!(
975
                            "ALTER TABLE \"{table_name}\" ALTER COLUMN {} {} NULL",
976
                            entity_field.field_name, to_sqlserver_alter_syntax(entity_field)
977
                        ),
978
                    #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!()
979

            
980
                }
981
            #[cfg(feature = "mssql")] ColumnOperation::DropNotNullBeforeDropColumn(table_name, column_name, column_datatype) =>
982
                format!(
983
                "ALTER TABLE {table_name} ALTER COLUMN {column_name} {column_datatype} NULL; DECLARE @tableName VARCHAR(MAX) = '{table_name}'
984
                DECLARE @columnName VARCHAR(MAX) = '{column_name}'
985
                DECLARE @ConstraintName nvarchar(200)
986
                SELECT @ConstraintName = Name
987
                FROM SYS.DEFAULT_CONSTRAINTS
988
                WHERE PARENT_OBJECT_ID = OBJECT_ID(@tableName)
989
                AND PARENT_COLUMN_ID = (
990
                    SELECT column_id FROM sys.columns
991
                    WHERE NAME = @columnName AND object_id = OBJECT_ID(@tableName))
992
                IF @ConstraintName IS NOT NULL
993
                    EXEC('ALTER TABLE '+@tableName+' DROP CONSTRAINT ' + @ConstraintName);"
994
            ),
995

            
996
            ColumnOperation::AlterColumnSetNotNull(table_name, entity_field) => {
997
                match db_type {
998
                    #[cfg(feature = "postgres")] DatabaseType::PostgreSql => format!(
999
                        "ALTER TABLE \"{table_name}\" ALTER COLUMN \"{}\" SET NOT NULL;", entity_field.field_name
                    ),
                    #[cfg(feature = "mssql")] DatabaseType::SqlServer => format!(
                        "ALTER TABLE \"{table_name}\" ALTER COLUMN {} {} NOT NULL",
                        entity_field.field_name,
                        to_sqlserver_alter_syntax(entity_field)
                    ),
                    #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!()
                }
            }
            #[cfg(feature = "postgres")] ColumnOperation::AlterColumnAddIdentity(table_name, entity_field) => format!(
                "ALTER TABLE \"{table_name}\" ALTER COLUMN \"{}\" ADD GENERATED ALWAYS AS IDENTITY;", entity_field.field_name
            ),
            #[cfg(feature = "postgres")] ColumnOperation::AlterColumnDropIdentity(table_name, entity_field) => format!(
                "ALTER TABLE \"{table_name}\" ALTER COLUMN \"{}\" DROP IDENTITY;", entity_field.field_name
            ),
        };
        save_migrations_query_to_execute(stmt, &datasource.name);
    }
}
/// Helper for operations involving sequences
#[cfg(feature = "postgres")]
#[derive(Debug)]
enum SequenceOperation {
    ModifySequence(String, CanyonRegisterEntityField),
}
#[cfg(feature = "postgres")]
impl Transaction<Self> for SequenceOperation {}
#[cfg(feature = "postgres")]
#[async_trait]
impl DatabaseOperation for SequenceOperation {
    async fn generate_sql(&self, datasource: &DatasourceConfig) {
        let stmt = match self {
            SequenceOperation::ModifySequence(table_name, entity_field) => {
                format!(
                    "SELECT setval(pg_get_serial_sequence('\"{table_name}\"', '{}'), max(\"{}\")) from \"{table_name}\";",
                    entity_field.field_name, entity_field.field_name
                )
            }
        };
        save_migrations_query_to_execute(stmt, &datasource.name);
    }
}