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

org.apache.calcite.sql.SqlIntervalQualifier Maven / Gradle / Ivy

There is a newer version: 1.17.0-flink-r3
Show 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.calcite.sql;

import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.avatica.util.TimeUnitRange;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.util.SqlVisitor;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Util;

import java.math.BigDecimal;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.apache.calcite.util.Static.RESOURCE;

/**
 * Represents an INTERVAL qualifier.
 *
 * 

INTERVAL qualifier is defined as follows: * *

* <interval qualifier> ::=
*    <start field> TO <end field>
*   | <single datetime field>
* <start field> ::=
*    <non-second primary datetime field>
*    [ <left paren> <interval leading field precision> * <right paren> ]
* <end field> ::=
*    <non-second primary datetime field>
*   | SECOND [ <left paren> * <interval fractional seconds precision> <right paren> ]
* <single datetime field> ::=
*   <non-second primary datetime field>
*   [ <left paren> <interval leading field precision> * <right paren> ]
*   | SECOND [ <left paren> * <interval leading field precision>
*   [ <comma> <interval fractional seconds precision> ] * <right paren> ]
* <primary datetime field> ::=
*   <non-second primary datetime field>
*   | SECOND
* <non-second primary datetime field> ::= YEAR | MONTH | DAY | HOUR * | MINUTE
* <interval fractional seconds precision> ::= * <unsigned integer>
* <interval leading field precision> ::= <unsigned integer> *
* *

Examples include: * *

    *
  • INTERVAL '1:23:45.678' HOUR TO SECOND
  • *
  • INTERVAL '1 2:3:4' DAY TO SECOND
  • *
  • INTERVAL '1 2:3:4' DAY(4) TO SECOND(4)
  • *
* *

An instance of this class is immutable. */ public class SqlIntervalQualifier extends SqlNode { //~ Static fields/initializers --------------------------------------------- private static final BigDecimal ZERO = BigDecimal.ZERO; private static final BigDecimal THOUSAND = BigDecimal.valueOf(1000); private static final BigDecimal INT_MAX_VALUE_PLUS_ONE = BigDecimal.valueOf(Integer.MAX_VALUE).add(BigDecimal.ONE); //~ Instance fields -------------------------------------------------------- private final int startPrecision; public final TimeUnitRange timeUnitRange; private final int fractionalSecondPrecision; //~ Constructors ----------------------------------------------------------- public SqlIntervalQualifier( TimeUnit startUnit, int startPrecision, TimeUnit endUnit, int fractionalSecondPrecision, SqlParserPos pos) { super(pos); if (endUnit == startUnit) { endUnit = null; } this.timeUnitRange = TimeUnitRange.of(Objects.requireNonNull(startUnit), endUnit); this.startPrecision = startPrecision; this.fractionalSecondPrecision = fractionalSecondPrecision; } public SqlIntervalQualifier( TimeUnit startUnit, TimeUnit endUnit, SqlParserPos pos) { this( startUnit, RelDataType.PRECISION_NOT_SPECIFIED, endUnit, RelDataType.PRECISION_NOT_SPECIFIED, pos); } //~ Methods ---------------------------------------------------------------- public SqlTypeName typeName() { switch (timeUnitRange) { case YEAR: case ISOYEAR: case CENTURY: case DECADE: case MILLENNIUM: return SqlTypeName.INTERVAL_YEAR; case YEAR_TO_MONTH: return SqlTypeName.INTERVAL_YEAR_MONTH; case MONTH: case QUARTER: return SqlTypeName.INTERVAL_MONTH; case DOW: case ISODOW: case DOY: case DAY: case WEEK: return SqlTypeName.INTERVAL_DAY; case DAY_TO_HOUR: return SqlTypeName.INTERVAL_DAY_HOUR; case DAY_TO_MINUTE: return SqlTypeName.INTERVAL_DAY_MINUTE; case DAY_TO_SECOND: return SqlTypeName.INTERVAL_DAY_SECOND; case HOUR: return SqlTypeName.INTERVAL_HOUR; case HOUR_TO_MINUTE: return SqlTypeName.INTERVAL_HOUR_MINUTE; case HOUR_TO_SECOND: return SqlTypeName.INTERVAL_HOUR_SECOND; case MINUTE: return SqlTypeName.INTERVAL_MINUTE; case MINUTE_TO_SECOND: return SqlTypeName.INTERVAL_MINUTE_SECOND; case SECOND: case MILLISECOND: case EPOCH: case MICROSECOND: case NANOSECOND: return SqlTypeName.INTERVAL_SECOND; default: throw new AssertionError(timeUnitRange); } } public void validate( SqlValidator validator, SqlValidatorScope scope) { validator.validateIntervalQualifier(this); } public R accept(SqlVisitor visitor) { return visitor.visit(this); } public boolean equalsDeep(SqlNode node, Litmus litmus) { final String thisString = this.toString(); final String thatString = node.toString(); if (!thisString.equals(thatString)) { return litmus.fail("{} != {}", this, node); } return litmus.succeed(); } public int getStartPrecision(RelDataTypeSystem typeSystem) { if (startPrecision == RelDataType.PRECISION_NOT_SPECIFIED) { return typeSystem.getDefaultPrecision(typeName()); } else { return startPrecision; } } public int getStartPrecisionPreservingDefault() { return startPrecision; } /** Returns {@code true} if start precision is not specified. */ public boolean useDefaultStartPrecision() { return startPrecision == RelDataType.PRECISION_NOT_SPECIFIED; } public static int combineStartPrecisionPreservingDefault( RelDataTypeSystem typeSystem, SqlIntervalQualifier qual1, SqlIntervalQualifier qual2) { final int start1 = qual1.getStartPrecision(typeSystem); final int start2 = qual2.getStartPrecision(typeSystem); if (start1 > start2) { // qual1 is more precise, but if it has the default indicator // set, we need to return that indicator so result will also // use default return qual1.getStartPrecisionPreservingDefault(); } else if (start1 < start2) { // qual2 is more precise, but if it has the default indicator // set, we need to return that indicator so result will also // use default return qual2.getStartPrecisionPreservingDefault(); } else { // they are equal. return default if both are default, // otherwise return exact precision if (qual1.useDefaultStartPrecision() && qual2.useDefaultStartPrecision()) { return qual1.getStartPrecisionPreservingDefault(); } else { return start1; } } } public int getFractionalSecondPrecision(RelDataTypeSystem typeSystem) { if (fractionalSecondPrecision == RelDataType.PRECISION_NOT_SPECIFIED) { return typeName().getDefaultScale(); } else { return fractionalSecondPrecision; } } public int getFractionalSecondPrecisionPreservingDefault() { if (useDefaultFractionalSecondPrecision()) { return RelDataType.PRECISION_NOT_SPECIFIED; } else { return fractionalSecondPrecision; } } /** Returns {@code true} if fractional second precision is not specified. */ public boolean useDefaultFractionalSecondPrecision() { return fractionalSecondPrecision == RelDataType.PRECISION_NOT_SPECIFIED; } public static int combineFractionalSecondPrecisionPreservingDefault( RelDataTypeSystem typeSystem, SqlIntervalQualifier qual1, SqlIntervalQualifier qual2) { final int p1 = qual1.getFractionalSecondPrecision(typeSystem); final int p2 = qual2.getFractionalSecondPrecision(typeSystem); if (p1 > p2) { // qual1 is more precise, but if it has the default indicator // set, we need to return that indicator so result will also // use default return qual1.getFractionalSecondPrecisionPreservingDefault(); } else if (p1 < p2) { // qual2 is more precise, but if it has the default indicator // set, we need to return that indicator so result will also // use default return qual2.getFractionalSecondPrecisionPreservingDefault(); } else { // they are equal. return default if both are default, // otherwise return exact precision if (qual1.useDefaultFractionalSecondPrecision() && qual2.useDefaultFractionalSecondPrecision()) { return qual1.getFractionalSecondPrecisionPreservingDefault(); } else { return p1; } } } public TimeUnit getStartUnit() { return timeUnitRange.startUnit; } public TimeUnit getEndUnit() { return timeUnitRange.endUnit; } /** Returns {@code SECOND} for both {@code HOUR TO SECOND} and * {@code SECOND}. */ public TimeUnit getUnit() { return Util.first(timeUnitRange.endUnit, timeUnitRange.startUnit); } public SqlNode clone(SqlParserPos pos) { return new SqlIntervalQualifier(timeUnitRange.startUnit, startPrecision, timeUnitRange.endUnit, fractionalSecondPrecision, pos); } public void unparse( SqlWriter writer, int leftPrec, int rightPrec) { writer.getDialect() .unparseSqlIntervalQualifier(writer, this, RelDataTypeSystem.DEFAULT); } /** * Returns whether this interval has a single datetime field. * *

Returns {@code true} if it is of the form {@code unit}, * {@code false} if it is of the form {@code unit TO unit}. */ public boolean isSingleDatetimeField() { return timeUnitRange.endUnit == null; } public final boolean isYearMonth() { return timeUnitRange.startUnit.yearMonth; } /** * @return 1 or -1 */ public int getIntervalSign(String value) { int sign = 1; // positive until proven otherwise if (!Util.isNullOrEmpty(value)) { if ('-' == value.charAt(0)) { sign = -1; // Negative } } return sign; } private String stripLeadingSign(String value) { String unsignedValue = value; if (!Util.isNullOrEmpty(value)) { if (('-' == value.charAt(0)) || ('+' == value.charAt(0))) { unsignedValue = value.substring(1); } } return unsignedValue; } private boolean isLeadFieldInRange(RelDataTypeSystem typeSystem, BigDecimal value, TimeUnit unit) { // we should never get handed a negative field value assert value.compareTo(ZERO) >= 0; // Leading fields are only restricted by startPrecision. final int startPrecision = getStartPrecision(typeSystem); return startPrecision < POWERS10.length ? value.compareTo(POWERS10[startPrecision]) < 0 : value.compareTo(INT_MAX_VALUE_PLUS_ONE) < 0; } private void checkLeadFieldInRange(RelDataTypeSystem typeSystem, int sign, BigDecimal value, TimeUnit unit, SqlParserPos pos) { if (!isLeadFieldInRange(typeSystem, value, unit)) { throw fieldExceedsPrecisionException( pos, sign, value, unit, getStartPrecision(typeSystem)); } } private static final BigDecimal[] POWERS10 = { ZERO, BigDecimal.valueOf(10), BigDecimal.valueOf(100), BigDecimal.valueOf(1000), BigDecimal.valueOf(10000), BigDecimal.valueOf(100000), BigDecimal.valueOf(1000000), BigDecimal.valueOf(10000000), BigDecimal.valueOf(100000000), BigDecimal.valueOf(1000000000), }; private boolean isFractionalSecondFieldInRange(BigDecimal field) { // we should never get handed a negative field value assert field.compareTo(ZERO) >= 0; // Fractional second fields are only restricted by precision, which // has already been checked for using pattern matching. // Therefore, always return true return true; } private boolean isSecondaryFieldInRange(BigDecimal field, TimeUnit unit) { // we should never get handed a negative field value assert field.compareTo(ZERO) >= 0; // YEAR and DAY can never be secondary units, // nor can unit be null. assert unit != null; switch (unit) { case YEAR: case DAY: default: throw Util.unexpected(unit); // Secondary field limits, as per section 4.6.3 of SQL2003 spec case MONTH: case HOUR: case MINUTE: case SECOND: return unit.isValidValue(field); } } private BigDecimal normalizeSecondFraction(String secondFracStr) { // Decimal value can be more than 3 digits. So just get // the millisecond part. return new BigDecimal("0." + secondFracStr).multiply(THOUSAND); } private int[] fillIntervalValueArray( int sign, BigDecimal year, BigDecimal month) { int[] ret = new int[3]; ret[0] = sign; ret[1] = year.intValue(); ret[2] = month.intValue(); return ret; } private int[] fillIntervalValueArray( int sign, BigDecimal day, BigDecimal hour, BigDecimal minute, BigDecimal second, BigDecimal secondFrac) { int[] ret = new int[6]; ret[0] = sign; ret[1] = day.intValue(); ret[2] = hour.intValue(); ret[3] = minute.intValue(); ret[4] = second.intValue(); ret[5] = secondFrac.intValue(); return ret; } /** * Validates an INTERVAL literal against a YEAR interval qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsYear( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal year; // validate as YEAR(startPrecision), e.g. 'YY' String intervalPattern = "(\\d+)"; Matcher m = Pattern.compile(intervalPattern).matcher(value); if (m.matches()) { // Break out field values try { year = parseField(m, 1); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, year, TimeUnit.YEAR, pos); // package values up for return return fillIntervalValueArray(sign, year, ZERO); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against a YEAR TO MONTH interval qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsYearToMonth( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal year; BigDecimal month; // validate as YEAR(startPrecision) TO MONTH, e.g. 'YY-DD' String intervalPattern = "(\\d+)-(\\d{1,2})"; Matcher m = Pattern.compile(intervalPattern).matcher(value); if (m.matches()) { // Break out field values try { year = parseField(m, 1); month = parseField(m, 2); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, year, TimeUnit.YEAR, pos); if (!(isSecondaryFieldInRange(month, TimeUnit.MONTH))) { throw invalidValueException(pos, originalValue); } // package values up for return return fillIntervalValueArray(sign, year, month); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against a MONTH interval qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsMonth( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal month; // validate as MONTH(startPrecision), e.g. 'MM' String intervalPattern = "(\\d+)"; Matcher m = Pattern.compile(intervalPattern).matcher(value); if (m.matches()) { // Break out field values try { month = parseField(m, 1); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, month, TimeUnit.MONTH, pos); // package values up for return return fillIntervalValueArray(sign, ZERO, month); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against a DAY interval qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsDay( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal day; // validate as DAY(startPrecision), e.g. 'DD' String intervalPattern = "(\\d+)"; Matcher m = Pattern.compile(intervalPattern).matcher(value); if (m.matches()) { // Break out field values try { day = parseField(m, 1); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, day, TimeUnit.DAY, pos); // package values up for return return fillIntervalValueArray(sign, day, ZERO, ZERO, ZERO, ZERO); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against a DAY TO HOUR interval qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsDayToHour( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal day; BigDecimal hour; // validate as DAY(startPrecision) TO HOUR, e.g. 'DD HH' String intervalPattern = "(\\d+) (\\d{1,2})"; Matcher m = Pattern.compile(intervalPattern).matcher(value); if (m.matches()) { // Break out field values try { day = parseField(m, 1); hour = parseField(m, 2); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, day, TimeUnit.DAY, pos); if (!(isSecondaryFieldInRange(hour, TimeUnit.HOUR))) { throw invalidValueException(pos, originalValue); } // package values up for return return fillIntervalValueArray(sign, day, hour, ZERO, ZERO, ZERO); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against a DAY TO MINUTE interval qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsDayToMinute( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal day; BigDecimal hour; BigDecimal minute; // validate as DAY(startPrecision) TO MINUTE, e.g. 'DD HH:MM' String intervalPattern = "(\\d+) (\\d{1,2}):(\\d{1,2})"; Matcher m = Pattern.compile(intervalPattern).matcher(value); if (m.matches()) { // Break out field values try { day = parseField(m, 1); hour = parseField(m, 2); minute = parseField(m, 3); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, day, TimeUnit.DAY, pos); if (!(isSecondaryFieldInRange(hour, TimeUnit.HOUR)) || !(isSecondaryFieldInRange(minute, TimeUnit.MINUTE))) { throw invalidValueException(pos, originalValue); } // package values up for return return fillIntervalValueArray(sign, day, hour, minute, ZERO, ZERO); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against a DAY TO SECOND interval qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsDayToSecond( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal day; BigDecimal hour; BigDecimal minute; BigDecimal second; BigDecimal secondFrac; boolean hasFractionalSecond; // validate as DAY(startPrecision) TO MINUTE, // e.g. 'DD HH:MM:SS' or 'DD HH:MM:SS.SSS' // Note: must check two patterns, since fractional second is optional final int fractionalSecondPrecision = getFractionalSecondPrecision(typeSystem); String intervalPatternWithFracSec = "(\\d+) (\\d{1,2}):(\\d{1,2}):(\\d{1,2})\\.(\\d{1," + fractionalSecondPrecision + "})"; String intervalPatternWithoutFracSec = "(\\d+) (\\d{1,2}):(\\d{1,2}):(\\d{1,2})"; Matcher m = Pattern.compile(intervalPatternWithFracSec).matcher(value); if (m.matches()) { hasFractionalSecond = true; } else { m = Pattern.compile(intervalPatternWithoutFracSec).matcher(value); hasFractionalSecond = false; } if (m.matches()) { // Break out field values try { day = parseField(m, 1); hour = parseField(m, 2); minute = parseField(m, 3); second = parseField(m, 4); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } if (hasFractionalSecond) { secondFrac = normalizeSecondFraction(m.group(5)); } else { secondFrac = ZERO; } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, day, TimeUnit.DAY, pos); if (!(isSecondaryFieldInRange(hour, TimeUnit.HOUR)) || !(isSecondaryFieldInRange(minute, TimeUnit.MINUTE)) || !(isSecondaryFieldInRange(second, TimeUnit.SECOND)) || !(isFractionalSecondFieldInRange(secondFrac))) { throw invalidValueException(pos, originalValue); } // package values up for return return fillIntervalValueArray( sign, day, hour, minute, second, secondFrac); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against an HOUR interval qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsHour( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal hour; // validate as HOUR(startPrecision), e.g. 'HH' String intervalPattern = "(\\d+)"; Matcher m = Pattern.compile(intervalPattern).matcher(value); if (m.matches()) { // Break out field values try { hour = parseField(m, 1); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, hour, TimeUnit.HOUR, pos); // package values up for return return fillIntervalValueArray(sign, ZERO, hour, ZERO, ZERO, ZERO); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against an HOUR TO MINUTE interval * qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsHourToMinute( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal hour; BigDecimal minute; // validate as HOUR(startPrecision) TO MINUTE, e.g. 'HH:MM' String intervalPattern = "(\\d+):(\\d{1,2})"; Matcher m = Pattern.compile(intervalPattern).matcher(value); if (m.matches()) { // Break out field values try { hour = parseField(m, 1); minute = parseField(m, 2); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, hour, TimeUnit.HOUR, pos); if (!(isSecondaryFieldInRange(minute, TimeUnit.MINUTE))) { throw invalidValueException(pos, originalValue); } // package values up for return return fillIntervalValueArray(sign, ZERO, hour, minute, ZERO, ZERO); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against an HOUR TO SECOND interval * qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsHourToSecond( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal hour; BigDecimal minute; BigDecimal second; BigDecimal secondFrac; boolean hasFractionalSecond; // validate as HOUR(startPrecision) TO SECOND, // e.g. 'HH:MM:SS' or 'HH:MM:SS.SSS' // Note: must check two patterns, since fractional second is optional final int fractionalSecondPrecision = getFractionalSecondPrecision(typeSystem); String intervalPatternWithFracSec = "(\\d+):(\\d{1,2}):(\\d{1,2})\\.(\\d{1," + fractionalSecondPrecision + "})"; String intervalPatternWithoutFracSec = "(\\d+):(\\d{1,2}):(\\d{1,2})"; Matcher m = Pattern.compile(intervalPatternWithFracSec).matcher(value); if (m.matches()) { hasFractionalSecond = true; } else { m = Pattern.compile(intervalPatternWithoutFracSec).matcher(value); hasFractionalSecond = false; } if (m.matches()) { // Break out field values try { hour = parseField(m, 1); minute = parseField(m, 2); second = parseField(m, 3); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } if (hasFractionalSecond) { secondFrac = normalizeSecondFraction(m.group(4)); } else { secondFrac = ZERO; } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, hour, TimeUnit.HOUR, pos); if (!(isSecondaryFieldInRange(minute, TimeUnit.MINUTE)) || !(isSecondaryFieldInRange(second, TimeUnit.SECOND)) || !(isFractionalSecondFieldInRange(secondFrac))) { throw invalidValueException(pos, originalValue); } // package values up for return return fillIntervalValueArray( sign, ZERO, hour, minute, second, secondFrac); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against an MINUTE interval qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsMinute( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal minute; // validate as MINUTE(startPrecision), e.g. 'MM' String intervalPattern = "(\\d+)"; Matcher m = Pattern.compile(intervalPattern).matcher(value); if (m.matches()) { // Break out field values try { minute = parseField(m, 1); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, minute, TimeUnit.MINUTE, pos); // package values up for return return fillIntervalValueArray(sign, ZERO, ZERO, minute, ZERO, ZERO); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against an MINUTE TO SECOND interval * qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsMinuteToSecond( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal minute; BigDecimal second; BigDecimal secondFrac; boolean hasFractionalSecond; // validate as MINUTE(startPrecision) TO SECOND, // e.g. 'MM:SS' or 'MM:SS.SSS' // Note: must check two patterns, since fractional second is optional final int fractionalSecondPrecision = getFractionalSecondPrecision(typeSystem); String intervalPatternWithFracSec = "(\\d+):(\\d{1,2})\\.(\\d{1," + fractionalSecondPrecision + "})"; String intervalPatternWithoutFracSec = "(\\d+):(\\d{1,2})"; Matcher m = Pattern.compile(intervalPatternWithFracSec).matcher(value); if (m.matches()) { hasFractionalSecond = true; } else { m = Pattern.compile(intervalPatternWithoutFracSec).matcher(value); hasFractionalSecond = false; } if (m.matches()) { // Break out field values try { minute = parseField(m, 1); second = parseField(m, 2); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } if (hasFractionalSecond) { secondFrac = normalizeSecondFraction(m.group(3)); } else { secondFrac = ZERO; } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, minute, TimeUnit.MINUTE, pos); if (!(isSecondaryFieldInRange(second, TimeUnit.SECOND)) || !(isFractionalSecondFieldInRange(secondFrac))) { throw invalidValueException(pos, originalValue); } // package values up for return return fillIntervalValueArray( sign, ZERO, ZERO, minute, second, secondFrac); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal against an SECOND interval qualifier. * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ private int[] evaluateIntervalLiteralAsSecond( RelDataTypeSystem typeSystem, int sign, String value, String originalValue, SqlParserPos pos) { BigDecimal second; BigDecimal secondFrac; boolean hasFractionalSecond; // validate as SECOND(startPrecision, fractionalSecondPrecision) // e.g. 'SS' or 'SS.SSS' // Note: must check two patterns, since fractional second is optional final int fractionalSecondPrecision = getFractionalSecondPrecision(typeSystem); String intervalPatternWithFracSec = "(\\d+)\\.(\\d{1," + fractionalSecondPrecision + "})"; String intervalPatternWithoutFracSec = "(\\d+)"; Matcher m = Pattern.compile(intervalPatternWithFracSec).matcher(value); if (m.matches()) { hasFractionalSecond = true; } else { m = Pattern.compile(intervalPatternWithoutFracSec).matcher(value); hasFractionalSecond = false; } if (m.matches()) { // Break out field values try { second = parseField(m, 1); } catch (NumberFormatException e) { throw invalidValueException(pos, originalValue); } if (hasFractionalSecond) { secondFrac = normalizeSecondFraction(m.group(2)); } else { secondFrac = ZERO; } // Validate individual fields checkLeadFieldInRange(typeSystem, sign, second, TimeUnit.SECOND, pos); if (!(isFractionalSecondFieldInRange(secondFrac))) { throw invalidValueException(pos, originalValue); } // package values up for return return fillIntervalValueArray( sign, ZERO, ZERO, ZERO, second, secondFrac); } else { throw invalidValueException(pos, originalValue); } } /** * Validates an INTERVAL literal according to the rules specified by the * interval qualifier. The assumption is made that the interval qualifier has * been validated prior to calling this method. Evaluating against an * invalid qualifier could lead to strange results. * * @return field values, never null * * @throws org.apache.calcite.runtime.CalciteContextException if the interval * value is illegal */ public int[] evaluateIntervalLiteral(String value, SqlParserPos pos, RelDataTypeSystem typeSystem) { // save original value for if we have to throw final String value0 = value; // First strip off any leading whitespace value = value.trim(); // check if the sign was explicitly specified. Record // the explicit or implicit sign, and strip it off to // simplify pattern matching later. final int sign = getIntervalSign(value); value = stripLeadingSign(value); // If we have an empty or null literal at this point, // it's illegal. Complain and bail out. if (Util.isNullOrEmpty(value)) { throw invalidValueException(pos, value0); } // Validate remaining string according to the pattern // that corresponds to the start and end units as // well as explicit or implicit precision and range. switch (timeUnitRange) { case YEAR: return evaluateIntervalLiteralAsYear(typeSystem, sign, value, value0, pos); case YEAR_TO_MONTH: return evaluateIntervalLiteralAsYearToMonth(typeSystem, sign, value, value0, pos); case MONTH: return evaluateIntervalLiteralAsMonth(typeSystem, sign, value, value0, pos); case DAY: return evaluateIntervalLiteralAsDay(typeSystem, sign, value, value0, pos); case DAY_TO_HOUR: return evaluateIntervalLiteralAsDayToHour(typeSystem, sign, value, value0, pos); case DAY_TO_MINUTE: return evaluateIntervalLiteralAsDayToMinute(typeSystem, sign, value, value0, pos); case DAY_TO_SECOND: return evaluateIntervalLiteralAsDayToSecond(typeSystem, sign, value, value0, pos); case HOUR: return evaluateIntervalLiteralAsHour(typeSystem, sign, value, value0, pos); case HOUR_TO_MINUTE: return evaluateIntervalLiteralAsHourToMinute(typeSystem, sign, value, value0, pos); case HOUR_TO_SECOND: return evaluateIntervalLiteralAsHourToSecond(typeSystem, sign, value, value0, pos); case MINUTE: return evaluateIntervalLiteralAsMinute(typeSystem, sign, value, value0, pos); case MINUTE_TO_SECOND: return evaluateIntervalLiteralAsMinuteToSecond(typeSystem, sign, value, value0, pos); case SECOND: return evaluateIntervalLiteralAsSecond(typeSystem, sign, value, value0, pos); default: throw invalidValueException(pos, value0); } } private BigDecimal parseField(Matcher m, int i) { return new BigDecimal(m.group(i)); } private CalciteContextException invalidValueException(SqlParserPos pos, String value) { return SqlUtil.newContextException(pos, RESOURCE.unsupportedIntervalLiteral( "'" + value + "'", "INTERVAL " + toString())); } private CalciteContextException fieldExceedsPrecisionException( SqlParserPos pos, int sign, BigDecimal value, TimeUnit type, int precision) { if (sign == -1) { value = value.negate(); } return SqlUtil.newContextException(pos, RESOURCE.intervalFieldExceedsPrecision( value, type.name() + "(" + precision + ")")); } } // End SqlIntervalQualifier.java





© 2015 - 2024 Weber Informatics LLC | Privacy Policy