This is an automated email from the ASF dual-hosted git repository.

jooger pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 7bee0308b32 IGNITE-27809 Add support for EXISTS keyword for SQL 
add/drop column (#7585)
7bee0308b32 is described below

commit 7bee0308b32456b8f3e2c85a7b2dbed66e16b2c9
Author: Ivan Zlenko <[email protected]>
AuthorDate: Fri Feb 13 17:31:14 2026 +0500

    IGNITE-27809 Add support for EXISTS keyword for SQL add/drop column (#7585)
---
 .../commands/AlterTableAddColumnCommand.java       | 37 +++++++---
 .../AlterTableAddColumnCommandBuilder.java         |  3 +
 .../commands/AlterTableDropColumnCommand.java      | 78 ++++++++++++++++------
 .../AlterTableDropColumnCommandBuilder.java        |  3 +
 .../AlterTableAddColumnCommandValidationTest.java  | 22 ++++++
 .../AlterTableDropColumnCommandValidationTest.java | 20 ++++++
 .../ignite/internal/sql/api/ItSqlApiBaseTest.java  |  4 ++
 .../src/main/codegen/includes/parserImpls.ftl      | 10 +--
 .../prepare/ddl/DdlSqlToCommandConverter.java      |  2 +
 .../engine/sql/IgniteSqlAlterTableAddColumn.java   | 42 ++++++++++--
 .../engine/sql/IgniteSqlAlterTableDropColumn.java  | 42 ++++++++++--
 .../internal/sql/engine/sql/SqlDdlParserTest.java  | 41 ++++++++++++
 12 files changed, 261 insertions(+), 43 deletions(-)

diff --git 
a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommand.java
 
b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommand.java
index 4d6f4fceb1f..195ee1b9512 100644
--- 
a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommand.java
+++ 
b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommand.java
@@ -50,6 +50,8 @@ public class AlterTableAddColumnCommand extends 
AbstractTableCommand {
 
     private final List<ColumnParams> columns;
 
+    private final boolean ifColumnNotExists;
+
     /**
      * Constructs the object.
      *
@@ -63,12 +65,15 @@ public class AlterTableAddColumnCommand extends 
AbstractTableCommand {
             String tableName,
             String schemaName,
             boolean ifTableExists,
-            List<ColumnParams> columns
+            List<ColumnParams> columns,
+            boolean ifColumnNotExists
     ) throws CatalogValidationException {
         super(schemaName, tableName, ifTableExists, true);
 
         this.columns = copyOrNull(columns);
 
+        this.ifColumnNotExists = ifColumnNotExists;
+
         validate();
     }
 
@@ -88,16 +93,22 @@ public class AlterTableAddColumnCommand extends 
AbstractTableCommand {
         List<CatalogTableColumnDescriptor> columnDescriptors = new 
ArrayList<>();
 
         for (ColumnParams column : columns) {
-            if (table.column(column.name()) != null) {
+            CatalogTableColumnDescriptor columnDescriptor = 
table.column(column.name());
+
+            if (columnDescriptor != null && !ifColumnNotExists) {
                 throw new CatalogValidationException("Column with name '{}' 
already exists.", column.name());
+            } else if (columnDescriptor == null) {
+                columnDescriptors.add(fromParams(column));
             }
-
-            columnDescriptors.add(fromParams(column));
         }
 
-        return List.of(
-                new NewColumnsEntry(table.id(), columnDescriptors)
-        );
+        if (columnDescriptors.isEmpty()) {
+            return List.of();
+        } else {
+            return List.of(
+                    new NewColumnsEntry(table.id(), columnDescriptors)
+            );
+        }
     }
 
     private void validate() {
@@ -129,6 +140,8 @@ public class AlterTableAddColumnCommand extends 
AbstractTableCommand {
 
         private boolean ifTableExists;
 
+        private boolean ifColumnNotExists;
+
         @Override
         public AlterTableAddColumnCommandBuilder schemaName(String schemaName) 
{
             this.schemaName = schemaName;
@@ -157,13 +170,21 @@ public class AlterTableAddColumnCommand extends 
AbstractTableCommand {
             return this;
         }
 
+        @Override
+        public AlterTableAddColumnCommandBuilder ifColumnNotExists(boolean 
notExists) {
+            this.ifColumnNotExists = notExists;
+
+            return this;
+        }
+
         @Override
         public CatalogCommand build() {
             return new AlterTableAddColumnCommand(
                     tableName,
                     schemaName,
                     ifTableExists,
-                    columns
+                    columns,
+                    ifColumnNotExists
             );
         }
     }
diff --git 
a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandBuilder.java
 
b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandBuilder.java
index c5952106097..20fb61eed44 100644
--- 
a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandBuilder.java
+++ 
b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandBuilder.java
@@ -29,4 +29,7 @@ import java.util.List;
 public interface AlterTableAddColumnCommandBuilder extends 
AbstractTableCommandBuilder<AlterTableAddColumnCommandBuilder> {
     /** List of columns to add to the table. There must be at least one 
column. */
     AlterTableAddColumnCommandBuilder columns(List<ColumnParams> columns);
+
+    /** The flag indicating that the command should not fail if a column with 
the same name already exists. */
+    AlterTableAddColumnCommandBuilder ifColumnNotExists(boolean notExists);
 }
diff --git 
a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommand.java
 
b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommand.java
index 9e4d53dc908..e2fd5046292 100644
--- 
a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommand.java
+++ 
b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommand.java
@@ -26,6 +26,7 @@ import static 
org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
 import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 import it.unimi.dsi.fastutil.ints.IntSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -43,6 +44,7 @@ import 
org.apache.ignite.internal.catalog.descriptors.CatalogTableColumnDescript
 import org.apache.ignite.internal.catalog.descriptors.CatalogTableDescriptor;
 import org.apache.ignite.internal.catalog.storage.DropColumnsEntry;
 import org.apache.ignite.internal.catalog.storage.UpdateEntry;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * A command that deletes columns from the table.
@@ -55,6 +57,8 @@ public class AlterTableDropColumnCommand extends 
AbstractTableCommand {
 
     private final Set<String> columns;
 
+    private final boolean ifColumnExists;
+
     /**
      * Constructs the object.
      *
@@ -68,7 +72,8 @@ public class AlterTableDropColumnCommand extends 
AbstractTableCommand {
             String tableName,
             String schemaName,
             boolean ifTableExists,
-            Set<String> columns
+            Set<String> columns,
+            boolean ifColumnExists
     ) throws CatalogValidationException {
         super(schemaName, tableName, ifTableExists, true);
 
@@ -76,6 +81,8 @@ public class AlterTableDropColumnCommand extends 
AbstractTableCommand {
         validate(columns);
 
         this.columns = copyOrNull(columns);
+
+        this.ifColumnExists = ifColumnExists;
     }
 
     @Override
@@ -96,31 +103,52 @@ public class AlterTableDropColumnCommand extends 
AbstractTableCommand {
                 .collect(IntOpenHashSet::new, IntSet::add, IntSet::addAll);
 
         // To validate always in the same order let's sort given columns
-        columns.stream().sorted().forEach(columnName -> {
-            CatalogTableColumnDescriptor column = table.column(columnName);
-            if (column == null) {
+        Set<String> columnsToDrop = columns.stream()
+                .sorted()
+                .map(columnName -> retrieveValidatedColumnName(columnName, 
catalog, table, indexedColumns))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        if (columnsToDrop.isEmpty()) {
+            return List.of();
+        } else {
+            return List.of(
+                    new DropColumnsEntry(table.id(), columnsToDrop)
+            );
+        }
+    }
+
+    private @Nullable String retrieveValidatedColumnName(
+            String columnName,
+            Catalog catalog,
+            CatalogTableDescriptor table,
+            IntSet indexedColumns
+    ) {
+        CatalogTableColumnDescriptor column = table.column(columnName);
+        if (column == null) {
+            if (ifColumnExists) {
+                return null;
+            } else {
                 throw new CatalogValidationException(
                         "Column with name '{}' not found in table '{}.{}'.", 
columnName, schemaName, tableName);
             }
+        }
 
-            if (table.isPrimaryKeyColumn(columnName)) {
-                throw new CatalogValidationException("Deleting column `{}` 
belonging to primary key is not allowed.", columnName);
-            }
+        if (table.isPrimaryKeyColumn(columnName)) {
+            throw new CatalogValidationException("Deleting column `{}` 
belonging to primary key is not allowed.", columnName);
+        }
 
-            if (indexedColumns.contains(column.id())) {
-                List<String> indexesNames = aliveIndexesForTable(catalog, 
table.id())
-                        .filter(index -> indexColumnIds(index).anyMatch(id -> 
id == column.id()))
-                        .map(CatalogIndexDescriptor::name)
-                        .collect(Collectors.toList());
+        if (indexedColumns.contains(column.id())) {
+            List<String> indexesNames = aliveIndexesForTable(catalog, 
table.id())
+                    .filter(index -> indexColumnIds(index).anyMatch(id -> id 
== column.id()))
+                    .map(CatalogIndexDescriptor::name)
+                    .collect(Collectors.toList());
 
-                throw new CatalogValidationException("Deleting column '{}' 
used by index(es) {}, it is not allowed.",
-                        columnName, indexesNames);
-            }
-        });
+            throw new CatalogValidationException("Deleting column '{}' used by 
index(es) {}, it is not allowed.",
+                    columnName, indexesNames);
+        }
 
-        return List.of(
-                new DropColumnsEntry(table.id(), columns)
-        );
+        return column.name();
     }
 
     private static Stream<CatalogIndexDescriptor> aliveIndexesForTable(Catalog 
catalog, int tableId) {
@@ -162,6 +190,8 @@ public class AlterTableDropColumnCommand extends 
AbstractTableCommand {
 
         private boolean ifTableExists;
 
+        private boolean ifColumnExists;
+
         @Override
         public AlterTableDropColumnCommandBuilder schemaName(String 
schemaName) {
             this.schemaName = schemaName;
@@ -190,13 +220,21 @@ public class AlterTableDropColumnCommand extends 
AbstractTableCommand {
             return this;
         }
 
+        @Override
+        public AlterTableDropColumnCommandBuilder ifColumnExists(boolean 
ifColumnExists) {
+            this.ifColumnExists = ifColumnExists;
+
+            return this;
+        }
+
         @Override
         public CatalogCommand build() {
             return new AlterTableDropColumnCommand(
                     tableName,
                     schemaName,
                     ifTableExists,
-                    columns
+                    columns,
+                    ifColumnExists
             );
         }
     }
diff --git 
a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommandBuilder.java
 
b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommandBuilder.java
index cde3f509dd8..90387a5116f 100644
--- 
a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommandBuilder.java
+++ 
b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommandBuilder.java
@@ -29,4 +29,7 @@ import java.util.Set;
 public interface AlterTableDropColumnCommandBuilder extends 
AbstractTableCommandBuilder<AlterTableDropColumnCommandBuilder> {
     /** Set of the columns to delete. There must be at least one column. */
     AlterTableDropColumnCommandBuilder columns(Set<String> columns);
+
+    /** The flag indicating that the command should not fail if a column with 
the specified name does not exist. */
+    AlterTableDropColumnCommandBuilder ifColumnExists(boolean ifColumnExists);
 }
diff --git 
a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandValidationTest.java
 
b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandValidationTest.java
index a59fea2f73e..e3df89d26f0 100644
--- 
a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandValidationTest.java
+++ 
b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandValidationTest.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.catalog.commands;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
 import static org.apache.ignite.sql.ColumnType.INT32;
 import static org.apache.ignite.sql.ColumnType.STRING;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 
 import java.util.List;
 import org.apache.ignite.internal.catalog.Catalog;
@@ -171,6 +172,27 @@ public class AlterTableAddColumnCommandValidationTest 
extends AbstractCommandVal
         );
     }
 
+    @Test
+    void 
exceptionNotThrownIfColumnWithGivenNameAlreadyExistsWithIfColumnNotExists() {
+        String tableName = "TEST";
+        String columnName = "TEST";
+        ColumnParams columnParams = 
ColumnParams.builder().name(columnName).type(INT32).build();
+        Catalog catalog = catalogWithTable(builder -> builder
+                .schemaName(SCHEMA_NAME)
+                .tableName(tableName)
+                .columns(List.of(columnParams))
+                .primaryKey(primaryKey(columnName))
+        );
+
+        AlterTableAddColumnCommandBuilder builder = 
AlterTableAddColumnCommand.builder()
+                .schemaName(SCHEMA_NAME)
+                .tableName(tableName)
+                .columns(List.of(columnParams))
+                .ifColumnNotExists(true);
+
+        assertDoesNotThrow(() -> builder.build().get(new 
UpdateContext(catalog)));
+    }
+
     @Test
     void cannotAddColumnWithFunctionalDefault() {
         String tableName = "TEST";
diff --git 
a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommandValidationTest.java
 
b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommandValidationTest.java
index 6c430f4cdd9..647056e5a7f 100644
--- 
a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommandValidationTest.java
+++ 
b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableDropColumnCommandValidationTest.java
@@ -175,6 +175,26 @@ public class AlterTableDropColumnCommandValidationTest 
extends AbstractCommandVa
         );
     }
 
+    @Test
+    void 
exceptionDoesNotThrownIfColumnWithGivenNameNotExistsWithIfColumnExists() {
+        String tableName = "TEST";
+        String columnName = "TEST";
+        Catalog catalog = catalogWithTable(builder -> builder
+                .schemaName(SCHEMA_NAME)
+                .tableName(tableName)
+                
.columns(List.of(ColumnParams.builder().name(columnName).type(INT32).build()))
+                .primaryKey(primaryKey(columnName))
+        );
+
+        AlterTableDropColumnCommandBuilder builder = 
AlterTableDropColumnCommand.builder()
+                .schemaName(SCHEMA_NAME)
+                .tableName(tableName)
+                .columns(Set.of(columnName + "_UNK"))
+                .ifColumnExists(true);
+
+        assertDoesNotThrow(() -> builder.build().get(new 
UpdateContext(catalog)));
+    }
+
     @Test
     void exceptionIsThrownIfColumnBelongsToPrimaryKey() {
         String tableName = "TEST";
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlApiBaseTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlApiBaseTest.java
index a55e9adf73b..adc3c22cb60 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlApiBaseTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlApiBaseTest.java
@@ -127,6 +127,7 @@ public abstract class ItSqlApiBaseTest extends 
BaseSqlIntegrationTest {
 
         // ADD COLUMN
         checkDdl(true, sql, "ALTER TABLE TEST ADD COLUMN VAL1 VARCHAR");
+        checkDdl(true, sql, "ALTER TABLE TEST ADD COLUMN (VAL2 VARCHAR, VAL3 
VARCHAR)");
         checkSqlError(
                 Sql.STMT_VALIDATION_ERR,
                 "Table with name 'PUBLIC.NOT_EXISTS_TABLE' not found",
@@ -140,6 +141,8 @@ public abstract class ItSqlApiBaseTest extends 
BaseSqlIntegrationTest {
                 sql,
                 "ALTER TABLE TEST ADD COLUMN VAL1 INT"
         );
+        checkDdl(false, sql, "ALTER TABLE TEST ADD COLUMN IF NOT EXISTS VAL1 
INT");
+        checkDdl(true, sql, "ALTER TABLE TEST ADD COLUMN IF NOT EXISTS (VAL1 
INT, VAL4 INT)");
 
         // CREATE INDEX
         checkDdl(true, sql, "CREATE INDEX TEST_IDX ON TEST(VAL0)");
@@ -200,6 +203,7 @@ public abstract class ItSqlApiBaseTest extends 
BaseSqlIntegrationTest {
                 sql,
                 "ALTER TABLE TEST DROP COLUMN VAL1"
         );
+        checkDdl(false, sql, "ALTER TABLE TEST DROP COLUMN IF EXISTS VAL1");
 
         // DROP TABLE
         checkDdl(false, sql, "DROP TABLE IF EXISTS NOT_EXISTS_TABLE");
diff --git a/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl 
b/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
index b1d9d3d7fe6..d483044a1a7 100644
--- a/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
+++ b/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
@@ -513,17 +513,19 @@ SqlNode SqlAlterTable() :
     SqlNode col;
     SqlNodeList cols;
     SqlNodeList propertyList;
+    final boolean ifColumnNotExists;
+    final boolean ifColumnExists;
 }
 {
     <ALTER> { s = span(); }
     <TABLE> ifExists = IfExistsOpt() id = CompoundIdentifier()
     (
-        <ADD> [<COLUMN>] cols = ColumnWithTypeOrList() {
-            return new IgniteSqlAlterTableAddColumn(s.end(this), ifExists, id, 
cols);
+        <ADD> [<COLUMN>] ifColumnNotExists = IfNotExistsOpt() cols = 
ColumnWithTypeOrList() {
+            return new IgniteSqlAlterTableAddColumn(s.end(this), ifExists, id, 
cols, ifColumnNotExists);
         }
     |
-        <DROP> [<COLUMN>] cols = SimpleIdentifierOrList() {
-            return new IgniteSqlAlterTableDropColumn(s.end(this), ifExists, 
id, cols);
+        <DROP> [<COLUMN>] ifColumnExists = IfExistsOpt() cols = 
SimpleIdentifierOrList() {
+            return new IgniteSqlAlterTableDropColumn(s.end(this), ifExists, 
id, cols, ifColumnExists);
         }
     |
         <ALTER> [<COLUMN>] {
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
index b000db18c3a..cfea9272fe3 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
@@ -565,6 +565,7 @@ public class DdlSqlToCommandConverter {
         builder.schemaName(deriveSchemaName(alterTblNode.name(), ctx));
         builder.tableName(deriveObjectName(alterTblNode.name(), ctx, "table 
name"));
         builder.ifTableExists(alterTblNode.ifExists());
+        builder.ifColumnNotExists(alterTblNode.ifColumnNotExists());
 
         List<ColumnParams> columns = new 
ArrayList<>(alterTblNode.columns().size());
 
@@ -672,6 +673,7 @@ public class DdlSqlToCommandConverter {
         builder.schemaName(deriveSchemaName(alterTblNode.name(), ctx));
         builder.tableName(deriveObjectName(alterTblNode.name(), ctx, "table 
name"));
         builder.ifTableExists(alterTblNode.ifExists());
+        builder.ifColumnExists(alterTblNode.ifColumnExists());
 
         Set<String> cols = new HashSet<>(alterTblNode.columns().size());
         alterTblNode.columns().forEach(c -> cols.add(((SqlIdentifier) 
c).getSimple()));
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableAddColumn.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableAddColumn.java
index 2763c214b59..ea0a6706540 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableAddColumn.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableAddColumn.java
@@ -39,27 +39,48 @@ public class IgniteSqlAlterTableAddColumn extends 
IgniteAbstractSqlAlterTable {
 
     /** ALTER TABLE ... ADD COLUMN operator. */
     protected static class Operator extends IgniteDdlOperator {
+        private final boolean columnNotExistFlag;
 
         /** Constructor. */
-        protected Operator(boolean existFlag) {
+        protected Operator(boolean existFlag, boolean columnNotExistFlag) {
             super("ALTER TABLE", SqlKind.ALTER_TABLE, existFlag);
+
+            this.columnNotExistFlag = columnNotExistFlag;
         }
 
         /** {@inheritDoc} */
         @Override
         public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
-            return new IgniteSqlAlterTableAddColumn(pos, existFlag(), 
(SqlIdentifier) operands[0], (SqlNodeList) operands[1]);
+            return new IgniteSqlAlterTableAddColumn(
+                    pos,
+                    existFlag(),
+                    (SqlIdentifier) operands[0],
+                    (SqlNodeList) operands[1],
+                    columnNotExistFlag()
+            );
+        }
+
+        boolean columnNotExistFlag() {
+            return columnNotExistFlag;
         }
     }
 
     /** Introduced columns. */
     private final SqlNodeList columns;
 
+    private final boolean ifColumnNotExists;
+
     /** Constructor. */
-    public IgniteSqlAlterTableAddColumn(SqlParserPos pos, boolean ifExists, 
SqlIdentifier tblName,
-            SqlNodeList columns) {
-        super(new Operator(ifExists), pos, tblName);
+    public IgniteSqlAlterTableAddColumn(
+            SqlParserPos pos,
+            boolean ifExists,
+            SqlIdentifier tblName,
+            SqlNodeList columns,
+            boolean ifColumnNotExists
+    ) {
+        super(new Operator(ifExists, ifColumnNotExists), pos, tblName);
         this.columns = Objects.requireNonNull(columns, "columns list");
+        this.ifColumnNotExists = ifColumnNotExists;
     }
 
     /** {@inheritDoc} */
@@ -74,6 +95,12 @@ public class IgniteSqlAlterTableAddColumn extends 
IgniteAbstractSqlAlterTable {
         writer.keyword("ADD");
         writer.keyword("COLUMN");
 
+        if (ifColumnNotExists) {
+            writer.keyword("IF");
+            writer.keyword("NOT");
+            writer.keyword("EXISTS");
+        }
+
         columns.unparse(writer, leftPrec, rightPrec);
     }
 
@@ -81,4 +108,9 @@ public class IgniteSqlAlterTableAddColumn extends 
IgniteAbstractSqlAlterTable {
     public SqlNodeList columns() {
         return columns;
     }
+
+    /** If not exists flag for the column. */
+    public boolean ifColumnNotExists() {
+        return ifColumnNotExists;
+    }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableDropColumn.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableDropColumn.java
index 8e790eadf51..ae80e692824 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableDropColumn.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableDropColumn.java
@@ -36,30 +36,50 @@ import org.jetbrains.annotations.Nullable;
  */
 @DdlBatchAware
 public class IgniteSqlAlterTableDropColumn extends IgniteAbstractSqlAlterTable 
{
-
     /** ALTER TABLE operator. */
     protected static class Operator extends IgniteDdlOperator {
+        private final boolean columnExistFlag;
 
         /** Constructor. */
-        protected Operator(boolean existFlag) {
+        protected Operator(boolean existFlag, boolean columnExistFlag) {
             super("ALTER TABLE", SqlKind.ALTER_TABLE, existFlag);
+
+            this.columnExistFlag = columnExistFlag;
         }
 
         /** {@inheritDoc} */
         @Override
         public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
-            return new IgniteSqlAlterTableDropColumn(pos, existFlag(), 
(SqlIdentifier) operands[0], (SqlNodeList) operands[1]);
+            return new IgniteSqlAlterTableDropColumn(
+                    pos,
+                    existFlag(),
+                    (SqlIdentifier) operands[0],
+                    (SqlNodeList) operands[1],
+                    columnExistFlag()
+            );
+        }
+
+        boolean columnExistFlag() {
+            return columnExistFlag;
         }
     }
 
     /** Columns to drop. */
     private final SqlNodeList columns;
 
+    private final boolean ifColumnExists;
+
     /** Constructor. */
-    public IgniteSqlAlterTableDropColumn(SqlParserPos pos, boolean ifExists, 
SqlIdentifier tblName,
-            SqlNodeList columns) {
-        super(new Operator(ifExists), pos, tblName);
+    public IgniteSqlAlterTableDropColumn(
+            SqlParserPos pos,
+            boolean ifExists,
+            SqlIdentifier tblName,
+            SqlNodeList columns,
+            boolean ifColumnExists
+    ) {
+        super(new Operator(ifExists, ifColumnExists), pos, tblName);
         this.columns = Objects.requireNonNull(columns, "columns list");
+        this.ifColumnExists = ifColumnExists;
     }
 
     /** {@inheritDoc} */
@@ -74,6 +94,11 @@ public class IgniteSqlAlterTableDropColumn extends 
IgniteAbstractSqlAlterTable {
         writer.keyword("DROP");
         writer.keyword("COLUMN");
 
+        if (ifColumnExists) {
+            writer.keyword("IF");
+            writer.keyword("EXISTS");
+        }
+
         columns.unparse(writer, leftPrec, rightPrec);
     }
 
@@ -81,4 +106,9 @@ public class IgniteSqlAlterTableDropColumn extends 
IgniteAbstractSqlAlterTable {
     public SqlNodeList columns() {
         return columns;
     }
+
+    /** If exists flag for column. */
+    public boolean ifColumnExists() {
+        return ifColumnExists;
+    }
 }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
index 3ec7347c5cb..3eb7745d6fa 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
@@ -1115,6 +1115,7 @@ public class SqlDdlParserTest extends AbstractParserTest {
         SqlColumnDeclaration declaration = (SqlColumnDeclaration) 
addColumn.columns().get(0);
 
         assertThat(addColumn.name.names, is(List.of("T")));
+        assertThat(addColumn.ifColumnNotExists(), is(false));
 
         expectColumnBasic(declaration, "C", ColumnStrategy.NULLABLE, 
"INTEGER", true);
         assertThat(declaration.expression, is(nullValue()));
@@ -1122,6 +1123,22 @@ public class SqlDdlParserTest extends AbstractParserTest 
{
         expectUnparsed(addColumn, "ALTER TABLE \"T\" ADD COLUMN \"C\" 
INTEGER");
     }
 
+    @Test
+    public void alterTableAddColumnIfNotExists() {
+        SqlNode sqlNode = parse("ALTER TABLE t ADD COLUMN IF NOT EXISTS c 
INT");
+
+        IgniteSqlAlterTableAddColumn addColumn = 
assertInstanceOf(IgniteSqlAlterTableAddColumn.class, sqlNode);
+        SqlColumnDeclaration declaration = (SqlColumnDeclaration) 
addColumn.columns().get(0);
+
+        assertThat(addColumn.name.names, is(List.of("T")));
+        assertThat(addColumn.ifColumnNotExists(), is(true));
+
+        expectColumnBasic(declaration, "C", ColumnStrategy.NULLABLE, 
"INTEGER", true);
+        assertThat(declaration.expression, is(nullValue()));
+
+        expectUnparsed(addColumn, "ALTER TABLE \"T\" ADD COLUMN IF NOT EXISTS 
\"C\" INTEGER");
+    }
+
     @Test
     public void alterTableAddColumnNull() {
         SqlNode sqlNode = parse("ALTER TABLE t ADD COLUMN c INT NULL");
@@ -1152,6 +1169,30 @@ public class SqlDdlParserTest extends AbstractParserTest 
{
         expectUnparsed(addColumn, "ALTER TABLE \"T\" ADD COLUMN \"C\" INTEGER 
NOT NULL");
     }
 
+    @Test
+    public void alterTableDropColumn() {
+        SqlNode sqlNode = parse("ALTER TABLE t DROP COLUMN c");
+
+        IgniteSqlAlterTableDropColumn dropColumn = 
assertInstanceOf(IgniteSqlAlterTableDropColumn.class, sqlNode);
+
+        assertThat(dropColumn.name.names, is(List.of("T")));
+        assertThat(dropColumn.ifColumnExists(), is(false));
+
+        expectUnparsed(dropColumn, "ALTER TABLE \"T\" DROP COLUMN \"C\"");
+    }
+
+    @Test
+    public void alterTableDropColumnIfExists() {
+        SqlNode sqlNode = parse("ALTER TABLE t DROP COLUMN IF EXISTS c");
+
+        IgniteSqlAlterTableDropColumn dropColumn = 
assertInstanceOf(IgniteSqlAlterTableDropColumn.class, sqlNode);
+
+        assertThat(dropColumn.name.names, is(List.of("T")));
+        assertThat(dropColumn.ifColumnExists(), is(true));
+
+        expectUnparsed(dropColumn, "ALTER TABLE \"T\" DROP COLUMN IF EXISTS 
\"C\"");
+    }
+
     /**
      * CHAR datatype has certain storage assignment rules, namely it requires 
value to be padded with `space` character up to declared
      * length. This currently not supported in storage, thus let's forbid 
usage of CHAR datatype for table's columns.

Reply via email to