All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.jet.sql.impl.validate.types.HazelcastIntegerType Maven / Gradle / Ivy

/*
 * Copyright 2021 Hazelcast Inc.
 *
 * Licensed under the Hazelcast Community License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://hazelcast.com/hazelcast-community-license
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hazelcast.jet.sql.impl.validate.types;

import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.sql.type.BasicSqlType;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeName;

import java.util.HashMap;
import java.util.Map;

import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.BIGINT;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.INTEGER;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.SMALLINT;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.TINYINT;

/**
 * Represents integer-valued types for Calcite.
 * 

* Unlike the standard Calcite implementation for TINYINT, SMALLINT, INT and * BIGINT, this implementation tracks the actual bit width required to represent * integer values. That bit width information is not directly related to the * underlying machine representation. The closest concept is SQL precision * tracking done for DECIMAL type, but the bit width information tracks the * precision in terms of binary (base-2) digits instead of decimal (base-10) * digits. For instance, -1 and 1 require a single binary digit, 14 requires 4 * binary digits. *

* There are 4 edge case values: {@link Byte#MIN_VALUE}, {@link Short#MIN_VALUE}, * {@link Integer#MIN_VALUE} and {@link Long#MIN_VALUE} which, due to their * hardware representation, require one less bit comparing to their positive * counterparts. For instance, -128 ({@link Byte#MIN_VALUE}) requires 7 bits * while 128 requires 8. *

* In general, for an N-bit integer type the valid range of bit widths is from 0 * to N: zero bit width corresponds to 0 integer value, bit widths from 1 to * N - 1 correspond to regular integer values, bit width of N bits has a special * meaning and indicates a possibility of an overflow. *

* For instance, for BIGINT type represented as Java {@code long} type: * the valid bit width range is from 0 to 64, 0L has BIGINT(0) type, -14L has * BIGINT(4) type, a BIGINT SQL column has BIGINT(63) type, BIGINT(64) indicates * a potential overflow. *

* Each arithmetic operation acting on integer types infers its return type * based on the bit width information: INT(4) + INT(10) -> INT(11), * INT(4) + INT(31) -> BIGINT(32), INT(10) + BIGINT(63) -> BIGINT(64). In the * first example the bit width was expanded; in the second example the bit width * was expanded and the type was promoted to the next wider integer type; in the * last example the bit width was expanded, but there was no wider integer type * to promote to and avoid a possible overflow. *

* The benefits of that bit width approach are: the smallest possible type is * always selected to represent a result of a certain operation and it's always * possible to tell from the selected type alone whether overflow checking is * necessary while executing the operation. */ @SuppressWarnings("checkstyle:ExecutableStatementCount") public final class HazelcastIntegerType extends BasicSqlType { private static final Map TYPES = new HashMap<>(); private static final Map NULLABLE_TYPES = new HashMap<>(); private static final HazelcastIntegerType[] TYPES_BY_BIT_WIDTH = new HazelcastIntegerType[Long.SIZE + 1]; private static final HazelcastIntegerType[] NULLABLE_TYPES_BY_BIT_WIDTH = new HazelcastIntegerType[Long.SIZE + 1]; static { // Preallocate all possible types of all possible bit widths. TYPES.put(TINYINT, new HazelcastIntegerType[Byte.SIZE + 1]); TYPES.put(SMALLINT, new HazelcastIntegerType[Short.SIZE + 1]); TYPES.put(INTEGER, new HazelcastIntegerType[Integer.SIZE + 1]); TYPES.put(BIGINT, new HazelcastIntegerType[Long.SIZE + 1]); for (Map.Entry entry : TYPES.entrySet()) { SqlTypeName typeName = entry.getKey(); HazelcastIntegerType[] types = entry.getValue(); HazelcastIntegerType[] nullableTypes = new HazelcastIntegerType[types.length]; NULLABLE_TYPES.put(typeName, nullableTypes); for (int i = 0; i < types.length; ++i) { types[i] = new HazelcastIntegerType(typeName, false, i); nullableTypes[i] = new HazelcastIntegerType(typeName, true, i); } } // Build reverse mapping structures to map from a bit width to a // preferred integer type. for (int i = 0; i <= Long.SIZE; ++i) { HazelcastIntegerType type; HazelcastIntegerType nullableType; if (i < Byte.SIZE) { type = TYPES.get(TINYINT)[i]; nullableType = NULLABLE_TYPES.get(TINYINT)[i]; } else if (i < Short.SIZE) { type = TYPES.get(SMALLINT)[i]; nullableType = NULLABLE_TYPES.get(SMALLINT)[i]; } else if (i < Integer.SIZE) { type = TYPES.get(INTEGER)[i]; nullableType = NULLABLE_TYPES.get(INTEGER)[i]; } else { type = TYPES.get(BIGINT)[i]; nullableType = NULLABLE_TYPES.get(BIGINT)[i]; } TYPES_BY_BIT_WIDTH[i] = type; NULLABLE_TYPES_BY_BIT_WIDTH[i] = nullableType; } } private final int bitWidth; private HazelcastIntegerType(SqlTypeName typeName, boolean nullable, int bitWidth) { super(HazelcastTypeSystem.INSTANCE, typeName); this.isNullable = nullable; this.bitWidth = bitWidth; // recompute the digest to reflect the nullability of the type computeDigest(); } public int getBitWidth() { return bitWidth; } @Override protected void generateTypeString(StringBuilder sb, boolean withDetail) { super.generateTypeString(sb, withDetail); if (withDetail) { sb.append('(').append(bitWidth).append(')'); } } public static RelDataType create(HazelcastIntegerType type, boolean nullable) { if (type.isNullable() == nullable) { return type; } return create0(type.getSqlTypeName(), nullable, type.getBitWidth()); } public static HazelcastIntegerType create(SqlTypeName typeName, boolean nullable) { return create0(typeName, nullable, bitWidthOf(typeName)); } private static HazelcastIntegerType create0(SqlTypeName typeName, boolean nullable, int bitWidth) { if (nullable) { return NULLABLE_TYPES.get(typeName)[bitWidth]; } else { return TYPES.get(typeName)[bitWidth]; } } /** * @return the narrowest integer type able to represent integer values of * given bit width with the given nullability. */ public static HazelcastIntegerType create(int bitWidth, boolean nullable) { assert bitWidth >= 0; if (nullable) { return NULLABLE_TYPES_BY_BIT_WIDTH[Math.min(bitWidth, Long.SIZE)]; } else { return TYPES_BY_BIT_WIDTH[Math.min(bitWidth, Long.SIZE)]; } } /** * @return the bit width required to represent the given value. */ public static int bitWidthOf(long value) { // handle edge cases if (value == Long.MIN_VALUE) { return Long.SIZE - 1; } else if (value == Integer.MIN_VALUE) { return Integer.SIZE - 1; } else if (value == Short.MIN_VALUE) { return Short.SIZE - 1; } else if (value == Byte.MIN_VALUE) { return Byte.SIZE - 1; } value = Math.abs(value); return Long.SIZE - Long.numberOfLeadingZeros(value); } /** * @return the bit width enough to represent any integer value of the given * type name. */ static int bitWidthOf(SqlTypeName typeName) { switch (typeName) { case TINYINT: return Byte.SIZE - 1; case SMALLINT: return Short.SIZE - 1; case INTEGER: return Integer.SIZE - 1; case BIGINT: return Long.SIZE - 1; default: throw new IllegalArgumentException("unexpected type: " + typeName); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy