IGNITE-13364 Improve calculation of default index inline size - Fixes #9167.

Signed-off-by: Ivan Daschinsky <ivandasch@apache.org>
diff --git a/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/indexreader/IgniteIndexReaderTest.java b/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/indexreader/IgniteIndexReaderTest.java
index 5d82835..8bc8fdd 100644
--- a/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/indexreader/IgniteIndexReaderTest.java
+++ b/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/indexreader/IgniteIndexReaderTest.java
@@ -891,7 +891,7 @@
             boolean idxReadingErr = isReportIdxAndPartFilesReadingErr();
             boolean partReadingErr = isReportIdxAndPartFilesReadingErr();
 
-            checkOutput(output, 19, 24, 0, 1, idxReadingErr, partReadingErr, false);
+            checkOutput(output, 19, 25, 0, 1, idxReadingErr, partReadingErr, false);
 
             for (int i = 0; i < CREATED_TABLES_CNT; i++)
                 checkIdxs(output, TableInfo.generate(i), true);
@@ -926,7 +926,7 @@
 
             boolean partReadingErr = isReportIdxAndPartFilesReadingErr();
 
-            checkOutput(output, 19, 22, 0, 0, false, partReadingErr, true);
+            checkOutput(output, 19, 23, 0, 0, false, partReadingErr, true);
 
             for (int i = 0; i < CREATED_TABLES_CNT; i++)
                 checkIdxs(output, TableInfo.generate(i), true);
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
index 3a3a4f8..178ce8e 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
@@ -1057,7 +1057,7 @@
      * Defaults to {@code 0}, meaning that inline index store is disabled.
      */
     @SystemProperty(value = "Maximum payload size in bytes for H2TreeIndex. " +
-        "0 means that inline index store is disabled", type = Integer.class, defaults = "10")
+        "0 means that inline index store is disabled", type = Integer.class, defaults = "64")
     public static final String IGNITE_MAX_INDEX_PAYLOAD_SIZE = "IGNITE_MAX_INDEX_PAYLOAD_SIZE";
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/IndexKeyDefinition.java b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/IndexKeyDefinition.java
index a7e6c5a..140f0ac 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/IndexKeyDefinition.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/IndexKeyDefinition.java
@@ -34,11 +34,20 @@
     /** Order. */
     private final Order order;
 
+    /** Precision for variable length key types. */
+    private final int precision;
+
     /** */
-    public IndexKeyDefinition(String name, int idxType, Order order) {
+    public IndexKeyDefinition(String name, int idxType, Order order, long precision) {
         this.idxType = idxType;
         this.order = order;
         this.name = name;
+
+        // Workaround due to wrong type conversion (int -> long).
+        if (precision >= Integer.MAX_VALUE)
+            this.precision = -1;
+        else
+            this.precision = (int)precision;
     }
 
     /** */
@@ -56,6 +65,11 @@
         return name;
     }
 
+    /** */
+    public int precision() {
+        return precision;
+    }
+
     /**
      * @return {@code true} if specified key's type matches to the current type, otherwise {@code false}.
      */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexTree.java b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexTree.java
index 6b3736d..b6c2764 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexTree.java
@@ -68,6 +68,12 @@
  * BPlusTree where nodes stores inlined index keys.
  */
 public class InlineIndexTree extends BPlusTree<IndexRow, IndexRow> {
+    /**
+     * Default sql index size for types with variable length (such as String or byte[]).
+     * Note that effective length will be lower, because 3 bytes will be taken for the inner representation of variable type.
+     */
+    public static final int IGNITE_VARIABLE_TYPE_DEFAULT_INLINE_SIZE = 10;
+
     /** Amount of bytes to store inlined index keys. */
     private final int inlineSize;
 
@@ -178,7 +184,9 @@
 
             rowHnd = rowHndFactory.create(def, keyTypeSettings);
 
-            inlineSize = computeInlineSize(rowHnd.inlineIndexKeyTypes(), configuredInlineSize, maxInlineSize);
+            inlineSize = computeInlineSize(
+                rowHnd.inlineIndexKeyTypes(), rowHnd.indexKeyDefinitions(),
+                configuredInlineSize, maxInlineSize);
 
             setIos(inlineSize, mvccEnabled);
         }
@@ -428,12 +436,14 @@
 
     /**
      * @param keyTypes Index key types.
+     * @param keyDefs Index key definitions.
      * @param cfgInlineSize Inline size from index config.
      * @param maxInlineSize Max inline size from cache config.
      * @return Inline size.
      */
     public static int computeInlineSize(
         List<InlineIndexKeyType> keyTypes,
+        List<IndexKeyDefinition> keyDefs,
         int cfgInlineSize,
         int maxInlineSize
     ) {
@@ -452,13 +462,27 @@
 
         int size = 0;
 
-        for (InlineIndexKeyType keyType: keyTypes) {
-            if (keyType.inlineSize() <= 0) {
+        for (int i = 0; i < keyTypes.size(); i++) {
+            InlineIndexKeyType keyType = keyTypes.get(i);
+
+            int sizeInc = keyType.inlineSize();
+
+            if (sizeInc < 0) {
+                int precision = keyDefs.get(i).precision();
+
+                if (precision > 0)
+                    // 3 is required to store (type, length) of value.
+                    sizeInc = 3 + precision;
+                else
+                    sizeInc = IGNITE_VARIABLE_TYPE_DEFAULT_INLINE_SIZE;
+            }
+
+            size += sizeInc;
+
+            if (size > propSize) {
                 size = propSize;
                 break;
             }
-
-            size += keyType.inlineSize();
         }
 
         return Math.min(PageIO.MAX_PAYLOAD_SIZE, size);
@@ -474,7 +498,7 @@
     }
 
     /** Default value for {@code IGNITE_MAX_INDEX_PAYLOAD_SIZE} */
-    public static final int IGNITE_MAX_INDEX_PAYLOAD_SIZE_DEFAULT = 10;
+    public static final int IGNITE_MAX_INDEX_PAYLOAD_SIZE_DEFAULT = 64;
 
     /**
      * @return Inline size.
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2TreeIndexBase.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2TreeIndexBase.java
index 8072cd1..2ca9f68 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2TreeIndexBase.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2TreeIndexBase.java
@@ -32,9 +32,6 @@
  * H2 tree index base.
  */
 public abstract class H2TreeIndexBase extends GridH2IndexBase {
-    /** Default value for {@code IGNITE_MAX_INDEX_PAYLOAD_SIZE} */
-    public static final int IGNITE_MAX_INDEX_PAYLOAD_SIZE_DEFAULT = 10;
-
     /**
      * Constructor.
      *
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/index/QueryIndexKeyDefinitionProvider.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/index/QueryIndexKeyDefinitionProvider.java
index 307ea55..6e07e00 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/index/QueryIndexKeyDefinitionProvider.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/index/QueryIndexKeyDefinitionProvider.java
@@ -66,7 +66,7 @@
     /** */
     private IndexKeyDefinition keyDefinition(IndexColumn c) {
         return new IndexKeyDefinition(
-            c.columnName, c.column.getType(), sortOrder(c.sortType));
+            c.columnName, c.column.getType(), sortOrder(c.sortType), c.column.getPrecision());
     }
 
     /** Maps H2 column order to Ignite index order. */
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/index/client/ClientIndexFactory.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/index/client/ClientIndexFactory.java
index 0bd1cd5..632a3ab 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/index/client/ClientIndexFactory.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/index/client/ClientIndexFactory.java
@@ -50,7 +50,7 @@
 
         List<InlineIndexKeyType> keyTypes = InlineIndexKeyTypeRegistry.types(keyDefs, DUMMY_SETTINGS);
 
-        int inlineSize = InlineIndexTree.computeInlineSize(keyTypes, def.getCfgInlineSize(), def.getMaxInlineSize());
+        int inlineSize = InlineIndexTree.computeInlineSize(keyTypes, keyDefs, def.getCfgInlineSize(), def.getMaxInlineSize());
 
         return new ClientInlineIndex(def.idxName().idxName(), inlineSize);
     }
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
index cc5739b..dde19f5 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
@@ -21,6 +21,7 @@
 import java.nio.file.Path;
 import java.sql.Timestamp;
 import java.time.Instant;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -299,7 +300,7 @@
                 {1374144180, "SQL_PUBLIC_DFLT_AFF_CACHE", "PUBLIC", "DFLT_AFF_CACHE", "AFFINITY_KEY", "BTREE",
                     "\"ID1\" ASC, \"ID2\" ASC", false, false, 10},
                 {1374144180, "SQL_PUBLIC_DFLT_AFF_CACHE", "PUBLIC", "DFLT_AFF_CACHE", "IDX_AFF_1", "BTREE",
-                    "\"ID2\" DESC, \"ID1\" ASC, \"MY_VAL\" DESC", false, false, 10},
+                    "\"ID2\" DESC, \"ID1\" ASC, \"MY_VAL\" DESC", false, false, 20},
                 {1374144180, "SQL_PUBLIC_DFLT_AFF_CACHE", "PUBLIC", "DFLT_AFF_CACHE", "__SCAN_", "SCAN", null, false, false, null},
                 {1374144180, "SQL_PUBLIC_DFLT_AFF_CACHE", "PUBLIC", "DFLT_AFF_CACHE", "_key_PK", "BTREE",
                     "\"ID1\" ASC, \"ID2\" ASC", true, true, 10},
@@ -307,9 +308,9 @@
                     "\"ID1\" ASC, \"ID2\" ASC, \"ID1\" ASC", false, true, null},
 
                 {1102275506, "SQL_PUBLIC_DFLT_CACHE", "PUBLIC", "DFLT_CACHE", "IDX_1", "BTREE",
-                    "\"ID2\" DESC, \"ID1\" ASC, \"MY_VAL\" DESC, \"ID1\" ASC, \"ID2\" ASC", false, false, 10},
+                    "\"ID2\" DESC, \"ID1\" ASC, \"MY_VAL\" DESC, \"ID1\" ASC, \"ID2\" ASC", false, false, 25},
                 {1102275506, "SQL_PUBLIC_DFLT_CACHE", "PUBLIC", "DFLT_CACHE", "IDX_3", "BTREE",
-                    "\"MY_VAL\" ASC, \"ID1\" ASC, \"ID2\" ASC, \"ID1\" ASC, \"ID2\" ASC", false, false, 10},
+                    "\"MY_VAL\" ASC, \"ID1\" ASC, \"ID2\" ASC, \"ID1\" ASC, \"ID2\" ASC", false, false, 25},
                 {1102275506, "SQL_PUBLIC_DFLT_CACHE", "PUBLIC", "DFLT_CACHE", "__SCAN_", "SCAN", null, false, false, null},
                 {1102275506, "SQL_PUBLIC_DFLT_CACHE", "PUBLIC", "DFLT_CACHE", "_key_PK", "BTREE",
                     "\"ID1\" ASC, \"ID2\" ASC", true, true, 10},
@@ -332,7 +333,7 @@
             assertEquals(expRow.length, resRow.size());
 
             for (int j = 0; j < expRow.length; j++)
-                assertEquals(expRow[j], resRow.get(j));
+                assertEquals("expRow: [" + Arrays.toString(expRow) + "]", expRow[j], resRow.get(j));
         }
     }
 
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/database/inlinecolumn/ComputeInlineSizeTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/database/inlinecolumn/ComputeInlineSizeTest.java
new file mode 100644
index 0000000..2cbe187
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/database/inlinecolumn/ComputeInlineSizeTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.h2.database.inlinecolumn;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.cache.query.index.Index;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndexImpl;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndexTree;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
+import org.apache.ignite.internal.processors.cache.index.AbstractIndexingCommonTest;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.junit.Test;
+
+/** Tests for the computation inline size. */
+public class ComputeInlineSizeTest extends AbstractIndexingCommonTest {
+    /** */
+    private static final String CACHE = "CACHE";
+
+    /** */
+    private static IgniteEx ignite;
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        ignite = startGrid();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        ignite.destroyCache(CACHE);
+    }
+
+    /** */
+    @Test
+    public void testAnnotationPrecision() {
+        CacheConfiguration<Long, Person> ccfg = new CacheConfiguration<Long, Person>()
+            .setName(CACHE)
+            .setIndexedTypes(Long.class, Person.class);
+
+        ignite.createCache(ccfg);
+
+        checkIdxsInlineSizes();
+    }
+
+    /** */
+    @Test
+    public void testSQLIndexes() {
+        StringBuilder bld = new StringBuilder();
+
+        String createQry = "create table TABLE (" +
+            "id long primary key" +
+            ", str varchar" +
+            ", bytes binary" +
+            ", strprec varchar(" + (InlineIndexTree.IGNITE_VARIABLE_TYPE_DEFAULT_INLINE_SIZE + 10) + ")" +
+            ", strprecbig varchar(" + (PageIO.MAX_PAYLOAD_SIZE * 2) + " )" +
+            ", bytesprec binary(" + (InlineIndexTree.IGNITE_VARIABLE_TYPE_DEFAULT_INLINE_SIZE + 20) + " )" +
+            ")with \"cache_name=" + CACHE + "\";";
+
+        bld.append(createQry);
+
+        for (String s: Arrays.asList("str", "strprec", "bytes", "bytesprec", "strprecbig"))
+            bld.append(String.format("create index PERSON_%s_IDX on TABLE (%s); ", s.toUpperCase(), s));
+
+        query(new SqlFieldsQuery(bld.toString()));
+
+        checkIdxsInlineSizes();
+    }
+
+    /** */
+    private void checkIdxsInlineSizes() {
+        Collection<Index> idx = ignite.context().indexProcessor().indexes(context());
+
+        Map<String, Integer> expInlineSize = new HashMap<String, Integer>() {{
+            // 9 is inline for _KEY (LongIndexKeyType).
+            put("PERSON_STR_IDX",
+                9 + InlineIndexTree.IGNITE_VARIABLE_TYPE_DEFAULT_INLINE_SIZE);
+            put("PERSON_STRPRECBIG_IDX",
+                InlineIndexTree.IGNITE_MAX_INDEX_PAYLOAD_SIZE_DEFAULT);
+            // 3 is for storing info (type, length) of inlined key.
+            put("PERSON_STRPREC_IDX",
+                9 + InlineIndexTree.IGNITE_VARIABLE_TYPE_DEFAULT_INLINE_SIZE + 10 + 3);
+            put("PERSON_BYTES_IDX",
+                9 + InlineIndexTree.IGNITE_VARIABLE_TYPE_DEFAULT_INLINE_SIZE);
+            put("PERSON_BYTESPREC_IDX",
+                9 + InlineIndexTree.IGNITE_VARIABLE_TYPE_DEFAULT_INLINE_SIZE + 20 + 3);
+        }};
+
+        for (Index i: idx) {
+            InlineIndexImpl impl = (InlineIndexImpl)i;
+
+            if (expInlineSize.containsKey(impl.name())) {
+                int inlineSize = expInlineSize.remove(impl.name());
+
+                assertEquals(inlineSize, impl.inlineSize());
+            }
+        }
+
+        assertTrue(expInlineSize.isEmpty());
+    }
+
+    /** */
+    private void query(SqlFieldsQuery qry) {
+        ignite.context().query().querySqlFields(qry, false, false);
+    }
+
+    /** */
+    private GridCacheContext<Long, Person> context() {
+        IgniteInternalCache<Long, Person> c = ignite.cachex(CACHE);
+
+        return c.context();
+    }
+
+    /** */
+    public static class Person {
+        /** */
+        @QuerySqlField(index = true)
+        private String str;
+
+        /** */
+        @QuerySqlField(index = true, precision = InlineIndexTree.IGNITE_VARIABLE_TYPE_DEFAULT_INLINE_SIZE + 10)
+        private String strPrec;
+
+        /** */
+        @QuerySqlField(index = true, precision = PageIO.MAX_PAYLOAD_SIZE * 2)
+        private String strPrecBig;
+
+        /** */
+        @QuerySqlField(index = true)
+        private byte[] bytes;
+
+        /** */
+        @QuerySqlField(index = true, precision = InlineIndexTree.IGNITE_VARIABLE_TYPE_DEFAULT_INLINE_SIZE + 20)
+        private byte[] bytesPrec;
+    }
+}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java
index 57ab453..c7afcfc 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java
@@ -53,6 +53,7 @@
 import org.apache.ignite.internal.processors.cache.ttl.CacheTtlTransactionalLocalSelfTest;
 import org.apache.ignite.internal.processors.cache.ttl.CacheTtlTransactionalPartitionedSelfTest;
 import org.apache.ignite.internal.processors.client.IgniteDataStreamerTest;
+import org.apache.ignite.internal.processors.query.h2.database.inlinecolumn.ComputeInlineSizeTest;
 import org.apache.ignite.internal.processors.query.h2.database.inlinecolumn.InlineIndexColumnTest;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
@@ -63,6 +64,7 @@
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
     InlineIndexColumnTest.class,
+    ComputeInlineSizeTest.class,
 
     GridIndexingWithNoopSwapSelfTest.class,
     GridCacheOffHeapSelfTest.class,