mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-07-23 18:47:13 -04:00
d4cc1bcb7c
# Description of Changes This comes up when doing a fresh `dotnet test`, which causes `dotnet test -warnaserror` to fail in CI: ``` ➜ dotnet test -warnaserror Determining projects to restore... Restored /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/Runtime/Runtime.csproj (in 126 ms). Restored /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/BSATN.Runtime/BSATN.Runtime.csproj (in 133 ms). Restored /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/BSATN.Runtime.Tests/BSATN.Runtime.Tests.csproj (in 142 ms). Restored /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/BSATN.Codegen/BSATN.Codegen.csproj (in 148 ms). Restored /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/Codegen/Codegen.csproj (in 148 ms). Restored /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/Codegen.Tests/Codegen.Tests.csproj (in 280 ms). BSATN.Codegen -> /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/BSATN.Codegen/bin/Debug/netstandard2.0/SpacetimeDB.BSATN.Codegen.dll BSATN.Runtime -> /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/BSATN.Runtime/bin/Debug/netstandard2.1/SpacetimeDB.BSATN.Runtime.dll Codegen -> /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/Codegen/bin/Debug/netstandard2.0/SpacetimeDB.Codegen.dll /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/BSATN.Runtime/QueryBuilder.cs(880,9): error CA1510: Use 'ArgumentNullException.ThrowIfNull' instead of explicitly throwing a new exception instance (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1510) [/home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/BSATN.Runtime/BSATN.Runtime.csproj::TargetFramework=net8.0] /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/BSATN.Runtime/Builtins.cs(3,1): error IDE0005: Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005) [/home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/BSATN.Runtime/BSATN.Runtime.csproj::TargetFramework=net8.0] BSATN.Runtime -> /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/BSATN.Runtime/bin/Debug/net8.0/SpacetimeDB.BSATN.Runtime.dll Codegen.Tests -> /home/work/SpacetimeDBPrivate/public/crates/bindings-csharp/Codegen.Tests/bin/Debug/net8.0/Codegen.Tests.dll ``` The fix is done by AI. # API and ABI breaking changes None. Behavior should be the same. # Expected complexity level and risk 1 # Testing `git clean -fdx . && dotnet test -warnaserror` no longer fails --------- Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
918 lines
31 KiB
C#
918 lines
31 KiB
C#
#nullable enable
|
|
|
|
namespace SpacetimeDB;
|
|
|
|
using System;
|
|
using System.Globalization;
|
|
|
|
public readonly struct SqlLiteral<T>
|
|
{
|
|
internal string Sql { get; }
|
|
|
|
internal SqlLiteral(string sql)
|
|
{
|
|
Sql = sql;
|
|
}
|
|
|
|
public override string ToString() => Sql;
|
|
}
|
|
|
|
public static class SqlLit
|
|
{
|
|
public static SqlLiteral<string> String(ReadOnlySpan<char> value) =>
|
|
new(SqlFormat.FormatStringLiteral(value));
|
|
|
|
public static SqlLiteral<bool> Bool(bool value) => new(value ? "TRUE" : "FALSE");
|
|
|
|
public static SqlLiteral<sbyte> Int(sbyte value) =>
|
|
new(value.ToString(CultureInfo.InvariantCulture));
|
|
|
|
public static SqlLiteral<byte> Int(byte value) =>
|
|
new(value.ToString(CultureInfo.InvariantCulture));
|
|
|
|
public static SqlLiteral<short> Int(short value) =>
|
|
new(value.ToString(CultureInfo.InvariantCulture));
|
|
|
|
public static SqlLiteral<ushort> Int(ushort value) =>
|
|
new(value.ToString(CultureInfo.InvariantCulture));
|
|
|
|
public static SqlLiteral<int> Int(int value) =>
|
|
new(value.ToString(CultureInfo.InvariantCulture));
|
|
|
|
public static SqlLiteral<uint> Int(uint value) =>
|
|
new(value.ToString(CultureInfo.InvariantCulture));
|
|
|
|
public static SqlLiteral<long> Int(long value) =>
|
|
new(value.ToString(CultureInfo.InvariantCulture));
|
|
|
|
public static SqlLiteral<ulong> Int(ulong value) =>
|
|
new(value.ToString(CultureInfo.InvariantCulture));
|
|
|
|
public static SqlLiteral<U128> Int(U128 value) => new(value.ToString());
|
|
|
|
public static SqlLiteral<Identity> Identity(Identity value) =>
|
|
new(SqlFormat.FormatHexLiteral(value.ToString()));
|
|
|
|
public static SqlLiteral<ConnectionId> ConnectionId(ConnectionId value) =>
|
|
new(SqlFormat.FormatHexLiteral(value.ToString()));
|
|
|
|
public static SqlLiteral<Uuid> Uuid(Uuid value) =>
|
|
new(SqlFormat.FormatHexLiteral(value.ToString()));
|
|
|
|
public static SqlLiteral<Timestamp> Timestamp(Timestamp value) =>
|
|
new(SqlFormat.FormatTimestampLiteral(value));
|
|
}
|
|
|
|
public interface IQuery<TRow>
|
|
{
|
|
string ToSql();
|
|
}
|
|
|
|
public readonly struct BoolExpr<TRow>
|
|
{
|
|
public string Sql { get; }
|
|
|
|
public BoolExpr(string sql)
|
|
{
|
|
Sql = sql;
|
|
}
|
|
|
|
public BoolExpr<TRow> And(BoolExpr<TRow> other) => new($"({Sql} AND {other.Sql})");
|
|
|
|
public BoolExpr<TRow> Or(BoolExpr<TRow> other) => new($"({Sql} OR {other.Sql})");
|
|
|
|
public BoolExpr<TRow> Not() => new($"(NOT {Sql})");
|
|
|
|
public override string ToString() => Sql;
|
|
|
|
public static implicit operator BoolExpr<TRow>(bool value) => new(value ? "TRUE" : "FALSE");
|
|
|
|
public static implicit operator BoolExpr<TRow>(Col<TRow, bool> col) => col.Eq(true);
|
|
|
|
public static implicit operator BoolExpr<TRow>(IxCol<TRow, bool> col) => col.Eq(true);
|
|
}
|
|
|
|
internal static class QueryPredicate
|
|
{
|
|
internal static BoolExpr<TRow> ToBoolExpr<TRow>(object value) =>
|
|
value switch
|
|
{
|
|
BoolExpr<TRow> expr => expr,
|
|
Col<TRow, bool> col => col.Eq(true),
|
|
IxCol<TRow, bool> col => col.Eq(true),
|
|
bool b => new BoolExpr<TRow>(b ? "TRUE" : "FALSE"),
|
|
_ => throw new ArgumentException(
|
|
$"Unsupported predicate type '{value.GetType().Name}'. Expected BoolExpr<{typeof(TRow).Name}> or a boolean column.",
|
|
nameof(value)
|
|
),
|
|
};
|
|
}
|
|
|
|
public readonly struct IxJoinEq<TLeftRow, TRightRow>
|
|
{
|
|
internal string LeftRefSql { get; }
|
|
internal string RightRefSql { get; }
|
|
|
|
internal IxJoinEq(string leftRefSql, string rightRefSql)
|
|
{
|
|
LeftRefSql = leftRefSql;
|
|
RightRefSql = rightRefSql;
|
|
}
|
|
}
|
|
|
|
public readonly struct Col<TRow, TValue>
|
|
where TValue : notnull
|
|
{
|
|
private readonly string tableName;
|
|
private readonly string columnName;
|
|
|
|
public Col(string tableName, string columnName)
|
|
{
|
|
this.tableName = tableName;
|
|
this.columnName = columnName;
|
|
}
|
|
|
|
internal string RefSql =>
|
|
$"{SqlFormat.QuoteIdent(tableName)}.{SqlFormat.QuoteIdent(columnName)}";
|
|
|
|
public BoolExpr<TRow> Eq(SqlLiteral<TValue> value) => new($"({RefSql} = {value.Sql})");
|
|
|
|
public BoolExpr<TRow> Eq(Col<TRow, TValue> other) => new($"({RefSql} = {other.RefSql})");
|
|
|
|
public BoolExpr<TRow> Neq(SqlLiteral<TValue> value) => new($"({RefSql} <> {value.Sql})");
|
|
|
|
public BoolExpr<TRow> Neq(Col<TRow, TValue> other) => new($"({RefSql} <> {other.RefSql})");
|
|
|
|
public BoolExpr<TRow> Lt(SqlLiteral<TValue> value) => new($"({RefSql} < {value.Sql})");
|
|
|
|
public BoolExpr<TRow> Lte(SqlLiteral<TValue> value) => new($"({RefSql} <= {value.Sql})");
|
|
|
|
public BoolExpr<TRow> Gt(SqlLiteral<TValue> value) => new($"({RefSql} > {value.Sql})");
|
|
|
|
public BoolExpr<TRow> Gte(SqlLiteral<TValue> value) => new($"({RefSql} >= {value.Sql})");
|
|
|
|
public BoolExpr<TRow> Lt(Col<TRow, TValue> other) => new($"({RefSql} < {other.RefSql})");
|
|
|
|
public BoolExpr<TRow> Lte(Col<TRow, TValue> other) => new($"({RefSql} <= {other.RefSql})");
|
|
|
|
public BoolExpr<TRow> Gt(Col<TRow, TValue> other) => new($"({RefSql} > {other.RefSql})");
|
|
|
|
public BoolExpr<TRow> Gte(Col<TRow, TValue> other) => new($"({RefSql} >= {other.RefSql})");
|
|
|
|
public override string ToString() => RefSql;
|
|
}
|
|
|
|
public readonly struct IxCol<TRow, TValue>
|
|
where TValue : notnull
|
|
{
|
|
private readonly string tableName;
|
|
private readonly string columnName;
|
|
|
|
public IxCol(string tableName, string columnName)
|
|
{
|
|
this.tableName = tableName;
|
|
this.columnName = columnName;
|
|
}
|
|
|
|
internal string RefSql =>
|
|
$"{SqlFormat.QuoteIdent(tableName)}.{SqlFormat.QuoteIdent(columnName)}";
|
|
|
|
public BoolExpr<TRow> Eq(SqlLiteral<TValue> value) => new($"({RefSql} = {value.Sql})");
|
|
|
|
public IxJoinEq<TRow, TOtherRow> Eq<TOtherRow>(IxCol<TOtherRow, TValue> other) =>
|
|
new(RefSql, other.RefSql);
|
|
|
|
public BoolExpr<TRow> Neq(SqlLiteral<TValue> value) => new($"({RefSql} <> {value.Sql})");
|
|
|
|
public override string ToString() => RefSql;
|
|
}
|
|
|
|
public sealed class Table<TRow, TCols, TIxCols> : IQuery<TRow>
|
|
{
|
|
private readonly string tableName;
|
|
private readonly TCols cols;
|
|
private readonly TIxCols ixCols;
|
|
|
|
public Table(string tableName, TCols cols, TIxCols ixCols)
|
|
{
|
|
this.tableName = tableName;
|
|
this.cols = cols;
|
|
this.ixCols = ixCols;
|
|
}
|
|
|
|
internal string TableRefSql => SqlFormat.QuoteIdent(tableName);
|
|
|
|
internal TCols Cols => cols;
|
|
|
|
internal TIxCols IxCols => ixCols;
|
|
|
|
public string ToSql() => $"SELECT * FROM {SqlFormat.QuoteIdent(tableName)}";
|
|
|
|
public FromWhere<TRow, TCols, TIxCols> Where<TPredicate>(Func<TCols, TPredicate> predicate) =>
|
|
new(this, QueryPredicate.ToBoolExpr<TRow>(predicate(cols)!));
|
|
|
|
public FromWhere<TRow, TCols, TIxCols> Where<TPredicate>(
|
|
Func<TCols, TIxCols, TPredicate> predicate
|
|
) => new(this, QueryPredicate.ToBoolExpr<TRow>(predicate(cols, ixCols)!));
|
|
|
|
public FromWhere<TRow, TCols, TIxCols> Filter<TPredicate>(Func<TCols, TPredicate> predicate) =>
|
|
Where(predicate);
|
|
|
|
public FromWhere<TRow, TCols, TIxCols> Filter<TPredicate>(
|
|
Func<TCols, TIxCols, TPredicate> predicate
|
|
) => Where(predicate);
|
|
|
|
public LeftSemiJoin<TRow, TCols, TIxCols, TRightRow, TRightCols, TRightIxCols> LeftSemijoin<
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
>(
|
|
Table<TRightRow, TRightCols, TRightIxCols> right,
|
|
Func<TIxCols, TRightIxCols, IxJoinEq<TRow, TRightRow>> on
|
|
) => new(this, right, on(ixCols, right.ixCols), whereExpr: null);
|
|
|
|
public RightSemiJoin<TRow, TCols, TIxCols, TRightRow, TRightCols, TRightIxCols> RightSemijoin<
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
>(
|
|
Table<TRightRow, TRightCols, TRightIxCols> right,
|
|
Func<TIxCols, TRightIxCols, IxJoinEq<TRow, TRightRow>> on
|
|
) => new(this, right, on(ixCols, right.ixCols), leftWhereExpr: null);
|
|
}
|
|
|
|
public sealed class FromWhere<TRow, TCols, TIxCols> : IQuery<TRow>
|
|
{
|
|
private readonly Table<TRow, TCols, TIxCols> table;
|
|
private readonly BoolExpr<TRow> expr;
|
|
|
|
internal FromWhere(Table<TRow, TCols, TIxCols> table, BoolExpr<TRow> expr)
|
|
{
|
|
this.table = table;
|
|
this.expr = expr;
|
|
}
|
|
|
|
public FromWhere<TRow, TCols, TIxCols> Where<TPredicate>(Func<TCols, TPredicate> predicate) =>
|
|
new(table, expr.And(QueryPredicate.ToBoolExpr<TRow>(predicate(table.Cols)!)));
|
|
|
|
public FromWhere<TRow, TCols, TIxCols> Where<TPredicate>(
|
|
Func<TCols, TIxCols, TPredicate> predicate
|
|
) =>
|
|
new(table, expr.And(QueryPredicate.ToBoolExpr<TRow>(predicate(table.Cols, table.IxCols)!)));
|
|
|
|
public FromWhere<TRow, TCols, TIxCols> Filter<TPredicate>(Func<TCols, TPredicate> predicate) =>
|
|
Where(predicate);
|
|
|
|
public FromWhere<TRow, TCols, TIxCols> Filter<TPredicate>(
|
|
Func<TCols, TIxCols, TPredicate> predicate
|
|
) => Where(predicate);
|
|
|
|
public string ToSql() => $"{table.ToSql()} WHERE {expr.Sql}";
|
|
|
|
public LeftSemiJoin<TRow, TCols, TIxCols, TRightRow, TRightCols, TRightIxCols> LeftSemijoin<
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
>(
|
|
Table<TRightRow, TRightCols, TRightIxCols> right,
|
|
Func<TIxCols, TRightIxCols, IxJoinEq<TRow, TRightRow>> on
|
|
) => new(table, right, on(table.IxCols, right.IxCols), expr);
|
|
|
|
public RightSemiJoin<TRow, TCols, TIxCols, TRightRow, TRightCols, TRightIxCols> RightSemijoin<
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
>(
|
|
Table<TRightRow, TRightCols, TRightIxCols> right,
|
|
Func<TIxCols, TRightIxCols, IxJoinEq<TRow, TRightRow>> on
|
|
) => new(table, right, on(table.IxCols, right.IxCols), expr);
|
|
}
|
|
|
|
public sealed class LeftSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> : IQuery<TLeftRow>
|
|
{
|
|
private readonly Table<TLeftRow, TLeftCols, TLeftIxCols> left;
|
|
private readonly Table<TRightRow, TRightCols, TRightIxCols> right;
|
|
private readonly string leftJoinRefSql;
|
|
private readonly string rightJoinRefSql;
|
|
private readonly BoolExpr<TLeftRow>? whereExpr;
|
|
|
|
internal LeftSemiJoin(
|
|
Table<TLeftRow, TLeftCols, TLeftIxCols> left,
|
|
Table<TRightRow, TRightCols, TRightIxCols> right,
|
|
IxJoinEq<TLeftRow, TRightRow> join,
|
|
BoolExpr<TLeftRow>? whereExpr
|
|
)
|
|
{
|
|
this.left = left;
|
|
this.right = right;
|
|
leftJoinRefSql = join.LeftRefSql;
|
|
rightJoinRefSql = join.RightRefSql;
|
|
this.whereExpr = whereExpr;
|
|
}
|
|
|
|
public LeftSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Where(Func<TLeftCols, BoolExpr<TLeftRow>> predicate)
|
|
{
|
|
var extra = predicate(left.Cols);
|
|
BoolExpr<TLeftRow>? next = whereExpr.HasValue ? whereExpr.Value.And(extra) : extra;
|
|
return new(
|
|
left,
|
|
right,
|
|
new IxJoinEq<TLeftRow, TRightRow>(leftJoinRefSql, rightJoinRefSql),
|
|
next
|
|
);
|
|
}
|
|
|
|
public LeftSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Where(Func<TLeftCols, TLeftIxCols, BoolExpr<TLeftRow>> predicate)
|
|
{
|
|
var extra = predicate(left.Cols, left.IxCols);
|
|
BoolExpr<TLeftRow>? next = whereExpr.HasValue ? whereExpr.Value.And(extra) : extra;
|
|
return new(
|
|
left,
|
|
right,
|
|
new IxJoinEq<TLeftRow, TRightRow>(leftJoinRefSql, rightJoinRefSql),
|
|
next
|
|
);
|
|
}
|
|
|
|
public LeftSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Where(Func<TLeftCols, Col<TLeftRow, bool>> predicate) =>
|
|
Where(cols => predicate(cols).Eq(true));
|
|
|
|
public LeftSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Filter(Func<TLeftCols, BoolExpr<TLeftRow>> predicate) => Where(predicate);
|
|
|
|
public LeftSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Filter(Func<TLeftCols, Col<TLeftRow, bool>> predicate) => Where(predicate);
|
|
|
|
public LeftSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Filter(Func<TLeftCols, TLeftIxCols, BoolExpr<TLeftRow>> predicate) => Where(predicate);
|
|
|
|
public string ToSql()
|
|
{
|
|
var whereClause = whereExpr.HasValue ? $" WHERE {whereExpr.Value.Sql}" : string.Empty;
|
|
return $"SELECT {left.TableRefSql}.* FROM {left.TableRefSql} JOIN {right.TableRefSql} ON {leftJoinRefSql} = {rightJoinRefSql}{whereClause}";
|
|
}
|
|
}
|
|
|
|
public sealed class RightSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> : IQuery<TRightRow>
|
|
{
|
|
private readonly Table<TLeftRow, TLeftCols, TLeftIxCols> left;
|
|
private readonly Table<TRightRow, TRightCols, TRightIxCols> right;
|
|
private readonly string leftJoinRefSql;
|
|
private readonly string rightJoinRefSql;
|
|
private readonly BoolExpr<TLeftRow>? leftWhereExpr;
|
|
private readonly BoolExpr<TRightRow>? rightWhereExpr;
|
|
|
|
internal RightSemiJoin(
|
|
Table<TLeftRow, TLeftCols, TLeftIxCols> left,
|
|
Table<TRightRow, TRightCols, TRightIxCols> right,
|
|
IxJoinEq<TLeftRow, TRightRow> join,
|
|
BoolExpr<TLeftRow>? leftWhereExpr,
|
|
BoolExpr<TRightRow>? rightWhereExpr
|
|
)
|
|
{
|
|
this.left = left;
|
|
this.right = right;
|
|
leftJoinRefSql = join.LeftRefSql;
|
|
rightJoinRefSql = join.RightRefSql;
|
|
this.leftWhereExpr = leftWhereExpr;
|
|
this.rightWhereExpr = rightWhereExpr;
|
|
}
|
|
|
|
internal RightSemiJoin(
|
|
Table<TLeftRow, TLeftCols, TLeftIxCols> left,
|
|
Table<TRightRow, TRightCols, TRightIxCols> right,
|
|
IxJoinEq<TLeftRow, TRightRow> join,
|
|
BoolExpr<TLeftRow>? leftWhereExpr
|
|
)
|
|
: this(left, right, join, leftWhereExpr, rightWhereExpr: null) { }
|
|
|
|
public RightSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Where(Func<TRightCols, BoolExpr<TRightRow>> predicate)
|
|
{
|
|
var extra = predicate(right.Cols);
|
|
BoolExpr<TRightRow>? nextRight = rightWhereExpr.HasValue
|
|
? rightWhereExpr.Value.And(extra)
|
|
: extra;
|
|
return new(
|
|
left,
|
|
right,
|
|
new IxJoinEq<TLeftRow, TRightRow>(leftJoinRefSql, rightJoinRefSql),
|
|
leftWhereExpr,
|
|
nextRight
|
|
);
|
|
}
|
|
|
|
public RightSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Where(Func<TRightCols, TRightIxCols, BoolExpr<TRightRow>> predicate)
|
|
{
|
|
var extra = predicate(right.Cols, right.IxCols);
|
|
BoolExpr<TRightRow>? nextRight = rightWhereExpr.HasValue
|
|
? rightWhereExpr.Value.And(extra)
|
|
: extra;
|
|
return new(
|
|
left,
|
|
right,
|
|
new IxJoinEq<TLeftRow, TRightRow>(leftJoinRefSql, rightJoinRefSql),
|
|
leftWhereExpr,
|
|
nextRight
|
|
);
|
|
}
|
|
|
|
public RightSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Where(Func<TRightCols, Col<TRightRow, bool>> predicate) =>
|
|
Where(cols => predicate(cols).Eq(true));
|
|
|
|
public RightSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Filter(Func<TRightCols, BoolExpr<TRightRow>> predicate) => Where(predicate);
|
|
|
|
public RightSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Filter(Func<TRightCols, Col<TRightRow, bool>> predicate) => Where(predicate);
|
|
|
|
public RightSemiJoin<
|
|
TLeftRow,
|
|
TLeftCols,
|
|
TLeftIxCols,
|
|
TRightRow,
|
|
TRightCols,
|
|
TRightIxCols
|
|
> Filter(Func<TRightCols, TRightIxCols, BoolExpr<TRightRow>> predicate) => Where(predicate);
|
|
|
|
public string ToSql()
|
|
{
|
|
var whereClause = string.Empty;
|
|
|
|
if (leftWhereExpr.HasValue && rightWhereExpr.HasValue)
|
|
{
|
|
whereClause = $" WHERE {leftWhereExpr.Value.Sql} AND {rightWhereExpr.Value.Sql}";
|
|
}
|
|
else if (leftWhereExpr.HasValue)
|
|
{
|
|
whereClause = $" WHERE {leftWhereExpr.Value.Sql}";
|
|
}
|
|
else if (rightWhereExpr.HasValue)
|
|
{
|
|
whereClause = $" WHERE {rightWhereExpr.Value.Sql}";
|
|
}
|
|
|
|
return $"SELECT {right.TableRefSql}.* FROM {left.TableRefSql} JOIN {right.TableRefSql} ON {leftJoinRefSql} = {rightJoinRefSql}{whereClause}";
|
|
}
|
|
}
|
|
|
|
public static class QueryBuilderExtensions
|
|
{
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, string> col, ReadOnlySpan<char> value) =>
|
|
col.Eq(SqlLit.String(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, string> col, ReadOnlySpan<char> value) =>
|
|
col.Neq(SqlLit.String(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, string> col, ReadOnlySpan<char> value) =>
|
|
col.Lt(SqlLit.String(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, string> col, ReadOnlySpan<char> value) =>
|
|
col.Lte(SqlLit.String(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, string> col, ReadOnlySpan<char> value) =>
|
|
col.Gt(SqlLit.String(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, string> col, ReadOnlySpan<char> value) =>
|
|
col.Gte(SqlLit.String(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, bool> col, bool value) =>
|
|
col.Eq(SqlLit.Bool(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, bool> col, bool value) =>
|
|
col.Neq(SqlLit.Bool(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, sbyte> col, sbyte value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, sbyte> col, sbyte value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, sbyte> col, sbyte value) =>
|
|
col.Lt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, sbyte> col, sbyte value) =>
|
|
col.Lte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, sbyte> col, sbyte value) =>
|
|
col.Gt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, sbyte> col, sbyte value) =>
|
|
col.Gte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, byte> col, byte value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, byte> col, byte value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, byte> col, byte value) =>
|
|
col.Lt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, byte> col, byte value) =>
|
|
col.Lte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, byte> col, byte value) =>
|
|
col.Gt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, byte> col, byte value) =>
|
|
col.Gte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, short> col, short value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, short> col, short value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, short> col, short value) =>
|
|
col.Lt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, short> col, short value) =>
|
|
col.Lte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, short> col, short value) =>
|
|
col.Gt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, short> col, short value) =>
|
|
col.Gte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, ushort> col, ushort value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, ushort> col, ushort value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, ushort> col, ushort value) =>
|
|
col.Lt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, ushort> col, ushort value) =>
|
|
col.Lte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, ushort> col, ushort value) =>
|
|
col.Gt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, ushort> col, ushort value) =>
|
|
col.Gte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, int> col, int value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, int> col, int value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, int> col, int value) =>
|
|
col.Lt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, int> col, int value) =>
|
|
col.Lte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, int> col, int value) =>
|
|
col.Gt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, int> col, int value) =>
|
|
col.Gte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, uint> col, uint value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, uint> col, uint value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, uint> col, uint value) =>
|
|
col.Lt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, uint> col, uint value) =>
|
|
col.Lte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, uint> col, uint value) =>
|
|
col.Gt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, uint> col, uint value) =>
|
|
col.Gte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, long> col, long value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, long> col, long value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, long> col, long value) =>
|
|
col.Lt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, long> col, long value) =>
|
|
col.Lte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, long> col, long value) =>
|
|
col.Gt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, long> col, long value) =>
|
|
col.Gte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, ulong> col, ulong value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, ulong> col, ulong value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, ulong> col, ulong value) =>
|
|
col.Lt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, ulong> col, ulong value) =>
|
|
col.Lte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, ulong> col, ulong value) =>
|
|
col.Gt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, ulong> col, ulong value) =>
|
|
col.Gte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, U128> col, U128 value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, U128> col, U128 value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, U128> col, U128 value) =>
|
|
col.Lt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, U128> col, U128 value) =>
|
|
col.Lte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, U128> col, U128 value) =>
|
|
col.Gt(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, U128> col, U128 value) =>
|
|
col.Gte(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, Identity> col, Identity value) =>
|
|
col.Eq(SqlLit.Identity(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, Identity> col, Identity value) =>
|
|
col.Neq(SqlLit.Identity(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, ConnectionId> col, ConnectionId value) =>
|
|
col.Eq(SqlLit.ConnectionId(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, ConnectionId> col, ConnectionId value) =>
|
|
col.Neq(SqlLit.ConnectionId(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, Uuid> col, Uuid value) =>
|
|
col.Eq(SqlLit.Uuid(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, Uuid> col, Uuid value) =>
|
|
col.Neq(SqlLit.Uuid(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this Col<TRow, Timestamp> col, Timestamp value) =>
|
|
col.Eq(SqlLit.Timestamp(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this Col<TRow, Timestamp> col, Timestamp value) =>
|
|
col.Neq(SqlLit.Timestamp(value));
|
|
|
|
public static BoolExpr<TRow> Lt<TRow>(this Col<TRow, Timestamp> col, Timestamp value) =>
|
|
col.Lt(SqlLit.Timestamp(value));
|
|
|
|
public static BoolExpr<TRow> Lte<TRow>(this Col<TRow, Timestamp> col, Timestamp value) =>
|
|
col.Lte(SqlLit.Timestamp(value));
|
|
|
|
public static BoolExpr<TRow> Gt<TRow>(this Col<TRow, Timestamp> col, Timestamp value) =>
|
|
col.Gt(SqlLit.Timestamp(value));
|
|
|
|
public static BoolExpr<TRow> Gte<TRow>(this Col<TRow, Timestamp> col, Timestamp value) =>
|
|
col.Gte(SqlLit.Timestamp(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, string> col, ReadOnlySpan<char> value) =>
|
|
col.Eq(SqlLit.String(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(
|
|
this IxCol<TRow, string> col,
|
|
ReadOnlySpan<char> value
|
|
) => col.Neq(SqlLit.String(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, bool> col, bool value) =>
|
|
col.Eq(SqlLit.Bool(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, bool> col, bool value) =>
|
|
col.Neq(SqlLit.Bool(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, sbyte> col, sbyte value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, sbyte> col, sbyte value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, byte> col, byte value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, byte> col, byte value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, short> col, short value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, short> col, short value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, ushort> col, ushort value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, ushort> col, ushort value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, int> col, int value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, int> col, int value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, uint> col, uint value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, uint> col, uint value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, long> col, long value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, long> col, long value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, ulong> col, ulong value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, ulong> col, ulong value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, U128> col, U128 value) =>
|
|
col.Eq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, U128> col, U128 value) =>
|
|
col.Neq(SqlLit.Int(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, Identity> col, Identity value) =>
|
|
col.Eq(SqlLit.Identity(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, Identity> col, Identity value) =>
|
|
col.Neq(SqlLit.Identity(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, ConnectionId> col, ConnectionId value) =>
|
|
col.Eq(SqlLit.ConnectionId(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(
|
|
this IxCol<TRow, ConnectionId> col,
|
|
ConnectionId value
|
|
) => col.Neq(SqlLit.ConnectionId(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, Uuid> col, Uuid value) =>
|
|
col.Eq(SqlLit.Uuid(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, Uuid> col, Uuid value) =>
|
|
col.Neq(SqlLit.Uuid(value));
|
|
|
|
public static BoolExpr<TRow> Eq<TRow>(this IxCol<TRow, Timestamp> col, Timestamp value) =>
|
|
col.Eq(SqlLit.Timestamp(value));
|
|
|
|
public static BoolExpr<TRow> Neq<TRow>(this IxCol<TRow, Timestamp> col, Timestamp value) =>
|
|
col.Neq(SqlLit.Timestamp(value));
|
|
}
|
|
|
|
internal static class SqlFormat
|
|
{
|
|
private const string TimestampFormat = "yyyy-MM-dd'T'HH:mm:ss.ffffff'Z'";
|
|
|
|
public static string QuoteIdent(string ident)
|
|
{
|
|
ident ??= string.Empty;
|
|
return $"\"{ident.Replace("\"", "\"\"")}\"";
|
|
}
|
|
|
|
private static string EscapeString(string s) => s.Replace("'", "''");
|
|
|
|
public static string FormatStringLiteral(ReadOnlySpan<char> value) =>
|
|
$"'{EscapeString(value.ToString())}'";
|
|
|
|
public static string FormatHexLiteral(string hex)
|
|
{
|
|
#if NET6_0_OR_GREATER
|
|
ArgumentNullException.ThrowIfNull(hex);
|
|
#else
|
|
if (hex is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(hex));
|
|
}
|
|
#endif
|
|
|
|
var s = hex;
|
|
if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
s = s.Substring(2);
|
|
}
|
|
|
|
s = s.Replace("-", string.Empty);
|
|
|
|
for (var i = 0; i < s.Length; i++)
|
|
{
|
|
var c = s[i];
|
|
var isHex = c is >= '0' and <= '9' or >= 'a' and <= 'f' or >= 'A' and <= 'F';
|
|
if (!isHex)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(hex), $"Invalid hex character '{c}'.");
|
|
}
|
|
}
|
|
|
|
return $"0x{s}";
|
|
}
|
|
|
|
public static string FormatTimestampLiteral(Timestamp timestamp) =>
|
|
FormatStringLiteral(
|
|
timestamp
|
|
.ToStd()
|
|
.ToUniversalTime()
|
|
.ToString(TimestampFormat, CultureInfo.InvariantCulture)
|
|
);
|
|
}
|