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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
21 package org.oran.smo.teiv.pgsqlgenerator.schema.data;
23 import java.util.List;
26 import org.oran.smo.teiv.pgsqlgenerator.PostgresIndex;
27 import org.springframework.stereotype.Component;
29 import lombok.extern.slf4j.Slf4j;
31 import org.oran.smo.teiv.pgsqlgenerator.Column;
32 import org.oran.smo.teiv.pgsqlgenerator.PostgresConstraint;
33 import org.oran.smo.teiv.pgsqlgenerator.ForeignKeyConstraint;
34 import org.oran.smo.teiv.pgsqlgenerator.NotNullConstraint;
35 import org.oran.smo.teiv.pgsqlgenerator.PrimaryKeyConstraint;
36 import org.oran.smo.teiv.pgsqlgenerator.Table;
37 import org.oran.smo.teiv.pgsqlgenerator.UniqueConstraint;
39 import static org.oran.smo.teiv.pgsqlgenerator.Constants.CREATE;
40 import static org.oran.smo.teiv.pgsqlgenerator.Constants.ALTER;
41 import static org.oran.smo.teiv.pgsqlgenerator.Constants.ALTER_TABLE_TIES_DATA_S_ADD_CONSTRAINT_S;
42 import static org.oran.smo.teiv.pgsqlgenerator.Constants.DEFAULT;
43 import static org.oran.smo.teiv.pgsqlgenerator.Constants.GEOGRAPHY;
44 import static org.oran.smo.teiv.pgsqlgenerator.Constants.GEO_LOCATION;
45 import static org.oran.smo.teiv.pgsqlgenerator.Constants.ID;
46 import static org.oran.smo.teiv.pgsqlgenerator.Constants.ALTER_TABLE_TIES_DATA_S;
47 import static org.oran.smo.teiv.pgsqlgenerator.Constants.INDEX;
51 public class DataSchemaHelper {
54 * Generates SQL statements for schema alterations based on identified changes.
57 * Map of identified changes to models
58 * @return StringBuilder containing SQL statements
60 public StringBuilder generateSchemaFromDifferences(Map<String, List<Table>> differences) {
61 StringBuilder generatedSchema = new StringBuilder();
62 if (differences.isEmpty()) {
63 log.info("No differences identified!!");
65 for (Map.Entry<String, List<Table>> entry : differences.entrySet()) {
66 switch (entry.getKey()) {
67 case DEFAULT -> generatedSchema.append(generateDefaultStatementsFromDifferences(entry.getValue()));
68 case INDEX -> generatedSchema.append(generateIndexStatementsFromDifferences(entry.getValue()));
69 case ALTER -> generatedSchema.append(generateAlterStatementsFromDifferences(entry.getValue()));
70 case CREATE -> generatedSchema.append(generateCreateStatementsFromDifferences(entry.getValue()));
74 return generatedSchema;
78 * Generates SQL statements for CREATE TABLE from differences.
80 private StringBuilder generateCreateStatementsFromDifferences(List<Table> tables) {
81 StringBuilder storeSchemaForCreateStatements = new StringBuilder();
82 StringBuilder storeAlterStatementsForPrimaryKeyConstraints = new StringBuilder();
83 StringBuilder storeAlterStatementsForAllOtherConstraints = new StringBuilder();
84 StringBuilder storeIndexStatements = new StringBuilder();
85 for (Table table : tables) {
86 storeAlterStatementsForPrimaryKeyConstraints.append(generateAlterStatementsForPrimaryKeyConstraints(table
88 storeAlterStatementsForAllOtherConstraints.append(generateAlterStatementsForAllOtherConstraints(table
90 storeIndexStatements.append(generateIndexStatementForColumns(table.getColumns()));
91 storeSchemaForCreateStatements.append(generateCreateTableStatements(table.getColumns(), table.getName()));
93 storeSchemaForCreateStatements.append(storeAlterStatementsForPrimaryKeyConstraints).append(
94 storeAlterStatementsForAllOtherConstraints).append(storeIndexStatements);
95 return storeSchemaForCreateStatements;
99 * Generates SQL statements for creating new tables.
101 private StringBuilder generateCreateTableStatements(List<Column> newColumns, String tableName) {
103 StringBuilder storeTableSchema = new StringBuilder(String.format("CREATE TABLE IF NOT EXISTS ties_data.\"%s\" (%n",
105 StringBuilder storeColumns = new StringBuilder();
106 StringBuilder storeDefaultValues = new StringBuilder();
108 for (Column newColumn : newColumns) {
109 if (newColumn.getDefaultValue() != null) {
110 storeDefaultValues.append(generateDefaultValueStatements(newColumn, tableName));
112 // id column must come in the top of the table
113 if (newColumn.getName().equals(ID)) {
114 storeTableSchema.append(String.format("\t\"%s\"\t\t\t%s,%n", newColumn.getName(), newColumn.getDataType()));
116 storeColumns.append(generateCreateColumnStatements(newColumn));
119 storeColumns.deleteCharAt(storeColumns.lastIndexOf(","));
120 storeTableSchema.append(storeColumns).append(");\n\n");
121 return storeTableSchema.append(storeDefaultValues);
125 * Generate CREATE sql statements for columns who have no constraints, default value or enums defined.
127 private StringBuilder generateCreateColumnStatements(Column newColumn) {
128 return new StringBuilder(String.format("\t\"%s\"\t\t\t%s,%n", newColumn.getName(), newColumn.getDataType()));
132 * Generate ALTER sql statements for attributes with default values.
134 private StringBuilder generateDefaultValueStatements(Column newColumn, String tableName) {
135 return new StringBuilder(String.format(
136 "ALTER TABLE ONLY ties_data.\"%s\" ALTER COLUMN \"%s\" SET DEFAULT '%s';%n%n", tableName, newColumn
137 .getName(), newColumn.getDefaultValue()));
141 * Write sql statements for UNIQUE, NOT NULL and FOREIGN KEY constraints.
143 private StringBuilder generateAlterStatementsForAllOtherConstraints(List<Column> columns) {
144 StringBuilder storeOtherAlterStatements = new StringBuilder();
146 columns.stream().flatMap(newColumn -> newColumn.getPostgresConstraints().stream()).filter(
147 constraint -> !(constraint instanceof PrimaryKeyConstraint)).forEach(constraint -> storeOtherAlterStatements
148 .append(generateConstraintStatement(constraint)));
150 return storeOtherAlterStatements;
153 private StringBuilder generateAlterStatementsForPrimaryKeyConstraints(List<Column> columns) {
154 StringBuilder storePKAlterStatements = new StringBuilder();
156 columns.stream().flatMap(newColumn -> newColumn.getPostgresConstraints().stream()).filter(
157 PrimaryKeyConstraint.class::isInstance).forEach(constraint -> storePKAlterStatements.append(
158 generateConstraintStatement(constraint)));
160 return storePKAlterStatements;
163 private String generateConstraintStatement(PostgresConstraint postgresConstraint) {
164 String constraintSql = generateConstraintSql(postgresConstraint);
165 return String.format("SELECT ties_data.create_constraint_if_not_exists(%n\t'%s',%n '%s',%n '%s;'%n);%n%n",
166 postgresConstraint.getTableToAddConstraintTo(), postgresConstraint.getConstraintName(), constraintSql);
169 private StringBuilder generateIndexStatementForColumns(List<Column> columns) {
170 StringBuilder indexStmt = new StringBuilder();
171 columns.forEach(column -> {
172 column.getPostgresIndexList().forEach(postgresIndex -> {
173 indexStmt.append(generateIndexStatement(postgresIndex));
179 private String generateConstraintSql(PostgresConstraint postgresConstraint) {
180 if (postgresConstraint instanceof PrimaryKeyConstraint) {
181 return String.format(ALTER_TABLE_TIES_DATA_S_ADD_CONSTRAINT_S + "PRIMARY KEY (\"%s\")", postgresConstraint
182 .getTableToAddConstraintTo(), postgresConstraint.getConstraintName(), postgresConstraint
183 .getColumnToAddConstraintTo());
184 } else if (postgresConstraint instanceof ForeignKeyConstraint) {
185 return String.format(
186 ALTER_TABLE_TIES_DATA_S_ADD_CONSTRAINT_S + "FOREIGN KEY (\"%s\") REFERENCES ties_data.\"%s\" (id) ON DELETE CASCADE",
187 postgresConstraint.getTableToAddConstraintTo(), postgresConstraint.getConstraintName(),
188 postgresConstraint.getColumnToAddConstraintTo(), ((ForeignKeyConstraint) postgresConstraint)
189 .getReferencedTable());
190 } else if (postgresConstraint instanceof UniqueConstraint) {
191 return String.format(ALTER_TABLE_TIES_DATA_S_ADD_CONSTRAINT_S + "UNIQUE (\"%s\")", postgresConstraint
192 .getTableToAddConstraintTo(), postgresConstraint.getConstraintName(), postgresConstraint
193 .getColumnToAddConstraintTo());
194 } else if (postgresConstraint instanceof NotNullConstraint) {
195 return String.format(ALTER_TABLE_TIES_DATA_S_ADD_CONSTRAINT_S + "NOT NULL (\"%s\")", postgresConstraint
196 .getTableToAddConstraintTo(), postgresConstraint.getConstraintName(), postgresConstraint
197 .getColumnToAddConstraintTo());
204 * Generates SQL statements for ALTER TABLE from mapped entity attributes.
206 private StringBuilder generateAlterStatementsFromDifferences(List<Table> tables) {
207 StringBuilder storeSchemaForAlterStatements = new StringBuilder();
208 StringBuilder storeAlterStatementsForPrimaryKeyConstraints = new StringBuilder();
209 StringBuilder storeAlterStatementsForAllOtherConstraints = new StringBuilder();
210 StringBuilder storeIndexStatements = new StringBuilder();
211 for (Table table : tables) {
212 storeSchemaForAlterStatements.append(generateAlterStatements(table.getColumns(), table.getName()));
213 storeAlterStatementsForPrimaryKeyConstraints.append(generateAlterStatementsForPrimaryKeyConstraints(table
215 storeAlterStatementsForAllOtherConstraints.append(generateAlterStatementsForAllOtherConstraints(table
217 storeIndexStatements.append(generateIndexStatementForColumns(table.getColumns()));
219 return storeSchemaForAlterStatements.append(storeAlterStatementsForPrimaryKeyConstraints).append(
220 storeAlterStatementsForAllOtherConstraints).append(storeIndexStatements);
224 * Generates SQL statements for altering tables based on mapped entity attributes.
226 private StringBuilder generateAlterStatements(List<Column> columns, String tableName) {
227 StringBuilder storeSchema = new StringBuilder();
228 for (Column newColumn : columns) {
229 if (newColumn.getName().equals(GEO_LOCATION)) {
230 newColumn.setDataType(GEOGRAPHY);
232 storeSchema.append(generateAlterTableStatements(newColumn, tableName));
233 if (newColumn.getDefaultValue() != null) {
234 storeSchema.append(generateDefaultValueStatements(newColumn, tableName));
241 * Generates ALTER SQL statements to add new default values in newly identified columns.
243 private StringBuilder generateDefaultStatementsFromDifferences(List<Table> tables) {
244 StringBuilder storeSchemaForDefaultStatements = new StringBuilder();
245 for (Table table : tables) {
246 StringBuilder storeSchema = new StringBuilder();
247 for (Column newColumn : table.getColumns()) {
248 if (newColumn.getDefaultValue() != null) {
249 storeSchema.append(generateDefaultValueStatements(newColumn, table.getName()));
252 storeSchemaForDefaultStatements.append(storeSchema);
254 return storeSchemaForDefaultStatements;
258 * Generate ALTER sql statements for attributes who have no constraints, default value or enums defined.
260 private StringBuilder generateAlterTableStatements(Column newColumn, String tableName) {
261 return new StringBuilder(String.format(ALTER_TABLE_TIES_DATA_S + "ADD COLUMN IF NOT EXISTS \"%s\" %s;%n%n",
262 tableName, newColumn.getName(), newColumn.getDataType()));
265 private StringBuilder generateIndexStatementsFromDifferences(List<Table> tables) {
266 StringBuilder storeSchemaForIndexStatements = new StringBuilder();
267 for (Table table : tables) {
268 StringBuilder storeSchema = new StringBuilder();
269 for (Column column : table.getColumns()) {
270 if (!column.getPostgresIndexList().isEmpty()) {
271 column.getPostgresIndexList().forEach(index -> {
272 storeSchema.append(generateIndexStatement(index));
276 storeSchemaForIndexStatements.append(storeSchema);
278 return storeSchemaForIndexStatements;
281 private String generateIndexStatement(PostgresIndex postgresIndex) {
282 return String.format(postgresIndex.getIndexType().getCreateIndexStmt(), postgresIndex.getIndexName(), postgresIndex
283 .getTableNameToAddIndexTo(), postgresIndex.getColumnNameToAddIndexTo()) + "\n\n";