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

io.questdb.griffin.FunctionParser Maven / Gradle / Ivy

/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2022 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.CairoConfiguration;
import io.questdb.cairo.ColumnType;
import io.questdb.cairo.sql.*;
import io.questdb.griffin.engine.functions.AbstractUnaryTimestampFunction;
import io.questdb.griffin.engine.functions.CursorFunction;
import io.questdb.griffin.engine.functions.GroupByFunction;
import io.questdb.griffin.engine.functions.bind.IndexedParameterLinkFunction;
import io.questdb.griffin.engine.functions.bind.NamedParameterLinkFunction;
import io.questdb.griffin.engine.functions.cast.CastGeoHashToGeoHashFunctionFactory;
import io.questdb.griffin.engine.functions.cast.CastStrToGeoHashFunctionFactory;
import io.questdb.griffin.engine.functions.cast.CastStrToTimestampFunctionFactory;
import io.questdb.griffin.engine.functions.cast.CastSymbolToTimestampFunctionFactory;
import io.questdb.griffin.engine.functions.columns.*;
import io.questdb.griffin.engine.functions.constants.*;
import io.questdb.griffin.model.ExpressionNode;
import io.questdb.griffin.model.IntervalUtils;
import io.questdb.log.Log;
import io.questdb.log.LogFactory;
import io.questdb.std.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayDeque;

import static io.questdb.griffin.SqlKeywords.isNullKeyword;
import static io.questdb.griffin.SqlKeywords.startsWithGeoHashKeyword;

public class FunctionParser implements PostOrderTreeTraversalAlgo.Visitor, Mutable {
    private static final Log LOG = LogFactory.getLog(FunctionParser.class);

    // order of values matters here, partial match must have greater value than fuzzy match
    private static final int MATCH_NO_MATCH = 0;
    private static final int MATCH_FUZZY_MATCH = 1;
    private static final int MATCH_PARTIAL_MATCH = 2;
    private static final int MATCH_EXACT_MATCH = 3;

    private final ObjList mutableArgs = new ObjList<>();
    private final IntList mutableArgPositions = new IntList();
    private final ArrayDeque functionStack = new ArrayDeque<>();
    private final IntStack positionStack = new IntStack();
    private final PostOrderTreeTraversalAlgo traverseAlgo = new PostOrderTreeTraversalAlgo();
    private final CairoConfiguration configuration;
    private final ArrayDeque metadataStack = new ArrayDeque<>();
    private final FunctionFactoryCache functionFactoryCache;
    private final IntList undefinedVariables = new IntList();
    private final Long256Impl long256Sink = new Long256Impl();
    private RecordMetadata metadata;
    private SqlCodeGenerator sqlCodeGenerator;
    private SqlExecutionContext sqlExecutionContext;

    public FunctionParser(CairoConfiguration configuration, FunctionFactoryCache functionFactoryCache) {
        this.configuration = configuration;
        this.functionFactoryCache = functionFactoryCache;
    }

    @NotNull
    public static ScalarFunction createColumn(
            int position,
            CharSequence name,
            RecordMetadata metadata
    ) throws SqlException {
        final int index = metadata.getColumnIndexQuiet(name);

        if (index == -1) {
            throw SqlException.invalidColumn(position, name);
        }

        int columnType = metadata.getColumnType(index);
        switch (ColumnType.tagOf(columnType)) {
            case ColumnType.BOOLEAN:
                return BooleanColumn.newInstance(index);
            case ColumnType.BYTE:
                return ByteColumn.newInstance(index);
            case ColumnType.SHORT:
                return ShortColumn.newInstance(index);
            case ColumnType.CHAR:
                return CharColumn.newInstance(index);
            case ColumnType.INT:
                return IntColumn.newInstance(index);
            case ColumnType.LONG:
                return LongColumn.newInstance(index);
            case ColumnType.FLOAT:
                return FloatColumn.newInstance(index);
            case ColumnType.DOUBLE:
                return DoubleColumn.newInstance(index);
            case ColumnType.STRING:
                return StrColumn.newInstance(index);
            case ColumnType.SYMBOL:
                return new SymbolColumn(index, metadata.isSymbolTableStatic(index));
            case ColumnType.BINARY:
                return BinColumn.newInstance(index);
            case ColumnType.DATE:
                return DateColumn.newInstance(index);
            case ColumnType.TIMESTAMP:
                return TimestampColumn.newInstance(index);
            case ColumnType.RECORD:
                return new RecordColumn(index, metadata.getMetadata(index));
            case ColumnType.GEOBYTE:
                return GeoByteColumn.newInstance(index, columnType);
            case ColumnType.GEOSHORT:
                return GeoShortColumn.newInstance(index, columnType);
            case ColumnType.GEOINT:
                return GeoIntColumn.newInstance(index, columnType);
            case ColumnType.GEOLONG:
                return GeoLongColumn.newInstance(index, columnType);
            case ColumnType.NULL:
                return NullConstant.NULL;
            case ColumnType.LONG256:
                return Long256Column.newInstance(index);
            case ColumnType.LONG128:
                return Long128Column.newInstance(index);
            default:
                throw SqlException.position(position)
                        .put("unsupported column type ")
                        .put(ColumnType.nameOf(columnType));
        }
    }

    @Override
    public void clear() {
        this.sqlExecutionContext = null;
    }

    public Function createBindVariable(SqlExecutionContext sqlExecutionContext, int position, CharSequence name) throws SqlException {
        this.sqlExecutionContext = sqlExecutionContext;
        if (name != null && name.length() > 0) {
            switch (name.charAt(0)) {
                case ':':
                    return createNamedParameter(position, name);
                case '$':
                    return parseIndexedParameter(position, name);
                default:
                    return new StrConstant(name);
            }
        }
        return NullConstant.NULL;
    }

    public Function createImplicitCast(int position, Function function, int toType) throws SqlException {
        Function cast = createImplicitCastOrNull(position, function, toType);
        if (cast != null && cast.isConstant()) {
            Function constant = functionToConstant(cast);
            // incoming function is now converted to a constant and can be closed here
            // since the returning constant will not use the function as underlying arg
            function.close();
            return constant;
        }
        // Do not close incoming function if cast is not a constant
        // it will be used inside the cast as an argument
        return cast;
    }

    public boolean findNoArgFunction(ExpressionNode node) throws SqlException {
        final ObjList overload = functionFactoryCache.getOverloadList(node.token);
        if (overload != null) {
            for (int i = 0, n = overload.size(); i < n; i++) {
                if (overload.getQuick(i).getSigArgCount() == 0) {
                    return true;
                }
            }
        }
        return false;
    }

    public FunctionFactoryCache getFunctionFactoryCache() {
        return functionFactoryCache;
    }

    /**
     * Creates function instance. When node type is {@link ExpressionNode#LITERAL} a column or parameter
     * function is returned. We will be using the supplied {@link #metadata} to resolve type of column. When node token
     * begins with ':' parameter is looked up from the supplied bindVariableService.
     * 

* When node type is {@link ExpressionNode#CONSTANT} a constant function is returned. Type of constant is * inferred from value of node token. *

* When node type is {@link ExpressionNode#QUERY} a cursor function is returned. Cursor function can be wrapping * stateful instance of {@link RecordCursorFactory} that has to be closed when disposed of. * Such instances are added to the supplied list of {@link java.io.Closeable} items. *

* For any other node type a function instance is created using {@link FunctionFactory} * * @param node expression node * @param metadata metadata for resolving types of columns. * @param executionContext for resolving parameters, which are ':' prefixed literals and creating cursors * @return function instance * @throws SqlException when function cannot be created. Can be one of list but not limited to *

    *
  • column not found
  • *
  • parameter not found
  • *
  • unknown function name
  • *
  • function argument mismatch
  • *
  • sql compilation errors in case of lambda
  • *
*/ public Function parseFunction( ExpressionNode node, RecordMetadata metadata, SqlExecutionContext executionContext ) throws SqlException { this.sqlExecutionContext = executionContext; if (this.metadata != null) { metadataStack.push(this.metadata); } try { this.metadata = metadata; try { traverseAlgo.traverse(node, this); } catch (SqlException e) { // release parsed functions for (int i = functionStack.size(); i > 0; i--) { Misc.free(functionStack.poll()); } positionStack.clear(); throw e; } final Function function = functionStack.poll(); positionStack.pop(); assert positionStack.size() == functionStack.size(); if (function != null && function.isConstant() && (function instanceof ScalarFunction)) { return functionToConstant(function); } return function; } finally { if (metadataStack.size() == 0) { this.metadata = null; } else { this.metadata = metadataStack.poll(); } } } public void setSqlCodeGenerator(SqlCodeGenerator sqlCodeGenerator) { this.sqlCodeGenerator = sqlCodeGenerator; } @Override public void visit(ExpressionNode node) throws SqlException { int argCount = node.paramCount; if (argCount == 0) { switch (node.type) { case ExpressionNode.LITERAL: functionStack.push(createColumn(node.position, node.token)); break; case ExpressionNode.BIND_VARIABLE: functionStack.push(createBindVariable0(node.position, node.token)); break; case ExpressionNode.MEMBER_ACCESS: functionStack.push(new StrConstant(node.token)); break; case ExpressionNode.CONSTANT: functionStack.push(createConstant(node.position, node.token)); break; case ExpressionNode.QUERY: functionStack.push(createCursorFunction(node)); break; default: // lookup zero arg function from symbol table functionStack.push(createFunction(node, null, null)); break; } } else { mutableArgs.clear(); mutableArgs.setPos(argCount); mutableArgPositions.clear(); mutableArgPositions.setPos(argCount); for (int n = 0; n < argCount; n++) { final Function arg = functionStack.poll(); final int pos = positionStack.pop(); mutableArgs.setQuick(n, arg); mutableArgPositions.setQuick(n, pos); if (arg instanceof GroupByFunction) { Misc.freeObjList(mutableArgs); throw SqlException.position(pos).put("Aggregate function cannot be passed as an argument"); } } functionStack.push(createFunction(node, mutableArgs, mutableArgPositions)); } positionStack.push(node.position); } private static SqlException invalidFunction(ExpressionNode node, ObjList args) { SqlException ex = SqlException.position(node.position); ex.put("unknown function name"); ex.put(": "); ex.put(node.token); ex.put('('); if (args != null) { for (int i = 0, n = args.size(); i < n; i++) { if (i > 0) { ex.put(','); } ex.put(ColumnType.nameOf(args.getQuick(i).getType())); } } ex.put(')'); Misc.freeObjList(args); return ex; } private static SqlException invalidArgument(ExpressionNode node, ObjList args, FunctionFactoryDescriptor descriptor) { SqlException ex = SqlException.position(node.position); ex.put("unexpected argument for function: "); ex.put(node.token); ex.put(". expected args: "); ex.put('('); if (descriptor != null) { for (int i = 0, n = descriptor.getSigArgCount(); i < n; i++) { if (i > 0) { ex.put(','); } final int mask = descriptor.getArgTypeMask(i); ex.put(ColumnType.nameOf(FunctionFactoryDescriptor.toType(mask))); if (FunctionFactoryDescriptor.isArray(mask)) { ex.put("[]"); } if (FunctionFactoryDescriptor.isConstant(mask)) { ex.put(" constant"); } } } ex.put("). actual args: "); ex.put('('); if (args != null) { for (int i = 0, n = args.size(); i < n; i++) { if (i > 0) { ex.put(','); } Function arg = args.getQuick(i); ex.put(ColumnType.nameOf(arg.getType())); if (arg.isConstant()) { ex.put(" constant"); } } } ex.put(')'); Misc.freeObjList(args); return ex; } private Function checkAndCreateFunction( FunctionFactory factory, @Transient ObjList args, @Transient IntList argPositions, @Transient ExpressionNode node, CairoConfiguration configuration ) throws SqlException { final int position = node.position; Function function; try { LOG.debug().$("call ").$(node).$(" -> ").$(factory.getSignature()).$(); function = factory.newInstance(position, args, argPositions, configuration, sqlExecutionContext); } catch (SqlException e) { Misc.freeObjList(args); throw e; } catch (Throwable e) { LOG.error().$("exception in function factory: ").$(e).$(); Misc.freeObjList(args); throw SqlException.position(position).put("exception in function factory"); } if (function == null) { LOG.error().$("NULL function").$(" [signature=").$(factory.getSignature()).$(",class=").$(factory.getClass().getName()).$(']').$(); Misc.freeObjList(args); throw SqlException.position(position).put("bad function factory (NULL), check log"); } return function; } private long convertToTimestamp(CharSequence str, int position) throws SqlException { try { return IntervalUtils.parseFloorPartialDate(str); } catch (NumericException e) { throw SqlException.invalidDate(position); } } private Function createBindVariable0(int position, CharSequence name) throws SqlException { if (name.charAt(0) != ':') { return parseIndexedParameter(position, name); } return createNamedParameter(position, name); } private Function createColumn(int position, CharSequence columnName) throws SqlException { return createColumn(position, columnName, metadata); } private Function createConstant(int position, final CharSequence tok) throws SqlException { final int len = tok.length(); if (isNullKeyword(tok)) { return NullConstant.NULL; } if (Chars.isQuoted(tok)) { if (len == 3) { // this is 'x' - char return CharConstant.newInstance(tok.charAt(1)); } if (len == 2) { // empty return StrConstant.EMPTY; } return new StrConstant(tok); } // special case E'str' // we treat it like normal string for now if (len > 2 && tok.charAt(0) == 'E' && tok.charAt(1) == '\'') { return new StrConstant(Chars.toString(tok, 2, len - 1)); } if (SqlKeywords.isTrueKeyword(tok)) { return BooleanConstant.TRUE; } if (SqlKeywords.isFalseKeyword(tok)) { return BooleanConstant.FALSE; } try { return IntConstant.newInstance(Numbers.parseInt(tok)); } catch (NumericException ignore) { } try { return LongConstant.newInstance(Numbers.parseLong(tok)); } catch (NumericException ignore) { } try { return DoubleConstant.newInstance(Numbers.parseDouble(tok)); } catch (NumericException ignore) { } try { return FloatConstant.newInstance(Numbers.parseFloat(tok)); } catch (NumericException ignore) { } // type constant for 'CAST' operation final short columnType = ColumnType.tagOf(tok); if ( (columnType >= ColumnType.BOOLEAN && columnType <= ColumnType.BINARY) || columnType == ColumnType.REGCLASS || columnType == ColumnType.REGPROCEDURE || columnType == ColumnType.ARRAY_STRING ) { return Constants.getTypeConstant(columnType); } // geohash type constant if (startsWithGeoHashKeyword(tok)) { return GeoHashTypeConstant.getInstanceByPrecision( GeoHashUtil.parseGeoHashBits(position, 7, tok)); } if (len > 1 && tok.charAt(0) == '#') { ConstantFunction geoConstant = GeoHashUtil.parseGeoHashConstant(position, tok, len); if (geoConstant != null) { return geoConstant; } } // long256 if (Numbers.extractLong256(tok, len, long256Sink)) { return new Long256Constant(long256Sink); // values are copied from this sink } throw SqlException.position(position).put("invalid constant: ").put(tok); } private Function createCursorFunction(ExpressionNode node) throws SqlException { assert node.queryModel != null; return new CursorFunction(sqlCodeGenerator.generate(node.queryModel, sqlExecutionContext)); } private Function createFunction( ExpressionNode node, @Transient ObjList args, @Transient IntList argPositions ) throws SqlException { final ObjList overload = functionFactoryCache.getOverloadList(node.token); if (overload == null) { throw invalidFunction(node, args); } final int argCount = args == null ? 0 : args.size(); FunctionFactory candidate = null; FunctionFactoryDescriptor candidateDescriptor = null; boolean candidateSigVarArgConst = false; int candidateSigArgCount = 0; int candidateSigArgTypeSum = -1; int bestMatch = MATCH_NO_MATCH; undefinedVariables.clear(); // find all undefined args for the purpose of setting // their types when we find suitable candidate function for (int i = 0; i < argCount; i++) { if (args.getQuick(i).isUndefined()) { undefinedVariables.add(i); } } for (int i = 0, n = overload.size(); i < n; i++) { final FunctionFactoryDescriptor descriptor = overload.getQuick(i); final FunctionFactory factory = descriptor.getFactory(); int sigArgCount = descriptor.getSigArgCount(); final boolean sigVarArg; final boolean sigVarArgConst; if (sigArgCount > 0) { final int lastSigArgMask = descriptor.getArgTypeMask(sigArgCount - 1); sigVarArg = FunctionFactoryDescriptor.toType(lastSigArgMask) == ColumnType.VAR_ARG; sigVarArgConst = FunctionFactoryDescriptor.isConstant(lastSigArgMask); } else { sigVarArg = false; sigVarArgConst = false; } if (sigVarArg) { sigArgCount--; } if (argCount == 0 && sigArgCount == 0) { // this is no-arg function, match right away return checkAndCreateFunction(factory, args, argPositions, node, configuration); } // otherwise, is number of arguments the same? if (candidateDescriptor == null) { candidateDescriptor = descriptor; } if (sigArgCount == argCount || (sigVarArg && argCount >= sigArgCount)) { int match = MATCH_NO_MATCH; // no match if (sigArgCount == 0) { match = MATCH_EXACT_MATCH; } int sigArgTypeSum = 0; for (int k = 0; k < sigArgCount; k++) { final Function arg = args.getQuick(k); final int sigArgTypeMask = descriptor.getArgTypeMask(k); if (FunctionFactoryDescriptor.isConstant(sigArgTypeMask) && !arg.isConstant()) { match = MATCH_NO_MATCH; // no match break; } final boolean isArray = FunctionFactoryDescriptor.isArray(sigArgTypeMask); final boolean isScalar = arg instanceof ScalarFunction; if ((isArray && isScalar) || (!isArray && !isScalar)) { match = MATCH_NO_MATCH; // no match break; } final short sigArgType = FunctionFactoryDescriptor.toType(sigArgTypeMask); final int argType = arg.getType(); final short argTypeTag = ColumnType.tagOf(argType); final short sigArgTypeTag = ColumnType.tagOf(sigArgType); if (sigArgTypeTag == argTypeTag || (sigArgTypeTag == ColumnType.GEOHASH && ColumnType.isGeoHash(argType))) { switch (match) { case MATCH_NO_MATCH: // was it no match match = MATCH_EXACT_MATCH; break; case MATCH_FUZZY_MATCH: // was it fuzzy match ? match = MATCH_PARTIAL_MATCH; // this is mixed match, fuzzy and exact break; default: // don't change match otherwise break; } continue; } int overloadDistance = ColumnType.overloadDistance(argTypeTag, sigArgType); // NULL to any is 0 sigArgTypeSum += overloadDistance; // Overload with cast to higher precision boolean overloadPossible = overloadDistance != ColumnType.NO_OVERLOAD; // Overload when arg is double NaN to func which accepts INT, LONG overloadPossible |= argTypeTag == ColumnType.DOUBLE && arg.isConstant() && Double.isNaN(arg.getDouble(null)) && (sigArgTypeTag == ColumnType.LONG || sigArgTypeTag == ColumnType.INT); // Implicit cast from CHAR to STRING overloadPossible |= argTypeTag == ColumnType.CHAR && sigArgTypeTag == ColumnType.STRING; // Implicit cast from STRING to TIMESTAMP overloadPossible |= argTypeTag == ColumnType.STRING && arg.isConstant() && sigArgTypeTag == ColumnType.TIMESTAMP && !factory.isGroupBy(); // Implicit cast from STRING to GEOHASH overloadPossible |= argTypeTag == ColumnType.STRING && sigArgTypeTag == ColumnType.GEOHASH && !factory.isGroupBy(); // Implicit cast from SYMBOL to TIMESTAMP overloadPossible |= argTypeTag == ColumnType.SYMBOL && arg.isConstant() && sigArgTypeTag == ColumnType.TIMESTAMP && !factory.isGroupBy(); overloadPossible |= arg.isUndefined(); // can we use overload mechanism? if (overloadPossible) { switch (match) { case MATCH_NO_MATCH: // no match? if (argTypeTag == ColumnType.NULL) { match = MATCH_PARTIAL_MATCH; } else { match = MATCH_FUZZY_MATCH; // upgrade to fuzzy match } break; case MATCH_EXACT_MATCH: // was it full match so far? ? oh, well, fuzzy now match = MATCH_PARTIAL_MATCH; // downgrade break; default: break; // don't change match otherwise } } else { // types mismatch match = MATCH_NO_MATCH; break; } } if (match == MATCH_NO_MATCH) { continue; } if (match == MATCH_EXACT_MATCH || match >= bestMatch) { // exact match may be? // special case - if signature enforces constant vararg we // have to ensure all args are indeed constant if (match != MATCH_EXACT_MATCH) { if (candidateSigArgTypeSum > sigArgTypeSum || bestMatch < match) { candidate = factory; candidateDescriptor = descriptor; candidateSigArgCount = sigArgCount; candidateSigVarArgConst = sigVarArgConst; candidateSigArgTypeSum = sigArgTypeSum; } bestMatch = match; } else { candidate = factory; candidateDescriptor = descriptor; candidateSigArgCount = sigArgCount; candidateSigVarArgConst = sigVarArgConst; break; } } } } if (candidate == null) { // no signature match throw invalidArgument(node, args, candidateDescriptor); } if (candidateSigVarArgConst) { for (int k = candidateSigArgCount; k < argCount; k++) { Function func = args.getQuick(k); if (!(func.isConstant() || func.isRuntimeConstant())) { Misc.freeObjList(args); throw SqlException.$(argPositions.getQuick(k), "constant expected"); } } } // it is possible that we have more undefined variables than // args in the descriptor, in case of vararg for example for (int i = 0, n = undefinedVariables.size(); i < n; i++) { final int pos = undefinedVariables.getQuick(i); if (pos < candidateSigArgCount) { final int sigArgType = FunctionFactoryDescriptor.toType(candidateDescriptor.getArgTypeMask(pos)); args.getQuick(pos).assignType(sigArgType, sqlExecutionContext.getBindVariableService()); } else { args.getQuick(pos).assignType(ColumnType.VAR_ARG, sqlExecutionContext.getBindVariableService()); } } for (int k = 0; k < candidateSigArgCount; k++) { final Function arg = args.getQuick(k); final short sigArgTypeTag = FunctionFactoryDescriptor.toType(candidateDescriptor.getArgTypeMask(k)); final short argTypeTag = ColumnType.tagOf(arg.getType()); if (argTypeTag == ColumnType.DOUBLE && arg.isConstant() && Double.isNaN(arg.getDouble(null))) { // substitute NaNs with appropriate types if (sigArgTypeTag == ColumnType.LONG) { args.setQuick(k, LongConstant.NULL); } else if (sigArgTypeTag == ColumnType.INT) { args.setQuick(k, IntConstant.NULL); } } else if ((argTypeTag == ColumnType.STRING || argTypeTag == ColumnType.SYMBOL) && sigArgTypeTag == ColumnType.TIMESTAMP) { // convert arguments if necessary int position = argPositions.getQuick(k); if (arg.isConstant()) { long timestamp = convertToTimestamp(arg.getStr(null), position); args.set(k, TimestampConstant.newInstance(timestamp)); } else { AbstractUnaryTimestampFunction castFn; if (argTypeTag == ColumnType.STRING) { castFn = new CastStrToTimestampFunctionFactory.Func(position, arg); } else { castFn = new CastSymbolToTimestampFunctionFactory.Func(arg); } args.set(k, castFn); } } } return checkAndCreateFunction(candidate, args, argPositions, node, configuration); } @Nullable private Function createImplicitCastOrNull(int position, Function function, int toType) throws SqlException { int fromType = function.getType(); switch (fromType) { case ColumnType.STRING: case ColumnType.SYMBOL: if (toType == ColumnType.TIMESTAMP) { return new CastStrToTimestampFunctionFactory.Func(position, function); } if (ColumnType.isGeoHash(toType)) { return CastStrToGeoHashFunctionFactory.newInstance(position, toType, function); } break; default: if (ColumnType.isGeoHash(fromType)) { int fromGeoBits = ColumnType.getGeoHashBits(fromType); int toGeoBits = ColumnType.getGeoHashBits(toType); if (ColumnType.isGeoHash(toType) && toGeoBits < fromGeoBits) { return CastGeoHashToGeoHashFunctionFactory.newInstance(position, function, toType, fromType); } } break; } return null; } private Function createIndexParameter(int variableIndex, int position) throws SqlException { Function function = getBindVariableService().getFunction(variableIndex); if (function == null) { // bind variable is undefined return new IndexedParameterLinkFunction(variableIndex, ColumnType.UNDEFINED, position); } return new IndexedParameterLinkFunction(variableIndex, function.getType(), position); } private Function createNamedParameter(int position, CharSequence name) throws SqlException { Function function = getBindVariableService().getFunction(name); if (function == null) { throw SqlException.position(position).put("undefined bind variable: ").put(name); } return new NamedParameterLinkFunction(Chars.toString(name), function.getType()); } private Function functionToConstant(Function function) { Function newFunction = functionToConstant0(function); // Sometimes functionToConstant0 returns same instance as passed in parameter if (newFunction != function) { // and we want to close underlying function only in case it's different form returned newFunction function.close(); } return newFunction; } private Function functionToConstant0(Function function) { int type = function.getType(); switch (ColumnType.tagOf(type)) { case ColumnType.INT: if (function instanceof IntConstant) { return function; } else { return IntConstant.newInstance(function.getInt(null)); } case ColumnType.BOOLEAN: if (function instanceof BooleanConstant) { return function; } else { return BooleanConstant.of(function.getBool(null)); } case ColumnType.BYTE: if (function instanceof ByteConstant) { return function; } else { return ByteConstant.newInstance(function.getByte(null)); } case ColumnType.SHORT: if (function instanceof ShortConstant) { return function; } else { return ShortConstant.newInstance(function.getShort(null)); } case ColumnType.CHAR: if (function instanceof CharConstant) { return function; } else { return CharConstant.newInstance(function.getChar(null)); } case ColumnType.FLOAT: if (function instanceof FloatConstant) { return function; } else { return FloatConstant.newInstance(function.getFloat(null)); } case ColumnType.DOUBLE: if (function instanceof DoubleConstant) { return function; } else { return DoubleConstant.newInstance(function.getDouble(null)); } case ColumnType.LONG: if (function instanceof LongConstant) { return function; } else { return LongConstant.newInstance(function.getLong(null)); } case ColumnType.LONG256: if (function instanceof Long256Constant) { return function; } else { return new Long256Constant(function.getLong256A(null)); } case ColumnType.GEOBYTE: if (function instanceof GeoByteConstant) { return function; } else { return new GeoByteConstant(function.getGeoByte(null), type); } case ColumnType.GEOSHORT: if (function instanceof GeoShortConstant) { return function; } else { return new GeoShortConstant(function.getGeoShort(null), type); } case ColumnType.GEOINT: if (function instanceof GeoIntConstant) { return function; } else { return new GeoIntConstant(function.getGeoInt(null), type); } case ColumnType.GEOLONG: if (function instanceof GeoLongConstant) { return function; } else { return new GeoLongConstant(function.getGeoLong(null), type); } case ColumnType.DATE: if (function instanceof DateConstant) { return function; } else { return DateConstant.getInstance(function.getDate(null)); } case ColumnType.STRING: if (function instanceof StrConstant) { return function; } else { return StrConstant.newInstance(function.getStr(null)); } case ColumnType.SYMBOL: if (function instanceof SymbolConstant) { return function; } return SymbolConstant.newInstance(function.getSymbol(null)); case ColumnType.TIMESTAMP: if (function instanceof TimestampConstant) { return function; } else { return TimestampConstant.newInstance(function.getTimestamp(null)); } default: return function; } } @NotNull private BindVariableService getBindVariableService() throws SqlException { final BindVariableService bindVariableService = sqlExecutionContext.getBindVariableService(); if (bindVariableService == null) { throw SqlException.$(0, "bind variable service is not provided"); } return bindVariableService; } private Function parseIndexedParameter(int position, CharSequence name) throws SqlException { // get variable index from token try { final int variableIndex = Numbers.parseInt(name, 1, name.length()); if (variableIndex < 1) { throw SqlException.$(position, "invalid bind variable index [value=").put(variableIndex).put(']'); } return createIndexParameter(variableIndex - 1, position); } catch (NumericException e) { throw SqlException.$(position, "invalid bind variable index [value=").put(name).put(']'); } } static { for (int i = 0, n = SqlCompiler.sqlControlSymbols.size(); i < n; i++) { FunctionFactoryCache.invalidFunctionNames.add(SqlCompiler.sqlControlSymbols.getQuick(i)); } FunctionFactoryCache.invalidFunctionNameChars.add(' '); FunctionFactoryCache.invalidFunctionNameChars.add('\"'); FunctionFactoryCache.invalidFunctionNameChars.add('\''); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy