/*
* 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;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cache.QueryEntity;
import org.apache.ignite.cache.QueryIndex;
import org.apache.ignite.cache.query.QueryCursor;
import org.apache.ignite.cache.query.SqlFieldsQuery;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.binary.BinaryMarshaller;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
import org.apache.ignite.testframework.GridTestUtils;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.Callable;
/**
* Test hidden _key, _val, _ver columns
*/
public class IgniteSqlKeyValueFieldsTest extends GridCommonAbstractTest {
/** IP finder. */
private static final TcpDiscoveryVmIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
/** */
private static String NODE_BAD_CONF_MISS_KEY_FIELD = "badConf1";
/** */
private static String NODE_BAD_CONF_MISS_VAL_FIELD = "badConf2";
/** */
private static String NODE_CLIENT = "client";
/** */
private static String CACHE_PERSON_NO_KV = "PersonNoKV";
/** */
private static String CACHE_INT_NO_KV_TYPE = "IntNoKVType";
/** */
private static String CACHE_PERSON = "Person";
/** */
private static String CACHE_JOB = "Job";
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
IgniteConfiguration c = super.getConfiguration(gridName);
TcpDiscoverySpi disco = new TcpDiscoverySpi();
disco.setIpFinder(IP_FINDER);
c.setDiscoverySpi(disco);
c.setMarshaller(new BinaryMarshaller());
List<CacheConfiguration> ccfgs = new ArrayList<>();
CacheConfiguration ccfg = buildCacheConfiguration(gridName);
if (ccfg != null)
ccfgs.add(ccfg);
ccfgs.add(buildCacheConfiguration(CACHE_PERSON_NO_KV));
ccfgs.add(buildCacheConfiguration(CACHE_INT_NO_KV_TYPE));
ccfgs.add(buildCacheConfiguration(CACHE_PERSON));
ccfgs.add(buildCacheConfiguration(CACHE_JOB));
c.setCacheConfiguration(ccfgs.toArray(new CacheConfiguration[ccfgs.size()]));
if (gridName.equals(NODE_CLIENT))
c.setClientMode(true);
return c;
}
/** {@inheritDoc} */
@Override protected void beforeTest() throws Exception {
super.beforeTest();
startGrid(0);
startGrid(NODE_CLIENT);
}
/** {@inheritDoc} */
@Override protected void afterTest() throws Exception {
super.afterTest();
stopAllGrids();
}
private CacheConfiguration buildCacheConfiguration(String name) {
if (name.equals(NODE_BAD_CONF_MISS_KEY_FIELD)) {
CacheConfiguration ccfg = new CacheConfiguration(NODE_BAD_CONF_MISS_KEY_FIELD);
QueryEntity qe = new QueryEntity(Object.class.getName(), Object.class.getName());
qe.setKeyFieldName("k");
qe.addQueryField("a", Integer.class.getName(), null);
ccfg.setQueryEntities(F.asList(qe));
return ccfg;
}
else if (name.equals(NODE_BAD_CONF_MISS_VAL_FIELD)) {
CacheConfiguration ccfg = new CacheConfiguration(NODE_BAD_CONF_MISS_VAL_FIELD);
QueryEntity qe = new QueryEntity(Object.class.getName(), Object.class.getName());
qe.setValueFieldName("v");
qe.addQueryField("a", Integer.class.getName(), null);
ccfg.setQueryEntities(F.asList(qe));
return ccfg;
}
else if (name.equals(CACHE_PERSON_NO_KV)) {
CacheConfiguration ccfg = new CacheConfiguration(CACHE_PERSON_NO_KV);
QueryEntity entity = new QueryEntity();
entity.setKeyType(Integer.class.getName());
entity.setValueType(Person.class.getName());
LinkedHashMap<String, String> fields = new LinkedHashMap<>();
fields.put("name", String.class.getName());
fields.put("age", Integer.class.getName());
entity.setFields(fields);
ccfg.setQueryEntities(Arrays.asList(entity));
return ccfg;
}
else if (name.equals(CACHE_INT_NO_KV_TYPE)) {
CacheConfiguration ccfg = new CacheConfiguration(CACHE_INT_NO_KV_TYPE);
QueryEntity entity = new QueryEntity();
entity.setKeyType(null);
entity.setValueType(null);
entity.setKeyFieldName("id");
entity.setValueFieldName("v");
LinkedHashMap<String, String> fields = new LinkedHashMap<>();
fields.put("id", Integer.class.getName());
fields.put("v", Integer.class.getName());
entity.setFields(fields);
ccfg.setQueryEntities(Arrays.asList(entity));
return ccfg;
}
else if (name.equals(CACHE_PERSON)) {
CacheConfiguration ccfg = new CacheConfiguration(CACHE_PERSON);
QueryEntity entity = new QueryEntity();
entity.setKeyType(Integer.class.getName());
entity.setValueType(Person.class.getName());
entity.setKeyFieldName("id");
entity.setValueFieldName("v");
LinkedHashMap<String, String> fields = new LinkedHashMap<>();
fields.put("name", String.class.getName());
fields.put("age", Integer.class.getName());
fields.put(entity.getKeyFieldName(), entity.getKeyType());
fields.put(entity.getValueFieldName(), entity.getValueType());
entity.setFields(fields);
ccfg.setQueryEntities(Arrays.asList(entity));
return ccfg;
}
else if (name.equals(CACHE_JOB)) {
CacheConfiguration ccfg = new CacheConfiguration(CACHE_JOB);
ccfg.setIndexedTypes(Integer.class, Integer.class);
return ccfg;
}
return null;
}
/** Test for setIndexedTypes() primitive types */
public void testSetIndexTypesPrimitive() throws Exception {
IgniteCache<Integer, Integer> cache = grid(NODE_CLIENT).cache(CACHE_JOB);
checkInsert(cache, "insert into Integer (_key, _val) values (?,?)", 1, 100);
checkSelect(cache, "select * from Integer", 1, 100);
checkSelect(cache, "select _key, _val from Integer", 1, 100);
}
/** Test configuration error : keyFieldName is missing from fields */
public void testErrorKeyFieldMissingFromFields() throws Exception {
checkCacheStartupError(NODE_BAD_CONF_MISS_KEY_FIELD);
}
/** Test configuration error : valueFieldName is missing from fields */
public void testErrorValueFieldMissingFromFields() throws Exception {
checkCacheStartupError(NODE_BAD_CONF_MISS_VAL_FIELD);
}
/** */
private void checkCacheStartupError(final String name) {
GridTestUtils.assertThrows(log, new Callable<Void>() {
@Override public Void call() throws Exception {
startGrid(name);
return null;
}
}, IgniteCheckedException.class, null);
}
/**
* Check that it is allowed to leave QE.keyType and QE.valueType unset
* in case keyFieldName and valueFieldName are set and present in fields
*/
public void testQueryEntityAutoKeyValTypes() throws Exception {
IgniteCache<Integer, Integer> cache = grid(NODE_CLIENT).cache(CACHE_INT_NO_KV_TYPE);
checkInsert(cache, "insert into Integer (_key, _val) values (?,?)", 1, 100);
checkSelect(cache, "select * from Integer where id = 1", 1, 100);
checkSelect(cache, "select * from Integer", 1, 100);
checkSelect(cache, "select _key, _val from Integer", 1, 100);
checkSelect(cache, "select id, v from Integer", 1, 100);
}
/** Check that it is possible to not have keyFieldName and valueFieldName */
public void testNoKeyValueAliases() throws Exception {
IgniteCache<Integer, Person> cache = grid(NODE_CLIENT).cache(CACHE_PERSON_NO_KV);
Person alice = new Person("Alice", 1);
checkInsert(cache, "insert into Person (_key, _val) values (?,?)", 1, alice);
checkSelect(cache, "select * from Person", alice.name, alice.age);
checkSelect(cache, "select _key, _val from Person", 1, alice);
}
/** Check keyFieldName and valueFieldName columns access */
public void testKeyValueAlias() throws Exception {
//_key, _val, _ver | name, age, id, v
Person alice = new Person("Alice", 1);
Person bob = new Person("Bob", 2);
IgniteCache<Integer, Person> cache = grid(NODE_CLIENT).cache(CACHE_PERSON);
checkInsert(cache, "insert into Person (_key, _val) values (?,?)", 1, alice);
checkInsert(cache, "insert into Person (id, v) values (?,?)", 2, bob);
checkSelect(cache, "select * from Person where _key=1", alice.name, alice.age, 1, alice);
checkSelect(cache, "select _key, _val from Person where id=1", 1, alice);
checkSelect(cache, "select * from Person where _key=2", bob.name, bob.age, 2, bob);
checkSelect(cache, "select _key, _val from Person where id=2", 2, bob);
checkInsert(cache, "update Person set age = ? where id = ?", 3, 1);
checkSelect(cache, "select _key, age from Person where id=1", 1, 3);
checkInsert(cache, "update Person set v = ? where id = ?", alice, 1);
checkSelect(cache, "select _key, _val from Person where id=1", 1, alice);
}
/** Check _ver version field is accessible */
public void testVersionField() throws Exception {
Person alice = new Person("Alice", 1);
Person bob = new Person("Bob", 2);
IgniteCache<Integer, Person> cache = grid(NODE_CLIENT).cache(CACHE_PERSON);
checkInsert(cache, "insert into Person (id, v) values (?,?)", 1, alice);
assertNotNull(getVersion(cache, 1));
checkInsert(cache, "insert into Person (id, v) values (?,?)", 2, bob);
assertNotNull(getVersion(cache, 2));
GridCacheVersion v1 = getVersion(cache, 1);
checkInsert(cache, "update Person set age = ? where id = ?", 3, 1);
GridCacheVersion v2 = getVersion(cache, 1);
assertFalse( v1.equals(v2) );
}
/** Check that joins are working on keyFieldName, valueFieldName columns */
public void testJoinKeyValFields() throws Exception {
IgniteEx client = grid(NODE_CLIENT);
IgniteCache<Integer, Person> cache = client.cache(CACHE_PERSON);
IgniteCache<Integer, Integer> cache2 = client.cache(CACHE_JOB);
checkInsert(cache, "insert into Person (id, v) values (?, ?)", 1, new Person("Bob", 30));
checkInsert(cache, "insert into Person (id, v) values (?, ?)", 2, new Person("David", 35));
checkInsert(cache2, "insert into Integer (_key, _val) values (?, ?)", 100, 1);
checkInsert(cache2, "insert into Integer (_key, _val) values (?, ?)", 200, 2);
QueryCursor<List<?>> cursor = cache.query(new SqlFieldsQuery("select p.id, j._key from Person p, \""+ CACHE_JOB +"\".Integer j where p.id = j._val"));
List<List<?>> results = cursor.getAll();
assertEquals(2, results.size());
assertEquals(1, results.get(0).get(0));
assertEquals(100, results.get(0).get(1));
assertEquals(2, results.get(1).get(0));
assertEquals(200, results.get(1).get(1));
}
/** Check automatic addition of index for keyFieldName column */
public void testAutoKeyFieldIndex() throws Exception {
IgniteEx client = grid(NODE_CLIENT);
IgniteCache<Integer, Person> cache = client.cache(CACHE_PERSON);
QueryCursor<List<?>> cursor = cache.query(new SqlFieldsQuery("explain select * from Person where id = 1"));
List<List<?>> results = cursor.getAll();
assertEquals(2, results.size());
assertTrue(((String)results.get(0).get(0)).contains("\"_key_PK_proxy\""));
cursor = cache.query(new SqlFieldsQuery("explain select * from Person where _key = 1"));
results = cursor.getAll();
assertEquals(2, results.size());
assertTrue(((String)results.get(0).get(0)).contains("\"_key_PK\""));
}
/** */
private GridCacheVersion getVersion(IgniteCache<?, ?> cache, int key) {
QueryCursor<List<?>> cursor = cache.query(new SqlFieldsQuery("select _ver from Person where id = ?").setArgs(key));
List<List<?>> results = cursor.getAll();
assertEquals(1, results.size());
return ((GridCacheVersion) results.get(0).get(0));
}
/** */
private void checkInsert(IgniteCache<?, ?> cache, String qry, Object ... args) throws Exception {
QueryCursor<List<?>> cursor = cache.query(new SqlFieldsQuery(qry).setArgs(args));
assertEquals(1, ((Number) cursor.getAll().get(0).get(0)).intValue());
}
/** */
private void checkSelect(IgniteCache<?, ?> cache, String selectQry, Object ... expected) {
QueryCursor<List<?>> cursor = cache.query(new SqlFieldsQuery(selectQry));
List<List<?>> results = cursor.getAll();
assertEquals(1, results.size());
List<?> row0 = results.get(0);
for(int col = 0; col < expected.length; ++col)
assertEquals(expected[col], row0.get(col));
}
/** */
private static class Person {
/** */
private String name;
/** */
private int age;
/** */
public Person(String name, int age) {
this.name = name;
this.age = age;
}
/** */
@Override public int hashCode() {
return name.hashCode() ^ age;
}
/** */
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Person))
return false;
Person other = (Person)o;
return name.equals(other.name) && age == other.age;
}
}
}