From fb80a121a46d866c3aec064401e5f8d95b91f8e9 Mon Sep 17 00:00:00 2001 From: Maksim Timonin Date: Thu, 25 Jul 2024 10:48:20 +0300 Subject: [PATCH] IGNITE-20943 Fix deserializing cache entries for IndexQuery on server (#11453) --- .../cache/ClientCacheIndexQueryRequest.java | 3 +- .../ThinClientQueryTestApplication.java | 164 ++++++++++++++++++ .../tests/thin_client_query_test.py | 95 ++++++++++ 3 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/thin_client_query_test/ThinClientQueryTestApplication.java create mode 100644 modules/ducktests/tests/ignitetest/tests/thin_client_query_test.py diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheIndexQueryRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheIndexQueryRequest.java index db86541fbb8ea..9d1a29cb7308f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheIndexQueryRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/cache/ClientCacheIndexQueryRequest.java @@ -155,7 +155,8 @@ private IndexQueryCriterion readInCriterion(BinaryRawReaderEx reader) { * {@inheritDoc} */ @Override public ClientResponse process(ClientConnectionContext ctx) { - IgniteCache cache = !isKeepBinary() ? rawCache(ctx) : cache(ctx); + IgniteCache cache = qry.getFilter() != null && !isKeepBinary() ? + rawCache(ctx) : cache(ctx); if (qry.getPartition() != null) updateAffinityMetrics(ctx, qry.getPartition()); diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/thin_client_query_test/ThinClientQueryTestApplication.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/thin_client_query_test/ThinClientQueryTestApplication.java new file mode 100644 index 0000000000000..6581ceefc0f0e --- /dev/null +++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/thin_client_query_test/ThinClientQueryTestApplication.java @@ -0,0 +1,164 @@ +/* + * 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.ducktest.tests.thin_client_query_test; + +import java.util.LinkedHashMap; +import java.util.List; +import javax.cache.Cache; +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.ignite.binary.BinaryObject; +import org.apache.ignite.cache.QueryEntity; +import org.apache.ignite.cache.QueryIndex; +import org.apache.ignite.cache.query.IndexQuery; +import org.apache.ignite.cache.query.ScanQuery; +import org.apache.ignite.client.ClientCache; +import org.apache.ignite.client.ClientCacheConfiguration; +import org.apache.ignite.internal.ducktest.utils.IgniteAwareApplication; +import org.apache.ignite.internal.util.typedef.F; + +/** Tests cache queries for thin client. */ +public class ThinClientQueryTestApplication extends IgniteAwareApplication { + /** */ + private static final int CNT = 100; + + /** */ + @Override protected void run(JsonNode jsonNode) throws Exception { + markInitialized(); + + QueryEntity qe = new QueryEntity(Integer.class, EntityValue.class) + .setValueType(EntityValue.class.getName()) + .setFields(new LinkedHashMap<>(F.asMap("val", Integer.class.getName()))) + .setIndexes(F.asList(new QueryIndex("val", false).setName("VAL_IDX"))); + + ClientCacheConfiguration clnCacheCfg = new ClientCacheConfiguration() + .setName("testCache") + .setQueryEntities(qe); + + ClientCache cache = client.createCache(clnCacheCfg); + + for (int i = 0; i < CNT; i++) + cache.put(i, new EntityValue(i)); + + boolean filter = jsonNode.get("filter").asBoolean(); + + testIndexQuery(cache, filter); + testBinaryIndexQuery(cache, filter); + testScanQuery(cache, filter); + testBinaryScanQuery(cache, filter); + + markFinished(); + } + + /** */ + private void testIndexQuery(ClientCache cache, boolean filter) { + IndexQuery idxQry = new IndexQuery<>(EntityValue.class.getName(), "VAL_IDX"); + + if (filter) + idxQry.setFilter((k, v) -> v.val < CNT / 2); + + List> result = cache.query(idxQry).getAll(); + + int cnt = filter ? CNT / 2 : CNT; + + assert result.size() == cnt; + + for (int i = 0; i < cnt; i++) { + Cache.Entry e = result.get(i); + + assert e.getKey() == i; + assert e.getValue().val == i; + } + } + + /** */ + private void testBinaryIndexQuery(ClientCache cache, boolean filter) { + IndexQuery idxQry = new IndexQuery<>(EntityValue.class.getName(), "VAL_IDX"); + + if (filter) + idxQry.setFilter((k, v) -> (int)v.field("val") < CNT / 2); + + List> result = cache.withKeepBinary().query(idxQry).getAll(); + + int cnt = filter ? CNT / 2 : CNT; + + assert result.size() == cnt; + + for (int i = 0; i < cnt; i++) { + Cache.Entry e = result.get(i); + + assert e.getKey() == i; + assert (int)e.getValue().field("val") == i; + } + } + + /** */ + private void testScanQuery(ClientCache cache, boolean filter) { + ScanQuery scanQry = new ScanQuery<>(); + + if (filter) + scanQry.setFilter((k, v) -> v.val < CNT / 2); + + List> result = cache.query(scanQry).getAll(); + + int cnt = filter ? CNT / 2 : CNT; + + assert result.size() == cnt; + + for (int i = 0; i < cnt; i++) { + Cache.Entry e = result.get(i); + + assert e.getKey() == e.getValue().val; + } + } + + /** */ + private void testBinaryScanQuery(ClientCache cache, boolean filter) { + ScanQuery scanQry = new ScanQuery<>(); + + if (filter) + scanQry.setFilter((k, v) -> (int)v.field("val") < CNT / 2); + + List> result = cache.withKeepBinary().query(scanQry).getAll(); + + int cnt = filter ? CNT / 2 : CNT; + + assert result.size() == cnt; + + for (int i = 0; i < cnt; i++) { + Cache.Entry e = result.get(i); + + assert e.getKey() == e.getValue().field("val"); + } + } + + /** */ + private static class EntityValue { + /** */ + private final int val; + + /** */ + public EntityValue(int val) { + this.val = val; + } + + /** */ + public String toString() { + return "EntityValue [val=" + val + "]"; + } + } +} diff --git a/modules/ducktests/tests/ignitetest/tests/thin_client_query_test.py b/modules/ducktests/tests/ignitetest/tests/thin_client_query_test.py new file mode 100644 index 0000000000000..e008878159a68 --- /dev/null +++ b/modules/ducktests/tests/ignitetest/tests/thin_client_query_test.py @@ -0,0 +1,95 @@ +# 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. + +""" +This module contains client queries tests. +""" +from ducktape.mark import matrix + +from ignitetest.services.ignite import IgniteService +from ignitetest.services.ignite_app import IgniteApplicationService +from ignitetest.services.utils.ignite_configuration import IgniteConfiguration, IgniteThinClientConfiguration +from ignitetest.services.utils.ignite_spec import IgniteNodeSpec +from ignitetest.services.utils.ssl.client_connector_configuration import ClientConnectorConfiguration +from ignitetest.utils import cluster, ignite_versions +from ignitetest.utils.ignite_test import IgniteTest +from ignitetest.utils.version import DEV_BRANCH, IgniteVersion + + +class ThinClientQueryTest(IgniteTest): + """ + cluster - cluster size. + JAVA_CLIENT_CLASS_NAME - running classname. + to use with ssl enabled: + export GLOBALS='{"ssl":{"enabled":true}}' . + """ + @cluster(num_nodes=3) + @ignite_versions(str(DEV_BRANCH), version_prefix="server_version") + @matrix(filter=[False, True]) + def test_thin_client_index_query(self, server_version, filter): + """ + Thin client IndexQuery test. + :param server_version Ignite node version. + :param filter Whether to use filter for queries. + """ + + server_config = IgniteConfiguration(version=IgniteVersion(server_version), + client_connector_configuration=ClientConnectorConfiguration()) + + ignite = IgniteService(self.test_context, server_config, 2) + + if not filter: + ignite.spec = IgniteNodeSpecExcludeDucktests(service=ignite) + + addresses = [ignite.nodes[0].account.hostname + ":" + str(server_config.client_connector_configuration.port)] + + cls = "org.apache.ignite.internal.ducktest.tests.thin_client_query_test.ThinClientQueryTestApplication" + + thin_clients = IgniteApplicationService(self.test_context, + IgniteThinClientConfiguration( + addresses=addresses, + version=IgniteVersion(str(DEV_BRANCH))), + java_class_name=cls, + num_nodes=1, + params={"filter": filter}) + + ignite.start() + thin_clients.run() + ignite.stop() + + +class IgniteNodeSpecExcludeDucktests(IgniteNodeSpec): + """ + Ignite node specification that excludes module 'ducktests' from classpath. + """ + def modules(self): + """ + Exclude module from preparing USER_LIBS environment variable. + """ + modules = super().modules() + + modules.remove("ducktests") + + return modules + + def envs(self): + """ + Skip the module target directory while building classpath. + """ + envs = super().envs() + + envs["EXCLUDE_MODULES"] = "ducktests" + + return envs