2eb328111557d718378ad686547b1d08a0941920
[smo/teiv.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2024 Ericsson
4  *  Modifications Copyright (C) 2024 OpenInfra Foundation Europe
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21 package org.oran.smo.teiv.pgsqlgenerator.schema.data;
22
23 import static org.oran.smo.teiv.pgsqlgenerator.Constants.ALTER;
24 import static org.oran.smo.teiv.pgsqlgenerator.Constants.CREATE;
25 import static org.oran.smo.teiv.pgsqlgenerator.Constants.DEFAULT;
26 import static org.oran.smo.teiv.pgsqlgenerator.Constants.INDEX;
27
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35
36 import org.oran.smo.teiv.pgsqlgenerator.IndexType;
37 import org.oran.smo.teiv.pgsqlgenerator.PostgresIndex;
38 import org.springframework.stereotype.Component;
39
40 import org.oran.smo.teiv.pgsqlgenerator.Column;
41 import org.oran.smo.teiv.pgsqlgenerator.Table;
42
43 @Component
44 public class ModelComparator {
45
46     private Map<String, List<Table>> identifiedChangesToModels;
47
48     /**
49      * Identifies differences between baseline and generated models.
50      *
51      * @param tablesFromModelService
52      *     Model information from Model Service
53      * @param tablesFromBaselineSql
54      *     Model information from the baseline
55      * @return A map with identified changes to models
56      */
57     public Map<String, List<Table>> identifyDifferencesInBaselineAndGenerated(List<Table> tablesFromModelService,
58             List<Table> tablesFromBaselineSql) {
59         //TODO: Throw error if there is table from model service doesn't contain any info in baseline
60         identifiedChangesToModels = identifiedModelChangeMapping();
61
62         List<String> tableNamesOfBaseline = extractTableNames(tablesFromBaselineSql);
63         List<String> tableNameOfModelSvc = extractTableNames(tablesFromModelService);
64
65         if (!tableNameOfModelSvc.equals(tableNamesOfBaseline)) {
66             storeNewTables(tablesFromModelService, tableNamesOfBaseline, tableNameOfModelSvc);
67         }
68         compareAndStoreChangesToColumns(tablesFromModelService, tablesFromBaselineSql);
69
70         return Collections.unmodifiableMap(identifiedChangesToModels);
71     }
72
73     private Map<String, List<Table>> identifiedModelChangeMapping() {
74         Map<String, List<Table>> storeIdentifiedChangesToModels = new LinkedHashMap<>();
75         storeIdentifiedChangesToModels.put(DEFAULT, new ArrayList<>());
76         storeIdentifiedChangesToModels.put(INDEX, new ArrayList<>());
77         storeIdentifiedChangesToModels.put(ALTER, new ArrayList<>());
78         storeIdentifiedChangesToModels.put(CREATE, new ArrayList<>());
79         return storeIdentifiedChangesToModels;
80     }
81
82     /**
83      * Check if all tables in extracted data from module service are same as what's in baseline schema Store identified with
84      * a "CREATE" key
85      */
86     private void storeNewTables(List<Table> tablesFromModelService, List<String> tableNamesOfBaseline,
87             List<String> tableNameOfGenerated) {
88         List<String> differences = tableNameOfGenerated.stream().filter(element -> !tableNamesOfBaseline.contains(element))
89                 .toList();
90         differences.forEach(tableName -> tablesFromModelService.stream().filter(table -> table.getName().equals(tableName))
91                 .findFirst().ifPresent(table -> identifiedChangesToModels.get(CREATE).add(Table.builder().name(table
92                         .getName()).columns(table.getColumns()).build())));
93     }
94
95     /**
96      * Compare columns of each table from module service with columns of each table from baseline schema
97      */
98     private void compareAndStoreChangesToColumns(List<Table> tablesFromModelService, List<Table> tablesFromBaselineSql) {
99         tablesFromModelService.forEach(tableFromModelService -> {
100             tablesFromBaselineSql.stream().filter(baselineTable -> tableFromModelService.getName().equals(baselineTable
101                     .getName())).findFirst().ifPresent(baselineTable -> {
102
103                         List<Column> columnsInBaseline = new ArrayList<>(baselineTable.getColumns());
104                         List<Column> columnsFromModuleSvc = new ArrayList<>(tableFromModelService.getColumns());
105
106                         columnsInBaseline.sort(Comparator.comparing(Column::getName));
107                         columnsFromModuleSvc.sort(Comparator.comparing(Column::getName));
108
109                         // Check for new columns in table
110                         if (columnsFromModuleSvc.size() > columnsInBaseline.size()) {
111                             storeNewColumns(tableFromModelService.getName(), columnsInBaseline, columnsFromModuleSvc);
112                         }
113                         detectAndStoreDefaultValueChanges(tableFromModelService.getName(), columnsInBaseline,
114                                 columnsFromModuleSvc);
115                         detectAndStoreNewlyAddedIndex(tableFromModelService.getName(), columnsInBaseline,
116                                 columnsFromModuleSvc);
117                     });
118         });
119     }
120
121     private List<String> extractTableNames(List<Table> tables) {
122         return tables.stream().map(Table::getName).sorted().toList();
123     }
124
125     /**
126      * Check if new columns are introduced by comparing data from module service and baseline schema
127      */
128     private void storeNewColumns(String tableName, List<Column> columnsInBaseline, List<Column> columnsFromModuleSvc) {
129         List<Column> newColumns = columnsFromModuleSvc.stream().filter(columnInGenerated -> !getListOfAllColumns(
130                 columnsInBaseline).contains(columnInGenerated.getName())).toList();
131         identifiedChangesToModels.get(ALTER).add(Table.builder().name(tableName).columns(newColumns.stream().map(
132                 column -> Column.builder().name(column.getName()).dataType(column.getDataType()).postgresConstraints(column
133                         .getPostgresConstraints()).defaultValue(column.getDefaultValue()).postgresIndexList(column
134                                 .getPostgresIndexList()).build()).toList()).build());
135     }
136
137     /**
138      * Check if default values for all columns in tables from module service are same as what's in baseline schema Store
139      * identified with a "DEFAULT" key
140      */
141     private void detectAndStoreDefaultValueChanges(String tableName, List<Column> columnsInBaseline,
142             List<Column> columnsFromModuleSvc) {
143         List<Column> list = new ArrayList<>();
144         columnsInBaseline.forEach(columnInBaseline -> {
145             columnsFromModuleSvc.forEach(columnInGenerated -> {
146                 if (columnInGenerated.getName().equals(columnInBaseline.getName()) && !Objects.equals(columnInGenerated
147                         .getDefaultValue(), columnInBaseline.getDefaultValue())) {
148                     list.add(columnInGenerated);
149                 }
150             });
151         });
152         if (!list.isEmpty()) {
153             identifiedChangesToModels.get(DEFAULT).add(Table.builder().name(tableName).columns(list).build());
154         }
155     }
156
157     private void detectAndStoreNewlyAddedIndex(String tableName, List<Column> columnsInBaseline,
158             List<Column> columnsFromModuleSvc) {
159         List<Column> columnList = new ArrayList<>();
160         columnsInBaseline.forEach(columnInBaseline -> columnsFromModuleSvc.forEach(columnInGenerated -> {
161             if (columnInGenerated.getName().equals(columnInBaseline.getName())) {
162                 List<IndexType> indexInBaselineSchema = columnInBaseline.getPostgresIndexList().stream().map(
163                         PostgresIndex::getIndexType).toList();
164                 List<PostgresIndex> postgresIndexList = new ArrayList<>();
165                 columnInGenerated.getPostgresIndexList().forEach(postgresIndex -> {
166                     if (!indexInBaselineSchema.contains(postgresIndex.getIndexType())) {
167                         postgresIndexList.add(postgresIndex);
168                     }
169                 });
170                 if (!postgresIndexList.isEmpty()) {
171                     columnInGenerated.setPostgresIndexList(postgresIndexList);
172                     columnList.add(columnInGenerated);
173                 }
174             }
175         }));
176         if (!columnList.isEmpty()) {
177             identifiedChangesToModels.get(INDEX).add(Table.builder().name(tableName).columns(columnList).build());
178         }
179     }
180
181     private List<String> getListOfAllColumns(List<Column> columns) {
182         List<String> allColumns = new ArrayList<>();
183         for (Column col : columns) {
184             allColumns.add(col.getName().replace("\"", ""));
185         }
186         return allColumns;
187     }
188
189 }