Esempio n. 1
0
        public override (string, QxType, QxNullity) CompileNative(QxCompilationContext ctx)
        {
            // Strings must be added in parameters to prevent SQL injection vulnerability
            var parameterName = ctx.Parameters.AddParameter(Value);
            var sql           = $"@{parameterName}";

            return(sql, QxType.String, QxNullity.NotNull);
        }
Esempio n. 2
0
        public override bool TryCompile(QxType targetType, QxCompilationContext ctx, out string resultSql, out QxNullity resultNullity)
        {
            switch (targetType)
            {
            case QxType.Date:
                if (DateTime.TryParse(Value, out DateTime d))
                {
                    d = d.Date;     // Remove the time component
                    string varDef  = $"N'{d:yyyy-MM-dd}'";
                    string varName = ctx.Variables.AddVariable("DATE", varDef);
                    resultSql     = $"@{varName}";
                    resultNullity = QxNullity.NotNull;
                    return(true);
                }
                else
                {
                    resultSql     = null;
                    resultNullity = default;
                    return(false);
                }

            case QxType.DateTime:
                if (DateTime.TryParse(Value, out DateTime dt))
                {
                    string varDef  = $"N'{dt:yyyy-MM-ddTHH:mm:ss.ff}'";
                    string varName = ctx.Variables.AddVariable("DATETIME2(2)", varDef);
                    resultSql     = $"@{varName}";
                    resultNullity = QxNullity.NotNull;
                    return(true);
                }
                else
                {
                    resultSql     = null;
                    resultNullity = default;
                    return(false);
                }

            case QxType.DateTimeOffset:
                if (DateTimeOffset.TryParse(Value, out DateTimeOffset dto))
                {
                    string varDef  = $"N'{dto:o}'";
                    string varName = ctx.Variables.AddVariable("DATETIMEOFFSET(7)", varDef);
                    resultSql     = $"@{varName}";
                    resultNullity = QxNullity.NotNull;
                    return(true);
                }
                else
                {
                    resultSql     = null;
                    resultNullity = default;
                    return(false);
                }
            }

            return(base.TryCompile(targetType, ctx, out resultSql, out resultNullity));
        }
Esempio n. 3
0
        /// <summary>
        /// Compiles the expression to the first <see cref="QxType"/> other than boolean that it can be compiled to.
        /// </summary>
        public string CompileToNonBoolean(QxCompilationContext ctx)
        {
            var(nativeSql, nativeType, _) = CompileNative(ctx);
            if (nativeType == QxType.Boolean)
            {
                throw new QueryException($"Expression {this} cannot be a {QxType.Boolean}.");
            }

            return(nativeSql);
        }
Esempio n. 4
0
        /// <summary>
        /// Compiles the expression to a boolean SQL, throws an exception if the expression cannot be compiled to boolean.
        /// </summary>
        public string CompileToBoolean(QxCompilationContext ctx)
        {
            if (TryCompile(QxType.Boolean, ctx, out string result, out QxNullity nullity))
            {
                if (nullity != QxNullity.NotNull)
                {
                    // Developer mistake
                    throw new InvalidOperationException($"[Bug] nullable boolean expression {this}.");
                }

                return(result);
            }
Esempio n. 5
0
        public override bool TryCompile(QxType targetType, QxCompilationContext ctx, out string resultSql, out QxNullity resultNullity)
        {
            string nameLower = Name?.ToLower();

            switch (nameLower)
            {
            case "min":
            case "max":
            {
                var(arg1, conditionSql) = AggregationParameters(ctx);

                if (arg1.TryCompile(targetType, ctx, out string expSql, out resultNullity))
                {
                    resultSql = AggregationCompile(expSql, conditionSql);
                    return(true);
                }
Esempio n. 6
0
 public override bool TryCompile(QxType targetType, QxCompilationContext ctx, out string resultSql, out QxNullity resultNullity)
 {
     // This here is merely an optimization
     if (Operator == "+")
     {
         if ((targetType == QxType.String && // String concatenation
              Left.TryCompile(QxType.String, ctx, out string leftSql, out QxNullity leftNullity) &&
              Right.TryCompile(QxType.String, ctx, out string rightSql, out QxNullity rightNullity)) ||
             (targetType == QxType.Numeric && // Numeric
              Left.TryCompile(QxType.Numeric, ctx, out leftSql, out leftNullity) &&
              Right.TryCompile(QxType.Numeric, ctx, out rightSql, out rightNullity)))
         {
             resultNullity = leftNullity | rightNullity;
             resultSql     = $"({leftSql} + {rightSql})";
             return(true);
         }
         else
         {
             // No other types are possible for +
             resultNullity = default;
             resultSql     = null;
             return(false);
         }
     }
Esempio n. 7
0
        public override (string sql, QxType type, QxNullity nullity) CompileNative(QxCompilationContext ctx)
        {
            // Note: The way the logic is structured assumes that for ALL operators
            // if either operand is NULL, then the result is NULL

            // Convenience variables
            string    operandSql;
            QxNullity operandNullity;

            // The result
            string    resultSql;
            QxType    resultType;
            QxNullity resultNullity;

            string opLower = Operator?.ToLower();

            switch (opLower)
            {
            case "+":
            case "-":
                // + maye be either addition or string concatenation
                // The output type is uniquely determined by the input types (context doesn't matter)
                // since there is no implicit cast from numeric to string or vice versa
            {
                if (!Operand.TryCompile(QxType.Numeric, ctx, out operandSql, out operandNullity))
                {
                    throw new QueryException($"Operator '{Operator}': Operand {Operand} could not be interpreted as {QxType.Numeric}.");
                }

                // Addition
                resultType    = QxType.Numeric;
                resultNullity = operandNullity;

                if (opLower == "+")
                {
                    resultSql = operandSql;         // +ve sign (does not do anything)
                }
                else if (opLower == "-")
                {
                    resultSql = $"(-{operandSql})";         // -ve sign
                }
                else
                {
                    // Developer mistake
                    throw new InvalidOperationException($"Unknown unary arithmetic operator {opLower}.");
                }

                break;
            }

            case "!":
            case "not":
                // These only accept booleans and return a boolean
            {
                resultType = QxType.Boolean;

                if (!Operand.TryCompile(QxType.Boolean, ctx, out operandSql, out operandNullity))
                {
                    throw new QueryException($"Operator '{Operator}': Operand {Operand} could not be interpreted as {QxType.Boolean}.");
                }

                resultNullity = operandNullity;
                if (resultNullity != QxNullity.NotNull)
                {
                    // Developer mistake
                    throw new InvalidOperationException($"[Bug] A nullable boolean expression: {this}");
                }

                if (operandSql == FALSE)
                {
                    resultSql = TRUE;
                }
                else if (operandSql == TRUE)
                {
                    resultSql = FALSE;
                }
                else
                {
                    resultSql = $"(NOT {operandSql})";
                }

                break;
            }

            default:
                // Developer mistake
                throw new InvalidOperationException($"Unknown unary operator {Operator}");     // Future proofing
            }

            // Return the result (or NULL if that's the only possible value)
            if (resultNullity == QxNullity.Null)
            {
                resultSql = "NULL";
            }

            return(resultSql, resultType, resultNullity);
        }
Esempio n. 8
0
        public override (string, QxType, QxNullity) CompileNative(QxCompilationContext ctx)
        {
            if (string.IsNullOrWhiteSpace(Property))
            {
                // Developer mistake
                throw new InvalidOperationException($"Bug: Invoking {nameof(CompileNative)} on a {nameof(QueryexColumnAccess)} that does not have a property.");
            }

            var propName = Property;

            // (A) Calculate Nullity (the entire path foreign keys + the final property must be all NOT NULL)
            bool pathNotNull = true;
            var  join        = ctx.Joins;

            foreach (var step in Path)
            {
                var navPropDesc = join.EntityDescriptor.NavigationProperty(step);
                pathNotNull = pathNotNull && navPropDesc.ForeignKey.IsNotNull;
                join        = join[step];
            }

            var propDesc = join.EntityDescriptor.Property(propName);

            if (propDesc == null)
            {
                throw new QueryException($"Property '{propName}' does not exist on type {join.EntityDescriptor.Name}.");
            }

            QxNullity nullity = pathNotNull && propDesc.IsNotNull ? QxNullity.NotNull : QxNullity.Nullable;

            // (B) Calculate the type
            QxType type;
            var    propType = Nullable.GetUnderlyingType(propDesc.Type) ?? propDesc.Type;

            switch (propType.Name)
            {
            case nameof(Char):
            case nameof(String):
                type = QxType.String;
                break;

            case nameof(Byte):
            case nameof(SByte):
            case nameof(Int16):
            case nameof(UInt16):
            case nameof(Int32):
            case nameof(UInt32):
            case nameof(Int64):
            case nameof(UInt64):
            case nameof(Single):
            case nameof(Double):
            case nameof(Decimal):
                type = QxType.Numeric;
                break;

            case nameof(Boolean):
                type = QxType.Bit;
                break;

            case nameof(DateTime):
                type = propDesc.IncludesTime ? QxType.DateTime : QxType.Date;
                break;

            case nameof(DateTimeOffset):
                type = QxType.DateTimeOffset;
                break;

            case nameof(HierarchyId):
                type = QxType.HierarchyId;
                break;

            case nameof(Geography):
                type = QxType.Geography;
                break;

            default:
                if (propDesc is NavigationPropertyDescriptor || propDesc is CollectionPropertyDescriptor)
                {
                    throw new QueryException($"A column access cannot terminate with a navigation property like {propDesc.Name}.");
                }
                else
                {
                    // Developer mistake
                    throw new InvalidOperationException($"[Bug] Could not map type {propType.Name} to a {nameof(QxType)}");     // Future proofing
                }
            }

            // (C) Calculate the SQL
            var sql = $"[{join.Symbol}].[{propName}]";

            // Return the result
            return(sql, type, nullity);
        }