| /* |
| * 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.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.cache.QueryIndexType; |
| import org.apache.ignite.internal.processors.cache.CacheObject; |
| import org.apache.ignite.internal.processors.cache.CacheObjectContext; |
| import org.apache.ignite.internal.processors.cache.KeyCacheObject; |
| import org.apache.ignite.internal.util.tostring.GridToStringExclude; |
| 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.jetbrains.annotations.Nullable; |
| |
| import static org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.NULL_KEY; |
| import static org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.NULL_VALUE; |
| import static org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.TOO_LONG_KEY; |
| import static org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.TOO_LONG_VALUE; |
| import static org.apache.ignite.internal.processors.query.QueryUtils.KEY_FIELD_NAME; |
| import static org.apache.ignite.internal.processors.query.QueryUtils.VAL_FIELD_NAME; |
| |
| /** |
| * Descriptor of type. |
| */ |
| public class QueryTypeDescriptorImpl implements GridQueryTypeDescriptor { |
| /** Cache name. */ |
| private final String cacheName; |
| |
| /** */ |
| private String name; |
| |
| /** Schema name. */ |
| private String schemaName; |
| |
| /** */ |
| private String tblName; |
| |
| /** Value field names and types with preserved order. */ |
| @GridToStringInclude |
| private final LinkedHashMap<String, Class<?>> fields = new LinkedHashMap<>(); |
| |
| /** */ |
| @GridToStringExclude |
| private final Map<String, GridQueryProperty> props = new HashMap<>(); |
| |
| /** Map with upper cased property names to help find properties based on SQL INSERT and MERGE queries. */ |
| private final Map<String, GridQueryProperty> uppercaseProps = new HashMap<>(); |
| |
| /** Mutex for operations on indexes. */ |
| private final Object idxMux = new Object(); |
| |
| /** */ |
| @GridToStringInclude |
| private final Map<String, QueryIndexDescriptorImpl> idxs = new HashMap<>(); |
| |
| /** Aliases. */ |
| private Map<String, String> aliases; |
| |
| /** */ |
| private QueryIndexDescriptorImpl fullTextIdx; |
| |
| /** */ |
| private Class<?> keyCls; |
| |
| /** */ |
| private Class<?> valCls; |
| |
| /** */ |
| private String keyTypeName; |
| |
| /** */ |
| private String valTypeName; |
| |
| /** */ |
| private boolean valTextIdx; |
| |
| /** */ |
| private int typeId; |
| |
| /** */ |
| private String affKey; |
| |
| /** */ |
| private String keyFieldName; |
| |
| /** */ |
| private String valFieldName; |
| |
| /** Obsolete. */ |
| private volatile boolean obsolete; |
| |
| /** */ |
| private List<GridQueryProperty> validateProps; |
| |
| /** */ |
| private List<GridQueryProperty> propsWithDefaultValue; |
| |
| /** */ |
| @Nullable private CacheObjectContext coCtx; |
| |
| /** |
| * Constructor. |
| * |
| * @param cacheName Cache name. |
| * @param coCtx Cache object context. |
| */ |
| public QueryTypeDescriptorImpl(String cacheName, @Nullable CacheObjectContext coCtx) { |
| this.cacheName = cacheName; |
| this.coCtx = coCtx; |
| } |
| |
| /** |
| * @return Cache name. |
| */ |
| public String cacheName() { |
| return cacheName; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String name() { |
| return name; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String schemaName() { |
| return schemaName; |
| } |
| |
| /** |
| * Sets type name. |
| * |
| * @param name Name. |
| */ |
| public void name(String name) { |
| this.name = name; |
| } |
| |
| /** |
| * Gets table name for type. |
| * @return Table name. |
| */ |
| @Override public String tableName() { |
| return tblName != null ? tblName : name; |
| } |
| |
| /** |
| * Sets table name for type. |
| * |
| * @param tblName Table name. |
| */ |
| public void tableName(String tblName) { |
| this.tblName = tblName; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public LinkedHashMap<String, Class<?>> fields() { |
| return fields; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public GridQueryProperty property(String name) { |
| GridQueryProperty res = props.get(name); |
| |
| if (res == null) |
| res = uppercaseProps.get(name.toUpperCase()); |
| |
| return res; |
| } |
| |
| /** |
| * @return Properties. |
| */ |
| public Map<String, GridQueryProperty> properties() { |
| return props; |
| } |
| |
| /** {@inheritDoc} */ |
| @SuppressWarnings("unchecked") |
| @Override public <T> T value(String field, Object key, Object val) throws IgniteCheckedException { |
| assert field != null; |
| |
| GridQueryProperty prop = property(field); |
| |
| if (prop == null) |
| throw new IgniteCheckedException("Failed to find field '" + field + "' in type '" + name + "'."); |
| |
| return (T)prop.value(key, val); |
| } |
| |
| /** {@inheritDoc} */ |
| @SuppressWarnings("unchecked") |
| @Override public void setValue(String field, Object key, Object val, Object propVal) |
| throws IgniteCheckedException { |
| assert field != null; |
| |
| GridQueryProperty prop = property(field); |
| |
| if (prop == null) |
| throw new IgniteCheckedException("Failed to find field '" + field + "' in type '" + name + "'."); |
| |
| prop.setValue(key, val, propVal); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public Map<String, GridQueryIndexDescriptor> indexes() { |
| synchronized (idxMux) { |
| return Collections.<String, GridQueryIndexDescriptor>unmodifiableMap(idxs); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public int typeId() { |
| return typeId; |
| } |
| |
| /** |
| * @param typeId Type ID. |
| */ |
| public void typeId(int typeId) { |
| this.typeId = typeId; |
| } |
| |
| /** |
| * Get index by name. |
| * |
| * @param name Name. |
| * @return Index. |
| */ |
| @Nullable public QueryIndexDescriptorImpl index(String name) { |
| synchronized (idxMux) { |
| return idxs.get(name); |
| } |
| } |
| |
| /** |
| * @return Raw index descriptors. |
| */ |
| public Collection<QueryIndexDescriptorImpl> indexes0() { |
| synchronized (idxMux) { |
| return new ArrayList<>(idxs.values()); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public GridQueryIndexDescriptor textIndex() { |
| return fullTextIdx; |
| } |
| |
| /** |
| * Add index. |
| * |
| * @param idx Index. |
| * @throws IgniteCheckedException If failed. |
| */ |
| public void addIndex(QueryIndexDescriptorImpl idx) throws IgniteCheckedException { |
| synchronized (idxMux) { |
| if (idxs.put(idx.name(), idx) != null) |
| throw new IgniteCheckedException("Index with name '" + idx.name() + "' already exists."); |
| } |
| } |
| |
| /** |
| * Drop index. |
| * |
| * @param idxName Index name. |
| */ |
| public void dropIndex(String idxName) { |
| synchronized (idxMux) { |
| idxs.remove(idxName); |
| } |
| } |
| |
| /** |
| * Chedk if particular field exists. |
| * |
| * @param field Field. |
| * @return {@code True} if exists. |
| */ |
| public boolean hasField(String field) { |
| return props.containsKey(field) || QueryUtils.VAL_FIELD_NAME.equalsIgnoreCase(field); |
| } |
| |
| /** |
| * Adds field to text index. |
| * |
| * @param field Field name. |
| * @throws IgniteCheckedException If failed. |
| */ |
| public void addFieldToTextIndex(String field) throws IgniteCheckedException { |
| if (fullTextIdx == null) |
| fullTextIdx = new QueryIndexDescriptorImpl(this, null, QueryIndexType.FULLTEXT, 0); |
| |
| fullTextIdx.addField(field, 0, false); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public Class<?> valueClass() { |
| return valCls; |
| } |
| |
| /** |
| * Sets value class. |
| * |
| * @param valCls Value class. |
| */ |
| public void valueClass(Class<?> valCls) { |
| A.notNull(valCls, "Value class must not be null"); |
| this.valCls = valCls; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public Class<?> keyClass() { |
| return keyCls; |
| } |
| |
| /** |
| * Set key class. |
| * |
| * @param keyCls Key class. |
| */ |
| public void keyClass(Class<?> keyCls) { |
| this.keyCls = keyCls; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String keyTypeName() { |
| return keyTypeName; |
| } |
| |
| /** |
| * Set key type name. |
| * |
| * @param keyTypeName Key type name. |
| */ |
| public void keyTypeName(String keyTypeName) { |
| this.keyTypeName = keyTypeName; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String valueTypeName() { |
| return valTypeName; |
| } |
| |
| /** |
| * Set value type name. |
| * |
| * @param valTypeName Value type name. |
| */ |
| public void valueTypeName(String valTypeName) { |
| this.valTypeName = valTypeName; |
| } |
| |
| /** |
| * Adds property to the type descriptor. |
| * |
| * @param prop Property. |
| * @param failOnDuplicate Fail on duplicate flag. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public void addProperty(GridQueryProperty prop, boolean failOnDuplicate) throws IgniteCheckedException { |
| addProperty(prop, failOnDuplicate, true); |
| } |
| |
| /** |
| * Adds property to the type descriptor. |
| * |
| * @param prop Property. |
| * @param failOnDuplicate Fail on duplicate flag. |
| * @param isField {@code True} if {@code prop} if field, {@code False} if prop is "_KEY" or "_VAL". |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public void addProperty(GridQueryProperty prop, boolean failOnDuplicate, boolean isField) |
| throws IgniteCheckedException { |
| String name = prop.name(); |
| |
| if (props.put(name, prop) != null && failOnDuplicate) |
| throw new IgniteCheckedException("Property with name '" + name + "' already exists."); |
| |
| if (uppercaseProps.put(name.toUpperCase(), prop) != null && failOnDuplicate) |
| throw new IgniteCheckedException("Property with upper cased name '" + name + "' already exists."); |
| |
| if (prop.notNull()) { |
| if (validateProps == null) |
| validateProps = new ArrayList<>(); |
| |
| validateProps.add(prop); |
| } |
| else if (prop.precision() != -1) { |
| if (validateProps == null) |
| validateProps = new ArrayList<>(); |
| |
| validateProps.add(prop); |
| } |
| |
| if (prop.defaultValue() != null) { |
| if (propsWithDefaultValue == null) |
| propsWithDefaultValue = new ArrayList<>(); |
| |
| propsWithDefaultValue.add(prop); |
| } |
| |
| if (isField) |
| fields.put(name, prop.type()); |
| } |
| |
| /** |
| * Removes a property with specified name. |
| * |
| * @param name Name of a property to remove. |
| */ |
| public void removeProperty(String name) throws IgniteCheckedException { |
| GridQueryProperty prop = props.remove(name); |
| |
| if (prop == null) |
| throw new IgniteCheckedException("Property with name '" + name + "' does not exist."); |
| |
| if (validateProps != null) |
| validateProps.remove(prop); |
| |
| uppercaseProps.remove(name.toUpperCase()); |
| |
| fields.remove(name); |
| } |
| |
| /** |
| * @param schemaName Schema name. |
| */ |
| public void schemaName(String schemaName) { |
| this.schemaName = schemaName; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean valueTextIndex() { |
| return valTextIdx; |
| } |
| |
| /** |
| * Sets if this value should be text indexed. |
| * |
| * @param valTextIdx Flag value. |
| */ |
| public void valueTextIndex(boolean valTextIdx) { |
| this.valTextIdx = valTextIdx; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String affinityKey() { |
| return affKey; |
| } |
| |
| /** |
| * @param affKey Affinity key field. |
| */ |
| public void affinityKey(String affKey) { |
| this.affKey = affKey; |
| } |
| |
| /** |
| * @return Aliases. |
| */ |
| public Map<String, String> aliases() { |
| return aliases != null ? aliases : Collections.<String, String>emptyMap(); |
| } |
| |
| /** |
| * @param aliases Aliases. |
| */ |
| public void aliases(Map<String, String> aliases) { |
| this.aliases = aliases; |
| } |
| |
| /** |
| * @return {@code True} if obsolete. |
| */ |
| public boolean obsolete() { |
| return obsolete; |
| } |
| |
| /** |
| * Mark index as obsolete. |
| */ |
| public void markObsolete() { |
| obsolete = true; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String toString() { |
| return S.toString(QueryTypeDescriptorImpl.class, this); |
| } |
| |
| /** |
| * Sets key field name. |
| * @param keyFieldName Key field name. |
| */ |
| void keyFieldName(String keyFieldName) { |
| this.keyFieldName = keyFieldName; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String keyFieldName() { |
| return keyFieldName; |
| } |
| |
| /** |
| * Sets value field name. |
| * @param valFieldName value field name. |
| */ |
| void valueFieldName(String valFieldName) { |
| this.valFieldName = valFieldName; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String valueFieldName() { |
| return valFieldName; |
| } |
| |
| /** {@inheritDoc} */ |
| @Nullable @Override public String keyFieldAlias() { |
| return keyFieldName != null ? aliases.get(keyFieldName) : null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Nullable @Override public String valueFieldAlias() { |
| return valFieldName != null ? aliases.get(valFieldName) : null; |
| } |
| |
| /** {@inheritDoc} */ |
| @SuppressWarnings("ForLoopReplaceableByForEach") |
| @Override public void validateKeyAndValue(Object key, Object val) throws IgniteCheckedException { |
| if (F.isEmpty(validateProps)) |
| return; |
| |
| for (int i = 0; i < validateProps.size(); ++i) { |
| GridQueryProperty prop = validateProps.get(i); |
| |
| Object propVal; |
| |
| boolean isKey = false; |
| |
| if (F.eq(prop.name(), keyFieldName) || (keyFieldName == null && F.eq(prop.name(), KEY_FIELD_NAME))) { |
| propVal = key instanceof KeyCacheObject && coCtx != null ? |
| ((KeyCacheObject)key).value(coCtx, true) : key; |
| |
| isKey = true; |
| } |
| else if (F.eq(prop.name(), valFieldName) || (valFieldName == null && F.eq(prop.name(), VAL_FIELD_NAME))) { |
| propVal = val instanceof CacheObject && coCtx != null ? |
| ((CacheObject)val).value(coCtx, true) : val; |
| } |
| else { |
| propVal = prop.value(key, val); |
| } |
| |
| if (propVal == null && prop.notNull()) { |
| throw new IgniteSQLException("Null value is not allowed for column '" + prop.name() + "'", |
| isKey ? NULL_KEY : NULL_VALUE); |
| } |
| |
| if (prop.precision() != -1 && |
| propVal != null && |
| String.class == propVal.getClass() && |
| ((String)propVal).length() > prop.precision()) { |
| throw new IgniteSQLException("Value for a column '" + prop.name() + "' is too long. " + |
| "Maximum length: " + prop.precision() + ", actual length: " + ((CharSequence)propVal).length(), |
| isKey ? TOO_LONG_KEY : TOO_LONG_VALUE); |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @SuppressWarnings("ForLoopReplaceableByForEach") |
| @Override public void setDefaults(Object key, Object val) throws IgniteCheckedException { |
| if (F.isEmpty(propsWithDefaultValue)) |
| return; |
| |
| for (int i = 0; i < propsWithDefaultValue.size(); ++i) { |
| GridQueryProperty prop = propsWithDefaultValue.get(i); |
| |
| prop.setValue(key, val, prop.defaultValue()); |
| } |
| } |
| } |