| /* |
| * 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.cache; |
| |
| import java.io.Serializable; |
| import java.lang.reflect.Field; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.UUID; |
| import javax.cache.CacheException; |
| import org.apache.ignite.cache.query.annotations.QueryGroupIndex; |
| import org.apache.ignite.cache.query.annotations.QuerySqlField; |
| import org.apache.ignite.cache.query.annotations.QueryTextField; |
| import org.apache.ignite.internal.processors.cache.query.QueryEntityClassProperty; |
| import org.apache.ignite.internal.processors.cache.query.QueryEntityTypeDescriptor; |
| import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor; |
| import org.apache.ignite.internal.processors.query.QueryField; |
| import org.apache.ignite.internal.processors.query.QueryUtils; |
| import org.apache.ignite.internal.processors.query.schema.operation.SchemaAbstractOperation; |
| import org.apache.ignite.internal.processors.query.schema.operation.SchemaAlterTableAddColumnOperation; |
| import org.apache.ignite.internal.processors.query.schema.operation.SchemaIndexCreateOperation; |
| import org.apache.ignite.internal.util.tostring.GridToStringInclude; |
| 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.S; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| /** |
| * Query entity is a description of {@link org.apache.ignite.IgniteCache cache} entry (composed of key and value) |
| * in a way of how it must be indexed and can be queried. |
| */ |
| public class QueryEntity implements Serializable { |
| /** */ |
| private static final long serialVersionUID = 0L; |
| |
| /** Key type. */ |
| private String keyType; |
| |
| /** Value type. */ |
| private String valType; |
| |
| /** Key name. Can be used in field list to denote the key as a whole. */ |
| private String keyFieldName; |
| |
| /** Value name. Can be used in field list to denote the entire value. */ |
| private String valueFieldName; |
| |
| /** Fields available for query. A map from field name to type name. */ |
| @GridToStringInclude |
| private LinkedHashMap<String, String> fields = new LinkedHashMap<>(); |
| |
| /** Set of field names that belong to the key. */ |
| @GridToStringInclude |
| private Set<String> keyFields; |
| |
| /** Aliases. */ |
| @GridToStringInclude |
| private Map<String, String> aliases = new HashMap<>(); |
| |
| /** Collection of query indexes. */ |
| @GridToStringInclude |
| private Collection<QueryIndex> idxs; |
| |
| /** Table name. */ |
| private String tableName; |
| |
| /** Fields that must have non-null value. NB: DO NOT remove underscore to avoid clashes with QueryEntityEx. */ |
| private Set<String> _notNullFields; |
| |
| /** Fields default values. */ |
| private Map<String, Object> defaultFieldValues = new HashMap<>(); |
| |
| /** Precision(Maximum length) for fields. */ |
| private Map<String, Integer> fieldsPrecision = new HashMap<>(); |
| |
| /** Scale for fields. */ |
| private Map<String, Integer> fieldsScale = new HashMap<>(); |
| |
| /** |
| * Creates an empty query entity. |
| */ |
| public QueryEntity() { |
| // No-op constructor. |
| } |
| |
| /** |
| * Copy constructor. |
| * |
| * @param other Other entity. |
| */ |
| public QueryEntity(QueryEntity other) { |
| keyType = other.keyType; |
| valType = other.valType; |
| |
| keyFieldName = other.keyFieldName; |
| valueFieldName = other.valueFieldName; |
| |
| fields = new LinkedHashMap<>(other.fields); |
| keyFields = other.keyFields != null ? new HashSet<>(other.keyFields) : null; |
| |
| aliases = new HashMap<>(other.aliases); |
| idxs = other.idxs != null ? new ArrayList<>(other.idxs) : null; |
| |
| tableName = other.tableName; |
| |
| _notNullFields = other._notNullFields != null ? new HashSet<>(other._notNullFields) : null; |
| |
| defaultFieldValues = other.defaultFieldValues != null ? new HashMap<>(other.defaultFieldValues) |
| : new HashMap<String, Object>(); |
| |
| fieldsPrecision = other.fieldsPrecision != null ? new HashMap<>(other.fieldsPrecision) : new HashMap<>(); |
| |
| fieldsScale = other.fieldsScale != null ? new HashMap<>(other.fieldsScale) : new HashMap<>(); |
| } |
| |
| /** |
| * Creates a query entity with the given key and value types. |
| * |
| * @param keyType Key type. |
| * @param valType Value type. |
| */ |
| public QueryEntity(String keyType, String valType) { |
| this.keyType = keyType; |
| this.valType = valType; |
| } |
| |
| /** |
| * Creates a query entity with the given key and value types. |
| * |
| * @param keyCls Key type. |
| * @param valCls Value type. |
| */ |
| public QueryEntity(Class<?> keyCls, Class<?> valCls) { |
| this(convert(processKeyAndValueClasses(keyCls, valCls))); |
| } |
| |
| /** |
| * Make query entity patch. This patch can only add properties to entity and can't remove them. |
| * Other words, the patch will contain only add operations(e.g. add column, create index) and not remove ones. |
| * |
| * @param target Query entity to which this entity should be expanded. |
| * @return Patch which contains operations for expanding this entity. |
| */ |
| @NotNull public QueryEntityPatch makePatch(QueryEntity target) { |
| if (target == null) |
| return QueryEntityPatch.empty(); |
| |
| StringBuilder conflicts = new StringBuilder(); |
| |
| checkEquals(conflicts, "keyType", keyType, target.keyType); |
| checkEquals(conflicts, "valType", valType, target.valType); |
| checkEquals(conflicts, "keyFieldName", keyFieldName, target.keyFieldName); |
| checkEquals(conflicts, "valueFieldName", valueFieldName, target.valueFieldName); |
| checkEquals(conflicts, "tableName", tableName, target.tableName); |
| |
| List<QueryField> queryFieldsToAdd = checkFields(target, conflicts); |
| |
| Collection<QueryIndex> indexesToAdd = checkIndexes(target, conflicts); |
| |
| if (conflicts.length() != 0) |
| return QueryEntityPatch.conflict(tableName + " conflict: \n" + conflicts.toString()); |
| |
| Collection<SchemaAbstractOperation> patchOperations = new ArrayList<>(); |
| |
| if (!queryFieldsToAdd.isEmpty()) |
| patchOperations.add(new SchemaAlterTableAddColumnOperation( |
| UUID.randomUUID(), |
| null, |
| null, |
| tableName, |
| queryFieldsToAdd, |
| true, |
| true |
| )); |
| |
| if (!indexesToAdd.isEmpty()) { |
| for (QueryIndex index : indexesToAdd) { |
| patchOperations.add(new SchemaIndexCreateOperation( |
| UUID.randomUUID(), |
| null, |
| null, |
| tableName, |
| index, |
| true, |
| 0 |
| )); |
| } |
| } |
| |
| return QueryEntityPatch.patch(patchOperations); |
| } |
| |
| /** |
| * Comparing local fields and target fields. |
| * |
| * @param target Query entity for check. |
| * @param conflicts Storage of conflicts. |
| * @return Indexes which exist in target and not exist in local. |
| */ |
| @NotNull private Collection<QueryIndex> checkIndexes(QueryEntity target, StringBuilder conflicts) { |
| HashSet<QueryIndex> indexesToAdd = new HashSet<>(); |
| |
| Map<String, QueryIndex> currentIndexes = new HashMap<>(); |
| |
| for (QueryIndex index : getIndexes()) { |
| if (currentIndexes.put(index.getName(), index) != null) |
| throw new IllegalStateException("Duplicate key"); |
| } |
| |
| for (QueryIndex queryIndex : target.getIndexes()) { |
| if (currentIndexes.containsKey(queryIndex.getName())) { |
| checkEquals( |
| conflicts, |
| "index " + queryIndex.getName(), |
| currentIndexes.get(queryIndex.getName()), |
| queryIndex |
| ); |
| } |
| else |
| indexesToAdd.add(queryIndex); |
| } |
| return indexesToAdd; |
| } |
| |
| /** |
| * Comparing local entity fields and target entity fields. |
| * |
| * @param target Query entity for check. |
| * @param conflicts Storage of conflicts. |
| * @return Fields which exist in target and not exist in local. |
| */ |
| private List<QueryField> checkFields(QueryEntity target, StringBuilder conflicts) { |
| List<QueryField> queryFieldsToAdd = new ArrayList<>(); |
| |
| for (Map.Entry<String, String> targetField : target.getFields().entrySet()) { |
| String targetFieldName = targetField.getKey(); |
| String targetFieldType = targetField.getValue(); |
| |
| if (getFields().containsKey(targetFieldName)) { |
| checkEquals( |
| conflicts, |
| "fieldType of " + targetFieldName, |
| getFields().get(targetFieldName), |
| targetFieldType |
| ); |
| |
| checkEquals( |
| conflicts, |
| "nullable of " + targetFieldName, |
| contains(getNotNullFields(), targetFieldName), |
| contains(target.getNotNullFields(), targetFieldName) |
| ); |
| |
| checkEquals( |
| conflicts, |
| "default value of " + targetFieldName, |
| getFromMap(getDefaultFieldValues(), targetFieldName), |
| getFromMap(target.getDefaultFieldValues(), targetFieldName) |
| ); |
| |
| checkEquals(conflicts, |
| "precision of " + targetFieldName, |
| getFromMap(getFieldsPrecision(), targetFieldName), |
| getFromMap(target.getFieldsPrecision(), targetFieldName)); |
| |
| checkEquals( |
| conflicts, |
| "scale of " + targetFieldName, |
| getFromMap(getFieldsScale(), targetFieldName), |
| getFromMap(target.getFieldsScale(), targetFieldName)); |
| } |
| else { |
| Integer precision = getFromMap(target.getFieldsPrecision(), targetFieldName); |
| Integer scale = getFromMap(target.getFieldsScale(), targetFieldName); |
| |
| queryFieldsToAdd.add(new QueryField( |
| targetFieldName, |
| targetFieldType, |
| !contains(target.getNotNullFields(),targetFieldName), |
| getFromMap(target.getDefaultFieldValues(), targetFieldName), |
| precision == null ? -1 : precision, |
| scale == null ? -1 : scale |
| )); |
| } |
| } |
| |
| return queryFieldsToAdd; |
| } |
| |
| /** |
| * @param collection Collection for checking. |
| * @param elementToCheck Element for checking to containing in collection. |
| * @return {@code true} if collection contain elementToCheck. |
| */ |
| private static boolean contains(Collection<String> collection, String elementToCheck) { |
| return collection != null && collection.contains(elementToCheck); |
| } |
| |
| /** |
| * @return Value from sourceMap or null if map is null. |
| */ |
| private static <V> V getFromMap(Map<String, V> sourceMap, String key) { |
| return sourceMap == null ? null : sourceMap.get(key); |
| } |
| |
| /** |
| * Comparing two objects and add formatted text to conflicts if needed. |
| * |
| * @param conflicts Storage of conflicts resulting error message. |
| * @param name Name of comparing object. |
| * @param local Local object. |
| * @param received Received object. |
| */ |
| private <V> void checkEquals(StringBuilder conflicts, String name, V local, V received) { |
| if (!Objects.equals(local, received)) |
| conflicts.append(String.format("%s is different: local=%s, received=%s\n", name, local, received)); |
| } |
| |
| /** |
| * Gets key type for this query pair. |
| * |
| * @return Key type. |
| */ |
| public String getKeyType() { |
| return keyType; |
| } |
| |
| /** |
| * Attempts to get key type from fields in case it was not set directly. |
| * |
| * @return Key type. |
| */ |
| public String findKeyType() { |
| if (keyType != null) |
| return keyType; |
| |
| if (fields != null && keyFieldName != null) |
| return fields.get(keyFieldName); |
| |
| return null; |
| } |
| |
| /** |
| * Sets key type for this query pair. |
| * |
| * @param keyType Key type. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setKeyType(String keyType) { |
| this.keyType = keyType; |
| |
| return this; |
| } |
| |
| /** |
| * Gets value type for this query pair. |
| * |
| * @return Value type. |
| */ |
| public String getValueType() { |
| return valType; |
| } |
| |
| /** |
| * Attempts to get value type from fields in case it was not set directly. |
| * |
| * @return Value type. |
| */ |
| public String findValueType() { |
| if (valType != null) |
| return valType; |
| |
| if (fields != null && valueFieldName != null) |
| return fields.get(valueFieldName); |
| |
| return null; |
| } |
| |
| /** |
| * Sets value type for this query pair. |
| * |
| * @param valType Value type. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setValueType(String valType) { |
| this.valType = valType; |
| |
| return this; |
| } |
| |
| /** |
| * Gets query fields for this query pair. The order of fields is important as it defines the order |
| * of columns returned by the 'select *' queries. |
| * |
| * @return Field-to-type map. |
| */ |
| public LinkedHashMap<String, String> getFields() { |
| return fields; |
| } |
| |
| /** |
| * Sets query fields for this query pair. The order if fields is important as it defines the |
| * order of columns returned by the 'select *' queries. |
| * |
| * @param fields Field-to-type map. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setFields(LinkedHashMap<String, String> fields) { |
| this.fields = fields; |
| |
| return this; |
| } |
| |
| /** |
| * Gets query fields for this query pair that belongs to the key. We need this for the cases when no key-value classes |
| * are present on cluster nodes, and we need to build/modify keys and values during SQL DML operations. |
| * Thus, setting this parameter in XML is not mandatory and should be based on particular use case. |
| * |
| * @return Set of names of key fields. |
| */ |
| public Set<String> getKeyFields() { |
| return keyFields; |
| } |
| |
| /** |
| * Gets query fields for this query pair that belongs to the key. We need this for the cases when no key-value classes |
| * are present on cluster nodes, and we need to build/modify keys and values during SQL DML operations. |
| * Thus, setting this parameter in XML is not mandatory and should be based on particular use case. |
| * |
| * @param keyFields Set of names of key fields. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setKeyFields(Set<String> keyFields) { |
| this.keyFields = keyFields; |
| |
| return this; |
| } |
| |
| /** |
| * Gets key field name. |
| * |
| * @return Key name. |
| */ |
| public String getKeyFieldName() { |
| return keyFieldName; |
| } |
| |
| /** |
| * Sets key field name. |
| * |
| * @param keyFieldName Key name. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setKeyFieldName(String keyFieldName) { |
| this.keyFieldName = keyFieldName; |
| |
| return this; |
| } |
| |
| /** |
| * Get value field name. |
| * |
| * @return Value name. |
| */ |
| public String getValueFieldName() { |
| return valueFieldName; |
| } |
| |
| /** |
| * Sets value field name. |
| * |
| * @param valueFieldName value name. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setValueFieldName(String valueFieldName) { |
| this.valueFieldName = valueFieldName; |
| |
| return this; |
| } |
| |
| /** |
| * Gets a collection of index entities. |
| * |
| * @return Collection of index entities. |
| */ |
| @NotNull public Collection<QueryIndex> getIndexes() { |
| return idxs == null ? Collections.<QueryIndex>emptyList() : idxs; |
| } |
| |
| /** |
| * Gets aliases map. |
| * |
| * @return Aliases. |
| */ |
| public Map<String, String> getAliases() { |
| return aliases; |
| } |
| |
| /** |
| * Sets mapping from full property name in dot notation to an alias that will be used as SQL column name. |
| * Example: {"parent.name" -> "parentName"}. |
| * |
| * @param aliases Aliases map. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setAliases(Map<String, String> aliases) { |
| this.aliases = aliases; |
| |
| return this; |
| } |
| |
| /** |
| * Sets a collection of index entities. |
| * |
| * @param idxs Collection of index entities. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setIndexes(Collection<QueryIndex> idxs) { |
| this.idxs = idxs; |
| |
| return this; |
| } |
| |
| /** |
| * Gets table name for this query entity. |
| * |
| * @return table name |
| */ |
| public String getTableName() { |
| return tableName; |
| } |
| |
| /** |
| * Sets table name for this query entity. |
| * |
| * @param tableName table name |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setTableName(String tableName) { |
| this.tableName = tableName; |
| |
| return this; |
| } |
| |
| /** |
| * Gets names of fields that must be checked for null. |
| * |
| * @return Set of names of fields that must have non-null values. |
| */ |
| @Nullable public Set<String> getNotNullFields() { |
| return _notNullFields; |
| } |
| |
| /** |
| * Sets names of fields that must checked for null. |
| * |
| * @param notNullFields Set of names of fields that must have non-null values. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setNotNullFields(@Nullable Set<String> notNullFields) { |
| this._notNullFields = notNullFields; |
| |
| return this; |
| } |
| |
| /** |
| * @return Precision map for a fields. |
| */ |
| public Map<String, Integer> getFieldsPrecision() { |
| return fieldsPrecision; |
| } |
| |
| /** |
| * Sets fieldsPrecision map for a fields. |
| * |
| * @param fieldsPrecision Precision map for a fields. |
| * @return {@code This} for chaining. |
| */ |
| public QueryEntity setFieldsPrecision(Map<String, Integer> fieldsPrecision) { |
| this.fieldsPrecision = fieldsPrecision; |
| |
| return this; |
| } |
| |
| /** |
| * @return Scale map for a fields. |
| */ |
| public Map<String, Integer> getFieldsScale() { |
| return fieldsScale; |
| } |
| |
| /** |
| * Sets fieldsScale map for a fields. |
| * |
| * @param fieldsScale Scale map for a fields. |
| * @return {@code This} for chaining. |
| */ |
| public QueryEntity setFieldsScale(Map<String, Integer> fieldsScale) { |
| this.fieldsScale = fieldsScale; |
| |
| return this; |
| } |
| |
| /** |
| * Gets fields default values. |
| * |
| * @return Field's name to default value map. |
| */ |
| public Map<String, Object> getDefaultFieldValues() { |
| return defaultFieldValues; |
| } |
| |
| /** |
| * Sets fields default values. |
| * |
| * @param defaultFieldValues Field's name to default value map. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity setDefaultFieldValues(Map<String, Object> defaultFieldValues) { |
| this.defaultFieldValues = defaultFieldValues; |
| |
| return this; |
| } |
| |
| /** |
| * Utility method for building query entities programmatically. |
| * |
| * @param fullName Full name of the field. |
| * @param type Type of the field. |
| * @param alias Field alias. |
| * @return {@code this} for chaining. |
| */ |
| public QueryEntity addQueryField(String fullName, String type, String alias) { |
| A.notNull(fullName, "fullName"); |
| A.notNull(type, "type"); |
| |
| fields.put(fullName, type); |
| |
| if (alias != null) |
| aliases.put(fullName, alias); |
| |
| return this; |
| } |
| |
| /** |
| * @param desc Type descriptor. |
| * @return Type metadata. |
| */ |
| private static QueryEntity convert(QueryEntityTypeDescriptor desc) { |
| QueryEntity entity = new QueryEntity(); |
| |
| // Key and val types. |
| entity.setKeyType(desc.keyClass().getName()); |
| entity.setValueType(desc.valueClass().getName()); |
| |
| for (QueryEntityClassProperty prop : desc.properties().values()) |
| entity.addQueryField(prop.fullName(), U.box(prop.type()).getName(), prop.alias()); |
| |
| entity.setKeyFields(desc.keyProperties()); |
| |
| QueryIndex txtIdx = null; |
| |
| Collection<QueryIndex> idxs = new ArrayList<>(); |
| |
| for (Map.Entry<String, GridQueryIndexDescriptor> idxEntry : desc.indexes().entrySet()) { |
| GridQueryIndexDescriptor idx = idxEntry.getValue(); |
| |
| if (idx.type() == QueryIndexType.FULLTEXT) { |
| assert txtIdx == null; |
| |
| txtIdx = new QueryIndex(); |
| |
| txtIdx.setIndexType(QueryIndexType.FULLTEXT); |
| |
| txtIdx.setFieldNames(idx.fields(), true); |
| txtIdx.setName(idxEntry.getKey()); |
| } |
| else { |
| QueryIndex sortedIdx = new QueryIndex(); |
| |
| sortedIdx.setIndexType(idx.type()); |
| |
| LinkedHashMap<String, Boolean> fields = new LinkedHashMap<>(); |
| |
| for (String f : idx.fields()) |
| fields.put(f, !idx.descending(f)); |
| |
| sortedIdx.setFields(fields); |
| |
| sortedIdx.setName(idxEntry.getKey()); |
| sortedIdx.setInlineSize(idx.inlineSize()); |
| |
| idxs.add(sortedIdx); |
| } |
| } |
| |
| if (desc.valueTextIndex()) { |
| if (txtIdx == null) { |
| txtIdx = new QueryIndex(); |
| |
| txtIdx.setIndexType(QueryIndexType.FULLTEXT); |
| |
| txtIdx.setFieldNames(Arrays.asList(QueryUtils.VAL_FIELD_NAME), true); |
| } |
| else |
| txtIdx.getFields().put(QueryUtils.VAL_FIELD_NAME, true); |
| } |
| |
| if (txtIdx != null) |
| idxs.add(txtIdx); |
| |
| if (!F.isEmpty(idxs)) |
| entity.setIndexes(idxs); |
| |
| if (!F.isEmpty(desc.notNullFields())) |
| entity.setNotNullFields(desc.notNullFields()); |
| |
| if (!F.isEmpty(desc.fieldsPrecision())) |
| entity.setFieldsPrecision(desc.fieldsPrecision()); |
| |
| if (!F.isEmpty(desc.fieldsScale())) |
| entity.setFieldsScale(desc.fieldsScale()); |
| |
| return entity; |
| } |
| |
| /** |
| * @param keyCls Key class. |
| * @param valCls Value class. |
| * @return Type descriptor. |
| */ |
| private static QueryEntityTypeDescriptor processKeyAndValueClasses( |
| @NotNull Class<?> keyCls, |
| @NotNull Class<?> valCls |
| ) { |
| QueryEntityTypeDescriptor d = new QueryEntityTypeDescriptor(keyCls, valCls); |
| |
| processAnnotationsInClass(true, d.keyClass(), d, null); |
| processAnnotationsInClass(false, d.valueClass(), d, null); |
| |
| return d; |
| } |
| |
| /** |
| * Process annotations for class. |
| * |
| * @param key If given class relates to key. |
| * @param cls Class. |
| * @param type Type descriptor. |
| * @param parent Parent in case of embeddable. |
| */ |
| private static void processAnnotationsInClass(boolean key, Class<?> cls, QueryEntityTypeDescriptor type, |
| @Nullable QueryEntityClassProperty parent) { |
| if (U.isJdk(cls) || QueryUtils.isGeometryClass(cls)) { |
| if (parent == null && !key && QueryUtils.isSqlType(cls)) { // We have to index primitive _val. |
| String idxName = cls.getSimpleName() + "_" + QueryUtils.VAL_FIELD_NAME + "_idx"; |
| |
| type.addIndex(idxName, QueryUtils.isGeometryClass(cls) ? |
| QueryIndexType.GEOSPATIAL : QueryIndexType.SORTED, QueryIndex.DFLT_INLINE_SIZE); |
| |
| type.addFieldToIndex(idxName, QueryUtils.VAL_FIELD_NAME, 0, false); |
| } |
| |
| return; |
| } |
| |
| if (parent != null && parent.knowsClass(cls)) |
| throw new CacheException("Recursive reference found in type: " + cls.getName()); |
| |
| if (parent == null) { // Check class annotation at top level only. |
| QueryTextField txtAnnCls = cls.getAnnotation(QueryTextField.class); |
| |
| if (txtAnnCls != null) |
| type.valueTextIndex(true); |
| |
| QueryGroupIndex grpIdx = cls.getAnnotation(QueryGroupIndex.class); |
| |
| if (grpIdx != null) |
| type.addIndex(grpIdx.name(), QueryIndexType.SORTED, grpIdx.inlineSize()); |
| |
| QueryGroupIndex.List grpIdxList = cls.getAnnotation(QueryGroupIndex.List.class); |
| |
| if (grpIdxList != null && !F.isEmpty(grpIdxList.value())) { |
| for (QueryGroupIndex idx : grpIdxList.value()) |
| type.addIndex(idx.name(), QueryIndexType.SORTED, idx.inlineSize()); |
| } |
| } |
| |
| for (Class<?> c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) { |
| for (Field field : c.getDeclaredFields()) { |
| QuerySqlField sqlAnn = field.getAnnotation(QuerySqlField.class); |
| QueryTextField txtAnn = field.getAnnotation(QueryTextField.class); |
| |
| if (sqlAnn != null || txtAnn != null) { |
| QueryEntityClassProperty prop = new QueryEntityClassProperty(field); |
| |
| prop.parent(parent); |
| |
| // Add parent property before its possible nested properties so that |
| // resulting parent column comes before columns corresponding to those |
| // nested properties in the resulting table - that way nested |
| // properties override will happen properly (first parent, then children). |
| type.addProperty(prop, key, true); |
| |
| processAnnotation(key, sqlAnn, txtAnn, cls, c, field.getType(), prop, type); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Processes annotation at field or method. |
| * |
| * @param key If given class relates to key. |
| * @param sqlAnn SQL annotation, can be {@code null}. |
| * @param txtAnn H2 text annotation, can be {@code null}. |
| * @param cls Entity class. |
| * @param curCls Current entity class. |
| * @param fldCls Class of field or return type for method. |
| * @param prop Current property. |
| * @param desc Class description. |
| */ |
| private static void processAnnotation(boolean key, QuerySqlField sqlAnn, QueryTextField txtAnn, |
| Class<?> cls, Class<?> curCls, Class<?> fldCls, QueryEntityClassProperty prop, QueryEntityTypeDescriptor desc) { |
| if (sqlAnn != null) { |
| processAnnotationsInClass(key, fldCls, desc, prop); |
| |
| if (!sqlAnn.name().isEmpty()) |
| prop.alias(sqlAnn.name()); |
| |
| if (sqlAnn.index()) { |
| String idxName = curCls.getSimpleName() + "_" + prop.alias() + "_idx"; |
| |
| if (cls != curCls) |
| idxName = cls.getSimpleName() + "_" + idxName; |
| |
| desc.addIndex(idxName, QueryUtils.isGeometryClass(prop.type()) ? |
| QueryIndexType.GEOSPATIAL : QueryIndexType.SORTED, sqlAnn.inlineSize()); |
| |
| desc.addFieldToIndex(idxName, prop.fullName(), 0, sqlAnn.descending()); |
| } |
| |
| if (sqlAnn.notNull()) |
| desc.addNotNullField(prop.fullName()); |
| |
| if (sqlAnn.precision() != -1) |
| desc.addPrecision(prop.fullName(), sqlAnn.precision()); |
| |
| if (sqlAnn.scale() != -1) |
| desc.addScale(prop.fullName(), sqlAnn.scale()); |
| |
| if ((!F.isEmpty(sqlAnn.groups()) || !F.isEmpty(sqlAnn.orderedGroups())) |
| && sqlAnn.inlineSize() != QueryIndex.DFLT_INLINE_SIZE) { |
| throw new CacheException("Inline size cannot be set on a field with group index [" + |
| "type=" + cls.getName() + ", property=" + prop.fullName() + ']'); |
| } |
| |
| if (!F.isEmpty(sqlAnn.groups())) { |
| for (String group : sqlAnn.groups()) |
| desc.addFieldToIndex(group, prop.fullName(), 0, false); |
| } |
| |
| if (!F.isEmpty(sqlAnn.orderedGroups())) { |
| for (QuerySqlField.Group idx : sqlAnn.orderedGroups()) |
| desc.addFieldToIndex(idx.name(), prop.fullName(), idx.order(), idx.descending()); |
| } |
| } |
| |
| if (txtAnn != null) |
| desc.addFieldToTextIndex(prop.fullName()); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean equals(Object o) { |
| if (this == o) |
| return true; |
| |
| if (o == null || getClass() != o.getClass()) |
| return false; |
| |
| QueryEntity entity = (QueryEntity)o; |
| |
| return F.eq(keyType, entity.keyType) && |
| F.eq(valType, entity.valType) && |
| F.eq(keyFieldName, entity.keyFieldName) && |
| F.eq(valueFieldName, entity.valueFieldName) && |
| F.eq(fields, entity.fields) && |
| F.eq(keyFields, entity.keyFields) && |
| F.eq(aliases, entity.aliases) && |
| F.eqNotOrdered(idxs, entity.idxs) && |
| F.eq(tableName, entity.tableName) && |
| F.eq(_notNullFields, entity._notNullFields) && |
| F.eq(defaultFieldValues, entity.defaultFieldValues) && |
| F.eq(fieldsPrecision, entity.fieldsPrecision) && |
| F.eq(fieldsScale, entity.fieldsScale); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public int hashCode() { |
| return Objects.hash(keyType, valType, keyFieldName, valueFieldName, fields, keyFields, aliases, idxs, |
| tableName, _notNullFields, defaultFieldValues, fieldsPrecision, fieldsScale); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String toString() { |
| return S.toString(QueryEntity.class, this); |
| } |
| } |