Skip to content

Initial Data Model for Schema SQL Generation #1481

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 13, 2023
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@schauder -

Is there a way to get the IdentifierProcessing from the RelationalMappingContext? (or when user's use this class to generate SQL - that they can easily get an IdentifierProcessing object - that matches the one they are using for executing SQL)?

For the Unit Test(s) I think passing it in gives flexibility.

this.identifierProcssing = identifierProcessing;
}

List<List<TableModel>> 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<List<TableModel>> orderedTables = new ArrayList<List<TableModel>>();
List<TableModel> tables = new ArrayList<TableModel>();

for (TableModel table : dataModel.getTableData()) {
tables.add(table);
}
orderedTables.add(tables);

return orderedTables;
}

HashMap<Class<?>,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<List<TableModel>> orderedTables = reorderTablesInHierarchy(dataModel);

for (List<TableModel> tables : orderedTables) {
for (TableModel table : tables) {
String tableSQL = generateSQL(table);
sql.append(tableSQL + "\n");
}
}

return sql.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
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;

Expand All @@ -33,28 +35,28 @@
public class SchemaSQLGenerationDataModelTests {

@Test
void testBasicModelGeneration() {
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);
context.getRequiredPersistentEntity(SchemaSQLGenerationDataModelTests.Vader.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");

assertThat(model.getTableData().size()).isEqualTo(2);

TableModel t1 = model.getTableData().get(1);
assertThat(t1.getName().getReference()).isEqualTo("luke");
assertThat(t1.getColumns().get(0).getName()).isEqualTo("force");
assertThat(t1.getColumns().get(1).getName()).isEqualTo("be");
assertThat(t1.getColumns().get(2).getName()).isEqualTo("with");
assertThat(t1.getColumns().get(3).getName()).isEqualTo("you");
context = new RelationalMappingContext();
context.getRequiredPersistentEntity(SchemaSQLGenerationDataModelTests.Vader.class);

TableModel t2 = model.getTableData().get(1);
assertThat(t2.getName()).isEqualTo("vader");
assertThat(t2.getColumns().get(0).getName()).isEqualTo("luke_i_am_your_father");
assertThat(t2.getColumns().get(1).getName()).isEqualTo("dark_side");
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The choice of testdata names doesn't help to understand why these look the way they do look.

I consider names like EntityWithMultipleColumns or stringProperty and so one more expressive.

@Column
Expand All @@ -73,6 +75,12 @@ static class Vader {
public String lukeIAmYourFather;
@Column
public Boolean darkSide;
@Column
public Float floater;
@Column
public Double doubleClass;
@Column
public Integer integerClass;
}


Expand Down