/* * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial agreement. */ package io.crate.types; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import io.crate.Streamer; import org.apache.logging.log4j.Logger; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.logging.Loggers; import java.io.IOException; import java.util.*; public final class DataTypes { private final static Logger logger = Loggers.getLogger(DataTypes.class); /** * If you add types here make sure to update the SizeEstimatorFactory in the SQL module. */ public final static UndefinedType UNDEFINED = UndefinedType.INSTANCE; public final static NotSupportedType NOT_SUPPORTED = NotSupportedType.INSTANCE; public final static ByteType BYTE = ByteType.INSTANCE; public final static BooleanType BOOLEAN = BooleanType.INSTANCE; public final static StringType STRING = StringType.INSTANCE; public final static IpType IP = IpType.INSTANCE; public final static DoubleType DOUBLE = DoubleType.INSTANCE; public final static FloatType FLOAT = FloatType.INSTANCE; public final static ShortType SHORT = ShortType.INSTANCE; public final static IntegerType INTEGER = IntegerType.INSTANCE; public final static LongType LONG = LongType.INSTANCE; public final static TimestampType TIMESTAMP = TimestampType.INSTANCE; public final static ObjectType OBJECT = ObjectType.INSTANCE; public final static GeoPointType GEO_POINT = GeoPointType.INSTANCE; public final static GeoShapeType GEO_SHAPE = GeoShapeType.INSTANCE; public final static DataType DOUBLE_ARRAY = new ArrayType(DOUBLE); public final static DataType OBJECT_ARRAY = new ArrayType(OBJECT); public final static ImmutableList<DataType> PRIMITIVE_TYPES = ImmutableList.of( BYTE, BOOLEAN, STRING, IP, DOUBLE, FLOAT, SHORT, INTEGER, LONG, TIMESTAMP ); public final static ImmutableList<DataType> NUMERIC_PRIMITIVE_TYPES = ImmutableList.of( DOUBLE, FLOAT, BYTE, SHORT, INTEGER, LONG ); /** * Type registry mapping type ids to the according data type instance. */ private static final Map<Integer, DataTypeFactory> TYPE_REGISTRY = new MapBuilder<Integer, DataTypeFactory>() .put(UndefinedType.ID, UNDEFINED) .put(NotSupportedType.ID, NOT_SUPPORTED) .put(ByteType.ID, BYTE) .put(BooleanType.ID, BOOLEAN) .put(StringType.ID, STRING) .put(IpType.ID, IP) .put(DoubleType.ID, DOUBLE) .put(FloatType.ID, FLOAT) .put(ShortType.ID, SHORT) .put(IntegerType.ID, INTEGER) .put(LongType.ID, LONG) .put(TimestampType.ID, TIMESTAMP) .put(ObjectType.ID, OBJECT) .put(GeoPointType.ID, GEO_POINT) .put(GeoShapeType.ID, GEO_SHAPE) .put(ArrayType.ID, ArrayType::new) .put(SetType.ID, SetType::new).map(); private static final Set<DataType> NUMBER_CONVERSIONS = ImmutableSet.<DataType>builder() .addAll(NUMERIC_PRIMITIVE_TYPES) .add(BOOLEAN) .add(STRING, TIMESTAMP, IP) .build(); // allowed conversion from key to one of the value types // the key type itself does not need to be in the value set static final ImmutableMap<Integer, Set<DataType>> ALLOWED_CONVERSIONS = ImmutableMap.<Integer, Set<DataType>>builder() .put(BYTE.id(), NUMBER_CONVERSIONS) .put(SHORT.id(), NUMBER_CONVERSIONS) .put(INTEGER.id(), NUMBER_CONVERSIONS) .put(LONG.id(), NUMBER_CONVERSIONS) .put(FLOAT.id(), NUMBER_CONVERSIONS) .put(DOUBLE.id(), NUMBER_CONVERSIONS) .put(BOOLEAN.id(), ImmutableSet.of(STRING)) .put(STRING.id(), ImmutableSet.<DataType>builder() .addAll(NUMBER_CONVERSIONS) .add(GEO_SHAPE) .add(GEO_POINT) .add(BOOLEAN) .add(OBJECT) .build()) .put(IP.id(), ImmutableSet.of(STRING)) .put(TIMESTAMP.id(), ImmutableSet.of(LONG)) .put(UNDEFINED.id(), ImmutableSet.of()) // actually convertible to every type, see NullType .put(GEO_POINT.id(), ImmutableSet.of(new ArrayType(DOUBLE))) .put(OBJECT.id(), ImmutableSet.of(GEO_SHAPE)) .put(ArrayType.ID, ImmutableSet.of()) // convertability handled in ArrayType .put(SetType.ID, ImmutableSet.of()) // convertability handled in SetType .build(); public static boolean isCollectionType(DataType type) { return type.id() == ArrayType.ID || type.id() == SetType.ID; } public static DataType fromStream(StreamInput in) throws IOException { int i = in.readVInt(); try { DataType type = TYPE_REGISTRY.get(i).create(); type.readFrom(in); return type; } catch (NullPointerException e) { logger.error(String.format(Locale.ENGLISH, "%d is missing in TYPE_REGISTRY", i), e); throw e; } } public static void toStream(DataType type, StreamOutput out) throws IOException { out.writeVInt(type.id()); type.writeTo(out); } private static final Map<Class<?>, DataType> POJO_TYPE_MAPPING = ImmutableMap.<Class<?>, DataType>builder() .put(Double.class, DOUBLE) .put(Float.class, FLOAT) .put(Integer.class, INTEGER) .put(Long.class, LONG) .put(Short.class, SHORT) .put(Byte.class, BYTE) .put(Boolean.class, BOOLEAN) .put(Map.class, OBJECT) .put(String.class, STRING) .put(BytesRef.class, STRING) .put(Character.class, STRING) .build(); public static DataType<?> guessType(Object value) { if (value == null) { return UNDEFINED; } else if (value instanceof Map) { return OBJECT; } else if (value instanceof List) { return valueFromList((List) value); } else if (value.getClass().isArray()) { return valueFromList(Arrays.asList((Object[]) value)); } return POJO_TYPE_MAPPING.get(value.getClass()); } private static DataType valueFromList(List<Object> value) { DataType previous = null; DataType current = null; for (Object o : value) { if (o == null) { continue; } current = guessType(o); if (previous != null && !current.equals(previous)) { throw new IllegalArgumentException("Mixed dataTypes inside a list are not supported"); } previous = current; } if (current == null) { return new ArrayType(UNDEFINED); } return new ArrayType(current); } private static final ImmutableMap<String, DataType> staticTypesNameMap = ImmutableMap.<String, DataType>builder() .put(UNDEFINED.getName(), UNDEFINED) .put(BYTE.getName(), BYTE) .put(BOOLEAN.getName(), BOOLEAN) .put(STRING.getName(), STRING) .put(IP.getName(), IP) .put(DOUBLE.getName(), DOUBLE) .put(FLOAT.getName(), FLOAT) .put(SHORT.getName(), SHORT) .put(INTEGER.getName(), INTEGER) .put(LONG.getName(), LONG) .put(TIMESTAMP.getName(), TIMESTAMP) .put(OBJECT.getName(), OBJECT) .put(GEO_POINT.getName(), GEO_POINT) .put(GEO_SHAPE.getName(), GEO_SHAPE) .build(); public static DataType ofName(String name) { DataType dataType = staticTypesNameMap.get(name); if (dataType == null) { throw new IllegalArgumentException("Cannot find data type of name " + name); } return dataType; } private static final ImmutableMap<String, DataType> MAPPING_NAMES_TO_TYPES = ImmutableMap.<String, DataType>builder() .put("date", DataTypes.TIMESTAMP) .put("string", DataTypes.STRING) .put("keyword", DataTypes.STRING) .put("text", DataTypes.STRING) .put("boolean", DataTypes.BOOLEAN) .put("byte", DataTypes.BYTE) .put("short", DataTypes.SHORT) .put("integer", DataTypes.INTEGER) .put("long", DataTypes.LONG) .put("float", DataTypes.FLOAT) .put("double", DataTypes.DOUBLE) .put("ip", DataTypes.IP) .put("geo_point", DataTypes.GEO_POINT) .put("geo_shape", DataTypes.GEO_SHAPE) .put("object", DataTypes.OBJECT) .put("nested", DataTypes.OBJECT).build(); @Nullable public static DataType ofMappingName(String name) { return MAPPING_NAMES_TO_TYPES.get(name); } public static DataType ofMappingNameSafe(String name) { DataType dataType = ofMappingName(name); if (dataType == null) { throw new IllegalArgumentException("Cannot find data type of mapping name " + name); } return dataType; } public static boolean isPrimitive(DataType type) { return PRIMITIVE_TYPES.contains(type); } /** * Register a custom data type to the type registry. * * <p>Note: If registering is done inside a static block, be sure the class is loaded initially. * Otherwise it might not be registered on all nodes. * </p> */ public static void register(int id, DataTypeFactory dataTypeFactory) { if (TYPE_REGISTRY.put(id, dataTypeFactory) != null) { throw new IllegalArgumentException("Already got a dataType with id " + id); } } public static Streamer<?>[] getStreamers(Collection<? extends DataType> dataTypes) { Streamer<?>[] streamer = new Streamer[dataTypes.size()]; int idx = 0; for (DataType dataType : dataTypes) { streamer[idx] = dataType.streamer(); idx++; } return streamer; } /** * Returns the first data type that is not {@link UndefinedType}, or {@code UNDEFINED} if none found. */ public static DataType tryFindNotNullType(Iterable<? extends DataType> dataTypes) { return Iterables.find(dataTypes, input -> input != UNDEFINED, UNDEFINED); } public static DataType fromId(Integer id) { return TYPE_REGISTRY.get(id).create(); } }