io.datarouter.client.mysql.ddl.generate.SqlAlterTableGeneratorFactory Maven / Gradle / Ivy
The newest version!
/*
* Copyright © 2009 HotPads ([email protected])
*
* 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
*
* http://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 io.datarouter.client.mysql.ddl.generate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.datarouter.client.mysql.ddl.domain.SqlColumn;
import io.datarouter.client.mysql.ddl.domain.SqlIndex;
import io.datarouter.client.mysql.ddl.domain.SqlTable;
import io.datarouter.storage.config.schema.SchemaUpdateOptions;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class SqlAlterTableGeneratorFactory{
private enum PrintVersion{
BOTH,
QUICK,
THOROUGH,
;
}
private static class DdlBuilder{
private static final boolean PREVENT_START_UP = true;
final List executeAlters;
final List quickPrintAlters;
final List thoroughPrintAlters;
boolean preventStartUp;
DdlBuilder(){
executeAlters = new ArrayList<>();
quickPrintAlters = new ArrayList<>();
thoroughPrintAlters = new ArrayList<>();
}
void add(Boolean execute, CharSequence alter, boolean required){
add(execute, Arrays.asList(alter), required, PrintVersion.BOTH);
}
void add(Boolean execute, List alters, boolean required){
add(execute, alters, required, PrintVersion.BOTH);
}
void add(Boolean execute, CharSequence alter, boolean required, PrintVersion printVersion){
add(execute, Arrays.asList(alter), required, printVersion);
}
void add(Boolean execute, List alters, boolean required, PrintVersion printVersion){
if(execute){
executeAlters.addAll(alters);
}else{
switch(printVersion){
case BOTH:
quickPrintAlters.addAll(alters);
thoroughPrintAlters.addAll(alters);
break;
case QUICK:
quickPrintAlters.addAll(alters);
break;
case THOROUGH:
thoroughPrintAlters.addAll(alters);
break;
}
}
if(!alters.isEmpty() && !execute && required && PREVENT_START_UP){
preventStartUp = true;
}
}
Ddl build(String hostname, String databaseName, String tableName){
String alterTablePrefix = "alter table " + databaseName + "." + tableName + "\n";
Optional print;
if(quickPrintAlters.isEmpty() && thoroughPrintAlters.isEmpty()){
print = Optional.empty();
}else{
Stream> scanner;
if(quickPrintAlters.equals(thoroughPrintAlters)){
scanner = Stream.of(quickPrintAlters);
}else{
scanner = Stream.of(quickPrintAlters, thoroughPrintAlters);
}
print = Optional.of(scanner
.map(clauses -> makeAlter(alterTablePrefix, clauses) + "\n"
+ "\n"
+ makePerconaToolkitCommand(hostname, databaseName, tableName, clauses))
.collect(Collectors.joining("\n\n")));
}
return new Ddl(
makeStatementFromClauses(alterTablePrefix, executeAlters),
print,
preventStartUp);
}
private static String makePerconaToolkitCommand(
String hostname,
String databaseName,
String tableName,
List clauses){
return "pt-online-schema-change "
+ "h=" + hostname + ",D=" + databaseName + ",t=" + tableName + " "
+ "--execute "
+ "--user=root "
+ "--ask-pass "
+ "--critical-load \"Threads_running=500\" "
+ "--alter=\"" + String.join(", ", clauses) + "\"";
}
Optional makeStatementFromClauses(String alterTablePrefix, List alters){
if(alters.isEmpty()){
return Optional.empty();
}
return Optional.of(makeAlter(alterTablePrefix, alters));
}
String makeAlter(String alterTablePrefix, List alters){
return alterTablePrefix + String.join(",\n", alters) + ";";
}
}
@Inject
private SchemaUpdateOptions schemaUpdateOptions;
@Inject
private DatabaseHostnameUrlSupplier databaseDnsSupplier;
public class SqlAlterTableGenerator{
private final SqlTable current;
private final SqlTable requested;
private final String hostname;
private final String databaseName;
public SqlAlterTableGenerator(SqlTable current, SqlTable requested, String hostname, String databaseName){
this.current = current;
this.requested = requested;
this.hostname = hostname;
this.databaseName = databaseName;
}
public Ddl generateDdl(){
var diff = new SqlTableDiffGenerator(current, requested);
if(!diff.isTableModified()){
return new Ddl(Optional.empty(), Optional.empty(), false);
}
diff.throwIfColumnTypesModified();
DdlBuilder ddlBuilder = generate(diff);
String hostnameUrl = databaseDnsSupplier.getHostnameUrl(databaseName)
.orElse(hostname);
return ddlBuilder.build(hostnameUrl, databaseName, current.getName());
}
private boolean printOrExecute(Function option){
return option.apply(false) || option.apply(true);
}
private DdlBuilder generate(SqlTableDiffGenerator diff){
DdlBuilder ddlBuilder = new DdlBuilder();
if(printOrExecute(schemaUpdateOptions::getAddColumns)){
ddlBuilder.add(
schemaUpdateOptions.getAddColumns(false),
getAlterTableForAddingColumns(diff.getColumnsToAdd()),
true);
}
if(printOrExecute(schemaUpdateOptions::getDeleteColumns)){
ddlBuilder.add(
schemaUpdateOptions.getDeleteColumns(false),
getAlterTableForRemovingColumns(diff.getColumnsToRemove()),
false);
}
if(printOrExecute(schemaUpdateOptions::getModifyColumns)){
ddlBuilder.add(
schemaUpdateOptions.getModifyColumns(false),
getAlterTableForModifyingColumns(diff.getColumnsToModify()),
false);
}
if(printOrExecute(schemaUpdateOptions::getModifyPrimaryKey) && diff.isPrimaryKeyModified()){
boolean execute = schemaUpdateOptions.getModifyPrimaryKey(false);
List pkColumnNames = requested.getPrimaryKey().getColumnNames();
addPk(ddlBuilder, execute, pkColumnNames, PrintVersion.QUICK);
if(!execute){
boolean pkUniqueIndexExists = current.getUniqueIndexes().stream()
.anyMatch(index -> index.getColumnNames().equals(pkColumnNames));
if(pkUniqueIndexExists){
addPk(ddlBuilder, execute, pkColumnNames, PrintVersion.THOROUGH);
}else{
var sqlIndex = new SqlIndex("temp_pk", pkColumnNames);
ddlBuilder.add(
execute,
getAlterTableForAddingIndexes(Set.of(sqlIndex), true),
true,
PrintVersion.THOROUGH);
}
}
}
if(printOrExecute(schemaUpdateOptions::getDropIndexes)){
ddlBuilder.add(
schemaUpdateOptions.getDropIndexes(false),
getAlterTableForRemovingIndexes(diff.getIndexesToRemove()),
false);
ddlBuilder.add(
schemaUpdateOptions.getDropIndexes(false),
getAlterTableForRemovingIndexes(diff.getUniqueIndexesToRemove()),
false);
}
if(printOrExecute(schemaUpdateOptions::getAddIndexes)){
ddlBuilder.add(
schemaUpdateOptions.getAddIndexes(false),
getAlterTableForAddingIndexes(diff.getIndexesToAdd(), false),
true);
ddlBuilder.add(
schemaUpdateOptions.getAddIndexes(false),
getAlterTableForAddingIndexes(diff.getUniqueIndexesToAdd(), true),
true);
}
if(printOrExecute(schemaUpdateOptions::getModifyEngine) && diff.isEngineModified()){
ddlBuilder.add(
schemaUpdateOptions.getModifyEngine(false),
"engine=" + requested.getEngine().toString().toLowerCase(),
false);
}
if(printOrExecute(schemaUpdateOptions::getModifyCharacterSetOrCollation)
&& (diff.isCharacterSetModified() || diff.isCollationModified())){
String characterSet = requested.getCharacterSet().toString();
String collation = requested.getCollation().toString();
ddlBuilder.add(
schemaUpdateOptions.getModifyCharacterSetOrCollation(false),
"character set " + characterSet + " collate " + collation,
false);
}
if(printOrExecute(schemaUpdateOptions::getModifyRowFormat) && diff.isRowFormatModified()){
ddlBuilder.add(
schemaUpdateOptions.getModifyRowFormat(false),
"row_format=" + requested.getRowFormat().value,
false);
}
return ddlBuilder;
}
private void addPk(DdlBuilder ddlBuilder, Boolean execute, List pkColumNames,
PrintVersion printVersion){
if(current.hasPrimaryKey()){
ddlBuilder.add(
execute,
"drop primary key",
false,
printVersion);
}
ddlBuilder.add(
execute,
"add primary key (" + String.join(",", pkColumNames) + ")",
false,
printVersion);
}
private List getAlterTableForAddingColumns(List colsToAdd){
return colsToAdd.stream()
.map(this::makeAddColumnDefinition)
.collect(Collectors.toUnmodifiableList());
}
private List getAlterTableForRemovingColumns(List colsToRemove){
return colsToRemove.stream()
.map(SqlColumn::getName)
.map("drop column "::concat)
.collect(Collectors.toUnmodifiableList());
}
private List getAlterTableForModifyingColumns(List columnsToModify){
return columnsToModify.stream()
.map(this::makeModifyColumnDefinition)
.collect(Collectors.toUnmodifiableList());
}
private StringBuilder makeModifyColumnDefinition(SqlColumn column){
return column.makeColumnDefinition("modify ");
}
private StringBuilder makeAddColumnDefinition(SqlColumn column){
return column.makeColumnDefinition("add ");
}
private List getAlterTableForRemovingIndexes(Set indexesToDrop){
return indexesToDrop.stream()
.map(SqlIndex::getName)
.map("drop index "::concat)
.collect(Collectors.toUnmodifiableList());
}
private List getAlterTableForAddingIndexes(
Set indexesToAdd,
boolean unique){
return indexesToAdd.stream()
.map(index -> {
String csvColumns = index.getColumnNames().stream()
.collect(Collectors.joining(",", "(", ")"));
StringBuilder sb = new StringBuilder("add ");
if(unique){
sb.append("unique ");
}
return sb.append("index ").append(index.getName()).append(csvColumns);
})
.collect(Collectors.toUnmodifiableList());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy