
io.questdb.griffin.SqlUtil Maven / Gradle / Ivy
Show all versions of questdb Show documentation
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2024 QuestDB
*
* Licensed 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 io.questdb.griffin;
import io.questdb.cairo.ColumnType;
import io.questdb.cairo.GeoHashes;
import io.questdb.cairo.ImplicitCastException;
import io.questdb.cairo.sql.Function;
import io.questdb.griffin.engine.functions.constants.Long256Constant;
import io.questdb.griffin.engine.functions.constants.Long256NullConstant;
import io.questdb.griffin.model.ExpressionNode;
import io.questdb.griffin.model.IntervalUtils;
import io.questdb.griffin.model.QueryColumn;
import io.questdb.griffin.model.QueryModel;
import io.questdb.std.CharSequenceHashSet;
import io.questdb.std.Chars;
import io.questdb.std.GenericLexer;
import io.questdb.std.Long256;
import io.questdb.std.Long256Acceptor;
import io.questdb.std.Long256FromCharSequenceDecoder;
import io.questdb.std.Long256Impl;
import io.questdb.std.LowerCaseCharSequenceObjHashMap;
import io.questdb.std.Numbers;
import io.questdb.std.NumericException;
import io.questdb.std.ObjList;
import io.questdb.std.ObjectPool;
import io.questdb.std.ThreadLocal;
import io.questdb.std.Uuid;
import io.questdb.std.datetime.DateFormat;
import io.questdb.std.datetime.microtime.Timestamps;
import io.questdb.std.datetime.millitime.DateFormatCompiler;
import io.questdb.std.datetime.millitime.DateFormatUtils;
import io.questdb.std.fastdouble.FastFloatParser;
import io.questdb.std.str.CharSink;
import io.questdb.std.str.Utf8Sequence;
import io.questdb.std.str.Utf8s;
import org.jetbrains.annotations.Nullable;
import static io.questdb.std.datetime.millitime.DateFormatUtils.*;
public class SqlUtil {
static final CharSequenceHashSet disallowedAliases = new CharSequenceHashSet();
private static final DateFormat[] DATE_FORMATS_FOR_TIMESTAMP;
private static final int DATE_FORMATS_FOR_TIMESTAMP_SIZE;
private static final ThreadLocal LONG256_FACTORY = new ThreadLocal<>(Long256ConstantFactory::new);
public static void addSelectStar(
QueryModel model,
ObjectPool queryColumnPool,
ObjectPool expressionNodePool
) throws SqlException {
model.addBottomUpColumn(nextColumn(queryColumnPool, expressionNodePool, "*", "*", 0));
model.setArtificialStar(true);
}
// used by Copier assembler
@SuppressWarnings("unused")
public static long dateToTimestamp(long millis) {
return millis != Numbers.LONG_NULL ? millis * 1000L : millis;
}
public static long expectMicros(CharSequence tok, int position) throws SqlException {
int k = -1;
final int len = tok.length();
// look for end of digits
for (int i = 0; i < len; i++) {
char c = tok.charAt(i);
if (c < '0' || c > '9') {
k = i;
break;
}
}
if (k == -1) {
throw SqlException.$(position + len, "expected interval qualifier in ").put(tok);
}
try {
long interval = Numbers.parseLong(tok, 0, k);
int nChars = len - k;
if (nChars > 2) {
throw SqlException.$(position + k, "expected 1/2 letter interval qualifier in ").put(tok);
}
switch (tok.charAt(k)) {
case 's':
if (nChars == 1) {
// seconds
return interval * Timestamps.SECOND_MICROS;
}
break;
case 'm':
if (nChars == 1) {
// minutes
return interval * Timestamps.MINUTE_MICROS;
} else {
if (tok.charAt(k + 1) == 's') {
// millis
return interval * Timestamps.MILLI_MICROS;
}
}
break;
case 'h':
if (nChars == 1) {
// hours
return interval * Timestamps.HOUR_MICROS;
}
break;
case 'd':
if (nChars == 1) {
// days
return interval * Timestamps.DAY_MICROS;
}
break;
case 'u':
if (nChars == 2 && tok.charAt(k + 1) == 's') {
return interval;
}
break;
default:
break;
}
} catch (NumericException ex) {
// Ignored
}
throw SqlException.$(position + len, "invalid interval qualifier ").put(tok);
}
public static long expectSeconds(CharSequence tok, int position) throws SqlException {
int k = -1;
final int len = tok.length();
// look for end of digits
for (int i = 0; i < len; i++) {
char c = tok.charAt(i);
if (c < '0' || c > '9') {
k = i;
break;
}
}
if (k == -1) {
throw SqlException.$(position + len, "expected interval qualifier in ").put(tok);
}
try {
long interval = Numbers.parseLong(tok, 0, k);
int nChars = len - k;
if (nChars > 1) {
throw SqlException.$(position + k, "expected single letter interval qualifier in ").put(tok);
}
switch (tok.charAt(k)) {
case 's': // seconds
return interval;
case 'm': // minutes
return interval * Timestamps.MINUTE_SECONDS;
case 'h': // hours
return interval * Timestamps.HOUR_SECONDS;
case 'd': // days
return interval * Timestamps.DAY_SECONDS;
default:
break;
}
} catch (NumericException ex) {
// Ignored
}
throw SqlException.$(position + len, "invalid interval qualifier ").put(tok);
}
/**
* Fetches next non-whitespace token that's not part of single or multiline comment.
*
* @param lexer input lexer
* @return with next valid token or null if end of input is reached .
*/
public static CharSequence fetchNext(GenericLexer lexer) throws SqlException {
return fetchNext(lexer, false);
}
/**
* Fetches next non-whitespace token that's not part of single or multiline comment.
*
* @param lexer The input lexer containing the token stream to process
* @param includeHints If true, hint block markers (/*+) are treated as valid tokens and returned;
* if false, hint blocks are treated as comments and skipped
* @return The next meaningful token as a CharSequence, or null if the end of input is reached
* @throws SqlException If a parsing error occurs while processing the token stream
* @see #fetchNextHintToken(GenericLexer) For handling tokens within hint blocks
*/
public static CharSequence fetchNext(GenericLexer lexer, boolean includeHints) throws SqlException {
int blockCount = 0;
boolean lineComment = false;
while (lexer.hasNext()) {
CharSequence cs = lexer.next();
if (lineComment) {
if (Chars.equals(cs, '\n') || Chars.equals(cs, '\r')) {
lineComment = false;
}
continue;
}
if (Chars.equals("--", cs)) {
lineComment = true;
continue;
}
if (Chars.equals("/*", cs)) {
blockCount++;
continue;
}
if (Chars.equals("/*+", cs) && (!includeHints || blockCount > 0)) {
blockCount++;
continue;
}
if (Chars.equals("*/", cs) && blockCount > 0) {
blockCount--;
continue;
}
if (blockCount == 0 && GenericLexer.WHITESPACE.excludes(cs)) {
// unclosed quote check
if (cs.length() == 1 && cs.charAt(0) == '"') {
throw SqlException.$(lexer.lastTokenPosition(), "unclosed quotation mark");
}
return cs;
}
}
return null;
}
/**
* Fetches the next non-whitespace, non-comment hint token from the lexer.
*
* This method should only be called after entering a hint block. Specifically,
* a previous call to {@link #fetchNext(GenericLexer, boolean)} must have returned
* a hint start token (/*+
) before this method can be used.
*
* The method processes the input stream, skipping over any nested comments and whitespace,
* and returns the next meaningful hint token. This allows for clean parsing of hint
* content without manual handling of comments and formatting characters.
*
* When the end of the hint block is reached, the method returns null, indicating
* no more hint tokens are available for processing.
*
* If a hint contains unbalanced quotes, the method will NOT throw an exception, instead
* it will consume all tokens until the end of the hint block is reached and then return null indicating
* the end of the hint block.
*
* @param lexer The input lexer containing the token stream to process
* @return The next meaningful hint token, or null if the end of the hint block is reached
* @see #fetchNext(GenericLexer, boolean) For entering the hint block initially
*/
public static CharSequence fetchNextHintToken(GenericLexer lexer) {
int blockCount = 0;
boolean lineComment = false;
boolean inError = false;
while (lexer.hasNext()) {
CharSequence cs = lexer.next();
if (lineComment) {
if (Chars.equals(cs, '\n') || Chars.equals(cs, '\r')) {
lineComment = false;
}
continue;
}
if (Chars.equals("--", cs)) {
lineComment = true;
continue;
}
if (Chars.equals("/*", cs)) {
blockCount++;
continue;
}
if (Chars.equals("/*+", cs)) {
// nested hints are treated as regular comments
blockCount++;
continue;
}
// end of hints or a nested comment
if (Chars.equals("*/", cs)) {
if (blockCount > 0) {
blockCount--;
continue;
}
return null;
}
if (!inError && blockCount == 0 && GenericLexer.WHITESPACE.excludes(cs)) {
// unclosed quote check
if (cs.length() == 1 && cs.charAt(0) == '"') {
inError = true;
} else {
return cs;
}
}
}
return null;
}
public static byte implicitCastAsByte(long value, int fromType) {
if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
return (byte) value;
}
throw ImplicitCastException.inconvertibleValue(value, fromType, ColumnType.BYTE);
}
public static char implicitCastAsChar(long value, int fromType) {
if (value >= 0 && value <= 9) {
return (char) (value + '0');
}
throw ImplicitCastException.inconvertibleValue(value, fromType, ColumnType.CHAR);
}
public static float implicitCastAsFloat(double value, int fromType) {
if ((value >= Float.MIN_VALUE && value <= Float.MAX_VALUE) || Numbers.isNull(value)) {
return (float) value;
}
throw ImplicitCastException.inconvertibleValue(value, fromType, ColumnType.FLOAT);
}
public static int implicitCastAsInt(long value, int fromType) {
if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
return (int) value;
}
throw ImplicitCastException.inconvertibleValue(value, fromType, ColumnType.INT);
}
public static short implicitCastAsShort(long value, int fromType) {
if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
return (short) value;
}
throw ImplicitCastException.inconvertibleValue(value, fromType, ColumnType.SHORT);
}
// used by bytecode assembler
@SuppressWarnings("unused")
public static byte implicitCastCharAsByte(char value, int toType) {
return implicitCastCharAsType(value, toType);
}
@SuppressWarnings("unused")
// used by copier bytecode assembler
public static byte implicitCastCharAsGeoHash(char value, int toType) {
int v;
// '0' .. '9' and 'A-Z', excl 'A', 'I', 'L', 'O'
if ((value >= '0' && value <= '9') || ((v = value | 32) > 'a' && v <= 'z' && v != 'i' && v != 'l' && v != 'o')) {
int toBits = ColumnType.getGeoHashBits(toType);
if (toBits < 5) {
// widening
return (byte) GeoHashes.widen(GeoHashes.encodeChar(value), 5, toBits);
}
if (toBits == 5) {
return GeoHashes.encodeChar(value);
}
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.CHAR, toType);
}
public static byte implicitCastCharAsType(char value, int toType) {
byte v = (byte) (value - '0');
if (v > -1 && v < 10) {
return v;
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.CHAR, toType);
}
@SuppressWarnings("unused")
// used by the row copier
public static byte implicitCastDoubleAsByte(double value) {
if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
return (byte) value;
}
if (Numbers.isNull(value)) {
return 0;
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.FLOAT, ColumnType.BYTE);
}
@SuppressWarnings("unused")
// used by the row copier
public static float implicitCastDoubleAsFloat(double value) {
final double d = Math.abs(value);
if ((d >= Float.MIN_VALUE && d <= Float.MAX_VALUE) || (Numbers.isNull(value) || d == 0.0)) {
return (float) value;
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.DOUBLE, ColumnType.FLOAT);
}
@SuppressWarnings("unused")
// used by the row copier
public static int implicitCastDoubleAsInt(double value) {
if (Numbers.isNull(value)) {
return Numbers.INT_NULL;
}
return implicitCastAsInt((long) value, ColumnType.LONG);
}
@SuppressWarnings("unused")
// used by the row copier
public static long implicitCastDoubleAsLong(double value) {
if (value > Long.MIN_VALUE && value <= Long.MAX_VALUE) {
return (long) value;
}
if (Numbers.isNull(value)) {
return Numbers.LONG_NULL;
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.DOUBLE, ColumnType.LONG);
}
@SuppressWarnings("unused")
// used by the row copier
public static short implicitCastDoubleAsShort(double value) {
if (Numbers.isNull(value)) {
return 0;
}
return implicitCastAsShort((long) value, ColumnType.LONG);
}
@SuppressWarnings("unused")
// used by the row copier
public static byte implicitCastFloatAsByte(float value) {
if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
return (byte) value;
}
if (Numbers.isNull(value)) {
return 0;
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.FLOAT, ColumnType.BYTE);
}
@SuppressWarnings("unused")
// used by the row copier
public static int implicitCastFloatAsInt(float value) {
if (value > Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
return (int) value;
}
if (Numbers.isNull(value)) {
return Numbers.INT_NULL;
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.FLOAT, ColumnType.INT);
}
@SuppressWarnings("unused")
// used by the row copier
public static long implicitCastFloatAsLong(float value) {
if (value > Long.MIN_VALUE && value <= Long.MAX_VALUE) {
return (long) value;
}
if (Numbers.isNull(value)) {
return Numbers.LONG_NULL;
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.FLOAT, ColumnType.LONG);
}
@SuppressWarnings("unused")
// used by the row copier
public static short implicitCastFloatAsShort(float value) {
if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
return (short) value;
}
if (Numbers.isNull(value)) {
return 0;
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.FLOAT, ColumnType.SHORT);
}
public static long implicitCastGeoHashAsGeoHash(long value, int fromType, int toType) {
final int fromBits = ColumnType.getGeoHashBits(fromType);
final int toBits = ColumnType.getGeoHashBits(toType);
assert fromBits >= toBits;
return GeoHashes.widen(value, fromBits, toBits);
}
@SuppressWarnings("unused")
// used by the row copier
public static byte implicitCastIntAsByte(int value) {
if (value != Numbers.INT_NULL) {
return implicitCastAsByte(value, ColumnType.INT);
}
return 0;
}
@SuppressWarnings("unused")
// used by the row copier
public static short implicitCastIntAsShort(int value) {
if (value != Numbers.INT_NULL) {
return implicitCastAsShort(value, ColumnType.INT);
}
return 0;
}
public static boolean implicitCastLong256AsStr(Long256 long256, CharSink> sink) {
if (Long256Impl.isNull(long256)) {
return false;
}
Numbers.appendLong256(long256, sink);
return true;
}
@SuppressWarnings("unused")
// used by the row copier
public static byte implicitCastLongAsByte(long value) {
if (value != Numbers.LONG_NULL) {
return implicitCastAsByte(value, ColumnType.LONG);
}
return 0;
}
@SuppressWarnings("unused")
// used by the row copier
public static int implicitCastLongAsInt(long value) {
if (value != Numbers.LONG_NULL) {
return implicitCastAsInt(value, ColumnType.LONG);
}
return Numbers.INT_NULL;
}
@SuppressWarnings("unused")
// used by the row copier
public static short implicitCastLongAsShort(long value) {
if (value != Numbers.LONG_NULL) {
return implicitCastAsShort(value, ColumnType.LONG);
}
return 0;
}
@SuppressWarnings("unused")
// used by the row copier
public static byte implicitCastShortAsByte(short value) {
return implicitCastAsByte(value, ColumnType.SHORT);
}
public static byte implicitCastStrAsByte(CharSequence value) {
if (value != null) {
try {
int res = Numbers.parseInt(value);
if (res >= Byte.MIN_VALUE && res <= Byte.MAX_VALUE) {
return (byte) res;
}
} catch (NumericException ignore) {
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.STRING, ColumnType.BYTE);
}
return 0;
}
public static char implicitCastStrAsChar(CharSequence value) {
if (value == null || value.length() == 0) {
return 0;
}
if (value.length() == 1) {
return value.charAt(0);
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.STRING, ColumnType.CHAR);
}
public static long implicitCastStrAsDate(CharSequence value) {
return implicitCastStrVarcharAsDate0(value, ColumnType.STRING);
}
public static double implicitCastStrAsDouble(CharSequence value) {
if (value != null) {
try {
return Numbers.parseDouble(value);
} catch (NumericException e) {
throw ImplicitCastException.inconvertibleValue(value, ColumnType.STRING, ColumnType.DOUBLE);
}
}
return Double.NaN;
}
public static float implicitCastStrAsFloat(CharSequence value) {
if (value != null) {
try {
return FastFloatParser.parseFloat(value, 0, value.length(), true);
} catch (NumericException ignored) {
throw ImplicitCastException.inconvertibleValue(value, ColumnType.STRING, ColumnType.FLOAT);
}
}
return Float.NaN;
}
public static int implicitCastStrAsIPv4(CharSequence value) {
if (value != null) {
try {
return Numbers.parseIPv4(value);
} catch (NumericException exception) {
throw ImplicitCastException.instance().put("invalid IPv4 format: ").put(value);
}
}
return Numbers.IPv4_NULL;
}
public static int implicitCastStrAsIPv4(Utf8Sequence value) {
if (value != null) {
try {
return Numbers.parseIPv4(value);
} catch (NumericException exception) {
throw ImplicitCastException.instance().put("invalid IPv4 format: ").put(value);
}
}
return Numbers.IPv4_NULL;
}
public static int implicitCastStrAsInt(CharSequence value) {
if (value != null) {
try {
return Numbers.parseInt(value);
} catch (NumericException e) {
throw ImplicitCastException.inconvertibleValue(value, ColumnType.STRING, ColumnType.INT);
}
}
return Numbers.INT_NULL;
}
public static long implicitCastStrAsLong(CharSequence value) {
if (value != null) {
try {
return Numbers.parseLong(value);
} catch (NumericException e) {
throw ImplicitCastException.inconvertibleValue(value, ColumnType.STRING, ColumnType.LONG);
}
}
return Numbers.LONG_NULL;
}
public static Long256Constant implicitCastStrAsLong256(CharSequence value) {
if (value == null || value.length() == 0) {
return Long256NullConstant.INSTANCE;
}
int start = 0;
int end = value.length();
if (end > 2 && value.charAt(start) == '0' && (value.charAt(start + 1) | 32) == 'x') {
start += 2;
}
Long256ConstantFactory factory = LONG256_FACTORY.get();
Long256FromCharSequenceDecoder.decode(value, start, end, factory);
return factory.pop();
}
public static void implicitCastStrAsLong256(CharSequence value, Long256Acceptor long256Acceptor) {
if (value != null) {
Long256FromCharSequenceDecoder.decode(value, 0, value.length(), long256Acceptor);
} else {
long256Acceptor.setAll(
Long256Impl.NULL_LONG256.getLong0(),
Long256Impl.NULL_LONG256.getLong1(),
Long256Impl.NULL_LONG256.getLong2(),
Long256Impl.NULL_LONG256.getLong3()
);
}
}
public static short implicitCastStrAsShort(@Nullable CharSequence value) {
try {
return value != null ? Numbers.parseShort(value) : 0;
} catch (NumericException ignore) {
throw ImplicitCastException.inconvertibleValue(value, ColumnType.STRING, ColumnType.SHORT);
}
}
public static long implicitCastStrAsTimestamp(CharSequence value) {
return implicitCastStrVarcharAsTimestamp0(value, ColumnType.STRING);
}
public static void implicitCastStrAsUuid(CharSequence str, Uuid uuid) {
if (str == null || str.length() == 0) {
uuid.ofNull();
return;
}
try {
uuid.of(str);
} catch (NumericException e) {
throw ImplicitCastException.inconvertibleValue(str, ColumnType.STRING, ColumnType.UUID);
}
}
public static void implicitCastStrAsUuid(Utf8Sequence str, Uuid uuid) {
if (str == null || str.size() == 0) {
uuid.ofNull();
return;
}
try {
uuid.of(str);
} catch (NumericException e) {
throw ImplicitCastException.inconvertibleValue(str, ColumnType.STRING, ColumnType.UUID);
}
}
public static long implicitCastSymbolAsTimestamp(CharSequence value) {
return implicitCastStrVarcharAsTimestamp0(value, ColumnType.SYMBOL);
}
public static boolean implicitCastUuidAsStr(long lo, long hi, CharSink> sink) {
if (Uuid.isNull(lo, hi)) {
return false;
}
Numbers.appendUuid(lo, hi, sink);
return true;
}
public static byte implicitCastVarcharAsByte(Utf8Sequence value) {
if (value != null) {
try {
int res = Numbers.parseInt(value);
if (res >= Byte.MIN_VALUE && res <= Byte.MAX_VALUE) {
return (byte) res;
}
} catch (NumericException ignore) {
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.VARCHAR, ColumnType.BYTE);
}
return 0;
}
public static char implicitCastVarcharAsChar(Utf8Sequence value) {
if (value == null || value.size() == 0) {
return 0;
}
int encodedResult = Utf8s.utf8CharDecode(value);
short consumedBytes = Numbers.decodeLowShort(encodedResult);
if (consumedBytes == value.size()) {
return (char) Numbers.decodeHighShort(encodedResult);
}
throw ImplicitCastException.inconvertibleValue(value, ColumnType.VARCHAR, ColumnType.CHAR);
}
public static long implicitCastVarcharAsDate(CharSequence value) {
return implicitCastStrVarcharAsDate0(value, ColumnType.VARCHAR);
}
public static double implicitCastVarcharAsDouble(Utf8Sequence value) {
if (value != null) {
try {
return Numbers.parseDouble(value.asAsciiCharSequence());
} catch (NumericException e) {
throw ImplicitCastException.inconvertibleValue(value, ColumnType.VARCHAR, ColumnType.DOUBLE);
}
}
return Double.NaN;
}
public static float implicitCastVarcharAsFloat(Utf8Sequence value) {
if (value != null) {
try {
CharSequence ascii = value.asAsciiCharSequence();
return FastFloatParser.parseFloat(ascii, 0, ascii.length(), true);
} catch (NumericException ignored) {
throw ImplicitCastException.inconvertibleValue(value, ColumnType.VARCHAR, ColumnType.FLOAT);
}
}
return Float.NaN;
}
public static int implicitCastVarcharAsInt(Utf8Sequence value) {
if (value != null) {
try {
return Numbers.parseInt(value);
} catch (NumericException e) {
throw ImplicitCastException.inconvertibleValue(value, ColumnType.VARCHAR, ColumnType.INT);
}
}
return Numbers.INT_NULL;
}
public static long implicitCastVarcharAsLong(Utf8Sequence value) {
if (value != null) {
try {
return Numbers.parseLong(value);
} catch (NumericException e) {
throw ImplicitCastException.inconvertibleValue(value, ColumnType.VARCHAR, ColumnType.LONG);
}
}
return Numbers.LONG_NULL;
}
public static short implicitCastVarcharAsShort(@Nullable Utf8Sequence value) {
try {
return value != null ? Numbers.parseShort(value) : 0;
} catch (NumericException ignore) {
throw ImplicitCastException.inconvertibleValue(value, ColumnType.VARCHAR, ColumnType.SHORT);
}
}
public static long implicitCastVarcharAsTimestamp(CharSequence value) {
return implicitCastStrVarcharAsTimestamp0(value, ColumnType.VARCHAR);
}
public static boolean isNotPlainSelectModel(QueryModel model) {
return model.getTableName() != null
|| model.getGroupBy().size() > 0
|| model.getJoinModels().size() > 1
|| model.getLatestByType() != QueryModel.LATEST_BY_NONE
|| model.getUnionModel() != null;
}
public static boolean isParallelismSupported(ObjList functions) {
for (int i = 0, n = functions.size(); i < n; i++) {
if (!functions.getQuick(i).supportsParallelism()) {
return false;
}
}
return true;
}
/**
* Returns true if the model stands for a SELECT ... FROM tab; or a SELECT ... FROM tab WHERE ...; query.
* We're aiming for potential page frame support with this check.
*/
public static boolean isPlainSelect(QueryModel model) {
while (model != null) {
if (model.getSelectModelType() != QueryModel.SELECT_MODEL_NONE
|| model.getGroupBy().size() > 0
|| model.getJoinModels().size() > 1
|| model.getLatestByType() != QueryModel.LATEST_BY_NONE
|| model.getUnionModel() != null) {
return false;
}
model = model.getNestedModel();
}
return true;
}
public static ExpressionNode nextExpr(ObjectPool pool, int exprNodeType, CharSequence token, int position) {
return pool.next().of(exprNodeType, token, 0, position);
}
/**
* Parses partial representation of timestamp with time zone.
*
* @param value the characters representing timestamp
* @param tupleIndex the tuple index for insert SQL, which inserts multiple rows at once
* @param targetColumnType the target column type, which might be different from timestamp
* @return epoch offset
* @throws ImplicitCastException inconvertible type error.
*/
public static long parseFloorPartialTimestamp(CharSequence value, int tupleIndex, int sourceColumnType, int targetColumnType) {
try {
return IntervalUtils.parseFloorPartialTimestamp(value);
} catch (NumericException e) {
throw ImplicitCastException.inconvertibleValue(tupleIndex, value, sourceColumnType, targetColumnType);
}
}
public static short toPersistedTypeTag(CharSequence tok, int tokPosition) throws SqlException {
final short typeTag = ColumnType.tagOf(tok);
if (typeTag == -1) {
throw SqlException.$(tokPosition, "unsupported column type: ").put(tok);
}
if (ColumnType.isPersisted(typeTag)) {
return typeTag;
}
throw SqlException.$(tokPosition, "non-persisted type: ").put(tok);
}
private static long implicitCastStrVarcharAsDate0(CharSequence value, int columnType) {
assert columnType == ColumnType.VARCHAR || columnType == ColumnType.STRING;
try {
return DateFormatUtils.parseDate(value);
} catch (NumericException e) {
throw ImplicitCastException.inconvertibleValue(value, columnType, ColumnType.DATE);
}
}
private static long implicitCastStrVarcharAsTimestamp0(CharSequence value, int columnType) {
assert columnType == ColumnType.STRING || columnType == ColumnType.VARCHAR || columnType == ColumnType.SYMBOL;
if (value != null) {
try {
return Numbers.parseLong(value);
} catch (NumericException ignore) {
}
// Parse as ISO with variable length.
try {
return IntervalUtils.parseFloorPartialTimestamp(value);
} catch (NumericException ignore) {
}
final int hi = value.length();
for (int i = 0; i < DATE_FORMATS_FOR_TIMESTAMP_SIZE; i++) {
try {
//
return DATE_FORMATS_FOR_TIMESTAMP[i].parse(value, 0, hi, EN_LOCALE) * 1000L;
} catch (NumericException ignore) {
}
}
throw ImplicitCastException.inconvertibleValue(value, columnType, ColumnType.TIMESTAMP);
}
return Numbers.LONG_NULL;
}
static CharSequence createColumnAlias(
CharacterStore store,
CharSequence base,
int indexOfDot,
LowerCaseCharSequenceObjHashMap aliasToColumnMap
) {
return createColumnAlias(store, base, indexOfDot, aliasToColumnMap, false);
}
static CharSequence createColumnAlias(
CharacterStore store,
CharSequence base,
int indexOfDot,
LowerCaseCharSequenceObjHashMap aliasToColumnMap,
boolean nonLiteral
) {
final boolean disallowed = nonLiteral && disallowedAliases.contains(base);
// short and sweet version
if (indexOfDot == -1 && !disallowed && aliasToColumnMap.excludes(base)) {
return base;
}
final CharacterStoreEntry characterStoreEntry = store.newEntry();
if (indexOfDot == -1) {
if (disallowed || Numbers.parseIntQuiet(base) != Numbers.INT_NULL) {
characterStoreEntry.put("column");
} else {
characterStoreEntry.put(base);
}
} else {
if (indexOfDot + 1 == base.length()) {
characterStoreEntry.put("column");
} else {
characterStoreEntry.put(base, indexOfDot + 1, base.length());
}
}
int len = characterStoreEntry.length();
int sequence = 0;
while (true) {
if (sequence > 0) {
characterStoreEntry.trimTo(len);
characterStoreEntry.put(sequence);
}
sequence++;
CharSequence alias = characterStoreEntry.toImmutable();
if (aliasToColumnMap.excludes(alias)) {
return alias;
}
}
}
static QueryColumn nextColumn(
ObjectPool queryColumnPool,
ObjectPool sqlNodePool,
CharSequence alias,
CharSequence column,
int position
) {
return queryColumnPool.next().of(alias, nextLiteral(sqlNodePool, column, position));
}
static ExpressionNode nextConstant(ObjectPool pool, CharSequence token, int position) {
return nextExpr(pool, ExpressionNode.CONSTANT, token, position);
}
static ExpressionNode nextLiteral(ObjectPool pool, CharSequence token, int position) {
return nextExpr(pool, ExpressionNode.LITERAL, token, position);
}
private static class Long256ConstantFactory implements Long256Acceptor {
private Long256Constant long256;
@Override
public void setAll(long l0, long l1, long l2, long l3) {
assert long256 == null;
long256 = new Long256Constant(l0, l1, l2, l3);
}
Long256Constant pop() {
Long256Constant v = long256;
long256 = null;
return v;
}
}
static {
// note: it's safe to take any registry (new or old) because we don't use precedence here
OperatorRegistry registry = OperatorExpression.getRegistry();
for (int i = 0, n = registry.operators.size(); i < n; i++) {
SqlUtil.disallowedAliases.add(registry.operators.getQuick(i).operator.token);
}
SqlUtil.disallowedAliases.add("");
final DateFormatCompiler milliCompiler = new DateFormatCompiler();
final DateFormat pgDateTimeFormat = milliCompiler.compile("y-MM-dd HH:mm:ssz");
// we are using "millis" compiler deliberately because clients encode millis into strings
DATE_FORMATS_FOR_TIMESTAMP = new DateFormat[]{
PG_DATE_Z_FORMAT,
PG_DATE_MILLI_TIME_Z_FORMAT,
pgDateTimeFormat
};
DATE_FORMATS_FOR_TIMESTAMP_SIZE = DATE_FORMATS_FOR_TIMESTAMP.length;
}
}