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

tkalkirill pushed a commit to branch ignite-28223
in repository https://gitbox.apache.org/repos/asf/ignite.git

commit c03e0e207055da5c4755d86db7483f3a44e2417b
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Sat Mar 14 13:03:19 2026 +0300

    IGNITE-28223 wip
---
 .../query/calcite/prepare/IgniteSqlValidator.java  |   4 +-
 .../calcite/schema/CacheTableDescriptorImpl.java   |  13 +-
 .../query/calcite/schema/SchemaHolderImpl.java     |   8 +-
 .../calcite/schema/VirtualColumnProvider.java      |  39 ++++++
 .../schema/AbstractTestCacheColumnDescriptor.java  | 136 +++++++++++++++++++
 .../calcite/schema/VirtualColumnProviderTest.java  | 144 +++++++++++++++++++++
 .../processors/plugin/IgnitePluginProcessor.java   |  28 +++-
 7 files changed, 363 insertions(+), 9 deletions(-)

diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
index c7101b05db6..383e79531b0 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
@@ -542,7 +542,9 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
     /** */
     private boolean isSystemFieldName(String alias) {
         return QueryUtils.KEY_FIELD_NAME.equalsIgnoreCase(alias)
-            || QueryUtils.VAL_FIELD_NAME.equalsIgnoreCase(alias);
+            || QueryUtils.VAL_FIELD_NAME.equalsIgnoreCase(alias)
+            // TODO: IGNITE-28223 Похоже что тут надо будет поменять
+            || "KEY_TO_STRING".equalsIgnoreCase(alias);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java
index 2cd164bc815..07c8326b484 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java
@@ -115,8 +115,12 @@ public class CacheTableDescriptorImpl extends 
NullInitializerExpressionFactory
     private RelDataType tableRowType;
 
     /** */
-    public CacheTableDescriptorImpl(GridCacheContextInfo<?, ?> cacheInfo, 
GridQueryTypeDescriptor typeDesc,
-        Object affinityIdentity) {
+    public CacheTableDescriptorImpl(
+        GridCacheContextInfo<?, ?> cacheInfo,
+        GridQueryTypeDescriptor typeDesc,
+        Object affinityIdentity,
+        VirtualColumnProvider virtColProv
+    ) {
         this.cacheInfo = cacheInfo;
         this.typeDesc = typeDesc;
         this.affinityIdentity = affinityIdentity;
@@ -150,6 +154,11 @@ public class CacheTableDescriptorImpl extends 
NullInitializerExpressionFactory
 
         int fldIdx = QueryUtils.VAL_COL + 1;
 
+        List<CacheColumnDescriptor> virtCols = 
virtColProv.provideVirtualColumns(fldIdx);
+        descriptors.addAll(virtCols);
+        fldIdx += virtCols.size();
+        virtCols.forEach(c -> virtualFields.set(c.fieldIndex()));
+
         int keyField = QueryUtils.KEY_COL;
         int valField = QueryUtils.VAL_COL;
 
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
index b9eae82520f..d2944d23e1a 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
@@ -254,8 +254,12 @@ public class SchemaHolderImpl extends AbstractService 
implements SchemaHolder, S
         GridQueryTypeDescriptor typeDesc,
         GridCacheContextInfo<?, ?> cacheInfo
     ) {
-        CacheTableDescriptorImpl desc =
-            new CacheTableDescriptorImpl(cacheInfo, typeDesc, 
affinityIdentity(cacheInfo.config()));
+        CacheTableDescriptorImpl desc = new CacheTableDescriptorImpl(
+            cacheInfo,
+            typeDesc,
+            affinityIdentity(cacheInfo.config()),
+            
ctx.plugins().createComponentOrDefault(VirtualColumnProvider.class, 
VirtualColumnProvider.EMPTY)
+        );
 
         return new CacheTableImpl(ctx, desc);
     }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/VirtualColumnProvider.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/VirtualColumnProvider.java
new file mode 100644
index 00000000000..aa947eb8574
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/VirtualColumnProvider.java
@@ -0,0 +1,39 @@
+/*
+ * 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.processors.query.calcite.schema;
+
+import java.util.List;
+import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
+import org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor;
+import org.apache.ignite.plugin.PluginProvider;
+
+/**
+ * Virtual table column provider from {@link PluginProvider plugin} created 
via {@link IgnitePluginProcessor#createComponent(Class)} for
+ * {@link CalciteQueryEngineConfiguration calcite engine}.
+ */
+@FunctionalInterface
+public interface VirtualColumnProvider {
+    VirtualColumnProvider EMPTY = nextColumnIndex -> List.of();
+
+    /**
+     * Returns a list of virtual columns to add to the table.
+     *
+     * @param nxtColIdx Next column index.
+     */
+    List<CacheColumnDescriptor> provideVirtualColumns(int nxtColIdx);
+}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/schema/AbstractTestCacheColumnDescriptor.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/schema/AbstractTestCacheColumnDescriptor.java
new file mode 100644
index 00000000000..b2aa731083c
--- /dev/null
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/schema/AbstractTestCacheColumnDescriptor.java
@@ -0,0 +1,136 @@
+/*
+ * 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.processors.query.calcite.schema;
+
+import org.apache.calcite.rel.type.RelDataType;
+import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
+
+import static org.apache.calcite.rel.type.RelDataType.PRECISION_NOT_SPECIFIED;
+import static org.apache.calcite.rel.type.RelDataType.SCALE_NOT_SPECIFIED;
+
+/** Abstract class for tests which allows to avoid redundant boilerplate code. 
*/
+public abstract class AbstractTestCacheColumnDescriptor implements 
CacheColumnDescriptor {
+    /** */
+    private final int idx;
+
+    /** */
+    private final String name;
+
+    /** */
+    private final Type type;
+
+    /** */
+    private final boolean isKey;
+
+    /** */
+    private final boolean isField;
+
+    /** */
+    private volatile RelDataType logicalType;
+
+    /** */
+    protected AbstractTestCacheColumnDescriptor(int idx, String name, Type 
type, boolean isKey, boolean isField) {
+        this.idx = idx;
+        this.name = name;
+        this.type = type;
+        this.isKey = isKey;
+        this.isField = isField;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean field() {
+        return isField;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean key() {
+        return isKey;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String name() {
+        return name;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int fieldIndex() {
+        return idx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelDataType logicalType(IgniteTypeFactory f) {
+        if (logicalType == null) {
+            logicalType = TypeUtils.sqlType(
+                f,
+                type.cls,
+                type.precision != Type.NOT_SPECIFIED ? type.precision : 
PRECISION_NOT_SPECIFIED,
+                type.scale != Type.NOT_SPECIFIED ? type.scale : 
SCALE_NOT_SPECIFIED,
+                type.nullable
+            );
+        }
+
+        return logicalType;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Class<?> storageType() {
+        return type.cls;
+    }
+
+    /** */
+    public static class Type {
+        /** */
+        public static final int NOT_SPECIFIED = -1;
+
+        /** */
+        private final Class<?> cls;
+
+        /** */
+        private final int precision;
+
+        /** */
+        private final int scale;
+
+        /** */
+        private final boolean nullable;
+
+        /** */
+        private Type(Class<?> cls, int precision, int scale, boolean nullable) 
{
+            this.cls = cls;
+            this.precision = precision;
+            this.scale = scale;
+            this.nullable = nullable;
+        }
+
+        /** */
+        public static Type nullable(Class<?> cls) {
+            return new Type(cls, NOT_SPECIFIED, NOT_SPECIFIED, true);
+        }
+
+        /** */
+        public static Type notNull(Class<?> cls) {
+            return new Type(cls, NOT_SPECIFIED, NOT_SPECIFIED, false);
+        }
+
+        /** */
+        public static Type of(Class<?> cls, int precision, int scale, boolean 
nullable) {
+            return new Type(cls, precision, scale, nullable);
+        }
+    }
+}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/schema/VirtualColumnProviderTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/schema/VirtualColumnProviderTest.java
new file mode 100644
index 00000000000..dca0fcee915
--- /dev/null
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/schema/VirtualColumnProviderTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.processors.query.calcite.schema;
+
+import java.util.List;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.SqlConfiguration;
+import org.apache.ignite.indexing.IndexingQueryEngineConfiguration;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import 
org.apache.ignite.internal.processors.query.calcite.integration.AbstractBasicIntegrationTest;
+import org.apache.ignite.plugin.AbstractTestPluginProvider;
+import org.apache.ignite.plugin.PluginContext;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+
+/** For {@link VirtualColumnProvider} testing. */
+public class VirtualColumnProviderTest extends AbstractBasicIntegrationTest {
+    /** */
+    private static final String KEY_TO_STRING_COLUMN_NAME = "KEY_TO_STRING";
+
+    /** {@inheritDoc} */
+    @Override protected int nodeCount() {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        SqlConfiguration sqlCfg = new 
SqlConfiguration().setQueryEnginesConfiguration(
+            new CalciteQueryEngineConfiguration().setDefault(true),
+            new IndexingQueryEngineConfiguration()
+        );
+
+        return super.getConfiguration(igniteInstanceName)
+            .setSqlConfiguration(sqlCfg)
+            .setPluginProviders(new TestVirtualColumnPluginProvider());
+    }
+
+    /** */
+    @Test
+    public void test() {
+        sql("create table PUBLIC.PERSON(id int primary key, name varchar)");
+
+        for (int i = 0; i < 2; i++)
+            sql("insert into PUBLIC.PERSON(id, name) values (?, ?)", i, "foo" 
+ i);
+
+        // Let's make sure that when using '*' there will be no virtual column.
+        assertQuery("select * from PUBLIC.PERSON order by id")
+            .columnNames("ID", "NAME")
+            .returns(0, "foo0")
+            .returns(1, "foo1")
+            .check();
+
+        // Let's make sure that when we specify a virtual column, we get it.
+        assertQuery(String.format("select id, name, %s from PUBLIC.PERSON 
order by id", KEY_TO_STRING_COLUMN_NAME))
+            .columnNames("ID", "NAME", KEY_TO_STRING_COLUMN_NAME)
+            .returns(0, "foo0", "0")
+            .returns(1, "foo1", "1")
+            .check();
+
+        // Let's check the use of a virtual column in where.
+        assertQuery(String.format(
+            "select id, name, %1$s from PUBLIC.PERSON where %1$s = %2$s order 
by id",
+            KEY_TO_STRING_COLUMN_NAME, "1"
+        ))
+            .columnNames("ID", "NAME", KEY_TO_STRING_COLUMN_NAME)
+            .returns(1, "foo1", "1")
+            .check();
+
+        // Let's check the use of a virtual column in order by.
+        assertQuery(String.format("select id, name, %1$s from PUBLIC.PERSON 
order by %1$s", KEY_TO_STRING_COLUMN_NAME))
+            .columnNames("ID", "NAME", KEY_TO_STRING_COLUMN_NAME)
+            .returns(0, "foo0", "0")
+            .returns(1, "foo1", "1")
+            .check();
+    }
+
+    /** */
+    private static class TestVirtualColumnPluginProvider extends 
AbstractTestPluginProvider {
+        /** {@inheritDoc} */
+        @Override public String name() {
+            return getClass().getSimpleName();
+        }
+
+        /** {@inheritDoc} */
+        @Override public <T> @Nullable T createComponent(PluginContext ctx, 
Class<T> cls) {
+            if (VirtualColumnProvider.class.equals(cls)) {
+                return (T) (VirtualColumnProvider) nxtColIdx -> List.of(new 
KeyToStingVirtualColumn(nxtColIdx));
+            }
+
+            return super.createComponent(ctx, cls);
+        }
+    }
+
+    /** */
+    private static class KeyToStingVirtualColumn extends 
AbstractTestCacheColumnDescriptor {
+        /** */
+        private KeyToStingVirtualColumn(int idx) {
+            super(idx, KEY_TO_STRING_COLUMN_NAME, Type.nullable(String.class), 
false, false);
+        }
+
+        /** {@inheritDoc} */
+        @Override public Object value(
+            ExecutionContext<?> ectx,
+            GridCacheContext<?, ?> cctx,
+            CacheDataRow src
+        ) throws IgniteCheckedException {
+            return cctx.unwrapBinaryIfNeeded(src.key(), false, 
null).toString();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void set(Object dst, Object val) {
+            throw new UnsupportedOperationException();
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean hasDefaultValue() {
+            return false;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Object defaultValue() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/plugin/IgnitePluginProcessor.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/plugin/IgnitePluginProcessor.java
index 6a8fbf2962e..79fbc03bb1c 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/plugin/IgnitePluginProcessor.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/plugin/IgnitePluginProcessor.java
@@ -128,21 +128,41 @@ public class IgnitePluginProcessor extends 
GridProcessorAdapter {
     }
 
     /**
+     * Creates an instance of a component from the {@link PluginProvider 
plugin} (first of
+     * {@link IgniteConfiguration#getPluginProviders()} that did not return 
{@code null}).
+     *
      * @param cls Component class.
      * @param <T> Component type.
      * @return Component class instance or {@code null} if no one plugin 
override this component.
      */
-    public <T> T createComponent(Class<T> cls) {
-        for (PluginProvider plugin : plugins.values()) {
+    public <T> @Nullable T createComponent(Class<T> cls) {
+        return createComponentOrDefault0(cls, null);
+    }
+
+    /**
+     * Creates an instance of a component from the {@link PluginProvider 
plugin} (first of
+     * {@link IgniteConfiguration#getPluginProviders()} that did not return 
{@code null}).
+     *
+     * @param cls Component class.
+     * @param dflt Default component instance.
+     * @param <T> Component type.
+     * @return Component class instance or {@code dflt} if no one plugin 
override this component.
+     */
+    public <T> T createComponentOrDefault(Class<T> cls, T dflt) {
+        return createComponentOrDefault0(cls, dflt);
+    }
+
+    private <T> @Nullable T createComponentOrDefault0(Class<T> cls, @Nullable 
T dflt) {
+        for (PluginProvider<?> plugin : plugins.values()) {
             PluginContext ctx = pluginContextForProvider(plugin);
 
-            T comp = (T)plugin.createComponent(ctx, cls);
+            T comp = plugin.createComponent(ctx, cls);
 
             if (comp != null)
                 return comp;
         }
 
-        return null;
+        return dflt;
     }
 
     /** {@inheritDoc} */

Reply via email to