/* * 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.IgniteCheckedException; import org.apache.ignite.cache.QueryEntity; import org.apache.ignite.cache.QueryIndex; import org.apache.ignite.cache.QueryIndexType; import org.apache.ignite.cache.affinity.AffinityKeyMapper; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.binary.BinaryMarshaller; import org.apache.ignite.internal.processors.cache.CacheObjectContext; import org.apache.ignite.internal.processors.cache.GridCacheContext; import org.apache.ignite.internal.processors.cache.GridCacheDefaultAffinityKeyMapper; import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl; import org.apache.ignite.internal.processors.query.schema.SchemaOperationException; import org.apache.ignite.internal.processors.query.property.QueryBinaryProperty; import org.apache.ignite.internal.processors.query.property.QueryClassProperty; import org.apache.ignite.internal.processors.query.property.QueryFieldAccessor; import org.apache.ignite.internal.processors.query.property.QueryMethodsAccessor; import org.apache.ignite.internal.processors.query.property.QueryPropertyAccessor; import org.apache.ignite.internal.processors.query.property.QueryReadOnlyMethodsAccessor; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.A; import org.apache.ignite.internal.util.typedef.internal.U; import org.jetbrains.annotations.Nullable; import java.lang.reflect.Method; import java.math.BigDecimal; import java.sql.Time; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.apache.ignite.IgniteSystemProperties.IGNITE_INDEXING_DISCOVERY_HISTORY_SIZE; import static org.apache.ignite.IgniteSystemProperties.getInteger; /** * Utility methods for queries. */ public class QueryUtils { /** Field name for key. */ public static final String KEY_FIELD_NAME = "_key"; /** Field name for value. */ public static final String VAL_FIELD_NAME = "_val"; /** Version field name. */ public static final String VER_FIELD_NAME = "_ver"; /** Discovery history size. */ private static final int DISCO_HIST_SIZE = getInteger(IGNITE_INDEXING_DISCOVERY_HISTORY_SIZE, 1000); /** */ private static final Class<?> GEOMETRY_CLASS = U.classForName("com.vividsolutions.jts.geom.Geometry", null); /** */ private static final Set<Class<?>> SQL_TYPES = new HashSet<>(F.<Class<?>>asList( Integer.class, Boolean.class, Byte.class, Short.class, Long.class, BigDecimal.class, Double.class, Float.class, Time.class, Timestamp.class, java.util.Date.class, java.sql.Date.class, String.class, UUID.class, byte[].class )); /** * Get table name for entity. * * @param entity Entity. * @return Table name. */ public static String tableName(QueryEntity entity) { String res = entity.getTableName(); if (res == null) res = typeName(entity.findValueType()); return res; } /** * Get index name. * * @param entity Query entity. * @param idx Index. * @return Index name. */ public static String indexName(QueryEntity entity, QueryIndex idx) { return indexName(tableName(entity), idx); } /** * Get index name. * * @param tblName Table name. * @param idx Index. * @return Index name. */ public static String indexName(String tblName, QueryIndex idx) { String res = idx.getName(); if (res == null) { StringBuilder idxName = new StringBuilder(tblName + "_"); for (Map.Entry<String, Boolean> field : idx.getFields().entrySet()) { idxName.append(field.getKey()); idxName.append('_'); idxName.append(field.getValue() ? "asc_" : "desc_"); } for (int i = 0; i < idxName.length(); i++) { char ch = idxName.charAt(i); if (Character.isWhitespace(ch)) idxName.setCharAt(i, '_'); else idxName.setCharAt(i, Character.toLowerCase(ch)); } idxName.append("idx"); return idxName.toString(); } return res; } /** * Create type candidate for query entity. * * @param space Space. * @param cctx Cache context. * @param qryEntity Query entity. * @param mustDeserializeClss Classes which must be deserialized. * @return Type candidate. * @throws IgniteCheckedException If failed. */ public static QueryTypeCandidate typeForQueryEntity(String space, GridCacheContext cctx, QueryEntity qryEntity, List<Class<?>> mustDeserializeClss) throws IgniteCheckedException { GridKernalContext ctx = cctx.kernalContext(); CacheConfiguration<?,?> ccfg = cctx.config(); boolean binaryEnabled = ctx.cacheObjects().isBinaryEnabled(ccfg); CacheObjectContext coCtx = binaryEnabled ? ctx.cacheObjects().contextForCache(ccfg) : null; QueryTypeDescriptorImpl desc = new QueryTypeDescriptorImpl(space); desc.aliases(qryEntity.getAliases()); // Key and value classes still can be available if they are primitive or JDK part. // We need that to set correct types for _key and _val columns. Class<?> keyCls = U.classForName(qryEntity.findKeyType(), null); Class<?> valCls = U.classForName(qryEntity.findValueType(), null); // If local node has the classes and they are externalizable, we must use reflection properties. boolean keyMustDeserialize = mustDeserializeBinary(ctx, keyCls); boolean valMustDeserialize = mustDeserializeBinary(ctx, valCls); boolean keyOrValMustDeserialize = keyMustDeserialize || valMustDeserialize; if (keyCls == null) keyCls = Object.class; String simpleValType = ((valCls == null) ? typeName(qryEntity.findValueType()) : typeName(valCls)); desc.name(simpleValType); desc.tableName(qryEntity.getTableName()); if (binaryEnabled && !keyOrValMustDeserialize) { // Safe to check null. if (SQL_TYPES.contains(valCls)) desc.valueClass(valCls); else desc.valueClass(Object.class); if (SQL_TYPES.contains(keyCls)) desc.keyClass(keyCls); else desc.keyClass(Object.class); } else { if (valCls == null) throw new IgniteCheckedException("Failed to find value class in the node classpath " + "(use default marshaller to enable binary objects) : " + qryEntity.findValueType()); desc.valueClass(valCls); desc.keyClass(keyCls); } desc.keyTypeName(qryEntity.findKeyType()); desc.valueTypeName(qryEntity.findValueType()); desc.keyFieldName(qryEntity.getKeyFieldName()); desc.valueFieldName(qryEntity.getValueFieldName()); if (binaryEnabled && keyOrValMustDeserialize) { if (keyMustDeserialize) mustDeserializeClss.add(keyCls); if (valMustDeserialize) mustDeserializeClss.add(valCls); } QueryTypeIdKey typeId; QueryTypeIdKey altTypeId = null; if (valCls == null || (binaryEnabled && !keyOrValMustDeserialize)) { processBinaryMeta(ctx, qryEntity, desc); typeId = new QueryTypeIdKey(space, ctx.cacheObjects().typeId(qryEntity.findValueType())); if (valCls != null) altTypeId = new QueryTypeIdKey(space, valCls); if (!cctx.customAffinityMapper() && qryEntity.findKeyType() != null) { // Need to setup affinity key for distributed joins. String affField = ctx.cacheObjects().affinityField(qryEntity.findKeyType()); if (affField != null) desc.affinityKey(affField); } } else { processClassMeta(qryEntity, desc, coCtx); AffinityKeyMapper keyMapper = cctx.config().getAffinityMapper(); if (keyMapper instanceof GridCacheDefaultAffinityKeyMapper) { String affField = ((GridCacheDefaultAffinityKeyMapper)keyMapper).affinityKeyPropertyName(desc.keyClass()); if (affField != null) desc.affinityKey(affField); } typeId = new QueryTypeIdKey(space, valCls); altTypeId = new QueryTypeIdKey(space, ctx.cacheObjects().typeId(qryEntity.findValueType())); } return new QueryTypeCandidate(typeId, altTypeId, desc); } /** * Processes declarative metadata for binary object. * * @param ctx Kernal context. * @param qryEntity Declared metadata. * @param d Type descriptor. * @throws IgniteCheckedException If failed. */ public static void processBinaryMeta(GridKernalContext ctx, QueryEntity qryEntity, QueryTypeDescriptorImpl d) throws IgniteCheckedException { Set<String> keyFields = qryEntity.getKeyFields(); // We have to distinguish between empty and null keyFields when the key is not of SQL type - // when a key is not of SQL type, absence of a field in nonnull keyFields tell us that this field // is a value field, and null keyFields tells us that current configuration // does not tell us anything about this field's ownership. boolean hasKeyFields = (keyFields != null); boolean isKeyClsSqlType = isSqlType(d.keyClass()); if (hasKeyFields && !isKeyClsSqlType) { //ensure that 'keyFields' is case sensitive subset of 'fields' for (String keyField : keyFields) { if (!qryEntity.getFields().containsKey(keyField)) throw new IgniteCheckedException("QueryEntity 'keyFields' property must be a subset of keys " + "from 'fields' property (case sensitive): " + keyField); } } for (Map.Entry<String, String> entry : qryEntity.getFields().entrySet()) { Boolean isKeyField; if (isKeyClsSqlType) // We don't care about keyFields in this case - it might be null, or empty, or anything isKeyField = false; else isKeyField = (hasKeyFields ? keyFields.contains(entry.getKey()) : null); QueryBinaryProperty prop = buildBinaryProperty(ctx, entry.getKey(), U.classForName(entry.getValue(), Object.class, true), d.aliases(), isKeyField); d.addProperty(prop, false); } processIndexes(qryEntity, d); } /** * Processes declarative metadata for binary object. * * @param qryEntity Declared metadata. * @param d Type descriptor. * @throws IgniteCheckedException If failed. */ public static void processClassMeta(QueryEntity qryEntity, QueryTypeDescriptorImpl d, CacheObjectContext coCtx) throws IgniteCheckedException { for (Map.Entry<String, String> entry : qryEntity.getFields().entrySet()) { GridQueryProperty prop = buildProperty( d.keyClass(), d.valueClass(), d.keyFieldName(), d.valueFieldName(), entry.getKey(), U.classForName(entry.getValue(), Object.class), d.aliases(), coCtx); d.addProperty(prop, false); } processIndexes(qryEntity, d); } /** * Processes indexes based on query entity. * * @param qryEntity Query entity to process. * @param d Type descriptor to populate. * @throws IgniteCheckedException If failed to build index information. */ private static void processIndexes(QueryEntity qryEntity, QueryTypeDescriptorImpl d) throws IgniteCheckedException { if (!F.isEmpty(qryEntity.getIndexes())) { for (QueryIndex idx : qryEntity.getIndexes()) processIndex(idx, d); } } /** * Process dynamic index change. * * @param idx Index. * @param d Type descriptor to populate. * @throws IgniteCheckedException If failed to build index information. */ public static void processDynamicIndexChange(String idxName, @Nullable QueryIndex idx, QueryTypeDescriptorImpl d) throws IgniteCheckedException { d.dropIndex(idxName); if (idx != null) processIndex(idx, d); } /** * Create index descriptor. * * @param typeDesc Type descriptor. * @param idx Index. * @return Index descriptor. * @throws IgniteCheckedException If failed. */ public static QueryIndexDescriptorImpl createIndexDescriptor(QueryTypeDescriptorImpl typeDesc, QueryIndex idx) throws IgniteCheckedException { String idxName = indexName(typeDesc.tableName(), idx); QueryIndexType idxTyp = idx.getIndexType(); assert idxTyp == QueryIndexType.SORTED || idxTyp == QueryIndexType.GEOSPATIAL; QueryIndexDescriptorImpl res = new QueryIndexDescriptorImpl(typeDesc, idxName, idxTyp, idx.getInlineSize()); int i = 0; for (Map.Entry<String, Boolean> entry : idx.getFields().entrySet()) { String field = entry.getKey(); boolean asc = entry.getValue(); String alias = typeDesc.aliases().get(field); if (alias != null) field = alias; res.addField(field, i++, !asc); } return res; } /** * Process single index. * * @param idx Index. * @param d Type descriptor to populate. * @throws IgniteCheckedException If failed to build index information. */ private static void processIndex(QueryIndex idx, QueryTypeDescriptorImpl d) throws IgniteCheckedException { QueryIndexType idxTyp = idx.getIndexType(); if (idxTyp == QueryIndexType.SORTED || idxTyp == QueryIndexType.GEOSPATIAL) { QueryIndexDescriptorImpl idxDesc = createIndexDescriptor(d, idx); d.addIndex(idxDesc); } else if (idxTyp == QueryIndexType.FULLTEXT){ for (String field : idx.getFields().keySet()) { String alias = d.aliases().get(field); if (alias != null) field = alias; d.addFieldToTextIndex(field); } } else if (idxTyp != null) throw new IllegalArgumentException("Unsupported index type [idx=" + idx.getName() + ", typ=" + idxTyp + ']'); else throw new IllegalArgumentException("Index type is not set: " + idx.getName()); } /** * Builds binary object property. * * @param ctx Kernal context. * @param pathStr String representing path to the property. May contains dots '.' to identify * nested fields. * @param resType Result type. * @param aliases Aliases. * @param isKeyField Key ownership flag, as defined in {@link QueryEntity#keyFields}: {@code true} if field belongs * to key, {@code false} if it belongs to value, {@code null} if QueryEntity#keyFields is null. * @return Binary property. */ public static QueryBinaryProperty buildBinaryProperty(GridKernalContext ctx, String pathStr, Class<?> resType, Map<String, String> aliases, @Nullable Boolean isKeyField) throws IgniteCheckedException { String[] path = pathStr.split("\\."); QueryBinaryProperty res = null; StringBuilder fullName = new StringBuilder(); for (String prop : path) { if (fullName.length() != 0) fullName.append('.'); fullName.append(prop); String alias = aliases.get(fullName.toString()); // The key flag that we've found out is valid for the whole path. res = new QueryBinaryProperty(ctx, prop, res, resType, isKeyField, alias); } return res; } /** * @param keyCls Key class. * @param valCls Value class. * @param pathStr Path string. * @param resType Result type. * @param aliases Aliases. * @return Class property. * @throws IgniteCheckedException If failed. */ public static QueryClassProperty buildClassProperty(Class<?> keyCls, Class<?> valCls, String pathStr, Class<?> resType, Map<String,String> aliases, CacheObjectContext coCtx) throws IgniteCheckedException { QueryClassProperty res = buildClassProperty( true, keyCls, pathStr, resType, aliases, coCtx); if (res == null) // We check key before value consistently with BinaryProperty. res = buildClassProperty(false, valCls, pathStr, resType, aliases, coCtx); if (res == null) throw new IgniteCheckedException(propertyInitializationExceptionMessage(keyCls, valCls, pathStr, resType)); return res; } /** * @param keyCls Key class. * @param valCls Value class. * @param keyFieldName Key Field. * @param valueFieldName Value Field. * @param pathStr Path string. * @param resType Result type. * @param aliases Aliases. * @return Class property. * @throws IgniteCheckedException If failed. */ public static GridQueryProperty buildProperty(Class<?> keyCls, Class<?> valCls, String keyFieldName, String valueFieldName, String pathStr, Class<?> resType, Map<String,String> aliases, CacheObjectContext coCtx) throws IgniteCheckedException { if (pathStr.equals(keyFieldName)) return new KeyOrValProperty(true, pathStr, keyCls); if (pathStr.equals(valueFieldName)) return new KeyOrValProperty(false, pathStr, valCls); return buildClassProperty(keyCls, valCls, pathStr, resType, aliases, coCtx); } /** * Exception message to compare in tests. * * @param keyCls key class * @param valCls value class * @param pathStr property name * @param resType property type * @return Exception message. */ public static String propertyInitializationExceptionMessage(Class<?> keyCls, Class<?> valCls, String pathStr, Class<?> resType) { return "Failed to initialize property '" + pathStr + "' of type '" + resType.getName() + "' for key class '" + keyCls + "' and value class '" + valCls + "'. " + "Make sure that one of these classes contains respective getter method or field."; } /** * @param key If this is a key property. * @param cls Source type class. * @param pathStr String representing path to the property. May contains dots '.' to identify nested fields. * @param resType Expected result type. * @param aliases Aliases. * @return Property instance corresponding to the given path. */ @SuppressWarnings("ConstantConditions") public static QueryClassProperty buildClassProperty(boolean key, Class<?> cls, String pathStr, Class<?> resType, Map<String,String> aliases, CacheObjectContext coCtx) { String[] path = pathStr.split("\\."); QueryClassProperty res = null; StringBuilder fullName = new StringBuilder(); for (String prop : path) { if (fullName.length() != 0) fullName.append('.'); fullName.append(prop); String alias = aliases.get(fullName.toString()); QueryPropertyAccessor accessor = findProperty(prop, cls); if (accessor == null) return null; QueryClassProperty tmp = new QueryClassProperty(accessor, key, alias, coCtx); tmp.parent(res); cls = tmp.type(); res = tmp; } if (!U.box(resType).isAssignableFrom(U.box(res.type()))) return null; return res; } /** * Find a member (either a getter method or a field) with given name of given class. * @param prop Property name. * @param cls Class to search for a member in. * @return Member for given name. */ @Nullable private static QueryPropertyAccessor findProperty(String prop, Class<?> cls) { StringBuilder getBldr = new StringBuilder("get"); getBldr.append(prop); getBldr.setCharAt(3, Character.toUpperCase(getBldr.charAt(3))); StringBuilder setBldr = new StringBuilder("set"); setBldr.append(prop); setBldr.setCharAt(3, Character.toUpperCase(setBldr.charAt(3))); try { Method getter = cls.getMethod(getBldr.toString()); Method setter; try { // Setter has to have the same name like 'setXxx' and single param of the same type // as the return type of the getter. setter = cls.getMethod(setBldr.toString(), getter.getReturnType()); } catch (NoSuchMethodException ignore) { // Have getter, but no setter - return read-only accessor. return new QueryReadOnlyMethodsAccessor(getter, prop); } return new QueryMethodsAccessor(getter, setter, prop); } catch (NoSuchMethodException ignore) { // No-op. } getBldr = new StringBuilder("is"); getBldr.append(prop); getBldr.setCharAt(2, Character.toUpperCase(getBldr.charAt(2))); // We do nothing about setBldr here as it corresponds to setProperty name which is what we need // for boolean property setter as well try { Method getter = cls.getMethod(getBldr.toString()); Method setter; try { // Setter has to have the same name like 'setXxx' and single param of the same type // as the return type of the getter. setter = cls.getMethod(setBldr.toString(), getter.getReturnType()); } catch (NoSuchMethodException ignore) { // Have getter, but no setter - return read-only accessor. return new QueryReadOnlyMethodsAccessor(getter, prop); } return new QueryMethodsAccessor(getter, setter, prop); } catch (NoSuchMethodException ignore) { // No-op. } Class cls0 = cls; while (cls0 != null) try { return new QueryFieldAccessor(cls0.getDeclaredField(prop)); } catch (NoSuchFieldException ignored) { cls0 = cls0.getSuperclass(); } try { Method getter = cls.getMethod(prop); Method setter; try { // Setter has to have the same name and single param of the same type // as the return type of the getter. setter = cls.getMethod(prop, getter.getReturnType()); } catch (NoSuchMethodException ignore) { // Have getter, but no setter - return read-only accessor. return new QueryReadOnlyMethodsAccessor(getter, prop); } return new QueryMethodsAccessor(getter, setter, prop); } catch (NoSuchMethodException ignored) { // No-op. } // No luck. return null; } /** * Check whether type still must be deserialized when binary marshaller is set. * * @param ctx Kernal context. * @param cls Class. * @return {@code True} if will be deserialized. */ private static boolean mustDeserializeBinary(GridKernalContext ctx, Class cls) { if (cls != null && cls != Object.class && ctx.config().getMarshaller() instanceof BinaryMarshaller) { CacheObjectBinaryProcessorImpl proc0 = (CacheObjectBinaryProcessorImpl)ctx.cacheObjects(); return proc0.binaryContext().mustDeserialize(cls); } else return false; } /** * Checks if the given class can be mapped to a simple SQL type. * * @param cls Class. * @return {@code true} If can. */ public static boolean isSqlType(Class<?> cls) { cls = U.box(cls); return SQL_TYPES.contains(cls) || QueryUtils.isGeometryClass(cls); } /** * Checks if the given class is GEOMETRY. * * @param cls Class. * @return {@code true} If this is geometry. */ public static boolean isGeometryClass(Class<?> cls) { return GEOMETRY_CLASS != null && GEOMETRY_CLASS.isAssignableFrom(cls); } /** * Gets type name by class. * * @param clsName Class name. * @return Type name. */ public static String typeName(String clsName) { int pkgEnd = clsName.lastIndexOf('.'); if (pkgEnd >= 0 && pkgEnd < clsName.length() - 1) clsName = clsName.substring(pkgEnd + 1); if (clsName.endsWith("[]")) clsName = clsName.substring(0, clsName.length() - 2) + "_array"; int parentEnd = clsName.lastIndexOf('$'); if (parentEnd >= 0) clsName = clsName.substring(parentEnd + 1); parentEnd = clsName.lastIndexOf('+'); // .NET parent if (parentEnd >= 0) clsName = clsName.substring(parentEnd + 1); return clsName; } /** * Gets type name by class. * * @param cls Class. * @return Type name. */ public static String typeName(Class<?> cls) { String typeName = cls.getSimpleName(); // To protect from failure on anonymous classes. if (F.isEmpty(typeName)) { String pkg = cls.getPackage().getName(); typeName = cls.getName().substring(pkg.length() + (pkg.isEmpty() ? 0 : 1)); } if (cls.isArray()) { assert typeName.endsWith("[]"); typeName = typeName.substring(0, typeName.length() - 2) + "_array"; } return typeName; } /** * @param timeout Timeout. * @param timeUnit Time unit. * @return Converted time. */ public static int validateTimeout(int timeout, TimeUnit timeUnit) { A.ensure(timeUnit != TimeUnit.MICROSECONDS && timeUnit != TimeUnit.NANOSECONDS, "timeUnit minimal resolution is millisecond."); A.ensure(timeout >= 0, "timeout value should be non-negative."); long tmp = TimeUnit.MILLISECONDS.convert(timeout, timeUnit); return (int) tmp; } /** * @param ccfg Cache configuration. * @return {@code true} If query index must be enabled for this cache. */ public static boolean isEnabled(CacheConfiguration<?,?> ccfg) { return !F.isEmpty(ccfg.getIndexedTypes()) || !F.isEmpty(ccfg.getQueryEntities()); } /** * Discovery history size. * * @return Discovery history size. */ public static int discoveryHistorySize() { return DISCO_HIST_SIZE; } /** * Wrap schema exception if needed. * * @param e Original exception. * @return Schema exception. */ @Nullable public static SchemaOperationException wrapIfNeeded(@Nullable Exception e) { if (e == null) return null; if (e instanceof SchemaOperationException) return (SchemaOperationException)e; return new SchemaOperationException("Unexpected exception.", e); } /** * Prepare cache configuration. * * @param ccfg Cache configuration. */ @SuppressWarnings("unchecked") public static void prepareCacheConfiguration(CacheConfiguration ccfg) { assert ccfg != null; Collection<QueryEntity> entities = ccfg.getQueryEntities(); if (!F.isEmpty(entities)) { for (QueryEntity entity : entities) { if (F.isEmpty(entity.findValueType())) continue; Collection<QueryIndex> idxs = entity.getIndexes(); if (!F.isEmpty(idxs)) { for (QueryIndex idx : idxs) { if (idx.getName() == null) { String idxName = indexName(entity, idx); idx.setName(idxName); } } } } } } /** * Prepare cache configuration. * * @param ccfg Cache configuration. * @throws IgniteCheckedException If failed. */ @SuppressWarnings("unchecked") public static void validateCacheConfiguration(CacheConfiguration ccfg) throws IgniteCheckedException { assert ccfg != null; Collection<QueryEntity> entities = ccfg.getQueryEntities(); if (!F.isEmpty(entities)) { for (QueryEntity entity : entities) { if (F.isEmpty(entity.findValueType())) throw new IgniteCheckedException("Value type cannot be null or empty [cacheName=" + ccfg.getName() + ", queryEntity=" + entity + ']'); Collection<QueryIndex> idxs = entity.getIndexes(); if (!F.isEmpty(idxs)) { Set<String> idxNames = new HashSet<>(); for (QueryIndex idx : idxs) { String idxName = idx.getName(); if (idxName == null) idxName = indexName(entity, idx); assert !F.isEmpty(idxName); if (!idxNames.add(idxName)) throw new IgniteCheckedException("Duplicate index name [cacheName=" + ccfg.getName() + ", queryEntity=" + entity + ", queryIdx=" + idx + ']'); if (idx.getIndexType() == null) throw new IgniteCheckedException("Index type is not set [cacheName=" + ccfg.getName() + ", queryEntity=" + entity + ", queryIdx=" + idx + ']'); } } } } } /** * Private constructor. */ private QueryUtils() { // No-op. } /** Property used for keyFieldName or valueFieldName */ public static class KeyOrValProperty implements GridQueryProperty { /** */ boolean isKey; /** */ String name; /** */ Class<?> cls; /** */ public KeyOrValProperty(boolean key, String name, Class<?> cls) { this.isKey = key; this.name = name; this.cls = cls; } /** {@inheritDoc} */ @Override public Object value(Object key, Object val) throws IgniteCheckedException { return isKey ? key : val; } /** {@inheritDoc} */ @Override public void setValue(Object key, Object val, Object propVal) throws IgniteCheckedException { //No-op } /** {@inheritDoc} */ @Override public String name() { return name; } /** {@inheritDoc} */ @Override public Class<?> type() { return cls; } /** {@inheritDoc} */ @Override public boolean key() { return isKey; } /** {@inheritDoc} */ @Override public GridQueryProperty parent() { return null; } } }