blob: 2eaeb1feadb32ae17ff50d167962e672d6bd87e4 [file] [log] [blame]
/*
* 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());
}
}
}