| /* |
| * 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 java.lang.reflect.Method; |
| import java.math.BigDecimal; |
| import java.sql.Time; |
| import java.sql.Timestamp; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.TimeUnit; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.IgniteException; |
| import org.apache.ignite.binary.BinaryField; |
| 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.CacheDefaultBinaryAffinityKeyMapper; |
| import org.apache.ignite.internal.processors.cache.CacheObjectContext; |
| import org.apache.ignite.internal.processors.cache.DynamicCacheDescriptor; |
| 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.cache.query.IgniteQueryErrorCode; |
| 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.processors.query.schema.SchemaOperationException; |
| import org.apache.ignite.internal.util.Jsr310Java8DateTimeApiUtils; |
| 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.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| 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 { |
| /** Default schema. */ |
| public static final String DFLT_SCHEMA = "PUBLIC"; |
| |
| /** Schema for system view. */ |
| public static final String SCHEMA_SYS = "IGNITE"; |
| |
| /** 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"; |
| |
| /** Well-known template name for PARTITIONED cache. */ |
| public static final String TEMPLATE_PARTITIONED = "PARTITIONED"; |
| |
| /** Well-known template name for REPLICATED cache. */ |
| public static final String TEMPLATE_REPLICATED = "REPLICATED"; |
| |
| /** 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("org.locationtech.jts.geom.Geometry", null); |
| |
| /** */ |
| private static final Set<Class<?>> SQL_TYPES = createSqlTypes(); |
| |
| /** |
| * Creates SQL types set. |
| * |
| * @return SQL types set. |
| */ |
| @NotNull private static Set<Class<?>> createSqlTypes() { |
| Set<Class<?>> sqlClasses = new HashSet<>(Arrays.<Class<?>>asList( |
| Integer.class, |
| Boolean.class, |
| Byte.class, |
| Short.class, |
| Long.class, |
| BigDecimal.class, |
| Double.class, |
| Float.class, |
| Time.class, |
| Timestamp.class, |
| Date.class, |
| java.sql.Date.class, |
| String.class, |
| UUID.class, |
| byte[].class |
| )); |
| |
| sqlClasses.addAll(Jsr310Java8DateTimeApiUtils.jsr310ApiClasses()); |
| |
| return sqlClasses; |
| } |
| |
| /** |
| * Get table name for entity. |
| * |
| * @param entity Entity. |
| * @return Table name. |
| */ |
| public static String tableName(QueryEntity entity) { |
| String res = entity.getTableName(); |
| |
| if (res == null) { |
| String valTyp = entity.findValueType(); |
| |
| if (valTyp == null) |
| throw new IgniteException("Value type cannot be null or empty [queryEntity=" + entity + ']'); |
| |
| 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; |
| } |
| |
| /** |
| * Normalize cache query entities. |
| * |
| * @param entities Query entities. |
| * @param cfg Cache config. |
| * @return Normalized query entities. |
| */ |
| public static Collection<QueryEntity> normalizeQueryEntities(Collection<QueryEntity> entities, |
| CacheConfiguration<?, ?> cfg) { |
| Collection<QueryEntity> normalEntities = new ArrayList<>(entities.size()); |
| |
| for (QueryEntity entity : entities) { |
| if (!F.isEmpty(entity.getNotNullFields())) |
| checkNotNullAllowed(cfg); |
| |
| normalEntities.add(normalizeQueryEntity(entity, cfg.isSqlEscapeAll())); |
| } |
| |
| return normalEntities; |
| } |
| |
| /** |
| * Normalize query entity. If "escape" flag is set, nothing changes. Otherwise we convert all object names to |
| * upper case and replace inner class separator characters ('$' for Java and '.' for .NET) with underscore. |
| * |
| * @param entity Query entity. |
| * @param escape Escape flag taken form configuration. |
| * @return Normalized query entity. |
| */ |
| public static QueryEntity normalizeQueryEntity(QueryEntity entity, boolean escape) { |
| if (escape) { |
| String tblName = tableName(entity); |
| |
| entity.setTableName(tblName); |
| |
| Map<String, String> aliases = new HashMap<>(entity.getAliases()); |
| |
| for (String fieldName : entity.getFields().keySet()) { |
| String fieldAlias = entity.getAliases().get(fieldName); |
| |
| if (fieldAlias == null) { |
| fieldAlias = aliasForFieldName(fieldName); |
| |
| aliases.put(fieldName, fieldAlias); |
| } |
| } |
| |
| entity.setAliases(aliases); |
| |
| for (QueryIndex idx : entity.getIndexes()) |
| idx.setName(indexName(tblName, idx)); |
| |
| validateQueryEntity(entity); |
| |
| return entity; |
| } |
| |
| QueryEntity normalEntity = entity instanceof QueryEntityEx ? new QueryEntityEx() : new QueryEntity(); |
| |
| // Propagate plain properties. |
| normalEntity.setKeyType(entity.getKeyType()); |
| normalEntity.setValueType(entity.getValueType()); |
| normalEntity.setFields(entity.getFields()); |
| normalEntity.setKeyFields(entity.getKeyFields()); |
| normalEntity.setKeyFieldName(entity.getKeyFieldName()); |
| normalEntity.setValueFieldName(entity.getValueFieldName()); |
| normalEntity.setNotNullFields(entity.getNotNullFields()); |
| normalEntity.setDefaultFieldValues(entity.getDefaultFieldValues()); |
| normalEntity.setFieldsPrecision(entity.getFieldsPrecision()); |
| normalEntity.setFieldsScale(entity.getFieldsScale()); |
| |
| // Normalize table name. |
| String normalTblName = entity.getTableName(); |
| |
| if (normalTblName == null) |
| // Replace special characters for auto-generated table name. |
| normalTblName = normalizeObjectName(tableName(entity), true); |
| else |
| // No replaces for manually defined table. |
| normalTblName = normalizeObjectName(normalTblName, false); |
| |
| normalEntity.setTableName(normalTblName); |
| |
| // Normalize field names through aliases. |
| Map<String, String> normalAliases = new HashMap<>(normalEntity.getAliases()); |
| |
| for (String fieldName : normalEntity.getFields().keySet()) { |
| String fieldAlias = entity.getAliases().get(fieldName); |
| |
| if (fieldAlias == null) |
| fieldAlias = aliasForFieldName(fieldName); |
| |
| assert fieldAlias != null; |
| |
| normalAliases.put(fieldName, normalizeObjectName(fieldAlias, false)); |
| } |
| |
| normalEntity.setAliases(normalAliases); |
| |
| // Normalize indexes. |
| Collection<QueryIndex> normalIdxs = new LinkedList<>(); |
| |
| for (QueryIndex idx : entity.getIndexes()) { |
| QueryIndex normalIdx = new QueryIndex(); |
| |
| normalIdx.setFields(idx.getFields()); |
| normalIdx.setIndexType(idx.getIndexType()); |
| normalIdx.setInlineSize(idx.getInlineSize()); |
| |
| normalIdx.setName(normalizeObjectName(indexName(normalTblName, idx), false)); |
| |
| normalIdxs.add(normalIdx); |
| } |
| |
| normalEntity.setIndexes(normalIdxs); |
| |
| validateQueryEntity(normalEntity); |
| |
| return normalEntity; |
| } |
| |
| /** |
| * Stores rule for constructing schemaName according to cache configuration. |
| * |
| * @param cacheName Cache name. |
| * @param schemaName Schema name. |
| * @return Proper schema name according to ANSI-99 standard. |
| */ |
| public static String normalizeSchemaName(String cacheName, @Nullable String schemaName) { |
| boolean escape = false; |
| |
| String res = schemaName; |
| |
| if (res == null) { |
| res = cacheName; |
| |
| // If schema name is not set explicitly, we will use escaped cache name. The reason is that cache name |
| // could contain weird characters, such as underscores, dots or non-Latin stuff, which are invalid from |
| // SQL synthax perspective. We do not want node to fail on startup due to this. |
| escape = true; |
| } |
| else { |
| if (res.startsWith("\"") && res.endsWith("\"")) { |
| res = res.substring(1, res.length() - 1); |
| |
| escape = true; |
| } |
| } |
| |
| if (!escape) |
| res = normalizeObjectName(res, false); |
| |
| return res; |
| } |
| |
| /** |
| * Get alias for the field name (i.e. last part of the property). |
| * |
| * @param fieldName Field name. |
| * @return Alias. |
| */ |
| private static String aliasForFieldName(String fieldName) { |
| int idx = fieldName.lastIndexOf('.'); |
| |
| if (idx >= 0) |
| fieldName = fieldName.substring(idx + 1); |
| |
| return fieldName; |
| } |
| |
| /** |
| * Normalize object name. |
| * |
| * @param str String. |
| * @param replace Whether to perform replace of special characters. |
| * @return Escaped string. |
| */ |
| public static @Nullable String normalizeObjectName(@Nullable String str, boolean replace) { |
| if (str == null) |
| return null; |
| |
| if (replace) |
| str = str.replace('.', '_').replace('$', '_'); |
| |
| return str.toUpperCase(); |
| } |
| |
| /** |
| * Create type candidate for query entity. |
| * |
| * @param cacheName Cache name. |
| * @param schemaName Schema name. |
| * @param cctx Cache context. |
| * @param qryEntity Query entity. |
| * @param mustDeserializeClss Classes which must be deserialized. |
| * @param escape Escape flag. |
| * @return Type candidate. |
| * @throws IgniteCheckedException If failed. |
| */ |
| public static QueryTypeCandidate typeForQueryEntity(String cacheName, String schemaName, GridCacheContext cctx, |
| QueryEntity qryEntity, List<Class<?>> mustDeserializeClss, boolean escape) 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(cacheName, coCtx); |
| |
| desc.schemaName(schemaName); |
| |
| 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. |
| // We better box these types - otherwise, if user provides, say, raw 'byte' for |
| // key or value (which they could), we'll deem key or value as Object which clearly is not right. |
| Class<?> keyCls = U.box(U.classForName(qryEntity.findKeyType(), null, true)); |
| Class<?> valCls = U.box(U.classForName(qryEntity.findValueType(), null, true)); |
| |
| // 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; |
| |
| int valTypeId = ctx.cacheObjects().typeId(qryEntity.findValueType()); |
| |
| if (valCls == null || (binaryEnabled && !keyOrValMustDeserialize)) { |
| processBinaryMeta(ctx, qryEntity, desc); |
| |
| typeId = new QueryTypeIdKey(cacheName, valTypeId); |
| |
| if (valCls != null) |
| altTypeId = new QueryTypeIdKey(cacheName, valCls); |
| |
| String affField = null; |
| |
| // Need to setup affinity key for distributed joins. |
| String keyType = qryEntity.getKeyType(); |
| |
| if (!cctx.customAffinityMapper() && keyType != null) { |
| if (coCtx != null) { |
| CacheDefaultBinaryAffinityKeyMapper mapper = |
| (CacheDefaultBinaryAffinityKeyMapper)coCtx.defaultAffMapper(); |
| |
| BinaryField field = mapper.affinityKeyField(keyType); |
| |
| if (field != null) |
| affField = field.name(); |
| } |
| } |
| |
| if (affField != null) { |
| if (!escape) |
| affField = normalizeObjectName(affField, false); |
| |
| 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) { |
| if (!escape) |
| affField = normalizeObjectName(affField, false); |
| |
| desc.affinityKey(affField); |
| } |
| } |
| |
| typeId = new QueryTypeIdKey(cacheName, valCls); |
| altTypeId = new QueryTypeIdKey(cacheName, valTypeId); |
| } |
| |
| desc.typeId(valTypeId); |
| |
| 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 { |
| LinkedHashMap<String, String> fields = qryEntity.getFields(); |
| Set<String> keyFields = qryEntity.getKeyFields(); |
| Set<String> notNulls = qryEntity.getNotNullFields(); |
| Map<String, Object> dlftVals = qryEntity.getDefaultFieldValues(); |
| Map<String, Integer> precision = qryEntity.getFieldsPrecision(); |
| Map<String, Integer> scale = qryEntity.getFieldsScale(); |
| |
| // 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 (!fields.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 : fields.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); |
| |
| boolean notNull = notNulls != null && notNulls.contains(entry.getKey()); |
| |
| Object dfltVal = dlftVals != null ? dlftVals.get(entry.getKey()) : null; |
| |
| QueryBinaryProperty prop = buildBinaryProperty(ctx, entry.getKey(), |
| U.classForName(entry.getValue(), Object.class, true), |
| d.aliases(), isKeyField, notNull, dfltVal, |
| precision == null ? -1 : precision.getOrDefault(entry.getKey(), -1), |
| scale == null ? -1 : scale.getOrDefault(entry.getKey(), -1)); |
| |
| d.addProperty(prop, false); |
| } |
| |
| String keyFieldName = qryEntity.getKeyFieldName(); |
| |
| if (keyFieldName == null) |
| keyFieldName = KEY_FIELD_NAME; |
| |
| if (!F.isEmpty(precision) && precision.containsKey(keyFieldName) && |
| !fields.containsKey(keyFieldName)) { |
| addKeyValueValidationProperty(ctx, qryEntity, d, keyFieldName, true); |
| } |
| |
| String valFieldName = qryEntity.getValueFieldName(); |
| |
| if (valFieldName == null) |
| valFieldName = VAL_FIELD_NAME; |
| |
| if (!F.isEmpty(precision) && precision.containsKey(valFieldName) && |
| !fields.containsKey(valFieldName)) { |
| addKeyValueValidationProperty(ctx, qryEntity, d, valFieldName, false); |
| } |
| |
| processIndexes(qryEntity, d); |
| } |
| |
| /** |
| * Add validate property to QueryTypeDescriptor. |
| * |
| * @param ctx Kernel context. |
| * @param qryEntity Query entity. |
| * @param d Descriptor. |
| * @param name Field name. |
| * @throws IgniteCheckedException |
| */ |
| private static void addKeyValueValidationProperty(GridKernalContext ctx, QueryEntity qryEntity, QueryTypeDescriptorImpl d, |
| String name, boolean isKey) throws IgniteCheckedException { |
| |
| Map<String, Object> dfltVals = qryEntity.getDefaultFieldValues(); |
| Map<String, Integer> precision = qryEntity.getFieldsPrecision(); |
| Map<String, Integer> scale = qryEntity.getFieldsScale(); |
| |
| String typeName = isKey ? qryEntity.getKeyType() : qryEntity.getValueType(); |
| |
| Object dfltVal = dfltVals.get(name); |
| |
| QueryBinaryProperty prop = buildBinaryProperty( |
| ctx, |
| name, |
| U.classForName(typeName, Object.class, true), |
| d.aliases(), |
| isKey, |
| true, |
| dfltVal, |
| precision == null ? -1 : precision.getOrDefault(name, -1), |
| scale == null ? -1 : scale.getOrDefault(name, -1)); |
| |
| d.addProperty(prop, true, false); |
| } |
| |
| /** |
| * 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 { |
| Set<String> notNulls = qryEntity.getNotNullFields(); |
| |
| 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(), |
| notNulls != null && notNulls.contains(entry.getKey()), |
| 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. |
| * @param notNull {@code true} if {@code null} value is not allowed. |
| * @param dlftVal Default value. |
| * @param precision Precision. |
| * @param scale Scale. |
| * @return Binary property. |
| */ |
| public static QueryBinaryProperty buildBinaryProperty(GridKernalContext ctx, String pathStr, |
| Class<?> resType, Map<String, String> aliases, @Nullable Boolean isKeyField, boolean notNull, Object dlftVal, |
| int precision, int scale) { |
| 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, notNull, dlftVal, |
| precision, scale); |
| } |
| |
| return res; |
| } |
| |
| /** |
| * @param keyCls Key class. |
| * @param valCls Value class. |
| * @param pathStr Path string. |
| * @param resType Result type. |
| * @param aliases Aliases. |
| * @param notNull {@code true} if {@code null} value is not allowed. |
| * @param coCtx Cache object context. |
| * @return Class property. |
| * @throws IgniteCheckedException If failed. |
| */ |
| public static QueryClassProperty buildClassProperty(Class<?> keyCls, Class<?> valCls, String pathStr, |
| Class<?> resType, Map<String,String> aliases, boolean notNull, CacheObjectContext coCtx) |
| throws IgniteCheckedException { |
| QueryClassProperty res = buildClassProperty( |
| true, |
| keyCls, |
| pathStr, |
| resType, |
| aliases, |
| notNull, |
| coCtx); |
| |
| if (res == null) // We check key before value consistently with BinaryProperty. |
| res = buildClassProperty(false, valCls, pathStr, resType, aliases, notNull, 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. |
| * @param notNull {@code true} if {@code null} value is not allowed. |
| * @param coCtx Cache object context. |
| * @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, boolean notNull, |
| 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, |
| notNull, |
| 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. |
| * @param notNull {@code true} if {@code null} value is not allowed. |
| * @param coCtx Cache object context. |
| * @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, boolean notNull, 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, notNull, 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); |
| } |
| |
| /** |
| * Check given {@link CacheConfiguration} for conflicts in table and index names from any query entities |
| * found in collection of {@link DynamicCacheDescriptor}s and belonging to the same schema. |
| * |
| * @param ccfg New cache configuration. |
| * @param descs Cache descriptors. |
| * @return Exception message describing found conflict or {@code null} if none found. |
| */ |
| public static SchemaOperationException checkQueryEntityConflicts(CacheConfiguration<?, ?> ccfg, |
| Collection<DynamicCacheDescriptor> descs) { |
| String schema = QueryUtils.normalizeSchemaName(ccfg.getName(), ccfg.getSqlSchema()); |
| |
| Set<String> idxNames = new HashSet<>(); |
| |
| Set<String> tblNames = new HashSet<>(); |
| |
| for (DynamicCacheDescriptor desc : descs) { |
| if (F.eq(ccfg.getName(), desc.cacheName())) |
| continue; |
| |
| String descSchema = QueryUtils.normalizeSchemaName(desc.cacheName(), |
| desc.cacheConfiguration().getSqlSchema()); |
| |
| if (!F.eq(schema, descSchema)) |
| continue; |
| |
| for (QueryEntity e : desc.schema().entities()) { |
| tblNames.add(e.getTableName()); |
| |
| for (QueryIndex idx : e.getIndexes()) |
| idxNames.add(idx.getName()); |
| } |
| } |
| |
| for (QueryEntity e : ccfg.getQueryEntities()) { |
| if (!tblNames.add(e.getTableName())) |
| return new SchemaOperationException(SchemaOperationException.CODE_TABLE_EXISTS, e.getTableName()); |
| |
| for (QueryIndex idx : e.getIndexes()) |
| if (!idxNames.add(idx.getName())) |
| return new SchemaOperationException(SchemaOperationException.CODE_INDEX_EXISTS, idx.getName()); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Validate query entity. |
| * |
| * @param entity Entity. |
| */ |
| private static void validateQueryEntity(QueryEntity entity) { |
| if (F.isEmpty(entity.findValueType())) |
| throw new IgniteException("Value type cannot be null or empty [queryEntity=" + entity + ']'); |
| |
| String keyFieldName = entity.getKeyFieldName(); |
| |
| if (keyFieldName != null && !entity.getFields().containsKey(keyFieldName)) { |
| throw new IgniteException("Key field is not in the field list [queryEntity=" + entity + |
| ", keyFieldName=" + keyFieldName + "]"); |
| } |
| |
| String valFieldName = entity.getValueFieldName(); |
| |
| if (valFieldName != null && !entity.getFields().containsKey(valFieldName)) { |
| throw new IgniteException("Value field is not in the field list [queryEntity=" + entity + |
| ", valFieldName=" + valFieldName + "]"); |
| } |
| |
| 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 IgniteException("Duplicate index name [queryEntity=" + entity + |
| ", queryIdx=" + idx + ']'); |
| |
| if (idx.getIndexType() == null) |
| throw new IgniteException("Index type is not set [queryEntity=" + entity + |
| ", queryIdx=" + idx + ']'); |
| } |
| } |
| |
| Map<String, Object> dfltVals = entity.getDefaultFieldValues(); |
| Map<String, Integer> precision = entity.getFieldsPrecision(); |
| |
| if (!F.isEmpty(precision)) { |
| for (String fld : precision.keySet()) { |
| if (!dfltVals.containsKey(fld)) |
| continue; |
| |
| Object dfltVal = dfltVals.get(fld); |
| |
| if (dfltVal == null) |
| continue; |
| |
| if (dfltVal.toString().length() > precision.get(fld)) { |
| throw new IgniteSQLException("Default value '" + dfltVal + |
| "' is longer than maximum length " + precision.get(fld)); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Construct cache name for table. |
| * |
| * @param schemaName Schema name. |
| * @param tblName Table name. |
| * @return Cache name. |
| */ |
| public static String createTableCacheName(String schemaName, String tblName) { |
| return "SQL_" + schemaName + "_" + tblName; |
| } |
| |
| /** |
| * Construct value type name for table. |
| * |
| * @param schemaName Schema name. |
| * @param tblName Table name. |
| * @return Value type name. |
| */ |
| public static String createTableValueTypeName(String schemaName, String tblName) { |
| return createTableCacheName(schemaName, tblName) + "_" + UUID.randomUUID().toString().replace("-", "_"); |
| } |
| |
| /** |
| * Construct key type name for table. |
| * |
| * @param valTypeName Value type name. |
| * @return Key type name. |
| */ |
| public static String createTableKeyTypeName(String valTypeName) { |
| return valTypeName + "_KEY"; |
| } |
| |
| /** |
| * Copy query entity. |
| * |
| * @param entity Query entity. |
| * @return Copied entity. |
| */ |
| public static QueryEntity copy(QueryEntity entity) { |
| QueryEntity res; |
| |
| if (entity instanceof QueryEntityEx) |
| res = new QueryEntityEx(entity); |
| else |
| res = new QueryEntity(entity); |
| |
| return res; |
| } |
| |
| /** |
| * Performs checks to forbid cache configurations that are not compatible with NOT NULL query fields. |
| * See {@link QueryEntity#setNotNullFields(Set)}. |
| * |
| * @param cfg Cache configuration. |
| */ |
| public static void checkNotNullAllowed(CacheConfiguration cfg) { |
| if (cfg.isReadThrough()) |
| throw new IgniteSQLException("NOT NULL constraint is not supported when CacheConfiguration.readThrough " + |
| "is enabled.", IgniteQueryErrorCode.UNSUPPORTED_OPERATION); |
| |
| if (cfg.getInterceptor() != null) |
| throw new IgniteSQLException("NOT NULL constraint is not supported when CacheConfiguration.interceptor " + |
| "is set.", IgniteQueryErrorCode.UNSUPPORTED_OPERATION); |
| } |
| |
| /** |
| * Checks if given column can be removed from table using its {@link QueryEntity}. |
| * |
| * @param entity Query entity. |
| * @param fieldName Name of the field of the key or value object. |
| * @param colName Name of the column. |
| * @return {@code null} if it's OK to remove the column and exception otherwise. |
| */ |
| public static SchemaOperationException validateDropColumn(QueryEntity entity, String fieldName, String colName) { |
| if (F.eq(fieldName, entity.getKeyFieldName()) || KEY_FIELD_NAME.equalsIgnoreCase(fieldName)) |
| return new SchemaOperationException("Cannot drop column \"" + colName + |
| "\" because it represents an entire cache key"); |
| |
| if (F.eq(fieldName, entity.getValueFieldName()) || VAL_FIELD_NAME.equalsIgnoreCase(fieldName)) |
| return new SchemaOperationException("Cannot drop column \"" + colName + |
| "\" because it represents an entire cache value"); |
| |
| Set<String> keyFields = entity.getKeyFields(); |
| |
| if (keyFields != null && keyFields.contains(fieldName)) |
| return new SchemaOperationException("Cannot drop column \"" + colName + |
| "\" because it is a part of a cache key"); |
| |
| Collection<QueryIndex> indexes = entity.getIndexes(); |
| |
| if (indexes != null) { |
| for (QueryIndex idxDesc : indexes) { |
| if (idxDesc.getFields().containsKey(fieldName)) |
| return new SchemaOperationException("Cannot drop column \"" + colName + |
| "\" because an index exists (\"" + idxDesc.getName() + "\") that uses the column."); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Checks if given column can be removed from the table using its {@link GridQueryTypeDescriptor}. |
| * |
| * @param type Type descriptor. |
| * @param colName Name of the column. |
| * @return {@code null} if it's OK to remove the column and exception otherwise. |
| */ |
| public static SchemaOperationException validateDropColumn(GridQueryTypeDescriptor type, String colName) { |
| if (F.eq(colName, type.keyFieldName()) || KEY_FIELD_NAME.equalsIgnoreCase(colName)) |
| return new SchemaOperationException("Cannot drop column \"" + colName + |
| "\" because it represents an entire cache key"); |
| |
| if (F.eq(colName, type.valueFieldName()) || VAL_FIELD_NAME.equalsIgnoreCase(colName)) |
| return new SchemaOperationException("Cannot drop column \"" + colName + |
| "\" because it represents an entire cache value"); |
| |
| GridQueryProperty prop = type.property(colName); |
| |
| if (prop != null && prop.key()) |
| return new SchemaOperationException("Cannot drop column \"" + colName + |
| "\" because it is a part of a cache key"); |
| |
| Collection<GridQueryIndexDescriptor> indexes = type.indexes().values(); |
| |
| for (GridQueryIndexDescriptor idxDesc : indexes) { |
| if (idxDesc.fields().contains(colName)) |
| return new SchemaOperationException("Cannot drop column \"" + colName + |
| "\" because an index exists (\"" + idxDesc.name() + "\") that uses the column."); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean notNull() { |
| return true; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public Object defaultValue() { |
| return null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public int precision() { |
| return -1; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public int scale() { |
| return -1; |
| } |
| } |
| } |