Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@ public record DdlCapabilities(boolean supportsDdl, boolean dropTableCascade, boo
boolean dropIndexRequiresTable, boolean createTableIfNotExists, boolean createIndexIfNotExists,
boolean dropIndexIfExists, boolean createOrReplaceView, boolean createOrReplaceTrigger,
boolean dropViewIfExists, boolean dropConstraintIfExists, boolean dropTableIfExists, boolean dropSchemaIfExists,
boolean requiresDropSchemaRestrict, int maxColumnNameLength) {
boolean requiresDropSchemaRestrict, int maxColumnNameLength,
boolean renameTable, boolean renameColumn, boolean renameIndex, boolean renameConstraint,
boolean atomicMultiRenameTable, boolean renameView, boolean renameTrigger, boolean renameSequence) {

/** All supported, no special requirements — default for modern engines. */
public static DdlCapabilities full() {
return new DdlCapabilities(true, true, true, false, true, true, true, true, true, true, true, true, true, false,
128);
128, true, true, true, true, true, true, true, true);
}

/** Most conservative — DDL allowed but no convenience clauses. */
public static DdlCapabilities minimal() {
return new DdlCapabilities(true, false, false, true, false, false, false, false, false, false, false, false,
false, true, 30);
false, true, 30, false, false, false, false, false, false, false, false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ default DdlCapabilities getDdlCapabilities() {
dropIndexRequiresTable(), supportsCreateTableIfNotExists(), supportsCreateIndexIfNotExists(),
supportsDropIndexIfExists(), supportsCreateOrReplaceView(), supportsCreateOrReplaceTrigger(),
supportsDropViewIfExists(), supportsDropConstraintIfExists(), supportsDropTableIfExists(),
supportsDropSchemaIfExists(), requiresDropSchemaRestrict(), getMaxColumnNameLength());
supportsDropSchemaIfExists(), requiresDropSchemaRestrict(), getMaxColumnNameLength(),
supportsRenameTable(), supportsRenameColumn(), supportsRenameIndex(), supportsRenameConstraint(),
supportsAtomicMultiRenameTable(), supportsRenameView(), supportsRenameTrigger(), supportsRenameSequence());
}

int getMaxColumnNameLength();
Expand Down Expand Up @@ -239,4 +241,21 @@ default boolean requiresDropSchemaRestrict() {
default boolean supportsNullsLast() {
return true;
}

boolean supportsRenameSequence();

boolean supportsRenameTrigger();

boolean supportsRenameView();

boolean supportsAtomicMultiRenameTable();

boolean supportsRenameConstraint();

boolean supportsRenameIndex();

boolean supportsRenameColumn();

boolean supportsRenameTable();

}
Original file line number Diff line number Diff line change
Expand Up @@ -292,21 +292,33 @@ default String alterColumnDropDefault(TableReference table, String columnName) {
}

default String renameColumn(TableReference table, String oldName, String newName) {
if (!supportsRenameColumn()) {
return null;
}
return new StringBuilder("ALTER TABLE ").append(qualified(table)).append(" RENAME COLUMN ")
.append(quoteIdentifier(oldName)).append(" TO ").append(quoteIdentifier(newName)).toString();
}

default String renameTable(TableReference table, String newName) {
if (!supportsRenameTable()) {
return null;
}
return new StringBuilder("ALTER TABLE ").append(qualified(table)).append(" RENAME TO ")
.append(quoteIdentifier(newName)).toString();
}

default String renameIndex(String oldName, String newName, TableReference table) {
if (!supportsRenameIndex()) {
return null;
}
return new StringBuilder("ALTER INDEX ").append(quoteIdentifier(oldName)).append(" RENAME TO ")
.append(quoteIdentifier(newName)).toString();
}

default String renameConstraint(TableReference table, String oldName, String newName) {
if (!supportsRenameConstraint()) {
return null;
}
return new StringBuilder("ALTER TABLE ").append(qualified(table)).append(" RENAME CONSTRAINT ")
.append(quoteIdentifier(oldName)).append(" TO ").append(quoteIdentifier(newName)).toString();
}
Expand Down Expand Up @@ -967,4 +979,107 @@ default String qualifiedRoutine(String schemaName, String routineName) {
return schemaName == null || schemaName.isBlank() ? quoteIdentifier(routineName)
: quoteIdentifier(schemaName, routineName);
}

/** @return true if {@code renameTable} renders a valid statement */
default boolean supportsRenameTable() {
return true;
}

/** @return true if {@code renameColumn} renders a valid statement */
default boolean supportsRenameColumn() {
return true;
}

/** @return true if {@code renameIndex} renders a valid statement */
default boolean supportsRenameIndex() {
return true;
}

/** @return true if {@code renameConstraint} renders a valid statement */
default boolean supportsRenameConstraint() {
return true;
}

/**
* @return true if {@code renameTables} renders ONE statement that applies all
* renames atomically (MySQL family {@code RENAME TABLE a TO b, c TO d},
* ClickHouse {@code RENAME TABLE}); false when the default renders one
* statement per pair with no atomicity guarantee
*/
default boolean supportsAtomicMultiRenameTable() {
return false;
}

/** @return true if {@code renameView} renders a valid statement */
default boolean supportsRenameView() {
return true;
}

/** @return true if {@code renameTrigger} renders a valid statement */
default boolean supportsRenameTrigger() {
return false;
}

/** @return true if {@code renameSequence} renders a valid statement */
default boolean supportsRenameSequence() {
return false;
}

/** One table-rename step of a multi-rename. */
record TableRename(TableReference table, String newName) {
}

/**
* Renames several tables. Atomic (one statement, e.g. the classic
* {@code a→tmp, b→a, tmp→b} swap) when {@code supportsAtomicMultiRenameTable()};
* otherwise one {@code renameTable} statement per step, in list order, no
* atomicity guarantee. Empty when any step is unsupported.
*/
default List<String> renameTables(List<TableRename> renames) {
if (!supportsRenameTable() || renames == null || renames.isEmpty()) {
return List.of();
}
return renames.stream().map(r -> renameTable(r.table(), r.newName())).toList();
}

/** {@code ALTER VIEW schema.view RENAME TO new}. Null when unsupported. */
default String renameView(TableReference view, String newName) {
if (!supportsRenameView()) {
return null;
}
return new StringBuilder("ALTER VIEW ").append(qualified(view)).append(" RENAME TO ")
.append(quoteIdentifier(newName)).toString();
}

/**
* PostgreSQL shape: {@code ALTER TRIGGER name ON schema.table RENAME TO new} —
* the trigger's table is part of the statement, so it is part of the
* signature. Null when unsupported (the default flag is false; PostgreSQL and
* Oracle switch it on).
*/
default String renameTrigger(String triggerName, TableReference table, String newName) {
if (!supportsRenameTrigger()) {
return null;
}
return new StringBuilder("ALTER TRIGGER ").append(quoteIdentifier(triggerName)).append(" ON ")
.append(qualified(table)).append(" RENAME TO ").append(quoteIdentifier(newName)).toString();
}

/** {@code ALTER SEQUENCE schema.name RENAME TO new}. Empty when unsupported. */
default Optional<String> renameSequence(String schemaName, String name, String newName) {
if (!supportsSequences() || !supportsRenameSequence()) {
return Optional.empty();
}
return Optional.of(new StringBuilder("ALTER SEQUENCE ").append(quoteIdentifier(schemaName, name))
.append(" RENAME TO ").append(quoteIdentifier(newName)).toString());
}

/**
* Rename with the column's full definition in hand. The default ignores the
* metadata; the MySQL family uses it to render the pre-8.0/10.5.2
* {@code ALTER TABLE t CHANGE old new <definition>} fallback.
*/
default String renameColumn(TableReference table, String oldName, String newName, ColumnMetaData currentMeta) {
return renameColumn(table, oldName, newName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.util.List;

import org.eclipse.daanse.sql.model.schema.TableReference;
import org.eclipse.daanse.sql.model.sql.BitOperation;
import org.eclipse.daanse.sql.model.sql.OrderedColumn;
import org.eclipse.daanse.sql.dialect.db.common.AbstractJdbcDialect;
Expand Down Expand Up @@ -150,6 +151,18 @@ public boolean supportsDropConstraintIfExists() {
return false;
}

/** ClickHouse has no index rename — it has no b-tree index DDL at all (see {@link #supportsIndexDdl}). */
@Override
public boolean supportsRenameIndex() {
return false; // skipping indexes: only DROP INDEX / ADD INDEX
}

/** ClickHouse has no constraint rename. */
@Override
public boolean supportsRenameConstraint() {
return false;
}

@Override
public boolean supportsCreateOrReplaceView() {
return false;
Expand Down Expand Up @@ -236,4 +249,51 @@ public boolean supportsNthValue() {
public boolean supportsListAgg() {
return true;
}

/** ClickHouse: {@code RENAME TABLE a TO b} — there is no ALTER TABLE … RENAME TO. */
@Override
public String renameTable(TableReference table, String newName) {
if (!supportsRenameTable()) {
return null;
}
return new StringBuilder("RENAME TABLE ").append(qualified(table)).append(" TO ")
.append(quoteIdentifier(newName)).toString();
}

/**
* ClickHouse views are table-like; {@code RENAME TABLE} covers them. Renders
* the statement directly rather than delegating to {@link #renameTable} so
* the two capability flags stay independent.
*/
@Override
public String renameView(TableReference view, String newName) {
if (!supportsRenameView()) {
return null;
}
return new StringBuilder("RENAME TABLE ").append(qualified(view)).append(" TO ")
.append(quoteIdentifier(newName)).toString();
}

/** {@code RENAME TABLE a TO b, c TO d} — one atomic statement. */
@Override
public List<String> renameTables(List<TableRename> renames) {
if (renames == null || renames.isEmpty()) {
return List.of();
}
StringBuilder sb = new StringBuilder("RENAME TABLE ");
for (int i = 0; i < renames.size(); i++) {
if (i > 0) {
sb.append(", ");
}
TableRename r = renames.get(i);
sb.append(qualified(r.table())).append(" TO ").append(quoteIdentifier(r.newName()));
}
return List.of(sb.toString());
}

@Override
public boolean supportsAtomicMultiRenameTable() {
return true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.daanse.sql.dialect.db.clickhouse.sqlgen;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import java.util.Optional;

import org.eclipse.daanse.sql.model.schema.SchemaReference;
import org.eclipse.daanse.sql.model.schema.TableReference;
import org.eclipse.daanse.sql.dialect.api.generator.DdlGenerator.TableRename;
import org.eclipse.daanse.sql.dialect.db.clickhouse.ClickHouseDialect;
import org.junit.jupiter.api.Test;

/**
* ClickHouse has no {@code ALTER TABLE ... RENAME TO} — table and view rename
* both go through the standalone {@code RENAME TABLE} statement, which also
* batches multiple pairs into one atomic statement. There is no b-tree index
* DDL and no constraint rename; column rename is the SQL-99 default.
*/
class ClickHouseAlterRenameOfflineTest {

private static final SchemaReference S = new SchemaReference(Optional.empty(), "PUBLIC");
private static final TableReference T = new TableReference(Optional.of(S), "EMPLOYEES", TableReference.TYPE_TABLE);
private static final TableReference V = new TableReference(Optional.of(S), "V_EMP", TableReference.TYPE_VIEW);
private static final TableReference TBL_A = new TableReference(Optional.of(S), "A", TableReference.TYPE_TABLE);
private static final TableReference TBL_B = new TableReference(Optional.of(S), "B", TableReference.TYPE_TABLE);

private final ClickHouseDialect dialect = new ClickHouseDialect();

@Test
void renameTable_uses_RENAME_TABLE() {
assertThat(dialect.ddlGenerator().renameTable(T, "STAFF"))
.isEqualTo("RENAME TABLE \"PUBLIC\".\"EMPLOYEES\" TO \"STAFF\"");
}

@Test
void renameView_uses_the_same_RENAME_TABLE_form() {
assertThat(dialect.ddlGenerator().renameView(V, "V_STAFF"))
.isEqualTo("RENAME TABLE \"PUBLIC\".\"V_EMP\" TO \"V_STAFF\"");
}

@Test
void renameTables_emits_one_atomic_RENAME_TABLE_statement() {
assertThat(dialect.ddlGenerator().supportsAtomicMultiRenameTable()).isTrue();
assertThat(dialect.ddlGenerator().renameTables(List.of(
new TableRename(TBL_A, "B"),
new TableRename(TBL_B, "C"))))
.containsExactly("RENAME TABLE \"PUBLIC\".\"A\" TO \"B\", \"PUBLIC\".\"B\" TO \"C\"");
}

@Test
void renameColumn_inherits_ANSI_default() {
assertThat(dialect.ddlGenerator().renameColumn(T, "OLD", "NEW"))
.isEqualTo("ALTER TABLE \"PUBLIC\".\"EMPLOYEES\" RENAME COLUMN \"OLD\" TO \"NEW\"");
}

@Test
void renameIndex_unsupported() {
assertThat(dialect.ddlGenerator().supportsRenameIndex()).isFalse();
assertThat(dialect.ddlGenerator().renameIndex("IDX_OLD", "IDX_NEW", T)).isNull();
}

@Test
void renameConstraint_unsupported() {
assertThat(dialect.ddlGenerator().supportsRenameConstraint()).isFalse();
assertThat(dialect.ddlGenerator().renameConstraint(T, "OLD_FK", "NEW_FK")).isNull();
}
}
Loading
Loading