diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index e0d059aec0..d2bb25024f 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -7,7 +7,6 @@ spring-data-jdbc 3.1.0-756-SNAPSHOT - Spring Data JDBC Spring Data module for JDBC repositories. https://projects.spring.io/spring-data-jdbc diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java index c6712a4c9a..4a20ba7080 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java @@ -22,6 +22,8 @@ import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; +import java.util.Iterator; + /** * {@link MappingContext} implementation. * @@ -101,5 +103,4 @@ protected RelationalPersistentProperty createPersistentProperty(Property propert public NamingStrategy getNamingStrategy() { return this.namingStrategy; } - } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/BaseTypeMapper.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/BaseTypeMapper.java new file mode 100644 index 0000000000..9e8f5335e0 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/BaseTypeMapper.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping.schemasqlgeneration; + +import java.util.HashMap; + +public class BaseTypeMapper { + + final HashMap,String> mapClassToDatabaseType = new HashMap,String>(); + + public BaseTypeMapper() { + mapClassToDatabaseType.put(String.class, "VARCHAR(255)"); + mapClassToDatabaseType.put(Boolean.class, "TINYINT"); + mapClassToDatabaseType.put(Double.class, "DOUBLE"); + mapClassToDatabaseType.put(Float.class, "FLOAT"); + mapClassToDatabaseType.put(Integer.class, "INT"); + } + public String databaseTypeFromClass(Class type) { + return mapClassToDatabaseType.get(type); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/ColumnModel.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/ColumnModel.java new file mode 100644 index 0000000000..18cd0d12c1 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/ColumnModel.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping.schemasqlgeneration; + +import org.springframework.data.relational.core.sql.SqlIdentifier; + +import java.io.Serial; +import java.io.Serializable; + +/** + * Class that models a Column for generating SQL for Schema generation. + * + * @author Kurt Niemi + */ +public class ColumnModel implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + private final SqlIdentifier name; + private final String type; + private final boolean nullable; + + public ColumnModel(SqlIdentifier name, String type, boolean nullable) { + this.name = name; + this.type = type; + this.nullable = nullable; + } + + public ColumnModel(SqlIdentifier name, String type) { + this.name = name; + this.type = type; + this.nullable = false; + } + + public SqlIdentifier getName() { + return name; + } + + public String getType() { + return type; + } + + public boolean isNullable() { + return nullable; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/ForeignKeyColumnModel.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/ForeignKeyColumnModel.java new file mode 100644 index 0000000000..04bc3d46f0 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/ForeignKeyColumnModel.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping.schemasqlgeneration; + +import java.io.Serial; +import java.io.Serializable; + +/** + * Class that models a Foreign Key relationship for generating SQL for Schema generation. + * + * @author Kurt Niemi + */ +public class ForeignKeyColumnModel implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + private final TableModel foreignTable; + private final ColumnModel foreignColumn; + private final ColumnModel column; + + public ForeignKeyColumnModel(TableModel foreignTable, ColumnModel foreignColumn, ColumnModel column) { + this.foreignTable = foreignTable; + this.foreignColumn = foreignColumn; + this.column = column; + } + + public TableModel getForeignTable() { + return foreignTable; + } + + public ColumnModel getForeignColumn() { + return foreignColumn; + } + + public ColumnModel getColumn() { + return column; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/SchemaSQLGenerationDataModel.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/SchemaSQLGenerationDataModel.java new file mode 100644 index 0000000000..aa31a656f5 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/SchemaSQLGenerationDataModel.java @@ -0,0 +1,77 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping.schemasqlgeneration; + +import org.springframework.data.relational.core.mapping.BasicRelationalPersistentProperty; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +/** + * Model class that contains Table/Column information that can be used + * to generate SQL for Schema generation. + * + * @author Kurt Niemi + */ +public class SchemaSQLGenerationDataModel implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + private final List tableData = new ArrayList(); + BaseTypeMapper typeMapper; + + /** + * Default constructor so that we can deserialize a model + */ + public SchemaSQLGenerationDataModel() { + } + + /** + * Create model from a RelationalMappingContext + */ + public SchemaSQLGenerationDataModel(RelationalMappingContext context) { + + if (typeMapper == null) { + typeMapper = new BaseTypeMapper(); + } + + for (RelationalPersistentEntity entity : context.getPersistentEntities()) { + TableModel tableModel = new TableModel(entity.getTableName()); + + Iterator iter = + entity.getPersistentProperties(Column.class).iterator(); + + while (iter.hasNext()) { + BasicRelationalPersistentProperty p = iter.next(); + ColumnModel columnModel = new ColumnModel(p.getColumnName(), + typeMapper.databaseTypeFromClass(p.getActualType()), + true); + tableModel.getColumns().add(columnModel); + } + tableData.add(tableModel); + } + } + + public List getTableData() { + return tableData; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/SchemaSQLGenerator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/SchemaSQLGenerator.java new file mode 100644 index 0000000000..8b9c8a77a0 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/SchemaSQLGenerator.java @@ -0,0 +1,113 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping.schemasqlgeneration; + +import org.springframework.data.relational.core.sql.IdentifierProcessing; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class SchemaSQLGenerator { + + private final IdentifierProcessing identifierProcssing; + public SchemaSQLGenerator(IdentifierProcessing identifierProcessing) { + this.identifierProcssing = identifierProcessing; + } + + List> reorderTablesInHierarchy(SchemaSQLGenerationDataModel dataModel) { + + // ::TODO:: Take Parent/Child relationships into account (i.e if a child table has + // a Foreign Key to a table, that parent table needs to be created first. + + // For now this method will simple put the tables in the same level + List> orderedTables = new ArrayList>(); + List tables = new ArrayList(); + + for (TableModel table : dataModel.getTableData()) { + tables.add(table); + } + orderedTables.add(tables); + + return orderedTables; + } + + HashMap,String> mapClassToSQLType = null; + + public String generateSQL(ColumnModel column) { + + StringBuilder sql = new StringBuilder(); + sql.append(column.getName().toSql(identifierProcssing)); + sql.append(" "); + + sql.append(column.getType()); + + if (!column.isNullable()) { + sql.append(" NOT NULL"); + } + + return sql.toString(); + } + + public String generatePrimaryKeySQL(TableModel table) { + // ::TODO:: Implement + return ""; + } + + public String generateForeignKeySQL(TableModel table) { + // ::TODO:: Implement + return ""; + } + + public String generateSQL(TableModel table) { + + StringBuilder sql = new StringBuilder(); + + sql.append("CREATE TABLE "); + sql.append(table.getName().toSql(identifierProcssing)); + sql.append(" ("); + + int numColumns = table.getColumns().size(); + for (int i=0; i < numColumns; i++) { + sql.append(generateSQL(table.getColumns().get(i))); + if (i != numColumns-1) { + sql.append(","); + } + } + + sql.append(generatePrimaryKeySQL(table)); + sql.append(generateForeignKeySQL(table)); + + sql.append(" );"); + + return sql.toString(); + } + + public String generateSQL(SchemaSQLGenerationDataModel dataModel) { + + StringBuilder sql = new StringBuilder(); + List> orderedTables = reorderTablesInHierarchy(dataModel); + + for (List tables : orderedTables) { + for (TableModel table : tables) { + String tableSQL = generateSQL(table); + sql.append(tableSQL + "\n"); + } + } + + return sql.toString(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/TableModel.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/TableModel.java new file mode 100644 index 0000000000..fb59264ffe --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/schemasqlgeneration/TableModel.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping.schemasqlgeneration; + +import org.springframework.data.relational.core.sql.SqlIdentifier; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Class that models a Table for generating SQL for Schema generation. + * + * @author Kurt Niemi + */ +public class TableModel implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + private final String schema; + private final SqlIdentifier name; + private final List columns = new ArrayList(); + private final List keyColumns = new ArrayList(); + private final List foreignKeyColumns = new ArrayList(); + + public TableModel(String schema, SqlIdentifier name) { + this.schema = schema; + this.name = name; + } + public TableModel(SqlIdentifier name) { + this(null, name); + } + + public String getSchema() { + return schema; + } + + public SqlIdentifier getName() { + return name; + } + + public List getColumns() { + return columns; + } + + public List getKeyColumns() { + return keyColumns; + } + + public List getForeignKeyColumns() { + return foreignKeyColumns; + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/schemasqlgeneration/SchemaSQLGenerationDataModelTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/schemasqlgeneration/SchemaSQLGenerationDataModelTests.java new file mode 100644 index 0000000000..517c369d63 --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/schemasqlgeneration/SchemaSQLGenerationDataModelTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.schemasqlgeneration; + +import org.junit.jupiter.api.Test; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.data.relational.core.mapping.Table; +import org.springframework.data.relational.core.mapping.schemasqlgeneration.SchemaSQLGenerationDataModel; +import org.springframework.data.relational.core.mapping.schemasqlgeneration.SchemaSQLGenerator; +import org.springframework.data.relational.core.mapping.schemasqlgeneration.TableModel; +import org.springframework.data.relational.core.sql.IdentifierProcessing; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for the {@link SchemaSQLGenerationDataModel}. + * + * @author Kurt Niemi + */ +public class SchemaSQLGenerationDataModelTests { + + @Test + void testBasicSchemaSQLGeneration() { + + IdentifierProcessing.Quoting quoting = new IdentifierProcessing.Quoting("`"); + IdentifierProcessing identifierProcessing = IdentifierProcessing.create(quoting, IdentifierProcessing.LetterCasing.LOWER_CASE); + SchemaSQLGenerator generator = new SchemaSQLGenerator(identifierProcessing); + + RelationalMappingContext context = new RelationalMappingContext(); + context.getRequiredPersistentEntity(SchemaSQLGenerationDataModelTests.Luke.class); + + SchemaSQLGenerationDataModel model = new SchemaSQLGenerationDataModel(context); + String sql = generator.generateSQL(model); + assertThat(sql).isEqualTo("CREATE TABLE `luke` (`force` VARCHAR(255),`be` VARCHAR(255),`with` VARCHAR(255),`you` VARCHAR(255) );\n"); + + context = new RelationalMappingContext(); + context.getRequiredPersistentEntity(SchemaSQLGenerationDataModelTests.Vader.class); + + model = new SchemaSQLGenerationDataModel(context); + sql = generator.generateSQL(model); + assertThat(sql).isEqualTo("CREATE TABLE `vader` (`luke_i_am_your_father` VARCHAR(255),`dark_side` TINYINT,`floater` FLOAT,`double_class` DOUBLE,`integer_class` INT );\n"); + } + + + @Table + static class Luke { + @Column + public String force; + @Column + public String be; + @Column + public String with; + @Column + public String you; + } + + @Table + static class Vader { + @Column + public String lukeIAmYourFather; + @Column + public Boolean darkSide; + @Column + public Float floater; + @Column + public Double doubleClass; + @Column + public Integer integerClass; + } + + +}