blob: be8b980c6693307c612b1acc5a5df9ee01c7e527 [file] [log] [blame]
/*
* 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.table;
import static java.util.Spliterators.spliteratorUnknownSize;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCode;
import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
import static org.apache.ignite.internal.testframework.matchers.TupleMatcher.tupleValue;
import static org.apache.ignite.internal.util.IgniteUtils.closeAll;
import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR;
import static org.apache.ignite.lang.util.IgniteNameUtils.quote;
import static org.apache.ignite.table.criteria.Criteria.columnValue;
import static org.apache.ignite.table.criteria.Criteria.equalTo;
import static org.apache.ignite.table.criteria.Criteria.greaterThan;
import static org.apache.ignite.table.criteria.Criteria.greaterThanOrEqualTo;
import static org.apache.ignite.table.criteria.Criteria.in;
import static org.apache.ignite.table.criteria.Criteria.lessThan;
import static org.apache.ignite.table.criteria.Criteria.lessThanOrEqualTo;
import static org.apache.ignite.table.criteria.Criteria.notEqualTo;
import static org.apache.ignite.table.criteria.Criteria.notIn;
import static org.apache.ignite.table.criteria.Criteria.notNullValue;
import static org.apache.ignite.table.criteria.Criteria.nullValue;
import static org.apache.ignite.table.criteria.CriteriaQueryOptions.builder;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Spliterator;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.internal.ClusterPerClassIntegrationTest;
import org.apache.ignite.internal.lang.IgniteBiTuple;
import org.apache.ignite.lang.AsyncCursor;
import org.apache.ignite.lang.Cursor;
import org.apache.ignite.lang.CursorClosedException;
import org.apache.ignite.lang.ErrorGroups.Common;
import org.apache.ignite.table.Table;
import org.apache.ignite.table.Tuple;
import org.apache.ignite.table.criteria.CriteriaException;
import org.apache.ignite.table.criteria.CriteriaQuerySource;
import org.apache.ignite.table.mapper.Mapper;
import org.apache.ignite.tx.Transaction;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/**
* Tests for the criteria query API.
*/
public class ItCriteriaQueryTest extends ClusterPerClassIntegrationTest {
/** Table name. */
private static final String TABLE_NAME = "tbl";
/** Table with quoted name. */
private static final String QUOTED_TABLE_NAME = quote("TaBleName");
private static IgniteClient CLIENT;
/** {@inheritDoc} */
@Override
protected int initialNodes() {
return 1;
}
/** {@inheritDoc} */
@BeforeAll
@Override
protected void beforeAll(TestInfo testInfo) {
super.beforeAll(testInfo);
CLIENT = IgniteClient.builder()
.addresses("127.0.0.1:" + CLUSTER.aliveNode().clientAddress().port()).build();
sql(format("CREATE TABLE {} (id INT PRIMARY KEY, name VARCHAR, salary DOUBLE, hash VARBINARY)", TABLE_NAME));
insertData(
TABLE_NAME,
List.of("ID", "name", "salary", "hash"),
new Object[]{0, null, 0.0d, "hash0".getBytes()},
new Object[]{1, "name1", 10.0d, "hash1".getBytes()},
new Object[]{2, "name2", 20.0d, "hash2".getBytes()}
);
sql(format("CREATE TABLE {} (id INT PRIMARY KEY, \"colUmn\" VARCHAR)", QUOTED_TABLE_NAME));
insertData(
QUOTED_TABLE_NAME,
List.of("id", quote("colUmn")),
new Object[]{0, "name0"},
new Object[]{1, "name1"},
new Object[]{2, "name2"}
);
}
@AfterAll
void stopClient() throws Exception {
closeAll(CLIENT);
}
private static Stream<Arguments> testRecordViewQuery() {
Table table = CLUSTER.aliveNode().tables().table(TABLE_NAME);
Table clientTable = CLIENT.tables().table(TABLE_NAME);
Function<TestObject, Tuple> objMapper = (obj) -> Tuple.create().set("id", obj.id).set("name", obj.name)
.set("salary", obj.salary).set("hash", obj.hash);
return Stream.of(
Arguments.of(table.recordView(), identity()),
Arguments.of(table.recordView(TestObject.class), objMapper),
Arguments.of(clientTable.recordView(), identity()),
Arguments.of(clientTable.recordView(TestObject.class), objMapper)
);
}
@ParameterizedTest
@MethodSource
public <T> void testRecordViewQuery(CriteriaQuerySource<T> view, Function<T, Tuple> mapper) {
assertThrowsWithCode(
CriteriaException.class,
STMT_VALIDATION_ERR,
() -> view.query(null, columnValue("id", equalTo("2"))),
"Dynamic parameter requires adding explicit type cast"
);
Matcher<Tuple> person0 = allOf(tupleValue("id", is(0)), tupleValue("name", Matchers.nullValue()), tupleValue("salary", is(0.0d)),
tupleValue("hash", is("hash0".getBytes())));
Matcher<Tuple> person1 = allOf(tupleValue("id", is(1)), tupleValue("name", is("name1")), tupleValue("salary", is(10.0d)),
tupleValue("hash", is("hash1".getBytes())));
Matcher<Tuple> person2 = allOf(tupleValue("id", is(2)), tupleValue("name", is("name2")), tupleValue("salary", is(20.0d)),
tupleValue("hash", is("hash2".getBytes())));
try (Cursor<T> cur = view.query(null, null)) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person0, person1, person2));
}
try (Cursor<T> cur = view.query(null, columnValue("id", equalTo(2)))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person2));
}
try (Cursor<T> cur = view.query(null, columnValue("hash", equalTo("hash2".getBytes())))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person2));
}
try (Cursor<T> cur = view.query(null, columnValue("id", notEqualTo(2)))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person0, person1));
}
try (Cursor<T> cur = view.query(null, columnValue("hash", notEqualTo("hash2".getBytes())))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person0, person1));
}
try (Cursor<T> cur = view.query(null, columnValue("id", greaterThan(1)))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person2));
}
try (Cursor<T> cur = view.query(null, columnValue("id", greaterThanOrEqualTo(1)))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person1, person2));
}
try (Cursor<T> cur = view.query(null, columnValue("id", lessThan(1)))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person0));
}
try (Cursor<T> cur = view.query(null, columnValue("id", lessThanOrEqualTo(1)))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person0, person1));
}
try (Cursor<T> cur = view.query(null, columnValue("name", nullValue()))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person0));
}
try (Cursor<T> cur = view.query(null, columnValue("name", notNullValue()))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person1, person2));
}
try (Cursor<T> cur = view.query(null, columnValue("id", in(1, 2)))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person1, person2));
}
try (Cursor<T> cur = view.query(null, columnValue("id", notIn(1, 2)))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person0));
}
try (Cursor<T> cur = view.query(null, columnValue("hash", in("hash1".getBytes(), "hash2".getBytes())))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person1, person2));
}
try (Cursor<T> cur = view.query(null, columnValue("hash", in((byte[]) null)))) {
assertThat(mapToTupleList(cur, mapper), empty());
}
try (Cursor<T> cur = view.query(null, columnValue("hash", notIn("hash1".getBytes(), "hash2".getBytes())))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person0));
}
try (Cursor<T> cur = view.query(null, columnValue("hash", notIn((byte[]) null)))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(person0, person1, person2));
}
}
private static Stream<Arguments> testKeyValueView() {
Table table = CLUSTER.aliveNode().tables().table(TABLE_NAME);
Table clientTable = CLIENT.tables().table(TABLE_NAME);
Function<Entry<TestObjectKey, TestObject>, Entry<Tuple, Tuple>> kvMapper = (entry) -> {
TestObjectKey key = entry.getKey();
TestObject val = entry.getValue();
return new IgniteBiTuple<>(Tuple.create().set("id", key.id), Tuple.create().set("name", val.name).set("salary", val.salary)
.set("hash", val.hash));
};
return Stream.of(
Arguments.of(table.keyValueView(), identity()),
Arguments.of(table.keyValueView(TestObjectKey.class, TestObject.class), kvMapper),
Arguments.of(clientTable.keyValueView(), identity()),
Arguments.of(clientTable.keyValueView(TestObjectKey.class, TestObject.class), kvMapper)
);
}
@ParameterizedTest
@MethodSource
public <T> void testKeyValueView(CriteriaQuerySource<T> view, Function<T, Entry<Tuple, Tuple>> mapper) {
assertThrowsWithCode(
CriteriaException.class,
STMT_VALIDATION_ERR,
() -> view.query(null, columnValue("id", equalTo("2"))),
"Dynamic parameter requires adding explicit type cast"
);
Matcher<Tuple> personKey0 = tupleValue("id", is(0));
Matcher<Tuple> person0 = allOf(tupleValue("name", Matchers.nullValue()), tupleValue("salary", is(0.0d)),
tupleValue("hash", is("hash0".getBytes())));
Matcher<Tuple> personKey1 = tupleValue("id", is(1));
Matcher<Tuple> person1 = allOf(tupleValue("name", is("name1")), tupleValue("salary", is(10.0d)),
tupleValue("hash", is("hash1".getBytes())));
Matcher<Tuple> personKey2 = tupleValue("id", is(2));
Matcher<Tuple> person2 = allOf(tupleValue("name", is("name2")), tupleValue("salary", is(20.0d)),
tupleValue("hash", is("hash2".getBytes())));
try (Cursor<T> cur = view.query(null, null)) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(3),
hasEntry(personKey0, person0),
hasEntry(personKey1, person1),
hasEntry(personKey2, person2)
));
}
try (Cursor<T> cur = view.query(null, columnValue("id", equalTo(2)))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(1),
hasEntry(personKey2, person2)
));
}
try (Cursor<T> cur = view.query(null, columnValue("hash", equalTo("hash2".getBytes())))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(1),
hasEntry(personKey2, person2)
));
}
try (Cursor<T> cur = view.query(null, columnValue("id", notEqualTo(2)))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(2),
hasEntry(personKey0, person0),
hasEntry(personKey1, person1)
));
}
try (Cursor<T> cur = view.query(null, columnValue("hash", notEqualTo("hash2".getBytes())))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(2),
hasEntry(personKey0, person0),
hasEntry(personKey1, person1)
));
}
try (Cursor<T> cur = view.query(null, columnValue("id", greaterThan(1)))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(1),
hasEntry(personKey2, person2)
));
}
try (Cursor<T> cur = view.query(null, columnValue("id", greaterThanOrEqualTo(1)))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(2),
hasEntry(personKey1, person1),
hasEntry(personKey2, person2)
));
}
try (Cursor<T> cur = view.query(null, columnValue("id", lessThan(1)))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(1),
hasEntry(personKey0, person0)
));
}
try (Cursor<T> cur = view.query(null, columnValue("id", lessThanOrEqualTo(1)))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(2),
hasEntry(personKey0, person0),
hasEntry(personKey1, person1)
));
}
try (Cursor<T> cur = view.query(null, columnValue("name", nullValue()))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(1),
hasEntry(personKey0, person0)
));
}
try (Cursor<T> cur = view.query(null, columnValue("name", notNullValue()))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(2),
hasEntry(personKey1, person1),
hasEntry(personKey2, person2)
));
}
try (Cursor<T> cur = view.query(null, columnValue("id", in(1, 2)))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(2),
hasEntry(personKey1, person1),
hasEntry(personKey2, person2)
));
}
try (Cursor<T> cur = view.query(null, columnValue("id", notIn(1, 2)))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(1),
hasEntry(personKey0, person0)
));
}
try (Cursor<T> cur = view.query(null, columnValue("hash", in("hash1".getBytes(), "hash2".getBytes())))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(2),
hasEntry(personKey1, person1),
hasEntry(personKey2, person2)
));
}
try (Cursor<T> cur = view.query(null, columnValue("hash", in((byte[]) null)))) {
assertThat(mapToTupleMap(cur, mapper), anEmptyMap());
}
try (Cursor<T> cur = view.query(null, columnValue("hash", notIn("hash1".getBytes(), "hash2".getBytes())))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(1),
hasEntry(personKey0, person0)
));
}
try (Cursor<T> cur = view.query(null, columnValue("hash", notIn((byte[]) null)))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(3),
hasEntry(personKey0, person0),
hasEntry(personKey1, person1),
hasEntry(personKey2, person2)
));
}
}
private static Stream<Arguments> clientViews() {
Table clientTable = CLIENT.tables().table(TABLE_NAME);
return Stream.of(
Arguments.of(clientTable.recordView()),
Arguments.of(clientTable.recordView(TestObject.class)),
Arguments.of(clientTable.keyValueView()),
Arguments.of(clientTable.keyValueView(TestObjectKey.class, TestObject.class))
);
}
@ParameterizedTest
@MethodSource("clientViews")
public <T> void testPageOption(CriteriaQuerySource<T> view) {
AsyncCursor<T> ars = await(view.queryAsync(null, null, null, builder().pageSize(2).build()));
assertNotNull(ars);
assertEquals(2, ars.currentPageSize());
AsyncCursor<T> ars1 = await(ars.fetchNextPage());
assertNotNull(ars1);
assertEquals(1, ars1.currentPageSize());
assertEquals(ars, ars1);
await(ars.closeAsync());
}
private static Stream<Arguments> allViews() {
Table table = CLUSTER.aliveNode().tables().table(TABLE_NAME);
Table clientTable = CLIENT.tables().table(TABLE_NAME);
return Stream.of(
Arguments.of(table.keyValueView()),
Arguments.of(table.keyValueView(TestObjectKey.class, TestObject.class)),
Arguments.of(clientTable.keyValueView()),
Arguments.of(clientTable.keyValueView(TestObjectKey.class, TestObject.class))
);
}
@ParameterizedTest
@MethodSource("allViews")
void testFetchCursorIsClosed(CriteriaQuerySource<TestObject> view) {
AsyncCursor<TestObject> ars1 = await(view.queryAsync(null, null, null, builder().pageSize(2).build()));
assertNotNull(ars1);
await(ars1.closeAsync());
assertThrowsWithCode(CursorClosedException.class, Common.CURSOR_ALREADY_CLOSED_ERR,
() -> await(ars1.fetchNextPage()), "Cursor is closed");
AsyncCursor<TestObject> ars2 = await(view.queryAsync(null, null, null, builder().pageSize(3).build()));
assertNotNull(ars2);
assertThrowsWithCode(CursorClosedException.class, Common.CURSOR_ALREADY_CLOSED_ERR,
() -> await(ars2.fetchNextPage()), "Cursor is closed");
}
@ParameterizedTest
@MethodSource("allViews")
<T> void testInvalidColumnName(CriteriaQuerySource<T> view) {
assertThrowsWithCode(CriteriaException.class, INTERNAL_ERR,
() -> await(view.queryAsync(null, columnValue("id1", equalTo(2)))), "Unexpected column name: ID1");
}
private static Stream<Arguments> testRecordViewWithQuotes() {
Table table = CLUSTER.aliveNode().tables().table(QUOTED_TABLE_NAME);
Table clientTable = CLIENT.tables().table(QUOTED_TABLE_NAME);
Mapper<QuotedObject> recMapper = Mapper.builder(QuotedObject.class)
.map("colUmn", quote("colUmn"))
.automap()
.build();
Function<QuotedObject, Tuple> objMapper = (obj) -> Tuple.create(Map.of("id", obj.id, quote("colUmn"), obj.colUmn));
return Stream.of(
Arguments.of(table.recordView(), identity()),
Arguments.of(table.recordView(recMapper), objMapper),
Arguments.of(clientTable.recordView(), identity()),
Arguments.of(clientTable.recordView(recMapper), objMapper)
);
}
@ParameterizedTest
@MethodSource
public <T> void testRecordViewWithQuotes(CriteriaQuerySource<T> view, Function<T, Tuple> mapper) {
try (Cursor<T> cur = view.query(null, columnValue(quote("colUmn"), equalTo("name1")))) {
assertThat(mapToTupleList(cur, mapper), containsInAnyOrder(
allOf(tupleValue("id", is(1)), tupleValue(quote("colUmn"), is("name1")))
));
}
}
private static Stream<Arguments> testKeyViewWithQuotes() {
Table table = CLUSTER.aliveNode().tables().table(QUOTED_TABLE_NAME);
Table clientTable = CLIENT.tables().table(QUOTED_TABLE_NAME);
Mapper<QuotedObjectKey> keyMapper = Mapper.of(QuotedObjectKey.class);
Mapper<QuotedObject> valMapper = Mapper.builder(QuotedObject.class)
.map("colUmn", quote("colUmn"))
.build();
Function<Entry<QuotedObjectKey, QuotedObject>, Entry<Tuple, Tuple>> kvMapper = (entry) ->
new IgniteBiTuple<>(Tuple.create(Map.of("id", entry.getKey().id)), Tuple.create(Map.of(quote("colUmn"),
entry.getValue().colUmn)));
return Stream.of(
Arguments.of(table.keyValueView(), identity()),
Arguments.of(table.keyValueView(keyMapper, valMapper), kvMapper),
Arguments.of(clientTable.keyValueView(), identity()),
Arguments.of(clientTable.keyValueView(keyMapper, valMapper), kvMapper)
);
}
@ParameterizedTest
@MethodSource
public <T> void testKeyViewWithQuotes(CriteriaQuerySource<T> view, Function<T, Entry<Tuple, Tuple>> mapper) {
try (Cursor<T> cur = view.query(null, columnValue(quote("colUmn"), equalTo("name1")))) {
assertThat(mapToTupleMap(cur, mapper), allOf(
aMapWithSize(1),
hasEntry(tupleValue("id", is(1)), tupleValue(quote("colUmn"), is("name1")))
));
}
}
private static Stream<Arguments> testSessionClosing() {
Table table = CLUSTER.aliveNode().tables().table(TABLE_NAME);
Transaction tx = CLUSTER.aliveNode().transactions().begin();
tx.rollback();
return Stream.of(
Arguments.of(table.recordView(), tx),
Arguments.of(table.recordView(TestObject.class), tx),
Arguments.of(table.keyValueView(), tx),
Arguments.of(table.keyValueView(TestObjectKey.class, TestObject.class), tx)
);
}
private static <T> List<Tuple> mapToTupleList(Cursor<T> cur, Function<T, Tuple> mapper) {
return StreamSupport.stream(spliteratorUnknownSize(cur, Spliterator.ORDERED), false)
.map(mapper)
.collect(toList());
}
private static <T, K, V> Map<K, V> mapToTupleMap(Cursor<T> cur, Function<T, Entry<K, V>> mapper) {
return StreamSupport.stream(spliteratorUnknownSize(cur, Spliterator.ORDERED), false)
.map(mapper)
.collect(toMap(Entry::getKey, Entry::getValue));
}
static class QuotedObjectKey {
int id;
}
static class QuotedObject {
int id;
String colUmn;
}
static class TestObjectKey {
int id;
}
static class TestObject {
int id;
String name;
double salary;
byte[] hash;
}
}