From 868e3fe90c19f2c88a68e3357b61670ca2af71f0 Mon Sep 17 00:00:00 2001 From: Mirza Karacic Date: Tue, 7 Jul 2026 11:46:49 -0700 Subject: [PATCH 1/3] =?UTF-8?q?CLIENT-4359=20Create=20index=20to=20use=20?= =?UTF-8?q?=E2=80=9Cinteger=E2=80=9D=20instead=20of=20=E2=80=9Cnumeric?= =?UTF-8?q?=E2=80=9D=20starting=20with=20Aerospike=20server=20>=3D=208.1.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/aerospike/client/query/IndexType.java | 10 ++- .../aerospike/test/sync/query/TestIndex.java | 72 +++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/client/src/com/aerospike/client/query/IndexType.java b/client/src/com/aerospike/client/query/IndexType.java index 5cfb445c4..b3d7f27d4 100644 --- a/client/src/com/aerospike/client/query/IndexType.java +++ b/client/src/com/aerospike/client/query/IndexType.java @@ -21,7 +21,7 @@ */ public enum IndexType { /** - * Number index. + * Number index. Use {@link #INTEGER} for server versions 8.1.3+. */ NUMERIC, @@ -38,5 +38,11 @@ public enum IndexType { /** * 2-dimensional spherical geospatial index. */ - GEO2DSPHERE; + GEO2DSPHERE, + + /** + * Integer index. Requires server version 8.1.3+. Use {@link #NUMERIC} for + * server versions prior to 8.1.3. + */ + INTEGER; } diff --git a/test/src/com/aerospike/test/sync/query/TestIndex.java b/test/src/com/aerospike/test/sync/query/TestIndex.java index 21fdab076..5a9b62928 100644 --- a/test/src/com/aerospike/test/sync/query/TestIndex.java +++ b/test/src/com/aerospike/test/sync/query/TestIndex.java @@ -22,7 +22,9 @@ import org.junit.Test; import com.aerospike.client.AerospikeException; +import com.aerospike.client.Bin; import com.aerospike.client.Info; +import com.aerospike.client.Key; import com.aerospike.client.ResultCode; import com.aerospike.client.Value; import com.aerospike.client.cdt.CTX; @@ -30,7 +32,10 @@ import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.Expression; import com.aerospike.client.exp.LoopVarPart; +import com.aerospike.client.query.Filter; import com.aerospike.client.query.IndexType; +import com.aerospike.client.query.RecordSet; +import com.aerospike.client.query.Statement; import com.aerospike.client.task.IndexTask; import com.aerospike.client.util.Version; import com.aerospike.test.sync.TestSync; @@ -39,6 +44,9 @@ public class TestIndex extends TestSync { private static final String indexName = "testindex"; private static final String binName = "testbin"; private static final String setIndexName = "testsetindex"; + private static final String integerIndexName = "testintegerindex"; + private static final String integerBinName = "testintegerbin"; + private static final String integerKeyPrefix = "testintegerkey"; @Test public void createDrop() { @@ -116,6 +124,70 @@ public void setIndexCreateDrop() { } } + @Test + public void integerIndexCreateQueryDrop() { + Assume.assumeTrue("INTEGER index type requires server version 8.1.3 or later", + args.serverVersion.isGreaterOrEqual(8, 1, 3, 0)); + + IndexTask task; + + // Drop index if it already exists. + try { + task = client.dropIndex(args.indexPolicy, args.namespace, args.set, integerIndexName); + task.waitTillComplete(); + } + catch (AerospikeException ae) { + if (ae.getResultCode() != ResultCode.INDEX_NOTFOUND) { + throw ae; + } + } + + task = client.createIndex(args.indexPolicy, args.namespace, args.set, integerIndexName, integerBinName, IndexType.INTEGER); + task.waitTillComplete(); + + int size = 20; + + for (int i = 1; i <= size; i++) { + Key key = new Key(args.namespace, args.set, integerKeyPrefix + i); + Bin bin = new Bin(integerBinName, i); + client.put(null, key, bin); + } + + Statement stmt = new Statement(); + stmt.setNamespace(args.namespace); + stmt.setSetName(args.set); + stmt.setBinNames(integerBinName); + stmt.setFilter(Filter.range(integerBinName, 4, 8)); + + RecordSet rs = client.query(null, stmt); + + try { + int count = 0; + + while (rs.next()) { + count++; + } + assertEquals(5, count); + } + finally { + rs.close(); + } + + task = client.dropIndex(args.indexPolicy, args.namespace, args.set, integerIndexName); + task.waitTillComplete(); + + // Ensure all nodes have dropped the index. + Node[] nodes = client.getNodes(); + + for (Node node : nodes) { + String cmd = IndexTask.buildStatusCommand(args.namespace, integerIndexName, node.serverVersion); + String response = Info.request(node, cmd); + int code = Info.parseResultCode(response); + + assertEquals(201, code); + } + } + @Test public void ctxRestore() { CTX[] ctx1 = new CTX[] { From 195298a2e7b124ed7d5583797cab73c8a51d243a Mon Sep 17 00:00:00 2001 From: Mirza Karacic Date: Mon, 3 Aug 2026 22:01:34 -0700 Subject: [PATCH 2/3] Added logic to check server version --- client/src/com/aerospike/client/AerospikeClient.java | 10 ++++++++++ client/src/com/aerospike/client/util/Version.java | 1 + 2 files changed, 11 insertions(+) diff --git a/client/src/com/aerospike/client/AerospikeClient.java b/client/src/com/aerospike/client/AerospikeClient.java index 06f2e0cf6..0ef921c3c 100644 --- a/client/src/com/aerospike/client/AerospikeClient.java +++ b/client/src/com/aerospike/client/AerospikeClient.java @@ -5452,6 +5452,16 @@ private String buildCreateIndexInfoCommand( Version currentServerVersion = node.getServerVersion(); String createIndexCommand = currentServerVersion.isGreaterOrEqual(Version.SERVER_VERSION_8_1) ? "sindex-create:namespace=": "sindex-create:ns="; + // Server versions 8.1.3+ use the "integer" index type instead of "numeric". + // Map between the two based on the node's server version so existing NUMERIC + // callers work on newer servers and INTEGER callers work on older servers. + if (indexType == IndexType.NUMERIC && currentServerVersion.isGreaterOrEqual(Version.SERVER_VERSION_8_1_3)) { + indexType = IndexType.INTEGER; + } + else if (indexType == IndexType.INTEGER && currentServerVersion.isLessThan(Version.SERVER_VERSION_8_1_3)) { + indexType = IndexType.NUMERIC; + } + sb.append(createIndexCommand); sb.append(namespace); diff --git a/client/src/com/aerospike/client/util/Version.java b/client/src/com/aerospike/client/util/Version.java index 4df0cdbbc..25fe9f763 100644 --- a/client/src/com/aerospike/client/util/Version.java +++ b/client/src/com/aerospike/client/util/Version.java @@ -27,6 +27,7 @@ public final class Version implements Comparable { public static final Version SERVER_VERSION_8_1 = new Version(8, 1, 0, 0); public static final Version SERVER_VERSION_8_1_2 = new Version(8, 1, 2, 0); + public static final Version SERVER_VERSION_8_1_3 = new Version(8, 1, 3, 0); public static final Version SERVER_VERSION_PSCAN = new Version(4, 9, 0, 3); public static final Version SERVER_VERSION_QUERY_SHOW = new Version(5, 7, 0, 0); public static final Version SERVER_VERSION_PQUERY_BATCH_ANY = new Version(6, 0, 0, 0); From 4d6dac0a56bcca6ac74d7318d8aa69a1eb9325a3 Mon Sep 17 00:00:00 2001 From: Mirza Karacic Date: Mon, 3 Aug 2026 22:17:00 -0700 Subject: [PATCH 3/3] Updated tests --- .../com/aerospike/client/AerospikeClient.java | 26 ++++-- .../client/AerospikeClientIndexTypeTest.java | 83 +++++++++++++++++++ test/src/com/aerospike/test/SuiteSync.java | 4 +- .../aerospike/test/sync/query/TestIndex.java | 73 ++++++++++++++++ 4 files changed, 177 insertions(+), 9 deletions(-) create mode 100644 test/src/com/aerospike/client/AerospikeClientIndexTypeTest.java diff --git a/client/src/com/aerospike/client/AerospikeClient.java b/client/src/com/aerospike/client/AerospikeClient.java index 0ef921c3c..255d3b1d2 100644 --- a/client/src/com/aerospike/client/AerospikeClient.java +++ b/client/src/com/aerospike/client/AerospikeClient.java @@ -5453,14 +5453,7 @@ private String buildCreateIndexInfoCommand( String createIndexCommand = currentServerVersion.isGreaterOrEqual(Version.SERVER_VERSION_8_1) ? "sindex-create:namespace=": "sindex-create:ns="; // Server versions 8.1.3+ use the "integer" index type instead of "numeric". - // Map between the two based on the node's server version so existing NUMERIC - // callers work on newer servers and INTEGER callers work on older servers. - if (indexType == IndexType.NUMERIC && currentServerVersion.isGreaterOrEqual(Version.SERVER_VERSION_8_1_3)) { - indexType = IndexType.INTEGER; - } - else if (indexType == IndexType.INTEGER && currentServerVersion.isLessThan(Version.SERVER_VERSION_8_1_3)) { - indexType = IndexType.NUMERIC; - } + indexType = resolveIndexType(indexType, currentServerVersion); sb.append(createIndexCommand); sb.append(namespace); @@ -5521,6 +5514,23 @@ else if (indexType == IndexType.INTEGER && currentServerVersion.isLessThan(Versi return sb.toString(); } + /** + * Map the requested index type to the type the target server understands. + * Server versions 8.1.3+ use "integer" instead of "numeric", so a NUMERIC + * request is upgraded to INTEGER on those servers and an INTEGER request is + * downgraded to NUMERIC on older servers. All other index types are returned + * unchanged. Package-private for unit testing. + */ + static IndexType resolveIndexType(IndexType indexType, Version serverVersion) { + if (indexType == IndexType.NUMERIC && serverVersion.isGreaterOrEqual(Version.SERVER_VERSION_8_1_3)) { + return IndexType.INTEGER; + } + if (indexType == IndexType.INTEGER && serverVersion.isLessThan(Version.SERVER_VERSION_8_1_3)) { + return IndexType.NUMERIC; + } + return indexType; + } + private String buildDropIndexInfoCommand(Node node, String namespace, String setName, String indexName) { StringBuilder sb = new StringBuilder(500); Version currentServerVersion = node.getServerVersion(); diff --git a/test/src/com/aerospike/client/AerospikeClientIndexTypeTest.java b/test/src/com/aerospike/client/AerospikeClientIndexTypeTest.java new file mode 100644 index 000000000..60b7932c5 --- /dev/null +++ b/test/src/com/aerospike/client/AerospikeClientIndexTypeTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2026 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client; + +import static org.junit.Assert.assertSame; + +import org.junit.Test; + +import com.aerospike.client.query.IndexType; +import com.aerospike.client.util.Version; + +/** + * Server-independent unit tests for {@link AerospikeClient#resolveIndexType}. + * + * Server versions 8.1.3+ use the "integer" index type instead of "numeric". + * The client transparently maps between the two based on the target server + * version. This is the only place the mapping is observable: on the server, + * "numeric" and "integer" collapse to the same internal type, so the created + * index carries no record of which spelling was used. + */ +public class AerospikeClientIndexTypeTest { + private static Version version(int major, int minor, int patch, int build) { + return new Version(major, minor, patch, build); + } + + @Test + public void numericUpgradesToIntegerOn813() { + // Exact boundary. + assertSame(IndexType.INTEGER, AerospikeClient.resolveIndexType(IndexType.NUMERIC, version(8, 1, 3, 0))); + } + + @Test + public void numericUpgradesToIntegerAbove813() { + assertSame(IndexType.INTEGER, AerospikeClient.resolveIndexType(IndexType.NUMERIC, version(8, 1, 4, 0))); + assertSame(IndexType.INTEGER, AerospikeClient.resolveIndexType(IndexType.NUMERIC, version(9, 0, 0, 0))); + // Build component past the boundary still counts as >= 8.1.3.0. + assertSame(IndexType.INTEGER, AerospikeClient.resolveIndexType(IndexType.NUMERIC, version(8, 1, 3, 5))); + } + + @Test + public void numericUnchangedBelow813() { + assertSame(IndexType.NUMERIC, AerospikeClient.resolveIndexType(IndexType.NUMERIC, version(8, 1, 2, 0))); + assertSame(IndexType.NUMERIC, AerospikeClient.resolveIndexType(IndexType.NUMERIC, version(8, 1, 2, 99))); + assertSame(IndexType.NUMERIC, AerospikeClient.resolveIndexType(IndexType.NUMERIC, version(8, 0, 0, 0))); + assertSame(IndexType.NUMERIC, AerospikeClient.resolveIndexType(IndexType.NUMERIC, version(4, 9, 0, 3))); + } + + @Test + public void integerUnchangedOnOrAbove813() { + assertSame(IndexType.INTEGER, AerospikeClient.resolveIndexType(IndexType.INTEGER, version(8, 1, 3, 0))); + assertSame(IndexType.INTEGER, AerospikeClient.resolveIndexType(IndexType.INTEGER, version(9, 0, 0, 0))); + } + + @Test + public void integerDowngradesToNumericBelow813() { + assertSame(IndexType.NUMERIC, AerospikeClient.resolveIndexType(IndexType.INTEGER, version(8, 1, 2, 0))); + assertSame(IndexType.NUMERIC, AerospikeClient.resolveIndexType(IndexType.INTEGER, version(8, 0, 0, 0))); + assertSame(IndexType.NUMERIC, AerospikeClient.resolveIndexType(IndexType.INTEGER, version(4, 9, 0, 3))); + } + + @Test + public void otherTypesUnchangedAcrossVersions() { + for (IndexType type : new IndexType[] {IndexType.STRING, IndexType.GEO2DSPHERE}) { + assertSame(type, AerospikeClient.resolveIndexType(type, version(8, 1, 2, 0))); + assertSame(type, AerospikeClient.resolveIndexType(type, version(8, 1, 3, 0))); + assertSame(type, AerospikeClient.resolveIndexType(type, version(9, 0, 0, 0))); + } + } +} diff --git a/test/src/com/aerospike/test/SuiteSync.java b/test/src/com/aerospike/test/SuiteSync.java index 9b4c9e272..87a3a06c6 100644 --- a/test/src/com/aerospike/test/SuiteSync.java +++ b/test/src/com/aerospike/test/SuiteSync.java @@ -73,6 +73,7 @@ import com.aerospike.test.sync.query.TestQueryString; import com.aerospike.test.sync.query.TestQuerySum; import com.aerospike.test.util.Args; +import com.aerospike.client.AerospikeClientIndexTypeTest; @RunWith(Suite.class) @Suite.SuiteClasses({ @@ -121,7 +122,8 @@ TestQueryOperations.class, TestQueryRPS.class, TestQueryString.class, - TestQuerySum.class + TestQuerySum.class, + AerospikeClientIndexTypeTest.class }) public class SuiteSync { public static IAerospikeClient client = null; diff --git a/test/src/com/aerospike/test/sync/query/TestIndex.java b/test/src/com/aerospike/test/sync/query/TestIndex.java index 5a9b62928..4f55386f0 100644 --- a/test/src/com/aerospike/test/sync/query/TestIndex.java +++ b/test/src/com/aerospike/test/sync/query/TestIndex.java @@ -47,6 +47,9 @@ public class TestIndex extends TestSync { private static final String integerIndexName = "testintegerindex"; private static final String integerBinName = "testintegerbin"; private static final String integerKeyPrefix = "testintegerkey"; + private static final String numericIndexName = "testnumericindex"; + private static final String numericBinName = "testnumericbin"; + private static final String numericKeyPrefix = "testnumerickey"; @Test public void createDrop() { @@ -188,6 +191,76 @@ public void integerIndexCreateQueryDrop() { } } + @Test + public void numericIndexUpgradesToIntegerQueryDrop() { + // On server versions 8.1.3+ the client transparently upgrades a NUMERIC + // request to the "integer" index type. The server collapses "numeric" and + // "integer" to the same internal type, so the create spelling cannot be + // read back; this test instead verifies the upgraded index is created and + // remains queryable end-to-end. The wire-level mapping itself is asserted + // by AerospikeClientIndexTypeTest. + Assume.assumeTrue("NUMERIC to INTEGER upgrade requires server version 8.1.3 or later", + args.serverVersion.isGreaterOrEqual(8, 1, 3, 0)); + + IndexTask task; + + // Drop index if it already exists. + try { + task = client.dropIndex(args.indexPolicy, args.namespace, args.set, numericIndexName); + task.waitTillComplete(); + } + catch (AerospikeException ae) { + if (ae.getResultCode() != ResultCode.INDEX_NOTFOUND) { + throw ae; + } + } + + task = client.createIndex(args.indexPolicy, args.namespace, args.set, numericIndexName, numericBinName, IndexType.NUMERIC); + task.waitTillComplete(); + + int size = 20; + + for (int i = 1; i <= size; i++) { + Key key = new Key(args.namespace, args.set, numericKeyPrefix + i); + Bin bin = new Bin(numericBinName, i); + client.put(null, key, bin); + } + + Statement stmt = new Statement(); + stmt.setNamespace(args.namespace); + stmt.setSetName(args.set); + stmt.setBinNames(numericBinName); + stmt.setFilter(Filter.range(numericBinName, 4, 8)); + + RecordSet rs = client.query(null, stmt); + + try { + int count = 0; + + while (rs.next()) { + count++; + } + assertEquals(5, count); + } + finally { + rs.close(); + } + + task = client.dropIndex(args.indexPolicy, args.namespace, args.set, numericIndexName); + task.waitTillComplete(); + + // Ensure all nodes have dropped the index. + Node[] nodes = client.getNodes(); + + for (Node node : nodes) { + String cmd = IndexTask.buildStatusCommand(args.namespace, numericIndexName, node.serverVersion); + String response = Info.request(node, cmd); + int code = Info.parseResultCode(response); + + assertEquals(201, code); + } + } + @Test public void ctxRestore() { CTX[] ctx1 = new CTX[] {