464 lines
15 KiB
C#
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));
|
|
}
|
|
}
|
|
}
|