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

ppa 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 27d63dadb1 IGNITE-20724 Fixed incorrect schema column mapping when 
deleting a column (#2748)
27d63dadb1 is described below

commit 27d63dadb1ec29f3bff03afce3aca40e986ee1d9
Author: Pavel Pereslegin <[email protected]>
AuthorDate: Fri Oct 27 14:35:21 2023 +0300

    IGNITE-20724 Fixed incorrect schema column mapping when deleting a column 
(#2748)
---
 .../internal/sql/engine/ItCreateTableDdlTest.java  |  69 +++++++++
 .../apache/ignite/internal/schema/SchemaUtils.java |  10 +-
 .../schema/registry/UpgradingRowAdapter.java       |  28 ++++
 .../internal/schema/SchemaColumnMapperTest.java    | 169 +++++++++++++++++++++
 4 files changed, 275 insertions(+), 1 deletion(-)

diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
index bf3beea782..323db5123f 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
@@ -17,18 +17,28 @@
 
 package org.apache.ignite.internal.sql.engine;
 
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
 import static 
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
 import static org.apache.ignite.internal.table.TableTestUtils.getTableStrict;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 
 import java.util.List;
+import java.util.Set;
+import org.apache.calcite.rel.type.RelDataType;
 import org.apache.ignite.internal.app.IgniteImpl;
+import org.apache.ignite.internal.lang.IgniteStringBuilder;
 import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.SchemaTestUtils;
 import org.apache.ignite.internal.sql.BaseSqlIntegrationTest;
+import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.engine.util.TypeUtils;
 import org.apache.ignite.internal.table.TableImpl;
+import org.apache.ignite.internal.type.NativeType;
+import org.apache.ignite.internal.type.NativeTypeSpec;
 import org.apache.ignite.lang.ErrorGroups.Sql;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
@@ -179,6 +189,65 @@ public class ItCreateTableDdlTest extends 
BaseSqlIntegrationTest {
         res = sql("SELECT c4 FROM my WHERE c1=3");
 
         assertEquals(3, res.get(0).get(0));
+
+        // Checking the correctness of reading a row created on a different 
version of the schema.
+        sql("ALTER TABLE my ADD COLUMN (c5 VARCHAR, c6 BOOLEAN)");
+        sql("ALTER TABLE my DROP COLUMN c4");
+        assertQuery("SELECT * FROM my WHERE c1=3")
+                .returns(3, "2", 3, null, null)
+                .check();
+    }
+
+    /**
+     * Adds columns of all supported types and checks that the row
+     * created on the old schema version is read correctly.
+     */
+    @Test
+    public void testDropAndAddColumnsAllTypes() {
+        List<NativeType> allTypes = SchemaTestUtils.ALL_TYPES;
+
+        Set<NativeTypeSpec> unsupportedTypes = Set.of(
+                // TODO https://issues.apache.org/jira/browse/IGNITE-18431
+                NativeTypeSpec.BITMASK,
+                // TODO https://issues.apache.org/jira/browse/IGNITE-19274
+                NativeTypeSpec.TIMESTAMP
+        );
+
+        // List of columns for 'ADD COLUMN' statement.
+        IgniteStringBuilder addColumnsList = new IgniteStringBuilder();
+        // List of columns for 'DROP COLUMN' statement.
+        IgniteStringBuilder dropColumnsList = new IgniteStringBuilder();
+
+        for (int i = 0; i < allTypes.size(); i++) {
+            NativeType type = allTypes.get(i);
+
+            if (unsupportedTypes.contains(type.spec())) {
+                continue;
+            }
+
+            RelDataType relDataType = 
TypeUtils.native2relationalType(Commons.typeFactory(), type);
+
+            if (addColumnsList.length() > 0) {
+                addColumnsList.app(',');
+                dropColumnsList.app(',');
+            }
+
+            addColumnsList.app("c").app(i).app(' 
').app(relDataType.getSqlTypeName());
+            dropColumnsList.app("c").app(i);
+        }
+
+        sql("CREATE TABLE test (id INT PRIMARY KEY, val INT)");
+        sql("INSERT INTO test VALUES (0, 1)");
+        sql(format("ALTER TABLE test ADD COLUMN ({})", 
addColumnsList.toString()));
+
+        List<List<Object>> res = sql("SELECT * FROM test");
+        assertThat(res.size(), is(1));
+        assertThat(res.get(0).size(), is(allTypes.size() - 
unsupportedTypes.size() + /* initial columns */ 2));
+
+        sql(format("ALTER TABLE test DROP COLUMN ({})", 
dropColumnsList.toString()));
+        assertQuery("SELECT * FROM test")
+                .returns(0, 1)
+                .check();
     }
 
     /**
diff --git 
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaUtils.java
 
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaUtils.java
index dec51ca92d..72bfa76cb3 100644
--- 
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaUtils.java
+++ 
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/SchemaUtils.java
@@ -85,7 +85,15 @@ public class SchemaUtils {
                         mapper = ColumnMapping.createMapper(newDesc);
                     }
 
-                    mapper.add(newCol.schemaIndex(), oldCol.schemaIndex());
+                    if (newCol.name().equals(oldCol.name())) {
+                        mapper.add(newCol.schemaIndex(), oldCol.schemaIndex());
+                    } else {
+                        Column oldIdx = oldDesc.column(newCol.name());
+
+                        assert oldIdx != null : newCol.name();
+
+                        mapper.add(newCol.schemaIndex(), oldIdx.schemaIndex());
+                    }
                 }
             } else {
                 if (mapper == null) {
diff --git 
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
 
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
index 3f8313fa34..6d37177bdd 100644
--- 
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
+++ 
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
@@ -111,6 +111,34 @@ public class UpgradingRowAdapter extends Row {
                 : newBinaryTupleSchema.value(this, colIdx);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public boolean booleanValue(int colIdx) {
+        int mappedId = mapColumn(colIdx);
+
+        Column column = mappedId < 0 ? mapper.mappedColumn(colIdx) : 
super.schema().column(mappedId);
+
+        if (NativeTypeSpec.BOOLEAN != column.type().spec()) {
+            throw new SchemaException("Type conversion is not supported yet.");
+        }
+
+        return mappedId < 0 ? (boolean) column.defaultValue() : 
super.booleanValue(mappedId);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Boolean booleanValueBoxed(int colIdx) {
+        int mappedId = mapColumn(colIdx);
+
+        Column column = mappedId < 0 ? mapper.mappedColumn(colIdx) : 
super.schema().column(mappedId);
+
+        if (NativeTypeSpec.BOOLEAN != column.type().spec()) {
+            throw new SchemaException("Type conversion is not supported yet.");
+        }
+
+        return mappedId < 0 ? (Boolean) column.defaultValue() : 
super.booleanValueBoxed(mappedId);
+    }
+
     /** {@inheritDoc} */
     @Override
     public byte byteValue(int colIdx) throws InvalidTypeException {
diff --git 
a/modules/schema/src/test/java/org/apache/ignite/internal/schema/SchemaColumnMapperTest.java
 
b/modules/schema/src/test/java/org/apache/ignite/internal/schema/SchemaColumnMapperTest.java
new file mode 100644
index 0000000000..fa9ebe8bc4
--- /dev/null
+++ 
b/modules/schema/src/test/java/org/apache/ignite/internal/schema/SchemaColumnMapperTest.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.schema;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.internal.schema.mapping.ColumnMapper;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.internal.type.NativeType;
+import org.apache.ignite.internal.type.NativeTypes;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Test checks the correctness of the {@link SchemaDescriptor#columnMapping() 
mapper} created using
+ * the {@link SchemaUtils#columnMapper(SchemaDescriptor, SchemaDescriptor)} 
method.
+ */
+public class SchemaColumnMapperTest extends BaseIgniteAbstractTest {
+    private static final int TOTAL_ITERATIONS = 30;
+
+    private static final SchemaDescriptor INITIAL_SCHEMA =
+            new SchemaDescriptor(0, new Column[]{new Column("ID", 
NativeTypes.INT32, false)}, new Column[0]);
+
+    @ParameterizedTest
+    @ValueSource(ints = {1, 2, 3})
+    public void removeFirstColumns(int batchSize) {
+        SchemaDescriptor newSchema = INITIAL_SCHEMA;
+
+        // Sequentially add columns and check the mapping for the previous 
version.
+        for (int i = 0; i < TOTAL_ITERATIONS; i += batchSize) {
+            SchemaDescriptor oldSchema = newSchema;
+            newSchema = addColumns(oldSchema, makeColumns(i, batchSize));
+
+            verifyMapping(oldSchema, newSchema);
+        }
+
+        // Sequentially remove columns located at the beginning, according to 
the column order.
+        for (int i = 0; i < TOTAL_ITERATIONS; i += batchSize) {
+            SchemaDescriptor oldSchema = newSchema;
+            int[] idxs = IntStream.range(0, batchSize).toArray();
+            newSchema = removeColumns(newSchema, idxs);
+
+            verifyMapping(oldSchema, newSchema);
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {1, 2, 3})
+    public void removeLastColumns(int batchSize) {
+        SchemaDescriptor newSchema = addColumns(INITIAL_SCHEMA, makeColumns(0, 
TOTAL_ITERATIONS));
+
+        for (int i = TOTAL_ITERATIONS - 1; i >= 0; i -= batchSize) {
+            SchemaDescriptor oldSchema = newSchema;
+            int[] idxs = IntStream.range(i, i - batchSize).toArray();
+            newSchema = removeColumns(newSchema, idxs);
+
+            verifyMapping(oldSchema, newSchema);
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {1, 2, 3})
+    public void removeRandomColumns(int batchSize) {
+        long seed = System.currentTimeMillis();
+
+        log.info("Using seed: " + seed);
+
+        Random rnd = new Random(seed);
+
+        SchemaDescriptor newSchema = addColumns(INITIAL_SCHEMA, makeColumns(0, 
TOTAL_ITERATIONS));
+
+        for (int i = TOTAL_ITERATIONS - 1; i >= batchSize; i -= batchSize) {
+            int[] idxs = rnd.ints(i - batchSize, 
i).distinct().limit(batchSize).toArray();
+
+            SchemaDescriptor oldSchema = newSchema;
+            newSchema = removeColumns(newSchema, idxs);
+
+            verifyMapping(oldSchema, newSchema);
+        }
+    }
+
+    private static void verifyMapping(SchemaDescriptor oldSchema, 
SchemaDescriptor newSchema) {
+        Column[] oldCols = allColumns(oldSchema);
+        Column[] newCols = allColumns(newSchema);
+        ColumnMapper mapper = SchemaUtils.columnMapper(oldSchema, newSchema);
+
+        Map<Integer, Column> schemaIndexMap = Arrays.stream(oldCols)
+                .collect(Collectors.toMap(Column::schemaIndex, 
Function.identity()));
+
+        for (Column column : newCols) {
+            int newSchemaIdx = column.schemaIndex();
+            int oldSchemaIdx = mapper.map(newSchemaIdx);
+
+            Column oldCol = oldSchemaIdx < 0 ? 
mapper.mappedColumn(newSchemaIdx) : schemaIndexMap.get(oldSchemaIdx);
+
+            assertThat("old=" + oldSchema + ", new=" + newSchema, oldCol, 
equalTo(column));
+        }
+    }
+
+    private static SchemaDescriptor addColumns(SchemaDescriptor schema, Column 
... newColumns) {
+        Column[] oldColumns = schema.valueColumns().columns();
+        Column[] columns = Arrays.copyOf(oldColumns, oldColumns.length + 
newColumns.length);
+        System.arraycopy(newColumns, 0, columns, oldColumns.length, 
newColumns.length);
+
+        return new SchemaDescriptor(schema.version() + 1, 
schema.keyColumns().columns(), columns);
+    }
+
+    private static SchemaDescriptor removeColumns(SchemaDescriptor schema, int 
... idxs) {
+        Column[] oldColumns = schema.valueColumns().columns();
+        Column[] newColumns = new Column[oldColumns.length - idxs.length];
+        Set<Integer> colIdxsSet = 
Arrays.stream(idxs).boxed().collect(Collectors.toSet());
+
+        int n = 0;
+        for (int i = 0; i < oldColumns.length; i++) {
+            if (!colIdxsSet.contains(i)) {
+                newColumns[n++] = oldColumns[i];
+            }
+        }
+
+        return new SchemaDescriptor(schema.version() + 1, 
schema.keyColumns().columns(), newColumns);
+    }
+
+    private static Column[] makeColumns(int offset, int count) {
+        Column[] columns = new Column[count];
+
+        for (int i = 0; i < count; i++) {
+            int index = offset + i;
+
+            NativeType type = 
SchemaTestUtils.ALL_TYPES.get((SchemaTestUtils.ALL_TYPES.size() - 1) % (index + 
1));
+
+            columns[i] = new Column(index, "COL" + index, type, false);
+        }
+
+        return columns;
+    }
+
+    private static Column[] allColumns(SchemaDescriptor schemaDescriptor) {
+        Column[] keyColumns = schemaDescriptor.keyColumns().columns();
+        Column[] valueColumns = schemaDescriptor.valueColumns().columns();
+
+        Column[] columns = Arrays.copyOf(keyColumns, keyColumns.length + 
valueColumns.length);
+        System.arraycopy(valueColumns, 0, columns, keyColumns.length, 
valueColumns.length);
+
+        return columns;
+    }
+}

Reply via email to