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

org.apache.iceberg.expressions.Literals Maven / Gradle / Ivy

The newest version!
/*
 * 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.iceberg.expressions;

import java.io.ObjectStreamException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.Objects;
import java.util.UUID;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.io.BaseEncoding;
import org.apache.iceberg.types.Comparators;
import org.apache.iceberg.types.Conversions;
import org.apache.iceberg.types.Type;
import org.apache.iceberg.types.Types;
import org.apache.iceberg.util.ByteBuffers;
import org.apache.iceberg.util.NaNUtil;

class Literals {
  private Literals() {
  }

  private static final OffsetDateTime EPOCH = Instant.ofEpochSecond(0).atOffset(ZoneOffset.UTC);
  private static final LocalDate EPOCH_DAY = EPOCH.toLocalDate();

  /**
   * Create a {@link Literal} from an Object.
   *
   * @param value a value
   * @param  Java type of value
   * @return a Literal for the given value
   */
  @SuppressWarnings("unchecked")
  static  Literal from(T value) {
    Preconditions.checkNotNull(value, "Cannot create expression literal from null");
    Preconditions.checkArgument(!NaNUtil.isNaN(value), "Cannot create expression literal from NaN");

    if (value instanceof Boolean) {
      return (Literal) new Literals.BooleanLiteral((Boolean) value);
    } else if (value instanceof Integer) {
      return (Literal) new Literals.IntegerLiteral((Integer) value);
    } else if (value instanceof Long) {
      return (Literal) new Literals.LongLiteral((Long) value);
    } else if (value instanceof Float) {
      return (Literal) new Literals.FloatLiteral((Float) value);
    } else if (value instanceof Double) {
      return (Literal) new Literals.DoubleLiteral((Double) value);
    } else if (value instanceof CharSequence) {
      return (Literal) new Literals.StringLiteral((CharSequence) value);
    } else if (value instanceof UUID) {
      return (Literal) new Literals.UUIDLiteral((UUID) value);
    } else if (value instanceof byte[]) {
      return (Literal) new Literals.FixedLiteral(ByteBuffer.wrap((byte[]) value));
    } else if (value instanceof ByteBuffer) {
      return (Literal) new Literals.BinaryLiteral((ByteBuffer) value);
    } else if (value instanceof BigDecimal) {
      return (Literal) new Literals.DecimalLiteral((BigDecimal) value);
    }

    throw new IllegalArgumentException(String.format(
        "Cannot create expression literal from %s: %s", value.getClass().getName(), value));
  }

  @SuppressWarnings("unchecked")
  static  AboveMax aboveMax() {
    return AboveMax.INSTANCE;
  }

  @SuppressWarnings("unchecked")
  static  BelowMin belowMin() {
    return BelowMin.INSTANCE;
  }

  private abstract static class BaseLiteral implements Literal {
    private final T value;
    private transient volatile ByteBuffer byteBuffer = null;

    BaseLiteral(T value) {
      Preconditions.checkNotNull(value, "Literal values cannot be null");
      this.value = value;
    }

    @Override
    public T value() {
      return value;
    }

    @Override
    public final ByteBuffer toByteBuffer() {
      if (byteBuffer == null) {
        synchronized (this) {
          if (byteBuffer == null) {
            byteBuffer = Conversions.toByteBuffer(typeId(), value());
          }
        }
      }
      return byteBuffer;
    }

    protected abstract Type.TypeID typeId();

    @Override
    public String toString() {
      return String.valueOf(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean equals(Object other) {
      if (this == other) {
        return true;
      }
      if (other == null || getClass() != other.getClass()) {
        return false;
      }
      BaseLiteral that = (BaseLiteral) other;

      return comparator().compare(value(), that.value()) == 0;
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(value);
    }

  }

  private abstract static class ComparableLiteral> extends BaseLiteral {
    @SuppressWarnings("unchecked")
    private static final Comparator CMP =
        Comparators.nullsFirst().thenComparing(Comparator.naturalOrder());

    ComparableLiteral(C value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Comparator comparator() {
      return (Comparator) CMP;
    }
  }

  static class AboveMax implements Literal {
    private static final AboveMax INSTANCE = new AboveMax();

    private AboveMax() {
    }

    @Override
    public T value() {
      throw new UnsupportedOperationException("AboveMax has no value");
    }

    @Override
    public  Literal to(Type type) {
      throw new UnsupportedOperationException("Cannot change the type of AboveMax");
    }

    @Override
    public Comparator comparator() {
      throw new UnsupportedOperationException("AboveMax has no comparator");
    }

    @Override
    public String toString() {
      return "aboveMax";
    }
  }

  static class BelowMin implements Literal {
    private static final BelowMin INSTANCE = new BelowMin();

    private BelowMin() {
    }

    @Override
    public T value() {
      throw new UnsupportedOperationException("BelowMin has no value");
    }

    @Override
    public  Literal to(Type type) {
      throw new UnsupportedOperationException("Cannot change the type of BelowMin");
    }

    @Override
    public Comparator comparator() {
      throw new UnsupportedOperationException("BelowMin has no comparator");
    }

    @Override
    public String toString() {
      return "belowMin";
    }
  }

  static class BooleanLiteral extends ComparableLiteral {
    BooleanLiteral(Boolean value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      if (type.typeId() == Type.TypeID.BOOLEAN) {
        return (Literal) this;
      }
      return null;
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.BOOLEAN;
    }
  }

  static class IntegerLiteral extends ComparableLiteral {
    IntegerLiteral(Integer value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      switch (type.typeId()) {
        case INTEGER:
          return (Literal) this;
        case LONG:
          return (Literal) new LongLiteral(value().longValue());
        case FLOAT:
          return (Literal) new FloatLiteral(value().floatValue());
        case DOUBLE:
          return (Literal) new DoubleLiteral(value().doubleValue());
        case DATE:
          return (Literal) new DateLiteral(value());
        case DECIMAL:
          int scale = ((Types.DecimalType) type).scale();
          // rounding mode isn't necessary, but pass one to avoid warnings
          return (Literal) new DecimalLiteral(
              BigDecimal.valueOf(value()).setScale(scale, RoundingMode.HALF_UP));
        default:
          return null;
      }
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.INTEGER;
    }
  }

  static class LongLiteral extends ComparableLiteral {
    LongLiteral(Long value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      switch (type.typeId()) {
        case INTEGER:
          if ((long) Integer.MAX_VALUE < value()) {
            return aboveMax();
          } else if ((long) Integer.MIN_VALUE > value()) {
            return belowMin();
          }
          return (Literal) new IntegerLiteral(value().intValue());
        case LONG:
          return (Literal) this;
        case FLOAT:
          return (Literal) new FloatLiteral(value().floatValue());
        case DOUBLE:
          return (Literal) new DoubleLiteral(value().doubleValue());
        case TIME:
          return (Literal) new TimeLiteral(value());
        case TIMESTAMP:
          return (Literal) new TimestampLiteral(value());
        case DATE:
          if ((long) Integer.MAX_VALUE < value()) {
            return aboveMax();
          } else if ((long) Integer.MIN_VALUE > value()) {
            return belowMin();
          }
          return (Literal) new DateLiteral(value().intValue());
        case DECIMAL:
          int scale = ((Types.DecimalType) type).scale();
          // rounding mode isn't necessary, but pass one to avoid warnings
          return (Literal) new DecimalLiteral(
              BigDecimal.valueOf(value()).setScale(scale, RoundingMode.HALF_UP));
        default:
          return null;
      }
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.LONG;
    }
  }

  static class FloatLiteral extends ComparableLiteral {
    FloatLiteral(Float value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      switch (type.typeId()) {
        case FLOAT:
          return (Literal) this;
        case DOUBLE:
          return (Literal) new DoubleLiteral(value().doubleValue());
        case DECIMAL:
          int scale = ((Types.DecimalType) type).scale();
          return (Literal) new DecimalLiteral(
              BigDecimal.valueOf(value()).setScale(scale, RoundingMode.HALF_UP));
        default:
          return null;
      }
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.FLOAT;
    }
  }

  static class DoubleLiteral extends ComparableLiteral {
    DoubleLiteral(Double value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      switch (type.typeId()) {
        case FLOAT:
          if ((double) Float.MAX_VALUE < value()) {
            return aboveMax();
          } else if ((double) -Float.MAX_VALUE > value()) {
            // Compare with -Float.MAX_VALUE because it is the most negative float value.
            // Float.MIN_VALUE is the smallest non-negative floating point value.
            return belowMin();
          }
          return (Literal) new FloatLiteral(value().floatValue());
        case DOUBLE:
          return (Literal) this;
        case DECIMAL:
          int scale = ((Types.DecimalType) type).scale();
          return (Literal) new DecimalLiteral(
              BigDecimal.valueOf(value()).setScale(scale, RoundingMode.HALF_UP));
        default:
          return null;
      }
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.DOUBLE;
    }
  }

  static class DateLiteral extends ComparableLiteral {
    DateLiteral(Integer value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      if (type.typeId() == Type.TypeID.DATE) {
        return (Literal) this;
      }
      return null;
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.DATE;
    }
  }

  static class TimeLiteral extends ComparableLiteral {
    TimeLiteral(Long value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      if (type.typeId() == Type.TypeID.TIME) {
        return (Literal) this;
      }
      return null;
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.TIME;
    }
  }

  static class TimestampLiteral extends ComparableLiteral {
    TimestampLiteral(Long value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      switch (type.typeId()) {
        case TIMESTAMP:
          return (Literal) this;
        case DATE:
          return (Literal) new DateLiteral((int) ChronoUnit.DAYS.between(
              EPOCH_DAY, EPOCH.plus(value(), ChronoUnit.MICROS).toLocalDate()));
        default:
      }
      return null;
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.TIMESTAMP;
    }
  }

  static class DecimalLiteral extends ComparableLiteral {
    DecimalLiteral(BigDecimal value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      switch (type.typeId()) {
        case DECIMAL:
          // do not change decimal scale
          return (Literal) this;
        default:
          return null;
      }
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.DECIMAL;
    }
  }

  static class StringLiteral extends BaseLiteral {
    private static final Comparator CMP =
        Comparators.nullsFirst().thenComparing(Comparators.charSequences());

    StringLiteral(CharSequence value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      switch (type.typeId()) {
        case DATE:
          int date = (int) ChronoUnit.DAYS.between(EPOCH_DAY,
              LocalDate.parse(value(), DateTimeFormatter.ISO_LOCAL_DATE));
          return (Literal) new DateLiteral(date);

        case TIME:
          long timeMicros = LocalTime.parse(value(), DateTimeFormatter.ISO_LOCAL_TIME)
              .toNanoOfDay() / 1000;
          return (Literal) new TimeLiteral(timeMicros);

        case TIMESTAMP:
          if (((Types.TimestampType) type).shouldAdjustToUTC()) {
            long timestampMicros = ChronoUnit.MICROS.between(EPOCH,
                OffsetDateTime.parse(value(), DateTimeFormatter.ISO_DATE_TIME));
            return (Literal) new TimestampLiteral(timestampMicros);
          } else {
            long timestampMicros = ChronoUnit.MICROS.between(EPOCH,
                LocalDateTime.parse(value(), DateTimeFormatter.ISO_LOCAL_DATE_TIME)
                    .atOffset(ZoneOffset.UTC));
            return (Literal) new TimestampLiteral(timestampMicros);
          }

        case STRING:
          return (Literal) this;

        case UUID:
          return (Literal) new UUIDLiteral(UUID.fromString(value().toString()));

        case DECIMAL:
          // do not change decimal scale
          BigDecimal decimal = new BigDecimal(value().toString());
          return (Literal) new DecimalLiteral(decimal);

        default:
          return null;
      }
    }

    @Override
    public Comparator comparator() {
      return CMP;
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.STRING;
    }

    @Override
    public String toString() {
      return "\"" + value() + "\"";
    }
  }

  static class UUIDLiteral extends ComparableLiteral {
    UUIDLiteral(UUID value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      if (type.typeId() == Type.TypeID.UUID) {
        return (Literal) this;
      }
      return null;
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.UUID;
    }
  }

  static class FixedLiteral extends BaseLiteral {
    private static final Comparator CMP =
        Comparators.nullsFirst().thenComparing(Comparators.unsignedBytes());

    FixedLiteral(ByteBuffer value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      switch (type.typeId()) {
        case FIXED:
          Types.FixedType fixed = (Types.FixedType) type;
          if (value().remaining() == fixed.length()) {
            return (Literal) this;
          }
          return null;
        case BINARY:
          return (Literal) new BinaryLiteral(value());
        default:
          return null;
      }
    }

    @Override
    public Comparator comparator() {
      return CMP;
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.FIXED;
    }

    Object writeReplace() throws ObjectStreamException {
      return new SerializationProxies.FixedLiteralProxy(value());
    }

    @Override
    public String toString() {
      byte[] bytes = ByteBuffers.toByteArray(value());
      return "X'" + BaseEncoding.base16().encode(bytes) + "'";
    }
  }

  static class BinaryLiteral extends BaseLiteral {
    private static final Comparator CMP =
        Comparators.nullsFirst().thenComparing(Comparators.unsignedBytes());

    BinaryLiteral(ByteBuffer value) {
      super(value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Literal to(Type type) {
      switch (type.typeId()) {
        case FIXED:
          Types.FixedType fixed = (Types.FixedType) type;
          if (value().remaining() == fixed.length()) {
            return (Literal) new FixedLiteral(value());
          }
          return null;
        case BINARY:
          return (Literal) this;
        default:
          return null;
      }
    }

    @Override
    public Comparator comparator() {
      return CMP;
    }

    Object writeReplace() throws ObjectStreamException {
      return new SerializationProxies.BinaryLiteralProxy(value());
    }

    @Override
    protected Type.TypeID typeId() {
      return Type.TypeID.BINARY;
    }

    @Override
    public String toString() {
      byte[] bytes = ByteBuffers.toByteArray(value());
      return "X'" + BaseEncoding.base16().encode(bytes) + "'";
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy