Skip to content

Commit a851488

Browse files
committed
Handle primary keys and foreign keys defined in Alter Table statements. Handle same entity relationships.
1 parent 2e20a6e commit a851488

File tree

5 files changed

+121
-74
lines changed

5 files changed

+121
-74
lines changed

src/main/java/org/ngbsn/generator/AssociationMappingsGenerator.java

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,52 @@
44
import org.ngbsn.model.ForeignKeyConstraint;
55
import org.ngbsn.model.Table;
66

7+
import java.util.Iterator;
78
import java.util.List;
9+
import java.util.Map;
810
import java.util.Optional;
911

1012
import static org.ngbsn.generator.BiDirectionalMappingsGenerator.addBiDirectionalMappings;
13+
import static org.ngbsn.generator.ModelGenerator.tablesMap;
1114
import static org.ngbsn.generator.UniDirectionalMappingsGenerator.addBothSideUniDirectionalMappings;
1215

1316
/**
1417
* This class contains logic for generating all the association mappings
1518
*/
1619
public class AssociationMappingsGenerator {
1720

18-
public static void generateMappings(final Table table) {
19-
20-
List<ForeignKeyConstraint> foreignKeyConstraintList = table.getForeignKeyConstraints();
21-
if (foreignKeyConstraintList.size() == 1) {
22-
//Case: There is only 1 Foreign key or 1 Composite Foreign Key
23-
//Treat this as a regular table and add a new column with for parent field with @ManyToOne
24-
//and add a new column in ReferencedTable for child field with @OneToMany
25-
addBothSideUniDirectionalMappings(table, foreignKeyConstraintList.get(0));
26-
27-
} else {
28-
//Case: There are multiple Foreign Keys or multiple Composite Foreign Keys
29-
//Treat this as a Link table
30-
List<String> foreignKeyColumns = foreignKeyConstraintList.stream().flatMap(foreignKeyConstraint -> foreignKeyConstraint.getColumns().stream()).toList();
31-
Optional<Column> optionalKey = table.getColumns().stream().filter(column -> !foreignKeyColumns.contains(column.getColumnName())).findAny();
32-
if (optionalKey.isEmpty() && foreignKeyConstraintList.size() == 2) {
33-
//Case: All fields are foreign keys. Also, the relation exits between 2 entities only
34-
//Remove this link entity from the tablesMap as separate entity is not needed to track Link Table. Use @ManyToMany on other 2 entities
35-
addBiDirectionalMappings(table, foreignKeyConstraintList);
21+
public static void generateMappings() {
22+
Iterator<Map.Entry<String, Table>> it = tablesMap.entrySet().iterator();
23+
while (it.hasNext()) {
24+
Map.Entry<String, Table> item = it.next();
25+
Table table = item.getValue();
26+
List<ForeignKeyConstraint> foreignKeyConstraintList = table.getForeignKeyConstraints();
27+
if (foreignKeyConstraintList.size() == 1) {
28+
//Case: There is only 1 Foreign key or 1 Composite Foreign Key
29+
//Treat this as a regular table and add a new column with for parent field with @ManyToOne
30+
//and add a new column in ReferencedTable for child field with @OneToMany
31+
addBothSideUniDirectionalMappings(table, foreignKeyConstraintList.get(0));
3632

3733
} else {
38-
//Case1: There are some fields that are not foreign keys. So separate entity is needed to track Link Table
39-
//Case2: All fields are foreign keys. But, the relation exits between 2 or more entities
40-
//Add @ManyToOne for each foreignKey and corresponding @OneToMany in referenced Table
41-
foreignKeyConstraintList.forEach(foreignKeyConstraint -> {
42-
addBothSideUniDirectionalMappings(table, foreignKeyConstraint);
43-
});
34+
//Case: There are multiple Foreign Keys or multiple Composite Foreign Keys
35+
//Treat this as a Link table
36+
List<String> foreignKeyColumns = foreignKeyConstraintList.stream().flatMap(foreignKeyConstraint -> foreignKeyConstraint.getColumns().stream()).toList();
37+
Optional<Column> optionalKey = table.getColumns().stream().filter(column -> !foreignKeyColumns.contains(column.getColumnName())).findAny();
38+
if (optionalKey.isEmpty() && foreignKeyConstraintList.size() == 2) {
39+
//Case: All fields are foreign keys. Also, the relation exits between 2 entities only
40+
//Remove this link entity from the tablesMap as separate entity is not needed to track Link Table. Use @ManyToMany on other 2 entities
41+
it.remove();
42+
addBiDirectionalMappings(table, foreignKeyConstraintList);
43+
44+
} else {
45+
//Case1: There are some fields that are not foreign keys. So separate entity is needed to track Link Table
46+
//Case2: All fields are foreign keys. But, the relation exits between 2 or more entities
47+
//Add @ManyToOne for each foreignKey and corresponding @OneToMany in referenced Table
48+
foreignKeyConstraintList.forEach(foreignKeyConstraint -> {
49+
addBothSideUniDirectionalMappings(table, foreignKeyConstraint);
50+
});
51+
}
4452
}
4553
}
46-
4754
}
4855
}

src/main/java/org/ngbsn/generator/BiDirectionalMappingsGenerator.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717

1818
public class BiDirectionalMappingsGenerator {
1919
static void addBiDirectionalMappings(Table table, List<ForeignKeyConstraint> foreignKeyConstraintList) {
20-
tablesMap.remove(table.getTableName());
21-
Table table1 = tablesMap.get(foreignKeyConstraintList.get(0).getReferencedTableName());
22-
Table table2 = tablesMap.get(foreignKeyConstraintList.get(1).getReferencedTableName());
20+
Table table1 = tablesMap.get(foreignKeyConstraintList.get(0).getReferencedTableName().replaceAll("[\"']", ""));
21+
Table table2 = tablesMap.get(foreignKeyConstraintList.get(1).getReferencedTableName().replaceAll("[\"']", ""));
2322

2423
//Adding @ManyToMany and @JoinTable to table1
2524
Column column1 = new Column();

src/main/java/org/ngbsn/generator/ModelGenerator.java

Lines changed: 84 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
44
import net.sf.jsqlparser.statement.Statements;
5+
import net.sf.jsqlparser.statement.alter.Alter;
56
import net.sf.jsqlparser.statement.create.table.CreateTable;
67
import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex;
78
import net.sf.jsqlparser.statement.create.table.Index;
@@ -25,8 +26,8 @@
2526
import static org.ngbsn.generator.AssociationMappingsGenerator.generateMappings;
2627

2728
/**
28-
* Ignoring these annotations as they are useful only in DDL generation:
29-
* UNIQUE
29+
* This class will parse the SQL script and generate the Table models for each table in the script
30+
* It will also extract all the columns from the script and add to the Table model
3031
*/
3132
public class ModelGenerator {
3233
private static final Logger logger = LoggerFactory.getLogger(ModelGenerator.class);
@@ -35,73 +36,113 @@ public class ModelGenerator {
3536
public static List<Table> parse(final String sqlScript) {
3637
try {
3738
Statements statements = CCJSqlParserUtil.parseStatements(sqlScript);
38-
statements.getStatements().forEach(statement -> {
39-
//Iterating over all Tables
40-
if (statement instanceof CreateTable parsedTable) {
41-
Table table = new Table();
42-
table.setTableName(parsedTable.getTable().getName().replaceAll("[\"']", ""));
43-
tablesMap.put(table.getTableName(), table);
44-
table.setClassName(Util.convertSnakeCaseToCamelCase(table.getTableName(), true));
45-
46-
Set<String> tableAnnotations = new HashSet<>();
47-
table.setAnnotations(tableAnnotations);
48-
//Adding @Entity
49-
tableAnnotations.add(new EntityAnnotation().toString());
50-
//Adding @Table
51-
tableAnnotations.add(TableAnnotation.builder().tableName(table.getTableName()).build().toString());
52-
53-
Set<Column> columns = new HashSet<>();
54-
table.setColumns(columns);
55-
extractColumns(parsedTable, columns);
56-
57-
extractPrimaryKeys(parsedTable, table);
58-
59-
extractForeignKeys(parsedTable, table);
60-
61-
generateMappings(table);
62-
63-
}
64-
});
39+
processCreateTableStatements(statements);
40+
processAlterTableStatements(statements);
41+
generateMappings();
6542
return tablesMap.values().stream().toList();
6643
} catch (Exception e) {
6744
logger.error("Error occurred", e);
6845
}
6946
return null;
7047
}
7148

49+
/**
50+
* Iterate over all the Create Table statements and prepare the list of Table Model
51+
* @param statements set of statements
52+
*/
53+
private static void processCreateTableStatements(Statements statements) {
54+
statements.getStatements().forEach(statement -> {
55+
//Iterating over all Tables
56+
if (statement instanceof CreateTable parsedTable) {
57+
Table table = new Table();
58+
table.setTableName(parsedTable.getTable().getName().replaceAll("[\"']", ""));
59+
tablesMap.put(table.getTableName(), table);
60+
table.setClassName(Util.convertSnakeCaseToCamelCase(table.getTableName(), true));
61+
62+
Set<String> tableAnnotations = new HashSet<>();
63+
table.setAnnotations(tableAnnotations);
64+
//Adding @Entity
65+
tableAnnotations.add(new EntityAnnotation().toString());
66+
//Adding @Table
67+
tableAnnotations.add(TableAnnotation.builder().tableName(table.getTableName()).build().toString());
68+
69+
Set<Column> columns = new HashSet<>();
70+
table.setColumns(columns);
71+
72+
//extract columns
73+
extractColumns(parsedTable, columns);
74+
75+
//extract primary keys
76+
Optional<Index> optionalIndex = parsedTable.getIndexes().stream().filter(index -> index.getType().equals("PRIMARY KEY")).findFirst();
77+
extractPrimaryKeys(optionalIndex.orElse(null), table);
78+
79+
//extract foreign keys
80+
List<Index> foreignKeyIndexes = parsedTable.getIndexes().stream().filter(index -> index instanceof ForeignKeyIndex).toList();
81+
extractForeignKeys(foreignKeyIndexes, table);
82+
}
83+
});
84+
}
85+
86+
/**
87+
* Iterate over all the Alter Table statements and prepare the list of Table Model
88+
* @param statements set of statements
89+
*/
90+
private static void processAlterTableStatements(Statements statements) {
91+
statements.getStatements().forEach(statement -> {
92+
//Iterating over all Tables
93+
// Look for primary and foreign keys in ALTER TABLE constraints
94+
if(statement instanceof Alter alterTable){
95+
Table table = tablesMap.get(alterTable.getTable().getName().replaceAll("[\"']", ""));
96+
List<Index> foreignKeyIndexes = new ArrayList<>();
97+
alterTable.getAlterExpressions().forEach(alterExpression -> {
98+
if(alterExpression.getIndex() instanceof ForeignKeyIndex foreignKeyIndex){
99+
//case: ALTER TABLE FOREIGN KEY
100+
foreignKeyIndexes.add(alterExpression.getIndex());
101+
} else if (alterExpression.getIndex().getType().equals("PRIMARY KEY")){
102+
//case: ALTER TABLE PRIMARY KEY
103+
extractPrimaryKeys(alterExpression.getIndex(), table);
104+
}
105+
});
106+
extractForeignKeys(foreignKeyIndexes, table);
107+
}
108+
});
109+
}
110+
111+
72112
/**
73113
* Looking for all foreign keys in this table and adding it to our model
74114
*
75-
* @param parsedTable The SQL script parsed table
76-
* @param table Table model
115+
* @param foreignKeyIndexes List of foreign key indexes
116+
* @param table Table model
77117
*/
78-
private static void extractForeignKeys(CreateTable parsedTable, Table table) {
79-
List<Index> foreignKeyIndexes = parsedTable.getIndexes().stream().filter(index -> index instanceof ForeignKeyIndex).toList();
118+
private static void extractForeignKeys(List<Index> foreignKeyIndexes, Table table) {
80119
if (!foreignKeyIndexes.isEmpty()) {
81120
foreignKeyIndexes.forEach(index -> {
82121
if (index instanceof ForeignKeyIndex foreignKeyIndex) {
83122
ForeignKeyConstraint foreignKeyConstraint = new ForeignKeyConstraint();
84-
foreignKeyConstraint.setColumns(foreignKeyIndex.getColumnsNames());
85-
foreignKeyConstraint.setReferencedColumns(foreignKeyIndex.getReferencedColumnNames());
86-
foreignKeyConstraint.setReferencedTableName(foreignKeyIndex.getTable().getName());
123+
foreignKeyConstraint.setColumns(foreignKeyIndex.getColumnsNames()
124+
.stream().map(s -> s.replaceAll("[\"']", "")).collect(Collectors.toList()));
125+
foreignKeyConstraint.setReferencedColumns(foreignKeyIndex.getReferencedColumnNames()
126+
.stream().map(s -> s.replaceAll("[\"']", "")).collect(Collectors.toList()));
127+
foreignKeyConstraint.setReferencedTableName(foreignKeyIndex.getTable().getName()
128+
.replaceAll("[\"']", ""));
87129
table.getForeignKeyConstraints().add(foreignKeyConstraint);
88130
}
89131
});
90132
}
91133
}
92134

93135
/**
94-
* Looking for all primary keys in this table
136+
* Extracting primary key information from the parsed data and setting them into the Table model
95137
*
96-
* @param parsedTable The SQL script parsed table
97-
* @param table Table model
138+
* @param primaryKeyIndex primaryKeyIndex
139+
* @param table Table model
98140
*/
99-
private static void extractPrimaryKeys(CreateTable parsedTable, Table table) {
100-
Optional<Index> optionalIndex = parsedTable.getIndexes().stream().filter(index -> index.getType().equals("PRIMARY KEY")).findFirst();
101-
List<Index.ColumnParams> columnParamsList = optionalIndex.map(Index::getColumns).orElse(null);
141+
private static void extractPrimaryKeys(Index primaryKeyIndex, Table table) {
142+
List<Index.ColumnParams> columnParamsList = primaryKeyIndex != null ? primaryKeyIndex.getColumns(): null;
102143
if (columnParamsList != null) {
103144
Set<Column> primaryKeyColumns = table.getColumns().stream().
104-
filter(column -> columnParamsList.stream().anyMatch(columnParams -> columnParams.getColumnName().equals(column.getColumnName()))).collect(Collectors.toSet());
145+
filter(column -> columnParamsList.stream().anyMatch(columnParams -> columnParams.getColumnName().replaceAll("[\"']", "").equals(column.getColumnName()))).collect(Collectors.toSet());
105146

106147
if (columnParamsList.size() > 1) {
107148
table.setNumOfPrimaryKeyColumns(columnParamsList.size());

src/main/java/org/ngbsn/generator/UniDirectionalMappingsGenerator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public class UniDirectionalMappingsGenerator {
2626
* @param foreignKeyConstraint foreignKeyConstraint
2727
*/
2828
static void addBothSideUniDirectionalMappings(Table table, ForeignKeyConstraint foreignKeyConstraint) {
29-
Table referencedTable = tablesMap.get(foreignKeyConstraint.getReferencedTableName());
29+
Table referencedTable = tablesMap.get(foreignKeyConstraint.getReferencedTableName().replaceAll("[\"']", ""));
30+
3031
//In the Child table, create a new column having field name as Parent(Referenced) Table.
3132
Column parentTableField = new Column();
3233
parentTableField.setFieldName(Util.convertSnakeCaseToCamelCase(referencedTable.getTableName(), false));
@@ -36,7 +37,7 @@ static void addBothSideUniDirectionalMappings(Table table, ForeignKeyConstraint
3637

3738
//In the Parent(Referenced) table, create a new column having field name as child Table.
3839
Column childTableField = new Column();
39-
childTableField.setFieldName(Util.convertSnakeCaseToCamelCase(table.getTableName(), false));
40+
childTableField.setFieldName(Util.convertSnakeCaseToCamelCase(table.getTableName(), false) + "Set");
4041
childTableField.setType("Set<" + table.getClassName() + ">");
4142
childTableField.getAnnotations().add(OneToManyAnnotation.builder().mappedBy(parentTableField.getFieldName()).build().toString());
4243
referencedTable.getColumns().add(childTableField);

src/main/resources/templates/entity.ftl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import ${import};
77
<#list table.annotations as annotation>
88
${annotation}
99
</#list>
10-
1110
public class ${table.className}{
1211
<#if (table.numOfPrimaryKeyColumns > 1) >
1312
<#list table.embeddableClasses as embeddableClass>

0 commit comments

Comments
 (0)