Files
DynamORM/DynamORM/TypedSql/TypedSqlExpression.cs

464 lines
15 KiB
C#

/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using DynamORM.Builders;
namespace DynamORM.TypedSql
{
/// <summary>Base selectable SQL fragment for the typed DSL.</summary>
public abstract class TypedSqlSelectable
{
internal abstract string Render(ITypedSqlRenderContext context);
}
/// <summary>Base SQL expression for the typed DSL.</summary>
public abstract class TypedSqlExpression : TypedSqlSelectable
{
/// <summary>Alias this expression in SELECT clause.</summary>
public TypedSqlAliasedExpression As(string alias)
{
return new TypedSqlAliasedExpression(this, alias);
}
/// <summary>Order ascending.</summary>
public TypedSqlOrderExpression Asc()
{
return new TypedSqlOrderExpression(this, true);
}
/// <summary>Order descending.</summary>
public TypedSqlOrderExpression Desc()
{
return new TypedSqlOrderExpression(this, false);
}
/// <summary>Equality predicate.</summary>
public TypedSqlPredicate Eq(object value)
{
return new TypedSqlBinaryPredicate(this, "=", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
}
/// <summary>Inequality predicate.</summary>
public TypedSqlPredicate NotEq(object value)
{
return new TypedSqlBinaryPredicate(this, "<>", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
}
/// <summary>Greater-than predicate.</summary>
public TypedSqlPredicate Gt(object value)
{
return new TypedSqlBinaryPredicate(this, ">", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
}
/// <summary>Greater-than-or-equal predicate.</summary>
public TypedSqlPredicate Gte(object value)
{
return new TypedSqlBinaryPredicate(this, ">=", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
}
/// <summary>Less-than predicate.</summary>
public TypedSqlPredicate Lt(object value)
{
return new TypedSqlBinaryPredicate(this, "<", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
}
/// <summary>Less-than-or-equal predicate.</summary>
public TypedSqlPredicate Lte(object value)
{
return new TypedSqlBinaryPredicate(this, "<=", value is TypedSqlExpression ? (TypedSqlExpression)value : Sql.Val(value));
}
/// <summary>IS NULL predicate.</summary>
public TypedSqlPredicate IsNull()
{
return new TypedSqlUnaryPredicate(this, "IS NULL");
}
/// <summary>IS NOT NULL predicate.</summary>
public TypedSqlPredicate IsNotNull()
{
return new TypedSqlUnaryPredicate(this, "IS NOT NULL");
}
/// <summary>LIKE predicate.</summary>
public TypedSqlPredicate Like(string pattern)
{
return new TypedSqlBinaryPredicate(this, "LIKE", Sql.Val(pattern));
}
/// <summary>IN predicate.</summary>
public TypedSqlPredicate In(params object[] values)
{
return new TypedSqlInPredicate(this, values);
}
/// <summary>IN predicate.</summary>
public TypedSqlPredicate In(IEnumerable values)
{
return new TypedSqlInPredicate(this, values);
}
/// <summary>NOT IN predicate.</summary>
public TypedSqlPredicate NotIn(params object[] values)
{
return new TypedSqlInPredicate(this, values, true);
}
/// <summary>NOT IN predicate.</summary>
public TypedSqlPredicate NotIn(IEnumerable values)
{
return new TypedSqlInPredicate(this, values, true);
}
/// <summary>BETWEEN predicate.</summary>
public TypedSqlPredicate Between(object lower, object upper)
{
return new TypedSqlBetweenPredicate(this, lower is TypedSqlExpression ? (TypedSqlExpression)lower : Sql.Val(lower), upper is TypedSqlExpression ? (TypedSqlExpression)upper : Sql.Val(upper));
}
/// <summary>Starts-with LIKE predicate.</summary>
public TypedSqlPredicate StartsWith(string value)
{
return Like((value ?? string.Empty) + "%");
}
/// <summary>Ends-with LIKE predicate.</summary>
public TypedSqlPredicate EndsWith(string value)
{
return Like("%" + (value ?? string.Empty));
}
/// <summary>Contains LIKE predicate.</summary>
public TypedSqlPredicate Contains(string value)
{
return Like("%" + (value ?? string.Empty) + "%");
}
}
/// <summary>Typed SQL expression.</summary>
public abstract class TypedSqlExpression<T> : TypedSqlExpression
{
}
/// <summary>Typed SQL predicate expression.</summary>
public abstract class TypedSqlPredicate : TypedSqlExpression<bool>
{
/// <summary>Combine with AND.</summary>
public TypedSqlPredicate And(TypedSqlPredicate right)
{
return new TypedSqlCombinedPredicate(this, "AND", right);
}
/// <summary>Combine with OR.</summary>
public TypedSqlPredicate Or(TypedSqlPredicate right)
{
return new TypedSqlCombinedPredicate(this, "OR", right);
}
/// <summary>Negate predicate.</summary>
public TypedSqlPredicate Not()
{
return new TypedSqlNegatedPredicate(this);
}
}
/// <summary>Aliased SQL expression.</summary>
public sealed class TypedSqlAliasedExpression : TypedSqlSelectable
{
private readonly TypedSqlExpression _expression;
private readonly string _alias;
internal TypedSqlAliasedExpression(TypedSqlExpression expression, string alias)
{
_expression = expression;
_alias = alias;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("{0} AS {1}", _expression.Render(context), context.DecorateName(_alias));
}
}
/// <summary>Ordered SQL expression.</summary>
public sealed class TypedSqlOrderExpression : TypedSqlSelectable
{
private readonly TypedSqlExpression _expression;
private readonly bool _ascending;
internal TypedSqlOrderExpression(TypedSqlExpression expression, bool ascending)
{
_expression = expression;
_ascending = ascending;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("{0} {1}", _expression.Render(context), _ascending ? "ASC" : "DESC");
}
}
internal sealed class TypedSqlColumnExpression<T> : TypedSqlExpression<T>
{
private readonly Type _modelType;
private readonly string _memberName;
private readonly string _alias;
internal TypedSqlColumnExpression(Type modelType, string memberName, string alias)
{
_modelType = modelType;
_memberName = memberName;
_alias = alias;
}
internal override string Render(ITypedSqlRenderContext context)
{
return context.ResolveColumn(_modelType, _memberName, _alias);
}
}
internal sealed class TypedSqlValueExpression<T> : TypedSqlExpression<T>, ITypedSqlNullValue
{
private readonly object _value;
internal TypedSqlValueExpression(object value)
{
_value = value;
}
internal override string Render(ITypedSqlRenderContext context)
{
return context.RenderValue(_value);
}
public bool IsNullValue
{
get { return _value == null; }
}
}
internal sealed class TypedSqlRawExpression<T> : TypedSqlExpression<T>
{
private readonly string _sql;
internal TypedSqlRawExpression(string sql)
{
_sql = sql;
}
internal override string Render(ITypedSqlRenderContext context)
{
return _sql;
}
}
internal sealed class TypedSqlFunctionExpression<T> : TypedSqlExpression<T>
{
private readonly string _name;
private readonly IList<TypedSqlExpression> _arguments;
internal TypedSqlFunctionExpression(string name, params TypedSqlExpression[] arguments)
{
_name = name;
_arguments = arguments ?? new TypedSqlExpression[0];
}
internal override string Render(ITypedSqlRenderContext context)
{
List<string> rendered = new List<string>();
foreach (TypedSqlExpression argument in _arguments)
rendered.Add(argument.Render(context));
return string.Format("{0}({1})", _name, string.Join(", ", rendered.ToArray()));
}
}
internal sealed class TypedSqlUnaryPredicate : TypedSqlPredicate
{
private readonly TypedSqlExpression _expression;
private readonly string _operator;
internal TypedSqlUnaryPredicate(TypedSqlExpression expression, string op)
{
_expression = expression;
_operator = op;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("({0} {1})", _expression.Render(context), _operator);
}
}
internal sealed class TypedSqlBinaryPredicate : TypedSqlPredicate
{
private readonly TypedSqlExpression _left;
private readonly string _operator;
private readonly TypedSqlExpression _right;
internal TypedSqlBinaryPredicate(TypedSqlExpression left, string op, TypedSqlExpression right)
{
_left = left;
_operator = op;
_right = right;
}
internal override string Render(ITypedSqlRenderContext context)
{
string op = _operator;
TypedSqlValueExpression<object> objRight = _right as TypedSqlValueExpression<object>;
if ((objRight != null && objRight.IsNullValue) || (_right is ITypedSqlNullValue && ((ITypedSqlNullValue)_right).IsNullValue))
op = _operator == "=" ? "IS" : _operator == "<>" ? "IS NOT" : _operator;
return string.Format("({0} {1} {2})", _left.Render(context), op, _right.Render(context));
}
}
internal interface ITypedSqlNullValue
{
bool IsNullValue { get; }
}
internal sealed class TypedSqlInPredicate : TypedSqlPredicate
{
private readonly TypedSqlExpression _left;
private readonly IEnumerable _values;
private readonly bool _negated;
internal TypedSqlInPredicate(TypedSqlExpression left, IEnumerable values, bool negated = false)
{
_left = left;
_values = values;
_negated = negated;
}
internal override string Render(ITypedSqlRenderContext context)
{
List<string> rendered = new List<string>();
foreach (object value in _values)
rendered.Add((value as TypedSqlExpression ?? Sql.Val(value)).Render(context));
if (rendered.Count == 0)
return _negated ? "(1 = 1)" : "(1 = 0)";
return string.Format("({0} {1}({2}))", _left.Render(context), _negated ? "NOT IN" : "IN", string.Join(", ", rendered.ToArray()));
}
}
internal sealed class TypedSqlBetweenPredicate : TypedSqlPredicate
{
private readonly TypedSqlExpression _left;
private readonly TypedSqlExpression _lower;
private readonly TypedSqlExpression _upper;
internal TypedSqlBetweenPredicate(TypedSqlExpression left, TypedSqlExpression lower, TypedSqlExpression upper)
{
_left = left;
_lower = lower;
_upper = upper;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("({0} BETWEEN {1} AND {2})", _left.Render(context), _lower.Render(context), _upper.Render(context));
}
}
internal sealed class TypedSqlCombinedPredicate : TypedSqlPredicate
{
private readonly TypedSqlPredicate _left;
private readonly string _operator;
private readonly TypedSqlPredicate _right;
internal TypedSqlCombinedPredicate(TypedSqlPredicate left, string op, TypedSqlPredicate right)
{
_left = left;
_operator = op;
_right = right;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("({0} {1} {2})", _left.Render(context), _operator, _right.Render(context));
}
}
internal sealed class TypedSqlNegatedPredicate : TypedSqlPredicate
{
private readonly TypedSqlPredicate _predicate;
internal TypedSqlNegatedPredicate(TypedSqlPredicate predicate)
{
_predicate = predicate;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("(NOT {0})", _predicate.Render(context));
}
}
internal sealed class TypedSqlCaseExpression<T> : TypedSqlExpression<T>
{
private readonly IList<KeyValuePair<TypedSqlPredicate, TypedSqlExpression>> _cases;
private readonly TypedSqlExpression _elseExpression;
internal TypedSqlCaseExpression(IList<KeyValuePair<TypedSqlPredicate, TypedSqlExpression>> cases, TypedSqlExpression elseExpression)
{
_cases = cases;
_elseExpression = elseExpression;
}
internal override string Render(ITypedSqlRenderContext context)
{
List<string> items = new List<string>();
items.Add("CASE");
foreach (KeyValuePair<TypedSqlPredicate, TypedSqlExpression> item in _cases)
items.Add(string.Format("WHEN {0} THEN {1}", item.Key.Render(context), item.Value.Render(context)));
if (_elseExpression != null)
items.Add(string.Format("ELSE {0}", _elseExpression.Render(context)));
items.Add("END");
return string.Join(" ", items.ToArray());
}
}
internal sealed class TypedSqlSubQueryExpression<T> : TypedSqlExpression<T>
{
private readonly IDynamicSelectQueryBuilder _query;
internal TypedSqlSubQueryExpression(IDynamicSelectQueryBuilder query)
{
_query = query;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("({0})", context.RenderSubQuery(_query));
}
}
internal sealed class TypedSqlExistsPredicate : TypedSqlPredicate
{
private readonly IDynamicSelectQueryBuilder _query;
internal TypedSqlExistsPredicate(IDynamicSelectQueryBuilder query)
{
_query = query;
}
internal override string Render(ITypedSqlRenderContext context)
{
return string.Format("(EXISTS ({0}))", context.RenderSubQuery(_query));
}
}
}