diff --git a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs index 2ed8911f..da8a202d 100644 --- a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs +++ b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs @@ -9,4 +9,3 @@ [assembly: InternalsVisibleTo("EdgeDB.Tests.Benchmarks")] [assembly: InternalsVisibleTo("EdgeDB.BinaryDebugger")] [assembly: InternalsVisibleTo("EdgeDB.Serializer.Experiments")] - diff --git a/src/EdgeDB.Net.QueryBuilder/Attributes/EdgeQLFunctionAttribute.cs b/src/EdgeDB.Net.QueryBuilder/Attributes/EdgeQLFunctionAttribute.cs index 00c76e5a..359c3991 100644 --- a/src/EdgeDB.Net.QueryBuilder/Attributes/EdgeQLFunctionAttribute.cs +++ b/src/EdgeDB.Net.QueryBuilder/Attributes/EdgeQLFunctionAttribute.cs @@ -1,12 +1,17 @@ namespace EdgeDB; -public sealed class EdgeQLFunctionAttribute(string name, string module, string returns, bool returnsSetOf, bool returnsOptional) : Attribute +public sealed class EdgeQLFunctionAttribute( + string name, + string module, + string returns, + bool returnsSetOf, + bool returnsOptional) : Attribute { - public readonly string Name = name; public readonly string Module = module; + public readonly string Name = name; public readonly string Returns = returns; - public readonly bool ReturnsSetOf = returnsSetOf; public readonly bool ReturnsOptional = returnsOptional; + public readonly bool ReturnsSetOf = returnsSetOf; public string GetFormattedReturnType() { diff --git a/src/EdgeDB.Net.QueryBuilder/Builders/ShapeBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Builders/ShapeBuilder.cs index 30eeacfa..0d35cd8d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Builders/ShapeBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Builders/ShapeBuilder.cs @@ -1,307 +1,278 @@ - -/* Unmerged change from project 'EdgeDB.Net.QueryBuilder (net6.0)' -Before: -using EdgeDB.Schema; -After: -using EdgeDB; -using EdgeDB.Builders; -using EdgeDB.Schema; -*/ -using EdgeDB.Schema; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Builders -{ - internal delegate void SelectShapeExpressionTranslatorCallback(QueryWriter writer, ShapeElementExpression expression); +namespace EdgeDB.Builders; - internal class SelectShape - { - private readonly SelectedProperty[] _shape; +internal delegate void SelectShapeExpressionTranslatorCallback(QueryWriter writer, ShapeElementExpression expression); - private readonly Type _type; +internal class SelectShape(IEnumerable shape, Type type) +{ + private readonly SelectedProperty[] _shape = shape.ToArray(); - public SelectShape(IEnumerable shape, Type type) + public void Compile(QueryWriter writer, SelectShapeExpressionTranslatorCallback translator) => + writer.Shape($"{type.GetEdgeDBTypeName()}_shape", _shape, (writer, x) => { - _shape = shape.ToArray(); - _type = type; - } + x.Compile(writer, translator); + }); +} - public void Compile(QueryWriter writer, SelectShapeExpressionTranslatorCallback translator) - { - writer.Shape($"{_type.GetEdgeDBTypeName()}_shape", _shape, (writer, x) => - { - x.Compile(writer, translator); - }); - } +internal class SelectedProperty(MemberInfo member) +{ + public SelectedProperty(MemberInfo member, ShapeElementExpression value) + : this(member) + { + ElementValue = value; } - internal class SelectedProperty + public SelectedProperty(MemberInfo member, SelectShape shape) + : this(member) { - public string Name { get; } - public ShapeElementExpression? ElementValue { get; } - public SelectShape? ElementShape { get; } + ElementShape = shape; + } - public SelectedProperty(MemberInfo member) - { - Name = member.GetEdgeDBPropertyName(); - } + public string Name { get; } = member.GetEdgeDBPropertyName(); + public ShapeElementExpression? ElementValue { get; } + public SelectShape? ElementShape { get; } - public SelectedProperty(MemberInfo member, ShapeElementExpression value) - : this(member) + public void Compile(QueryWriter writer, SelectShapeExpressionTranslatorCallback translator) + { + if (ElementValue.HasValue) { - ElementValue = value; + writer.Append(Name, " := "); + translator(writer, ElementValue.Value); } - - public SelectedProperty(MemberInfo member, SelectShape shape) - : this(member) + else if (ElementShape is not null) { - ElementShape = shape; + writer.Append(Name, ": "); + ElementShape.Compile(writer, translator); } - - public void Compile(QueryWriter writer, SelectShapeExpressionTranslatorCallback translator) + else { - if (ElementValue.HasValue) - { - writer.Append(Name, " := "); - translator(writer, ElementValue.Value); - } - else if (ElementShape is not null) - { - writer.Append(Name, ": "); - ElementShape.Compile(writer, translator); - } - else - { - writer.Append(Name); - } + writer.Append(Name); } } +} - internal readonly struct ShapeElementExpression - { - //public readonly bool IsSelector; +internal readonly struct ShapeElementExpression +{ + //public readonly bool IsSelector; - public readonly LambdaExpression Root; - public readonly Expression Expression; + public readonly LambdaExpression Root; + public readonly Expression Expression; - public ShapeElementExpression(LambdaExpression root, Expression exp) - { - Root = root; - Expression = exp; - } + public ShapeElementExpression(LambdaExpression root, Expression exp) + { + Root = root; + Expression = exp; } +} - public abstract class BaseShapeBuilder : IShapeBuilder +public abstract class BaseShapeBuilder : IShapeBuilder +{ + public BaseShapeBuilder(Type type) { - public static BaseShapeBuilder CreateDefault(Type type) - => new StaticShapeBuilder(type); - public Type SelectedType { get; private set; } + SelectedType = type; - internal Dictionary SelectedProperties { get; set; } - internal Dictionary LinkProperties { get; set; } + LinkProperties = new Dictionary(); + SelectedProperties = new Dictionary(); - public BaseShapeBuilder(Type type) - { - SelectedType = type; + var allProps = type.GetEdgeDBTargetProperties(); - LinkProperties = new(); - SelectedProperties = new(); + foreach (var prop in allProps) + if (EdgeDBTypeUtils.IsLink(prop.PropertyType, out var isMulti, out _)) + LinkProperties.Add(prop.GetEdgeDBPropertyName(), (prop, isMulti)); + else + SelectedProperties.Add(prop.GetEdgeDBPropertyName(), new SelectedProperty(prop)); + } - var allProps = type.GetEdgeDBTargetProperties(); + internal Dictionary SelectedProperties { get; set; } + internal Dictionary LinkProperties { get; set; } + public Type SelectedType { get; } - foreach (var prop in allProps) - if (EdgeDBTypeUtils.IsLink(prop.PropertyType, out var isMulti, out _)) - LinkProperties.Add(prop.GetEdgeDBPropertyName(), (prop, isMulti)); - else - SelectedProperties.Add(prop.GetEdgeDBPropertyName(), new(prop)); - } + SelectShape IShapeBuilder.GetShape() => GetShape(); - internal static Dictionary FlattenAnonymousExpression(Type selectedType, LambdaExpression expression) - { - // new expression - if (expression.Body is not NewExpression init || !init.Type.IsAnonymousType() || init.Members is null) - throw new InvalidOperationException($"Expected anonymous object initializer, but got {expression.Body}"); + public static BaseShapeBuilder CreateDefault(Type type) + => new StaticShapeBuilder(type); - return FlattenNewExpression(selectedType, init, expression); - } + internal static Dictionary FlattenAnonymousExpression(Type selectedType, + LambdaExpression expression) + { + // new expression + if (expression.Body is not NewExpression init || !init.Type.IsAnonymousType() || init.Members is null) + throw new InvalidOperationException($"Expected anonymous object initializer, but got {expression.Body}"); - internal static Dictionary FlattenNewExpression(Type? selectedType, NewExpression expression, LambdaExpression root) - { - if (!expression.Type.IsAnonymousType() || expression.Members is null) - throw new InvalidOperationException($"Expected anonymous object initializer, but got {expression}"); + return FlattenNewExpression(selectedType, init, expression); + } - var realProps = selectedType?.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - ?? Array.Empty(); + internal static Dictionary FlattenNewExpression(Type? selectedType, + NewExpression expression, LambdaExpression root) + { + if (!expression.Type.IsAnonymousType() || expression.Members is null) + throw new InvalidOperationException($"Expected anonymous object initializer, but got {expression}"); - Dictionary dict = new(); + var realProps = + selectedType?.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + ?? Array.Empty(); - for (var i = 0; i != expression.Arguments.Count; i++) - { - var argExpression = expression.Arguments[i]; - var member = expression.Members[i]; + Dictionary dict = new(); - // cross reference the 'T' type and check for any explicit name or naming convention - var realProp = realProps.FirstOrDefault(x => x.Name == member.Name); + for (var i = 0; i != expression.Arguments.Count; i++) + { + var argExpression = expression.Arguments[i]; + var member = expression.Members[i]; - dict.Add(realProp ?? member, new(root, argExpression)); - } + // cross reference the 'T' type and check for any explicit name or naming convention + var realProp = realProps.FirstOrDefault(x => x.Name == member.Name); - return dict; + dict.Add(realProp ?? member, new ShapeElementExpression(root, argExpression)); } - internal SelectShape GetShape() - { - return new(SelectedProperties.Select(x => x.Value), SelectedType); - } + return dict; + } - SelectShape IShapeBuilder.GetShape() => GetShape(); + internal SelectShape GetShape() => new(SelectedProperties.Select(x => x.Value), SelectedType); - private class StaticShapeBuilder : BaseShapeBuilder + private class StaticShapeBuilder : BaseShapeBuilder + { + public StaticShapeBuilder(Type type) + : base(type) { - public StaticShapeBuilder(Type type) - : base(type) - { - - } } } +} - public sealed class ShapeBuilder : BaseShapeBuilder +public sealed class ShapeBuilder : BaseShapeBuilder +{ + public ShapeBuilder() + : base(typeof(T)) { - public ShapeBuilder() - : base(typeof(T)) - { } - - public ShapeBuilder IncludeMultiLink(Expression?>> selector) - => IncludeInternal(selector); + } - public ShapeBuilder IncludeMultiLink( - Expression?>> selector, - Action> shape) - => IncludeInternal(selector, shape); + public ShapeBuilder IncludeMultiLink(Expression?>> selector) + => IncludeInternal(selector); - public ShapeBuilder Include(Expression> selector) - => IncludeInternal(selector, errorOnMultiLink: true); + public ShapeBuilder IncludeMultiLink( + Expression?>> selector, + Action> shape) + => IncludeInternal(selector, shape); - public ShapeBuilder Include(Expression> selector, Action> shape) - => IncludeInternal(selector, shape, errorOnMultiLink: true); + public ShapeBuilder Include(Expression> selector) + => IncludeInternal(selector, errorOnMultiLink: true); - public ShapeBuilder Exclude(Expression> selector) - { - var member = ExpressionUtils.GetMemberSelection(selector); + public ShapeBuilder Include(Expression> selector, + Action> shape) + => IncludeInternal(selector, shape, true); - SelectedProperties.Remove(member.GetEdgeDBPropertyName()); + public ShapeBuilder Exclude(Expression> selector) + { + var member = ExpressionUtils.GetMemberSelection(selector); - return this; - } + SelectedProperties.Remove(member.GetEdgeDBPropertyName()); - public ShapeBuilder Computeds(Expression> computedsSelector) - => ComputedsInternal(computedsSelector); + return this; + } - public ShapeBuilder Computeds(Expression, T, TAnon>> computedsSelector) - => ComputedsInternal(computedsSelector); + public ShapeBuilder Computeds(Expression> computedsSelector) + => ComputedsInternal(computedsSelector); - internal ShapeBuilder ComputedsInternal(LambdaExpression expression) - { - var computeds = FlattenAnonymousExpression(SelectedType, expression); + public ShapeBuilder Computeds(Expression, T, TAnon>> computedsSelector) + => ComputedsInternal(computedsSelector); - foreach (var computed in computeds) - SelectedProperties[computed.Key.GetEdgeDBPropertyName()] = new(computed.Key, computed.Value); - - return this; - } + internal ShapeBuilder ComputedsInternal(LambdaExpression expression) + { + var computeds = FlattenAnonymousExpression(SelectedType, expression); - public ShapeBuilder Explicitly(Expression> explicitSelector) - => ExplicitlyInternal(explicitSelector); + foreach (var computed in computeds) + SelectedProperties[computed.Key.GetEdgeDBPropertyName()] = + new SelectedProperty(computed.Key, computed.Value); - public ShapeBuilder Explicitly(Expression, T, TAnon>> explicitSelector) - => ExplicitlyInternal(explicitSelector); + return this; + } + public ShapeBuilder Explicitly(Expression> explicitSelector) + => ExplicitlyInternal(explicitSelector); - internal ShapeBuilder ExplicitlyInternal(LambdaExpression expression) - { - var members = FlattenAnonymousExpression(SelectedType, expression); + public ShapeBuilder Explicitly(Expression, T, TAnon>> explicitSelector) + => ExplicitlyInternal(explicitSelector); - SelectedProperties.Clear(); - foreach (var member in members) - SelectedProperties[member.Key.GetEdgeDBPropertyName()] = ParseShape(member.Key, member.Value); + internal ShapeBuilder ExplicitlyInternal(LambdaExpression expression) + { + var members = FlattenAnonymousExpression(SelectedType, expression); - return new(SelectedType, this); - } + SelectedProperties.Clear(); - private SelectedProperty ParseShape(MemberInfo info, ShapeElementExpression element) - { - // theres 3 types we could come across: - // 1. Property: include the specified property in the shape; ex: 'Name = x.Name' - // 2. Computed: include the compuded in the shape; ex: 'Name = {exp}' - // 3. Subshape: include the sub shape; ex: 'Friend = new { Name = x.Friend.Name } + foreach (var member in members) + SelectedProperties[member.Key.GetEdgeDBPropertyName()] = ParseShape(member.Key, member.Value); - if (element.Expression is MemberExpression) - { - var treeTail = ExpressionUtils.DisassembleExpression(element.Expression).Last(); + return new ShapeBuilder(SelectedType, this); + } - if (treeTail is ParameterExpression param && element.Root.Parameters.Contains(param)) - { - return new SelectedProperty(info); - } + private SelectedProperty ParseShape(MemberInfo info, ShapeElementExpression element) + { + // theres 3 types we could come across: + // 1. Property: include the specified property in the shape; ex: 'Name = x.Name' + // 2. Computed: include the compuded in the shape; ex: 'Name = {exp}' + // 3. Subshape: include the sub shape; ex: 'Friend = new { Name = x.Friend.Name } - // treat as a computed - return new SelectedProperty(info, element); - } + if (element.Expression is MemberExpression) + { + var treeTail = ExpressionUtils.DisassembleExpression(element.Expression).Last(); - if (element.Expression is NewExpression newExpression) + if (treeTail is ParameterExpression param && element.Root.Parameters.Contains(param)) { - // this is a subshape, try to get the type - - var flattened = FlattenNewExpression(info.GetMemberType(), newExpression, element.Root) - .Select(x => ParseShape(x.Key, x.Value)); - - return new SelectedProperty(info, new SelectShape(flattened, info.GetMemberType())); + return new SelectedProperty(info); } - // computed + // treat as a computed return new SelectedProperty(info, element); } - private ShapeBuilder IncludeInternal(LambdaExpression selector, Action>? shape = null, bool errorOnMultiLink = false) + if (element.Expression is NewExpression newExpression) { - var member = ExpressionUtils.GetMemberSelection(selector); - - if (errorOnMultiLink && LinkProperties.TryGetValue(member.GetEdgeDBPropertyName(), out var info) && info.Item2) - throw new InvalidOperationException("Use IncludeMultiLink for multi-link properties"); + // this is a subshape, try to get the type - if (LinkProperties.ContainsKey(member.GetEdgeDBPropertyName())) - { - var builder = new ShapeBuilder(); - if (shape is not null) - shape(builder); - SelectedProperties.TryAdd(member.GetEdgeDBPropertyName(), new(member, builder.GetShape())); - } + var flattened = FlattenNewExpression(info.GetMemberType(), newExpression, element.Root) + .Select(x => ParseShape(x.Key, x.Value)); - return this; + return new SelectedProperty(info, new SelectShape(flattened, info.GetMemberType())); } + + // computed + return new SelectedProperty(info, element); } - public sealed class ShapeBuilder : BaseShapeBuilder + private ShapeBuilder IncludeInternal(LambdaExpression selector, + Action>? shape = null, bool errorOnMultiLink = false) { - public ShapeBuilder(Type type, BaseShapeBuilder other) : base(type) + var member = ExpressionUtils.GetMemberSelection(selector); + + if (errorOnMultiLink && LinkProperties.TryGetValue(member.GetEdgeDBPropertyName(), out var info) && info.Item2) + throw new InvalidOperationException("Use IncludeMultiLink for multi-link properties"); + + if (LinkProperties.ContainsKey(member.GetEdgeDBPropertyName())) { - this.LinkProperties = other.LinkProperties; - this.SelectedProperties = other.SelectedProperties; + var builder = new ShapeBuilder(); + if (shape is not null) + shape(builder); + SelectedProperties.TryAdd(member.GetEdgeDBPropertyName(), new SelectedProperty(member, builder.GetShape())); } + + return this; } +} - internal interface IShapeBuilder +public sealed class ShapeBuilder : BaseShapeBuilder +{ + public ShapeBuilder(Type type, BaseShapeBuilder other) : base(type) { - Type SelectedType { get; } - internal SelectShape GetShape(); + LinkProperties = other.LinkProperties; + SelectedProperties = other.SelectedProperties; } } + +internal interface IShapeBuilder +{ + Type SelectedType { get; } + internal SelectShape GetShape(); +} diff --git a/src/EdgeDB.Net.QueryBuilder/Builders/UpdateShapeBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Builders/UpdateShapeBuilder.cs index 3eaa3e4f..d1df2016 100644 --- a/src/EdgeDB.Net.QueryBuilder/Builders/UpdateShapeBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Builders/UpdateShapeBuilder.cs @@ -6,35 +6,6 @@ namespace EdgeDB; public sealed class UpdateShapeBuilder : IUpdateShapeBuilder where U : IQueryContext { - private readonly struct ShapeElement(MemberInfo key, LambdaExpression value, ExpressionType type) - { - public void Write(QueryWriter writer, Action translator) - { - var op = type switch - { - ExpressionType.Assign => ":=", - ExpressionType.AddAssign => "+=", - ExpressionType.SubtractAssign => "-=", - _ => throw new InvalidOperationException($"Unsupported operator \"{type}\"") - }; - var key1 = key; - var expression = value; - - writer.Term( - TermType.BinaryOp, - "update_shape_element", - Defer.This(() => $"Operator {op} for update shape element on {key1.Name}"), - metadata: new BinaryOpMetadata(type), - Token.Of(writer => - { - writer.Append(key1.GetEdgeDBPropertyName(), ' ', op, ' '); - translator(writer, expression); - } - ) - ); - } - } - private readonly LinkedList _elements = []; private UpdateShapeBuilder(LinkedList elements) @@ -44,12 +15,13 @@ private UpdateShapeBuilder(LinkedList elements) public UpdateShapeBuilder() { - } + void IUpdateShapeBuilder.Compile(QueryWriter writer, Action translator) + => Compile(writer, translator); + internal static IUpdateShapeBuilder FromInitExpression(LambdaExpression expression) { - var elements = new LinkedList(); switch (expression.Body) @@ -69,6 +41,7 @@ internal static IUpdateShapeBuilder FromInitExpression(LambdaExpression expressi $"Unsupported initialization binding {memberInit.Bindings[i].GetType().Name}"); } } + break; case NewExpression newExpression when newExpression.Type.IsAnonymousType(): var members = newExpression.Type.GetProperties(); @@ -78,6 +51,7 @@ internal static IUpdateShapeBuilder FromInitExpression(LambdaExpression expressi Expression.Lambda(newExpression.Arguments[i], false, expression.Parameters), ExpressionType.Assign)); } + break; default: throw new InvalidOperationException($"Unsupported shape initialization type {expression.GetType()}"); @@ -88,11 +62,14 @@ internal static IUpdateShapeBuilder FromInitExpression(LambdaExpression expressi public UpdateShapeBuilder Set(Expression> selector, Expression> value) => AddElement(selector, value, ExpressionType.Assign); + public UpdateShapeBuilder Set(Expression> selector, Expression> value) => AddElement(selector, value, ExpressionType.Assign); - public UpdateShapeBuilder Add(Expression?>> selector, Expression> value) + public UpdateShapeBuilder Add(Expression?>> selector, + Expression> value) => AddElement(selector, value, ExpressionType.AddAssign); + public UpdateShapeBuilder Add(Expression?>> selector, Expression> value) => AddElement(selector, value, ExpressionType.AddAssign); @@ -104,14 +81,20 @@ public UpdateShapeBuilder Add(Expression?>> sele Expression>> value) => AddElement(selector, value, ExpressionType.AddAssign); - public UpdateShapeBuilder Remove(Expression?>> selector, Expression> value) + public UpdateShapeBuilder Remove(Expression?>> selector, + Expression> value) => AddElement(selector, value, ExpressionType.SubtractAssign); - public UpdateShapeBuilder Remove(Expression?>> selector, Expression> value) + + public UpdateShapeBuilder Remove(Expression?>> selector, + Expression> value) => AddElement(selector, value, ExpressionType.SubtractAssign); - public UpdateShapeBuilder Remove(Expression?>> selector, Expression>> value) + public UpdateShapeBuilder Remove(Expression?>> selector, + Expression>> value) => AddElement(selector, value, ExpressionType.SubtractAssign); - public UpdateShapeBuilder Remove(Expression?>> selector, Expression>> value) + + public UpdateShapeBuilder Remove(Expression?>> selector, + Expression>> value) => AddElement(selector, value, ExpressionType.SubtractAssign); private UpdateShapeBuilder AddElement(LambdaExpression selector, LambdaExpression value, ExpressionType type) @@ -123,18 +106,42 @@ private UpdateShapeBuilder AddElement(LambdaExpression selector, LambdaExp return this; } - internal void Compile(QueryWriter writer, Action translator) - { + internal void Compile(QueryWriter writer, Action translator) => writer.Shape( "update_shape", _elements.ToArray(), (writer, v) => v.Write(writer, translator), debug: Defer.This(() => $"Update shape for {typeof(T).Name}") ); - } - void IUpdateShapeBuilder.Compile(QueryWriter writer, Action translator) - => Compile(writer, translator); + private readonly struct ShapeElement(MemberInfo key, LambdaExpression value, ExpressionType type) + { + public void Write(QueryWriter writer, Action translator) + { + var op = type switch + { + ExpressionType.Assign => ":=", + ExpressionType.AddAssign => "+=", + ExpressionType.SubtractAssign => "-=", + _ => throw new InvalidOperationException($"Unsupported operator \"{type}\"") + }; + var key1 = key; + var expression = value; + + writer.Term( + TermType.BinaryOp, + "update_shape_element", + Defer.This(() => $"Operator {op} for update shape element on {key1.Name}"), + new BinaryOpMetadata(type), + Token.Of(writer => + { + writer.Append(key1.GetEdgeDBPropertyName(), ' ', op, ' '); + translator(writer, expression); + } + ) + ); + } + } } internal interface IUpdateShapeBuilder diff --git a/src/EdgeDB.Net.QueryBuilder/Compiled/CompiledQuery.cs b/src/EdgeDB.Net.QueryBuilder/Compiled/CompiledQuery.cs index fb3ef42b..2b397cae 100644 --- a/src/EdgeDB.Net.QueryBuilder/Compiled/CompiledQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Compiled/CompiledQuery.cs @@ -5,6 +5,7 @@ namespace EdgeDB; public class CompiledQuery(string query, Dictionary? variables) { + internal readonly Dictionary? RawVariables = variables; public string Query { get; } = query; public string Prettied => Prettify(); @@ -12,8 +13,6 @@ public class CompiledQuery(string query, Dictionary? variables) public IReadOnlyDictionary? Variables { get; } = variables?.ToImmutableDictionary(); - internal readonly Dictionary? RawVariables = variables; - /// /// Prettifies the query text. /// @@ -21,7 +20,7 @@ public class CompiledQuery(string query, Dictionary? variables) /// This method uses a lot of regex and can be unreliable, if /// you're using this in a production setting please use with care. /// - /// A prettified version of . + /// A prettified version of . public string Prettify() { // add newlines @@ -36,7 +35,8 @@ public string Prettify() return $"{m.Groups[1].Value}\n"; default: - return $"{((m.Groups[1].Value == "}" && (Query[m.Index - 1] == '{' || Query[m.Index - 1] == '}')) ? "" : "\n")}{m.Groups[1].Value}{((Query.Length != m.Index + 1 && (Query[m.Index + 1] != ',')) ? "\n" : "")}"; + return + $"{(m.Groups[1].Value == "}" && (Query[m.Index - 1] == '{' || Query[m.Index - 1] == '}') ? "" : "\n")}{m.Groups[1].Value}{(Query.Length != m.Index + 1 && Query[m.Index + 1] != ',' ? "\n" : "")}"; } }).Trim().Replace("\n ", "\n"); @@ -46,7 +46,7 @@ public string Prettify() // add indentation result = Regex.Replace(result, "^", m => { - int indent = 0; + var indent = 0; foreach (var c in result[..m.Index]) { diff --git a/src/EdgeDB.Net.QueryBuilder/Compiled/DebugCompiledQuery.cs b/src/EdgeDB.Net.QueryBuilder/Compiled/DebugCompiledQuery.cs index b522fe0b..2c46bf92 100644 --- a/src/EdgeDB.Net.QueryBuilder/Compiled/DebugCompiledQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Compiled/DebugCompiledQuery.cs @@ -6,14 +6,15 @@ namespace EdgeDB.Compiled; [DebuggerDisplay("{DebugView}")] public sealed class DebugCompiledQuery : CompiledQuery { - public string DebugView { get; } - - internal DebugCompiledQuery(string query, Dictionary variables, LinkedList terms, LinkedList tokens) + internal DebugCompiledQuery(string query, Dictionary variables, LinkedList terms, + LinkedList tokens) : base(query, variables) { DebugView = CreateDebugText(query, variables, terms, tokens); } + public string DebugView { get; } + private static string CreateDebugText(string query, Dictionary variables, LinkedList terms, LinkedList tokens) { @@ -50,7 +51,7 @@ private static string CreateDebugText(string query, Dictionary // bar rowText.Remove(column.Range.Start.Value, size); - var barText = new StringBuilder($"\u2550".PadLeft(size - 3, '\u2550')); + var barText = new StringBuilder("\u2550".PadLeft(size - 3, '\u2550')); barText.Insert(barText.Length / 2, "\u2566"); // T barText.Insert(0, "\u255a"); // corner UR @@ -117,19 +118,18 @@ char DrawVert(char prev) termTexts.Add(icon, column); - var position = query.Length + 1 // line 2 - + column.Range.Start.Value // start of the slice - + size / 2 // half of the slices' length : center of the slice - - (desc.Length == 1 - ? size % 2 == 0 ? 1 : 0 // don't ask - : desc.Length / 2); // half of the contents length : centers it + var position = query.Length + 1 // line 2 + + column.Range.Start.Value // start of the slice + + size / 2 // half of the slices' length : center of the slice + - (desc.Length == 1 + ? size % 2 == 0 ? 1 : 0 // don't ask + : desc.Length / 2); // half of the contents length : centers it rowText.Remove(position, desc.Length); rowText.Insert(position, desc); } rows.Add(rowText); - } if (topRow is not null) @@ -211,7 +211,8 @@ char DrawVert(char prev) private static List> CreateTermView(LinkedList spans) { - var ordered = new Queue(spans.OrderBy(x => x.Range.End.Value - x.Range.Start.Value)); // order by 'size' + var ordered = + new Queue(spans.OrderBy(x => x.Range.End.Value - x.Range.Start.Value)); // order by 'size' var result = new List>(); var row = new List(); @@ -250,7 +251,7 @@ private static List> CreateTermView(LinkedList spans) } -#if DEBUG +#if DEBUG internal static string QuickView(QueryWriter writer) { var (query, terms, tokens) = writer.CompileDebug(); diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj index df045774..63ba2d24 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj +++ b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj @@ -1,13 +1,13 @@ - + - EdgeDB.Net.QueryBuilder - EdgeDB + EdgeDB.Net.QueryBuilder + EdgeDB An optional extension to the base driver that adds a query builder. net6.0;net7.0 enable enable - CS1591 + CS1591 True 5 true @@ -17,18 +17,18 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + - + diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs b/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs index 371ecd18..1259f0af 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs +++ b/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs @@ -1,30 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB; -namespace EdgeDB +/// +/// Represents a generic object within EdgeDB. +/// +public sealed class EdgeDBObject { /// - /// Represents a generic object within EdgeDB. + /// Constructs a new with the given data. /// - public sealed class EdgeDBObject + /// The raw data for this object. + [EdgeDBDeserializer] + internal EdgeDBObject(IDictionary data) { - /// - /// Gets the unique identifier for this object. - /// - [EdgeDBProperty("id")] - public Guid Id { get; } - - /// - /// Constructs a new with the given data. - /// - /// The raw data for this object. - [EdgeDBDeserializer] - internal EdgeDBObject(IDictionary data) - { - Id = (Guid)data["id"]!; - } + Id = (Guid)data["id"]!; } + + /// + /// Gets the unique identifier for this object. + /// + [EdgeDBProperty("id")] + public Guid Id { get; } } diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs b/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs index 3035b01d..1948a58c 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs +++ b/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs @@ -1,64 +1,64 @@ -using EdgeDB.Interfaces; using System.Diagnostics.CodeAnalysis; using System.Reflection; -namespace EdgeDB +namespace EdgeDB; + +public sealed partial class EdgeQL { - public sealed partial class EdgeQL + private static readonly Dictionary>> _edgeqlFunctions; + + static EdgeQL() { - private static readonly Dictionary>> _edgeqlFunctions; + var methods = typeof(EdgeQL).GetMethods(); + _edgeqlFunctions = new Dictionary>>(); - static EdgeQL() + foreach (var method in methods) { - var methods = typeof(EdgeQL).GetMethods(); - _edgeqlFunctions = new(); - - foreach (var method in methods) - { - var edgeqlFuncAttribute = method.GetCustomAttribute(); - - if(edgeqlFuncAttribute is null) - continue; + var edgeqlFuncAttribute = method.GetCustomAttribute(); - if (!_edgeqlFunctions.TryGetValue(edgeqlFuncAttribute.Module, out var moduleFunctions)) - moduleFunctions = _edgeqlFunctions[edgeqlFuncAttribute.Module] = new(); + if (edgeqlFuncAttribute is null) + continue; - if (!moduleFunctions.TryGetValue(edgeqlFuncAttribute.Name, out var functions)) - functions = moduleFunctions[edgeqlFuncAttribute.Name] = new(); + if (!_edgeqlFunctions.TryGetValue(edgeqlFuncAttribute.Module, out var moduleFunctions)) + moduleFunctions = _edgeqlFunctions[edgeqlFuncAttribute.Module] = + new Dictionary>(); - functions.Add(method); - } - } + if (!moduleFunctions.TryGetValue(edgeqlFuncAttribute.Name, out var functions)) + functions = moduleFunctions[edgeqlFuncAttribute.Name] = new List(); - internal static bool TryGetMethods(string name, string module, [MaybeNullWhen(false)] out List methods) - { - methods = null; - return _edgeqlFunctions.TryGetValue(module, out var moduleFunctions) && moduleFunctions.TryGetValue(name, out methods); + functions.Add(method); } + } - internal static List SearchMethods(string name) - { - var result = new List(); + internal static bool TryGetMethods(string name, string module, [MaybeNullWhen(false)] out List methods) + { + methods = null; + return _edgeqlFunctions.TryGetValue(module, out var moduleFunctions) && + moduleFunctions.TryGetValue(name, out methods); + } - foreach (var (module, functions) in _edgeqlFunctions) - { - if (functions.TryGetValue(name, out var targetFunctions)) - result.AddRange(targetFunctions); - } + internal static List SearchMethods(string name) + { + var result = new List(); - return result; + foreach (var (module, functions) in _edgeqlFunctions) + { + if (functions.TryGetValue(name, out var targetFunctions)) + result.AddRange(targetFunctions); } - public static T Rollup(T value) - => default!; + return result; + } - public static T Cube(T value) - => default!; + public static T Rollup(T value) + => default!; - public static JsonReferenceVariable AsJson(T value) => new(value); + public static T Cube(T value) + => default!; - public static long Count(IQuery a) { return default!; } + public static JsonReferenceVariable AsJson(T value) => new(value); - public static EdgeDBTypeContainer SchemaType() => EdgeDBTypeContainer.Create(); - } + public static long Count(IQuery a) => default!; + + public static EdgeDBTypeContainer SchemaType() => EdgeDBTypeContainer.Create(); } diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs index c5926d93..97203620 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs @@ -1,23 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB; -namespace EdgeDB +public static class EdgeDBExtensions { - public static class EdgeDBExtensions - { - internal static SubQuery SelectSubQuery(this Guid id, Type queryType) - { - return new SubQuery(writer => writer - .Wrapped(writer => writer - .Append("select ") - .Append(queryType.GetEdgeDBTypeName()) - .Append(" filter .id = ") - .SingleQuoted(id.ToString()) - ) - ); - } - } + internal static SubQuery SelectSubQuery(this Guid id, Type queryType) => + new(writer => writer + .Wrapped(writer => writer + .Append("select ") + .Append(queryType.GetEdgeDBTypeName()) + .Append(" filter .id = ") + .SingleQuoted(id.ToString()) + ) + ); } diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/GroupingExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/GroupingExtensions.cs index ad65e8b9..7a58a725 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/GroupingExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/GroupingExtensions.cs @@ -1,7 +1,4 @@ -using EdgeDB.Interfaces.Queries; -using System.Linq.Expressions; - -namespace EdgeDB; +namespace EdgeDB; public static class QueryBuilderGroupingExtensions { diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/IEnumerableExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/IEnumerableExtensions.cs index d1bf372b..af92088f 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/IEnumerableExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/IEnumerableExtensions.cs @@ -4,7 +4,7 @@ public static class EnumerableExtensions { public static Dictionary> ToBucketedDictionary(this IEnumerable collection, Func selectKey, Func selectValue) - where T: notnull + where T : notnull { var dict = new Dictionary>(); @@ -14,7 +14,7 @@ public static Dictionary> ToBucketedDictionary(this IE var value = selectValue(item); if (!dict.TryGetValue(key, out var bucket)) - dict[key] = bucket = new(); + dict[key] = bucket = new LinkedList(); bucket.AddLast(value); } diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/QueryBuilderExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/QueryBuilderExtensions.cs index 3c14c205..c6734629 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/QueryBuilderExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/QueryBuilderExtensions.cs @@ -1,57 +1,53 @@ using EdgeDB.QueryNodes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +internal static class QueryBuilderExtensions { - internal static class QueryBuilderExtensions + public static void WriteTo(this IQueryBuilder source, QueryWriter writer, IQueryBuilder target, + CompileContext? context = null) { - public static void WriteTo(this IQueryBuilder source, QueryWriter writer, IQueryBuilder target, CompileContext? context = null) - { - source.CompileInternal(writer, context); + source.CompileInternal(writer, context); - if (source.Variables.Any(variable => !target.Variables.TryAdd(variable.Key, variable.Value))) - { - throw new InvalidOperationException( - "A variable with the same name already exists in the target builder"); - } - - target.Globals.AddRange(source.Globals); + if (source.Variables.Any(variable => !target.Variables.TryAdd(variable.Key, variable.Value))) + { + throw new InvalidOperationException( + "A variable with the same name already exists in the target builder"); } - public static void WriteTo( - this IQueryBuilder source, - QueryWriter writer, - ExpressionContext expressionContext, - CompileContext? compileContext = null) - { - source.CompileInternal(writer, compileContext); + target.Globals.AddRange(source.Globals); + } - foreach (var variable in source.Variables) - { - expressionContext.SetVariable(variable.Key, variable.Value); - } + public static void WriteTo( + this IQueryBuilder source, + QueryWriter writer, + ExpressionContext expressionContext, + CompileContext? compileContext = null) + { + source.CompileInternal(writer, compileContext); - foreach (var global in source.Globals) - { - expressionContext.SetGlobal(global.Name, global.Value, global.Reference); - } + foreach (var variable in source.Variables) + { + expressionContext.SetVariable(variable.Key, variable.Value); } - public static void WriteTo(this IQueryBuilder source, QueryWriter writer, QueryNode node, CompileContext? compileContext = null) + foreach (var global in source.Globals) { - source.CompileInternal(writer, compileContext); + expressionContext.SetGlobal(global.Name, global.Value, global.Reference); + } + } - if (source.Variables.Any(variable => !node.Builder.QueryVariables.TryAdd(variable.Key, variable.Value))) - { - throw new InvalidOperationException( - "A variable with the same name already exists in the target builder"); - } + public static void WriteTo(this IQueryBuilder source, QueryWriter writer, QueryNode node, + CompileContext? compileContext = null) + { + source.CompileInternal(writer, compileContext); - node.Builder.QueryGlobals.AddRange(source.Globals); + if (source.Variables.Any(variable => !node.Builder.QueryVariables.TryAdd(variable.Key, variable.Value))) + { + throw new InvalidOperationException( + "A variable with the same name already exists in the target builder"); } + + node.Builder.QueryGlobals.AddRange(source.Globals); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/RangeExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/RangeExtensions.cs index 0fda7fdf..4a4ca770 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/RangeExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/RangeExtensions.cs @@ -2,15 +2,9 @@ public static class RangeExtensions { - public static bool Overlaps(this Range a, Range b) - { - return a.Start.Value < b.End.Value && b.Start.Value < a.End.Value; - } + public static bool Overlaps(this Range a, Range b) => a.Start.Value < b.End.Value && b.Start.Value < a.End.Value; - public static Range Normalize(this Range range) - { - return range.Start..(range.End.Value + range.Start.Value); - } + public static Range Normalize(this Range range) => range.Start..(range.End.Value + range.Start.Value); public static bool Contains(this Range range, int point) => range.Start.Value <= point && range.End.Value >= point; diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/StringBuilderExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/StringBuilderExtensions.cs index e7b68305..bbc32527 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/StringBuilderExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/StringBuilderExtensions.cs @@ -1,8 +1,5 @@ -using System.Text; - namespace EdgeDB; internal static class StringBuilderExtensions { - } diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs index 5e43336f..dbc318c0 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs @@ -1,71 +1,69 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +internal static class TypeExtensions { - internal static class TypeExtensions + public static bool References(this Type type, Type other) + => References(type, other, true, []); + + private static bool References(Type type, Type other, bool checkInterfaces, HashSet hasChecked) { - public static bool References(this Type type, Type other) - => References(type, other, true, []); + if (!hasChecked.Add(type)) + return false; + + if (type == other) + return true; - private static bool References(Type type, Type other, bool checkInterfaces, HashSet hasChecked) + return type switch { - if (!hasChecked.Add(type)) - return false; + {IsArray: true} => References(type.GetElementType()!, other, true, hasChecked), + {IsGenericType: true} => type.GetGenericArguments().Any(x => References(x, other, true, hasChecked)), + _ => (type.BaseType?.References(other) ?? false) || (checkInterfaces && + type.GetInterfaces().Any(x => + References(x, other, false, hasChecked))) + }; + } - if (type == other) - return true; + public static IEnumerable GetEdgeDBTargetProperties(this Type type, bool excludeId = false) + => type.GetProperties().Where(x => + x.GetCustomAttribute() == null && !(excludeId && x.Name == "Id" && + (x.PropertyType == typeof(Guid) || + x.PropertyType == typeof(Guid?)))); - return type switch - { - { IsArray: true } => References(type.GetElementType()!, other, true, hasChecked), - { IsGenericType: true } => type.GetGenericArguments().Any(x => References(x, other, true, hasChecked)), - _ => (type.BaseType?.References(other) ?? false) || (checkInterfaces && type.GetInterfaces().Any(x => References(x, other, false, hasChecked))) - }; - } - - public static IEnumerable GetEdgeDBTargetProperties(this Type type, bool excludeId = false) - => type.GetProperties().Where(x => x.GetCustomAttribute() == null && !(excludeId && x.Name == "Id" && (x.PropertyType == typeof(Guid) || x.PropertyType == typeof(Guid?)))); + public static string GetEdgeDBTypeName(this Type type) + { + var attr = type.GetCustomAttribute(); + var name = attr?.Name ?? type.Name; + return attr != null ? $"{(attr.ModuleName != null ? $"{attr.ModuleName}::" : "default::")}{name}" : name; + } - public static string GetEdgeDBTypeName(this Type type) - { - var attr = type.GetCustomAttribute(); - var name = attr?.Name ?? type.Name; - return attr != null ? $"{(attr.ModuleName != null ? $"{attr.ModuleName}::" : "default::")}{name}" : name; - } - public static string GetEdgeDBPropertyName(this MemberInfo info) - { - var att = info.GetCustomAttribute(); + public static string GetEdgeDBPropertyName(this MemberInfo info) + { + var att = info.GetCustomAttribute(); - return $"{(att?.IsLinkProperty ?? false ? "@" : "")}{att?.Name ?? (info is PropertyInfo p ? TypeBuilder.SchemaNamingStrategy.Convert(p) : TypeBuilder.SchemaNamingStrategy.Convert(info.Name))}"; - } + return + $"{(att?.IsLinkProperty ?? false ? "@" : "")}{att?.Name ?? (info is PropertyInfo p ? TypeBuilder.SchemaNamingStrategy.Convert(p) : TypeBuilder.SchemaNamingStrategy.Convert(info.Name))}"; + } - public static Type GetMemberType(this MemberInfo info) + public static Type GetMemberType(this MemberInfo info) + { + switch (info) { - switch (info) - { - case PropertyInfo propertyInfo: - return propertyInfo.PropertyType; - case FieldInfo fieldInfo: - return fieldInfo.FieldType; - default: - throw new NotSupportedException(); - } + case PropertyInfo propertyInfo: + return propertyInfo.PropertyType; + case FieldInfo fieldInfo: + return fieldInfo.FieldType; + default: + throw new NotSupportedException(); } + } - public static object? GetMemberValue(this MemberInfo info, object? obj) + public static object? GetMemberValue(this MemberInfo info, object? obj) => + info switch { - return info switch - { - FieldInfo field => field.GetValue(obj), - PropertyInfo property => property.GetValue(obj), - _ => throw new InvalidOperationException("Cannot resolve constant member expression") - }; - } - } + FieldInfo field => field.GetValue(obj), + PropertyInfo property => property.GetValue(obj), + _ => throw new InvalidOperationException("Cannot resolve constant member expression") + }; } diff --git a/src/EdgeDB.Net.QueryBuilder/FodyWeavers.xml b/src/EdgeDB.Net.QueryBuilder/FodyWeavers.xml index cad4b900..3f8f5625 100644 --- a/src/EdgeDB.Net.QueryBuilder/FodyWeavers.xml +++ b/src/EdgeDB.Net.QueryBuilder/FodyWeavers.xml @@ -1,4 +1,4 @@  - - \ No newline at end of file + + diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/EdgeQLOpAttribute.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/EdgeQLOpAttribute.cs index 5dbba8a4..806c38eb 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/EdgeQLOpAttribute.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/EdgeQLOpAttribute.cs @@ -1,12 +1,11 @@ -namespace EdgeDB +namespace EdgeDB; + +internal class EdgeQLOpAttribute : Attribute { - internal class EdgeQLOpAttribute : Attribute + public EdgeQLOpAttribute(string v) { - public string Operator { get; } - - public EdgeQLOpAttribute(string v) - { - Operator = v; - } + Operator = v; } + + public string Operator { get; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/EnumSerializerAttribute.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/EnumSerializerAttribute.cs index 75d8de02..4ecf29ba 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/EnumSerializerAttribute.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/EnumSerializerAttribute.cs @@ -1,36 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB; -namespace EdgeDB +[AttributeUsage(AttributeTargets.Enum)] +public class EnumSerializerAttribute : Attribute { - [AttributeUsage(AttributeTargets.Enum)] - public class EnumSerializerAttribute : Attribute - { - internal readonly SerializationMethod Method; + internal readonly SerializationMethod Method; - /// - /// Marks this enum to be serialized in a certain way. - /// - /// The serialization method to use when serializing this enum. - public EnumSerializerAttribute(SerializationMethod method) - { - Method = method; - } + /// + /// Marks this enum to be serialized in a certain way. + /// + /// The serialization method to use when serializing this enum. + public EnumSerializerAttribute(SerializationMethod method) + { + Method = method; } +} - public enum SerializationMethod - { - /// - /// Converts the name of the enums value ex: Day to the lowercase representation "day" as a string. - /// - Lower, +public enum SerializationMethod +{ + /// + /// Converts the name of the enums value ex: Day to the lowercase representation "day" as a string. + /// + Lower, - /// - /// Converts the value of the enum to the numeric value. - /// - Numeric, - } + /// + /// Converts the value of the enum to the numeric value. + /// + Numeric } diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/EquivalentExpressionAttribute.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/EquivalentExpressionAttribute.cs index cd422421..93fe40a7 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/EquivalentExpressionAttribute.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/EquivalentExpressionAttribute.cs @@ -1,14 +1,13 @@ using System.Linq.Expressions; -namespace EdgeDB +namespace EdgeDB; + +internal class EquivalentExpressionAttribute : Attribute { - internal class EquivalentExpressionAttribute : Attribute + public EquivalentExpressionAttribute(params ExpressionType[] expressions) { - public ExpressionType[] Expressions { get; } - public EquivalentExpressionAttribute(params ExpressionType[] expressions) - { - Expressions = expressions; - } - + Expressions = expressions; } + + public ExpressionType[] Expressions { get; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/Grammar.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/Grammar.cs index 29d17227..4a0bb3c9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/Grammar.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/Grammar.cs @@ -1,98 +1,88 @@ -using EdgeDB.Translators; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +internal partial class Grammar { - internal partial class Grammar + private static readonly List _operators; + + static Grammar() { - private static readonly List _operators; + var operators = typeof(Grammar).GetMethods() + .Where(x => x.GetCustomAttribute() is not null); - private class Operator - { - public ExpressionType[] ExpressionTypes { get; } - public string Name { get; } - public int ParameterCount { get; } - - private readonly MethodInfo _method; - - public Operator(ExpressionType[] expressionTypes, string name, int paramCount, MethodInfo method) - { - ExpressionTypes = expressionTypes; - Name = name; - ParameterCount = paramCount; - _method = method; - } - - public void Build(QueryWriter writer, params WriterProxy[] args) - { - writer.Term( - TermType.BinaryOp, - "binary_op_translation", - Defer.This(() => $"Called from {Name}({ParameterCount}) using method {_method.Name}"), - new BinaryOpMetadata(ExpressionTypes), - Token.Of(writer => - { - var arr = new object?[args.Length + 1]; - arr[0] = writer; - args.CopyTo(arr, 1); - _method.Invoke(null, arr); - }) - ); - } - } + _operators = new List(); - static Grammar() + foreach (var op in operators) { - var operators = typeof(Grammar).GetMethods() - .Where(x => x.GetCustomAttribute() is not null); + var opAttr = op.GetCustomAttribute()!; - _operators = new(); + var expAttr = op.GetCustomAttribute(); - foreach (var op in operators) - { - var opAttr = op.GetCustomAttribute()!; + var expressions = expAttr is null + ? Array.Empty() + : expAttr.Expressions; - var expAttr = op.GetCustomAttribute(); + var opInfo = new Operator(expressions, opAttr.Operator, op.GetParameters().Length - 1, op); - var expressions = expAttr is null - ? Array.Empty() - : expAttr.Expressions; + _operators.Add(opInfo); + } + } - var opInfo = new Operator(expressions, opAttr.Operator, op.GetParameters().Length - 1, op); + private static Operator? SearchForBestMatch(ExpressionType type, WriterProxy[] args) + => _operators.FirstOrDefault(x => x.ExpressionTypes.Contains(type) && x.ParameterCount == args.Length); - _operators.Add(opInfo); - } - } + public static bool TryBuildOperator(ExpressionType type, QueryWriter writer, + params WriterProxy[] args) + { + var op = SearchForBestMatch(type, args); - private static Operator? SearchForBestMatch(ExpressionType type, WriterProxy[] args) - => _operators.FirstOrDefault(x => x.ExpressionTypes.Contains(type) && x.ParameterCount == args.Length); + if (op is null) + return false; - public static bool TryBuildOperator(ExpressionType type, QueryWriter writer, - params WriterProxy[] args) - { - var op = SearchForBestMatch(type, args); + op.Build(writer, args); + return true; + } - if (op is null) - return false; + private class Operator + { + private readonly MethodInfo _method; - op.Build(writer, args); - return true; + public Operator(ExpressionType[] expressionTypes, string name, int paramCount, MethodInfo method) + { + ExpressionTypes = expressionTypes; + Name = name; + ParameterCount = paramCount; + _method = method; } - // public static bool TryBuildOperator(ExpressionType type, QueryWriter writer, params object?[] args) - // => TryBuildOperator( - // type, - // writer, - // args.Select(x => new WriterProxy(writer => writer - // .Append(x) - // )).ToArray() - // ); + public ExpressionType[] ExpressionTypes { get; } + public string Name { get; } + public int ParameterCount { get; } + + public void Build(QueryWriter writer, params WriterProxy[] args) => + writer.Term( + TermType.BinaryOp, + "binary_op_translation", + Defer.This(() => $"Called from {Name}({ParameterCount}) using method {_method.Name}"), + new BinaryOpMetadata(ExpressionTypes), + Token.Of(writer => + { + var arr = new object?[args.Length + 1]; + arr[0] = writer; + args.CopyTo(arr, 1); + _method.Invoke(null, arr); + }) + ); } + + // public static bool TryBuildOperator(ExpressionType type, QueryWriter writer, params object?[] args) + // => TryBuildOperator( + // type, + // writer, + // args.Select(x => new WriterProxy(writer => writer + // .Append(x) + // )).ToArray() + // ); } diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/GlobalReducer.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/GlobalReducer.cs index e6e14c7e..a7da6afa 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/GlobalReducer.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/GlobalReducer.cs @@ -1,5 +1,4 @@ using EdgeDB.QueryNodes; -using System.Diagnostics.CodeAnalysis; namespace EdgeDB; @@ -15,7 +14,7 @@ public void Reduce(IQueryBuilder builder, QueryWriter writer) if (withNode is null) return; - int reducedCount = 0; + var reducedCount = 0; foreach (var (_, terms) in writer.Terms.TermsByType.Where(x => x.Key is TermType.GlobalDeclaration) .ToArray()) foreach (var global in terms) diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/NestedSelectReducer.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/NestedSelectReducer.cs index e94a59a8..c10e1950 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/NestedSelectReducer.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/NestedSelectReducer.cs @@ -5,10 +5,10 @@ namespace EdgeDB; internal sealed class NestedSelectReducer : IReducer { /// - /// Reduces sub-query selects if grammatical rules allow it.
+ /// Reduces sub-query selects if grammatical rules allow it.
/// An example of this would be the following: /// - /// select (select Person) + /// select (select Person) /// ///
/// @@ -20,22 +20,22 @@ public void Reduce(IQueryBuilder builder, QueryWriter writer) foreach (var node in nodes) { - if(node.Metadata is not QueryNodeMetadata {Node: SelectNode selectNode}) + if (node.Metadata is not QueryNodeMetadata {Node: SelectNode selectNode}) continue; // get the start token of the selects operand - if(!(node.Slice.Head?.Value.Equals("select ") ?? false) || node.Slice.Head.Next is null) + if (!(node.Slice.Head?.Value.Equals("select ") ?? false) || node.Slice.Head.Next is null) continue; var operandNodes = ExtractQueryOperandNode(writer.Terms.GetStartingAt(node.Slice.Head.Next), writer); var operandNode = operandNodes?[^1]; - if(operandNode?.Metadata is not QueryNodeMetadata { Node: SelectNode }) + if (operandNode?.Metadata is not QueryNodeMetadata {Node: SelectNode}) continue; // only reduce if the select doesn't have any shape or additional tokens - if(node.Slice.Tail != operandNodes?[0].Slice.Tail) + if (node.Slice.Tail != operandNodes?[0].Slice.Tail) continue; writer.Strip(node.Slice, node.Range, operandNode.Slice, operandNode.Range); @@ -57,9 +57,8 @@ public void Reduce(IQueryBuilder builder, QueryWriter writer) return null; } - private Term[]? ExtractQueryOperandNode(Term term, QueryWriter writer) - { - return term.Type switch + private Term[]? ExtractQueryOperandNode(Term term, QueryWriter writer) => + term.Type switch { TermType.QueryNode => [term], TermType.SubQuery when term.Slice.Head?.Next is not null => @@ -68,5 +67,4 @@ public void Reduce(IQueryBuilder builder, QueryWriter writer) ], _ => null }; - } } diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/QueryReducer.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/QueryReducer.cs index 0bc4172e..8d77a498 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/QueryReducer.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/QueryReducer.cs @@ -1,6 +1,4 @@ -using EdgeDB.QueryNodes; - -namespace EdgeDB; +namespace EdgeDB; internal static class QueryReducer { @@ -16,7 +14,7 @@ internal static class QueryReducer public static void Apply(IQueryBuilder builder, QueryWriter writer) { - for(var i = 0; i != _reducers.Length; i++) + for (var i = 0; i != _reducers.Length; i++) _reducers[i].Reduce(builder, writer); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/TypeCastReducer.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/TypeCastReducer.cs index 199507e0..8c8261ee 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/TypeCastReducer.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/TypeCastReducer.cs @@ -30,7 +30,8 @@ term.Metadata is not CastMetadata castMetadata || term.Remove(); goto end_neighbour_search; case TermType.GlobalReference when neighbour.Metadata is GlobalMetadata globalMetadata: - if (globalMetadata.EdgeDBType is not null && EdgeDBTypeUtils.CompareEdgeDBTypes(castMetadata.Type, globalMetadata.EdgeDBType)) + if (globalMetadata.EdgeDBType is not null && + EdgeDBTypeUtils.CompareEdgeDBTypes(castMetadata.Type, globalMetadata.EdgeDBType)) { term.Remove(); goto end_neighbour_search; @@ -38,13 +39,15 @@ term.Metadata is not CastMetadata castMetadata || switch (globalMetadata.Global.Reference) { - case Expression expression when EdgeDBTypeUtils.TryGetScalarType(expression.Type, out var scalar): - if(!EdgeDBTypeUtils.CompareEdgeDBTypes(castMetadata.Type, scalar.EdgeDBType)) + case Expression expression + when EdgeDBTypeUtils.TryGetScalarType(expression.Type, out var scalar): + if (!EdgeDBTypeUtils.CompareEdgeDBTypes(castMetadata.Type, scalar.EdgeDBType)) continue; term.Remove(); goto end_neighbour_search; } + continue; } } @@ -78,7 +81,7 @@ private static bool TryGetFunctionResultType(FunctionMetadata metadata, [MaybeNu { var functionInfo = method.GetCustomAttribute(); - if(functionInfo is null) + if (functionInfo is null) continue; if (funcResult is null) diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/WhitespaceReducer.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/WhitespaceReducer.cs index 6dc21956..aa43e9fb 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/WhitespaceReducer.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/Reducers/WhitespaceReducer.cs @@ -54,7 +54,7 @@ public static void TrimWhitespaceAround(QueryWriter writer, Term term) { if (term.Slice.Head?.Previous is not null) Trim(writer, term.Position - 1, term.Slice.Head.Previous, false); - if(term.Slice.Tail?.Next is not null) + if (term.Slice.Tail?.Next is not null) Trim(writer, term.Position + term.Size + 1, term.Slice.Tail.Next, true); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/Terms.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/Terms.cs index 20ab7be8..5fff018c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/Terms.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/Terms.cs @@ -2,19 +2,23 @@ internal static class Terms { - public static QueryWriter LabelVariable(this QueryWriter writer, string name, Deferrable? debug = null, params Token[] values) + public static QueryWriter LabelVariable(this QueryWriter writer, string name, Deferrable? debug = null, + params Token[] values) => writer.Term(TermType.Variable, name, debug, values); - public static QueryWriter LabelVerbose(this QueryWriter writer, string name, Deferrable? debug = null, params Token[] values) + + public static QueryWriter LabelVerbose(this QueryWriter writer, string name, Deferrable? debug = null, + params Token[] values) => writer.Term(TermType.Verbose, name, debug, values); - public static QueryWriter Wrapped(this QueryWriter writer, Token token, string separator = "()", bool spaced = false) + public static QueryWriter Wrapped(this QueryWriter writer, Token token, string separator = "()", + bool spaced = false) { if (separator.Length != 2) throw new ArgumentOutOfRangeException(nameof(separator)); - return spaced ? - writer.Append(separator[0], ' ', token, ' ', separator[1]) : - writer.Append(separator[0], token, separator[1]); + return spaced + ? writer.Append(separator[0], ' ', token, ' ', separator[1]) + : writer.Append(separator[0], token, separator[1]); } public static QueryWriter Wrapped(this QueryWriter writer, WriterProxy value, string separator = "()") @@ -38,7 +42,8 @@ public static QueryWriter WrappedValues(this QueryWriter writer, string separato return writer.Append(value); } - public static QueryWriter Shape(this QueryWriter writer, string name, Deferrable? debug, params Token[] values) + public static QueryWriter Shape(this QueryWriter writer, string name, Deferrable? debug, + params Token[] values) { var value = new Token[values.Length + 2]; value[0] = "{ "; @@ -56,29 +61,28 @@ public static QueryWriter Shape(this QueryWriter writer, string name, T[] ele throw new ArgumentException("Parentheses must contain 2 characters", nameof(parentheses)); return writer.Term(TermType.Shape, name, new Token( - writer => - { - writer.Append(parentheses[0], ' '); - - for (var i = 0; i < elements.Length; i++) + writer => { - var iLocal = i; - var isEmpty = writer.AppendIsEmpty(Token.Of(writer => func(writer, elements[iLocal]))); + writer.Append(parentheses[0], ' '); - if (!isEmpty && i + 1 < elements.Length) - writer.Append(", "); - } + for (var i = 0; i < elements.Length; i++) + { + var iLocal = i; + var isEmpty = writer.AppendIsEmpty(Token.Of(writer => func(writer, elements[iLocal]))); + + if (!isEmpty && i + 1 < elements.Length) + writer.Append(", "); + } - writer.Append(' ', parentheses[1]); - }), + writer.Append(' ', parentheses[1]); + }), debug ); } public static QueryWriter Shape(this QueryWriter writer, string name, params T[] elements) - where T : IWriteable - { - return writer.Term(TermType.Shape, name, new Token( + where T : IWriteable => + writer.Term(TermType.Shape, name, new Token( writer => { writer.Append("{ "); @@ -96,7 +100,6 @@ public static QueryWriter Shape(this QueryWriter writer, string name, params writer.Append(" }"); }) ); - } public static QueryWriter Assignment(this QueryWriter writer, Token name, Token token) => writer.Append(name, " := ", token); @@ -109,34 +112,16 @@ public static QueryWriter TypeCast(this QueryWriter writer, Token type, CastMeta metadata: metadata ); - public readonly struct FunctionArg - { - public readonly Token Token; - public readonly string? Named; - - public FunctionArg(Token token, string? named = null) - { - Token = token; - Named = named; - } - - public static implicit operator FunctionArg(Token token) => new(token); - public static implicit operator FunctionArg(string? str) => new(str); - public static implicit operator FunctionArg(char ch) => new(ch); - public static implicit operator FunctionArg(WriterProxy writerProxy) => new(writerProxy); - } - public static QueryWriter Function(this QueryWriter writer, string name, Deferrable? debug, - FunctionMetadata? metadata, params FunctionArg[] args) - { - return writer.Term(TermType.Function, $"func_{name}", debug, new FunctionMetadata(name), Token.Of( + FunctionMetadata? metadata, params FunctionArg[] args) => + writer.Term(TermType.Function, $"func_{name}", debug, new FunctionMetadata(name), Token.Of( writer => { writer.Append(name, '('); for (var i = 0; i < args.Length;) { - int commaPos = 0; + var commaPos = 0; LooseLinkedList.NodeSlice? commaSlice = null; if (i > 0) @@ -152,7 +137,7 @@ public static QueryWriter Function(this QueryWriter writer, string name, Deferra TermType.FunctionArg, $"func_{name}_arg_{i}", null, - metadata: new FunctionArgumentMetadata(checked((uint)i - 1), name, arg.Named), + new FunctionArgumentMetadata(checked((uint)i - 1), name, arg.Named), Token.Of( writer => { @@ -180,7 +165,6 @@ out isEmpty writer.Append(')'); } )); - } public static QueryWriter Function(this QueryWriter writer, string name, Deferrable? debug, params FunctionArg[] args) @@ -192,7 +176,8 @@ public static QueryWriter Function(this QueryWriter writer, string name, params public static QueryWriter SingleQuoted(this QueryWriter writer, Token token) => writer.Append('\'', token, '\''); - public static QueryWriter QueryArgument(this QueryWriter writer, Token type, Token name, Deferrable? debug = null, bool optional = false) + public static QueryWriter QueryArgument(this QueryWriter writer, Token type, Token name, + Deferrable? debug = null, bool optional = false) => writer.Term(TermType.Variable, $"variable_{name}", debug, optional ? "$", name); public static Token[] Span(this QueryWriter writer, WriterProxy proxy) @@ -202,7 +187,8 @@ public static Token[] Span(this QueryWriter writer, WriterProxy proxy) return span.ToTokens(); } - public static QueryWriter AppendSpanned(this QueryWriter writer, ref Token[]? span, Func create) + public static QueryWriter AppendSpanned(this QueryWriter writer, ref Token[]? span, + Func create) { if (span is null) span = create(writer); @@ -211,4 +197,21 @@ public static QueryWriter AppendSpanned(this QueryWriter writer, ref Token[]? sp return writer; } + + public readonly struct FunctionArg + { + public readonly Token Token; + public readonly string? Named; + + public FunctionArg(Token token, string? named = null) + { + Token = token; + Named = named; + } + + public static implicit operator FunctionArg(Token token) => new(token); + public static implicit operator FunctionArg(string? str) => new(str); + public static implicit operator FunctionArg(char ch) => new(ch); + public static implicit operator FunctionArg(WriterProxy writerProxy) => new(writerProxy); + } } diff --git a/src/EdgeDB.Net.QueryBuilder/GroupContext.cs b/src/EdgeDB.Net.QueryBuilder/GroupContext.cs index 3b40f2d4..0cf74d0c 100644 --- a/src/EdgeDB.Net.QueryBuilder/GroupContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/GroupContext.cs @@ -2,6 +2,6 @@ public abstract class GroupContext : IQueryContextUsing where TContext : IQueryContext { - public abstract TUsing Using { get; } public abstract TContext Context { get; } + public abstract TUsing Using { get; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs index 3484b96c..35f2e9bb 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs @@ -1,48 +1,44 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.Interfaces; -namespace EdgeDB.Interfaces +/// +/// Represents an executable query with one or more returning objects. +/// +/// The object the query will return. +public interface IMultiCardinalityExecutable : IQueryBuilder, IMultiCardinalityQuery { /// - /// Represents an executable query with one or more returning objects. + /// Executes the current query. /// - /// The object the query will return. - public interface IMultiCardinalityExecutable : IQueryBuilder, IMultiCardinalityQuery - { - /// - /// Executes the current query. - /// - /// The client to preform the query on. - /// The allowed capabilities for the query. - /// A cancellation token to cancel the asynchronous operation. - /// - /// A read-only collection of . - /// - Task> ExecuteAsync(IEdgeDBQueryable edgedb, Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); + /// The client to preform the query on. + /// The allowed capabilities for the query. + /// A cancellation token to cancel the asynchronous operation. + /// + /// A read-only collection of . + /// + Task> ExecuteAsync(IEdgeDBQueryable edgedb, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); - /// - /// Executes the current query with the cardinality . - /// - /// The client to preform the query on. - /// The allowed capabilities for the query. - /// A cancellation token to cancel the asynchronous operation. - /// - /// The result of the query if present; otherwise . - /// - Task ExecuteSingleAsync(IEdgeDBQueryable edgedb, Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); + /// + /// Executes the current query with the cardinality . + /// + /// The client to preform the query on. + /// The allowed capabilities for the query. + /// A cancellation token to cancel the asynchronous operation. + /// + /// The result of the query if present; otherwise . + /// + Task ExecuteSingleAsync(IEdgeDBQueryable edgedb, Capabilities? capabilities = Capabilities.Modifications, + CancellationToken token = default); - /// - /// Executes the current query with the cardinality . - /// - /// The client to preform the query on. - /// The allowed capabilities for the query. - /// A cancellation token to cancel the asynchronous operation. - /// - /// The result of the query. - /// - Task ExecuteRequiredSingleAsync(IEdgeDBQueryable edgedb, Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); - } + /// + /// Executes the current query with the cardinality . + /// + /// The client to preform the query on. + /// The allowed capabilities for the query. + /// A cancellation token to cancel the asynchronous operation. + /// + /// The result of the query. + /// + Task ExecuteRequiredSingleAsync(IEdgeDBQueryable edgedb, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs index e15c468c..d3e7aa07 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.Interfaces; -namespace EdgeDB.Interfaces +/// +/// Represents a query with a cardinality of . +/// +/// The result type of the query. +public interface IMultiCardinalityQuery : IQuery { - /// - /// Represents a query with a cardinality of . - /// - /// The result type of the query. - public interface IMultiCardinalityQuery : IQuery { } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs index d7437308..7c5d52fe 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB; -namespace EdgeDB +/// +/// Represents a generic query. +/// +/// The inner 'working' type of the query. +public interface IQuery : IQueryBuilder { - /// - /// Represents a generic query. - /// - /// The inner 'working' type of the query. - public interface IQuery : IQueryBuilder { } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs index c459627f..2cef0557 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs @@ -6,324 +6,339 @@ using EdgeDB.Schema; using System.Linq.Expressions; -namespace EdgeDB +namespace EdgeDB; + +/// +/// Represents a generic query builder for querying against . +/// +/// The type of which queries will be preformed with. +/// The type of context representing the current builder. +public interface IQueryBuilder : + IQueryBuilder, + ISelectQuery, + IUpdateQuery, + IDeleteQuery, + IInsertQuery, + IUnlessConflictOn, + IGroupQuery, + IGroupUsingQuery + where TContext : IQueryContext { + IGroupQuery Group(); + + IGroupQuery Group(Action> shape); + + IGroupQuery Group(Expression> selector); + IGroupQuery Group(Expression> selector); + + IGroupQuery Group(Expression> selector, + Action> shape); + + IGroupQuery Group(Expression> selector, + Action> shape); + + internal IGroupQuery GroupInternal(LambdaExpression? selector = null, + Action>? shape = null + ) where TNewContext : IQueryContextSelf; + + /// + /// Adds a FOR statement on the with a UNION + /// whos inner query is the . + /// + /// The collection to iterate over. + /// The iterator for the UNION statement. + /// The type of the collection. + /// The type returned from the iterator query. + /// The current query. + IMultiCardinalityExecutable For(IEnumerable collection, + Expression, IQuery>> iterator); + + /// + /// Adds a FOR statement on the with a UNION + /// whos inner query is the . + /// + /// The collection to iterate over. + /// The iterator for the UNION statement. + /// The type of the collection. + /// The type returned from the iterator query. + /// The current query. + IMultiCardinalityExecutable For(Expression>> collection, + Expression, IQuery>> iterator); + + + /// + /// Adds a WITH statement whos variables are the properties defined in . + /// + /// The type whos properties will be used as variables. + /// + /// The instance whos properties will be extrapolated as variables for the query builder. + /// + /// + /// The current query. + /// + IQueryBuilder> With(TVariables variables); + + /// + /// Adds a WITH statement whos variables are the properties defined in . + /// + /// The type whos properties will be used as variables. + /// + /// The instance whos properties will be extrapolated as variables for the query builder. + /// + /// + /// The current query. + /// + IQueryBuilder> With( + Expression, TVariables>> variables); + + /// + /// Adds a SELECT statement selecting the current with an + /// autogenerated shape. + /// + /// + /// A . + /// + ISelectQuery Select(); + + /// + /// Adds a SELECT statement selecting with an + /// autogenerated shape. + /// + /// + /// A . + /// + ISelectQuery Select(); + + /// + /// Adds a SELECT statement selecting the provided expression. + /// + /// The return result of the select expression. + /// A delegate to build the shape for selecting . + /// + /// A . + /// + ISelectQuery Select(Action> shape); + + internal ISelectQuery SelectInternal( + Action>? shape = null) + where TNewContext : IQueryContextSelf; + + /// + /// Adds a SELECT statement, selecting the result of a . + /// + /// The resulting type of the expression. + /// The expression on which to select. + /// + /// A . + /// + ISelectQuery SelectExpression(Expression> expression); + + /// + /// Adds a SELECT statement, selecting the result of a . + /// + /// The resulting type of the expression. + /// The expression on which to select. + /// + /// A . + /// + ISelectQuery SelectExpression(Expression> expression); + + /// + /// Adds a SELECT statement, selecting the result of a . + /// + /// The resulting type of the expression. + /// The expression on which to select. + /// A optional delegate to build the shape for selecting . + /// + /// A . + /// + ISelectQuery SelectExpression(Expression> expression, + Action>? shape); + + /// + /// Adds a SELECT statement, selecting the result of a . + /// + /// The resulting type of the expression. + /// The shape result of . + /// The expression on which to select. + /// A optional delegate to build the shape for selecting . + /// + /// A . + /// + ISelectQuery SelectExpression( + Expression> expression, + Func, ShapeBuilder>? shape = null + ); + + // TODO: is this needed? + // internal ISelectQuery SelectExpressionInternal(LambdaExpression expression, Action>? shape = null) + // where TNewContext : IQueryContextSelf; + + /// + /// Adds a INSERT statement inserting an instance of . + /// + /// + /// This statement requires introspection when contains a + /// property thats a . + /// + /// The value to insert. + /// + /// whether or not to implicitly add a select statement to return the inserted value. + /// + /// A . + IInsertQuery Insert(TNew value, bool returnInsertedValue); + + /// + /// Adds a INSERT statement inserting an instance of . + /// + /// + /// This statement requires introspection when contains a + /// property thats a . + /// + /// The value to insert. + /// A . + IInsertQuery Insert(TNew value); + + /// + /// Adds a INSERT statement inserting an instance of . + /// + /// + /// This statement requires introspection when contains a + /// property thats a . + /// + /// The callback containing the value initialization to insert. + /// + /// whether or not to implicitly add a select statement to return the inserted value. + /// + /// A . + IInsertQuery Insert(Expression> value, bool returnInsertedValue); + + /// + /// Adds a INSERT statement inserting an instance of . + /// + /// + /// This statement requires introspection when contains a + /// property thats a . + /// + /// The callback containing the value initialization to insert. + /// A . + IInsertQuery Insert(Expression> value); + + /// + /// Adds a UPDATE statement updating an instance of . + /// + /// + /// A function to select the target for the update. + /// + /// + /// whether or not to implicitly add a select statement to return the inserted value. + /// + /// A . + IUpdateQuery Update(Expression> selector, + bool returnUpdatedValue); + + /// + /// Adds a UPDATE statement updating an instance of . + /// + /// + /// A function to select the target for the update. + /// + /// A . + IUpdateQuery Update(Expression> selector); + + /// + /// Adds a UPDATE statement updating an instance of . + /// + /// A . + IUpdateQuery Update(); + + /// + /// Adds a DELETE statement deleting an instance of . + /// + IDeleteQuery Delete(); + + /// + /// Adds a DELETE statement deleting an instance of . + /// + /// The type to delete. + IDeleteQuery Delete(); + + internal IDeleteQuery DeleteInternal() + where TNewContext : IQueryContextSelf; +} + +/// +/// Represents a generic query builder with a build function. +/// +public interface IQueryBuilder +{ + /// + /// Gets whether or not this query builder requires introspection to build. + /// + public bool RequiresIntrospection { get; } + + /// + /// Gets a read-only collection of query nodes within this query builder. + /// + internal IReadOnlyCollection Nodes { get; } + + /// + /// Gets a read-only collection of globals defined within this query builder. + /// + internal List Globals { get; } + + /// + /// Gets a read-only dictionary of query variables defined within the query builder. + /// + internal Dictionary Variables { get; } + + internal SchemaInfo? SchemaInfo { get; } + + /// + /// Configures whether the query builder should optimize the query produced. + /// + /// + /// Optimization occurs when the query is compiled, if the query builder is a sub query to another, it will + /// inherit the optimization from the parent query builder. + /// + /// + /// The configuration value for optimization: + ///
- : Force the query to be optimized, even if its a subquery + ///
- : Force the query to not be optimized, even if its a sub query and the + /// parents' configuration permits it. + ///
- : Use the default setting. + /// + /// The current query builder. + public IQueryBuilder SetShouldOptimizeQuery(bool? value); + /// - /// Represents a generic query builder for querying against . + /// Compiles the current query. /// - /// The type of which queries will be preformed with. - /// The type of context representing the current builder. - public interface IQueryBuilder : - IQueryBuilder, - ISelectQuery, - IUpdateQuery, - IDeleteQuery, - IInsertQuery, - IUnlessConflictOn, - IGroupQuery, - IGroupUsingQuery - where TContext : IQueryContext - { - IGroupQuery Group(); - - IGroupQuery Group(Action> shape); - - IGroupQuery Group(Expression> selector); - IGroupQuery Group(Expression> selector); - IGroupQuery Group(Expression> selector, Action> shape); - IGroupQuery Group(Expression> selector, Action> shape); - - internal IGroupQuery GroupInternal(LambdaExpression? selector = null, - Action>? shape = null - ) where TNewContext : IQueryContextSelf; - - /// - /// Adds a FOR statement on the with a UNION - /// whos inner query is the . - /// - /// The collection to iterate over. - /// The iterator for the UNION statement. - /// The type of the collection. - /// The type returned from the iterator query. - /// The current query. - IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQuery>> iterator); - - /// - /// Adds a FOR statement on the with a UNION - /// whos inner query is the . - /// - /// The collection to iterate over. - /// The iterator for the UNION statement. - /// The type of the collection. - /// The type returned from the iterator query. - /// The current query. - IMultiCardinalityExecutable For(Expression>> collection, Expression, IQuery>> iterator); - - - /// - /// Adds a WITH statement whos variables are the properties defined in . - /// - /// The type whos properties will be used as variables. - /// - /// The instance whos properties will be extrapolated as variables for the query builder. - /// - /// - /// The current query. - /// - IQueryBuilder> With(TVariables variables); - - /// - /// Adds a WITH statement whos variables are the properties defined in . - /// - /// The type whos properties will be used as variables. - /// - /// The instance whos properties will be extrapolated as variables for the query builder. - /// - /// - /// The current query. - /// - IQueryBuilder> With(Expression, TVariables>> variables); - - /// - /// Adds a SELECT statement selecting the current with an - /// autogenerated shape. - /// - /// - /// A . - /// - ISelectQuery Select(); - - /// - /// Adds a SELECT statement selecting with an - /// autogenerated shape. - /// - /// - /// A . - /// - ISelectQuery Select(); - - /// - /// Adds a SELECT statement selecting the provided expression. - /// - /// The return result of the select expression. - /// A delegate to build the shape for selecting . - /// - /// A . - /// - ISelectQuery Select(Action> shape); - - internal ISelectQuery SelectInternal(Action>? shape = null) - where TNewContext : IQueryContextSelf; - - /// - /// Adds a SELECT statement, selecting the result of a . - /// - /// The resulting type of the expression. - /// The expression on which to select. - /// - /// A . - /// - ISelectQuery SelectExpression(Expression> expression); - - /// - /// Adds a SELECT statement, selecting the result of a . - /// - /// The resulting type of the expression. - /// The expression on which to select. - /// - /// A . - /// - ISelectQuery SelectExpression(Expression> expression); - - /// - /// Adds a SELECT statement, selecting the result of a . - /// - /// The resulting type of the expression. - /// The expression on which to select. - /// A optional delegate to build the shape for selecting . - /// - /// A . - /// - ISelectQuery SelectExpression(Expression> expression, - Action>? shape); - - /// - /// Adds a SELECT statement, selecting the result of a . - /// - /// The resulting type of the expression. - /// The shape result of . - /// The expression on which to select. - /// A optional delegate to build the shape for selecting . - /// - /// A . - /// - ISelectQuery SelectExpression( - Expression> expression, - Func, ShapeBuilder>? shape = null - ); - - // TODO: is this needed? - // internal ISelectQuery SelectExpressionInternal(LambdaExpression expression, Action>? shape = null) - // where TNewContext : IQueryContextSelf; - - /// - /// Adds a INSERT statement inserting an instance of . - /// - /// - /// This statement requires introspection when contains a - /// property thats a . - /// - /// The value to insert. - /// - /// whether or not to implicitly add a select statement to return the inserted value. - /// - /// A . - IInsertQuery Insert(TNew value, bool returnInsertedValue); - - /// - /// Adds a INSERT statement inserting an instance of . - /// - /// - /// This statement requires introspection when contains a - /// property thats a . - /// - /// The value to insert. - /// A . - IInsertQuery Insert(TNew value); - - /// - /// Adds a INSERT statement inserting an instance of . - /// - /// - /// This statement requires introspection when contains a - /// property thats a . - /// - /// The callback containing the value initialization to insert. - /// - /// whether or not to implicitly add a select statement to return the inserted value. - /// - /// A . - IInsertQuery Insert(Expression> value, bool returnInsertedValue); - - /// - /// Adds a INSERT statement inserting an instance of . - /// - /// - /// This statement requires introspection when contains a - /// property thats a . - /// - /// The callback containing the value initialization to insert. - /// A . - IInsertQuery Insert(Expression> value); - - /// - /// Adds a UPDATE statement updating an instance of . - /// - /// - /// A function to select the target for the update. - /// - /// - /// whether or not to implicitly add a select statement to return the inserted value. - /// - /// A . - IUpdateQuery Update(Expression> selector, bool returnUpdatedValue); - - /// - /// Adds a UPDATE statement updating an instance of . - /// - /// - /// A function to select the target for the update. - /// - /// A . - IUpdateQuery Update(Expression> selector); - - /// - /// Adds a UPDATE statement updating an instance of . - /// - /// A . - IUpdateQuery Update(); - - /// - /// Adds a DELETE statement deleting an instance of . - /// - IDeleteQuery Delete(); - - /// - /// Adds a DELETE statement deleting an instance of . - /// - /// The type to delete. - IDeleteQuery Delete(); - - internal IDeleteQuery DeleteInternal() - where TNewContext : IQueryContextSelf; - } + /// + /// If the query requires introspection please use + /// . + /// + /// + /// Whether or not to compile the query in a debug fashion, returning a + /// . + /// + /// + /// A . + /// + CompiledQuery Compile(bool debug = false); /// - /// Represents a generic query builder with a build function. + /// Compiles the current query asynchronously, allowing database introspection. /// - public interface IQueryBuilder - { - /// - /// Gets whether or not this query builder requires introspection to build. - /// - public bool RequiresIntrospection { get; } - - /// - /// Gets a read-only collection of query nodes within this query builder. - /// - internal IReadOnlyCollection Nodes { get; } - - /// - /// Gets a read-only collection of globals defined within this query builder. - /// - internal List Globals { get; } - - /// - /// Gets a read-only dictionary of query variables defined within the query builder. - /// - internal Dictionary Variables { get; } - - internal SchemaInfo? SchemaInfo { get; } - - /// - /// Configures whether the query builder should optimize the query produced. - /// - /// - /// Optimization occurs when the query is compiled, if the query builder is a sub query to another, it will - /// inherit the optimization from the parent query builder. - /// - /// - /// The configuration value for optimization: - ///
- : Force the query to be optimized, even if its a subquery - ///
- : Force the query to not be optimized, even if its a sub query and the - /// parents' configuration permits it. - ///
- : Use the default setting. - /// - /// The current query builder. - public IQueryBuilder SetShouldOptimizeQuery(bool? value); - - /// - /// Compiles the current query. - /// - /// - /// If the query requires introspection please use - /// . - /// - /// Whether or not to compile the query in a debug fashion, returning a . - /// - /// A . - /// - CompiledQuery Compile(bool debug = false); - - /// - /// Compiles the current query asynchronously, allowing database introspection. - /// - /// The client to preform introspection with. - /// Whether or not to compile the query in a debug fashion, returning a . - /// A cancellation token to cancel the asynchronous operation. - /// A . - ValueTask CompileAsync(IEdgeDBQueryable edgedb, bool debug = false, CancellationToken token = default); - - internal void CompileInternal(QueryWriter writer, CompileContext? context = null); - } + /// The client to preform introspection with. + /// + /// Whether or not to compile the query in a debug fashion, returning a + /// . + /// + /// A cancellation token to cancel the asynchronous operation. + /// A . + ValueTask CompileAsync(IEdgeDBQueryable edgedb, bool debug = false, + CancellationToken token = default); + + internal void CompileInternal(QueryWriter writer, CompileContext? context = null); } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs index 9e1c17ea..7a16ba2e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs @@ -1,26 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.Interfaces; -namespace EdgeDB.Interfaces +/// +/// Represents an executable query with at most one returning objects. +/// +/// The object the query will return. +public interface ISingleCardinalityExecutable : IQueryBuilder, ISingleCardinalityQuery { /// - /// Represents an executable query with at most one returning objects. + /// Executes the current query. /// - /// The object the query will return. - public interface ISingleCardinalityExecutable : IQueryBuilder, ISingleCardinalityQuery - { - /// - /// Executes the current query. - /// - /// The client to preform the query on. - /// The allowed capabilities for the query. - /// A cancellation token to cancel the asynchronous operation. - /// - /// A or <>. - /// - Task ExecuteAsync(IEdgeDBQueryable edgedb, Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); - } + /// The client to preform the query on. + /// The allowed capabilities for the query. + /// A cancellation token to cancel the asynchronous operation. + /// + /// A or <>. + /// + Task ExecuteAsync(IEdgeDBQueryable edgedb, Capabilities? capabilities = Capabilities.Modifications, + CancellationToken token = default); } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs index 4959487d..1775e642 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.Interfaces; -namespace EdgeDB.Interfaces +/// +/// Represents a query with a cardinality of . +/// +/// The result type of the query. +public interface ISingleCardinalityQuery : IQuery { - /// - /// Represents a query with a cardinality of . - /// - /// The result type of the query. - public interface ISingleCardinalityQuery : IQuery { } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/GroupContextExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/GroupContextExtensions.cs index 4dd645ff..1383b58e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/GroupContextExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/GroupContextExtensions.cs @@ -21,25 +21,25 @@ public static IGroupQuery> Group> query, EdgeDBTypeContainer schemaType, Expression> selector) - => query.GroupInternal>(selector: selector); + => query.GroupInternal>(selector); public static IGroupQuery> Group( this IQueryBuilder> query, EdgeDBTypeContainer schemaType, Expression, TNew>> selector) - => query.GroupInternal>(selector: selector); + => query.GroupInternal>(selector); public static IGroupQuery> Group( this IQueryBuilder> query, EdgeDBTypeContainer schemaType, Expression> selector, Action> shape) - => query.GroupInternal>(selector: selector); + => query.GroupInternal>(selector); public static IGroupQuery> Group( this IQueryBuilder> query, EdgeDBTypeContainer schemaType, Expression, TNew>> selector, Action> shape) - => query.GroupInternal>(selector: selector); + => query.GroupInternal>(selector); } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs index df80c162..7e8b8466 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs @@ -1,77 +1,77 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.Interfaces.Queries +namespace EdgeDB.Interfaces.Queries; + +/// +/// Represents a generic DELETE query used within a . +/// +/// The type which this DELETE query is querying against. +/// The type of context representing the current builder. +public interface IDeleteQuery : IMultiCardinalityExecutable where TContext : IQueryContext { /// - /// Represents a generic DELETE query used within a . + /// Filters the current delete query by the given predicate. /// - /// The type which this DELETE query is querying against. - /// The type of context representing the current builder. - public interface IDeleteQuery : IMultiCardinalityExecutable where TContext : IQueryContext - { - /// - /// Filters the current delete query by the given predicate. - /// - /// The filter to apply to the current delete query. - /// The current query. - IDeleteQuery Filter(Expression> filter); + /// The filter to apply to the current delete query. + /// The current query. + IDeleteQuery Filter(Expression> filter); - /// - IDeleteQuery Filter(Expression> filter); + /// + IDeleteQuery Filter(Expression> filter); - /// - /// Orders the current s by the given property accending first. - /// - /// The property to order by. - /// The order of which null values should occor. - /// The current query. - IDeleteQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + /// + /// Orders the current s by the given property accending first. + /// + /// The property to order by. + /// The order of which null values should occor. + /// The current query. + IDeleteQuery OrderBy(Expression> propertySelector, + OrderByNullPlacement? nullPlacement = null); - /// - IDeleteQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + /// + IDeleteQuery OrderBy(Expression> propertySelector, + OrderByNullPlacement? nullPlacement = null); - /// - /// Orders the current s by the given property descending first. - /// - /// The property to order by. - /// The order of which null values should occur. - /// The current query. - IDeleteQuery OrderByDescending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + /// + /// Orders the current s by the given property descending first. + /// + /// The property to order by. + /// The order of which null values should occur. + /// The current query. + IDeleteQuery OrderByDescending(Expression> propertySelector, + OrderByNullPlacement? nullPlacement = null); - /// - IDeleteQuery OrderByDescending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + /// + IDeleteQuery OrderByDescending(Expression> propertySelector, + OrderByNullPlacement? nullPlacement = null); - /// - /// Offsets the current s by the given amount. - /// - /// The amount to offset by. - /// The current query. - IDeleteQuery Offset(long offset); + /// + /// Offsets the current s by the given amount. + /// + /// The amount to offset by. + /// The current query. + IDeleteQuery Offset(long offset); - /// - /// Offsets the current s by the given amount. - /// - /// A callback returning the amount to offset by. - /// The current query. - IDeleteQuery Offset(Expression> offset); + /// + /// Offsets the current s by the given amount. + /// + /// A callback returning the amount to offset by. + /// The current query. + IDeleteQuery Offset(Expression> offset); - /// - /// Limits the current s to the given amount. - /// - /// The amount to limit to. - /// The current query. - IDeleteQuery Limit(long limit); + /// + /// Limits the current s to the given amount. + /// + /// The amount to limit to. + /// The current query. + IDeleteQuery Limit(long limit); - /// - /// Limits the current s to the given amount. - /// - /// A callback returning the amount to limit to. - /// The current query. - IDeleteQuery Limit(Expression> limit); - } + /// + /// Limits the current s to the given amount. + /// + /// A callback returning the amount to limit to. + /// The current query. + IDeleteQuery Limit(Expression> limit); } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IGroupQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IGroupQuery.cs index 3a1459a8..4cc66a24 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IGroupQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IGroupQuery.cs @@ -1,4 +1,5 @@ -using System.Linq.Expressions; +using EdgeDB.Interfaces.Queries; +using System.Linq.Expressions; namespace EdgeDB.Interfaces.Queries { @@ -21,7 +22,6 @@ public interface IGroupUsingQuery namespace EdgeDB { - using Interfaces.Queries; public static class GroupQueryExtensions { public static IGroupUsingQuery> Using( @@ -44,12 +44,14 @@ public static IGroupUsingQuery> Usin Expression> expression) => query.UsingInternal>(expression); - public static IGroupUsingQuery> Using( + public static IGroupUsingQuery> Using( this IGroupQuery> query, Expression, TUsing>> expression) => query.UsingInternal>(expression); - public static IGroupUsingQuery> Using( + public static IGroupUsingQuery> Using( this IGroupQuery> query, Expression> expression) => query.UsingInternal>(expression); diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs index aaa80501..575c8ee4 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs @@ -1,40 +1,34 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.Interfaces.Queries +namespace EdgeDB.Interfaces.Queries; + +/// +/// Represents a generic INSERT query used within a . +/// +/// The type which this INSERT query is querying against. +/// The type of context representing the current builder. +public interface IInsertQuery : ISingleCardinalityExecutable where TContext : IQueryContext { /// - /// Represents a generic INSERT query used within a . + /// Automatically adds an UNLESS CONFLICT ON ... statement to the current insert + /// query, preventing any conflicts from throwing an exception. /// - /// The type which this INSERT query is querying against. - /// The type of context representing the current builder. - public interface IInsertQuery : ISingleCardinalityExecutable where TContext : IQueryContext - { - /// - /// Automatically adds an UNLESS CONFLICT ON ... statement to the current insert - /// query, preventing any conflicts from throwing an exception. - /// - /// - /// This query requires introspection of the database, multiple queries may be executed - /// when this query executes. - /// - /// The current query. - IUnlessConflictOn UnlessConflict(); + /// + /// This query requires introspection of the database, multiple queries may be executed + /// when this query executes. + /// + /// The current query. + IUnlessConflictOn UnlessConflict(); - /// - /// Adds an UNLESS CONFLICT ON statement with the given property selector. - /// - /// - /// A lambda function selecting which property will be added to the UNLESS CONFLICT ON statement - /// - /// The current query. - IUnlessConflictOn UnlessConflictOn(Expression> propertySelector); + /// + /// Adds an UNLESS CONFLICT ON statement with the given property selector. + /// + /// + /// A lambda function selecting which property will be added to the UNLESS CONFLICT ON statement + /// + /// The current query. + IUnlessConflictOn UnlessConflictOn(Expression> propertySelector); - /// - IUnlessConflictOn UnlessConflictOn(Expression> propertySelector); - } + /// + IUnlessConflictOn UnlessConflictOn(Expression> propertySelector); } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs index c1004a95..adcd14af 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs @@ -1,77 +1,77 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.Interfaces.Queries +namespace EdgeDB.Interfaces.Queries; + +/// +/// Represents a generic SELECT query used within a . +/// +/// The type which this SELECT query is querying against. +/// The type of context representing the current builder. +public interface ISelectQuery : IMultiCardinalityExecutable where TContext : IQueryContext { /// - /// Represents a generic SELECT query used within a . + /// Filters the current select query by the given predicate. /// - /// The type which this SELECT query is querying against. - /// The type of context representing the current builder. - public interface ISelectQuery : IMultiCardinalityExecutable where TContext : IQueryContext - { - /// - /// Filters the current select query by the given predicate. - /// - /// The filter to apply to the current select query. - /// The current query. - ISelectQuery Filter(Expression> filter); + /// The filter to apply to the current select query. + /// The current query. + ISelectQuery Filter(Expression> filter); - /// - ISelectQuery Filter(Expression> filter); + /// + ISelectQuery Filter(Expression> filter); - /// - /// Orders the current s by the given property ascending first. - /// - /// The property to order by. - /// The order of which null values should occur. - /// The current query. - ISelectQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + /// + /// Orders the current s by the given property ascending first. + /// + /// The property to order by. + /// The order of which null values should occur. + /// The current query. + ISelectQuery OrderBy(Expression> propertySelector, + OrderByNullPlacement? nullPlacement = null); - /// - ISelectQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + /// + ISelectQuery OrderBy(Expression> propertySelector, + OrderByNullPlacement? nullPlacement = null); - /// - /// Orders the current s by the given property descending first. - /// - /// The property to order by. - /// The order of which null values should occur. - /// The current query. - ISelectQuery OrderByDescending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + /// + /// Orders the current s by the given property descending first. + /// + /// The property to order by. + /// The order of which null values should occur. + /// The current query. + ISelectQuery OrderByDescending(Expression> propertySelector, + OrderByNullPlacement? nullPlacement = null); - /// - ISelectQuery OrderByDescending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + /// + ISelectQuery OrderByDescending(Expression> propertySelector, + OrderByNullPlacement? nullPlacement = null); - /// - /// Offsets the current s by the given amount. - /// - /// The amount to offset by. - /// The current query. - ISelectQuery Offset(long offset); + /// + /// Offsets the current s by the given amount. + /// + /// The amount to offset by. + /// The current query. + ISelectQuery Offset(long offset); - /// - /// Offsets the current s by the given amount. - /// - /// A callback returning the amount to offset by. - /// The current query. - ISelectQuery Offset(Expression> offset); + /// + /// Offsets the current s by the given amount. + /// + /// A callback returning the amount to offset by. + /// The current query. + ISelectQuery Offset(Expression> offset); - /// - /// Limits the current s to the given amount. - /// - /// The amount to limit to. - /// The current query. - ISelectQuery Limit(long limit); + /// + /// Limits the current s to the given amount. + /// + /// The amount to limit to. + /// The current query. + ISelectQuery Limit(long limit); - /// - /// Limits the current s to the given amount. - /// - /// A callback returning the amount to limit to. - /// The current query. - ISelectQuery Limit(Expression> limit); - } + /// + /// Limits the current s to the given amount. + /// + /// A callback returning the amount to limit to. + /// The current query. + ISelectQuery Limit(Expression> limit); } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs index a4b62459..e76c059d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs @@ -1,34 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.Interfaces.Queries +namespace EdgeDB.Interfaces.Queries; + +/// +/// Represents a generic UPDATE query used within a . +/// +/// The type which this UPDATE query is querying against. +/// The type of context representing the current builder. +public interface IUpdateQuery : IUpdateQuerySet where TContext : IQueryContext { /// - /// Represents a generic UPDATE query used within a . + /// Filters the current update query by the given predicate. /// - /// The type which this UPDATE query is querying against. - /// The type of context representing the current builder. - public interface IUpdateQuery : IUpdateQuerySet where TContext : IQueryContext - { - /// - /// Filters the current update query by the given predicate. - /// - /// The filter to apply to the current update query. - /// The current query. - IUpdateQuerySet Filter(Expression> filter); + /// The filter to apply to the current update query. + /// The current query. + IUpdateQuerySet Filter(Expression> filter); - /// - IUpdateQuerySet Filter(Expression> filter); - } + /// + IUpdateQuerySet Filter(Expression> filter); +} - public interface IUpdateQuerySet where TContext : IQueryContext - { - IMultiCardinalityExecutable Set(Expression> shape); - IMultiCardinalityExecutable Set(Expression> shape); - IMultiCardinalityExecutable Set(Action> shape); - } +public interface IUpdateQuerySet where TContext : IQueryContext +{ + IMultiCardinalityExecutable Set(Expression> shape); + IMultiCardinalityExecutable Set(Expression> shape); + IMultiCardinalityExecutable Set(Action> shape); } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs index 321cd024..289384d5 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs @@ -1,49 +1,44 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.Interfaces; -namespace EdgeDB.Interfaces +/// +/// Represents a generic UNLESS CONFLICT ON query used within a . +/// +/// The type which this UNLESS CONFLICT ON query is querying against. +/// The type of context representing the current builder. +public interface IUnlessConflictOn : ISingleCardinalityExecutable where TContext : IQueryContext { /// - /// Represents a generic UNLESS CONFLICT ON query used within a . + /// Adds an ELSE (SELECT ...) statment to the current query returning the conflicting object. /// - /// The type which this UNLESS CONFLICT ON query is querying against. - /// The type of context representing the current builder. - public interface IUnlessConflictOn : ISingleCardinalityExecutable where TContext : IQueryContext - { - /// - /// Adds an ELSE (SELECT ...) statment to the current query returning the conflicting object. - /// - /// An executable query. - ISingleCardinalityExecutable ElseReturn(); + /// An executable query. + ISingleCardinalityExecutable ElseReturn(); - /// - /// Adds an ELSE ... statement with the else clause being the provided query builder. - /// - /// - /// The callback that modifies the provided query builder to return a zero-many cardinality result. - /// - /// An executable query. - IMultiCardinalityExecutable Else(Func, IMultiCardinalityExecutable> elseQuery); + /// + /// Adds an ELSE ... statement with the else clause being the provided query builder. + /// + /// + /// The callback that modifies the provided query builder to return a zero-many cardinality result. + /// + /// An executable query. + IMultiCardinalityExecutable Else( + Func, IMultiCardinalityExecutable> elseQuery); - /// - /// Adds an ELSE ... statement with the else clause being the provided query builder. - /// - /// - /// The callback that modifies the provided query builder to return a zero-one cardinality result. - /// - /// An executable query. - ISingleCardinalityExecutable Else(Func, ISingleCardinalityExecutable> elseQuery); + /// + /// Adds an ELSE ... statement with the else clause being the provided query builder. + /// + /// + /// The callback that modifies the provided query builder to return a zero-one cardinality result. + /// + /// An executable query. + ISingleCardinalityExecutable Else( + Func, ISingleCardinalityExecutable> elseQuery); - /// - /// Adds an ELSE ... statement with the else clause being the provided query builder. - /// - /// The type of the query builder - /// The elses' inner clause - /// A query builder representing a generic result. - IQueryBuilder Else(TQueryBuilder elseQuery) - where TQueryBuilder : IQueryBuilder; - } + /// + /// Adds an ELSE ... statement with the else clause being the provided query builder. + /// + /// The type of the query builder + /// The elses' inner clause + /// A query builder representing a generic result. + IQueryBuilder Else(TQueryBuilder elseQuery) + where TQueryBuilder : IQueryBuilder; } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/SelectContextExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/SelectContextExtensions.cs index 1c488e18..df9831d3 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/SelectContextExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/SelectContextExtensions.cs @@ -1,6 +1,5 @@ using EdgeDB.Builders; using EdgeDB.Interfaces.Queries; -using System.Linq.Expressions; namespace EdgeDB; @@ -10,15 +9,18 @@ public static ISelectQuery> Select> query, EdgeDBTypeContainer schemaType) => query.SelectInternal>(); + public static ISelectQuery> Select( this IQueryBuilder> query, EdgeDBTypeContainer schemaType, Action> shape) => query.SelectInternal>(shape); + public static ISelectQuery> Select( this IQueryBuilder> query, EdgeDBTypeContainer schemaType) => query.SelectInternal>(); + public static ISelectQuery> Select( this IQueryBuilder> query, EdgeDBTypeContainer schemaType, diff --git a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs index d29e2c80..913716f6 100644 --- a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs +++ b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs @@ -1,208 +1,201 @@ using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +public interface IJsonVariable : IJsonVariable { - public interface IJsonVariable : IJsonVariable - { - Type IJsonVariable.InnerType => typeof(T); - } + Type IJsonVariable.InnerType => typeof(T); +} + +/// +/// Represents an abstracted form of . +/// +public interface IJsonVariable +{ + /// + /// Gets the depth of the json. + /// + int Depth { get; } + + /// + /// Gets the name used to reference this json value. + /// + string Name { get; } + + /// + /// Gets the variable name representing this json value. + /// + string VariableName { get; } + + /// + /// Gets the inner type of the json value. + /// + Type InnerType { get; } /// - /// Represents an abstracted form of . + /// Gets a collection of at a specific depth. /// - public interface IJsonVariable + /// The target depth to get the objects at. + /// + /// A collection of at the . + /// + IEnumerable GetObjectsAtDepth(int targetDepth); +} + +/// +/// A class representing a singleton, user defined json variable. +/// +/// The type that this json variable was initialized with. +public class JsonReferenceVariable : IJsonVariable +{ + /// + /// Constructs a new . + /// + /// The object reference to be used within this . + internal JsonReferenceVariable(T reference) { - /// - /// Gets the depth of the json. - /// - int Depth { get; } - - /// - /// Gets the name used to reference this json value. - /// - string Name { get; } - - /// - /// Gets the variable name representing this json value. - /// - string VariableName { get; } - - /// - /// Gets the inner type of the json value. - /// - Type InnerType { get; } - - /// - /// Gets a collection of at a specific depth. - /// - /// The target depth to get the objects at. - /// - /// A collection of at the . - /// - IEnumerable GetObjectsAtDepth(int targetDepth); + Value = reference; } /// - /// A class representing a singleton, user defined json variable. + /// Gets the value this represents. /// - /// The type that this json variable was initialized with. - public class JsonReferenceVariable : IJsonVariable - { - /// - /// Gets the value this represents. - /// - public T Value { get; } - - /// - /// Gets the variable name containing the jsonified . - /// - internal string? VariableName { get; } - - /// - /// Gets the name (in the with block) of this reference variable. - /// - internal string? Name { get; } - - /// - /// Constructs a new . - /// - /// The object reference to be used within this . - internal JsonReferenceVariable(T reference) - { - Value = reference; - } + public T Value { get; } - /// - int IJsonVariable.Depth - => 0; + /// + /// Gets the variable name containing the jsonified . + /// + internal string? VariableName { get; } - /// - string IJsonVariable.Name - => Name ?? throw new InvalidOperationException("Cannot access name until reference variable initializes"); + /// + /// Gets the name (in the with block) of this reference variable. + /// + internal string? Name { get; } - /// - string IJsonVariable.VariableName - => VariableName ?? throw new InvalidOperationException("Cannot access variable name until reference variable initializes"); + /// + int IJsonVariable.Depth + => 0; - /// - IEnumerable IJsonVariable.GetObjectsAtDepth(int targetDepth) - => Array.Empty(); - } + /// + string IJsonVariable.Name + => Name ?? throw new InvalidOperationException("Cannot access name until reference variable initializes"); + /// + string IJsonVariable.VariableName + => VariableName ?? + throw new InvalidOperationException("Cannot access variable name until reference variable initializes"); + + /// + IEnumerable IJsonVariable.GetObjectsAtDepth(int targetDepth) + => Array.Empty(); +} + +/// +/// Represents a json value used within queries. +/// +/// The inner type that the json value represents. +public class JsonCollectionVariable : IJsonVariable +{ /// - /// Represents a json value used within queries. + /// The root containing all the json objects. /// - /// The inner type that the json value represents. - public class JsonCollectionVariable : IJsonVariable + private readonly JArray _array; + + /// + /// Constructs a new . + /// + /// The name of the variable. + /// The name of the edgedb variable containing the json value + /// The containing all the json objects. + internal JsonCollectionVariable(string name, string varName, JArray array) { - /// - /// Gets the name of the json variable. - /// - public string Name { get; } - - /// - /// Gets a mock reference of the json variable. - /// - /// - /// This property can only be accessed within query builder lambda - /// functions. Attempting to access this property outside of a query - /// builder context will result in a - /// being thrown. - /// - public T Value - => throw new InvalidOperationException("Value cannot be accessed outside of an expression."); - - /// - /// Gets whether or not the inner array is an object array. - /// - internal bool IsObjectArray - => _array.All(x => x is JObject); - - /// - /// Gets the variable name of the current json variable. - /// - internal string VariableName { get; } - - /// - /// The root containing all the json objects. - /// - private readonly JArray _array; - - /// - /// Constructs a new . - /// - /// The name of the variable. - /// The name of the edgedb variable containing the json value - /// The containing all the json objects. - internal JsonCollectionVariable(string name, string varName, JArray array) - { - _array = array; - VariableName = varName; - Name = name; - } + _array = array; + VariableName = varName; + Name = name; + } - /// . - private IEnumerable GetObjectsAtDepth(int targetDepth) - { - IEnumerable GetObjects(JObject obj, int currentDepth) - { - if (targetDepth == currentDepth) - return new JObject[] { obj }; + /// + /// Gets the name of the json variable. + /// + public string Name { get; } - if (targetDepth > currentDepth) - return obj.Properties().Where(x => x.Value is JObject).SelectMany(x => GetObjects((JObject)x.Value, currentDepth + 1)); + /// + /// Gets a mock reference of the json variable. + /// + /// + /// This property can only be accessed within query builder lambda + /// functions. Attempting to access this property outside of a query + /// builder context will result in a + /// being thrown. + /// + public T Value + => throw new InvalidOperationException("Value cannot be accessed outside of an expression."); - return Array.Empty(); - } + /// + /// Gets whether or not the inner array is an object array. + /// + internal bool IsObjectArray + => _array.All(x => x is JObject); - return _array.Where(x => x is JObject).SelectMany(x => GetObjects((JObject)x, 0)); - } + /// + /// Gets the variable name of the current json variable. + /// + internal string VariableName { get; } - /// - private int CalculateDepth() - { - return _array.Max(x => - { - if (x is JObject obj) - return CalculateNodeDepth(obj, 0); - return 0; - }); - } + string IJsonVariable.Name => Name; + IEnumerable IJsonVariable.GetObjectsAtDepth(int targetDepth) => GetObjectsAtDepth(targetDepth); + int IJsonVariable.Depth => CalculateDepth(); + string IJsonVariable.VariableName => VariableName; - /// - /// Calculates the depth of a given json node. - /// - /// The node to calculate depth for. - /// The current depth of the computation. - /// - /// The depth of the given . - /// - private int CalculateNodeDepth(JObject node, int depth = 0) + /// + /// . + private IEnumerable GetObjectsAtDepth(int targetDepth) + { + IEnumerable GetObjects(JObject obj, int currentDepth) { - return node.Properties().Max(x => - { - switch(x.Value) - { - case JObject jObject: - return CalculateNodeDepth(jObject, depth + 1); - case JArray jArray when jArray.Any(): - return jArray.Max(x => x is JObject subNode ? CalculateNodeDepth(subNode, depth + 1) : depth); - case JArray jArray: - return -1; // empty array has no depth - default: - return depth; - } - }); + if (targetDepth == currentDepth) + return new[] {obj}; + + if (targetDepth > currentDepth) + return obj.Properties().Where(x => x.Value is JObject) + .SelectMany(x => GetObjects((JObject)x.Value, currentDepth + 1)); + + return Array.Empty(); } - string IJsonVariable.Name => Name; - IEnumerable IJsonVariable.GetObjectsAtDepth(int targetDepth) => GetObjectsAtDepth(targetDepth); - int IJsonVariable.Depth => CalculateDepth(); - string IJsonVariable.VariableName => VariableName; + return _array.Where(x => x is JObject).SelectMany(x => GetObjects((JObject)x, 0)); } + + /// + private int CalculateDepth() => + _array.Max(x => + { + if (x is JObject obj) + return CalculateNodeDepth(obj); + return 0; + }); + + /// + /// Calculates the depth of a given json node. + /// + /// The node to calculate depth for. + /// The current depth of the computation. + /// + /// The depth of the given . + /// + private int CalculateNodeDepth(JObject node, int depth = 0) => + node.Properties().Max(x => + { + switch (x.Value) + { + case JObject jObject: + return CalculateNodeDepth(jObject, depth + 1); + case JArray jArray when jArray.Any(): + return jArray.Max(x => x is JObject subNode ? CalculateNodeDepth(subNode, depth + 1) : depth); + case JArray jArray: + return -1; // empty array has no depth + default: + return depth; + } + }); } diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/FunctionArgumentMetadata.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/FunctionArgumentMetadata.cs index 5220317f..067a231e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/FunctionArgumentMetadata.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/FunctionArgumentMetadata.cs @@ -1,3 +1,4 @@ namespace EdgeDB; -internal sealed record FunctionArgumentMetadata(uint Index, string FunctionName, string? NamedParameter) : ITermMetadata; +internal sealed record FunctionArgumentMetadata(uint Index, string FunctionName, string? NamedParameter) + : ITermMetadata; diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/FunctionMetadata.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/FunctionMetadata.cs index 661f044b..c2d0237b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/FunctionMetadata.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/FunctionMetadata.cs @@ -10,7 +10,8 @@ public bool TryResolveExactFunctionInfo(List arguments, [MaybeNullWhen(fal TryResolveFunctionInfos(out var infos) && TryResolveExactFunctionInfo(infos, arguments, out methodInfo); - public bool TryResolveExactFunctionInfo(List potentials, List arguments, [MaybeNullWhen(false)] out MethodInfo methodInfo) + public bool TryResolveExactFunctionInfo(List potentials, List arguments, + [MaybeNullWhen(false)] out MethodInfo methodInfo) { if (potentials.Count == 1) { @@ -24,7 +25,7 @@ public bool TryResolveExactFunctionInfo(List potentials, List var optionalParamsCount = parameters.Count(x => x.IsOptional); var shouldBeIn = (parameters.Length - optionalParamsCount)..parameters.Length; - if(!shouldBeIn.Contains(arguments.Count)) + if (!shouldBeIn.Contains(arguments.Count)) continue; methodInfo = potential; diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/IMarkerMetadata.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/IMarkerMetadata.cs index 2f137b68..da1bfe1e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/IMarkerMetadata.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerMetadata/IMarkerMetadata.cs @@ -1,3 +1,5 @@ namespace EdgeDB; -internal interface ITermMetadata{} +internal interface ITermMetadata +{ +} diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/Observers/RangeNodeObserver.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/Observers/RangeNodeObserver.cs index 2e3dbba1..3c94c111 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/Observers/RangeNodeObserver.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/Observers/RangeNodeObserver.cs @@ -1,10 +1,17 @@ using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; namespace EdgeDB; internal sealed class RangeNodeObserver : INodeObserver, IDisposable { + private readonly QueryWriter _writer; + + public RangeNodeObserver(QueryWriter writer) + { + _writer = writer; + _writer.AddObserver(this); + } + [MemberNotNullWhen(true, nameof(First))] [MemberNotNullWhen(true, nameof(Last))] public bool HasValue @@ -14,14 +21,7 @@ public bool HasValue public LooseLinkedList.Node? Last { get; private set; } - - private readonly QueryWriter _writer; - - public RangeNodeObserver(QueryWriter writer) - { - _writer = writer; - _writer.AddObserver(this); - } + public void Dispose() => _writer.RemoveObserver(this); public void OnAdd(LooseLinkedList.Node node) { @@ -44,9 +44,4 @@ public void OnRemove(LooseLinkedList.Node node) Last = null; } } - - public void Dispose() - { - _writer.RemoveObserver(this); - } } diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/Observers/TokenSpan.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/Observers/TokenSpan.cs index 79ec275e..cb20c069 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/Observers/TokenSpan.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/Observers/TokenSpan.cs @@ -15,23 +15,11 @@ public TokenSpan(QueryWriter writer) writer.AddObserver(this); } - public void OnAdd(TokenNode node) - { - _nodes.Add(node); - } + public void Dispose() => _writer.RemoveObserver(this); - public void OnRemove(TokenNode node) - { - _nodes.Remove(node); - } + public void OnAdd(TokenNode node) => _nodes.Add(node); - public void Dispose() - { - _writer.RemoveObserver(this); - } + public void OnRemove(TokenNode node) => _nodes.Remove(node); - public Token[] ToTokens() - { - return _nodes.Select(x => x.Value).ToArray(); - } + public Token[] ToTokens() => _nodes.Select(x => x.Value).ToArray(); } diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/QueryWriter.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/QueryWriter.cs index e222ece6..1f1fff5f 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/QueryWriter.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/QueryWriter.cs @@ -8,67 +8,52 @@ namespace EdgeDB; internal sealed class QueryWriter(bool isDebugQuery = false) : IDisposable { - private sealed class PositionalTrack : IDisposable - { - private readonly TokenNode? _oldRef; - private readonly QueryWriter _writer; - - public PositionalTrack(QueryWriter writer, TokenNode? from) - { - _oldRef = writer._track; - - writer.TrackedPosition = from is not null - ? writer.GetIndexOfNode(from) - : 0; - writer._track = from; - - _writer = writer; - } - - public void Dispose() - { - _writer._track = _oldRef; - } - } + private readonly List _observers = []; - public bool IsDebug { get; } = isDebugQuery; + private readonly Queue _termUpdates = []; public readonly TermCollection Terms = new(); public readonly LooseLinkedList Tokens = new(); - private readonly List _observers = []; - - private readonly Queue _termUpdates = []; - private TokenNode? _track; + public bool IsDebug { get; } = isDebugQuery; + public int TailIndex => Tokens.Count - 1; private int TrackedPosition { get; set; } + public void Dispose() + { + Terms.Clear(); + Tokens.Clear(); + _observers.Clear(); + } + private void UpdateTerms() => Terms.Update(_termUpdates, Tokens.Count); /// /// Creates a new scope that appends the next tokens at the start of this writer until the - /// is disposed. + /// is disposed. /// - /// A that represents the lifetime of the scope. + /// A that represents the lifetime of the scope. public IDisposable PositionalScopeFromStart() => PositionalScope(null); /// /// Creates a new scope that appends the next tokens after the provided node until the - /// is disposed. + /// is disposed. /// /// The node to append tokens after. - /// A that represents the lifetime of the scope. + /// A that represents the lifetime of the scope. public IDisposable PositionalScope(TokenNode? from) => new PositionalTrack(this, from); private TokenNodeSlice AddAfterTracked(in Token token) => AddTracked(in token, true); + private TokenNodeSlice AddBeforeTracked(in Token token) => AddTracked(in token, false); @@ -109,7 +94,7 @@ public void RemoveObserver(INodeObserver observer) private void OnNodeAdd(TokenNode node) { - foreach(var observer in _observers) + foreach (var observer in _observers) observer.OnAdd(node); } @@ -133,7 +118,8 @@ private int GetIndexOfNode(TokenNode node) return -1; } - public QueryWriter Term(TermType type, string name, in Token token, Deferrable? debug1 = null, ITermMetadata? metadata = null) + public QueryWriter Term(TermType type, string name, in Token token, Deferrable? debug1 = null, + ITermMetadata? metadata = null) { if (type is TermType.Verbose && !IsDebug) { @@ -152,13 +138,15 @@ public QueryWriter Term(TermType type, string name, in Token token, Deferrable? debug = null, ITermMetadata? metadata = null) + public QueryWriter Term(TermType type, string name, Deferrable? debug = null, + ITermMetadata? metadata = null) => Term(type, name, debug, metadata, name); public QueryWriter Term(TermType type, string name, Deferrable? debug = null, params Token[] tokens) => Term(type, name, debug, null, tokens); - public QueryWriter Term(TermType type, string name, Deferrable? debug = null, ITermMetadata? metadata = null, params Token[] values) + public QueryWriter Term(TermType type, string name, Deferrable? debug = null, + ITermMetadata? metadata = null, params Token[] values) { if (type is TermType.Verbose && !IsDebug) { @@ -329,10 +317,7 @@ public QueryWriter Append(out TokenNodeSlice node, params Token[] values) return this; } - public QueryWriter AppendIf(Func condition, in Token token) - { - return condition() ? Append(in token) : this; - } + public QueryWriter AppendIf(Func condition, in Token token) => condition() ? Append(in token) : this; public bool AppendIsEmpty(in Token token) => AppendIsEmpty(in token, out _, out _); @@ -385,23 +370,6 @@ public StringBuilder Compile(StringBuilder? builder = null) return builder; } - private sealed class ActiveTermTrack(int index, Term term, string name, StringBuilder builder, int count) - { - public string Name { get; } = name; - public int Index { get; } = index; - public Term Term { get; } = term; - public StringBuilder Builder { get; } = builder; - public bool TryWrite(Token token) - { - if (count == 0) - return false; - - token.WriteTo(Builder); - count--; - return true; - } - } - public (string Query, LinkedList Terms, LinkedList Tokens) CompileDebug() { var query = new StringBuilder(); @@ -420,7 +388,8 @@ public bool TryWrite(Token token) activeTerms.Remove(activeTerm); var content = activeTerm.Builder.ToString(); - spans.AddLast(new QuerySpan(activeTerm.Index..(activeTerm.Index + content.Length), content, activeTerm.Term, activeTerm.Name)); + spans.AddLast(new QuerySpan(activeTerm.Index..(activeTerm.Index + content.Length), content, + activeTerm.Term, activeTerm.Name)); } foreach (var startingTerm in terms.Where(x => x.Item2.IsAlive && x.Item2.Slice.Head == current)) @@ -428,7 +397,8 @@ public bool TryWrite(Token token) terms.Remove(startingTerm); var sb = new StringBuilder(); - activeTerms.Add(new (query.Length, startingTerm.Item2, startingTerm.Item1, sb, startingTerm.Item2.Size - 1)); + activeTerms.Add(new ActiveTermTrack(query.Length, startingTerm.Item2, startingTerm.Item1, sb, + startingTerm.Item2.Size - 1)); current.Value.WriteTo(sb); } @@ -442,7 +412,8 @@ public bool TryWrite(Token token) foreach (var remaining in activeTerms) { var content = remaining.Builder.ToString(); - spans.AddLast(new QuerySpan(remaining.Index..(remaining.Index + content.Length), content, remaining.Term, remaining.Name)); + spans.AddLast(new QuerySpan(remaining.Index..(remaining.Index + content.Length), content, remaining.Term, + remaining.Name)); } return (query.ToString(), spans, tokens); @@ -451,11 +422,41 @@ public bool TryWrite(Token token) #if DEBUG public string QuickDebugView() => DebugCompiledQuery.QuickView(this); #endif + private sealed class PositionalTrack : IDisposable + { + private readonly TokenNode? _oldRef; + private readonly QueryWriter _writer; - public void Dispose() + public PositionalTrack(QueryWriter writer, TokenNode? from) + { + _oldRef = writer._track; + + writer.TrackedPosition = from is not null + ? writer.GetIndexOfNode(from) + : 0; + writer._track = from; + + _writer = writer; + } + + public void Dispose() => _writer._track = _oldRef; + } + + private sealed class ActiveTermTrack(int index, Term term, string name, StringBuilder builder, int count) { - Terms.Clear(); - Tokens.Clear(); - _observers.Clear(); + public string Name { get; } = name; + public int Index { get; } = index; + public Term Term { get; } = term; + public StringBuilder Builder { get; } = builder; + + public bool TryWrite(Token token) + { + if (count == 0) + return false; + + token.WriteTo(Builder); + count--; + return true; + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/Term.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/Term.cs index c0ff6015..c0c55f10 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/Term.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/Term.cs @@ -4,6 +4,27 @@ namespace EdgeDB; internal sealed class Term { + private readonly QueryWriter _writer; + + private int _position; + private int _size; + private LooseLinkedList.NodeSlice _slice; + private int _sliceVersion; + private int _version; + + internal Term(string name, TermType type, QueryWriter writer, int size, int position, + LooseLinkedList.NodeSlice slice, Deferrable? debugText, ITermMetadata? metadata) + { + Name = name; + Type = type; + _writer = writer; + Size = size; + Position = position; + _slice = slice; + DebugText = debugText; + Metadata = metadata; + } + public bool IsAlive { get; private set; } = true; public string Name { get; } @@ -23,7 +44,7 @@ public int Size public Range Range => Position..Size; - public Deferrable? DebugText { get; private set;} + public Deferrable? DebugText { get; private set; } public ITermMetadata? Metadata { get; private set; } @@ -39,26 +60,6 @@ public LooseLinkedList.NodeSlice Slice } } - private readonly QueryWriter _writer; - - private int _position; - private int _size; - private int _sliceVersion; - private int _version; - private LooseLinkedList.NodeSlice _slice; - - internal Term(string name, TermType type, QueryWriter writer, int size, int position, LooseLinkedList.NodeSlice slice, Deferrable? debugText, ITermMetadata? metadata) - { - Name = name; - Type = type; - _writer = writer; - Size = size; - Position = position; - _slice = slice; - DebugText = debugText; - Metadata = metadata; - } - public bool IsChildOf(Term term) => term.Position <= Position && term.Size >= Size; @@ -110,10 +111,7 @@ private void RecalculateSlice() _sliceVersion = _version; } - public void Replace(Token token) - { - _writer.Move(Position, Slice, in token); - } + public void Replace(Token token) => _writer.Move(Position, Slice, in token); public void Remove() => _writer.Remove(Position, Slice); diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/TermCollection.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/TermCollection.cs index 2d15899a..9adb470a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/TermCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/TermCollection.cs @@ -1,5 +1,4 @@ - -using System.Buffers; +using System.Buffers; using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -10,24 +9,28 @@ namespace EdgeDB; internal sealed class TermCollection : IEnumerable { private const int MaxStackallocSize = 1024; - - public readonly Dictionary> TermsByType = new(); private readonly LinkedList _terms = new(); private readonly SortedDictionary> _termsByPosition = new(); public readonly Dictionary> TermsByName = new(); + public readonly Dictionary> TermsByType = new(); + + public IEnumerator GetEnumerator() => _terms.Where(x => x.IsAlive).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Add(Term term) { _terms.AddLast(term); if (!TermsByType.TryGetValue(term.Type, out var termsByType)) - TermsByType[term.Type] = termsByType = new(); + TermsByType[term.Type] = termsByType = new LinkedList(); if (!_termsByPosition.TryGetValue(term.Position, out var termsByPosition)) - _termsByPosition[term.Position] = termsByPosition = new(); + _termsByPosition[term.Position] = termsByPosition = new LinkedList(); if (!TermsByName.TryGetValue(term.Name, out var termsByName)) - TermsByName[term.Name] = termsByName = new(); + TermsByName[term.Name] = termsByName = new LinkedList(); termsByPosition.AddLast(term); termsByType.AddLast(term); @@ -96,7 +99,7 @@ public void Move(Range from, Range to) term.UpdatePosition(offset); } // otherwise we move any terms towards the head by decrementing the size of the moved range - else if(term.Position > from.Start.Value + from.End.Value) + else if (term.Position > from.Start.Value + from.End.Value) { term.UpdatePosition(-from.End.Value); } @@ -150,19 +153,19 @@ public unsafe void Update(Queue inserts, int tokenCount) } } - if(deltas.Length > deltasIndex) + if (deltas.Length > deltasIndex) deltas = deltas[..deltasIndex]; foreach (var term in _terms) { - if(!term.IsAlive) + if (!term.IsAlive) continue; var oldPos = term.Position; foreach (var delta in deltas) { - if(delta.Start.Value > term.Position) + if (delta.Start.Value > term.Position) continue; term.UpdatePosition(delta.End.Value); @@ -182,8 +185,9 @@ private void UpdateTermBucket(LinkedList bucket, int newPos) { if (!_termsByPosition.TryGetValue(newPos, out var existing)) existing = bucket; - else foreach (var oldTerm in bucket) - existing.AddLast(oldTerm); + else + foreach (var oldTerm in bucket) + existing.AddLast(oldTerm); _termsByPosition[newPos] = existing; } @@ -219,7 +223,8 @@ public LinkedList GetDirectParents(Term term) if (minStart is not null) { - foreach (var minStartTerm in _termsByPosition[term.Position].Where(x => x.Size == minStart.Size && x.IsAlive)) + foreach (var minStartTerm in _termsByPosition[term.Position] + .Where(x => x.Size == minStart.Size && x.IsAlive)) { result.AddLast(minStartTerm); } @@ -241,7 +246,9 @@ public LinkedList GetDirectParents(Term term) } public IEnumerable GetParents(Term term) - => _terms.Where(x => x.Position != term.Position && x.Size != term.Size && x.Position <= term.Position && x.Position + x.Size >= term.Position + term.Size); + => _terms.Where(x => + x.Position != term.Position && x.Size != term.Size && x.Position <= term.Position && + x.Position + x.Size >= term.Position + term.Size); public IEnumerable GetChildren(Term term) => _terms.Where(x => @@ -280,8 +287,4 @@ public void Clear() TermsByType.Clear(); TermsByName.Clear(); } - - public IEnumerator GetEnumerator() => _terms.Where(x => x.IsAlive).GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/Token.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/Token.cs index db96150d..05c8a3fa 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/Token.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/Token.cs @@ -1,6 +1,5 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; using System.Text; namespace EdgeDB; @@ -105,7 +104,8 @@ public override string ToString() public static implicit operator Token(int v) => new(v.ToString()); public static implicit operator Token(long v) => new(v.ToString()); - public bool Equals(Token other) => Equals(Callback, other.Callback) && Equals(RawValue, other.RawValue) && StringValue == other.StringValue && CharValue == other.CharValue; + public bool Equals(Token other) => Equals(Callback, other.Callback) && Equals(RawValue, other.RawValue) && + StringValue == other.StringValue && CharValue == other.CharValue; public override bool Equals(object? obj) => obj is Token other && Equals(other); diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/WriterProxy.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/WriterProxy.cs index 1ed515a5..5cc34251 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/WriterProxy.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/WriterProxy.cs @@ -1,4 +1,3 @@ namespace EdgeDB; internal delegate void WriterProxy(QueryWriter writer); - diff --git a/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs b/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs index e2a752c0..3de03206 100644 --- a/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs +++ b/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs @@ -1,24 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB; -namespace EdgeDB +/// +/// An enum representing the placement of null values within queries. +/// +public enum OrderByNullPlacement { /// - /// An enum representing the placement of null values within queries. + /// Places values at the front of the ordered set. /// - public enum OrderByNullPlacement - { - /// - /// Places values at the front of the ordered set. - /// - First, + First, - /// - /// Places values at the end of the ordered set. - /// - Last - } + /// + /// Places values at the end of the ordered set. + /// + Last } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index cfa6e18c..31f66d65 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -1,669 +1,665 @@ -using EdgeDB.Builders; using EdgeDB.Compiled; using EdgeDB.Interfaces; -using EdgeDB.Interfaces.Queries; using EdgeDB.QueryNodes; using EdgeDB.Schema; -using EdgeDB.Translators.Expressions; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +/// +/// Represents a query builder used to build queries against . +/// +/// The type that this query builder is currently building queries for. +public partial class QueryBuilder : QueryBuilder> { + public QueryBuilder() + { + } + + internal QueryBuilder(QueryBuilderState state) + : base(state) + { + } +} + +/// +/// Represents a query builder used to build queries against +/// with the context type . +/// +/// The type that this query builder is currently building queries for. +/// The context type used for contextual expressions. +public partial class QueryBuilder : IQueryBuilder where TContext : IQueryContext +{ + private readonly QueryBuilderState _state; + /// - /// Represents a query builder used to build queries against . + /// Constructs an empty query builder. /// - /// The type that this query builder is currently building queries for. - public partial class QueryBuilder : QueryBuilder> + public QueryBuilder() : this(QueryBuilderState.Empty) { - public QueryBuilder() - { } + } - internal QueryBuilder(QueryBuilderState state) - : base(state) { } + /// + /// Constructs a query builder with the given nodes, globals, and variables. + /// + /// The state information for this querybuilder. + internal QueryBuilder(QueryBuilderState state) + { + _state = state; + } + + /// + /// Constructs a query builder with the given schema introspection info. + /// + /// The schema introspection info. + internal QueryBuilder(SchemaInfo info) + : this() + { + SchemaInfo = info; } /// - /// Represents a query builder used to build queries against - /// with the context type . + /// A list of query nodes that make up the current query builder. /// - /// The type that this query builder is currently building queries for. - /// The context type used for contextual expressions. - public partial class QueryBuilder : IQueryBuilder where TContext : IQueryContext + private List Nodes + => _state.Nodes; + + /// + /// The current user defined query node. + /// + private QueryNode? CurrentUserNode { - /// - public bool RequiresIntrospection - => Nodes.Any(x => x.RequiresIntrospection); - - /// - /// A list of query nodes that make up the current query builder. - /// - private List Nodes - => _state.Nodes; - - /// - /// The current user defined query node. - /// - private QueryNode? CurrentUserNode + get { - get - { - var latestNode = Nodes.LastOrDefault(x => !x.IsAutoGenerated); + var latestNode = Nodes.LastOrDefault(x => !x.IsAutoGenerated); - if (latestNode is not null) - return latestNode; + if (latestNode is not null) + return latestNode; - if (Nodes.Count == 0) - return null; + if (Nodes.Count == 0) + return null; - for (int i = Nodes.Count - 1; i >= 0; i--) + for (var i = Nodes.Count - 1; i >= 0; i--) + { + var n = Nodes[i]; + if (n.IsAutoGenerated) { - var n = Nodes[i]; - if (n.IsAutoGenerated) - { - var child = n.SubNodes.FirstOrDefault(x => !x.IsAutoGenerated); - if (child is not null) - return child; - } + var child = n.SubNodes.FirstOrDefault(x => !x.IsAutoGenerated); + if (child is not null) + return child; } - - throw new NotSupportedException("No user defined query node found. (this is most likely a bug)"); } - } - /// - /// A list of query globals used by this query builder. - /// - private List QueryGlobals - => _state.Globals; - - /// - /// The current schema introspection info if it has been fetched. - /// - private SchemaInfo? SchemaInfo { get => _state.SchemaInfo; set => _state.SchemaInfo = value; } - - /// - /// A dictionary of query variables used by the . - /// - private Dictionary QueryVariables - => _state.Variables; - - private QueryBuilderState _state; - - /// - /// Constructs an empty query builder. - /// - public QueryBuilder() : this(QueryBuilderState.Empty) - {} - - /// - /// Constructs a query builder with the given nodes, globals, and variables. - /// - /// The state information for this querybuilder. - internal QueryBuilder(QueryBuilderState state) - { - _state = state; + throw new NotSupportedException("No user defined query node found. (this is most likely a bug)"); } + } - /// - /// Constructs a query builder with the given schema introspection info. - /// - /// The schema introspection info. - internal QueryBuilder(SchemaInfo info) - : this() - { - SchemaInfo = info; - } + /// + /// A list of query globals used by this query builder. + /// + private List QueryGlobals + => _state.Globals; - /// - /// Configures whether the query builder should optimize the query produced. - /// - /// - /// Optimization occurs when the query is compiled, if the query builder is a sub query to another, it will - /// inherit the optimization from the parent query builder. - /// - /// - /// The configuration value for optimization: - ///
- : Force the query to be optimized, even if its a subquery - ///
- : Force the query to not be optimized, even if its a sub query and the - /// parents' configuration permits it. - ///
- : Use the default setting. - /// - /// The current query builder. - public QueryBuilder SetShouldOptimizeQuery(bool? value) - { - _state.RunReducers = value; - return this; - } + /// + /// The current schema introspection info if it has been fetched. + /// + private SchemaInfo? SchemaInfo { get => _state.SchemaInfo; set => _state.SchemaInfo = value; } - /// - /// Adds a query variable to the current query builder. - /// - /// The name of the variable. - /// The value of the variable. - internal void AddQueryVariable(string name, object? value) - => QueryVariables[name] = value; - - /// - /// Copies this query builders nodes, globals, and variables - /// to a new query builder with a given generic type. - /// - /// The target type of the new query builder. - /// - /// A new with the target type. - /// - private QueryBuilder EnterNewType() - { - if (this is QueryBuilder s) - return s; + /// + /// A dictionary of query variables used by the . + /// + private Dictionary QueryVariables + => _state.Variables; - return new(_state); - } + /// + public bool RequiresIntrospection + => Nodes.Any(x => x.RequiresIntrospection); - /// - /// Copies this query builders nodes, globals, and variables - /// to a new query builder with the given context type. - /// - /// The target context type of the new builder. - /// - /// A new with the target context type. - /// - internal QueryBuilder EnterNewContext() - where TNewContext : IQueryContext - { - if (this is QueryBuilder s) - return s; + /// + public CompiledQuery Compile(bool debug = false) + => CompileInternal(new CompileContext {Debug = debug}); - return new(_state); - } + /// + public ValueTask CompileAsync(IEdgeDBQueryable edgedb, bool debug = false, + CancellationToken token = default) + => IntrospectAndCompileAsync(edgedb, debug, token); - /// - /// Adds a new node to this query builder. - /// - /// The type of the node - /// The specified nodes context. - /// - /// Whether or not this node was added by the user or was added as - /// part of an implicit build step. - /// - /// The child node for the newly added node. - /// An instance of the specified . - private TNode AddNode(NodeContext context, bool autoGenerated = false, QueryNode? child = null) - where TNode : QueryNode - { - // create a new builder for the node. - var builder = new NodeBuilder(context, QueryGlobals, Nodes, QueryVariables) - { - IsAutoGenerated = autoGenerated - }; + /// + async Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, + Capabilities? capabilities, CancellationToken token) + { + var result = await IntrospectAndCompileAsync(edgedb, false, token).ConfigureAwait(false); + return await edgedb.QueryAsync(result.Query, result.RawVariables, capabilities, token) + .ConfigureAwait(false); + } - // construct the node. - var node = (TNode)Activator.CreateInstance(typeof(TNode), builder)!; + /// + async Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, + Capabilities? capabilities, CancellationToken token) + { + var result = await IntrospectAndCompileAsync(edgedb, false, token).ConfigureAwait(false); + return await edgedb.QuerySingleAsync(result.Query, result.RawVariables, capabilities, token) + .ConfigureAwait(false); + } - if(child is not null) - { - node.SubNodes.Add(child); - child.Parent = node; - Nodes.Remove(child); - } + /// + async Task IMultiCardinalityExecutable.ExecuteSingleAsync(IEdgeDBQueryable edgedb, + Capabilities? capabilities, CancellationToken token) + { + var result = await IntrospectAndCompileAsync(edgedb, false, token).ConfigureAwait(false); + return await edgedb.QuerySingleAsync(result.Query, result.RawVariables, capabilities, token) + .ConfigureAwait(false); + } - // visit the node - node.Visit(); + /// + async Task IMultiCardinalityExecutable.ExecuteRequiredSingleAsync(IEdgeDBQueryable edgedb, + Capabilities? capabilities, CancellationToken token) + { + var result = await IntrospectAndCompileAsync(edgedb, false, token).ConfigureAwait(false); + return await edgedb.QueryRequiredSingleAsync(result.Query, result.RawVariables, capabilities, token) + .ConfigureAwait(false); + } - Nodes.Add(node); + /// + /// Configures whether the query builder should optimize the query produced. + /// + /// + /// Optimization occurs when the query is compiled, if the query builder is a sub query to another, it will + /// inherit the optimization from the parent query builder. + /// + /// + /// The configuration value for optimization: + ///
- : Force the query to be optimized, even if its a subquery + ///
- : Force the query to not be optimized, even if its a sub query and the + /// parents' configuration permits it. + ///
- : Use the default setting. + /// + /// The current query builder. + public QueryBuilder SetShouldOptimizeQuery(bool? value) + { + _state.RunReducers = value; + return this; + } - return node; - } + /// + /// Adds a query variable to the current query builder. + /// + /// The name of the variable. + /// The value of the variable. + internal void AddQueryVariable(string name, object? value) + => QueryVariables[name] = value; - /// - /// Compiles the current query builder into its form. - /// - /// - /// A . - /// - internal CompiledQuery CompileInternal(CompileContext? context = null) - { - context ??= new CompileContext(); + /// + /// Copies this query builders nodes, globals, and variables + /// to a new query builder with a given generic type. + /// + /// The target type of the new query builder. + /// + /// A new with the target type. + /// + private QueryBuilder EnterNewType() + { + if (this is QueryBuilder s) + return s; - using var writer = new QueryWriter(context.Debug); + return new QueryBuilder(_state); + } - CompileInternal(writer, context); + /// + /// Copies this query builders nodes, globals, and variables + /// to a new query builder with the given context type. + /// + /// The target context type of the new builder. + /// + /// A new with the target context type. + /// + internal QueryBuilder EnterNewContext() + where TNewContext : IQueryContext + { + if (this is QueryBuilder s) + return s; - if (!context.Debug) return new CompiledQuery(writer.Compile().ToString(), QueryVariables); + return new QueryBuilder(_state); + } - var compiled = writer.CompileDebug(); - return new DebugCompiledQuery(compiled.Query, QueryVariables, compiled.Terms, compiled.Tokens); + /// + /// Adds a new node to this query builder. + /// + /// The type of the node + /// The specified nodes context. + /// + /// Whether or not this node was added by the user or was added as + /// part of an implicit build step. + /// + /// The child node for the newly added node. + /// An instance of the specified . + private TNode AddNode(NodeContext context, bool autoGenerated = false, QueryNode? child = null) + where TNode : QueryNode + { + // create a new builder for the node. + var builder = new NodeBuilder(context, QueryGlobals, Nodes, QueryVariables) {IsAutoGenerated = autoGenerated}; - } + // construct the node. + var node = (TNode)Activator.CreateInstance(typeof(TNode), builder)!; - internal void CompileInternal(QueryWriter writer, CompileContext? context = null) + if (child is not null) { - SchemaInfo ??= context?.SchemaInfo; - context ??= new(); - - var nodes = Nodes; + node.SubNodes.Add(child); + child.Parent = node; + Nodes.Remove(child); + } - if (!context.IncludeAutogeneratedNodes) - nodes = nodes - .Where(x => !nodes.Any(y => y.SubNodes.Contains(x)) || !x.IsAutoGenerated) - .ToList(); + // visit the node + node.Visit(); - // reference the introspection and finalize all nodes. - foreach (var node in nodes) - { - node.SchemaInfo ??= SchemaInfo; - context.PreFinalizerModifier?.Invoke(node); - } + Nodes.Add(node); - for (var i = 0; i != nodes.Count; i++) - { - var node = nodes[i]; + return node; + } - if(node is WithNode) - continue; + /// + /// Compiles the current query builder into its form. + /// + /// + /// A . + /// + internal CompiledQuery CompileInternal(CompileContext? context = null) + { + context ??= new CompileContext(); - writer.Term( - TermType.QueryNode, - nodes[i].GetType().Name, - debug: null, - metadata: new QueryNodeMetadata(node), - values: Token.Of(writer => node.FinalizeQuery(writer)) - ); + using var writer = new QueryWriter(context.Debug); - if (i != nodes.Count - 1) - writer.Append(' '); - } + CompileInternal(writer, context); - // create a with block if we have any globals - if (context.IncludeGlobalsInQuery && QueryGlobals.Any()) - { - var with = (WithNode?)Nodes.FirstOrDefault(x => x is WithNode); + if (!context.Debug) return new CompiledQuery(writer.Compile().ToString(), QueryVariables); - if (with is null) - { - var builder = new NodeBuilder(new WithContext(typeof(TType)), QueryGlobals, nodes, QueryVariables); + var compiled = writer.CompileDebug(); + return new DebugCompiledQuery(compiled.Query, QueryVariables, compiled.Terms, compiled.Tokens); + } - with = new WithNode(builder) - { - SchemaInfo = SchemaInfo - }; + internal void CompileInternal(QueryWriter writer, CompileContext? context = null) + { + SchemaInfo ??= context?.SchemaInfo; + context ??= new CompileContext(); - nodes = nodes.Prepend(with).ToList(); - } + var nodes = Nodes; - // visit the with node and add it to the front of our local collection of nodes. - using var _ = writer.PositionalScopeFromStart(); + if (!context.IncludeAutogeneratedNodes) + nodes = nodes + .Where(x => !nodes.Any(y => y.SubNodes.Contains(x)) || !x.IsAutoGenerated) + .ToList(); - writer.Term( - TermType.QueryNode, - with.GetType().Name, - debug: null, - metadata: new QueryNodeMetadata(with), - values: Token.Of(writer => with.FinalizeQuery(writer)) - ); + // reference the introspection and finalize all nodes. + foreach (var node in nodes) + { + node.SchemaInfo ??= SchemaInfo; + context.PreFinalizerModifier?.Invoke(node); + } - writer.Append(' '); - } + for (var i = 0; i != nodes.Count; i++) + { + var node = nodes[i]; - // reduce the query - var shouldReduce = context is {RunReducers: true}; + if (node is WithNode) + continue; - if (_state.RunReducers is not null) - shouldReduce = _state.RunReducers.Value; + writer.Term( + TermType.QueryNode, + nodes[i].GetType().Name, + null, + new QueryNodeMetadata(node), + Token.Of(writer => node.FinalizeQuery(writer)) + ); - if(shouldReduce) - QueryReducer.Apply(this, writer); + if (i != nodes.Count - 1) + writer.Append(' '); } - /// - public CompiledQuery Compile(bool debug = false) - => CompileInternal(new CompileContext {Debug = debug}); - - /// - public ValueTask CompileAsync(IEdgeDBQueryable edgedb, bool debug = false, CancellationToken token = default) - => IntrospectAndCompileAsync(edgedb, debug, token); - - /// - /// Preforms introspection and then compiles this query builder into a . - /// - /// The client to preform introspection with. - /// Whether or not to produce a debug form of . - /// A cancellation token to cancel the introspection query. - /// - /// A ValueTask representing the (a)sync introspection and compiling operation. - /// The result is the compiled form of this query builder. - /// - private async ValueTask IntrospectAndCompileAsync(IEdgeDBQueryable edgedb, bool debug, CancellationToken token) + // create a with block if we have any globals + if (context.IncludeGlobalsInQuery && QueryGlobals.Any()) { - if (Nodes.Any(x => x.RequiresIntrospection) || QueryGlobals.Any(x => x.Value is SubQuery subQuery && subQuery.RequiresIntrospection)) - SchemaInfo ??= await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(edgedb, token).ConfigureAwait(false); + var with = (WithNode?)Nodes.FirstOrDefault(x => x is WithNode); - var result = Compile(debug); - Nodes.Clear(); - QueryGlobals.Clear(); + if (with is null) + { + var builder = new NodeBuilder(new WithContext(typeof(TType)), QueryGlobals, nodes, QueryVariables); - return result; - } + with = new WithNode(builder) {SchemaInfo = SchemaInfo}; - #region Generic sub-query methods - private QueryBuilder By(LambdaExpression selector) - { - if(CurrentUserNode is not GroupNode groupNode) - throw new InvalidOperationException($"Cannot add a 'by' expression on a {CurrentUserNode}"); + nodes = nodes.Prepend(with).ToList(); + } - groupNode.By(selector); - return this; - } + // visit the with node and add it to the front of our local collection of nodes. + using var _ = writer.PositionalScopeFromStart(); - private QueryBuilder Using(LambdaExpression expression) - where TNewContext : IQueryContextUsing - { - if(CurrentUserNode is not GroupNode groupNode) - throw new InvalidOperationException($"Cannot add a 'by' expression on a {CurrentUserNode}"); + writer.Term( + TermType.QueryNode, + with.GetType().Name, + null, + new QueryNodeMetadata(with), + Token.Of(writer => with.FinalizeQuery(writer)) + ); - groupNode.Using(expression); - return EnterNewContext(); + writer.Append(' '); } + // reduce the query + var shouldReduce = context is {RunReducers: true}; - /// - /// Adds a 'FILTER' statement to the current node. - /// - /// The filter lambda to add - /// The current builder. - /// - /// The current node doesn't support a filter statement. - /// - private QueryBuilder Filter(LambdaExpression filter) - { - switch (CurrentUserNode) - { - case SelectNode selectNode: - selectNode.Filter(filter); - break; - case UpdateNode updateNode: - updateNode.Filter(filter); - break; - default: - throw new InvalidOperationException($"Cannot filter on a {CurrentUserNode}"); - } - return this; - } + if (_state.RunReducers is not null) + shouldReduce = _state.RunReducers.Value; - /// - /// Adds a 'ORDER BY' statement to the current node. - /// - /// - /// if the ordered result should be ascending first. - /// - /// The lambda property selector on which to order by. - /// The placement for null values. - /// The current builder. - /// - /// The current node does not support order by statements - /// - private QueryBuilder OrderBy(bool asc, LambdaExpression selector, OrderByNullPlacement? placement) - { - if (CurrentUserNode is not SelectNode selectNode) - throw new InvalidOperationException($"Cannot order by on a {CurrentUserNode}"); + if (shouldReduce) + QueryReducer.Apply(this, writer); + } - selectNode.OrderBy(asc, selector, placement); + /// + /// Preforms introspection and then compiles this query builder into a . + /// + /// The client to preform introspection with. + /// Whether or not to produce a debug form of . + /// A cancellation token to cancel the introspection query. + /// + /// A ValueTask representing the (a)sync introspection and compiling operation. + /// The result is the compiled form of this query builder. + /// + private async ValueTask IntrospectAndCompileAsync(IEdgeDBQueryable edgedb, bool debug, + CancellationToken token) + { + if (Nodes.Any(x => x.RequiresIntrospection) || + QueryGlobals.Any(x => x.Value is SubQuery subQuery && subQuery.RequiresIntrospection)) + SchemaInfo ??= await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(edgedb, token) + .ConfigureAwait(false); - return this; - } + var result = Compile(debug); + Nodes.Clear(); + QueryGlobals.Clear(); - /// - /// Adds a 'OFFSET' statement to the current node. - /// - /// The amount to offset by. - /// The current builder. - /// - /// The current node does not support offset statements. - /// - private QueryBuilder Offset(long offset) - { - if (CurrentUserNode is not SelectNode selectNode) - throw new InvalidOperationException($"Cannot offset on a {CurrentUserNode}"); + return result; + } - selectNode.Offset(offset); + #region Generic sub-query methods - return this; - } + private QueryBuilder By(LambdaExpression selector) + { + if (CurrentUserNode is not GroupNode groupNode) + throw new InvalidOperationException($"Cannot add a 'by' expression on a {CurrentUserNode}"); - /// - /// Adds a 'OFFSET' statement to the current node. - /// - /// The lambda function of which the result is the amount to offset by. - /// The current builder. - /// - /// The current node does not support offset statements. - /// - private QueryBuilder OffsetExp(LambdaExpression offset) - { - if (CurrentUserNode is not SelectNode selectNode) - throw new InvalidOperationException($"Cannot offset on a {CurrentUserNode}"); + groupNode.By(selector); + return this; + } - selectNode.OffsetExpression(offset); + private QueryBuilder Using(LambdaExpression expression) + where TNewContext : IQueryContextUsing + { + if (CurrentUserNode is not GroupNode groupNode) + throw new InvalidOperationException($"Cannot add a 'by' expression on a {CurrentUserNode}"); + + groupNode.Using(expression); + return EnterNewContext(); + } - return this; - } - /// - /// Adds a 'LIMIT' statement to the current node. - /// - /// The amount to limit by. - /// The current builder. - /// - /// The current node does not support limit statements. - /// - private QueryBuilder Limit(long limit) + /// + /// Adds a 'FILTER' statement to the current node. + /// + /// The filter lambda to add + /// The current builder. + /// + /// The current node doesn't support a filter statement. + /// + private QueryBuilder Filter(LambdaExpression filter) + { + switch (CurrentUserNode) { - if (CurrentUserNode is not SelectNode selectNode) - throw new InvalidOperationException($"Cannot limit on a {CurrentUserNode}"); + case SelectNode selectNode: + selectNode.Filter(filter); + break; + case UpdateNode updateNode: + updateNode.Filter(filter); + break; + default: + throw new InvalidOperationException($"Cannot filter on a {CurrentUserNode}"); + } - selectNode.Limit(limit); + return this; + } - return this; - } + /// + /// Adds a 'ORDER BY' statement to the current node. + /// + /// + /// if the ordered result should be ascending first. + /// + /// The lambda property selector on which to order by. + /// The placement for null values. + /// The current builder. + /// + /// The current node does not support order by statements + /// + private QueryBuilder OrderBy(bool asc, LambdaExpression selector, OrderByNullPlacement? placement) + { + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot order by on a {CurrentUserNode}"); - /// - /// Adds a 'LIMIT' statement to the current node. - /// - /// The lambda function of which the result is the amount to limit by. - /// The current builder. - /// - /// The current node does not support limit statements. - /// - private QueryBuilder LimitExp(LambdaExpression limit) - { - if (CurrentUserNode is not SelectNode selectNode) - throw new InvalidOperationException($"Cannot limit on a {CurrentUserNode}"); + selectNode.OrderBy(asc, selector, placement); - selectNode.LimitExpression(limit); + return this; + } - return this; - } + /// + /// Adds a 'OFFSET' statement to the current node. + /// + /// The amount to offset by. + /// The current builder. + /// + /// The current node does not support offset statements. + /// + private QueryBuilder Offset(long offset) + { + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot offset on a {CurrentUserNode}"); - /// - /// Adds a 'UNLESS CONFLICT ON' statement to the current node. - /// - /// - /// This function causes the node to preform introspection. - /// - /// The current builder. - /// - /// The current node does not support unless conflict on statements. - /// - private QueryBuilder UnlessConflict() - { - if (CurrentUserNode is not InsertNode insertNode) - throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); + selectNode.Offset(offset); - insertNode.UnlessConflict(); + return this; + } - return this; - } + /// + /// Adds a 'OFFSET' statement to the current node. + /// + /// The lambda function of which the result is the amount to offset by. + /// The current builder. + /// + /// The current node does not support offset statements. + /// + private QueryBuilder OffsetExp(LambdaExpression offset) + { + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot offset on a {CurrentUserNode}"); - /// - /// Adds a 'UNLESS CONFLICT ON' statement to the current node. - /// - /// - /// The property selector of which to add the conflict expression to. - /// - /// The current builder. - /// - /// The current node does not support unless conflict on statements. - /// - private QueryBuilder UnlessConflictOn(LambdaExpression selector) - { - if (CurrentUserNode is not InsertNode insertNode) - throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); + selectNode.OffsetExpression(offset); + + return this; + } - insertNode.UnlessConflictOn(selector); + /// + /// Adds a 'LIMIT' statement to the current node. + /// + /// The amount to limit by. + /// The current builder. + /// + /// The current node does not support limit statements. + /// + private QueryBuilder Limit(long limit) + { + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot limit on a {CurrentUserNode}"); - return this; - } + selectNode.Limit(limit); - /// - /// Adds a 'ELSE (SELECT )' statement to the current node. - /// - /// The current builder. - /// - /// The current node does not support else statements. - /// - private QueryBuilder ElseReturnDefault() - { - if (CurrentUserNode is not InsertNode insertNode) - throw new InvalidOperationException($"Cannot else return on a {CurrentUserNode}"); + return this; + } - insertNode.ElseDefault(); + /// + /// Adds a 'LIMIT' statement to the current node. + /// + /// The lambda function of which the result is the amount to limit by. + /// The current builder. + /// + /// The current node does not support limit statements. + /// + private QueryBuilder LimitExp(LambdaExpression limit) + { + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot limit on a {CurrentUserNode}"); - return this; - } + selectNode.LimitExpression(limit); - /// - /// Adds a 'ELSE' statement to the current node. - /// - /// The query builder for the else statement. - /// A query builder representing an unknown return type. - /// - /// The current node does not support else statements - /// - private IQueryBuilder ElseJoint(IQueryBuilder builder) - { - if (CurrentUserNode is not InsertNode insertNode) - throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); + return this; + } - insertNode.Else(builder); + /// + /// Adds a 'UNLESS CONFLICT ON' statement to the current node. + /// + /// + /// This function causes the node to preform introspection. + /// + /// The current builder. + /// + /// The current node does not support unless conflict on statements. + /// + private QueryBuilder UnlessConflict() + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); - return EnterNewType(); - } + insertNode.UnlessConflict(); - /// - /// Adds a 'ELSE' statement to the current node. - /// - /// - /// A function that returns a multi-cardinality query from the provided builder. - /// - /// The current builder. - /// - /// The current node does not support else statements. - /// - private QueryBuilder Else(Func, IMultiCardinalityQuery> func) - { - if (CurrentUserNode is not InsertNode insertNode) - throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); + return this; + } - var builder = new QueryBuilder(_state with - { - Nodes = new(), - Variables = new() - }); + /// + /// Adds a 'UNLESS CONFLICT ON' statement to the current node. + /// + /// + /// The property selector of which to add the conflict expression to. + /// + /// The current builder. + /// + /// The current node does not support unless conflict on statements. + /// + private QueryBuilder UnlessConflictOn(LambdaExpression selector) + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); - func(builder); - insertNode.Else(builder); + insertNode.UnlessConflictOn(selector); - return this; - } + return this; + } - /// - /// Adds a 'ELSE' statement to the current node. - /// - /// - /// A function that returns a single-cardinality query from the provided builder. - /// - /// The current builder. - /// - /// The current node does not support else statements. - /// - private QueryBuilder Else(Func, ISingleCardinalityQuery> func) - { - if (CurrentUserNode is not InsertNode insertNode) - throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); + /// + /// Adds a 'ELSE (SELECT )' statement to the current node. + /// + /// The current builder. + /// + /// The current node does not support else statements. + /// + private QueryBuilder ElseReturnDefault() + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot else return on a {CurrentUserNode}"); - var builder = new QueryBuilder(_state with - { - Nodes = new(), - Variables = new() - }); - func(builder); - insertNode.Else(builder); + insertNode.ElseDefault(); - return this; - } + return this; + } - #endregion + /// + /// Adds a 'ELSE' statement to the current node. + /// + /// The query builder for the else statement. + /// A query builder representing an unknown return type. + /// + /// The current node does not support else statements + /// + private IQueryBuilder ElseJoint(IQueryBuilder builder) + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); - /// - async Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, - Capabilities? capabilities, CancellationToken token) - { - var result = await IntrospectAndCompileAsync(edgedb, false, token).ConfigureAwait(false); - return await edgedb.QueryAsync(result.Query, result.RawVariables, capabilities, token).ConfigureAwait(false); - } + insertNode.Else(builder); - /// - async Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, - Capabilities? capabilities, CancellationToken token) - { - var result = await IntrospectAndCompileAsync(edgedb, false, token).ConfigureAwait(false); - return await edgedb.QuerySingleAsync(result.Query, result.RawVariables, capabilities, token).ConfigureAwait(false); - } + return EnterNewType(); + } + + /// + /// Adds a 'ELSE' statement to the current node. + /// + /// + /// A function that returns a multi-cardinality query from the provided builder. + /// + /// The current builder. + /// + /// The current node does not support else statements. + /// + private QueryBuilder Else(Func, IMultiCardinalityQuery> func) + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); - /// - async Task IMultiCardinalityExecutable.ExecuteSingleAsync(IEdgeDBQueryable edgedb, Capabilities? capabilities, CancellationToken token) + var builder = new QueryBuilder(_state with { - var result = await IntrospectAndCompileAsync(edgedb, false, token).ConfigureAwait(false); - return await edgedb.QuerySingleAsync(result.Query, result.RawVariables, capabilities, token).ConfigureAwait(false); - } + Nodes = new List(), Variables = new Dictionary() + }); + + func(builder); + insertNode.Else(builder); + + return this; + } + + /// + /// Adds a 'ELSE' statement to the current node. + /// + /// + /// A function that returns a single-cardinality query from the provided builder. + /// + /// The current builder. + /// + /// The current node does not support else statements. + /// + private QueryBuilder Else( + Func, ISingleCardinalityQuery> func) + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); - /// - async Task IMultiCardinalityExecutable.ExecuteRequiredSingleAsync(IEdgeDBQueryable edgedb, Capabilities? capabilities, CancellationToken token) + var builder = new QueryBuilder(_state with { - var result = await IntrospectAndCompileAsync(edgedb, false, token).ConfigureAwait(false); - return await edgedb.QueryRequiredSingleAsync(result.Query, result.RawVariables, capabilities, token).ConfigureAwait(false); - } + Nodes = new List(), Variables = new Dictionary() + }); + func(builder); + insertNode.Else(builder); + return this; + } - #region IQueryBuilder + #endregion - SchemaInfo? IQueryBuilder.SchemaInfo => SchemaInfo; - IReadOnlyCollection IQueryBuilder.Nodes => Nodes; - List IQueryBuilder.Globals => QueryGlobals; - Dictionary IQueryBuilder.Variables => QueryVariables; - IQueryBuilder IQueryBuilder.SetShouldOptimizeQuery(bool? value) => SetShouldOptimizeQuery(value); + #region IQueryBuilder - void IQueryBuilder.CompileInternal(QueryWriter writer, CompileContext? context) => - CompileInternal(writer, context); + SchemaInfo? IQueryBuilder.SchemaInfo => SchemaInfo; + IReadOnlyCollection IQueryBuilder.Nodes => Nodes; + List IQueryBuilder.Globals => QueryGlobals; + Dictionary IQueryBuilder.Variables => QueryVariables; - #endregion - } + IQueryBuilder IQueryBuilder.SetShouldOptimizeQuery(bool? value) => SetShouldOptimizeQuery(value); + + void IQueryBuilder.CompileInternal(QueryWriter writer, CompileContext? context) => + CompileInternal(writer, context); + #endregion } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/CompileContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/CompileContext.cs index 89af14ff..3bbde2a6 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/CompileContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/CompileContext.cs @@ -12,9 +12,8 @@ internal sealed record CompileContext public bool Debug { get; init; } public SchemaInfo? SchemaInfo { get; init; } - public static CompileContext SubQueryContext(SchemaInfo? schemaInfo, Action? finalizer, bool debug) - { - return new CompileContext() + public static CompileContext SubQueryContext(SchemaInfo? schemaInfo, Action? finalizer, bool debug) => + new() { SchemaInfo = schemaInfo, RunReducers = false, @@ -23,5 +22,4 @@ public static CompileContext SubQueryContext(SchemaInfo? schemaInfo, Action + public static IDeleteQuery> Delete() + => new QueryBuilder().Delete(); +} + +public partial class QueryBuilder +{ + /// + public IDeleteQuery Delete() { - /// - public static IDeleteQuery> Delete() - => new QueryBuilder().Delete(); + AddNode(new DeleteContext(typeof(TType))); + return this; } - public partial class QueryBuilder + /// + public IDeleteQuery Delete() + { + AddNode(new DeleteContext(typeof(TNewType))); + return EnterNewType(); + } + + IDeleteQuery IQueryBuilder.DeleteInternal() + => DeleteInternal(); + + IDeleteQuery IDeleteQuery.Filter(Expression> filter) + => Filter(filter); + + IDeleteQuery IDeleteQuery.OrderBy(Expression> propertySelector, + OrderByNullPlacement? nullPlacement) + => OrderBy(true, propertySelector, nullPlacement); + + IDeleteQuery IDeleteQuery.OrderByDescending( + Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(false, propertySelector, nullPlacement); + + IDeleteQuery IDeleteQuery.Offset(long offset) + => Offset(offset); + + IDeleteQuery IDeleteQuery.Limit(long limit) + => Limit(limit); + + IDeleteQuery IDeleteQuery.Filter(Expression> filter) + => Filter(filter); + + IDeleteQuery IDeleteQuery.OrderBy( + Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(true, propertySelector, nullPlacement); + + IDeleteQuery IDeleteQuery.OrderByDescending( + Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(false, propertySelector, nullPlacement); + + IDeleteQuery IDeleteQuery.Offset(Expression> offset) + => OffsetExp(offset); + + IDeleteQuery IDeleteQuery.Limit(Expression> limit) + => LimitExp(limit); + + public IDeleteQuery DeleteInternal() + where TNewContext : IQueryContext { - /// - public IDeleteQuery Delete() - { - AddNode(new DeleteContext(typeof(TType))); - return this; - } - - /// - public IDeleteQuery Delete() - { - AddNode(new DeleteContext(typeof(TNewType))); - return EnterNewType(); - } - - public IDeleteQuery DeleteInternal() - where TNewContext : IQueryContext - { - AddNode(new DeleteContext(typeof(TNewType))); - return EnterNewType().EnterNewContext(); - } - - IDeleteQuery IQueryBuilder.DeleteInternal() - => DeleteInternal(); - - IDeleteQuery IDeleteQuery.Filter(Expression> filter) - => Filter(filter); - IDeleteQuery IDeleteQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) - => OrderBy(true, propertySelector, nullPlacement); - IDeleteQuery IDeleteQuery.OrderByDescending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) - => OrderBy(false, propertySelector, nullPlacement); - IDeleteQuery IDeleteQuery.Offset(long offset) - => Offset(offset); - IDeleteQuery IDeleteQuery.Limit(long limit) - => Limit(limit); - IDeleteQuery IDeleteQuery.Filter(Expression> filter) - => Filter(filter); - IDeleteQuery IDeleteQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) - => OrderBy(true, propertySelector, nullPlacement); - IDeleteQuery IDeleteQuery.OrderByDescending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) - => OrderBy(false, propertySelector, nullPlacement); - IDeleteQuery IDeleteQuery.Offset(Expression> offset) - => OffsetExp(offset); - IDeleteQuery IDeleteQuery.Limit(Expression> limit) - => LimitExp(limit); + AddNode(new DeleteContext(typeof(TNewType))); + return EnterNewType().EnterNewContext(); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.For.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.For.cs index 63554049..f4d648e2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.For.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.For.cs @@ -1,61 +1,51 @@ using EdgeDB.Interfaces; using EdgeDB.QueryNodes; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +public static partial class QueryBuilder +{ + /// + public static IMultiCardinalityExecutable For(IEnumerable collection, + Expression, IQuery>> iterator) + => new QueryBuilder().For(collection, iterator); + + /// + public static IMultiCardinalityExecutable For( + Expression>> collection, + Expression, IQuery>> iterator) + => new QueryBuilder().For(collection, iterator); +} + +public partial class QueryBuilder { - public static partial class QueryBuilder + public IMultiCardinalityExecutable For(IEnumerable collection, + Expression, IQuery>> iterator) { - /// - public static IMultiCardinalityExecutable For(IEnumerable collection, - Expression, IQuery>> iterator) - => new QueryBuilder().For(collection, iterator); - - /// - public static IMultiCardinalityExecutable For( - Expression>> collection, - Expression, IQuery>> iterator) - => new QueryBuilder().For(collection, iterator); + AddNode(new ForContext(typeof(TNew)) {Expression = iterator, Set = collection}); + + return EnterNewType(); } - public partial class QueryBuilder + public IMultiCardinalityExecutable For(Expression>> collection, + Expression, IQuery>> iterator) { - public IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQuery>> iterator) - { - AddNode(new ForContext(typeof(TNew)) - { - Expression = iterator, - Set = collection - }); - - return EnterNewType(); - } - - public IMultiCardinalityExecutable For(Expression>> collection, Expression, IQuery>> iterator) - { - AddNode(new ForContext(typeof(TNew)) - { - Expression = iterator, - SetExpression = collection - }); - - return EnterNewType(); - } - - // public IMultiCardinalityExecutable For(LambdaExpression collection, Expression, IQueryBuilder>> iterator) - // { - // AddNode(new ForContext(typeof(TType)) - // { - // Expression = iterator, - // SetExpression = collection - // }); - // - // return this; - // } + AddNode(new ForContext(typeof(TNew)) {Expression = iterator, SetExpression = collection}); + + return EnterNewType(); } + + // public IMultiCardinalityExecutable For(LambdaExpression collection, Expression, IQueryBuilder>> iterator) + // { + // AddNode(new ForContext(typeof(TType)) + // { + // Expression = iterator, + // SetExpression = collection + // }); + // + // return this; + // } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Group.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Group.cs index 50795d94..91742e8a 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Group.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Group.cs @@ -2,89 +2,86 @@ using EdgeDB.Interfaces; using EdgeDB.Interfaces.Queries; using EdgeDB.QueryNodes; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +public static partial class QueryBuilder { - public static partial class QueryBuilder - { - public static IGroupQuery> Group() - => new QueryBuilder().GroupInternal>(); + public static IGroupQuery> Group() + => new QueryBuilder().GroupInternal>(); - public static IGroupQuery> Group(Action> shape) - => new QueryBuilder().GroupInternal>(shape: shape); + public static IGroupQuery> Group(Action> shape) + => new QueryBuilder().GroupInternal>(shape: shape); - public static IGroupQuery> Group(Expression> selector) - => new QueryBuilder().GroupInternal>(selector); + public static IGroupQuery> Group(Expression> selector) + => new QueryBuilder().GroupInternal>(selector); - public static IGroupQuery> Group(Expression> selector) - => new QueryBuilder().GroupInternal>(selector); + public static IGroupQuery> Group(Expression> selector) + => new QueryBuilder().GroupInternal>(selector); - public static IGroupQuery> Group(Expression> selector, Action> shape) - => new QueryBuilder().GroupInternal>(selector, shape); + public static IGroupQuery> Group(Expression> selector, + Action> shape) + => new QueryBuilder().GroupInternal>(selector, shape); - public static IGroupQuery> Group(Expression> selector, Action> shape) - => new QueryBuilder().GroupInternal>(selector, shape); - } + public static IGroupQuery> Group(Expression> selector, + Action> shape) + => new QueryBuilder().GroupInternal>(selector, shape); +} - public partial class QueryBuilder - { - internal IGroupQuery GroupInternal(LambdaExpression? selector = null, - Action>? shape = null) - where TNewContext : IQueryContext - { - ShapeBuilder? shapeBuilder = shape is not null ? new() : null; - shape?.Invoke(shapeBuilder!); +public partial class QueryBuilder +{ + public IGroupQuery Group() + => GroupInternal(); - AddNode(new GroupContext(typeof(TType)) - { - Selector = selector, - Shape = shapeBuilder - }); + public IGroupQuery Group(Action> shape) + => GroupInternal(shape: shape); - return EnterNewType().EnterNewContext(); - } + IGroupQuery IQueryBuilder.GroupInternal( + LambdaExpression? selector, + Action>? shape) + => GroupInternal(selector, shape); - public IGroupQuery Group() - => GroupInternal(); + IGroupQuery IQueryBuilder.Group(Expression> selector) + => GroupInternal(selector); - public IGroupQuery Group(Action> shape) - => GroupInternal(shape: shape); + IGroupQuery IQueryBuilder.Group( + Expression> selector) + => GroupInternal(selector); - IGroupQuery IQueryBuilder.GroupInternal( - LambdaExpression? selector, - Action>? shape) - => GroupInternal(selector, shape); + IGroupQuery IQueryBuilder.Group(Expression> selector, + Action> shape) + => GroupInternal(selector, shape); - IGroupQuery IQueryBuilder.Group(Expression> selector) - => GroupInternal(selector); + IGroupQuery IQueryBuilder.Group( + Expression> selector, Action> shape) + => GroupInternal(selector, shape); - IGroupQuery IQueryBuilder.Group(Expression> selector) - => GroupInternal(selector); + IMultiCardinalityExecutable> IGroupQuery.By( + Expression> selector) + => By(selector).EnterNewType>(); - IGroupQuery IQueryBuilder.Group(Expression> selector, Action> shape) - => GroupInternal(selector, shape); + IMultiCardinalityExecutable> IGroupQuery.By( + Expression> selector) + => By(selector).EnterNewType>(); - IGroupQuery IQueryBuilder.Group(Expression> selector, Action> shape) - => GroupInternal(selector, shape); + IGroupUsingQuery IGroupQuery.UsingInternal( + LambdaExpression expression) + => Using(expression); - IMultiCardinalityExecutable> IGroupQuery.By(Expression> selector) - => By(selector).EnterNewType>(); + IMultiCardinalityExecutable> IGroupUsingQuery.By( + Expression> selector) + => By(selector).EnterNewType>(); - IMultiCardinalityExecutable> IGroupQuery.By(Expression> selector) - => By(selector).EnterNewType>(); + internal IGroupQuery GroupInternal(LambdaExpression? selector = null, + Action>? shape = null) + where TNewContext : IQueryContext + { + ShapeBuilder? shapeBuilder = shape is not null ? new ShapeBuilder() : null; + shape?.Invoke(shapeBuilder!); - IGroupUsingQuery IGroupQuery.UsingInternal( - LambdaExpression expression) - => Using(expression); + AddNode(new GroupContext(typeof(TType)) {Selector = selector, Shape = shapeBuilder}); - IMultiCardinalityExecutable> IGroupUsingQuery.By( - Expression> selector) - => By(selector).EnterNewType>(); + return EnterNewType().EnterNewContext(); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Insert.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Insert.cs index 4cadcfc7..9727fa49 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Insert.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Insert.cs @@ -1,143 +1,146 @@ using EdgeDB.Interfaces; using EdgeDB.Interfaces.Queries; using EdgeDB.QueryNodes; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB -{ - public static partial class QueryBuilder - { +namespace EdgeDB; - /// - public static IInsertQuery> Insert(Expression, TType>> value, bool returnInsertedValue) - => new QueryBuilder().Insert(value, returnInsertedValue); +public static partial class QueryBuilder +{ + /// + public static IInsertQuery> Insert( + Expression, TType>> value, bool returnInsertedValue) + => new QueryBuilder().Insert(value, returnInsertedValue); - /// - public static IInsertQuery> Insert(Expression, TType>> value) - => new QueryBuilder().Insert(value); + /// + public static IInsertQuery> Insert( + Expression, TType>> value) + => new QueryBuilder().Insert(value); - /// - public static IInsertQuery> Insert(Expression> value) - => new QueryBuilder().Insert(value); + /// + public static IInsertQuery> Insert(Expression> value) + => new QueryBuilder().Insert(value); - /// - public static IInsertQuery> Insert(TType value, bool returnInsertedValue) - => new QueryBuilder().Insert(value, returnInsertedValue); + /// + public static IInsertQuery> Insert(TType value, bool returnInsertedValue) + => new QueryBuilder().Insert(value, returnInsertedValue); - /// - public static IInsertQuery> Insert(TType value) - where TType : class - => new QueryBuilder().Insert(value, false); + /// + public static IInsertQuery> Insert(TType value) + where TType : class + => new QueryBuilder().Insert(value, false); - public static IInsertQuery> Insert(Type type, IDictionary values) - => new QueryBuilder().Insert(type, values); + public static IInsertQuery> Insert(Type type, IDictionary values) + => new QueryBuilder().Insert(type, values); - public static IInsertQuery> Insert(Type type, TType value) - => new QueryBuilder().Insert(type, value, false); + public static IInsertQuery> Insert(Type type, TType value) + => new QueryBuilder().Insert(type, value, false); - public static IInsertQuery> Insert(Type type, TType value, bool returnInsertedValue) - => new QueryBuilder().Insert(type, value, returnInsertedValue); + public static IInsertQuery> Insert(Type type, TType value, + bool returnInsertedValue) + => new QueryBuilder().Insert(type, value, returnInsertedValue); - public static IInsertQuery> Insert(Type type, IDictionary values, bool returnInsertedValue) - => new QueryBuilder().Insert(type, values, returnInsertedValue); + public static IInsertQuery> Insert(Type type, IDictionary values, + bool returnInsertedValue) + => new QueryBuilder().Insert(type, values, returnInsertedValue); - public static IInsertQuery> Insert(Type type, Expression> shape) - => new QueryBuilder().Insert(type, shape); + public static IInsertQuery> Insert(Type type, Expression> shape) + => new QueryBuilder().Insert(type, shape); - public static IInsertQuery> Insert(Type type, Expression> shape) - => new QueryBuilder().Insert(type, shape); - } + public static IInsertQuery> Insert(Type type, Expression> shape) + => new QueryBuilder().Insert(type, shape); +} - public partial class QueryBuilder +public partial class QueryBuilder +{ + /// + public IInsertQuery Insert(TNew value, bool returnInsertedValue = true) { - public IInsertQuery Insert(Type type, LambdaExpression expression, - bool returnInsertedValue = true) - { - var insertNode = AddNode(new InsertContext(type, expression)); - - if (returnInsertedValue) - { - AddNode(new SelectContext(type), true, insertNode); - } + var insertNode = AddNode(new InsertContext(typeof(TNew), value)); - return this; + if (returnInsertedValue) + { + AddNode(new SelectContext(typeof(TNew)), true, insertNode); } - public IInsertQuery Insert(Type type, IDictionary values, bool returnInsertedValue = true) - { - var insertNode = AddNode(new InsertContext(type, InsertNode.InsertValue.FromRaw(type, values))); + return EnterNewType(); + } - if (returnInsertedValue) - { - AddNode(new SelectContext(type), true, insertNode); - } + /// + public IInsertQuery Insert(TNew value) + => Insert(value, false); - return this; - } + /// + public IInsertQuery Insert(Expression> value, + bool returnInsertedValue = true) + { + var insertNode = AddNode(new InsertContext(typeof(TNew), value)); - /// - public IInsertQuery Insert(TNew value, bool returnInsertedValue = true) + if (returnInsertedValue) { - var insertNode = AddNode(new InsertContext(typeof(TNew), value)); + AddNode(new SelectContext(typeof(TNew)), true, insertNode); + } + + return EnterNewType(); + } - if (returnInsertedValue) - { - AddNode(new SelectContext(typeof(TNew)), true, insertNode); - } + /// + public IInsertQuery Insert(Expression> value) + => Insert(value, false); - return EnterNewType(); - } + IUnlessConflictOn IInsertQuery.UnlessConflict() + => UnlessConflict(); - /// - public IInsertQuery Insert(Type type, object? value, bool returnInsertedValue = true) - { - var insertNode = AddNode(new InsertContext(type, value)); + IUnlessConflictOn IInsertQuery.UnlessConflictOn( + Expression> propertySelector) + => UnlessConflictOn(propertySelector); + + IUnlessConflictOn IInsertQuery.UnlessConflictOn( + Expression> propertySelector) + => UnlessConflictOn(propertySelector); - if (returnInsertedValue) - { - AddNode(new SelectContext(type), true, insertNode); - } + public IInsertQuery Insert(Type type, LambdaExpression expression, + bool returnInsertedValue = true) + { + var insertNode = AddNode(new InsertContext(type, expression)); - return this; + if (returnInsertedValue) + { + AddNode(new SelectContext(type), true, insertNode); } - /// - public IInsertQuery Insert(TNew value) - => Insert(value, false); + return this; + } - /// - public IInsertQuery Insert(Expression> value, bool returnInsertedValue = true) + public IInsertQuery Insert(Type type, IDictionary values, + bool returnInsertedValue = true) + { + var insertNode = AddNode(new InsertContext(type, InsertNode.InsertValue.FromRaw(type, values))); + + if (returnInsertedValue) { - var insertNode = AddNode(new InsertContext(typeof(TNew), value)); + AddNode(new SelectContext(type), true, insertNode); + } - if (returnInsertedValue) - { - AddNode(new SelectContext(typeof(TNew)), true, insertNode); - } + return this; + } - return EnterNewType(); - } + /// + public IInsertQuery Insert(Type type, object? value, bool returnInsertedValue = true) + { + var insertNode = AddNode(new InsertContext(type, value)); - public IInsertQuery Insert(Expression> value) + if (returnInsertedValue) { - AddNode(new InsertContext(typeof(TNew), value)); - return EnterNewType(); + AddNode(new SelectContext(type), true, insertNode); } - /// - public IInsertQuery Insert(Expression> value) - => Insert(value, false); + return this; + } - IUnlessConflictOn IInsertQuery.UnlessConflict() - => UnlessConflict(); - IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) - => UnlessConflictOn(propertySelector); - IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) - => UnlessConflictOn(propertySelector); + public IInsertQuery Insert(Expression> value) + { + AddNode(new InsertContext(typeof(TNew), value)); + return EnterNewType(); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Select.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Select.cs index 9db921cf..613038d8 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Select.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Select.cs @@ -1,232 +1,229 @@ using EdgeDB.Builders; using EdgeDB.Interfaces.Queries; using EdgeDB.QueryNodes; -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +public static partial class QueryBuilder +{ + /// + public static ISelectQuery> Select() + => new QueryBuilder().Select(); + + /// + public static ISelectQuery> Select(Action> shape) + => new QueryBuilder().Select(shape); + + /// + /// Adds a SELECT statement, selecting the result of a . + /// + /// The resulting type of the expression. + /// The expression on which to select. + /// + /// A . + /// + public static ISelectQuery> SelectExpression( + Expression> expression) + => new QueryBuilder().SelectExpression(expression); + + /// + /// Adds a SELECT statement, selecting the result of a . + /// + /// The resulting type of the expression. + /// The expression on which to select. + /// + /// A . + /// + public static ISelectQuery> SelectExpression( + Expression> expression) + => new QueryBuilder().SelectExp(expression); +} + +public partial class QueryBuilder { - public static partial class QueryBuilder + /// + public ISelectQuery Select() { - /// - public static ISelectQuery> Select() - => new QueryBuilder().Select(); - - /// - public static ISelectQuery> Select(Action> shape) - => new QueryBuilder().Select(shape); - - /// - /// Adds a SELECT statement, selecting the result of a . - /// - /// The resulting type of the expression. - /// The expression on which to select. - /// - /// A . - /// - public static ISelectQuery> SelectExpression(Expression> expression) - => new QueryBuilder().SelectExpression(expression); - - /// - /// Adds a SELECT statement, selecting the result of a . - /// - /// The resulting type of the expression. - /// The expression on which to select. - /// - /// A . - /// - public static ISelectQuery> SelectExpression(Expression> expression) - => new QueryBuilder().SelectExp(expression); + AddNode(new SelectContext(typeof(TType))); + return this; } - public partial class QueryBuilder + /// + public ISelectQuery Select() { - /// - public ISelectQuery Select() - { - AddNode(new SelectContext(typeof(TType))); - return this; - } + AddNode(new SelectContext(typeof(TResult))); + return EnterNewType(); + } - /// - public ISelectQuery Select() + /// + public ISelectQuery Select(Action> shape) + { + var shapeBuilder = new ShapeBuilder(); + shape(shapeBuilder); + AddNode(new SelectContext(typeof(TResult)) { - AddNode(new SelectContext(typeof(TResult))); - return EnterNewType(); - } + Shape = shapeBuilder, IsFreeObject = typeof(TResult).IsAnonymousType() + }); + return EnterNewType(); + } - /// - public ISelectQuery Select(Action> shape) + /// + /// Adds a SELECT statement, selecting the result of a . + /// + /// The resulting type of the expression. + /// The expression on which to select. + /// + /// A . + /// + public ISelectQuery SelectExpression(Expression> expression) + { + AddNode(new SelectContext(typeof(TType)) { - var shapeBuilder = new ShapeBuilder(); - shape(shapeBuilder); - AddNode(new SelectContext(typeof(TResult)) - { - Shape = shapeBuilder, - IsFreeObject = typeof(TResult).IsAnonymousType() - }); - return EnterNewType(); - } + Expression = expression, IncludeShape = false, IsFreeObject = typeof(TNewType).IsAnonymousType() + }); + return EnterNewType(); + } - internal ISelectQuery SelectInternal(Action>? shape) - where TNewContext : IQueryContext + /// + /// Adds a SELECT statement, selecting the result of a . + /// + /// The resulting type of the expression. + /// The expression on which to select. + /// + /// A . + /// + public ISelectQuery SelectExpression(Expression> expression) + { + AddNode(new SelectContext(typeof(TType)) { - ShapeBuilder? shapeBuilder = null; - - if (shape is not null) - { - shape(shapeBuilder = new()); - } - - AddNode(new SelectContext(typeof(TNew)) - { - Shape = shapeBuilder, - IsFreeObject = typeof(TNew).IsAnonymousType() - }); - return EnterNewType().EnterNewContext(); - } + Expression = expression, IncludeShape = false, IsFreeObject = typeof(TNewType).IsAnonymousType() + }); + return EnterNewType(); + } - /// - /// Adds a SELECT statement, selecting the result of a . - /// - /// The resulting type of the expression. - /// The expression on which to select. - /// - /// A . - /// - public ISelectQuery SelectExpression(Expression> expression) - { - AddNode(new SelectContext(typeof(TType)) - { - Expression = expression, - IncludeShape = false, - IsFreeObject = typeof(TNewType).IsAnonymousType(), - }); - return EnterNewType(); - } + ISelectQuery IQueryBuilder.SelectInternal( + Action>? shape) + => SelectInternal(shape); - /// - /// Adds a SELECT statement, selecting the result of a . - /// - /// The resulting type of the expression. - /// The expression on which to select. - /// - /// A . - /// - public ISelectQuery SelectExpression(Expression> expression) - { - AddNode(new SelectContext(typeof(TType)) - { - Expression = expression, - IncludeShape = false, - IsFreeObject = typeof(TNewType).IsAnonymousType(), - }); - return EnterNewType(); - } + ISelectQuery IQueryBuilder.SelectExpression( + Expression> expression, Action>? shape) + => SelectExp(expression, shape); + ISelectQuery IQueryBuilder.SelectExpression( + Expression> expression, + Func, ShapeBuilder>? shape) + => SelectExp(expression, shape); - /// - /// Adds a SELECT statement, selecting the result of a . - /// - /// The resulting type of the expression. - /// The expression on which to select. - /// A optional delegate to build the shape for selecting . - /// - /// A . - /// - public ISelectQuery SelectExp( - Expression> expression, - Action>? shape = null - ) - { - var shapeBuilder = shape is not null ? new ShapeBuilder() : null; + ISelectQuery ISelectQuery.Filter(Expression> filter) + => Filter(filter); + + ISelectQuery ISelectQuery.OrderBy(Expression> propertySelector, + OrderByNullPlacement? nullPlacement) + => OrderBy(true, propertySelector, nullPlacement); + + ISelectQuery ISelectQuery.OrderByDescending( + Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(false, propertySelector, nullPlacement); + + ISelectQuery ISelectQuery.Offset(long offset) + => Offset(offset); - if (shape is not null && shapeBuilder is not null) - { - shape(shapeBuilder); - } + ISelectQuery ISelectQuery.Limit(long limit) + => Limit(limit); - AddNode(new SelectContext(typeof(TType)) - { - Expression = expression, - Shape = shapeBuilder, - IsFreeObject = typeof(TNewType).IsAnonymousType(), - }); + ISelectQuery ISelectQuery.Filter(Expression> filter) + => Filter(filter); - return EnterNewType(); + ISelectQuery ISelectQuery.OrderBy( + Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(true, propertySelector, nullPlacement); + + ISelectQuery ISelectQuery.OrderByDescending( + Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(false, propertySelector, nullPlacement); + + ISelectQuery ISelectQuery.Offset(Expression> offset) + => OffsetExp(offset); + + ISelectQuery ISelectQuery.Limit(Expression> limit) + => LimitExp(limit); + + internal ISelectQuery SelectInternal(Action>? shape) + where TNewContext : IQueryContext + { + ShapeBuilder? shapeBuilder = null; + + if (shape is not null) + { + shape(shapeBuilder = new ShapeBuilder()); } - public ISelectQuery SelectExp( - Expression> expression, - Func, ShapeBuilder>? shape = null - ) + AddNode(new SelectContext(typeof(TNew)) { - var shapeBuilder = shape is not null ? new ShapeBuilder() : null; + Shape = shapeBuilder, IsFreeObject = typeof(TNew).IsAnonymousType() + }); + return EnterNewType().EnterNewContext(); + } - if (shape is not null && shapeBuilder is not null) - { - shape(shapeBuilder); - } - AddNode(new SelectContext(typeof(TType)) - { - Expression = expression, - Shape = shapeBuilder, - IsFreeObject = typeof(TShape).IsAnonymousType(), - }); + /// + /// Adds a SELECT statement, selecting the result of a . + /// + /// The resulting type of the expression. + /// The expression on which to select. + /// A optional delegate to build the shape for selecting . + /// + /// A . + /// + public ISelectQuery SelectExp( + Expression> expression, + Action>? shape = null + ) + { + var shapeBuilder = shape is not null ? new ShapeBuilder() : null; - return EnterNewType(); + if (shape is not null && shapeBuilder is not null) + { + shape(shapeBuilder); } + AddNode(new SelectContext(typeof(TType)) + { + Expression = expression, Shape = shapeBuilder, IsFreeObject = typeof(TNewType).IsAnonymousType() + }); + + return EnterNewType(); + } + + public ISelectQuery SelectExp( + Expression> expression, + Func, ShapeBuilder>? shape = null + ) + { + var shapeBuilder = shape is not null ? new ShapeBuilder() : null; - internal ISelectQuery SelectExp(Expression> expression) + if (shape is not null && shapeBuilder is not null) { - AddNode(new SelectContext(typeof(TType)) - { - Expression = expression, - IncludeShape = false, - IsFreeObject = typeof(TNewType).IsAnonymousType(), - }); - return EnterNewType(); + shape(shapeBuilder); } - ISelectQuery IQueryBuilder.SelectInternal( - Action>? shape) - => SelectInternal(shape); - - ISelectQuery IQueryBuilder.SelectExpression( - Expression> expression, Action>? shape) - => SelectExp(expression, shape); - - ISelectQuery IQueryBuilder.SelectExpression( - Expression> expression, - Func, ShapeBuilder>? shape) - => SelectExp(expression, shape); - - ISelectQuery ISelectQuery.Filter(Expression> filter) - => Filter(filter); - ISelectQuery ISelectQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) - => OrderBy(true, propertySelector, nullPlacement); - ISelectQuery ISelectQuery.OrderByDescending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) - => OrderBy(false, propertySelector, nullPlacement); - ISelectQuery ISelectQuery.Offset(long offset) - => Offset(offset); - ISelectQuery ISelectQuery.Limit(long limit) - => Limit(limit); - ISelectQuery ISelectQuery.Filter(Expression> filter) - => Filter(filter); - ISelectQuery ISelectQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) - => OrderBy(true, propertySelector, nullPlacement); - ISelectQuery ISelectQuery.OrderByDescending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) - => OrderBy(false, propertySelector, nullPlacement); - ISelectQuery ISelectQuery.Offset(Expression> offset) - => OffsetExp(offset); - ISelectQuery ISelectQuery.Limit(Expression> limit) - => LimitExp(limit); + AddNode(new SelectContext(typeof(TType)) + { + Expression = expression, Shape = shapeBuilder, IsFreeObject = typeof(TShape).IsAnonymousType() + }); + + return EnterNewType(); + } + + + internal ISelectQuery SelectExp( + Expression> expression) + { + AddNode(new SelectContext(typeof(TType)) + { + Expression = expression, IncludeShape = false, IsFreeObject = typeof(TNewType).IsAnonymousType() + }); + return EnterNewType(); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.UnlessConflict.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.UnlessConflict.cs index be19ee1c..a95f75c7 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.UnlessConflict.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.UnlessConflict.cs @@ -1,21 +1,20 @@ using EdgeDB.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +public partial class QueryBuilder { - public partial class QueryBuilder - { - ISingleCardinalityExecutable IUnlessConflictOn.ElseReturn() - => ElseReturnDefault(); - IQueryBuilder IUnlessConflictOn.Else(TQueryBuilder elseQuery) - => ElseJoint(elseQuery); - IMultiCardinalityExecutable IUnlessConflictOn.Else(Func, IMultiCardinalityExecutable> elseQuery) - => Else(elseQuery); - ISingleCardinalityExecutable IUnlessConflictOn.Else(Func, ISingleCardinalityExecutable> elseQuery) - => Else(elseQuery); - } + ISingleCardinalityExecutable IUnlessConflictOn.ElseReturn() + => ElseReturnDefault(); + + IQueryBuilder IUnlessConflictOn.Else(TQueryBuilder elseQuery) + => ElseJoint(elseQuery); + + IMultiCardinalityExecutable IUnlessConflictOn.Else( + Func, IMultiCardinalityExecutable> elseQuery) + => Else(elseQuery); + + ISingleCardinalityExecutable IUnlessConflictOn.Else( + Func, ISingleCardinalityExecutable> elseQuery) + => Else(elseQuery); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Update.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Update.cs index d8c16d1b..4bd8811a 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Update.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.Update.cs @@ -1,100 +1,93 @@ using EdgeDB.Interfaces; using EdgeDB.Interfaces.Queries; using EdgeDB.QueryNodes; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB -{ - public static partial class QueryBuilder - { - public static IUpdateQuery> Update() - => new QueryBuilder().Update(null, false); - - public static IUpdateQuery> Update(bool returnUpdatedValue) - => new QueryBuilder().Update(null, returnUpdatedValue); - - public static IUpdateQuery> Update( - Expression> selector) - => new QueryBuilder().Update(selector, false); - - public static IUpdateQuery> Update( - Expression> selector, bool returnUpdatedValue) - => new QueryBuilder().Update(selector, returnUpdatedValue); - } +namespace EdgeDB; - public partial class QueryBuilder - { - /// - /// Adds a generic update node, with the specified update function and target selector. - /// - /// - /// The expression that selects the object to update. - /// - /// - /// Whether or not to implicitly add a SELECT node that selects the result of the update, - /// with a default shape. - /// - /// A . - internal IUpdateQuery Update(LambdaExpression? selector, bool returnUpdatedValue) - { - var updateNode = AddNode(new UpdateContext(typeof(TSelected)) - { - Selector = selector, - }); +public static partial class QueryBuilder +{ + public static IUpdateQuery> Update() + => new QueryBuilder().Update(null, false); - if (returnUpdatedValue) - { - AddNode(new SelectContext(typeof(TSelected)), true, updateNode); - } + public static IUpdateQuery> Update(bool returnUpdatedValue) + => new QueryBuilder().Update(null, returnUpdatedValue); - return EnterNewType(); - } + public static IUpdateQuery> Update( + Expression> selector) + => new QueryBuilder().Update(selector, false); - private IMultiCardinalityExecutable Set(IUpdateShapeBuilder shape) - { - if (CurrentUserNode is not UpdateNode updateNode) - throw new InvalidOperationException("Cannot 'set' on a node that isn't 'Update'"); + public static IUpdateQuery> Update( + Expression> selector, bool returnUpdatedValue) + => new QueryBuilder().Update(selector, returnUpdatedValue); +} - updateNode.Set(shape); +public partial class QueryBuilder +{ + IUpdateQuerySet IUpdateQuery.Filter( + Expression> filter) + => Filter(filter); - return this; - } + IUpdateQuerySet IUpdateQuery.Filter(Expression> filter) + => Filter(filter); - IUpdateQuerySet IUpdateQuery.Filter(Expression> filter) - => Filter(filter); + IMultiCardinalityExecutable IUpdateQuerySet.Set( + Expression> shape) + => Set(UpdateShapeBuilder.FromInitExpression(shape)); - IUpdateQuerySet IUpdateQuery.Filter(Expression> filter) - => Filter(filter); + IMultiCardinalityExecutable IUpdateQuerySet.Set( + Expression> shape) + => Set(UpdateShapeBuilder.FromInitExpression(shape)); - IMultiCardinalityExecutable IUpdateQuerySet.Set( - Expression> shape) - => Set(UpdateShapeBuilder.FromInitExpression(shape)); + IMultiCardinalityExecutable IUpdateQuerySet.Set( + Action> shape) + { + var builder = new UpdateShapeBuilder(); + shape(builder); + return Set(builder); + } - IMultiCardinalityExecutable IUpdateQuerySet.Set(Expression> shape) - => Set(UpdateShapeBuilder.FromInitExpression(shape)); + IUpdateQuery IQueryBuilder.Update( + Expression> selector, bool returnUpdatedValue) + => Update(selector, returnUpdatedValue); + + IUpdateQuery IQueryBuilder.Update( + Expression> selector) + => Update(selector, false); + + IUpdateQuery IQueryBuilder.Update() + => Update(null, false); + + /// + /// Adds a generic update node, with the specified update function and target selector. + /// + /// + /// The expression that selects the object to update. + /// + /// + /// Whether or not to implicitly add a SELECT node that selects the result of the update, + /// with a default shape. + /// + /// A . + internal IUpdateQuery Update(LambdaExpression? selector, bool returnUpdatedValue) + { + var updateNode = AddNode(new UpdateContext(typeof(TSelected)) {Selector = selector}); - IMultiCardinalityExecutable IUpdateQuerySet.Set( - Action> shape) + if (returnUpdatedValue) { - var builder = new UpdateShapeBuilder(); - shape(builder); - return Set(builder); + AddNode(new SelectContext(typeof(TSelected)), true, updateNode); } - IUpdateQuery IQueryBuilder.Update( - Expression> selector, bool returnUpdatedValue) - => Update(selector, returnUpdatedValue); + return EnterNewType(); + } + + private IMultiCardinalityExecutable Set(IUpdateShapeBuilder shape) + { + if (CurrentUserNode is not UpdateNode updateNode) + throw new InvalidOperationException("Cannot 'set' on a node that isn't 'Update'"); - IUpdateQuery IQueryBuilder.Update( - Expression> selector) - => Update(selector, false); + updateNode.Set(shape); - IUpdateQuery IQueryBuilder.Update() - => Update(null, false); + return this; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.With.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.With.cs index 9f687de0..219aded8 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.With.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder/QueryBuilder.With.cs @@ -1,116 +1,116 @@ +using EdgeDB.DataTypes; using EdgeDB.QueryNodes; -using EdgeDB.Translators.Expressions; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +public static partial class QueryBuilder { - public static partial class QueryBuilder - { - /// - public static IQueryBuilder> With(TVariables variables) - => new QueryBuilder>() - .With(variables).EnterNewContext>(); - - /// - public static IQueryBuilder> With( - Expression> variables) - => new QueryBuilder>() - .With(variables).EnterNewContext>(); // QueryBuilder.With(variables); - } + /// + public static IQueryBuilder> With(TVariables variables) + => new QueryBuilder>() + .With(variables).EnterNewContext>(); - public partial class QueryBuilder - { - public new static IQueryBuilder> With(TVariables variables) - => new QueryBuilder>().With(variables); + /// + public static IQueryBuilder> With( + Expression> variables) + => new QueryBuilder>() + .With(variables).EnterNewContext>(); // QueryBuilder.With(variables); +} - public static IQueryBuilder> With(Expression, TVariables>> variables) - => new QueryBuilder>().WithInternal(variables); - } +public partial class QueryBuilder +{ + public new static IQueryBuilder> With( + TVariables variables) + => new QueryBuilder>().With(variables); + + public static IQueryBuilder> With( + Expression, TVariables>> variables) + => new QueryBuilder>().WithInternal(variables); +} + +public partial class QueryBuilder +{ + IQueryBuilder> IQueryBuilder. + With(TVariables variables) => With(variables); + + IQueryBuilder> IQueryBuilder.With( + Expression, TVariables>> variables) => WithInternal(variables); - public partial class QueryBuilder + internal QueryBuilder> WithInternal( + LambdaExpression variables) { - internal QueryBuilder> WithInternal( - LambdaExpression variables) - { - if (variables is null) - throw new NullReferenceException("Variables cannot be null"); + if (variables is null) + throw new NullReferenceException("Variables cannot be null"); - // check if TVariables is an anonymous type - if (!typeof(TVariables).IsAnonymousType()) - throw new ArgumentException("Variables must be an anonymous type"); + // check if TVariables is an anonymous type + if (!typeof(TVariables).IsAnonymousType()) + throw new ArgumentException("Variables must be an anonymous type"); - AddNode(new WithContext(typeof(TType)) {ValuesExpression = variables}); + AddNode(new WithContext(typeof(TType)) {ValuesExpression = variables}); - return EnterNewContext>(); - } + return EnterNewContext>(); + } - public QueryBuilder> With( - Expression> variables) - => WithInternal(variables); + public QueryBuilder> With( + Expression> variables) + => WithInternal(variables); - public QueryBuilder> With(TVariables variables) - { - if (variables is null) - throw new NullReferenceException("Variables cannot be null"); + public QueryBuilder> With(TVariables variables) + { + if (variables is null) + throw new NullReferenceException("Variables cannot be null"); - // check if TVariables is an anonymous type - if (!typeof(TVariables).IsAnonymousType()) - throw new ArgumentException("Variables must be an anonymous type"); + // check if TVariables is an anonymous type + if (!typeof(TVariables).IsAnonymousType()) + throw new ArgumentException("Variables must be an anonymous type"); - // add the properties to our query variables & globals - foreach (var property in typeof(TVariables).GetProperties()) + // add the properties to our query variables & globals + foreach (var property in typeof(TVariables).GetProperties()) + { + var value = property.GetValue(variables); + // if its scalar, just add it as a query variable + if (EdgeDBTypeUtils.TryGetScalarType(property.PropertyType, out var scalarInfo)) { - var value = property.GetValue(variables); - // if its scalar, just add it as a query variable - if (EdgeDBTypeUtils.TryGetScalarType(property.PropertyType, out var scalarInfo)) - { - var varName = QueryUtils.GenerateRandomVariableName(); - QueryVariables.Add(varName, value); - QueryGlobals.Add( - new QueryGlobal( - property.Name, - new SubQuery(writer => writer - .QueryArgument(scalarInfo.ToString(), varName) - ) + var varName = QueryUtils.GenerateRandomVariableName(); + QueryVariables.Add(varName, value); + QueryGlobals.Add( + new QueryGlobal( + property.Name, + new SubQuery(writer => writer + .QueryArgument(scalarInfo.ToString(), varName) ) - ); - } - else if (property.PropertyType.IsAssignableTo(typeof(IQueryBuilder))) - { - // add it as a sub-query - QueryGlobals.Add(new QueryGlobal(property.Name, value)); - } - // TODO: revisit references - //else if ( - // EdgeDBTypeUtils.IsLink(property.PropertyType, out var isMultiLink, out var innerType) - // && !isMultiLink - // && QueryObjectManager.TryGetObjectId(value, out var id)) - //{ - // _queryGlobals.Add(new QueryGlobal(property.Name, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = '{id}')"))); - //} - else if (ReflectionUtils.IsSubclassOfRawGeneric(typeof(JsonReferenceVariable<>), property.PropertyType)) - { - // serialize and add as global and variable - var referenceValue = property.PropertyType.GetProperty("Value")!.GetValue(value); - var jsonVarName = QueryUtils.GenerateRandomVariableName(); - QueryVariables.Add(jsonVarName, DataTypes.Json.Serialize(referenceValue)); - QueryGlobals.Add(new QueryGlobal(property.Name, new SubQuery(writer => writer - .QueryArgument("json", jsonVarName) - ), value)); - } - else - throw new InvalidOperationException($"Cannot serialize {property.Name}: No serialization strategy found for {property.PropertyType}"); + ) + ); } - - return EnterNewContext>(); + else if (property.PropertyType.IsAssignableTo(typeof(IQueryBuilder))) + { + // add it as a sub-query + QueryGlobals.Add(new QueryGlobal(property.Name, value)); + } + // TODO: revisit references + //else if ( + // EdgeDBTypeUtils.IsLink(property.PropertyType, out var isMultiLink, out var innerType) + // && !isMultiLink + // && QueryObjectManager.TryGetObjectId(value, out var id)) + //{ + // _queryGlobals.Add(new QueryGlobal(property.Name, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = '{id}')"))); + //} + else if (ReflectionUtils.IsSubclassOfRawGeneric(typeof(JsonReferenceVariable<>), property.PropertyType)) + { + // serialize and add as global and variable + var referenceValue = property.PropertyType.GetProperty("Value")!.GetValue(value); + var jsonVarName = QueryUtils.GenerateRandomVariableName(); + QueryVariables.Add(jsonVarName, Json.Serialize(referenceValue)); + QueryGlobals.Add(new QueryGlobal(property.Name, new SubQuery(writer => writer + .QueryArgument("json", jsonVarName) + ), value)); + } + else + throw new InvalidOperationException( + $"Cannot serialize {property.Name}: No serialization strategy found for {property.PropertyType}"); } - IQueryBuilder> IQueryBuilder.With(TVariables variables) => With(variables); - IQueryBuilder> IQueryBuilder.With(Expression, TVariables>> variables) => WithInternal(variables); + return EnterNewContext>(); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilderState.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilderState.cs index 4864fc6a..8f4bc1cc 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilderState.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilderState.cs @@ -13,5 +13,7 @@ internal sealed record QueryBuilderState( public bool? RunReducers { get; set; } = null; public SchemaInfo? SchemaInfo { get; set; } = SchemaInfo; - public static QueryBuilderState Empty => new(new(), new(), new(), null); + + public static QueryBuilderState Empty => new(new List(), new List(), + new Dictionary(), null); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index c561b620..70d54366 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -2,242 +2,248 @@ using EdgeDB.Interfaces; using System.Linq.Expressions; -namespace EdgeDB +namespace EdgeDB; + +public abstract class QueryContext : IQueryContext +{ + /// + /// References a defined type within the schema. + /// + /// The type to reference. + /// A reference to the type. + public abstract TType Type(); + + /// + /// References a defined type within the schema. + /// + /// The type to reference. + /// The module of the type. + /// A reference to the type. + public abstract TType Type(string module); + + /// + /// References a defined query argument with the given name. + /// + /// The name of the query argument. + /// The type of the query argument. + /// A mock reference to a global with the given . + public abstract TType QueryArgument(string name); + + /// + /// References a defined query global given a name. + /// + /// The type of the global. + /// The name of the global. + /// + /// A mock reference to a global with the given . + /// + public abstract TType Global(string name); + + /// + /// References a contextual local. + /// + /// The type of the local. + /// The name of the local. + /// + /// A mock reference to a local with the given . + /// + public abstract TType Local(string name); + + /// + /// References a contextual local. + /// + /// The name of the local. + /// + /// A mock reference to a local with the given . + /// + public abstract object? Local(string name); + + /// + /// References a contextual local without checking the local context. + /// + /// The name of the local. + /// The type of the local. + /// + /// A mock reference to a local with the given . + /// + public abstract TType UnsafeLocal(string name); + + /// + /// References a contextual local without checking the local context. + /// + /// The name of the local. + /// + /// A mock reference to a local with the given . + /// + public abstract object? UnsafeLocal(string name); + + /// + /// Adds raw edgeql to the current query. + /// + /// The return type of the raw edgeql. + /// The edgeql to add. + /// + /// A mock reference of the returning type of the raw edgeql. + /// + public abstract TType Raw(string query); + + /// + /// Adds a backlink to the current query. + /// + /// The property on which to backlink. + /// + /// A mock array of containing just the objects id. + /// To return a specific type use + /// . + /// + public abstract EdgeDBObject[] BackLink(string property); + + /// + /// Adds a backlink to the current query. + /// + /// The collection type to return. + /// The property on which to backlink. + /// + /// A mock collection of containing just the objects id. + /// To return a specific type use . + /// + public abstract TCollection BackLink(string property) + where TCollection : IEnumerable; + + /// + /// Adds a backlink with the given type to the current query. + /// + /// The type of which to backlink with. + /// The property selector for the backlink. + /// + /// A mock array of . + /// + public abstract TType[] BackLink(Expression> propertySelector); + + /// + /// Adds a backlink with the given type and shape to the current query. + /// + /// The type of which to backlink with. + /// The property selector for the backlink. + /// The shape of the backlink. + /// + /// A mock array of . + /// + public abstract TType[] BackLink(Expression> propertySelector, + Action> shape); + + /// + /// Adds a backlink with the given type and shape to the current query. + /// + /// The type of which to backlink with. + /// The collection type to return. + /// The property selector for the backlink. + /// The shape of the backlink. + /// + /// A mock collection of . + /// + public abstract TCollection BackLink(Expression> propertySelector, + Action> shape) + where TCollection : IEnumerable; + + /// + /// Adds a sub query to the current query. + /// + /// The returning type of the query. + /// The single-cardinality query to add as a sub query. + /// + /// A single mock instance of . + /// + public abstract TType SubQuery(ISingleCardinalityQuery query); + + /// + /// Adds a sub query to the current query. + /// + /// The returning type of the query. + /// The multi-cardinality query to add as a sub query. + /// + /// A mock array of . + /// + public abstract TType[] SubQuery(IMultiCardinalityQuery query); + + public abstract TType SubQuerySingle(IMultiCardinalityQuery query); + + /// + /// Adds a sub query to the current query. + /// + /// The returning type of the query. + /// The collection type to return. + /// The multi-cardinality query to add as a sub query. + /// A mock collection of . + public abstract TCollection SubQuery(IMultiCardinalityQuery query) + where TCollection : IEnumerable; + + public abstract IEnumerable Aggregate(IEnumerable? collection, Func operand); + + public abstract T AsSetAggregation(IEnumerable? set); + public abstract IEnumerable AsSet(T item); +} + +public abstract class QueryContextSelf : QueryContext, IQueryContextSelf +{ + public abstract TSelf Self { get; } +} + +public abstract class QueryContextVars : QueryContext, IQueryContextVars +{ + public abstract TVars Variables { get; } +} + +public abstract class QueryContextUsing : QueryContext, IQueryContextUsing +{ + public abstract TUsing Using { get; } +} + +public abstract class QueryContextSelfVars : QueryContext, IQueryContextSelf, + IQueryContextVars +{ + public abstract TSelf Self { get; } + + public abstract TVars Variables { get; } +} + +public abstract class QueryContextSelfUsing : QueryContext, IQueryContextUsing, + IQueryContextSelf +{ + public abstract TSelf Self { get; } + public abstract TUsing Using { get; } +} + +public abstract class QueryContextUsingVars : QueryContext, IQueryContextUsing, + IQueryContextVars +{ + public abstract TUsing Using { get; } + public abstract TVars Variables { get; } +} + +public abstract class QueryContextSelfUsingVars : QueryContext, IQueryContextUsing, + IQueryContextVars, IQueryContextSelf +{ + public abstract TSelf Self { get; } + public abstract TUsing Using { get; } + public abstract TVars Variables { get; } +} + +public interface IQueryContext +{ +} + +public interface IQueryContextSelf : IQueryContext +{ + TSelf Self { get; } +} + +public interface IQueryContextUsing : IQueryContext +{ + TUsing Using { get; } +} + +public interface IQueryContextVars : IQueryContext { - public abstract class QueryContext : IQueryContext - { - /// - /// References a defined type within the schema. - /// - /// The type to reference. - /// A reference to the type. - public abstract TType Type(); - - /// - /// References a defined type within the schema. - /// - /// The type to reference. - /// The module of the type. - /// A reference to the type. - public abstract TType Type(string module); - - /// - /// References a defined query argument with the given name. - /// - /// The name of the query argument. - /// The type of the query argument. - /// A mock reference to a global with the given . - public abstract TType QueryArgument(string name); - - /// - /// References a defined query global given a name. - /// - /// The type of the global. - /// The name of the global. - /// - /// A mock reference to a global with the given . - /// - public abstract TType Global(string name); - - /// - /// References a contextual local. - /// - /// The type of the local. - /// The name of the local. - /// - /// A mock reference to a local with the given . - /// - public abstract TType Local(string name); - - /// - /// References a contextual local. - /// - /// The name of the local. - /// - /// A mock reference to a local with the given . - /// - public abstract object? Local(string name); - - /// - /// References a contextual local without checking the local context. - /// - /// The name of the local. - /// The type of the local. - /// - /// A mock reference to a local with the given . - /// - public abstract TType UnsafeLocal(string name); - - /// - /// References a contextual local without checking the local context. - /// - /// The name of the local. - /// - /// A mock reference to a local with the given . - /// - public abstract object? UnsafeLocal(string name); - - /// - /// Adds raw edgeql to the current query. - /// - /// The return type of the raw edgeql. - /// The edgeql to add. - /// - /// A mock reference of the returning type of the raw edgeql. - /// - public abstract TType Raw(string query); - - /// - /// Adds a backlink to the current query. - /// - /// The property on which to backlink. - /// - /// A mock array of containing just the objects id. - /// To return a specific type use . - /// - public abstract EdgeDBObject[] BackLink(string property); - - /// - /// Adds a backlink to the current query. - /// - /// The collection type to return. - /// The property on which to backlink. - /// - /// A mock collection of containing just the objects id. - /// To return a specific type use . - /// - public abstract TCollection BackLink(string property) - where TCollection : IEnumerable; - - /// - /// Adds a backlink with the given type to the current query. - /// - /// The type of which to backlink with. - /// The property selector for the backlink. - /// - /// A mock array of . - /// - public abstract TType[] BackLink(Expression> propertySelector); - - /// - /// Adds a backlink with the given type and shape to the current query. - /// - /// The type of which to backlink with. - /// The property selector for the backlink. - /// The shape of the backlink. - /// - /// A mock array of . - /// - public abstract TType[] BackLink(Expression> propertySelector, - Action> shape); - - /// - /// Adds a backlink with the given type and shape to the current query. - /// - /// The type of which to backlink with. - /// The collection type to return. - /// The property selector for the backlink. - /// The shape of the backlink. - /// - /// A mock collection of . - /// - public abstract TCollection BackLink(Expression> propertySelector, - Action> shape) - where TCollection : IEnumerable; - - /// - /// Adds a sub query to the current query. - /// - /// The returning type of the query. - /// The single-cardinality query to add as a sub query. - /// - /// A single mock instance of . - /// - public abstract TType SubQuery(ISingleCardinalityQuery query); - - /// - /// Adds a sub query to the current query. - /// - /// The returning type of the query. - /// The multi-cardinality query to add as a sub query. - /// - /// A mock array of . - /// - public abstract TType[] SubQuery(IMultiCardinalityQuery query); - - public abstract TType SubQuerySingle(IMultiCardinalityQuery query); - - /// - /// Adds a sub query to the current query. - /// - /// The returning type of the query. - /// The collection type to return. - /// The multi-cardinality query to add as a sub query. - /// A mock collection of . - public abstract TCollection SubQuery(IMultiCardinalityQuery query) - where TCollection : IEnumerable; - - public abstract IEnumerable Aggregate(IEnumerable? collection, Func operand); - - public abstract T AsSetAggregation(IEnumerable? set); - public abstract IEnumerable AsSet(T item); - } - - public abstract class QueryContextSelf : QueryContext, IQueryContextSelf - { - public abstract TSelf Self { get; } - } - - public abstract class QueryContextVars : QueryContext, IQueryContextVars - { - public abstract TVars Variables { get; } - } - - public abstract class QueryContextUsing : QueryContext, IQueryContextUsing - { - public abstract TUsing Using { get; } - } - - public abstract class QueryContextSelfVars : QueryContext, IQueryContextSelf, IQueryContextVars - { - public abstract TSelf Self { get; } - - public abstract TVars Variables { get; } - } - - public abstract class QueryContextSelfUsing : QueryContext, IQueryContextUsing, IQueryContextSelf - { - public abstract TUsing Using { get; } - public abstract TSelf Self { get; } - } - - public abstract class QueryContextUsingVars : QueryContext, IQueryContextUsing, IQueryContextVars - { - public abstract TUsing Using { get; } - public abstract TVars Variables { get; } - } - - public abstract class QueryContextSelfUsingVars : QueryContext, IQueryContextUsing, IQueryContextVars, IQueryContextSelf - { - public abstract TSelf Self { get; } - public abstract TUsing Using { get; } - public abstract TVars Variables { get; } - } - - public interface IQueryContext { } - - public interface IQueryContextSelf : IQueryContext - { - TSelf Self { get; } - } - - public interface IQueryContextUsing : IQueryContext - { - TUsing Using { get; } - } - - public interface IQueryContextVars : IQueryContext - { - TVars Variables { get; } - } + TVars Variables { get; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs b/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs index fc016ae0..1778a1a3 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs @@ -1,114 +1,107 @@ using EdgeDB.QueryNodes; using EdgeDB.Schema; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +/// +/// Represents a globally defined variables contained within a 'WITH' statement. +/// +internal class QueryGlobal { /// - /// Represents a globally defined variables contained within a 'WITH' statement. + /// Constructs a new . /// - internal class QueryGlobal + /// The name of the global. + /// The value which will be the assignment of this global. + public QueryGlobal(string name, object? value) { - /// - /// Gets the value that was included in the 'WITH' statement. - /// - public object? Value { get; init; } + Name = name; + Value = value; + } - /// - /// Gets the object reference that the represents. - /// For example the following code - /// - /// QueryBuilder.Insert(new Person {..., Friend = new Person {...}}) - /// - /// would cause the nested person object to be converted to a - /// and this property would be the actual reference to that person instance. - /// - public object? Reference { get; init; } + /// + /// Constructs a new . + /// + /// The name of the global. + /// The value which will be the assignment of this global. + /// The refrence object that caused this global to be created. + public QueryGlobal(string name, object? value, object? reference) + { + Name = name; + Value = value; + Reference = reference; + } - /// - /// Gets the name of the global. - /// - public string Name { get; init; } + /// + /// Gets the value that was included in the 'WITH' statement. + /// + public object? Value { get; init; } - /// - /// Constructs a new . - /// - /// The name of the global. - /// The value which will be the assignment of this global. - public QueryGlobal(string name, object? value) - { - Name = name; - Value = value; - } + /// + /// Gets the object reference that the represents. + /// For example the following code + /// + /// QueryBuilder.Insert(new Person {..., Friend = new Person {...}}) + /// + /// would cause the nested person object to be converted to a + /// and this property would be the actual reference to that person instance. + /// + public object? Reference { get; init; } - /// - /// Constructs a new . - /// - /// The name of the global. - /// The value which will be the assignment of this global. - /// The refrence object that caused this global to be created. - public QueryGlobal(string name, object? value, object? reference) - { - Name = name; - Value = value; - Reference = reference; - } + /// + /// Gets the name of the global. + /// + public string Name { get; init; } - public Token[] Compile(IQueryBuilder source, QueryWriter writer, CompileContext? context = null, - SchemaInfo? info = null) - => Compile(source, QueryBuilderExtensions.WriteTo, writer, context, info); + public Token[] Compile(IQueryBuilder source, QueryWriter writer, CompileContext? context = null, + SchemaInfo? info = null) + => Compile(source, QueryBuilderExtensions.WriteTo, writer, context, info); - public Token[] Compile(ExpressionContext source, QueryWriter writer, CompileContext? context = null, - SchemaInfo? info = null) - => Compile(source, QueryBuilderExtensions.WriteTo, writer, context, info); + public Token[] Compile(ExpressionContext source, QueryWriter writer, CompileContext? context = null, + SchemaInfo? info = null) + => Compile(source, QueryBuilderExtensions.WriteTo, writer, context, info); - public Token[] Compile(QueryNode source, QueryWriter writer, CompileContext? context = null, - SchemaInfo? info = null) - => Compile(source, QueryBuilderExtensions.WriteTo, writer, context, info); + public Token[] Compile(QueryNode source, QueryWriter writer, CompileContext? context = null, + SchemaInfo? info = null) + => Compile(source, QueryBuilderExtensions.WriteTo, writer, context, info); - private Token[] Compile(T source, Action compileBuilder, QueryWriter writer, CompileContext? context = null, - SchemaInfo? info = null) - { - return writer.Span(writer => writer - .Term( - TermType.GlobalDeclaration, - Name, - Defer.This(() => $"Reference?: {Reference?.GetType().ToString() ?? "null"}, Value?: {Value?.GetType().ToString() ?? "null"}"), - new GlobalMetadata(this), - EdgeDB.Token.Of(writer => + private Token[] Compile(T source, Action compileBuilder, + QueryWriter writer, CompileContext? context = null, + SchemaInfo? info = null) => + writer.Span(writer => writer + .Term( + TermType.GlobalDeclaration, + Name, + Defer.This(() => + $"Reference?: {Reference?.GetType().ToString() ?? "null"}, Value?: {Value?.GetType().ToString() ?? "null"}"), + new GlobalMetadata(this), + Token.Of(writer => + { + switch (Value) { - switch (Value) - { - case IQueryBuilder queryBuilder: - writer.Term( - TermType.SubQuery, - "sub_query_from_query_builder", - EdgeDB.Token.Of(writer => writer - .Wrapped(writer => - compileBuilder(queryBuilder, writer, source, context ?? new CompileContext { SchemaInfo = info }) - ) + case IQueryBuilder queryBuilder: + writer.Term( + TermType.SubQuery, + "sub_query_from_query_builder", + Token.Of(writer => writer + .Wrapped(writer => + compileBuilder(queryBuilder, writer, source, + context ?? new CompileContext {SchemaInfo = info}) ) - ); - break; - case SubQuery {RequiresIntrospection: true} when info is null: - throw new - InvalidOperationException( - "Cannot build without introspection! A node requires query introspection."); - case SubQuery {RequiresIntrospection: true} subQuery: - subQuery.Build(writer, info); - break; - default: - QueryUtils.ParseObject(writer, Value); - break; - } - }) - + ) + ); + break; + case SubQuery {RequiresIntrospection: true} when info is null: + throw new + InvalidOperationException( + "Cannot build without introspection! A node requires query introspection."); + case SubQuery {RequiresIntrospection: true} subQuery: + subQuery.Build(writer, info); + break; + default: + QueryUtils.ParseObject(writer, Value); + break; + } + }) )); - } - } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs index 56925020..2e888c8f 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs @@ -1,19 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.QueryNodes; -namespace EdgeDB.QueryNodes +/// +/// Represents the context for a . +/// +internal class DeleteContext : SelectContext { - /// - /// Represents the context for a . - /// - internal class DeleteContext : SelectContext + /// + public DeleteContext(Type currentType) : base(currentType) { - /// - public DeleteContext(Type currentType) : base(currentType) - { - } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/ForContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/ForContext.cs index 12a61cc0..73c2e370 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/ForContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/ForContext.cs @@ -1,33 +1,27 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; +using System.Collections; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents context for a . +/// +internal class ForContext : NodeContext { - /// - /// Represents context for a . - /// - internal class ForContext : NodeContext + /// + public ForContext(Type currentType) : base(currentType) { - /// - /// Gets the iteration expression used to build the 'UNION (...)' statement. - /// - public LambdaExpression? Expression { get; init; } + } - /// - /// Gets the collection used within the 'FOR' statement. - /// - public IEnumerable? Set { get; init; } + /// + /// Gets the iteration expression used to build the 'UNION (...)' statement. + /// + public LambdaExpression? Expression { get; init; } - public LambdaExpression? SetExpression { get; init; } + /// + /// Gets the collection used within the 'FOR' statement. + /// + public IEnumerable? Set { get; init; } - /// - public ForContext(Type currentType) : base(currentType) - { - } - } + public LambdaExpression? SetExpression { get; init; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/GroupContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/GroupContext.cs index 45f1d490..34a7fcff 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/GroupContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/GroupContext.cs @@ -1,22 +1,15 @@ using EdgeDB.Builders; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +internal class GroupContext : NodeContext { - internal class GroupContext : NodeContext + public GroupContext(Type currentType) : base(currentType) { - public bool IncludeShape { get; set; } = true; - public LambdaExpression? Selector { get; init; } - public IShapeBuilder? Shape { get; init; } - - public GroupContext(Type currentType) : base(currentType) - { - - } } + + public bool IncludeShape { get; set; } = true; + public LambdaExpression? Selector { get; init; } + public IShapeBuilder? Shape { get; init; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs index 248f728c..09d23183 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs @@ -1,28 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents context for a . +/// +internal class InsertContext : NodeContext { - /// - /// Represents context for a . - /// - internal class InsertContext : NodeContext + /// + public InsertContext(Type currentType, object? value) : base(currentType) { - /// - /// Gets the value that is to be inserted. - /// - public Union? Value { get; init; } - - /// - public InsertContext(Type currentType, object? value) : base(currentType) - { - Value = value is not null - ? Union.From(value, () => InsertNode.InsertValue.FromType(currentType, value)) - : null; - } + Value = value is not null + ? Union.From(value, + () => InsertNode.InsertValue.FromType(currentType, value)) + : null; } + + /// + /// Gets the value that is to be inserted. + /// + public Union? Value { get; init; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs index 1bfb8a07..5f96d4ab 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs @@ -1,46 +1,38 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.QueryNodes; -namespace EdgeDB.QueryNodes +/// +/// Represents a 's context. +/// +internal abstract class NodeContext { /// - /// Represents a 's context. + /// Constructs a new . /// - internal abstract class NodeContext + /// The type that the node is building for. + public NodeContext(Type currentType) { - /// - /// Gets or sets whether or not the node should be set as a global sub query. - /// - public bool SetAsGlobal { get; set; } + CurrentType = currentType; + } - /// - /// Gets the name of the global variable the node should be set to - /// if is true. - /// - public string? GlobalName { get; init; } + /// + /// Gets or sets whether or not the node should be set as a global sub query. + /// + public bool SetAsGlobal { get; set; } - /// - /// Gets the current type the node is building for. - /// - public Type CurrentType { get; init; } + /// + /// Gets the name of the global variable the node should be set to + /// if is true. + /// + public string? GlobalName { get; init; } - /// - /// Gets whether or not the current type is a json variable. - /// - public bool IsJsonVariable - => ReflectionUtils.IsSubclassOfRawGeneric(typeof(JsonCollectionVariable<>), CurrentType); + /// + /// Gets the current type the node is building for. + /// + public Type CurrentType { get; init; } - /// - /// Constructs a new . - /// - /// The type that the node is building for. - public NodeContext(Type currentType) - { - CurrentType = currentType; - } - } + /// + /// Gets whether or not the current type is a json variable. + /// + public bool IsJsonVariable + => ReflectionUtils.IsSubclassOfRawGeneric(typeof(JsonCollectionVariable<>), CurrentType); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeTranslationContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeTranslationContext.cs index 5a5ad227..d8d2e7ec 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeTranslationContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeTranslationContext.cs @@ -1,58 +1,47 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes.Contexts +namespace EdgeDB.QueryNodes.Contexts; + +internal sealed class NodeTranslationContext { - internal sealed class NodeTranslationContext - { - private readonly List _globals; - private readonly List _prevGlobals; - private readonly QueryNode _node; + private readonly List _globals; + private readonly QueryNode _node; + private readonly List _prevGlobals; - public NodeTranslationContext(QueryNode node) - { - _globals = new(node.Builder.QueryGlobals); - _prevGlobals = new(node.Builder.QueryGlobals); - _node = node; - } + public NodeTranslationContext(QueryNode node) + { + _globals = new List(node.Builder.QueryGlobals); + _prevGlobals = new List(node.Builder.QueryGlobals); + _node = node; + } - public ContextConsumer CreateContextConsumer(LambdaExpression expression) - { - return new(SetTrackedReferences, _node, expression, _node.Builder.QueryVariables, _globals); - } + public ContextConsumer CreateContextConsumer(LambdaExpression expression) => new(SetTrackedReferences, _node, + expression, _node.Builder.QueryVariables, _globals); - private void SetTrackedReferences() + private void SetTrackedReferences() + { + foreach (var addedGlobal in _globals.Except(_prevGlobals)) { - foreach (var addedGlobal in _globals.Except(_prevGlobals)) - { - _node.ReferencedGlobals.AddRange(_globals); - _node.Builder.QueryGlobals.Add(addedGlobal); - } + _node.ReferencedGlobals.AddRange(_globals); + _node.Builder.QueryGlobals.Add(addedGlobal); } } +} - internal sealed class ContextConsumer : ExpressionContext, IDisposable - { - private readonly Action _callback; - - public ContextConsumer( - Action callback, - QueryNode node, - LambdaExpression rootExpression, - IDictionary queryArguments, - List globals) - : base(node.Context, rootExpression, queryArguments, globals, node) - { - _callback = callback; - } +internal sealed class ContextConsumer : ExpressionContext, IDisposable +{ + private readonly Action _callback; - public void Dispose() - { - _callback(); - } + public ContextConsumer( + Action callback, + QueryNode node, + LambdaExpression rootExpression, + IDictionary queryArguments, + List globals) + : base(node.Context, rootExpression, queryArguments, globals, node) + { + _callback = callback; } + + public void Dispose() => _callback(); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs index 0e7e6745..a2c54cf4 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs @@ -1,42 +1,36 @@ using EdgeDB.Builders; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents the context for a . +/// +internal class SelectContext : NodeContext { + public SelectContext(Type currentType) : base(currentType) + { + } + /// - /// Represents the context for a . + /// Gets the shape of the select statement. /// - internal class SelectContext : NodeContext - { - /// - /// Gets the shape of the select statement. - /// - public IShapeBuilder? Shape { get; init; } + public IShapeBuilder? Shape { get; init; } - /// - /// Gets the expression of the select statement. - /// - public LambdaExpression? Expression { get; init; } + /// + /// Gets the expression of the select statement. + /// + public LambdaExpression? Expression { get; init; } - /// - /// Gets or sets the name that is to be selected. - /// - public string? SelectName { get; set; } + /// + /// Gets or sets the name that is to be selected. + /// + public string? SelectName { get; set; } - /// - /// Gets whether or not the select statement is selecting a free object. - /// - public bool IsFreeObject { get; init; } - - public bool IncludeShape { get; set; } = true; + /// + /// Gets whether or not the select statement is selecting a free object. + /// + public bool IsFreeObject { get; init; } - public SelectContext(Type currentType) : base(currentType) - { - } - } + public bool IncludeShape { get; set; } = true; } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs index 93f913cf..eeb4bbb4 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs @@ -1,30 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents context for a . +/// +internal class UpdateContext : NodeContext { + /// + public UpdateContext(Type currentType) : base(currentType) + { + } + /// - /// Represents context for a . + /// Gets the update factory used within the 'SET' statement. /// - internal class UpdateContext : NodeContext - { - /// - /// Gets the update factory used within the 'SET' statement. - /// - public LambdaExpression? UpdateExpression { get; init; } + public LambdaExpression? UpdateExpression { get; init; } - /// - /// Gets the selector for the target of the update statement. - /// - public LambdaExpression? Selector { get; init; } - - /// - public UpdateContext(Type currentType) : base(currentType) - { - } - } + /// + /// Gets the selector for the target of the update statement. + /// + public LambdaExpression? Selector { get; init; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs index ae92d0f3..817c9761 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs @@ -1,27 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents context for a . +/// +internal class WithContext : NodeContext { - /// - /// Represents context for a . - /// - internal class WithContext : NodeContext + /// + public WithContext(Type currentType) : base(currentType) { - /// - /// Gets the global variables that are included in the 'WITH' statement. - /// - public List? Values { get; set; } + } - public LambdaExpression? ValuesExpression { get; init; } + /// + /// Gets the global variables that are included in the 'WITH' statement. + /// + public List? Values { get; set; } - /// - public WithContext(Type currentType) : base(currentType) - { - } - } + public LambdaExpression? ValuesExpression { get; init; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs index 2b903729..1beadecb 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs @@ -1,29 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.QueryNodes; -namespace EdgeDB.QueryNodes +/// +/// Represents a 'DELETE' node +/// +internal class DeleteNode : SelectNode { - /// - /// Represents a 'DELETE' node - /// - internal class DeleteNode : SelectNode + /// + public DeleteNode(NodeBuilder builder) : base(builder) { - /// - public DeleteNode(NodeBuilder builder) : base(builder) - { - } + } - /// - public override void FinalizeQuery(QueryWriter writer) - { - writer.Append("delete ", Context.SelectName ?? OperatingType.GetEdgeDBTypeName()); - FilterProxy?.Invoke(writer); - OrderByProxy?.Invoke(writer); - OffsetProxy?.Invoke(writer); - LimitProxy?.Invoke(writer); - } + /// + public override void FinalizeQuery(QueryWriter writer) + { + writer.Append("delete ", Context.SelectName ?? OperatingType.GetEdgeDBTypeName()); + FilterProxy?.Invoke(writer); + OrderByProxy?.Invoke(writer); + OffsetProxy?.Invoke(writer); + LimitProxy?.Invoke(writer); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs index 45eddefc..9ef3d654 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs @@ -1,140 +1,133 @@ using EdgeDB.DataTypes; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents a 'FOR' node. +/// +internal class ForNode : QueryNode { + private WriterProxy? _expression; + private string? _name; + private WriterProxy? _set; + + /// + public ForNode(NodeBuilder builder) : base(builder) + { + } + /// - /// Represents a 'FOR' node. + /// Parsed the given contextual expression into an iterator. /// - internal class ForNode : QueryNode + /// The name of the root iterator. + /// The name of the query variable containing the json value. + /// The json used for iteration. + /// + /// A type cannot be used as a parameter to a 'FOR' expression + /// + private WriterProxy ParseExpression(string name, string varName, string json) { - private string? _name; - private WriterProxy? _set; - - private WriterProxy? _expression; - - /// - public ForNode(NodeBuilder builder) : base(builder) + // check if we're returning a query builder + if (Context.Expression!.ReturnType.IsAssignableTo(typeof(IQueryBuilder))) { - } + // parse our json value for processing by sub nodes. + var jArray = JArray.Parse(json); - /// - /// Parsed the given contextual expression into an iterator. - /// - /// The name of the root iterator. - /// The name of the query variable containing the json value. - /// The json used for iteration. - /// - /// A type cannot be used as a parameter to a 'FOR' expression - /// - private WriterProxy ParseExpression(string name, string varName, string json) - { - // check if we're returning a query builder - if (Context.Expression!.ReturnType.IsAssignableTo(typeof(IQueryBuilder))) + // construct the parameters for the lambda + var parameters = Context.Expression.Parameters.Select(x => { - // parse our json value for processing by sub nodes. - var jArray = JArray.Parse(json); - - // construct the parameters for the lambda - var parameters = Context.Expression.Parameters.Select(x => + return x switch { - return x switch - { - _ when x.Type == typeof(QueryContext) => null!, - _ when ReflectionUtils.IsSubclassOfRawGeneric(typeof(JsonCollectionVariable<>), x.Type) - => x.Type - .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, [ - typeof(string), typeof(string), typeof(JArray) - ])! - .Invoke([name, varName, jArray])!, - _ => throw new ArgumentException($"Cannot use {x.Type} as a parameter to a 'FOR' expression") - }; - }).ToArray(); - - // build and compile our lambda to get the query builder instance - var builder = (IQueryBuilder)Context.Expression!.Compile().DynamicInvoke(parameters)!; - - // add all nodes as sub nodes to this node - SubNodes.AddRange(builder.Nodes); - - // return indicating we need to do introspection - RequiresIntrospection = SubNodes.Any(x => x.RequiresIntrospection); - - return writer => - { - foreach (var node in SubNodes) - { - node.SchemaInfo = SchemaInfo; - - writer.Term( - TermType.Verbose, - $"FOR_inner_{GetHashCode()}", - Defer.This(() => $"FOR iteration inner node {node.GetType().Name}"), - Token.Of(writer => node.FinalizeQuery(writer)) - ); - - foreach (var variable in node.Builder.QueryVariables) - SetVariable(variable.Key, variable.Value); - - // copy the globals & variables to the current builder - foreach (var global in node.ReferencedGlobals) - SetGlobal(global.Name, global.Value, global.Reference); - } + _ when x.Type == typeof(QueryContext) => null!, + _ when ReflectionUtils.IsSubclassOfRawGeneric(typeof(JsonCollectionVariable<>), x.Type) + => x.Type + .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, [ + typeof(string), typeof(string), typeof(JArray) + ])! + .Invoke([name, varName, jArray])!, + _ => throw new ArgumentException($"Cannot use {x.Type} as a parameter to a 'FOR' expression") }; - } + }).ToArray(); - return writer => writer.Wrapped(writer => TranslateExpression(Context.Expression!, writer)); - } + // build and compile our lambda to get the query builder instance + var builder = (IQueryBuilder)Context.Expression!.Compile().DynamicInvoke(parameters)!; - /// - public override void Visit() - { - // pull the name of the value that the user has specified - _name = Context.Expression!.Parameters.First(x => x.Type != typeof(QueryContext)).Name!; + // add all nodes as sub nodes to this node + SubNodes.AddRange(builder.Nodes); - if (Context.Set is not null) - { - // serialize the collection & generate a name for the json variable - var json = JsonConvert.SerializeObject(Context.Set); - var variableName = QueryUtils.GenerateRandomVariableName(); - _set = writer => writer.Function( - "json_array_unpack", - Token.Of(writer => writer.QueryArgument("json", variableName)) - ); - - // set the json variable - SetVariable(variableName, new Json(json)); - _expression = ParseExpression(_name, variableName, json); - } - else if (Context.SetExpression is not null) + // return indicating we need to do introspection + RequiresIntrospection = SubNodes.Any(x => x.RequiresIntrospection); + + return writer => { - _set = ProxyExpression(Context.SetExpression); - _expression = ProxyExpression(Context.Expression); - } - else throw new InvalidOperationException("Cannot compile for expression: missing set specifier"); + foreach (var node in SubNodes) + { + node.SchemaInfo = SchemaInfo; + + writer.Term( + TermType.Verbose, + $"FOR_inner_{GetHashCode()}", + Defer.This(() => $"FOR iteration inner node {node.GetType().Name}"), + Token.Of(writer => node.FinalizeQuery(writer)) + ); + + foreach (var variable in node.Builder.QueryVariables) + SetVariable(variable.Key, variable.Value); + + // copy the globals & variables to the current builder + foreach (var global in node.ReferencedGlobals) + SetGlobal(global.Name, global.Value, global.Reference); + } + }; } - /// - public override void FinalizeQuery(QueryWriter writer) + return writer => writer.Wrapped(writer => TranslateExpression(Context.Expression!, writer)); + } + + /// + public override void Visit() + { + // pull the name of the value that the user has specified + _name = Context.Expression!.Parameters.First(x => x.Type != typeof(QueryContext)).Name!; + + if (Context.Set is not null) { - if (_name is null || _set is null || _expression is null) - throw new InvalidOperationException("No initialization of this node was preformed"); - - writer.Append( - "for ", - _name, - " in ", - _set, - " union ", - Token.Of(writer => writer.Wrapped(_expression)) + // serialize the collection & generate a name for the json variable + var json = JsonConvert.SerializeObject(Context.Set); + var variableName = QueryUtils.GenerateRandomVariableName(); + _set = writer => writer.Function( + "json_array_unpack", + Token.Of(writer => writer.QueryArgument("json", variableName)) ); + + // set the json variable + SetVariable(variableName, new Json(json)); + _expression = ParseExpression(_name, variableName, json); + } + else if (Context.SetExpression is not null) + { + _set = ProxyExpression(Context.SetExpression); + _expression = ProxyExpression(Context.Expression); } + else throw new InvalidOperationException("Cannot compile for expression: missing set specifier"); + } + + /// + public override void FinalizeQuery(QueryWriter writer) + { + if (_name is null || _set is null || _expression is null) + throw new InvalidOperationException("No initialization of this node was preformed"); + + writer.Append( + "for ", + _name, + " in ", + _set, + " union ", + Token.Of(writer => writer.Wrapped(_expression)) + ); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/GroupNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/GroupNode.cs index 551ced2b..6434dbf2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/GroupNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/GroupNode.cs @@ -1,79 +1,71 @@ using EdgeDB.Builders; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +internal sealed class GroupNode(NodeBuilder builder) : QueryNode(builder) { - internal sealed class GroupNode(NodeBuilder builder) : QueryNode(builder) + private WriterProxy? _by; + private WriterProxy? _using; + private string? _usingReferenceName; + + public override void FinalizeQuery(QueryWriter writer) { - private WriterProxy? _by; - private WriterProxy? _using; - private string? _usingReferenceName; + if (_by is null) + throw new InvalidOperationException("A 'by' expression is required for groups!"); - public override void FinalizeQuery(QueryWriter writer) + writer.Append("group "); + + if (Context.Selector is not null) { - if (_by is null) - throw new InvalidOperationException("A 'by' expression is required for groups!"); + if (_usingReferenceName is not null) + { + writer.Append(_usingReferenceName, " := "); + } - writer.Append("group "); + writer.Append(ProxyExpression(Context.Selector)); + } + else + { + writer.Append(OperatingType.GetEdgeDBTypeName()); - if (Context.Selector is not null) + if (Context.IncludeShape) { - if (_usingReferenceName is not null) - { - writer.Append(_usingReferenceName, " := "); - } - - writer.Append(ProxyExpression(Context.Selector)); + (Context.Shape ?? BaseShapeBuilder.CreateDefault(GetOperatingType())) + .GetShape() + .Compile(writer.Append(' '), (writer, expression) => + { + using var consumer = NodeTranslationContext.CreateContextConsumer(expression.Root); + ExpressionTranslator.ContextualTranslate(expression.Expression, consumer, writer); + }); } - else - { - writer.Append(OperatingType.GetEdgeDBTypeName()); + } - if (Context.IncludeShape) - { - (Context.Shape ?? BaseShapeBuilder.CreateDefault(GetOperatingType())) - .GetShape() - .Compile(writer.Append(' '), (writer, expression) => - { - using var consumer = NodeTranslationContext.CreateContextConsumer(expression.Root); - ExpressionTranslator.ContextualTranslate(expression.Expression, consumer, writer); - }); - } - } + _using?.Invoke(writer); + _by?.Invoke(writer); + } - _using?.Invoke(writer); - _by?.Invoke(writer); - } + public void By(LambdaExpression selector) => _by ??= writer => + writer.Append(" by ", ProxyExpression(selector, ctx => ctx.WrapNewExpressionInBrackets = false)); - public void By(LambdaExpression selector) + public void Using(LambdaExpression expression) + { + if (Context.Selector?.Body is MemberExpression) { - _by ??= writer => writer.Append(" by ", ProxyExpression(selector, ctx => ctx.WrapNewExpressionInBrackets = false)); + // selecting out a property, create an alias + _usingReferenceName = QueryUtils.GenerateRandomVariableName(); } - public void Using(LambdaExpression expression) - { - if (Context.Selector?.Body is MemberExpression) + _using ??= writer => writer.Append(" using ", + ProxyExpression(expression, ctx => { - // selecting out a property, create an alias - _usingReferenceName = QueryUtils.GenerateRandomVariableName(); - } + ctx.WrapNewExpressionInBrackets = false; - _using ??= writer => writer.Append(" using ", - ProxyExpression(expression, ctx => + if (_usingReferenceName is not null) { - ctx.WrapNewExpressionInBrackets = false; - - if (_usingReferenceName is not null) - { - ctx.ParameterAliases.Add(expression.Parameters.First(), _usingReferenceName); - } - //ctx.UseInitializationOperator = true; - })); - } + ctx.ParameterAliases.Add(expression.Parameters.First(), _usingReferenceName); + } + //ctx.UseInitializationOperator = true; + })); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.Json.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.Json.cs index 8d412ed4..536abcd7 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.Json.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.Json.cs @@ -1,7 +1,6 @@ using EdgeDB.DataTypes; using EdgeDB.Schema; using Newtonsoft.Json; -using System.Linq.Expressions; using System.Reflection; namespace EdgeDB.QueryNodes; @@ -30,7 +29,8 @@ private WriterProxy JsonSetterPath(PropertyInfo propertyInfo, string? parentRefe $"Unknown type on property '{propertyInfo.Name}': {propertyInfo.PropertyType.Name}"); } - private void GenerateJsonMapping(QueryWriter writer, string varName, string? parentReference, Type type, SchemaInfo info) + private void GenerateJsonMapping(QueryWriter writer, string varName, string? parentReference, Type type, + SchemaInfo info) { var shape = EdgeDBPropertyMapInfo.Create(type).Properties .ToDictionary( @@ -41,10 +41,11 @@ private void GenerateJsonMapping(QueryWriter writer, string varName, string? par writer.Wrapped(writer => QueryBuilder.For( ctx => EdgeQL.JsonArrayUnpack(ctx.QueryArgument(varName)), x => QueryBuilder.Insert(type, shape, false).UnlessConflict() - ).WriteTo(writer, this, new CompileContext() {SchemaInfo = SchemaInfo})); + ).WriteTo(writer, this, new CompileContext {SchemaInfo = SchemaInfo})); } - private WriterProxy JsonLinkLookup(string? parentReference, bool isArray, Type? innerType, PropertyInfo propertyInfo) + private WriterProxy JsonLinkLookup(string? parentReference, bool isArray, Type? innerType, + PropertyInfo propertyInfo) { if (parentReference is null) { @@ -141,7 +142,7 @@ private ShapeDefinition BuildJsonShape() var elements = new List(); - foreach (var property in jsonValue.InnerType.GetEdgeDBTargetProperties(excludeId: true)) + foreach (var property in jsonValue.InnerType.GetEdgeDBTargetProperties(true)) { elements.Add(new ShapeSetter(writer => writer .Assignment(property.GetEdgeDBPropertyName(), JsonSetterPath(property, $"{mappingName}_d1"))) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index db5ffce7..2f3d5719 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -1,605 +1,593 @@ -using EdgeDB.DataTypes; using EdgeDB.Schema; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents a 'INSERT' node. +/// +internal partial class InsertNode : QueryNode { /// - /// Represents a 'INSERT' node. + /// The list of currently inserted types used to determine if + /// a nested query can be preformed. /// - internal partial class InsertNode : QueryNode - { - /// - /// A readonly struct representing a setter in an insert shape. - /// - private readonly struct ShapeSetter : IWriteable - { - /// - /// Whether or not the setter requires introspection. - /// - public readonly bool RequiresIntrospection; - - /// - /// A string-based setter. - /// - private readonly WriterProxy? _setter; - - /// - /// A function-based setter which requires introspection. - /// - private readonly Action? _setterBuilder; - - /// - /// Constructs a new . - /// - /// A string-based setter. - public ShapeSetter(WriterProxy setter) - { - _setter = setter; - _setterBuilder = null; - RequiresIntrospection = false; - } - - /// - /// Constructs a new . - /// - /// A function-based setter that requires introspection. - public ShapeSetter(Action builder) - { - _setterBuilder = builder; - _setter = null; - RequiresIntrospection = true; - } - - /// - /// Builds this to a string form without introspection. - /// - /// The query string writer to append this shape to. - /// - /// The current setter requires introspection. - /// - public void Write(QueryWriter writer) - { - if (_setter is null) - throw new InvalidOperationException("Cannot build insert setter, a setter requires introspection"); + private readonly List _subQueryMap = []; - _setter(writer); - } - - /// - /// Converts this to a string form with introspection. - /// - /// The query string writer to append the shape to. - /// The introspected schema info. - /// A stringified edgeql setter. - public void Write(QueryWriter writer, SchemaInfo info) - { - if (RequiresIntrospection && _setterBuilder is not null) - _setterBuilder(writer, info); - else - Write(writer); - } - - public static implicit operator ShapeSetter(WriterProxy s) => new(s); - public static implicit operator ShapeSetter(Action s) => new(s); - } + /// + /// Whether or not to autogenerate the unless conflict clause. + /// + private bool _autogenerateUnlessConflict; - /// - /// Represents a insert shape definition. - /// - private readonly struct ShapeDefinition - { - /// - /// Whether or not the setter requires introspection. - /// - public readonly bool RequiresIntrospection; - - /// - /// The raw string form shape definition, if any. - /// - private readonly WriterProxy? _rawShape; - - /// - /// The setters in this shape definition. - /// - private readonly ShapeSetter[] _shape; - - private readonly string _name; - - /// - /// Constructs a new with the given shape body. - /// - /// The name of the shape. - /// The shape itself. - public ShapeDefinition(string name, WriterProxy shape) - { - _name = name; - _rawShape = shape; - _shape = Array.Empty(); - RequiresIntrospection = false; - } + /// + /// The else clause if any. + /// + private WriterProxy? _elseStatement; - /// - /// Constructs a new with the given shape body. - /// - /// The name of the shape. - /// The shape itself. - public ShapeDefinition(string name, IEnumerable shape) - { - _name = name; - _shape = shape.ToArray(); - _rawShape = null; - RequiresIntrospection = _shape.Any(x => x.RequiresIntrospection); - } + /// + /// The insert shape definition. + /// + private ShapeDefinition _shape; - /// - /// Builds this into the string form without using introspection. - /// - /// The string form of the shape definition. - /// The shape body requires introspection to build. - public void Build(QueryWriter writer) - { - if (_rawShape is not null) - { - _rawShape(writer); - return; - } + private WriterProxy? _unlessConflictExpression; - if (_shape.Any(x => x.RequiresIntrospection)) - throw new InvalidOperationException( - "Cannot build insert shape, some properties require introspection"); + /// + public InsertNode(NodeBuilder builder) : base(builder) + { + } - writer.Shape($"shape_{_name}", _shape); - } + /// + public override void Visit() + { + // add the current type to the sub query map + _subQueryMap.Add(OperatingType); - /// - /// Builds this into a string using schema introspection. - /// - /// The query string writer to append the built shape to. - /// The schema introspection info. - /// The string form of the shape definition. - public void Build(QueryWriter writer, SchemaInfo? info) - { - if (_rawShape is not null) - { - _rawShape(writer); - return; - } + // build the insert shape + _shape = Context.IsJsonVariable + ? BuildJsonShape() + : BuildInsertShape(); - if (RequiresIntrospection && info is null) - throw new InvalidOperationException("Introspection is required to build this shape definition"); + RequiresIntrospection |= _shape.RequiresIntrospection; + } - if (RequiresIntrospection) - writer.Shape($"shape_{_name}", _shape, (w, x) => x.Write(w, info!)); - else - Build(writer); - } + /// + public override void FinalizeQuery(QueryWriter writer) + { + if (Context is {SetAsGlobal: true, GlobalName: not null}) + { + SetGlobal(Context.GlobalName, new SubQuery(writer => writer + .Wrapped(Token.Of(WriteInsertStatement)) + ), null); } - - internal readonly struct InsertValue(Type type, IDictionary values, EdgeDBPropertyMapInfo? propertyMapInfo) + else { - public readonly IDictionary Values = values; - public readonly Type Type = type; - public readonly EdgeDBPropertyMapInfo? PropertyMapInfo = propertyMapInfo; + WriteInsertStatement(writer); + } + } - public static InsertValue FromType(Type type, object value) - { - var map = EdgeDBPropertyMapInfo.Create(type); + private void WriteInsertStatement(QueryWriter writer) + { + writer.Append("insert ", OperatingType.GetEdgeDBTypeName(), ' '); - if (value is IDictionary vals) - return new InsertValue( - type, - vals, - map - ); + _shape.Build(writer, SchemaInfo); - return new InsertValue( - type, - map.Properties.ToDictionary(x => x.EdgeDBName, x => x.PropertyInfo.GetValue(value)), - map - ); - } - - public static InsertValue FromRaw(Type type, IDictionary values) - => new(type, values, null); + if (_unlessConflictExpression is not null) + { + _unlessConflictExpression(writer); } + else if (_autogenerateUnlessConflict) + { + if (SchemaInfo is null) + throw new InvalidOperationException( + "Cannot use autogenerated unless conflict on without schema interpolation"); - /// - /// The insert shape definition. - /// - private ShapeDefinition _shape; + if (!SchemaInfo.TryGetObjectInfo(OperatingType, out var typeInfo)) + throw new NotSupportedException($"Could not find type info for {OperatingType}"); - /// - /// Whether or not to autogenerate the unless conflict clause. - /// - private bool _autogenerateUnlessConflict; + ConflictUtils.GenerateExclusiveConflictStatement(writer, typeInfo, _elseStatement is not null); + } - private WriterProxy? _unlessConflictExpression; + _elseStatement?.Invoke(writer); + } - /// - /// The else clause if any. - /// - private WriterProxy? _elseStatement; + /// + /// Builds an insert shape based on the given type and value. + /// + /// The type to build the shape for. + /// The value to build the shape with. + /// The built insert shape. + /// + /// No serialization method could be found for a property. + /// + private ShapeDefinition BuildInsertShape(Type? shapeType = null, + Union? shapeValue = null) + { + List setters = new(); - /// - /// The list of currently inserted types used to determine if - /// a nested query can be preformed. - /// - private readonly List _subQueryMap = []; + // use the provide shape and value if they're not null, otherwise + // use the ones defined in context + var type = shapeType ?? OperatingType; + var insertValueUnion = shapeValue ?? Context.Value!; - /// - public InsertNode(NodeBuilder builder) : base(builder) + // if the value is an expression we can just directly translate it + if (insertValueUnion.Is(out var expression)) { + return new ShapeDefinition($"type_{type.GetEdgeDBTypeName()}:{type}", writer => + { + writer.Append('{', ProxyExpression(expression, ctx => ctx.WrapNewExpressionInBrackets = false), '}'); + }); } - /// - public override void Visit() - { - // add the current type to the sub query map - _subQueryMap.Add(OperatingType); - - // build the insert shape - _shape = Context.IsJsonVariable - ? BuildJsonShape() - : BuildInsertShape(); + if (!insertValueUnion.Is(out var insertValue)) + throw new InvalidOperationException($"Expected an {nameof(InsertValue)}"); - RequiresIntrospection |= _shape.RequiresIntrospection; - } - - /// - public override void FinalizeQuery(QueryWriter writer) + foreach (var (name, value) in insertValue.Values) { - if (Context is {SetAsGlobal: true, GlobalName: not null}) + if (value is WriterProxy proxy) { - SetGlobal(Context.GlobalName, new SubQuery(writer => writer - .Wrapped(Token.Of(WriteInsertStatement)) - ), null); + setters.Add(new ShapeSetter(writer => writer.Assignment(name, proxy))); + continue; } - else + + if (insertValue.PropertyMapInfo is null) { - WriteInsertStatement(writer); + setters.Add(new ShapeSetter(writer => + writer.Assignment(name, Token.Of(writer => QueryUtils.ParseObject(writer, value))) + )); + continue; } - } - private void WriteInsertStatement(QueryWriter writer) - { - writer.Append("insert ", OperatingType.GetEdgeDBTypeName(), ' '); + if (!insertValue.PropertyMapInfo.Value.Map.TryGetValue(name, out var property)) + throw new InvalidOperationException($"Unknown property '{name}'"); - _shape.Build(writer, SchemaInfo); + // define the type and whether or not it's a link + var isScalar = EdgeDBTypeUtils.TryGetScalarType(property.Type, out var edgeqlType); - if (_unlessConflictExpression is not null) + if (property.CustomConverter is not null) { - _unlessConflictExpression(writer); + // convert it and parameterize it + if (!EdgeDBTypeUtils.TryGetScalarType(property.CustomConverter.Target, out var scalar)) + throw new ArgumentException( + $"Cannot resolve scalar type for {property.CustomConverter.Target}"); + + var varName = QueryUtils.GenerateRandomVariableName(); + SetVariable(varName, property.CustomConverter.ConvertTo(value)); + + setters.Add(new ShapeSetter(writer => writer + .Append(property.EdgeDBName) + .Append(" := ") + .QueryArgument(new Token(scalar), varName) + )); + continue; } - else if (_autogenerateUnlessConflict) + + // if its a default value of a struct, ignore it. + if (isScalar && + property.Type is {IsValueType: true, IsEnum: false} && + (value?.Equals(ReflectionUtils.GetDefault(property.Type)) ?? false)) { - if (SchemaInfo is null) - throw new InvalidOperationException( - "Cannot use autogenerated unless conflict on without schema interpolation"); + setters.Add(new ShapeSetter((writer, info) => + { + if (!info.TryGetObjectInfo(type!, out var typeInfo)) + throw new InvalidOperationException( + $"Could not find {type!.GetEdgeDBTypeName()} in schema info!" + ); - if (!SchemaInfo.TryGetObjectInfo(OperatingType, out var typeInfo)) - throw new NotSupportedException($"Could not find type info for {OperatingType}"); + var edgedbProp = typeInfo.Properties!.FirstOrDefault(x => x.Name == property.EdgeDBName); - ConflictUtils.GenerateExclusiveConflictStatement(writer, typeInfo, _elseStatement is not null); - } + if (edgedbProp is null) + throw new InvalidOperationException( + $"Could not find property '{property.EdgeDBName}' on type {type!.GetEdgeDBTypeName()}" + ); - _elseStatement?.Invoke(writer); - } - - /// - /// Builds an insert shape based on the given type and value. - /// - /// The type to build the shape for. - /// The value to build the shape with. - /// The built insert shape. - /// - /// No serialization method could be found for a property. - /// - private ShapeDefinition BuildInsertShape(Type? shapeType = null, Union? shapeValue = null) - { - List setters = new(); + if (edgedbProp is not {Required: true, HasDefault: false}) return; - // use the provide shape and value if they're not null, otherwise - // use the ones defined in context - var type = shapeType ?? OperatingType; - var insertValueUnion = shapeValue ?? Context.Value!; + var varName = QueryUtils.GenerateRandomVariableName(); + SetVariable(varName, value); - // if the value is an expression we can just directly translate it - if (insertValueUnion.Is(out var expression)) - { - return new ShapeDefinition($"type_{type.GetEdgeDBTypeName()}:{type}", writer => - { - writer.Append('{', ProxyExpression(expression, ctx => ctx.WrapNewExpressionInBrackets = false), '}'); - }); + writer + .Append(property.EdgeDBName) + .Append(" := ") + .QueryArgument(new Token(edgeqlType), varName, optional: !edgedbProp.Required); + })); + continue; } - if (!insertValueUnion.Is(out var insertValue)) - throw new InvalidOperationException($"Expected an {nameof(InsertValue)}"); + var isLink = EdgeDBTypeUtils.IsLink(property.Type, out var isArray, out var innerType); - foreach (var (name, value) in insertValue.Values) + // if a scalar type is found for the property type + if (isScalar) { - if (value is WriterProxy proxy) - { - setters.Add(new ShapeSetter(writer => writer.Assignment(name, proxy))); - continue; - } + // set it as a variable and continue the iteration + var varName = QueryUtils.GenerateRandomVariableName(); + SetVariable(varName, value); + setters.Add(new ShapeSetter(writer => writer + .Append(property.EdgeDBName) + .Append(" := ") + .QueryArgument(new Token(edgeqlType), varName, optional: value is null) + )); + continue; + } - if (insertValue.PropertyMapInfo is null) - { - setters.Add(new ShapeSetter(writer => - writer.Assignment(name, Token.Of(writer => QueryUtils.ParseObject(writer, value))) + // if the property is a link + if (isLink) + { + // if its null we can append an empty set + if (value is null) + setters.Add(new ShapeSetter(writer => writer + .Append(property.EdgeDBName).Append(" := {}") )); - continue; - } - - if (!insertValue.PropertyMapInfo.Value.Map.TryGetValue(name, out var property)) - throw new InvalidOperationException($"Unknown property '{name}'"); - - // define the type and whether or not it's a link - var isScalar = EdgeDBTypeUtils.TryGetScalarType(property.Type, out var edgeqlType); - - if (property.CustomConverter is not null) + else if (isArray) // if its a multi link { - // convert it and parameterize it - if (!EdgeDBTypeUtils.TryGetScalarType(property.CustomConverter.Target, out var scalar)) - throw new ArgumentException( - $"Cannot resolve scalar type for {property.CustomConverter.Target}"); - - var varName = QueryUtils.GenerateRandomVariableName(); - SetVariable(varName, property.CustomConverter.ConvertTo(value)); + if (value is not IEnumerable enumerable) + throw new InvalidOperationException( + $"Expected enumerable type for array, got {value.GetType()}" + ); setters.Add(new ShapeSetter(writer => writer .Append(property.EdgeDBName) .Append(" := ") - .QueryArgument(new(scalar), varName) - )); - continue; + .Shape( + $"shape_{type.GetEdgeDBTypeName()}_prop_{property.EdgeDBName}", + enumerable.Cast().ToArray(), + (w, x) => BuildLinkResolver(w, innerType!, x), + debug: Defer.This( + () => $"Shape of property {property.PropertyName} of type {property.Type}") + ))); + + RequiresIntrospection = true; } - - // if its a default value of a struct, ignore it. - if (isScalar && - property.Type is {IsValueType: true, IsEnum: false} && - (value?.Equals(ReflectionUtils.GetDefault(property.Type)) ?? false)) + else { - setters.Add(new ShapeSetter((writer, info) => + // generate the link resolver and append it + setters.Add(new ShapeSetter(writer => { - if (!info.TryGetObjectInfo(type!, out var typeInfo)) - throw new InvalidOperationException( - $"Could not find {type!.GetEdgeDBTypeName()} in schema info!" - ); + writer + .Append(property.EdgeDBName) + .Append(" := "); - var edgedbProp = typeInfo.Properties!.FirstOrDefault(x => x.Name == property.EdgeDBName); + BuildLinkResolver(writer, property.Type, value); + })); - if (edgedbProp is null) - throw new InvalidOperationException( - $"Could not find property '{property.EdgeDBName}' on type {type!.GetEdgeDBTypeName()}" - ); + RequiresIntrospection = true; + } - if (edgedbProp is not {Required: true, HasDefault: false}) return; + continue; + } - var varName = QueryUtils.GenerateRandomVariableName(); - SetVariable(varName, value); + throw new InvalidOperationException( + $"Failed to find method to serialize the property \"{property.Type.Name}\" on type {type.Name}"); + } - writer - .Append(property.EdgeDBName) - .Append(" := ") - .QueryArgument(new(edgeqlType), varName, optional: !edgedbProp.Required); - })); - continue; - } + return new ShapeDefinition($"type_{type.GetEdgeDBTypeName()}:{type}", setters); + } - var isLink = EdgeDBTypeUtils.IsLink(property.Type, out var isArray, out var innerType); + /// + /// Resolves a sub query for a link. + /// + /// The query string writer to append the link resolver to. + /// The type of the link + /// The value of the link. + /// + /// A sub query or global name to reference the links value within the query. + /// + private void BuildLinkResolver(QueryWriter writer, Type type, object? value) + { + // if the value is null we can just return an empty set + if (value is null) + { + writer.Append("{}"); + return; + } - // if a scalar type is found for the property type - if (isScalar) + // TODO: revisit references. + //// is it a value that's been returned from a previous query? + //if (QueryObjectManager.TryGetObjectId(value, out var id)) + //{ + // // add a sub select statement + // return InlineOrGlobal( + // type, + // new SubQuery($"(select {type.GetEdgeDBTypeName()} filter .id = \"{id}\")"), + // value); + //} + + RequiresIntrospection = true; + + // add a insert select statement + InlineOrGlobal(writer, type, new SubQuery((info, writer) => + { + writer.LabelVerbose( + $"link_resolver_{type.GetEdgeDBTypeName()}_{value.GetHashCode()}", + Defer.This(() => $"Built link resolver for {type}"), + Token.Of(writer => writer.Wrapped(writer => { - // set it as a variable and continue the iteration - var varName = QueryUtils.GenerateRandomVariableName(); - SetVariable(varName, value); - setters.Add(new ShapeSetter(writer => writer - .Append(property.EdgeDBName) - .Append(" := ") - .QueryArgument(new(edgeqlType), varName, optional: value is null) - )); - continue; - } + QueryBuilder + .Insert(type, value) + .UnlessConflict() + .ElseReturn() + .WriteTo(writer, this, new CompileContext {SchemaInfo = info}); + })) + ); + }), value); + } - // if the property is a link - if (isLink) - { - // if its null we can append an empty set - if (value is null) - setters.Add(new ShapeSetter(writer => writer - .Append(property.EdgeDBName).Append(" := {}") - )); - else if (isArray) // if its a multi link - { - if (value is not IEnumerable enumerable) - throw new InvalidOperationException( - $"Expected enumerable type for array, got {value.GetType()}" - ); + /// + /// Adds a sub query as an inline query or as a global depending on if the current + /// query contains any statements for the provided type. + /// + /// The query string writer to append the inlined query or global. + /// The returning type of the sub query. + /// The query itself. + /// The optional reference object. + /// + /// A sub query or global name to reference the sub query. + /// + private void InlineOrGlobal(QueryWriter writer, Type type, SubQuery value, object? reference) + { + // if were in a query with the type or the query requires introspection add it as a global + if (_subQueryMap.Contains(type) || value.RequiresIntrospection) + { + writer.Term( + TermType.GlobalReference, + GetOrAddGlobal(reference, value), + Defer.This(() => $"Global of type {type}") + ); + return; + } - setters.Add(new ShapeSetter(writer => writer - .Append(property.EdgeDBName) - .Append(" := ") - .Shape( - $"shape_{type.GetEdgeDBTypeName()}_prop_{property.EdgeDBName}", - enumerable.Cast().ToArray(), - (w, x) => BuildLinkResolver(w, innerType!, x), - debug: Defer.This(() => $"Shape of property {property.PropertyName} of type {property.Type}") - ))); - - RequiresIntrospection = true; - } - else - { - // generate the link resolver and append it - setters.Add(new ShapeSetter(writer => - { - writer - .Append(property.EdgeDBName) - .Append(" := "); + // add it to our sub query map and return the inlined version + _subQueryMap.Add(type); + writer.Append(value.Query); + } - BuildLinkResolver(writer, property.Type, value); - })); + /// + /// Adds a unless conflict on (...) statement to the insert node + /// + /// + /// This method requires introspection on the step. + /// + public void UnlessConflict() + { + _autogenerateUnlessConflict = true; + RequiresIntrospection = true; + } - RequiresIntrospection = true; - } + /// + /// Adds a unless conflict on statement to the insert node + /// + /// The property selector for the conflict clause. + public void UnlessConflictOn(LambdaExpression selector) => _unlessConflictExpression ??= + writer => writer.Append(" unless conflict on ", ProxyExpression(selector)); - continue; - } + /// + /// Adds the default else clause to the insert node that returns the conflicting object. + /// + public void ElseDefault() + { + if (_elseStatement is not null) + throw new InvalidOperationException("An insert statement may only contain one else statement"); + + _elseStatement = writer => writer + .Append(" else ") + .Wrapped(writer => writer + .Append("select ") + .Append(OperatingType.GetEdgeDBTypeName()) + ); + } - throw new InvalidOperationException( - $"Failed to find method to serialize the property \"{property.Type.Name}\" on type {type.Name}"); - } + /// + /// Adds a else statement to the insert node. + /// + /// The builder that contains the else statement. + public void Else(IQueryBuilder builder) + { + if (_elseStatement is not null) + throw new InvalidOperationException("An insert statement may only contain one else statement"); + + _elseStatement = writer => writer + .Append(" else ") + .Wrapped(writer => + builder.WriteTo(writer, this, + new CompileContext {IncludeAutogeneratedNodes = false, IncludeGlobalsInQuery = false}) + ); + } - return new ShapeDefinition($"type_{type.GetEdgeDBTypeName()}:{type}", setters); - } + /// + /// A readonly struct representing a setter in an insert shape. + /// + private readonly struct ShapeSetter : IWriteable + { + /// + /// Whether or not the setter requires introspection. + /// + public readonly bool RequiresIntrospection; /// - /// Resolves a sub query for a link. + /// A string-based setter. /// - /// The query string writer to append the link resolver to. - /// The type of the link - /// The value of the link. - /// - /// A sub query or global name to reference the links value within the query. - /// - private void BuildLinkResolver(QueryWriter writer, Type type, object? value) - { - // if the value is null we can just return an empty set - if (value is null) - { - writer.Append("{}"); - return; - } + private readonly WriterProxy? _setter; - // TODO: revisit references. - //// is it a value that's been returned from a previous query? - //if (QueryObjectManager.TryGetObjectId(value, out var id)) - //{ - // // add a sub select statement - // return InlineOrGlobal( - // type, - // new SubQuery($"(select {type.GetEdgeDBTypeName()} filter .id = \"{id}\")"), - // value); - //} + /// + /// A function-based setter which requires introspection. + /// + private readonly Action? _setterBuilder; - RequiresIntrospection = true; + /// + /// Constructs a new . + /// + /// A string-based setter. + public ShapeSetter(WriterProxy setter) + { + _setter = setter; + _setterBuilder = null; + RequiresIntrospection = false; + } - // add a insert select statement - InlineOrGlobal(writer, type, new SubQuery((info, writer) => - { - writer.LabelVerbose( - $"link_resolver_{type.GetEdgeDBTypeName()}_{value.GetHashCode()}", - Defer.This(() => $"Built link resolver for {type}"), - Token.Of(writer => writer.Wrapped(writer => - { - QueryBuilder - .Insert(type, value) - .UnlessConflict() - .ElseReturn() - .WriteTo(writer, this, new CompileContext { SchemaInfo = info}); - })) - ); - }), value); + /// + /// Constructs a new . + /// + /// A function-based setter that requires introspection. + public ShapeSetter(Action builder) + { + _setterBuilder = builder; + _setter = null; + RequiresIntrospection = true; } /// - /// Adds a sub query as an inline query or as a global depending on if the current - /// query contains any statements for the provided type. + /// Builds this to a string form without introspection. /// - /// The query string writer to append the inlined query or global. - /// The returning type of the sub query. - /// The query itself. - /// The optional reference object. - /// - /// A sub query or global name to reference the sub query. - /// - private void InlineOrGlobal(QueryWriter writer, Type type, SubQuery value, object? reference) + /// The query string writer to append this shape to. + /// + /// The current setter requires introspection. + /// + public void Write(QueryWriter writer) { - // if were in a query with the type or the query requires introspection add it as a global - if (_subQueryMap.Contains(type) || value.RequiresIntrospection) - { - writer.Term( - TermType.GlobalReference, - GetOrAddGlobal(reference, value), - Defer.This(() => $"Global of type {type}") - ); - return; - } + if (_setter is null) + throw new InvalidOperationException("Cannot build insert setter, a setter requires introspection"); - // add it to our sub query map and return the inlined version - _subQueryMap.Add(type); - writer.Append(value.Query); + _setter(writer); } /// - /// Adds a unless conflict on (...) statement to the insert node + /// Converts this to a string form with introspection. /// - /// - /// This method requires introspection on the step. - /// - public void UnlessConflict() + /// The query string writer to append the shape to. + /// The introspected schema info. + /// A stringified edgeql setter. + public void Write(QueryWriter writer, SchemaInfo info) { - _autogenerateUnlessConflict = true; - RequiresIntrospection = true; + if (RequiresIntrospection && _setterBuilder is not null) + _setterBuilder(writer, info); + else + Write(writer); } + public static implicit operator ShapeSetter(WriterProxy s) => new(s); + public static implicit operator ShapeSetter(Action s) => new(s); + } + + /// + /// Represents a insert shape definition. + /// + private readonly struct ShapeDefinition + { + /// + /// Whether or not the setter requires introspection. + /// + public readonly bool RequiresIntrospection; + + /// + /// The raw string form shape definition, if any. + /// + private readonly WriterProxy? _rawShape; + + /// + /// The setters in this shape definition. + /// + private readonly ShapeSetter[] _shape; + + private readonly string _name; + /// - /// Adds a unless conflict on statement to the insert node + /// Constructs a new with the given shape body. /// - /// The property selector for the conflict clause. - public void UnlessConflictOn(LambdaExpression selector) + /// The name of the shape. + /// The shape itself. + public ShapeDefinition(string name, WriterProxy shape) { - _unlessConflictExpression ??= writer => writer.Append(" unless conflict on ", ProxyExpression(selector)); + _name = name; + _rawShape = shape; + _shape = Array.Empty(); + RequiresIntrospection = false; } /// - /// Adds the default else clause to the insert node that returns the conflicting object. + /// Constructs a new with the given shape body. /// - public void ElseDefault() + /// The name of the shape. + /// The shape itself. + public ShapeDefinition(string name, IEnumerable shape) { - if (_elseStatement is not null) - throw new InvalidOperationException("An insert statement may only contain one else statement"); - - _elseStatement = writer => writer - .Append(" else ") - .Wrapped(writer => writer - .Append("select ") - .Append(OperatingType.GetEdgeDBTypeName()) - ); + _name = name; + _shape = shape.ToArray(); + _rawShape = null; + RequiresIntrospection = _shape.Any(x => x.RequiresIntrospection); } /// - /// Adds a else statement to the insert node. + /// Builds this into the string form without using introspection. /// - /// The builder that contains the else statement. - public void Else(IQueryBuilder builder) + /// The string form of the shape definition. + /// The shape body requires introspection to build. + public void Build(QueryWriter writer) { - if (_elseStatement is not null) - throw new InvalidOperationException("An insert statement may only contain one else statement"); + if (_rawShape is not null) + { + _rawShape(writer); + return; + } - _elseStatement = writer => writer - .Append(" else ") - .Wrapped(writer => - builder.WriteTo(writer, this, new CompileContext() - { - IncludeAutogeneratedNodes = false, - IncludeGlobalsInQuery = false - }) + if (_shape.Any(x => x.RequiresIntrospection)) + throw new InvalidOperationException( + "Cannot build insert shape, some properties require introspection"); + + writer.Shape($"shape_{_name}", _shape); + } + + /// + /// Builds this into a string using schema introspection. + /// + /// The query string writer to append the built shape to. + /// The schema introspection info. + /// The string form of the shape definition. + public void Build(QueryWriter writer, SchemaInfo? info) + { + if (_rawShape is not null) + { + _rawShape(writer); + return; + } + + if (RequiresIntrospection && info is null) + throw new InvalidOperationException("Introspection is required to build this shape definition"); + + if (RequiresIntrospection) + writer.Shape($"shape_{_name}", _shape, (w, x) => x.Write(w, info!)); + else + Build(writer); + } + } + + internal readonly struct InsertValue( + Type type, + IDictionary values, + EdgeDBPropertyMapInfo? propertyMapInfo) + { + public readonly IDictionary Values = values; + public readonly Type Type = type; + public readonly EdgeDBPropertyMapInfo? PropertyMapInfo = propertyMapInfo; + + public static InsertValue FromType(Type type, object value) + { + var map = EdgeDBPropertyMapInfo.Create(type); + + if (value is IDictionary vals) + return new InsertValue( + type, + vals, + map ); + + return new InsertValue( + type, + map.Properties.ToDictionary(x => x.EdgeDBName, x => x.PropertyInfo.GetValue(value)), + map + ); } + + public static InsertValue FromRaw(Type type, IDictionary values) + => new(type, values, null); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs index 6d84d90b..1fdd1a56 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs @@ -1,55 +1,50 @@ using EdgeDB.QueryNodes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +/// +/// Represents a builder used by s to build a section of a query. +/// +internal class NodeBuilder { /// - /// Represents a builder used by s to build a section of a query. + /// Constructs a new . /// - internal class NodeBuilder + /// The context for the node this builder is being supplied to. + /// The global collection. + /// The collection of defined nodes. + /// The variable collection. + public NodeBuilder(NodeContext context, List globals, List? nodes = null, + Dictionary? variables = null) { - /// - /// Gets a collection of nodes currently within the builder. - /// - public List Nodes { get; } + Nodes = nodes ?? new List(); + Context = context; + QueryGlobals = globals; + QueryVariables = variables ?? new Dictionary(); + } - /// - /// Gets the node context for the current builder. - /// - public NodeContext Context { get; } + /// + /// Gets a collection of nodes currently within the builder. + /// + public List Nodes { get; } - /// - /// Gets the query variable collection used to add new variables. - /// - public Dictionary QueryVariables { get; } + /// + /// Gets the node context for the current builder. + /// + public NodeContext Context { get; } - /// - /// Gets the query global collection used to add new globals. - /// - public List QueryGlobals { get; } + /// + /// Gets the query variable collection used to add new variables. + /// + public Dictionary QueryVariables { get; } - /// - /// Gets whether or not the current node is auto generated. - /// - public bool IsAutoGenerated { get; init; } + /// + /// Gets the query global collection used to add new globals. + /// + public List QueryGlobals { get; } - /// - /// Constructs a new . - /// - /// The context for the node this builder is being supplied to. - /// The global collection. - /// The collection of defined nodes. - /// The variable collection. - public NodeBuilder(NodeContext context, List globals, List? nodes = null, Dictionary? variables = null) - { - Nodes = nodes ?? new(); - Context = context; - QueryGlobals = globals; - QueryVariables = variables ?? new(); - } - } + /// + /// Gets whether or not the current node is auto generated. + /// + public bool IsAutoGenerated { get; init; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index 725e29d9..5b622335 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -1,209 +1,199 @@ using EdgeDB.QueryNodes.Contexts; using EdgeDB.Schema; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents a generic root query node. +/// +/// The context type for the node. +internal abstract class QueryNode : QueryNode + where TContext : NodeContext +{ + /// + /// Constructs a new query node with the given builder. + /// + /// The node builder used to build this node. + protected QueryNode(NodeBuilder builder) : base(builder) { } + + /// + /// Gets the context for this node. + /// + internal new TContext Context + => (TContext)Builder.Context; +} + +/// +/// Represents an abstract root query node. +/// +internal abstract class QueryNode { /// - /// Represents a generic root query node. + /// The builder used to build this node. + /// + internal readonly NodeBuilder Builder; + + /// + /// The operating type within the context of the query builder. + /// + public readonly Type OperatingType; + + private bool _requiresIntrospection; + + /// + /// Constructs a new query node with the given builder. + /// + /// the builder used to build this node. + public QueryNode(NodeBuilder builder) + { + Builder = builder; + OperatingType = GetOperatingType(); + NodeTranslationContext = new NodeTranslationContext(this); + } + + /// + /// Gets the context used to translate expressions for this node. + /// + public NodeTranslationContext NodeTranslationContext { get; } + + /// + /// Gets whether or not this node was automatically generated. + /// + public bool IsAutoGenerated + => Builder.IsAutoGenerated; + + /// + /// Gets or sets whether or not this node requires introspection to build. + /// + public virtual bool RequiresIntrospection + { + get => _requiresIntrospection || SubNodes.Any(x => x.RequiresIntrospection); + set => _requiresIntrospection = value; + } + + /// + /// Gets or sets the schema introspection data. + /// + public SchemaInfo? SchemaInfo { get; set; } + + /// + /// Gets a collection of child nodes. /// - /// The context type for the node. - internal abstract class QueryNode : QueryNode - where TContext : NodeContext + internal List SubNodes { get; } = new(); + + /// + /// Gets the parent node that created this node. + /// + internal QueryNode? Parent { get; set; } + + /// + /// Gets a collection of global variables this node references. + /// + internal List ReferencedGlobals { get; } = new(); + + /// + /// Gets the context for this node. + /// + internal NodeContext Context + => Builder.Context; + + /// + /// Visits the current node, completing the first phase of this nodes build process. + /// + /// + /// This function modifies , + /// should be populated for the final build step, + /// . + /// + public virtual void Visit() { } + + /// + /// Finalizes the nodes query, completing the second and final phase of this + /// nodes build process by writing the nodes query string to the writer. + /// + public abstract void FinalizeQuery(QueryWriter writer); + + /// + /// Sets a query variable with the given name. + /// + /// The name of the variable to set. + /// The value of the variable to set. + protected void SetVariable(string name, object? value) => Builder.QueryVariables[name] = value; + + /// + /// Sets a query global with the given name and reference. + /// + /// The name of the global to set. + /// The value of the global to set. + /// The reference of the global to set. + protected QueryGlobal SetGlobal(string name, object? value, object? reference) { - /// - /// Constructs a new query node with the given builder. - /// - /// The node builder used to build this node. - protected QueryNode(NodeBuilder builder) : base(builder) { } - - /// - /// Gets the context for this node. - /// - internal new TContext Context - => (TContext)Builder.Context; + var global = new QueryGlobal(name, value, reference); + Builder.QueryGlobals.Add(global); + ReferencedGlobals.Add(global); + return global; } /// - /// Represents an abstract root query node. + /// Gets or adds a global with the given reference and value. + /// + /// The reference of the global. + /// The value to add if no global exists with the given reference. + /// The name of the global. + protected string GetOrAddGlobal(object? reference, object? value) + { + var global = Builder.QueryGlobals.FirstOrDefault(x => x.Value == value); + if (global != null) + return global.Name; + var name = QueryUtils.GenerateRandomVariableName(); + SetGlobal(name, value, reference); + return name; + } + + /// + /// Gets the current operating type in the context of the query builder. + /// + internal Type GetOperatingType() + => Context.CurrentType.IsAssignableTo(typeof(IJsonVariable)) + ? Context.CurrentType.GenericTypeArguments[0] + : Context.CurrentType; + + /// + /// Translates a given lambda expression into EdgeQL. + /// + /// + /// This function tracks the translations globals and stores them in + /// this nodes . + /// + /// The expression to translate. + /// The query string writer to append the translated expression to. + /// Context to provide to the translation. + protected void TranslateExpression(LambdaExpression expression, QueryWriter writer, + Action? context = null) + { + using var consumer = NodeTranslationContext.CreateContextConsumer(expression); + context?.Invoke(consumer); + ExpressionTranslator.Translate(expression, consumer, writer); + } + + protected WriterProxy ProxyExpression(LambdaExpression expression, Action? context = null) => + writer => TranslateExpression(expression, writer, context); + + /// + /// Translates a given expression into EdgeQL. /// - internal abstract class QueryNode + /// + /// This function tracks the translations globals and stores them in + /// this nodes . + /// + /// The root delegate that this expression is apart of. + /// The expression to translate. + /// The query string writer to write the translated edgeql. + /// The EdgeQL version of the expression. + protected void TranslateExpression(LambdaExpression root, Expression expression, QueryWriter writer) { - /// - /// Gets the context used to translate expressions for this node. - /// - public NodeTranslationContext NodeTranslationContext { get; } - - /// - /// Gets whether or not this node was automatically generated. - /// - public bool IsAutoGenerated - => Builder.IsAutoGenerated; - - /// - /// Gets or sets whether or not this node requires introspection to build. - /// - public virtual bool RequiresIntrospection - { - get => _requiresIntrospection || SubNodes.Any(x => x.RequiresIntrospection); - set => _requiresIntrospection = value; - } - - /// - /// Gets or sets the schema introspection data. - /// - public SchemaInfo? SchemaInfo { get; set; } - - /// - /// The operating type within the context of the query builder. - /// - public readonly Type OperatingType; - - /// - /// Gets a collection of child nodes. - /// - internal List SubNodes { get; } = new(); - - /// - /// Gets the parent node that created this node. - /// - internal QueryNode? Parent { get; set; } - - /// - /// Gets a collection of global variables this node references. - /// - internal List ReferencedGlobals { get; } = new(); - - /// - /// Gets the context for this node. - /// - internal NodeContext Context - => Builder.Context; - - /// - /// The builder used to build this node. - /// - internal readonly NodeBuilder Builder; - - private bool _requiresIntrospection; - - /// - /// Constructs a new query node with the given builder. - /// - /// the builder used to build this node. - public QueryNode(NodeBuilder builder) - { - Builder = builder; - OperatingType = GetOperatingType(); - NodeTranslationContext = new(this); - } - - /// - /// Visits the current node, completing the first phase of this nodes build process. - /// - /// - /// This function modifies , - /// should be populated for the final build step, - /// . - /// - public virtual void Visit(){} - - /// - /// Finalizes the nodes query, completing the second and final phase of this - /// nodes build process by writing the nodes query string to the writer. - /// - public abstract void FinalizeQuery(QueryWriter writer); - - /// - /// Sets a query variable with the given name. - /// - /// The name of the variable to set. - /// The value of the variable to set. - protected void SetVariable(string name, object? value) - { - Builder.QueryVariables[name] = value; - } - - /// - /// Sets a query global with the given name and reference. - /// - /// The name of the global to set. - /// The value of the global to set. - /// The reference of the global to set. - protected QueryGlobal SetGlobal(string name, object? value, object? reference) - { - var global = new QueryGlobal(name, value, reference); - Builder.QueryGlobals.Add(global); - ReferencedGlobals.Add(global); - return global; - } - - /// - /// Gets or adds a global with the given reference and value. - /// - /// The reference of the global. - /// The value to add if no global exists with the given reference. - /// The name of the global. - protected string GetOrAddGlobal(object? reference, object? value) - { - var global = Builder.QueryGlobals.FirstOrDefault(x => x.Value == value); - if (global != null) - return global.Name; - var name = QueryUtils.GenerateRandomVariableName(); - SetGlobal(name, value, reference); - return name; - } - - /// - /// Gets the current operating type in the context of the query builder. - /// - internal Type GetOperatingType() - => Context.CurrentType.IsAssignableTo(typeof(IJsonVariable)) - ? Context.CurrentType.GenericTypeArguments[0] - : Context.CurrentType; - - /// - /// Translates a given lambda expression into EdgeQL. - /// - /// - /// This function tracks the translations globals and stores them in - /// this nodes . - /// - /// The expression to translate. - /// The query string writer to append the translated expression to. - /// Context to provide to the translation. - protected void TranslateExpression(LambdaExpression expression, QueryWriter writer, Action? context = null) - { - using var consumer = NodeTranslationContext.CreateContextConsumer(expression); - context?.Invoke(consumer); - ExpressionTranslator.Translate(expression, consumer, writer); - } - - protected WriterProxy ProxyExpression(LambdaExpression expression, Action? context = null) - { - return writer => TranslateExpression(expression, writer, context); - } - - /// - /// Translates a given expression into EdgeQL. - /// - /// - /// This function tracks the translations globals and stores them in - /// this nodes . - /// - /// The root delegate that this expression is apart of. - /// The expression to translate. - /// The query string writer to write the translated edgeql. - /// The EdgeQL version of the expression. - protected void TranslateExpression(LambdaExpression root, Expression expression, QueryWriter writer) - { - using var consumer = NodeTranslationContext.CreateContextConsumer(root); - ExpressionTranslator.ContextualTranslate(expression, consumer, writer); - } + using var consumer = NodeTranslationContext.CreateContextConsumer(root); + ExpressionTranslator.ContextualTranslate(expression, consumer, writer); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 3cca0214..69e619b4 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -1,234 +1,213 @@ using EdgeDB.Builders; -using EdgeDB.Interfaces.Queries; -using EdgeDB.QueryNodes.Contexts; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents a 'SELECT' node +/// +internal class SelectNode : QueryNode { + private SelectShape? _shape; + protected WriterProxy? FilterProxy; + protected WriterProxy? LimitProxy; + protected WriterProxy? OffsetProxy; + protected WriterProxy? OrderByProxy; + + /// + public SelectNode(NodeBuilder builder) : base(builder) { } + /// - /// Represents a 'SELECT' node + /// Wraps the parent node and removes it from the query builder. /// - internal class SelectNode : QueryNode + private void WrapParent(QueryNode parent) { - protected WriterProxy? FilterProxy; - protected WriterProxy? LimitProxy; - protected WriterProxy? OffsetProxy; - protected WriterProxy? OrderByProxy; - private SelectShape? _shape; - - /// - public SelectNode(NodeBuilder builder) : base(builder) { } - - /// - /// Wraps the parent node and removes it from the query builder. - /// - private void WrapParent(QueryNode parent) - { - // remove the node from the query builder - Builder.Nodes.Remove(parent); - RequiresIntrospection = parent.RequiresIntrospection; - // make the node a child of this one - SubNodes.Add(parent); - } + // remove the node from the query builder + Builder.Nodes.Remove(parent); + RequiresIntrospection = parent.RequiresIntrospection; + // make the node a child of this one + SubNodes.Add(parent); + } - /// - public override void Visit() + /// + public override void Visit() + { + if (Context is {IncludeShape: true, Expression: null}) { - if (Context is {IncludeShape: true, Expression: null}) - { - // build the shape - var shape = Context.Shape ?? BaseShapeBuilder.CreateDefault(GetOperatingType()); - - _shape = shape.GetShape(); - } + // build the shape + var shape = Context.Shape ?? BaseShapeBuilder.CreateDefault(GetOperatingType()); - if(Context.Shape is not null && Context.Expression is not null) - { - _shape = Context.Shape.GetShape(); - } + _shape = shape.GetShape(); + } - // is this node autogenerated and does it have a parent? - if (Parent is not null) - WrapParent(Parent); + if (Context.Shape is not null && Context.Expression is not null) + { + _shape = Context.Shape.GetShape(); } - /// - public override void FinalizeQuery(QueryWriter writer) + // is this node autogenerated and does it have a parent? + if (Parent is not null) + WrapParent(Parent); + } + + /// + public override void FinalizeQuery(QueryWriter writer) + { + switch (SubNodes.Count) { - switch (SubNodes.Count) + case > 1: + throw new NotSupportedException("Got more than one child node for select statement (this is a bug)"); + // if parent is defined, our select logic was generated in the + // visit step, we can just return out. + case 1: { - case > 1: - throw new NotSupportedException("Got more than one child node for select statement (this is a bug)"); - // if parent is defined, our select logic was generated in the - // visit step, we can just return out. - case 1: - { - var node = SubNodes.First(); + var node = SubNodes.First(); - // set introspection details & finalize - node.SchemaInfo = SchemaInfo; + // set introspection details & finalize + node.SchemaInfo = SchemaInfo; - writer.Append($"select ").Wrapped(writer => - { - if (writer.AppendIsEmpty(Token.Of(node.FinalizeQuery))) - return; - - if(node.Context.SetAsGlobal && !string.IsNullOrEmpty(node.Context.GlobalName)) - { - // wrap global name - writer.Append(node.Context.GlobalName).Append(' '); - } - else - throw new InvalidOperationException($"Cannot resolve parent node {Parent}'s query"); - }); + writer.Append("select ").Wrapped(writer => + { + if (writer.AppendIsEmpty(Token.Of(node.FinalizeQuery))) + return; - // append the shape of the parents node operating type if we should include ours - if (Context.IncludeShape && _shape is not null) + if (node.Context.SetAsGlobal && !string.IsNullOrEmpty(node.Context.GlobalName)) { - _shape.Compile(writer, (writer, expression) => - { - using var consumer = NodeTranslationContext.CreateContextConsumer(expression.Root); - ExpressionTranslator.ContextualTranslate(expression.Expression, consumer, writer); - }); + // wrap global name + writer.Append(node.Context.GlobalName).Append(' '); } + else + throw new InvalidOperationException($"Cannot resolve parent node {Parent}'s query"); + }); - break; - } - default: + // append the shape of the parents node operating type if we should include ours + if (Context.IncludeShape && _shape is not null) { - if(!Context.IncludeShape) + _shape.Compile(writer, (writer, expression) => { - if (Context.Expression is not null) - { - var expressionWriter = writer - .Append("select "); - - if (Context.SelectName is not null) - expressionWriter - .Append(Context.SelectName) - .Append(' '); - - TranslateExpression(Context.Expression, expressionWriter); - } - else - writer.Append("select ", Context.SelectName ?? OperatingType.GetEdgeDBTypeName()); - } - else if (_shape is not null) + using var consumer = NodeTranslationContext.CreateContextConsumer(expression.Root); + ExpressionTranslator.ContextualTranslate(expression.Expression, consumer, writer); + }); + } + + break; + } + default: + { + if (!Context.IncludeShape) + { + if (Context.Expression is not null) { - var shapeWriter = writer + var expressionWriter = writer .Append("select "); - if (Context.Expression is not null) - { - writer.Append(ProxyExpression(Context.Expression), ' '); - } - else if (!Context.IsFreeObject) - shapeWriter - .Append(Context.SelectName ?? OperatingType.GetEdgeDBTypeName()) + if (Context.SelectName is not null) + expressionWriter + .Append(Context.SelectName) .Append(' '); - _shape.Compile(shapeWriter, (writer, expression) => - { - using var consumer = NodeTranslationContext.CreateContextConsumer(expression.Root); - ExpressionTranslator.ContextualTranslate(expression.Expression, consumer, writer); - }); + TranslateExpression(Context.Expression, expressionWriter); + } + else + writer.Append("select ", Context.SelectName ?? OperatingType.GetEdgeDBTypeName()); + } + else if (_shape is not null) + { + var shapeWriter = writer + .Append("select "); + + if (Context.Expression is not null) + { + writer.Append(ProxyExpression(Context.Expression), ' '); } + else if (!Context.IsFreeObject) + shapeWriter + .Append(Context.SelectName ?? OperatingType.GetEdgeDBTypeName()) + .Append(' '); - break; + _shape.Compile(shapeWriter, (writer, expression) => + { + using var consumer = NodeTranslationContext.CreateContextConsumer(expression.Root); + ExpressionTranslator.ContextualTranslate(expression.Expression, consumer, writer); + }); } - } - FilterProxy?.Invoke(writer); - OrderByProxy?.Invoke(writer); - OffsetProxy?.Invoke(writer); - LimitProxy?.Invoke(writer); + break; + } } - /// - /// Adds a filter to the select node. - /// - /// The filter predicate to add. - public void Filter(LambdaExpression expression) - { - FilterProxy ??= writer => - { - writer.Append(" filter "); - TranslateExpression(expression, writer); - }; - } + FilterProxy?.Invoke(writer); + OrderByProxy?.Invoke(writer); + OffsetProxy?.Invoke(writer); + LimitProxy?.Invoke(writer); + } - /// - /// Adds a order by statement to the select node. - /// - /// - /// if the ordered result should be ascending first. - /// - /// The lambda property selector on which to order by. - /// The placement for null values. - public void OrderBy(bool asc, LambdaExpression selector, OrderByNullPlacement? nullPlacement) + /// + /// Adds a filter to the select node. + /// + /// The filter predicate to add. + public void Filter(LambdaExpression expression) => + FilterProxy ??= writer => { - var direction = asc ? "asc" : "desc"; + writer.Append(" filter "); + TranslateExpression(expression, writer); + }; - OrderByProxy ??= writer => - { - writer.Append(" order by "); - TranslateExpression(selector, writer); - writer.Append(" ").Append(direction); - - if (nullPlacement.HasValue) - writer.Append(nullPlacement.Value.ToString().ToLowerInvariant()); - }; - } + /// + /// Adds a order by statement to the select node. + /// + /// + /// if the ordered result should be ascending first. + /// + /// The lambda property selector on which to order by. + /// The placement for null values. + public void OrderBy(bool asc, LambdaExpression selector, OrderByNullPlacement? nullPlacement) + { + var direction = asc ? "asc" : "desc"; - /// - /// Adds a offset statement to the select node. - /// - /// The number of elements to offset by. - internal void Offset(long offset) + OrderByProxy ??= writer => { - OffsetProxy ??= writer => writer.Append(" offset ", offset); - } + writer.Append(" order by "); + TranslateExpression(selector, writer); + writer.Append(" ").Append(direction); - /// - /// Adds a offset statement to the select node. - /// - /// The expression returning the number of elements to offset by. - internal void OffsetExpression(LambdaExpression exp) - { - OffsetProxy ??= writer => - { - writer.Append(" offset "); - TranslateExpression(exp, writer); - }; - } + if (nullPlacement.HasValue) + writer.Append(nullPlacement.Value.ToString().ToLowerInvariant()); + }; + } - /// - /// Adds a limit statement to the select node. - /// - /// The number of element to limit to. - internal void Limit(long limit) + /// + /// Adds a offset statement to the select node. + /// + /// The number of elements to offset by. + internal void Offset(long offset) => OffsetProxy ??= writer => writer.Append(" offset ", offset); + + /// + /// Adds a offset statement to the select node. + /// + /// The expression returning the number of elements to offset by. + internal void OffsetExpression(LambdaExpression exp) => + OffsetProxy ??= writer => { - LimitProxy ??= writer => writer.Append(" limit ", limit); - } + writer.Append(" offset "); + TranslateExpression(exp, writer); + }; - /// - /// Adds a limit statement to the select node. - /// - /// The expression returning the number of elements to limit to. - internal void LimitExpression(LambdaExpression exp) + /// + /// Adds a limit statement to the select node. + /// + /// The number of element to limit to. + internal void Limit(long limit) => LimitProxy ??= writer => writer.Append(" limit ", limit); + + /// + /// Adds a limit statement to the select node. + /// + /// The expression returning the number of elements to limit to. + internal void LimitExpression(LambdaExpression exp) => + LimitProxy ??= writer => { - LimitProxy ??= writer => - { - writer.Append(" limit "); - TranslateExpression(exp, writer); - }; - } - } + writer.Append(" limit "); + TranslateExpression(exp, writer); + }; } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs index 71326713..6ff7edc6 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs @@ -1,75 +1,65 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents a 'UPDATE' node. +/// +internal class UpdateNode : QueryNode { - /// - /// Represents a 'UPDATE' node. - /// - internal class UpdateNode : QueryNode - { - private WriterProxy? _filter; - private WriterProxy? _set; + private WriterProxy? _filter; + private WriterProxy? _set; - /// - public UpdateNode(NodeBuilder builder) : base(builder) { } + /// + public UpdateNode(NodeBuilder builder) : base(builder) { } - /// - public override void Visit() - { - } + /// + public override void Visit() + { + } - private void AppendUpdateStatement(QueryWriter writer) - { - // resolve and append the UPDATE ... statement - writer.Append("update "); + private void AppendUpdateStatement(QueryWriter writer) + { + // resolve and append the UPDATE ... statement + writer.Append("update "); - if (Context.Selector is not null) - TranslateExpression(Context.Selector, writer); - else - writer.Append(OperatingType.GetEdgeDBTypeName()); + if (Context.Selector is not null) + TranslateExpression(Context.Selector, writer); + else + writer.Append(OperatingType.GetEdgeDBTypeName()); - _filter?.Invoke(writer); - _set?.Invoke(writer); - } + _filter?.Invoke(writer); + _set?.Invoke(writer); + } - /// - public override void FinalizeQuery(QueryWriter writer) + /// + public override void FinalizeQuery(QueryWriter writer) + { + // if the builder wants this node to be a global + if (Context is {SetAsGlobal: true, GlobalName: not null}) { - // if the builder wants this node to be a global - if (Context is {SetAsGlobal: true, GlobalName: not null}) - { - SetGlobal(Context.GlobalName, new SubQuery(writer => writer - .Wrapped(AppendUpdateStatement) - ), null); - } - else - AppendUpdateStatement(writer); + SetGlobal(Context.GlobalName, new SubQuery(writer => writer + .Wrapped(AppendUpdateStatement) + ), null); } + else + AppendUpdateStatement(writer); + } - /// - /// Adds a filter to the update node. - /// - /// The filter predicate to add. - public void Filter(LambdaExpression filter) + /// + /// Adds a filter to the update node. + /// + /// The filter predicate to add. + public void Filter(LambdaExpression filter) => + _filter ??= writer => { - _filter ??= writer => - { - writer.Append(" filter ", ProxyExpression(filter)); - }; - } + writer.Append(" filter ", ProxyExpression(filter)); + }; - public void Set(IUpdateShapeBuilder setter) + public void Set(IUpdateShapeBuilder setter) => + _set ??= writer => { - _set ??= writer => - { - writer.Append(" set "); - setter.Compile(writer, (writer, expression) => TranslateExpression(expression, writer)); - }; - } - } + writer.Append(" set "); + setter.Compile(writer, (writer, expression) => TranslateExpression(expression, writer)); + }; } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs index af51414e..54897c9c 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs @@ -1,107 +1,108 @@ using EdgeDB.Translators.Expressions; using System.Linq.Expressions; -namespace EdgeDB.QueryNodes +namespace EdgeDB.QueryNodes; + +/// +/// Represents a 'WITH' node. +/// +internal class WithNode : QueryNode { - /// - /// Represents a 'WITH' node. - /// - internal class WithNode : QueryNode - { - /// - public WithNode(NodeBuilder builder) : base(builder) { } + /// + public WithNode(NodeBuilder builder) : base(builder) { } - public override void Visit() - { - if (Context.ValuesExpression is null) return; + public override void Visit() + { + if (Context.ValuesExpression is null) return; - var inits = InitializationTranslator.PullInitializationExpression(Context.ValuesExpression.Body); + var inits = InitializationTranslator.PullInitializationExpression(Context.ValuesExpression.Body); - Context.Values ??= inits.Any() ? new List() : null; + Context.Values ??= inits.Any() ? new List() : null; - foreach (var global in inits) - { - Context.Values!.Add(SetGlobal( - global.Key.Name, - new SubQuery(writer => TranslateExpression(Context.ValuesExpression, global.Value, writer)), - global.Value - )); - } - } - - /// - public override void FinalizeQuery(QueryWriter writer) + foreach (var global in inits) { - if (!Builder.QueryGlobals.Any()) - return; + Context.Values!.Add(SetGlobal( + global.Key.Name, + new SubQuery(writer => TranslateExpression(Context.ValuesExpression, global.Value, writer)), + global.Value + )); + } + } - var groups = Builder.QueryGlobals.GroupBy(x => x.Reference).ToArray(); + /// + public override void FinalizeQuery(QueryWriter writer) + { + if (!Builder.QueryGlobals.Any()) + return; - writer.Append("with "); + var groups = Builder.QueryGlobals.GroupBy(x => x.Reference).ToArray(); - for (var i = 0; i != groups.Length; i++) - { - if (i > 0 && i < groups.Length) - writer.Append(", "); + writer.Append("with "); - var globalGroup = groups[i]; + for (var i = 0; i != groups.Length; i++) + { + if (i > 0 && i < groups.Length) + writer.Append(", "); - // basic global - QueryGlobal global; - if (globalGroup.Count() == 1) - { - global = globalGroup.First(); - - writer.Term( - TermType.BinaryOp, - "with_assignment", - Defer.This(() => $"Single global assignment: {global.Name}"), - new BinaryOpMetadata(ExpressionType.Assign), - Token.Of(writer => - { - writer.Append(global.Name, " := "); - global.Compile(this, writer, CompileContext.SubQueryContext(SchemaInfo, null, writer.IsDebug), SchemaInfo); - }) - ); - - continue; - } + var globalGroup = groups[i]; + // basic global + QueryGlobal global; + if (globalGroup.Count() == 1) + { global = globalGroup.First(); - var followers = globalGroup.Skip(1); writer.Term( TermType.BinaryOp, "with_assignment", - Defer.This(() => $"Global group assignment: {global.Name} ({globalGroup.Count()})"), + Defer.This(() => $"Single global assignment: {global.Name}"), new BinaryOpMetadata(ExpressionType.Assign), Token.Of(writer => { writer.Append(global.Name, " := "); - global.Compile(this, writer, CompileContext.SubQueryContext(SchemaInfo, null, writer.IsDebug), SchemaInfo); + global.Compile(this, writer, CompileContext.SubQueryContext(SchemaInfo, null, writer.IsDebug), + SchemaInfo); }) ); - foreach (var follower in followers) + continue; + } + + global = globalGroup.First(); + var followers = globalGroup.Skip(1); + + writer.Term( + TermType.BinaryOp, + "with_assignment", + Defer.This(() => $"Global group assignment: {global.Name} ({globalGroup.Count()})"), + new BinaryOpMetadata(ExpressionType.Assign), + Token.Of(writer => { - if (!writer.Terms.TermsByType.TryGetValue(TermType.GlobalReference, out var terms)) - { - throw new InvalidOperationException( - $"The global {follower.Name} mimics another global, but this one doesn't have any references"); - } + writer.Append(global.Name, " := "); + global.Compile(this, writer, CompileContext.SubQueryContext(SchemaInfo, null, writer.IsDebug), + SchemaInfo); + }) + ); - foreach (var term in terms.ToArray()) - { - term.Replace(Token.Of(writer => writer - .Term( - TermType.GlobalReference, - $"{global.Name}_follower_{follower.Name}", - Defer.This(() => $"Term is a follower of {global.Name} by reference"), - metadata: null, - global.Name - ) - )); - } + foreach (var follower in followers) + { + if (!writer.Terms.TermsByType.TryGetValue(TermType.GlobalReference, out var terms)) + { + throw new InvalidOperationException( + $"The global {follower.Name} mimics another global, but this one doesn't have any references"); + } + + foreach (var term in terms.ToArray()) + { + term.Replace(Token.Of(writer => writer + .Term( + TermType.GlobalReference, + $"{global.Name}_follower_{follower.Name}", + Defer.This(() => $"Term is a follower of {global.Name} by reference"), + metadata: null, + global.Name + ) + )); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/README.md b/src/EdgeDB.Net.QueryBuilder/README.md index 06a8b495..f84e925a 100644 --- a/src/EdgeDB.Net.QueryBuilder/README.md +++ b/src/EdgeDB.Net.QueryBuilder/README.md @@ -1,6 +1,7 @@ ## Things to look at + - [Demo using the query builder](https://github.com/quinchs/EdgeDB.Net/blob/feat/querybuilder-v2/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs) - [QueryBuilder class](https://github.com/quinchs/EdgeDB.Net/blob/feat/querybuilder-v2/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs) - [Query nodes (select, update, insert, etc...)](https://github.com/quinchs/EdgeDB.Net/tree/feat/querybuilder-v2/src/EdgeDB.Net.QueryBuilder/QueryNodes) - [dotnet lambdas -> edgeql translators, ex: `string (string a) => a.ToLower()` -> `str_lower(a)`](https://github.com/quinchs/EdgeDB.Net/tree/feat/querybuilder-v2/src/EdgeDB.Net.QueryBuilder/Translators/Expressions) -- [EdgeDB standard library class](https://github.com/quinchs/EdgeDB.Net/blob/feat/querybuilder-v2/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs) \ No newline at end of file +- [EdgeDB standard library class](https://github.com/quinchs/EdgeDB.Net/blob/feat/querybuilder-v2/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs) diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Constraint.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Constraint.cs index b860814d..a227daf2 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Constraint.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Constraint.cs @@ -1,25 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.Schema.DataTypes; -namespace EdgeDB.Schema.DataTypes +[EdgeDBType(ModuleName = "schema")] +internal class Constraint { - [EdgeDBType(ModuleName = "schema")] - internal class Constraint - { - [EdgeDBProperty("subjectexpr")] - public string? SubjectExpression { get; set; } + [EdgeDBProperty("subjectexpr")] public string? SubjectExpression { get; set; } - public string? Name { get; set; } + public string? Name { get; set; } - [EdgeDBIgnore] - public bool IsExclusive - => Name == "std::exclusive"; + [EdgeDBIgnore] + public bool IsExclusive + => Name == "std::exclusive"; - [EdgeDBIgnore] - public string[] Properties - => SubjectExpression![1..^1].Split(", ").Select(x => x[1..]).ToArray(); - } + [EdgeDBIgnore] + public string[] Properties + => SubjectExpression![1..^1].Split(", ").Select(x => x[1..]).ToArray(); } diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs index b4a834e2..5cb788c9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs @@ -1,48 +1,41 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.Schema.DataTypes; -namespace EdgeDB.Schema.DataTypes +/// +/// Represents the partial 'schema::ObjectType' type within EdgeDB. +/// +[EdgeDBType(ModuleName = "schema")] +internal class ObjectType { /// - /// Represents the partial 'schema::ObjectType' type within EdgeDB. + /// Gets the cleaned name of the oject type. /// - [EdgeDBType(ModuleName = "schema")] - internal class ObjectType - { - /// - /// Gets the cleaned name of the oject type. - /// - [EdgeDBIgnore] - public string CleanedName - => Name!.Split("::")[1]; + [EdgeDBIgnore] + public string CleanedName + => Name!.Split("::")[1]; - /// - /// Gets or sets the id of this object type. - /// - public Guid Id { get; set; } + /// + /// Gets or sets the id of this object type. + /// + public Guid Id { get; set; } - /// - /// Gets or sets the name of this object type. - /// - public string? Name { get; set; } + /// + /// Gets or sets the name of this object type. + /// + public string? Name { get; set; } - /// - /// Gets or sets whether or not this object type is abstract. - /// - public bool IsAbstract { get; set; } + /// + /// Gets or sets whether or not this object type is abstract. + /// + public bool IsAbstract { get; set; } - /// - /// Gets or sets a collection of properties within this object type. - /// - [EdgeDBProperty("pointers")] - public Property[]? Properties { get; set; } + /// + /// Gets or sets a collection of properties within this object type. + /// + [EdgeDBProperty("pointers")] + public Property[]? Properties { get; set; } - /// - /// Gets or sets a collection of constaints on the object level. - /// - public Constraint[]? Constraints { get; set; } - } + /// + /// Gets or sets a collection of constaints on the object level. + /// + public Constraint[]? Constraints { get; set; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs index 12fd539f..dd0e900b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs @@ -1,67 +1,61 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.Schema.DataTypes; -namespace EdgeDB.Schema.DataTypes +/// +/// Represents the cardinality of a . +/// +public enum Cardinality +{ + One, + AtMostOne, + AtLeastOne, + Many +} + +internal class Property { /// - /// Represents the cardinality of a . + /// Gets or sets the "real" cardinality of the property. /// - public enum Cardinality - { - One, - AtMostOne, - AtLeastOne, - Many, - } - internal class Property - { - /// - /// Gets or sets the "real" cardinality of the property. - /// - [EdgeDBProperty("real_cardinality")] - public Cardinality Cardinality { get; set; } + [EdgeDBProperty("real_cardinality")] + public Cardinality Cardinality { get; set; } - /// - /// Gets or sets the name of the property. - /// - public string? Name { get; set; } + /// + /// Gets or sets the name of the property. + /// + public string? Name { get; set; } - /// - /// Gets or sets the target id of this property. - /// - public Guid? TargetId { get; set; } + /// + /// Gets or sets the target id of this property. + /// + public Guid? TargetId { get; set; } - /// - /// Gets or sets whether or not this property is a link. - /// - public bool IsLink { get; set; } + /// + /// Gets or sets whether or not this property is a link. + /// + public bool IsLink { get; set; } - /// - /// Gets or sets whether or not the property is required. - /// - public bool Required { get; set; } + /// + /// Gets or sets whether or not the property is required. + /// + public bool Required { get; set; } - /// - /// Gets or sets whether or not this property is exclusive - /// - public bool IsExclusive { get; set; } + /// + /// Gets or sets whether or not this property is exclusive + /// + public bool IsExclusive { get; set; } - /// - /// Gets or sets whether or not this property is computed. - /// - public bool IsComputed { get; set; } + /// + /// Gets or sets whether or not this property is computed. + /// + public bool IsComputed { get; set; } - /// - /// Gets or sets whether or not this property is read-only. - /// - public bool IsReadonly { get; set; } + /// + /// Gets or sets whether or not this property is read-only. + /// + public bool IsReadonly { get; set; } - /// - /// Gets or sets whether or not this property has a default value. - /// - public bool HasDefault { get; set; } - } + /// + /// Gets or sets whether or not this property has a default value. + /// + public bool HasDefault { get; set; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs index edbdd99b..76d63289 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs @@ -1,49 +1,42 @@ using EdgeDB.Schema.DataTypes; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Schema +namespace EdgeDB.Schema; + +/// +/// Represents the schema info containing user-defined types. +/// +internal class SchemaInfo { /// - /// Represents the schema info containing user-defined types. + /// Constructs a new with the given types. /// - internal class SchemaInfo + /// A read-only collection of user-defined types. + public SchemaInfo(IReadOnlyCollection types) { - /// - /// Gets a read-only collection of all user-defined types. - /// - public IReadOnlyCollection Types { get; } + Types = types!; + } - /// - /// Constructs a new with the given types. - /// - /// A read-only collection of user-defined types. - public SchemaInfo(IReadOnlyCollection types) - { - Types = types!; - } + /// + /// Gets a read-only collection of all user-defined types. + /// + public IReadOnlyCollection Types { get; } - /// - /// Attempts to get a for the given dotnet type. - /// - /// The type to get an object type for. - /// - /// The out parameter which is the object type representing . - /// - /// - /// if a matching was found; - /// otherwise . - /// - public bool TryGetObjectInfo(Type type, [MaybeNullWhen(false)] out ObjectType info) - => (info = Types.FirstOrDefault(x => - { - var name = type.GetEdgeDBTypeName(); - return name == x.CleanedName || name == x.Name; - })) != null; - } + /// + /// Attempts to get a for the given dotnet type. + /// + /// The type to get an object type for. + /// + /// The out parameter which is the object type representing . + /// + /// + /// if a matching was found; + /// otherwise . + /// + public bool TryGetObjectInfo(Type type, [MaybeNullWhen(false)] out ObjectType info) + => (info = Types.FirstOrDefault(x => + { + var name = type.GetEdgeDBTypeName(); + return name == x.CleanedName || name == x.Name; + })) != null; } diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs index de6eb69d..3eb29921 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs @@ -1,83 +1,84 @@ using EdgeDB.Schema.DataTypes; -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Schema +namespace EdgeDB.Schema; + +/// +/// Represents a class responsible for preforming and caching schema introspection data. +/// +internal class SchemaIntrospector { /// - /// Represents a class responsible for preforming and caching schema introspection data. + /// The cache of schema info key'd by the client. /// - internal class SchemaIntrospector - { - /// - /// The cache of schema info key'd by the client. - /// - private static readonly ConcurrentDictionary _schemas; + private static readonly ConcurrentDictionary _schemas; - /// - /// Initializes the schema info collection. - /// - static SchemaIntrospector() - { - _schemas = new ConcurrentDictionary(); - } + /// + /// Initializes the schema info collection. + /// + static SchemaIntrospector() + { + _schemas = new ConcurrentDictionary(); + } - /// - /// Gets or creates schema introspection info. - /// - /// The client to preform introspection with if the cache doesn't have it. - /// A cancellation token used to cancel the introspection query. - /// - /// A ValueTask representing the (a)sync introspection operation. The result of the - /// task is the introspection info. - /// - public static ValueTask GetOrCreateSchemaIntrospectionAsync(IEdgeDBQueryable edgedb, CancellationToken token = default) - { - if (_schemas.TryGetValue(edgedb, out var info)) - return ValueTask.FromResult(info); - return new ValueTask(IntrospectSchemaAsync(edgedb, token)); - } + /// + /// Gets or creates schema introspection info. + /// + /// The client to preform introspection with if the cache doesn't have it. + /// A cancellation token used to cancel the introspection query. + /// + /// A ValueTask representing the (a)sync introspection operation. The result of the + /// task is the introspection info. + /// + public static ValueTask GetOrCreateSchemaIntrospectionAsync(IEdgeDBQueryable edgedb, + CancellationToken token = default) + { + if (_schemas.TryGetValue(edgedb, out var info)) + return ValueTask.FromResult(info); + return new ValueTask(IntrospectSchemaAsync(edgedb, token)); + } - /// - /// Preforms an introspection and adds its result to the collection. - /// - /// The client to preform introspection with. - /// A cancellation token used to cancel the introspection query. - /// - /// A ValueTask representing the (a)sync introspection operation. The result of the - /// task is the introspection info. - /// - private static async Task IntrospectSchemaAsync(IEdgeDBQueryable edgedb, CancellationToken token) - { - // select out all object types and filter where they're not built-in - var result = await QueryBuilder - .Select(shape => - { - shape.IncludeMultiLink(x => x.Constraints); - shape.IncludeMultiLink(x => x.Properties, shape => - shape.Computeds((ctx, prop) => new - { - Cardinality = (string)ctx.UnsafeLocal("cardinality") == "One" - ? ctx.UnsafeLocal("required") ? DataTypes.Cardinality.One : DataTypes.Cardinality.AtMostOne - : ctx.UnsafeLocal("required") ? DataTypes.Cardinality.AtLeastOne : DataTypes.Cardinality.Many, - TargetId = ctx.UnsafeLocal("target.id"), - IsLink = ctx.Raw("[IS schema::Link]") != null, - IsExclusive = ctx.Raw("exists (select .constraints filter .name = 'std::exclusive')"), - IsComputed = EdgeQL.Len(ctx.UnsafeLocal("computed_fields")) != 0, - IsReadonly = ctx.UnsafeLocal("readonly"), - HasDefault = ctx.Raw("EXISTS .default or (\"std::sequence\" in .target[IS schema::ScalarType].ancestors.name)") - }) - ); - }) - .Filter((x, ctx) => !ctx.UnsafeLocal("builtin")) - .ExecuteAsync(edgedb, token: token); + /// + /// Preforms an introspection and adds its result to the collection. + /// + /// The client to preform introspection with. + /// A cancellation token used to cancel the introspection query. + /// + /// A ValueTask representing the (a)sync introspection operation. The result of the + /// task is the introspection info. + /// + private static async Task IntrospectSchemaAsync(IEdgeDBQueryable edgedb, CancellationToken token) + { + // select out all object types and filter where they're not built-in + var result = await QueryBuilder + .Select(shape => + { + shape.IncludeMultiLink(x => x.Constraints); + shape.IncludeMultiLink(x => x.Properties, shape => + shape.Computeds((ctx, prop) => new + { + Cardinality = (string)ctx.UnsafeLocal("cardinality") == "One" + ? ctx.UnsafeLocal("required") + ? DataTypes.Cardinality.One + : DataTypes.Cardinality.AtMostOne + : ctx.UnsafeLocal("required") + ? DataTypes.Cardinality.AtLeastOne + : DataTypes.Cardinality.Many, + TargetId = ctx.UnsafeLocal("target.id"), + IsLink = ctx.Raw("[IS schema::Link]") != null, + IsExclusive = ctx.Raw("exists (select .constraints filter .name = 'std::exclusive')"), + IsComputed = EdgeQL.Len(ctx.UnsafeLocal("computed_fields")) != 0, + IsReadonly = ctx.UnsafeLocal("readonly"), + HasDefault = + ctx.Raw( + "EXISTS .default or (\"std::sequence\" in .target[IS schema::ScalarType].ancestors.name)") + }) + ); + }) + .Filter((x, ctx) => !ctx.UnsafeLocal("builtin")) + .ExecuteAsync(edgedb, token: token); - // add to our cache - return _schemas[edgedb] = new SchemaInfo(result); - } + // add to our cache + return _schemas[edgedb] = new SchemaInfo(result); } } diff --git a/src/EdgeDB.Net.QueryBuilder/SubQuery.cs b/src/EdgeDB.Net.QueryBuilder/SubQuery.cs index 6120508c..99c2d919 100644 --- a/src/EdgeDB.Net.QueryBuilder/SubQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/SubQuery.cs @@ -1,91 +1,85 @@ using EdgeDB.Schema; -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +/// +/// Represents a generic subquery. +/// +internal class SubQuery { /// - /// Represents a generic subquery. + /// Constructs a new . /// - internal class SubQuery + /// The builder callback to build this . + public SubQuery(SubQueryBuilder builder) { - /// - /// Represents a function used to compile a sub query. - /// - internal delegate void SubQueryBuilder(SchemaInfo schema, QueryWriter result); - - /// - /// Gets the query string for this subquery. - /// - /// - /// This property is null when is . - /// - public WriterProxy? Query { get; init; } + RequiresIntrospection = true; + Builder = builder; + } - /// - /// Gets whether or not this query requires introspection to generate. - /// - [MemberNotNullWhen(true, nameof(Builder))] - [MemberNotNullWhen(false, nameof(Query))] - public bool RequiresIntrospection { get; init; } + /// + /// Constructs a new . + /// + /// The containing the sub query. + public SubQuery(WriterProxy writer) + { + Query = writer; + RequiresIntrospection = false; + } - /// - /// Gets the builder for this subquery. - /// - private SubQueryBuilder? Builder { get; init; } + /// + /// Gets the query string for this subquery. + /// + /// + /// This property is null when is . + /// + public WriterProxy? Query { get; init; } - /// - /// Constructs a new . - /// - /// The builder callback to build this . - public SubQuery(SubQueryBuilder builder) - { - RequiresIntrospection = true; - Builder = builder; - } + /// + /// Gets whether or not this query requires introspection to generate. + /// + [MemberNotNullWhen(true, nameof(Builder))] + [MemberNotNullWhen(false, nameof(Query))] + public bool RequiresIntrospection { get; init; } - /// - /// Constructs a new . - /// - /// The containing the sub query. - public SubQuery(WriterProxy writer) - { - Query = writer; - RequiresIntrospection = false; - } + /// + /// Gets the builder for this subquery. + /// + private SubQueryBuilder? Builder { get; init; } - /// - /// Builds this using the provided introspection. - /// - /// The introspection info to build this . - /// The builder to append the compiled sub query to. - /// - /// A representing the built form of this query. - /// - public void Build(QueryWriter writer, SchemaInfo? info = null) - { - if (info is null && RequiresIntrospection) - throw new NullReferenceException("Required introspection info, but it was null"); + /// + /// Builds this using the provided introspection. + /// + /// The introspection info to build this . + /// The builder to append the compiled sub query to. + /// + /// A representing the built form of this query. + /// + public void Build(QueryWriter writer, SchemaInfo? info = null) + { + if (info is null && RequiresIntrospection) + throw new NullReferenceException("Required introspection info, but it was null"); - writer.Term( - TermType.SubQuery, - "sub_query", - Token.Of(writer => + writer.Term( + TermType.SubQuery, + "sub_query", + Token.Of(writer => + { + if (RequiresIntrospection) + { + Builder(info!, writer); + } + else { - if (RequiresIntrospection) - { - Builder(info!, writer); - } - else - { - writer.Append(Query); - } - }) - ); - } + writer.Append(Query); + } + }) + ); } + + /// + /// Represents a function used to compile a sub query. + /// + internal delegate void SubQueryBuilder(SchemaInfo schema, QueryWriter result); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs index 327b36e4..0af95249 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs @@ -1,42 +1,36 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +/// +/// Represents a translator for translating an expression with a binary operator. +/// +internal class BinaryExpressionTranslator : ExpressionTranslator { - /// - /// Represents a translator for translating an expression with a binary operator. - /// - internal class BinaryExpressionTranslator : ExpressionTranslator + /// + public override void Translate(BinaryExpression expression, ExpressionContext context, QueryWriter writer) { - /// - public override void Translate(BinaryExpression expression, ExpressionContext context, QueryWriter writer) + // special case for exists keyword + if ((expression.Right is ConstantExpression {Value: null} || + expression.Left is ConstantExpression {Value: null}) && + expression.NodeType is ExpressionType.Equal or ExpressionType.NotEqual) { - // special case for exists keyword - if ((expression.Right is ConstantExpression { Value: null } || - expression.Left is ConstantExpression { Value: null }) && - expression.NodeType is ExpressionType.Equal or ExpressionType.NotEqual) - { - writer.Append(expression.NodeType is ExpressionType.Equal ? "not exists" : "exists"); - - TranslateExpression( - expression.Right is ConstantExpression { Value: null } - ? expression.Left - : expression.Right, - context, - writer); + writer.Append(expression.NodeType is ExpressionType.Equal ? "not exists" : "exists"); - return; - } + TranslateExpression( + expression.Right is ConstantExpression {Value: null} + ? expression.Left + : expression.Right, + context, + writer); - // try to build an operator for the given binary operator - if (!Grammar.TryBuildOperator( - expression.NodeType, writer, - Proxy(context, expression.Left, expression.Right)) - ) throw new NotSupportedException($"Failed to find operator for node type {expression.NodeType}"); + return; } + + // try to build an operator for the given binary operator + if (!Grammar.TryBuildOperator( + expression.NodeType, writer, + Proxy(context, expression.Left, expression.Right)) + ) throw new NotSupportedException($"Failed to find operator for node type {expression.NodeType}"); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConditionalExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConditionalExpressionTranslator.cs index 3cb4f7a0..039ea985 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConditionalExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConditionalExpressionTranslator.cs @@ -1,30 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +/// +/// Represents a translator for translating an expression with a conditional operator. +/// +internal class ConditionalExpressionTranslator : ExpressionTranslator { - /// - /// Represents a translator for translating an expression with a conditional operator. - /// - internal class ConditionalExpressionTranslator : ExpressionTranslator - { - /// - public override void Translate( - ConditionalExpression expression, - ExpressionContext context, - QueryWriter writer) - { - writer.Append( - Proxy(expression.IfTrue, context), - " IF ", - Proxy(expression.Test, context), - " ELSE ", - Proxy(expression.IfFalse, context) - ); - } - } + /// + public override void Translate( + ConditionalExpression expression, + ExpressionContext context, + QueryWriter writer) => + writer.Append( + Proxy(expression.IfTrue, context), + " IF ", + Proxy(expression.Test, context), + " ELSE ", + Proxy(expression.IfFalse, context) + ); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs index 5488b440..425aeb16 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs @@ -1,29 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +/// +/// Represents a translator for translating an expression with a constant value. +/// +internal class ConstantExpressionTranslator : ExpressionTranslator { - /// - /// Represents a translator for translating an expression with a constant value. - /// - internal class ConstantExpressionTranslator : ExpressionTranslator + /// + public override void Translate( + ConstantExpression expression, + ExpressionContext context, + QueryWriter writer) { - /// - public override void Translate( - ConstantExpression expression, - ExpressionContext context, - QueryWriter writer) - { - // return the string form if the context requests its raw string - // form, otherwise parse the constant value. - if (context.StringWithoutQuotes && expression.Value is string str) - writer.Append(str); - else - QueryUtils.ParseObject(writer, expression.Value); - } + // return the string form if the context requests its raw string + // form, otherwise parse the constant value. + if (context.StringWithoutQuotes && expression.Value is string str) + writer.Append(str); + else + QueryUtils.ParseObject(writer, expression.Value); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index 516d7db9..6809abe6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -1,204 +1,196 @@ using EdgeDB.QueryNodes; -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +/// +/// Represents context used by an . +/// +internal class ExpressionContext { /// - /// Represents context used by an . + /// The collection of query globals. + /// + internal readonly List Globals; + + /// + /// The collection of query variables. + /// + internal readonly IDictionary QueryArguments; + + /// + /// Constructs a new . + /// + /// The calling nodes context. + /// The root lambda expression. + /// The query arguments collection. + /// The query global collection. + /// The query node constructing this translation context. + public ExpressionContext(NodeContext context, LambdaExpression rootExpression, + IDictionary queryArguments, List globals, + QueryNode? node = null) + { + Node = node; + RootExpression = rootExpression; + QueryArguments = queryArguments; + NodeContext = context; + Globals = globals; + + Parameters = rootExpression.Parameters.ToDictionary(x => x.Name!, x => x.Type); + } + + /// + /// Gets the calling nodes context. + /// + public NodeContext NodeContext { get; } + + /// + /// Gets the root lambda function that is currently being translated. + /// + public LambdaExpression RootExpression { get; set; } + + /// + /// Gets a collection of method parameters within the . + /// + public Dictionary Parameters { get; } + + public Dictionary ParameterAliases { get; set; } = new(); + + /// + /// Gets or sets whether or not to serialize string without quotes. + /// + public bool StringWithoutQuotes { get; set; } + + /// + /// Gets or sets the current type scope. This is used when verifying shaped. + /// + public Type? LocalScope { get; set; } + + /// + /// Gets or sets whether or not the current expressions is or is within a shape. + /// + public bool IsShape { get; set; } + + /// + /// Gets or sets whether or not the current expression has an initialization + /// operator, ex: ':=, +=, -='. + /// + public bool UseInitializationOperator { get; set; } = true; + + /// + /// Gets or sets whether or not to include a self reference. + /// Ex: : '.name', : 'name' + /// + public bool IncludeSelfReference { get; set; } = true; + + /// + /// Gets or sets whether or not to wrap new expressions in brackets. + /// + public bool WrapNewExpressionInBrackets { get; set; } = true; + + public Dictionary ParameterPrefixes { get; set; } = new(); + + /// + /// Gets whether or not the current expression tree is a free object. + /// + public bool IsFreeObject + => NodeContext is SelectContext selectContext && selectContext.IsFreeObject; + + /// + /// Gets the query node requesting the translation; otherwise + /// if the translation was not requested by a query node. + /// + public QueryNode? Node { get; } + + /// + /// Adds a query variable. + /// + /// The value of the variable + /// The randomly generated name of the variable. + public string AddVariable(object? value) + { + var name = QueryUtils.GenerateRandomVariableName(); + QueryArguments[name] = value; + return name; + } + + /// + /// Sets a query variable with the given name. + /// + /// The name of the query variable. + /// The value of the query variable. + public void SetVariable(string name, object? value) + => QueryArguments[name] = value; + + /// + /// Attempts to fetch a query global by reference. + /// + /// The reference of the global. + /// The out parameter containing the global. + /// + /// if a global could be found with the reference; + /// otherwise . + /// + public bool TryGetGlobal(object? reference, [MaybeNullWhen(false)] out QueryGlobal global) + { + global = Globals.FirstOrDefault(x => x.Reference == reference); + return global != null; + } + + /// + /// Attempts to fetch a query global by reference. + /// + /// The name of the global. + /// The out parameter containing the global. + /// + /// if a global could be found with the reference; + /// otherwise . + /// + public bool TryGetGlobal(string? name, [MaybeNullWhen(false)] out QueryGlobal global) + { + global = Globals.FirstOrDefault(x => x.Name == name); + return global != null; + } + + /// + /// Gets or adds a global with the given reference and value. + /// + /// The reference of the global. + /// The value to add if no global exists with the given reference. + /// The name of the global. + public string GetOrAddGlobal(object? reference, object? value) + { + if (reference is not null && TryGetGlobal(reference, out var global)) + return global.Name; + + var name = QueryUtils.GenerateRandomVariableName(); + SetGlobal(name, value, reference); + return name; + } + + /// + /// Sets a global with the given name, value, and reference. + /// + /// The name of the global to set. + /// The value of the global to set. + /// The reference of the global to set. + public QueryGlobal SetGlobal(string name, object? value, object? reference) + { + var global = new QueryGlobal(name, value, reference); + Globals.Add(global); + return global; + } + + /// + /// Enters a new context with the given modification delegate. /// - internal class ExpressionContext + /// The modifying delegate. + /// The new modified context. + public ExpressionContext Enter(Action func) { - /// - /// Gets the calling nodes context. - /// - public NodeContext NodeContext { get; } - - /// - /// Gets the root lambda function that is currently being translated. - /// - public LambdaExpression RootExpression { get; set; } - - /// - /// Gets a collection of method parameters within the . - /// - public Dictionary Parameters { get; } - - public Dictionary ParameterAliases { get; set; } = new(); - - /// - /// Gets or sets whether or not to serialize string without quotes. - /// - public bool StringWithoutQuotes { get; set; } - - /// - /// Gets or sets the current type scope. This is used when verifying shaped. - /// - public Type? LocalScope { get; set; } - - /// - /// Gets or sets whether or not the current expressions is or is within a shape. - /// - public bool IsShape { get; set; } - - /// - /// Gets or sets whether or not the current expression has an initialization - /// operator, ex: ':=, +=, -='. - /// - public bool UseInitializationOperator { get; set; } = true; - - /// - /// Gets or sets whether or not to include a self reference. - /// Ex: : '.name', : 'name' - /// - /// - public bool IncludeSelfReference { get; set; } = true; - - /// - /// Gets or sets whether or not to wrap new expressions in brackets. - /// - public bool WrapNewExpressionInBrackets { get; set; } = true; - - public Dictionary ParameterPrefixes { get; set; } = new(); - - /// - /// Gets whether or not the current expression tree is a free object. - /// - public bool IsFreeObject - => NodeContext is SelectContext selectContext && selectContext.IsFreeObject; - - /// - /// Gets the query node requesting the translation; otherwise - /// if the translation was not requested by a query node. - /// - public QueryNode? Node { get; } - - /// - /// The collection of query variables. - /// - internal readonly IDictionary QueryArguments; - - /// - /// The collection of query globals. - /// - internal readonly List Globals; - - /// - /// Constructs a new . - /// - /// The calling nodes context. - /// The root lambda expression. - /// The query arguments collection. - /// The query global collection. - /// The query node constructing this translation context. - public ExpressionContext(NodeContext context, LambdaExpression rootExpression, - IDictionary queryArguments, List globals, - QueryNode? node = null) - { - Node = node; - RootExpression = rootExpression; - QueryArguments = queryArguments; - NodeContext = context; - Globals = globals; - - Parameters = rootExpression.Parameters.ToDictionary(x => x.Name!, x => x.Type); - } - - /// - /// Adds a query variable. - /// - /// The value of the variable - /// The randomly generated name of the variable. - public string AddVariable(object? value) - { - var name = QueryUtils.GenerateRandomVariableName(); - QueryArguments[name] = value; - return name; - } - - /// - /// Sets a query variable with the given name. - /// - /// The name of the query variable. - /// The value of the query variable. - public void SetVariable(string name, object? value) - => QueryArguments[name] = value; - - /// - /// Attempts to fetch a query global by reference. - /// - /// The reference of the global. - /// The out parameter containing the global. - /// - /// if a global could be found with the reference; - /// otherwise . - /// - public bool TryGetGlobal(object? reference, [MaybeNullWhen(false)]out QueryGlobal global) - { - global = Globals.FirstOrDefault(x => x.Reference == reference); - return global != null; - } - - /// - /// Attempts to fetch a query global by reference. - /// - /// The name of the global. - /// The out parameter containing the global. - /// - /// if a global could be found with the reference; - /// otherwise . - /// - public bool TryGetGlobal(string? name, [MaybeNullWhen(false)] out QueryGlobal global) - { - global = Globals.FirstOrDefault(x => x.Name == name); - return global != null; - } - - /// - /// Gets or adds a global with the given reference and value. - /// - /// The reference of the global. - /// The value to add if no global exists with the given reference. - /// The name of the global. - public string GetOrAddGlobal(object? reference, object? value) - { - if (reference is not null && TryGetGlobal(reference, out var global)) - return global.Name; - - var name = QueryUtils.GenerateRandomVariableName(); - SetGlobal(name, value, reference); - return name; - } - - /// - /// Sets a global with the given name, value, and reference. - /// - /// The name of the global to set. - /// The value of the global to set. - /// The reference of the global to set. - public QueryGlobal SetGlobal(string name, object? value, object? reference) - { - var global = new QueryGlobal(name, value, reference); - Globals.Add(global); - return global; - } - - /// - /// Enters a new context with the given modification delegate. - /// - /// The modifying delegate. - /// The new modified context. - public ExpressionContext Enter(Action func) - { - var exp = (ExpressionContext)MemberwiseClone(); - func(exp); - return exp; - } + var exp = (ExpressionContext)MemberwiseClone(); + func(exp); + return exp; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs index 12403998..54abb158 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -1,212 +1,205 @@ using EdgeDB.QueryNodes; -using EdgeDB.Translators; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +/// +/// Represents an abstract translator that can translate the given . +/// +/// The expression type that this translator can translate. +internal abstract class ExpressionTranslator : ExpressionTranslator + where TExpression : Expression { /// - /// Represents an abstract translator that can translate the given . + /// Translate the given into the EdgeQL equivalent. /// - /// The expression type that this translator can translate. - internal abstract class ExpressionTranslator : ExpressionTranslator - where TExpression : Expression - { - /// - /// Translate the given into the EdgeQL equivalent. - /// - /// The expression to translate. - /// The context for the translation. - /// The query string builder to populate with the translated expression. - public abstract void Translate(TExpression expression, ExpressionContext context, QueryWriter writer); - - /// - /// Overrides the default translation method to call the generic one. - /// - /// - public override void Translate(Expression expression, ExpressionContext context, QueryWriter writer) - => Translate((TExpression)expression, context, writer); - } + /// The expression to translate. + /// The context for the translation. + /// The query string builder to populate with the translated expression. + public abstract void Translate(TExpression expression, ExpressionContext context, QueryWriter writer); /// - /// Represents a translator capable of translating expressions to EdgeQL. + /// Overrides the default translation method to call the generic one. /// - internal abstract class ExpressionTranslator + /// + public override void Translate(Expression expression, ExpressionContext context, QueryWriter writer) + => Translate((TExpression)expression, context, writer); +} + +/// +/// Represents a translator capable of translating expressions to EdgeQL. +/// +internal abstract class ExpressionTranslator +{ + /// + /// The collection of expression (key) and translators (value). + /// + private static readonly Dictionary _translators = new(); + + /// + /// Statically initializes the translator, setting . + /// + static ExpressionTranslator() { - /// - /// The collection of expression (key) and translators (value). - /// - private static readonly Dictionary _translators = new(); - - /// - /// Statically initializes the translator, setting . - /// - static ExpressionTranslator() - { - var types = Assembly.GetExecutingAssembly().DefinedTypes; + var types = Assembly.GetExecutingAssembly().DefinedTypes; - // load current translators - var translators = types.Where(x => x.BaseType?.Name == "ExpressionTranslator`1"); + // load current translators + var translators = types.Where(x => x.BaseType?.Name == "ExpressionTranslator`1"); - foreach(var translator in translators) - { - _translators[translator.BaseType!.GenericTypeArguments[0]] = (ExpressionTranslator)Activator.CreateInstance(translator)!; - } + foreach (var translator in translators) + { + _translators[translator.BaseType!.GenericTypeArguments[0]] = + (ExpressionTranslator)Activator.CreateInstance(translator)!; } + } - public static string? UnsafeExpressionAsString(Expression expression) - { - if (expression is ConstantExpression constantExpression && - constantExpression.Type.IsAssignableTo(typeof(string))) - return (string?)constantExpression.Value; + public static string? UnsafeExpressionAsString(Expression expression) + { + if (expression is ConstantExpression constantExpression && + constantExpression.Type.IsAssignableTo(typeof(string))) + return (string?)constantExpression.Value; - var expressionResult = Expression - .Lambda(expression) - .Compile() - .DynamicInvoke(); + var expressionResult = Expression + .Lambda(expression) + .Compile() + .DynamicInvoke(); - if (expressionResult is not string strResult) - throw new ArgumentException( - $"Expected expression {expression} to evaluate to a string, but " + - $"got {expressionResult?.GetType().ToString() ?? "NULL"}" - ); + if (expressionResult is not string strResult) + throw new ArgumentException( + $"Expected expression {expression} to evaluate to a string, but " + + $"got {expressionResult?.GetType().ToString() ?? "NULL"}" + ); - return strResult; - } + return strResult; + } - public static WriterProxy Proxy(Expression expression, ExpressionContext expressionContext, string? label = null) + public static WriterProxy Proxy(Expression expression, ExpressionContext expressionContext, string? label = null) + { + if (label is not null) { - if (label is not null) - { - return writer => - writer.LabelVerbose( - label, - Defer.This(() => $"Translation of {expression}"), - Token.Of(writer => ContextualTranslate(expression, expressionContext, writer)) - ); - } - - return writer => ContextualTranslate(expression, expressionContext, writer); + return writer => + writer.LabelVerbose( + label, + Defer.This(() => $"Translation of {expression}"), + Token.Of(writer => ContextualTranslate(expression, expressionContext, writer)) + ); } - protected static WriterProxy[] Proxy(ExpressionContext context, params Expression[] expressions) - { - var proxies = new WriterProxy[expressions.Length]; + return writer => ContextualTranslate(expression, expressionContext, writer); + } - for (var i = 0; i != expressions.Length; i++) - { - proxies[i] = Proxy(expressions[i], context); - } + protected static WriterProxy[] Proxy(ExpressionContext context, params Expression[] expressions) + { + var proxies = new WriterProxy[expressions.Length]; - return proxies; + for (var i = 0; i != expressions.Length; i++) + { + proxies[i] = Proxy(expressions[i], context); } - /// - /// Translate the given expression into the EdgeQL equivalent. - /// - /// The expression to translate. - /// The context for the translation. - /// The query string builder to populate with the translated expression. - /// The string form of the expression. - public abstract void Translate(Expression expression, ExpressionContext context, QueryWriter writer); - - /// - /// Translates a lambda expression into the EdgeQL equivalent. - /// - /// - /// This function can add globals and query variables. - /// - /// The expression to translate. - /// The collection of query arguments. - /// The context of the calling node. - /// The collection of globals. - /// The query string builder to populate with the translated expression. - public static void Translate( - LambdaExpression expression, - IDictionary queryArguments, - NodeContext nodeContext, - List globals, - QueryWriter writer) + return proxies; + } + + /// + /// Translate the given expression into the EdgeQL equivalent. + /// + /// The expression to translate. + /// The context for the translation. + /// The query string builder to populate with the translated expression. + /// The string form of the expression. + public abstract void Translate(Expression expression, ExpressionContext context, QueryWriter writer); + + /// + /// Translates a lambda expression into the EdgeQL equivalent. + /// + /// + /// This function can add globals and query variables. + /// + /// The expression to translate. + /// The collection of query arguments. + /// The context of the calling node. + /// The collection of globals. + /// The query string builder to populate with the translated expression. + public static void Translate( + LambdaExpression expression, + IDictionary queryArguments, + NodeContext nodeContext, + List globals, + QueryWriter writer) + { + var context = new ExpressionContext(nodeContext, expression, queryArguments, globals); + TranslateExpression(expression.Body, context, writer); + } + + /// + /// Translates a lambda expression into the edgeql equivalent. + /// + /// + /// This function can add globals and query variables. + /// + /// The expression to translate. + /// The translation context. + /// The query string builder to populate with the translated expression. + public static void Translate(LambdaExpression expression, ExpressionContext context, QueryWriter writer) + => TranslateExpression(expression.Body, context, writer); + + /// + /// Translates a sub expression into its edgeql equivalent. + /// + /// The expression to translate. + /// The current context of the calling translator. + /// The query string builder to populate with the translated expression. + /// No translator was found for the given expression. + protected static void TranslateExpression( + Expression expression, + ExpressionContext context, + QueryWriter writer) + { + // special fallthru for lambda functions + if (expression is LambdaExpression lambda) { - var context = new ExpressionContext(nodeContext, expression, queryArguments, globals); - TranslateExpression(expression.Body, context, writer); + _translators[typeof(LambdaExpression)].Translate(lambda, context, writer); + return; } - /// - /// Translates a lambda expression into the edgeql equivalent. - /// - /// - /// This function can add globals and query variables. - /// - /// The expression to translate. - /// The translation context. - /// The query string builder to populate with the translated expression. - public static void Translate(LambdaExpression expression, ExpressionContext context, QueryWriter writer) - => TranslateExpression(expression.Body, context, writer); - - /// - /// Translates a sub expression into its edgeql equivalent. - /// - /// The expression to translate. - /// The current context of the calling translator. - /// The query string builder to populate with the translated expression. - /// No translator was found for the given expression. - protected static void TranslateExpression( - Expression expression, - ExpressionContext context, - QueryWriter writer) - { - // special fallthru for lambda functions - if (expression is LambdaExpression lambda) - { - _translators[typeof(LambdaExpression)].Translate(lambda, context, writer); - return; - } - - // since some expression classes a private, this while loop will - // find the first base class that isn't private and use that class to find a translator. - var expType = expression.GetType(); - while (!expType.IsPublic) - expType = expType.BaseType!; - - // if we can find a translator for the expression type, use it. - if (_translators.TryGetValue(expType, out var translator)) - { - writer.LabelVerbose( - expType.Name, - Defer.This(() => $"Translated form of '{expression}'"), - Token.Of(writer => translator.Translate(expression, context, writer)) - ); - return; - } + // since some expression classes a private, this while loop will + // find the first base class that isn't private and use that class to find a translator. + var expType = expression.GetType(); + while (!expType.IsPublic) + expType = expType.BaseType!; - throw new NotSupportedException( - $"Failed to find translator for expression type: {expType.Name}.{expression.NodeType}" + // if we can find a translator for the expression type, use it. + if (_translators.TryGetValue(expType, out var translator)) + { + writer.LabelVerbose( + expType.Name, + Defer.This(() => $"Translated form of '{expression}'"), + Token.Of(writer => translator.Translate(expression, context, writer)) ); + return; } - /// - /// Translates a given expression with the provided expression context. - /// - /// - /// This method requires and should only be called - /// by child translators that depend on expression translators. - /// - /// The expression to translate. - /// The current context of the calling translator. - /// The query string builder to populate with the translated expression. - /// No translator was found for the given expression. - internal static void ContextualTranslate( - Expression expression, - ExpressionContext context, - QueryWriter writer) - => TranslateExpression(expression, context, writer); + throw new NotSupportedException( + $"Failed to find translator for expression type: {expType.Name}.{expression.NodeType}" + ); } + + /// + /// Translates a given expression with the provided expression context. + /// + /// + /// This method requires and should only be called + /// by child translators that depend on expression translators. + /// + /// The expression to translate. + /// The current context of the calling translator. + /// The query string builder to populate with the translated expression. + /// No translator was found for the given expression. + internal static void ContextualTranslate( + Expression expression, + ExpressionContext context, + QueryWriter writer) + => TranslateExpression(expression, context, writer); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs index 3d7b7952..2be9a0ee 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs @@ -1,269 +1,265 @@ using EdgeDB.QueryNodes; using EdgeDB.Schema.DataTypes; -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +internal static class InitializationTranslator { - internal static class InitializationTranslator - { - public static Dictionary PullInitializationExpression(Expression expression) + public static Dictionary PullInitializationExpression(Expression expression) => + expression switch { - return expression switch - { - MemberInitExpression memberInit => memberInit.Bindings.ToDictionary(x => x.Member, - x => x is not MemberAssignment assignment - ? throw new InvalidOperationException($"Expected MemberAssignment, but got {x.GetType().Name}") - : assignment.Expression), - NewExpression {Members: null} => throw new NullReferenceException( - "New expression must contain arguments"), - NewExpression newExpression => newExpression.Members!.Zip(newExpression.Arguments) - .ToDictionary(x => x.First, x => x.Second), - _ => throw new ArgumentException($"expression is not an initialization expression", nameof(expression)) - }; - } + MemberInitExpression memberInit => memberInit.Bindings.ToDictionary(x => x.Member, + x => x is not MemberAssignment assignment + ? throw new InvalidOperationException($"Expected MemberAssignment, but got {x.GetType().Name}") + : assignment.Expression), + NewExpression {Members: null} => throw new NullReferenceException( + "New expression must contain arguments"), + NewExpression newExpression => newExpression.Members!.Zip(newExpression.Arguments) + .ToDictionary(x => x.First, x => x.Second), + _ => throw new ArgumentException("expression is not an initialization expression", nameof(expression)) + }; + + private static SubQuery? GenerateMultiLinkInserter(Type innerType, IEnumerable collection, + ExpressionContext context) + { + var methodDef = typeof(InitializationTranslator) + .GetMethods(BindingFlags.Static | BindingFlags.NonPublic).FirstOrDefault(x => + x.ContainsGenericParameters && x.Name == nameof(GenerateMultiLinkInserter)); - private static SubQuery? GenerateMultiLinkInserter(Type innerType, IEnumerable collection, ExpressionContext context) - { - var methodDef = typeof(InitializationTranslator) - .GetMethods(BindingFlags.Static | BindingFlags.NonPublic).FirstOrDefault(x => x.ContainsGenericParameters && x.Name == nameof(GenerateMultiLinkInserter)); + if (methodDef is null) + throw new NotSupportedException("Unable to find GenerateMultiLinkInserter. this is a bug"); - if (methodDef is null) - throw new NotSupportedException("Unable to find GenerateMultiLinkInserter. this is a bug"); + return (SubQuery?)methodDef.MakeGenericMethod(innerType).Invoke(null, new object[] {collection, context}); + } - return (SubQuery?)methodDef.MakeGenericMethod(innerType).Invoke(null, new object[] { collection, context }); - } - private static SubQuery? GenerateMultiLinkInserter(IEnumerable collection, ExpressionContext context) + private static SubQuery? GenerateMultiLinkInserter(IEnumerable collection, ExpressionContext context) + { + if (!collection.Any()) + return null; + + return new SubQuery((info, writer) => { - if (!collection.Any()) - return null; + new QueryBuilder() + .For(collection, x => QueryBuilder + .Insert(x) + .UnlessConflict() + ) + .WriteTo(writer, context, new CompileContext {SchemaInfo = info}); + }); + } - return new SubQuery((info, writer) => - { - new QueryBuilder() - .For(collection, x => QueryBuilder - .Insert(x) - .UnlessConflict() - ) - .WriteTo(writer, context, new CompileContext() { SchemaInfo = info }); - }); - } + private static void CompileInsertExpression( + Type propertyType, + QueryWriter subQuery, + WriterProxy shapeProxy, + ObjectType objectInfo, + ExpressionContext context) + { + var typename = propertyType.GetEdgeDBTypeName(); - private static void CompileInsertExpression( - Type propertyType, - QueryWriter subQuery, - WriterProxy shapeProxy, - ObjectType objectInfo, - ExpressionContext context) - { - var typename = propertyType.GetEdgeDBTypeName(); + subQuery + .Append("(insert ", typename, " { ", shapeProxy, " } "); - subQuery - .Append("(insert ", typename, " { ", shapeProxy, " } "); + ConflictUtils.GenerateExclusiveConflictStatement(subQuery, objectInfo, true); - ConflictUtils.GenerateExclusiveConflictStatement(subQuery, objectInfo, true); + subQuery + .Append(" else ( select ", typename, "))"); + } - subQuery - .Append(" else ( select ", typename, "))"); - } + private static void AppendInitialization(QueryWriter writer, string name, string? value = null) => + writer + .Append(name, " := ", value ?? "{}"); - private static void AppendInitialization(QueryWriter writer, string name, string? value = null) + public static void Translate( + List<(EdgeDBPropertyInfo, Expression)> expressions, + ExpressionContext context, + QueryWriter writer) + { + if (context.WrapNewExpressionInBrackets) { - writer - .Append(name, " := ", value ?? "{}"); + writer.Wrapped(Token.Of(writer => TranslateInternal(expressions, context, writer)), "{}", true); + return; } - public static void Translate( - List<(EdgeDBPropertyInfo, Expression)> expressions, - ExpressionContext context, - QueryWriter writer) + TranslateInternal(expressions, context, writer); + } + + private static void TranslateInternal( + List<(EdgeDBPropertyInfo, Expression)> expressions, + ExpressionContext context, + QueryWriter writer) + { + for (var i = 0; i != expressions.Count; i++) { - if (context.WrapNewExpressionInBrackets) - { - writer.Wrapped(Token.Of(writer => TranslateInternal(expressions, context, writer)), "{}", true); - return; - } + var (property, expression) = expressions[i]; - TranslateInternal(expressions, context, writer); - } + // get the members type and edgedb equivalent name + var isLink = EdgeDBTypeUtils.IsLink(property.Type, out var isMultiLink, out var innerType); + var disassembled = ExpressionUtils.DisassembleExpression(expression).ToArray(); - private static void TranslateInternal( - List<(EdgeDBPropertyInfo, Expression)> expressions, - ExpressionContext context, - QueryWriter writer) - { - for (var i = 0; i != expressions.Count; i++) + switch (expression) { - var (property, expression) = expressions[i]; + case MemberExpression when isLink && isMultiLink: + { + if (disassembled.Last() is ConstantExpression constant && + disassembled[^2] is MemberExpression constParent) + { + if (!property.Type.IsAssignableTo(typeof(IEnumerable))) + throw new NotSupportedException( + $"cannot use {property.Type} as a multi link collection type; its not assignable to IEnumerable."); - // get the members type and edgedb equivalent name - var isLink = EdgeDBTypeUtils.IsLink(property.Type, out var isMultiLink, out var innerType); - var disassembled = ExpressionUtils.DisassembleExpression(expression).ToArray(); + // get the value + var memberValue = constParent.Member.GetMemberValue(constant.Value); - switch (expression) - { - case MemberExpression when isLink && isMultiLink: + var subQuery = GenerateMultiLinkInserter(innerType!, (IEnumerable)memberValue!, context); + + if (subQuery == null) { - if (disassembled.Last() is ConstantExpression constant && disassembled[^2] is MemberExpression constParent) - { - if (!property.Type.IsAssignableTo(typeof(IEnumerable))) - throw new NotSupportedException($"cannot use {property.Type} as a multi link collection type; its not assignable to IEnumerable."); - - // get the value - var memberValue = constParent.Member.GetMemberValue(constant.Value); - - var subQuery = GenerateMultiLinkInserter(innerType!, (IEnumerable)memberValue!, context); - - if (subQuery == null) - { - AppendInitialization(writer, property.EdgeDBName); - break; - } - - AppendInitialization( - writer, - property.EdgeDBName, - context.GetOrAddGlobal(memberValue, subQuery) - ); - } + AppendInitialization(writer, property.EdgeDBName); + break; } - break; - case MemberExpression when isLink && !isMultiLink: - { - if (disassembled.Last() is ConstantExpression constant && disassembled[^2] is MemberExpression constParent) - { - // get the value - var memberValue = constParent.Member.GetMemberValue(constant.Value); - - // check if its a global value we've already got a query for - if (context.TryGetGlobal(memberValue, out var global)) - { - AppendInitialization(writer, property.EdgeDBName, global.Name); - break; - } - - // TODO: revisit references - // check if its a value returned in a previous query - //if (QueryObjectManager.TryGetObjectId(memberValue, out var id)) - //{ - // var globalName = context.GetOrAddGlobal(id, id.SelectSubQuery(property.Type)); - // initializations.Add($"{property.EdgeDBName} := {globalName}"); - // break; - //} - - // generate an insert or select based on its unique constraints. - var name = QueryUtils.GenerateRandomVariableName(); - context.SetGlobal(name, new SubQuery((info, subQuery) => - { - if (!info.TryGetObjectInfo(property.Type, out var objInfo)) - throw new InvalidOperationException( - $"No schema type found for {property.Type}" - ); - - CompileInsertExpression( - property.Type, - subQuery, - ExpressionTranslator.Proxy( - QueryGenerationUtils.GenerateInsertShapeExpression(memberValue, property.Type), - context - ), - objInfo, - context - ); - }), memberValue); - - AppendInitialization(writer, property.PropertyName, name); - } - else if (disassembled.Last().Type.IsAssignableTo(typeof(QueryContext))) - { - writer - .Append(property.EdgeDBName, " := "); - - ExpressionTranslator.ContextualTranslate(expression, context, writer); - } - else - throw new InvalidOperationException($"Cannot translate {expression}"); - } - break; - case not null when property.CustomConverter is not null: + AppendInitialization( + writer, + property.EdgeDBName, + context.GetOrAddGlobal(memberValue, subQuery) + ); + } + } + break; + case MemberExpression when isLink && !isMultiLink: + { + if (disassembled.Last() is ConstantExpression constant && + disassembled[^2] is MemberExpression constParent) + { + // get the value + var memberValue = constParent.Member.GetMemberValue(constant.Value); + + // check if its a global value we've already got a query for + if (context.TryGetGlobal(memberValue, out var global)) { - // get the value, convert it, and parameterize it - if (!EdgeDBTypeUtils.TryGetScalarType(property.CustomConverter.Target, out var scalar)) - throw new ArgumentException($"Cannot resolve scalar type for {property.CustomConverter.Target}"); - - var expressionResult = Expression.Lambda(expression).Compile().DynamicInvoke(); - var converted = property.CustomConverter.ConvertTo(expressionResult); - var varName = context.AddVariable(converted); - - writer - .Append(property.EdgeDBName, " := ") - .QueryArgument(scalar.ToString(), varName); + AppendInitialization(writer, property.EdgeDBName, global.Name); + break; } - break; - case MemberInitExpression or NewExpression: + + // TODO: revisit references + // check if its a value returned in a previous query + //if (QueryObjectManager.TryGetObjectId(memberValue, out var id)) + //{ + // var globalName = context.GetOrAddGlobal(id, id.SelectSubQuery(property.Type)); + // initializations.Add($"{property.EdgeDBName} := {globalName}"); + // break; + //} + + // generate an insert or select based on its unique constraints. + var name = QueryUtils.GenerateRandomVariableName(); + context.SetGlobal(name, new SubQuery((info, subQuery) => { - var name = QueryUtils.GenerateRandomVariableName(); - context.SetGlobal(name, new SubQuery((info, subQuery) => - { - if (!info.TryGetObjectInfo(property.Type, out var objInfo)) - throw new InvalidOperationException($"No schema type found for {property.Type}"); - - CompileInsertExpression( - property.Type, - subQuery, - str => ExpressionTranslator.ContextualTranslate(expression, context, str), - objInfo, - context + if (!info.TryGetObjectInfo(property.Type, out var objInfo)) + throw new InvalidOperationException( + $"No schema type found for {property.Type}" ); - }), null); - AppendInitialization(writer, property.EdgeDBName, name); - } - break; - default: - { - // translate the value and determine if were setting a value or referencing a value. - var newContext = context.Enter(x => - { - x.LocalScope = property.Type; - x.IsShape = false; - }); - - var isSetter = !(context.NodeContext is not InsertContext and not UpdateContext && - context.NodeContext.CurrentType.GetProperty(property.PropertyName) != null && - expression is not MethodCallExpression); - - var value = ExpressionTranslator.Proxy(expression!, context); - - if (newContext.IsShape) - { - // add the start and end shape form. - writer - .Append(property.EdgeDBName, ": {", value, '}'); - } - else if ((isSetter || context.IsFreeObject) && newContext.UseInitializationOperator) - { - writer - .Append(property.EdgeDBName, " := ", value); - } - else - { - writer.Append(value); - } - } - break; + CompileInsertExpression( + property.Type, + subQuery, + ExpressionTranslator.Proxy( + QueryGenerationUtils.GenerateInsertShapeExpression(memberValue, property.Type), + context + ), + objInfo, + context + ); + }), memberValue); + + AppendInitialization(writer, property.PropertyName, name); + } + else if (disassembled.Last().Type.IsAssignableTo(typeof(QueryContext))) + { + writer + .Append(property.EdgeDBName, " := "); + + ExpressionTranslator.ContextualTranslate(expression, context, writer); + } + else + throw new InvalidOperationException($"Cannot translate {expression}"); } - - if (i + 1 != expressions.Count) - writer.Append(", "); + break; + case not null when property.CustomConverter is not null: + { + // get the value, convert it, and parameterize it + if (!EdgeDBTypeUtils.TryGetScalarType(property.CustomConverter.Target, out var scalar)) + throw new ArgumentException( + $"Cannot resolve scalar type for {property.CustomConverter.Target}"); + + var expressionResult = Expression.Lambda(expression).Compile().DynamicInvoke(); + var converted = property.CustomConverter.ConvertTo(expressionResult); + var varName = context.AddVariable(converted); + + writer + .Append(property.EdgeDBName, " := ") + .QueryArgument(scalar.ToString(), varName); + } + break; + case MemberInitExpression or NewExpression: + { + var name = QueryUtils.GenerateRandomVariableName(); + context.SetGlobal(name, new SubQuery((info, subQuery) => + { + if (!info.TryGetObjectInfo(property.Type, out var objInfo)) + throw new InvalidOperationException($"No schema type found for {property.Type}"); + + CompileInsertExpression( + property.Type, + subQuery, + str => ExpressionTranslator.ContextualTranslate(expression, context, str), + objInfo, + context + ); + }), null); + + AppendInitialization(writer, property.EdgeDBName, name); + } + break; + default: + { + // translate the value and determine if were setting a value or referencing a value. + var newContext = context.Enter(x => + { + x.LocalScope = property.Type; + x.IsShape = false; + }); + + var isSetter = !(context.NodeContext is not InsertContext and not UpdateContext && + context.NodeContext.CurrentType.GetProperty(property.PropertyName) != null && + expression is not MethodCallExpression); + + var value = ExpressionTranslator.Proxy(expression!, context); + + if (newContext.IsShape) + { + // add the start and end shape form. + writer + .Append(property.EdgeDBName, ": {", value, '}'); + } + else if ((isSetter || context.IsFreeObject) && newContext.UseInitializationOperator) + { + writer + .Append(property.EdgeDBName, " := ", value); + } + else + { + writer.Append(value); + } + } + break; } + + if (i + 1 != expressions.Count) + writer.Append(", "); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/LambdaExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/LambdaExpressionTranslator.cs index d35fd1c4..3eabaa51 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/LambdaExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/LambdaExpressionTranslator.cs @@ -1,28 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +/// +/// Represents a translator for translating a lambda expression. +/// +internal class LambdaExpressionTranslator : ExpressionTranslator { - /// - /// Represents a translator for translating a lambda expression. - /// - internal class LambdaExpressionTranslator : ExpressionTranslator + /// + public override void Translate(LambdaExpression expression, ExpressionContext context, QueryWriter result) { - /// - public override void Translate(LambdaExpression expression, ExpressionContext context, QueryWriter result) - { - // create a new context and translate the body of the lambda. - var newContext = - new ExpressionContext(context.NodeContext, expression, context.QueryArguments, context.Globals); + // create a new context and translate the body of the lambda. + var newContext = + new ExpressionContext(context.NodeContext, expression, context.QueryArguments, context.Globals); - newContext.ParameterPrefixes = context.ParameterPrefixes; - newContext.ParameterAliases = context.ParameterAliases; + newContext.ParameterPrefixes = context.ParameterPrefixes; + newContext.ParameterAliases = context.ParameterAliases; - TranslateExpression(expression.Body, newContext, result); - } + TranslateExpression(expression.Body, newContext, result); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ListInitExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ListInitExpressionTranslator.cs index 70166c85..efd6f7b6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ListInitExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ListInitExpressionTranslator.cs @@ -4,8 +4,7 @@ namespace EdgeDB.Translators.Expressions; internal sealed class ListInitExpressionTranslator : ExpressionTranslator { - public override void Translate(ListInitExpression expression, ExpressionContext context, QueryWriter writer) - { + public override void Translate(ListInitExpression expression, ExpressionContext context, QueryWriter writer) => writer.Wrapped(writer => { for (var i = 0; i != expression.Initializers.Count - 1; i++) @@ -16,7 +15,6 @@ public override void Translate(ListInitExpression expression, ExpressionContext WriteInitializer(writer, expression.Initializers[^1], context); }, "{}"); - } private void WriteInitializer(QueryWriter writer, ElementInit initializer, ExpressionContext context) { @@ -28,7 +26,7 @@ private void WriteInitializer(QueryWriter writer, ElementInit initializer, Expre { writer.Wrapped(Token.Of(writer => { - for (int i = 0; i < initializer.Arguments.Count - 1; i++) + for (var i = 0; i < initializer.Arguments.Count - 1; i++) { writer.Append(Proxy(initializer.Arguments[i], context), ", "); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index c5c14dae..efa79de7 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -1,219 +1,214 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +/// +/// Represents a translator for translating an expression accessing a field or property. +/// +internal class MemberExpressionTranslator : ExpressionTranslator { - /// - /// Represents a translator for translating an expression accessing a field or property. - /// - internal class MemberExpressionTranslator : ExpressionTranslator + private (Expression BaseExpression, MemberExpression[] Path) DisassembleExpression(MemberExpression member) { - private (Expression BaseExpression, MemberExpression[] Path) DisassembleExpression(MemberExpression member) + var path = new List {member}; + var current = member.Expression; + + while (current is MemberExpression element) { - var path = new List() { member }; - var current = member.Expression; - - while (current is MemberExpression element) - { - path.Add(element); - current = element.Expression; - } - - return ( - current ?? path.Last(), - path.ToArray() - ); + path.Add(element); + current = element.Expression; } - /// - public override void Translate(MemberExpression expression, ExpressionContext context, QueryWriter writer) + return ( + current ?? path.Last(), + path.ToArray() + ); + } + + /// + public override void Translate(MemberExpression expression, ExpressionContext context, QueryWriter writer) + { + // deconstruct the member access tree. + var (baseExpression, path) = DisassembleExpression(expression); + + if (PropertyTranslationTable.TryGetTranslator(path[0], out var translator)) { - // deconstruct the member access tree. - var (baseExpression, path) = DisassembleExpression(expression); - - if (PropertyTranslationTable.TryGetTranslator(path[0], out var translator)) - { - translator(writer, path[0], context); - return; - } - - switch (baseExpression) - { - case { } contextParam when contextParam.Type.IsAssignableTo(typeof(IQueryContext)): - TranslateContextMember(writer, contextParam, path, context); - break; - case ParameterExpression param: - TranslateParameterMember(writer, param, path, context); - break; - case ConstantExpression jsonConstant - when jsonConstant.Type.IsAssignableTo(typeof(IJsonVariable)) || - ( - jsonConstant.Type.GenericTypeArguments.Length == 1 && - jsonConstant.Type.IsAssignableTo(typeof(IStrongBox)) && - jsonConstant.Type.GenericTypeArguments[0].IsAssignableTo(typeof(IJsonVariable)) - ): - TranslateJsonMember(writer, jsonConstant, path, context); - break; - case ConstantExpression constant: - TranslateMemberAccess(writer, constant, path, context); - break; - // static field/property - case MemberExpression {Expression: null}: - TranslateMemberAccess(writer, null, path, context); - break; - default: - TranslateExpression(baseExpression, context, writer); - writer.Append('.'); - WritePath(writer, path); - break; - } + translator(writer, path[0], context); + return; } - private void TranslateJsonMember(QueryWriter writer, ConstantExpression expression, MemberExpression[] path, - ExpressionContext context) + switch (baseExpression) { - if (expression.Value is not IJsonVariable jsonVariable) - { - if (expression.Value is IStrongBox {Value: IJsonVariable boxedJsonVariable}) - jsonVariable = boxedJsonVariable; - else - throw new InvalidOperationException("Could not extract json variable reference"); - } - - var jsonpath = path[..^2]; - - if (!EdgeDBTypeUtils.TryGetScalarType(jsonpath[0].Type, out var scalar)) - throw new InvalidOperationException($"Expecting a scalar value, found {jsonpath[0].Type.Name}"); - - var args = new Terms.FunctionArg[jsonpath.Length + 1]; - - args[0] = jsonVariable.Name; - - for (var i = 0; i != jsonpath.Length; i++) - { - var name = jsonpath[^(i + 1)].Member.Name; - args[i + 1] = Token.Of(writer => writer.SingleQuoted(name)); - } - - writer - .TypeCast(scalar.ToString(), new CastMetadata(scalar, scalar.ToString())) - .Function( - "json_get", - debug: Defer.This(() => $"Json member path"), - args - ); + case { } contextParam when contextParam.Type.IsAssignableTo(typeof(IQueryContext)): + TranslateContextMember(writer, contextParam, path, context); + break; + case ParameterExpression param: + TranslateParameterMember(writer, param, path, context); + break; + case ConstantExpression jsonConstant + when jsonConstant.Type.IsAssignableTo(typeof(IJsonVariable)) || + ( + jsonConstant.Type.GenericTypeArguments.Length == 1 && + jsonConstant.Type.IsAssignableTo(typeof(IStrongBox)) && + jsonConstant.Type.GenericTypeArguments[0].IsAssignableTo(typeof(IJsonVariable)) + ): + TranslateJsonMember(writer, jsonConstant, path, context); + break; + case ConstantExpression constant: + TranslateMemberAccess(writer, constant, path, context); + break; + // static field/property + case MemberExpression {Expression: null}: + TranslateMemberAccess(writer, null, path, context); + break; + default: + TranslateExpression(baseExpression, context, writer); + writer.Append('.'); + WritePath(writer, path); + break; } + } - private void TranslateMemberAccess(QueryWriter writer, ConstantExpression? instance, MemberExpression[] path, - ExpressionContext context) + private void TranslateJsonMember(QueryWriter writer, ConstantExpression expression, MemberExpression[] path, + ExpressionContext context) + { + if (expression.Value is not IJsonVariable jsonVariable) { - if (!EdgeDBTypeUtils.TryGetScalarType(path[0].Type, out var edgeqlType)) - throw new NotSupportedException($"The type {path[0].Type} cannot be used as a query argument"); + if (expression.Value is IStrongBox {Value: IJsonVariable boxedJsonVariable}) + jsonVariable = boxedJsonVariable; + else + throw new InvalidOperationException("Could not extract json variable reference"); + } - var refHolder = instance?.Value; + var jsonpath = path[..^2]; - for (var i = path.Length - 1; i >= 0; i--) - { - refHolder = path[i].Member.GetMemberValue(refHolder); - } + if (!EdgeDBTypeUtils.TryGetScalarType(jsonpath[0].Type, out var scalar)) + throw new InvalidOperationException($"Expecting a scalar value, found {jsonpath[0].Type.Name}"); - writer.QueryArgument(edgeqlType.ToString(), context.AddVariable(refHolder)); - } + var args = new Terms.FunctionArg[jsonpath.Length + 1]; + + args[0] = jsonVariable.Name; - private void TranslateParameterMember(QueryWriter writer, ParameterExpression parameter, MemberExpression[] path, ExpressionContext context) + for (var i = 0; i != jsonpath.Length; i++) { - if (context.ParameterAliases.TryGetValue(parameter, out var alias) && context.IncludeSelfReference) - writer.Append(alias); - else if (context.ParameterPrefixes.TryGetValue(parameter, out var prefix)) - writer.Append(prefix); - else if (context.IncludeSelfReference) - writer.Append('.'); + var name = jsonpath[^(i + 1)].Member.Name; + args[i + 1] = Token.Of(writer => writer.SingleQuoted(name)); + } + + writer + .TypeCast(scalar.ToString(), new CastMetadata(scalar, scalar.ToString())) + .Function( + "json_get", + Defer.This(() => "Json member path"), + args + ); + } + + private void TranslateMemberAccess(QueryWriter writer, ConstantExpression? instance, MemberExpression[] path, + ExpressionContext context) + { + if (!EdgeDBTypeUtils.TryGetScalarType(path[0].Type, out var edgeqlType)) + throw new NotSupportedException($"The type {path[0].Type} cannot be used as a query argument"); - WritePath(writer, path); + var refHolder = instance?.Value; + + for (var i = path.Length - 1; i >= 0; i--) + { + refHolder = path[i].Member.GetMemberValue(refHolder); } - private void TranslateContextMember(QueryWriter writer, Expression contextExpression, MemberExpression[] path, ExpressionContext context) + writer.QueryArgument(edgeqlType.ToString(), context.AddVariable(refHolder)); + } + + private void TranslateParameterMember(QueryWriter writer, ParameterExpression parameter, MemberExpression[] path, + ExpressionContext context) + { + if (context.ParameterAliases.TryGetValue(parameter, out var alias) && context.IncludeSelfReference) + writer.Append(alias); + else if (context.ParameterPrefixes.TryGetValue(parameter, out var prefix)) + writer.Append(prefix); + else if (context.IncludeSelfReference) + writer.Append('.'); + + WritePath(writer, path); + } + + private void TranslateContextMember(QueryWriter writer, Expression contextExpression, MemberExpression[] path, + ExpressionContext context) + { + var contextAccessor = path[^1]; + + switch (contextAccessor.Member.Name) { - var contextAccessor = path[^1]; - - switch (contextAccessor.Member.Name) - { - case nameof(IQueryContextSelf.Self): - case nameof(IQueryContextUsing.Using): - WritePath(writer, path[..^1], contextAccessor.Member.Name == nameof(IQueryContextSelf.Self)); - break; - case nameof(IQueryContextVars.Variables): - var target = path[^2]; - if (!context.TryGetGlobal(target.Member.Name, out var global)) - throw new InvalidOperationException($"Unknown global in 'with' access of '{target.Member.Name}'"); - - context.Node?.ReferencedGlobals.Add(global); - - if (target.Type.IsAssignableTo(typeof(IJsonVariable))) + case nameof(IQueryContextSelf.Self): + case nameof(IQueryContextUsing.Using): + WritePath(writer, path[..^1], contextAccessor.Member.Name == nameof(IQueryContextSelf.Self)); + break; + case nameof(IQueryContextVars.Variables): + var target = path[^2]; + if (!context.TryGetGlobal(target.Member.Name, out var global)) + throw new InvalidOperationException($"Unknown global in 'with' access of '{target.Member.Name}'"); + + context.Node?.ReferencedGlobals.Add(global); + + if (target.Type.IsAssignableTo(typeof(IJsonVariable))) + { + // we go up to three because the real access looks like `ctx.Self.Value.XXXX.YY.ZZ...` + var jsonPath = path[..^3]; + + if (global.Reference is not IJsonVariable) + throw new InvalidOperationException($"The global '{global.Name}' is not a json value"); + + if (!EdgeDBTypeUtils.TryGetScalarType(path[0].Type, out var edgeqlType)) + throw new InvalidOperationException("The json value must be a scalar type"); + + var args = new Terms.FunctionArg[jsonPath.Length + 1]; + args[0] = new Terms.FunctionArg(global.Name); + + for (var i = jsonPath.Length - 1; i >= 0; i--) { - // we go up to three because the real access looks like `ctx.Self.Value.XXXX.YY.ZZ...` - var jsonPath = path[..^3]; - - if (global.Reference is not IJsonVariable) - throw new InvalidOperationException($"The global '{global.Name}' is not a json value"); - - if (!EdgeDBTypeUtils.TryGetScalarType(path[0].Type, out var edgeqlType)) - throw new InvalidOperationException("The json value must be a scalar type"); - - var args = new Terms.FunctionArg[jsonPath.Length + 1]; - args[0] = new Terms.FunctionArg(global.Name); - - for (var i = jsonPath.Length - 1; i >= 0; i--) - { - var pathRef = jsonPath[i].Member.Name; - args[i + 1] = Token.Of(writer => writer.SingleQuoted(pathRef)); - } - - writer - .TypeCast(edgeqlType.ToString()) - .Function( - "json_get", - args - ); - return; + var pathRef = jsonPath[i].Member.Name; + args[i + 1] = Token.Of(writer => writer.SingleQuoted(pathRef)); } - writer.Term( - TermType.GlobalReference, - global.Name, - Defer.This(() => "Global referenced from member expression"), - new GlobalMetadata(global), - Token.Of(writer => - { - writer.Append(global.Name); - - if (path.Length < 3) return; - - writer.Append('.'); - WritePath(writer, path[..^2]); - }) - ); - break; - } - } + writer + .TypeCast(edgeqlType.ToString()) + .Function( + "json_get", + args + ); + return; + } + + writer.Term( + TermType.GlobalReference, + global.Name, + Defer.This(() => "Global referenced from member expression"), + new GlobalMetadata(global), + Token.Of(writer => + { + writer.Append(global.Name); - private void WritePath(QueryWriter writer, MemberExpression[] path, bool prefixWithDot = false) - { - if (prefixWithDot) - writer.Append('.'); + if (path.Length < 3) return; - for (var i = path.Length - 1; i > 0; i--) - { - writer.Append(path[i].Member.GetEdgeDBPropertyName(), '.'); - } + writer.Append('.'); + WritePath(writer, path[..^2]); + }) + ); + break; + } + } + + private void WritePath(QueryWriter writer, MemberExpression[] path, bool prefixWithDot = false) + { + if (prefixWithDot) + writer.Append('.'); - writer.Append(path[0].Member.GetEdgeDBPropertyName()); + for (var i = path.Length - 1; i > 0; i--) + { + writer.Append(path[i].Member.GetEdgeDBPropertyName(), '.'); } + + writer.Append(path[0].Member.GetEdgeDBPropertyName()); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs index 15818ced..e1efb127 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs @@ -1,47 +1,40 @@ -using EdgeDB.QueryNodes; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Translators.Expressions -{ - /// - /// Represents a translator for translating an expression calling a - /// constructor and initializing one or more members of the new object. - /// - internal class MemberInitExpressionTranslator : ExpressionTranslator - { - /// - public override void Translate(MemberInitExpression expression, ExpressionContext context, - QueryWriter writer) +namespace EdgeDB.Translators.Expressions; - { - List<(EdgeDBPropertyInfo, Expression)> expressions = []; +/// +/// Represents a translator for translating an expression calling a +/// constructor and initializing one or more members of the new object. +/// +internal class MemberInitExpressionTranslator : ExpressionTranslator +{ + /// + public override void Translate(MemberInitExpression expression, ExpressionContext context, + QueryWriter writer) - var map = new Dictionary( - EdgeDBPropertyMapInfo.Create(expression.Type).Properties - .Select(x => new KeyValuePair(x.PropertyInfo, x))); + { + List<(EdgeDBPropertyInfo, Expression)> expressions = []; - // ctor - // TODO: custom type converters? - //if(expression.NewExpression.Arguments is not null && expression.NewExpression.Arguments.Any()) - // for(int i = 0; i != expression.NewExpression.Arguments.Count; i++) - // expressions.Add((expression.NewExpression.Members![i], expression.NewExpression.Arguments[i])); + var map = new Dictionary( + EdgeDBPropertyMapInfo.Create(expression.Type).Properties + .Select(x => new KeyValuePair(x.PropertyInfo, x))); - // members - foreach (var binding in expression.Bindings.Where(x => x is MemberAssignment).Cast()) - { - if (binding.Member is not PropertyInfo propInfo || !map.TryGetValue(propInfo, out var edgedbPropInfo)) - continue; + // ctor + // TODO: custom type converters? + //if(expression.NewExpression.Arguments is not null && expression.NewExpression.Arguments.Any()) + // for(int i = 0; i != expression.NewExpression.Arguments.Count; i++) + // expressions.Add((expression.NewExpression.Members![i], expression.NewExpression.Arguments[i])); - expressions.Add((edgedbPropInfo, binding.Expression)); - } + // members + foreach (var binding in expression.Bindings.Where(x => x is MemberAssignment).Cast()) + { + if (binding.Member is not PropertyInfo propInfo || !map.TryGetValue(propInfo, out var edgedbPropInfo)) + continue; - InitializationTranslator.Translate(expressions, context, writer); + expressions.Add((edgedbPropInfo, binding.Expression)); } + + InitializationTranslator.Translate(expressions, context, writer); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index 162f1e5c..c38d9190 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -1,82 +1,75 @@ -using EdgeDB.QueryNodes; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +/// +/// Represents a translator for translating an expression with a method +/// call to either static or an instance method. +/// +internal class MethodCallExpressionTranslator : ExpressionTranslator { - /// - /// Represents a translator for translating an expression with a method - /// call to either static or an instance method. - /// - internal class MethodCallExpressionTranslator : ExpressionTranslator + private bool IsIllegalToInvoke(MethodCallExpression expression, ExpressionContext context) { - private bool IsIllegalToInvoke(MethodCallExpression expression, ExpressionContext context) - { - // if any arguments reference context or json variables or root parameters - if (expression.Arguments.Any(x => - { - var disassembled = ExpressionUtils.DisassembleExpression(x); - - return disassembled.Any(y => - y.Type.IsAssignableTo(typeof(IQueryContext)) || y.Type.IsAssignableTo(typeof(IJsonVariable)) || - context.RootExpression.Parameters.Contains(y)); - })) - return true; - - if(expression.Object is not null) + // if any arguments reference context or json variables or root parameters + if (expression.Arguments.Any(x => { - var disassembledObject = ExpressionUtils.DisassembleExpression(expression.Object).ToArray(); + var disassembled = ExpressionUtils.DisassembleExpression(x); - // if instance references context or json variables - if (disassembledObject.Any(x => - x.Type.IsAssignableTo(typeof(IQueryContext)) || x.Type.IsAssignableTo(typeof(IJsonVariable)))) - return true; + return disassembled.Any(y => + y.Type.IsAssignableTo(typeof(IQueryContext)) || y.Type.IsAssignableTo(typeof(IJsonVariable)) || + context.RootExpression.Parameters.Contains(y)); + })) + return true; - // if instance references any root expression parameters. - if (context.RootExpression.Parameters.Any(x => disassembledObject.Contains(x))) - return true; - } + if (expression.Object is not null) + { + var disassembledObject = ExpressionUtils.DisassembleExpression(expression.Object).ToArray(); + + // if instance references context or json variables + if (disassembledObject.Any(x => + x.Type.IsAssignableTo(typeof(IQueryContext)) || x.Type.IsAssignableTo(typeof(IJsonVariable)))) + return true; - return false; + // if instance references any root expression parameters. + if (context.RootExpression.Parameters.Any(x => disassembledObject.Contains(x))) + return true; } - public override void Translate(MethodCallExpression expression, ExpressionContext context, - QueryWriter writer) - { - // figure out if the method is something we should translate or something that we should - // call to pull the result from. - if (MethodTranslator.TryGetTranslator(expression, out var translator)) - { - translator.Translate(writer, expression, context); - return; - } + return false; + } - if(IsIllegalToInvoke(expression, context)) - throw new InvalidOperationException($"No translator could be found for {expression.Method.Name}, and it's illegal to invoke."); + public override void Translate(MethodCallExpression expression, ExpressionContext context, + QueryWriter writer) + { + // figure out if the method is something we should translate or something that we should + // call to pull the result from. + if (MethodTranslator.TryGetTranslator(expression, out var translator)) + { + translator.Translate(writer, expression, context); + return; + } - // invoke and translate the result - var expressionResult = Expression - .Lambda(expression, context.RootExpression.Parameters) - .Compile() - .DynamicInvoke(new object[context.RootExpression.Parameters.Count]); + if (IsIllegalToInvoke(expression, context)) + throw new InvalidOperationException( + $"No translator could be found for {expression.Method.Name}, and it's illegal to invoke."); - // attempt to get the scalar type of the result of the method. - if (!EdgeDBTypeUtils.TryGetScalarType(expression.Type, out var type)) - { - // if we can't, add it as a global - writer.Term(TermType.GlobalReference, context.GetOrAddGlobal(expression, expressionResult)); - return; - //throw new InvalidOperationException("Expected a scalar type for "); - } + // invoke and translate the result + var expressionResult = Expression + .Lambda(expression, context.RootExpression.Parameters) + .Compile() + .DynamicInvoke(new object[context.RootExpression.Parameters.Count]); - // return the variable name containing the result of the method. - var varName = context.AddVariable(expressionResult); - writer.QueryArgument(type.ToString(), varName); + // attempt to get the scalar type of the result of the method. + if (!EdgeDBTypeUtils.TryGetScalarType(expression.Type, out var type)) + { + // if we can't, add it as a global + writer.Term(TermType.GlobalReference, context.GetOrAddGlobal(expression, expressionResult)); + return; + //throw new InvalidOperationException("Expected a scalar type for "); } + + // return the variable name containing the result of the method. + var varName = context.AddVariable(expressionResult); + writer.QueryArgument(type.ToString(), varName); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs index 9480eebc..55f55fda 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs @@ -1,36 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +/// +/// Represents a translator for translating an expression creating a new array and +/// possibly initializing the elements of the new array. +/// +internal class NewArrayExpressionTranslator : ExpressionTranslator { - /// - /// Represents a translator for translating an expression creating a new array and - /// possibly initializing the elements of the new array. - /// - internal class NewArrayExpressionTranslator : ExpressionTranslator + /// + public override void Translate(NewArrayExpression expression, ExpressionContext context, QueryWriter writer) { - /// - public override void Translate(NewArrayExpression expression, ExpressionContext context, QueryWriter writer) - { - var brackets = EdgeDBTypeUtils.IsLink(expression.Type, out _, out _) - ? "{}" - : "[]"; - - writer.Append(brackets[0]); + var brackets = EdgeDBTypeUtils.IsLink(expression.Type, out _, out _) + ? "{}" + : "[]"; - for (var i = 0; i != expression.Expressions.Count;) - { - TranslateExpression(expression.Expressions[i], context, writer); + writer.Append(brackets[0]); - if (i++ < expression.Expressions.Count) - writer.Append(','); - } + for (var i = 0; i != expression.Expressions.Count;) + { + TranslateExpression(expression.Expressions[i], context, writer); - writer.Append(brackets[1]); + if (i++ < expression.Expressions.Count) + writer.Append(','); } + + writer.Append(brackets[1]); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs index 7269a4ca..7b049807 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs @@ -1,41 +1,35 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +/// +/// Represents a translator for translating an expression with a constructor call. +/// +internal class NewExpressionTranslator : ExpressionTranslator { - /// - /// Represents a translator for translating an expression with a constructor call. - /// - internal class NewExpressionTranslator : ExpressionTranslator + /// + public override void Translate(NewExpression expression, ExpressionContext context, QueryWriter writer) { - /// - public override void Translate(NewExpression expression, ExpressionContext context, QueryWriter writer) - { - var map = new Dictionary( - EdgeDBPropertyMapInfo.Create(expression.Type).Properties + var map = new Dictionary( + EdgeDBPropertyMapInfo.Create(expression.Type).Properties .Select(x => new KeyValuePair(x.PropertyInfo, x))); - List<(EdgeDBPropertyInfo, Expression)> expressions = new(); - - for(int i = 0; i != expression.Members!.Count; i++) - { - var binding = expression.Members[i]; - var value = expression.Arguments[i]; + List<(EdgeDBPropertyInfo, Expression)> expressions = new(); - if (binding is not PropertyInfo propInfo || !map.TryGetValue(propInfo, out var edgedbPropInfo)) - continue; + for (var i = 0; i != expression.Members!.Count; i++) + { + var binding = expression.Members[i]; + var value = expression.Arguments[i]; - expressions.Add((edgedbPropInfo, value)); - } + if (binding is not PropertyInfo propInfo || !map.TryGetValue(propInfo, out var edgedbPropInfo)) + continue; - InitializationTranslator.Translate( - expressions, context, writer - ); + expressions.Add((edgedbPropInfo, value)); } + + InitializationTranslator.Translate( + expressions, context, writer + ); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ParameterExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ParameterExpressionTranslator.cs index 8246e60b..7ef5cd92 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ParameterExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ParameterExpressionTranslator.cs @@ -1,34 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +/// +/// Represents a translator for translating a parameter within a lambda function. +/// +/// +/// This translator is only called when a parameter is directly referenced, normally +/// A parameter reference is accessed which will cause a .x to be added where as +/// this translator will just serialize the parameters name. +/// +internal class ParameterExpressionTranslator : ExpressionTranslator { - /// - /// Represents a translator for translating a parameter within a lambda function. - /// - /// - /// This translator is only called when a parameter is directly referenced, normally - /// A parameter reference is accessed which will cause a .x to be added where as - /// this translator will just serialize the parameters name. - /// - internal class ParameterExpressionTranslator : ExpressionTranslator + /// + public override void Translate(ParameterExpression expression, ExpressionContext context, QueryWriter writer) { - /// - public override void Translate(ParameterExpression expression, ExpressionContext context, QueryWriter writer) - { - Token name = expression.Name; + Token name = expression.Name; - if (context.ParameterAliases.TryGetValue(expression, out var alias)) - name = alias; + if (context.ParameterAliases.TryGetValue(expression, out var alias)) + name = alias; - if (context.ParameterPrefixes.TryGetValue(expression, out var prefix)) - writer.Append(prefix); + if (context.ParameterPrefixes.TryGetValue(expression, out var prefix)) + writer.Append(prefix); - writer.Append(name!); - } + writer.Append(name!); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs index 53e64e68..0af7fc69 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs @@ -1,72 +1,66 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Translators.Expressions +namespace EdgeDB.Translators.Expressions; + +/// +/// Represents a translator for translating an expression with a unary operator. +/// +internal class UnaryExpressionTranslator : ExpressionTranslator { - /// - /// Represents a translator for translating an expression with a unary operator. - /// - internal class UnaryExpressionTranslator : ExpressionTranslator + /// + public override void Translate(UnaryExpression expression, ExpressionContext context, QueryWriter writer) { - /// - public override void Translate(UnaryExpression expression, ExpressionContext context, QueryWriter writer) + switch (expression.NodeType) { - switch (expression.NodeType) + // quote expressions are literal funcs (im pretty sure), so we can just + // directly translate them and return the result. + case ExpressionType.Quote when expression.Operand is LambdaExpression lambda: + TranslateExpression(lambda.Body, context.Enter(x => x.StringWithoutQuotes = false), writer); + return; + // convert is a type change, so we translate the dotnet form '(type)value' to 'value' + case ExpressionType.Convert: { - // quote expressions are literal funcs (im pretty sure), so we can just - // directly translate them and return the result. - case ExpressionType.Quote when expression.Operand is LambdaExpression lambda: - TranslateExpression(lambda.Body, context.Enter(x => x.StringWithoutQuotes = false), writer); - return; - // convert is a type change, so we translate the dotnet form '(type)value' to 'value' - case ExpressionType.Convert: + // this is a selector-based expression converting value types to objects, for + // this case we can just return the value + if (expression.Type == typeof(object)) { - // this is a selector-based expression converting value types to objects, for - // this case we can just return the value - if (expression.Type == typeof(object)) - { - TranslateExpression(expression.Operand, context, writer); - return; - } - - // dotnet nullable check - if (ReflectionUtils.IsSubclassOfRawGeneric(typeof(Nullable<>), expression.Type) && - expression.Type.GenericTypeArguments[0] == expression.Operand.Type) - { - // no need to cast in edgedb, return the value - return; - } - - var type = EdgeDBTypeUtils.TryGetScalarType(expression.Type, out var edgedbType) - ? edgedbType.ToString() - : expression.Type.GetEdgeDBTypeName(); - - writer - .TypeCast(type, new CastMetadata(edgedbType, type)) - .Append(Proxy(expression.Operand, context)); + TranslateExpression(expression.Operand, context, writer); return; } - case ExpressionType.ArrayLength: - writer.Function( - "std::len", - Defer.This(() => $"ArrayLength expression implicit conversion"), - Token.Of(writer => TranslateExpression(expression.Operand, context, writer)) - ); - return; - // default case attempts to get an IEdgeQLOperator for the given - // node type, and uses that to translate the expression. - default: - if (!Grammar.TryBuildOperator(expression.NodeType, writer, Proxy(expression.Operand, context))) - throw new NotSupportedException($"Failed to find operator for node type {expression.NodeType}"); + // dotnet nullable check + if (ReflectionUtils.IsSubclassOfRawGeneric(typeof(Nullable<>), expression.Type) && + expression.Type.GenericTypeArguments[0] == expression.Operand.Type) + { + // no need to cast in edgedb, return the value return; + } + + var type = EdgeDBTypeUtils.TryGetScalarType(expression.Type, out var edgedbType) + ? edgedbType.ToString() + : expression.Type.GetEdgeDBTypeName(); + + writer + .TypeCast(type, new CastMetadata(edgedbType, type)) + .Append(Proxy(expression.Operand, context)); + return; } + case ExpressionType.ArrayLength: + writer.Function( + "std::len", + Defer.This(() => "ArrayLength expression implicit conversion"), + Token.Of(writer => TranslateExpression(expression.Operand, context, writer)) + ); + return; - //throw new NotSupportedException($"Failed to find converter for {expression.NodeType}!"); + // default case attempts to get an IEdgeQLOperator for the given + // node type, and uses that to translate the expression. + default: + if (!Grammar.TryBuildOperator(expression.NodeType, writer, Proxy(expression.Operand, context))) + throw new NotSupportedException($"Failed to find operator for node type {expression.NodeType}"); + return; } + + //throw new NotSupportedException($"Failed to find converter for {expression.NodeType}!"); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs index e60ed773..a33f0589 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs @@ -1,92 +1,70 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.Translators.Methods -{ - /// - /// Represents a translator for translating methods within - /// the class. - /// - internal class EnumerableMethodTranslator : MethodTranslator - { - /// - public override Type TranslatorTargetType => typeof(Enumerable); +using System.Linq.Expressions; - [MethodName(nameof(Enumerable.All))] - public void All(QueryWriter writer, TranslatedParameter source, TranslatedParameter func) - { - if (func.RawValue is not LambdaExpression lambda) - throw new ArgumentException("Expected a lambda function", nameof(func)); +namespace EdgeDB.Translators.Methods; - // any reference to the source is replaced with the source itself. - func.Context.ParameterAliases[lambda.Parameters[0]] = source; +/// +/// Represents a translator for translating methods within +/// the class. +/// +internal class EnumerableMethodTranslator : MethodTranslator +{ + /// + public override Type TranslatorTargetType => typeof(Enumerable); - writer.Function( - "all", - Defer.This(() => "Enumerable.All translation"), - new FunctionMetadata("std::all"), - func - ); - } + [MethodName(nameof(Enumerable.All))] + public void All(QueryWriter writer, TranslatedParameter source, TranslatedParameter func) + { + if (func.RawValue is not LambdaExpression lambda) + throw new ArgumentException("Expected a lambda function", nameof(func)); + + // any reference to the source is replaced with the source itself. + func.Context.ParameterAliases[lambda.Parameters[0]] = source; + + writer.Function( + "all", + Defer.This(() => "Enumerable.All translation"), + new FunctionMetadata("std::all"), + func + ); + } - [MethodName(nameof(Enumerable.Any))] - public void Any(QueryWriter writer, TranslatedParameter source, TranslatedParameter? func) + [MethodName(nameof(Enumerable.Any))] + public void Any(QueryWriter writer, TranslatedParameter source, TranslatedParameter? func) + { + if (func is null) { - if (func is null) - { - writer.Append("exists ", source); - return; - } - - if (func.RawValue is not LambdaExpression lambda) - throw new ArgumentException("Expected a lambda function", nameof(func)); - - // any reference to the source is replaced with the source itself. - func.Context.ParameterAliases[lambda.Parameters[0]] = source; - - writer.Function( - "any", - Defer.This(() => "Enumerable.Any translation"), - new FunctionMetadata("std::any"), - func - ); + writer.Append("exists ", source); + return; } - [MethodName(nameof(Enumerable.Append))] - public void Append(QueryWriter writer, TranslatedParameter source, TranslatedParameter other) - => writer.Append(source, " union ", other); + if (func.RawValue is not LambdaExpression lambda) + throw new ArgumentException("Expected a lambda function", nameof(func)); - [MethodName(nameof(Enumerable.AsEnumerable))] - public void AsEnumerable(QueryWriter writer, TranslatedParameter source) - {} // TODO: maybe do array -> set? + // any reference to the source is replaced with the source itself. + func.Context.ParameterAliases[lambda.Parameters[0]] = source; - [MethodName(nameof(Enumerable.Average))] - public void Average(QueryWriter writer, TranslatedParameter source, TranslatedParameter? func) - { - if (func is null) - { - writer.Function( - "mean", - Defer.This(() => "Enumerable.Average translation"), - new FunctionMetadata( - "std::mean", - Info.OfMethod(nameof(EdgeQL.Mean)) - ), - source - ); - return; - } + writer.Function( + "any", + Defer.This(() => "Enumerable.Any translation"), + new FunctionMetadata("std::any"), + func + ); + } - if (func.RawValue is not LambdaExpression lambda) - throw new ArgumentException("Expected a lambda function", nameof(func)); + [MethodName(nameof(Enumerable.Append))] + public void Append(QueryWriter writer, TranslatedParameter source, TranslatedParameter other) + => writer.Append(source, " union ", other); - // any reference to the source is replaced with the source itself. - func.Context.ParameterAliases[lambda.Parameters[0]] = source; + [MethodName(nameof(Enumerable.AsEnumerable))] + public void AsEnumerable(QueryWriter writer, TranslatedParameter source) + { + } // TODO: maybe do array -> set? + [MethodName(nameof(Enumerable.Average))] + public void Average(QueryWriter writer, TranslatedParameter source, TranslatedParameter? func) + { + if (func is null) + { writer.Function( "mean", Defer.This(() => "Enumerable.Average translation"), @@ -94,260 +72,272 @@ public void Average(QueryWriter writer, TranslatedParameter source, TranslatedPa "std::mean", Info.OfMethod(nameof(EdgeQL.Mean)) ), - func + source ); + return; } - [MethodName(nameof(Enumerable.Cast))] - public void Cast(QueryWriter writer, MethodCallExpression method, TranslatedParameter source) - { - var type = method.Method.GetGenericArguments()[0]; + if (func.RawValue is not LambdaExpression lambda) + throw new ArgumentException("Expected a lambda function", nameof(func)); + + // any reference to the source is replaced with the source itself. + func.Context.ParameterAliases[lambda.Parameters[0]] = source; + + writer.Function( + "mean", + Defer.This(() => "Enumerable.Average translation"), + new FunctionMetadata( + "std::mean", + Info.OfMethod(nameof(EdgeQL.Mean)) + ), + func + ); + } + + [MethodName(nameof(Enumerable.Cast))] + public void Cast(QueryWriter writer, MethodCallExpression method, TranslatedParameter source) + { + var type = method.Method.GetGenericArguments()[0]; - if (!EdgeDBTypeUtils.TryGetScalarType(type, out var info)) - throw new ArgumentException($"No scalar type information found for {type}", nameof(method)); + if (!EdgeDBTypeUtils.TryGetScalarType(type, out var info)) + throw new ArgumentException($"No scalar type information found for {type}", nameof(method)); - writer.TypeCast(info.ToString()).Append(source); - } + writer.TypeCast(info.ToString()).Append(source); + } + + [MethodName(nameof(Enumerable.Concat))] + public void Concat(QueryWriter writer, TranslatedParameter source, TranslatedParameter other) + => writer.Append(source, " ++ ", other); - [MethodName(nameof(Enumerable.Concat))] - public void Concat(QueryWriter writer, TranslatedParameter source, TranslatedParameter other) - => writer.Append(source, " ++ ", other); - - /// - /// Translates the method . - /// - /// The query string writer to append the translated method to. - /// The source collection. - /// The value to locate within the collection. - /// The EdgeQL equivalent of the method. - [MethodName(nameof(Enumerable.Contains))] - public void Contains(QueryWriter writer, TranslatedParameter source, TranslatedParameter target) + /// + /// Translates the method . + /// + /// The query string writer to append the translated method to. + /// The source collection. + /// The value to locate within the collection. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Enumerable.Contains))] + public void Contains(QueryWriter writer, TranslatedParameter source, TranslatedParameter target) + { + if (source.IsScalarArrayType || source.IsScalarType) + writer.Function("std::contains", source); + else + writer.Append(target).Append(" in ").Append(source); + } + + /// + /// Translates the method . + /// + /// The query string writer to append the translated method to. + /// The source collection to count. + /// The mapping function of what to count. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Enumerable.Count))] + public void Count(QueryWriter writer, TranslatedParameter source, TranslatedParameter? mapper) + { + if (mapper is null) { if (source.IsScalarArrayType || source.IsScalarType) - writer.Function("std::contains", source); + writer.Function( + "len", + Defer.This(() => "Scalar length of Enumerable.Count"), + new FunctionMetadata( + "std::len", + Info.OfMethod(nameof(EdgeQL.Len)) + ), + source + ); else - writer.Append(target).Append(" in ").Append(source); + writer.Function( + "count", + Defer.This(() => "count from Enumerable.Count"), + new FunctionMetadata( + "std::count", + Info.OfMethod(nameof(EdgeQL.Count)) + ), + source + ); + + return; } - /// - /// Translates the method . - /// - /// The query string writer to append the translated method to. - /// The source collection to count. - /// The mapping function of what to count. - /// The EdgeQL equivalent of the method. - [MethodName(nameof(Enumerable.Count))] - public void Count(QueryWriter writer, TranslatedParameter source, TranslatedParameter? mapper) - { - if (mapper is null) - { - if (source.IsScalarArrayType || source.IsScalarType) - writer.Function( - "len", - Defer.This(() => "Scalar length of Enumerable.Count"), - new FunctionMetadata( - "std::len", - Info.OfMethod(nameof(EdgeQL.Len)) - ), - source - ); - else - writer.Function( - "count", - Defer.This(() => "count from Enumerable.Count"), - new FunctionMetadata( - "std::count", - Info.OfMethod(nameof(EdgeQL.Count)) - ), - source - ); - - return; - } - - if (mapper.RawValue is not LambdaExpression lambda) - throw new ArgumentException("Expected a lambda function", nameof(mapper)); - - // any reference to the source is replaced with the source itself. - mapper.Context.ParameterAliases[lambda.Parameters[0]] = source; + if (mapper.RawValue is not LambdaExpression lambda) + throw new ArgumentException("Expected a lambda function", nameof(mapper)); + + // any reference to the source is replaced with the source itself. + mapper.Context.ParameterAliases[lambda.Parameters[0]] = source; + + writer.Function( + "count", + Defer.This(() => "Enumerable.Count mapping translation"), + new FunctionMetadata( + "std::count", + Info.OfMethod(nameof(EdgeQL.Count)) + ), + mapper + ); + } - writer.Function( - "count", - Defer.This(() => "Enumerable.Count mapping translation"), - new FunctionMetadata( - "std::count", - Info.OfMethod(nameof(EdgeQL.Count)) - ), - mapper - ); - } + [MethodName(nameof(Enumerable.DefaultIfEmpty))] + public void DefaultIfEmpty(QueryWriter writer, TranslatedParameter source, TranslatedParameter? defaultParam) + { + if (defaultParam is null) + writer.Append(source); + else + writer.Append(source, " ?? ", defaultParam); + } - [MethodName(nameof(Enumerable.DefaultIfEmpty))] - public void DefaultIfEmpty(QueryWriter writer, TranslatedParameter source, TranslatedParameter? defaultParam) - { - if (defaultParam is null) - writer.Append(source); - else - writer.Append(source, " ?? ", defaultParam); - } + [MethodName(nameof(Enumerable.Distinct))] + public void Distinct(QueryWriter writer, TranslatedParameter source) => writer.Append("distinct ", source); - [MethodName(nameof(Enumerable.Distinct))] - public void Distinct(QueryWriter writer, TranslatedParameter source) - { - writer.Append("distinct ", source); - } + /// + /// Translates the method . + /// + /// The query string writer to append the translated method to. + /// The source collection. + /// The index of the element to get + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Enumerable.ElementAt))] + [MethodName(nameof(Enumerable.ElementAtOrDefault))] + public void ElementAt(QueryWriter writer, TranslatedParameter source, TranslatedParameter index) => + // TODO: figure out set detection, since array_get doesn't work on sets. + writer.Function("array_get", source, index); + + [MethodName(nameof(Enumerable.Empty))] + public void Empty(QueryWriter writer, MethodCallExpression method) + { + if (!EdgeDBTypeUtils.TryGetScalarType(method.Method.GetGenericArguments()[0], out var info)) + throw new ArgumentException("Expected scalar type", nameof(method)); - /// - /// Translates the method . - /// - /// The query string writer to append the translated method to. - /// The source collection. - /// The index of the element to get - /// The EdgeQL equivalent of the method. - [MethodName(nameof(Enumerable.ElementAt))] - [MethodName(nameof(Enumerable.ElementAtOrDefault))] - public void ElementAt(QueryWriter writer, TranslatedParameter source, TranslatedParameter index) - { - // TODO: figure out set detection, since array_get doesn't work on sets. - writer.Function("array_get", source, index); - } + writer.TypeCast(info.ToString()).Append("{}"); + } - [MethodName(nameof(Enumerable.Empty))] - public void Empty(QueryWriter writer, MethodCallExpression method) - { - if (!EdgeDBTypeUtils.TryGetScalarType(method.Method.GetGenericArguments()[0], out var info)) - throw new ArgumentException("Expected scalar type", nameof(method)); + [MethodName(nameof(Enumerable.Except))] + public void Except(QueryWriter writer, TranslatedParameter source, TranslatedParameter other) => + writer.Append(source, " except ", other); - writer.TypeCast(info.ToString()).Append("{}"); - } + [MethodName(nameof(Enumerable.Select))] + public void Select(QueryWriter writer, MethodCallExpression method, TranslatedParameter source, + TranslatedParameter expressive, ExpressionContext context) + { + if (expressive.RawValue is not LambdaExpression lambda) + throw new NotSupportedException("The expressive operand of 'Ref' must be a lambda function"); - [MethodName(nameof(Enumerable.Except))] - public void Except(QueryWriter writer, TranslatedParameter source, TranslatedParameter other) + // we handle Select(Func) differently + if (method.Method.GetParameters()[1].ParameterType.GenericTypeArguments.Length == 3) { - writer.Append(source, " except ", other); - } + // we globalize the source in an `enumerate` call and then prefix any reference to `X` with GLOBAL.1 + // and any reference to the index as GLOBAL.0 + var enumerationName = QueryUtils.GenerateRandomVariableName(); - [MethodName(nameof(Enumerable.Select))] - public void Select(QueryWriter writer, MethodCallExpression method, TranslatedParameter source, TranslatedParameter expressive, ExpressionContext context) - { - if (expressive.RawValue is not LambdaExpression lambda) - throw new NotSupportedException("The expressive operand of 'Ref' must be a lambda function"); + var global = context.SetGlobal(enumerationName, new SubQuery(writer => writer + .Function( + "std::enumerate", + source + ) + ), null); - // we handle Select(Func) differently - if (method.Method.GetParameters()[1].ParameterType.GenericTypeArguments.Length == 3) + expressive.Context = expressive.Context.Enter(x => { - // we globalize the source in an `enumerate` call and then prefix any reference to `X` with GLOBAL.1 - // and any reference to the index as GLOBAL.0 - var enumerationName = QueryUtils.GenerateRandomVariableName(); - - var global = context.SetGlobal(enumerationName, new SubQuery(writer => writer - .Function( - "std::enumerate", - source - ) - ), null); - - expressive.Context = expressive.Context.Enter(x => - { - x.ParameterAliases.Add(lambda.Parameters[0], Token.Of( - writer => writer - .Term( - TermType.GlobalReference, - enumerationName, - Defer.This(() => "Enumeration global reference element for Enumerable.Select translator"), - new GlobalMetadata(global), - Token.Of(writer => writer - .Append(enumerationName, ".1.") - ) + x.ParameterAliases.Add(lambda.Parameters[0], Token.Of( + writer => writer + .Term( + TermType.GlobalReference, + enumerationName, + Defer.This( + () => "Enumeration global reference element for Enumerable.Select translator"), + new GlobalMetadata(global), + Token.Of(writer => writer + .Append(enumerationName, ".1.") ) - ) - ); - x.ParameterAliases.Add(lambda.Parameters[1], Token.Of( - writer => writer - .Term( - TermType.GlobalReference, - enumerationName, - Defer.This(() => "Enumeration global reference index for Enumerable.Select translator"), - new GlobalMetadata(global, "int64"), - Token.Of(writer => writer - .Append(enumerationName, ".0") - ) + ) + ) + ); + x.ParameterAliases.Add(lambda.Parameters[1], Token.Of( + writer => writer + .Term( + TermType.GlobalReference, + enumerationName, + Defer.This(() => "Enumeration global reference index for Enumerable.Select translator"), + new GlobalMetadata(global, "int64"), + Token.Of(writer => writer + .Append(enumerationName, ".0") ) - ) - ); - x.IncludeSelfReference = true; - }); - - } - else + ) + ) + ); + x.IncludeSelfReference = true; + }); + } + else + { + expressive.Context = expressive.Context.Enter(x => { - expressive.Context = expressive.Context.Enter(x => - { - x.ParameterPrefixes.Add(lambda.Parameters[0], writer => writer.Append(source, '.')); - x.IncludeSelfReference = true; - }); - } - - writer.Append(expressive); + x.ParameterPrefixes.Add(lambda.Parameters[0], writer => writer.Append(source, '.')); + x.IncludeSelfReference = true; + }); } - /// - /// Translates the method . - /// - /// The query string writer to append the translated method to. - /// The source collection. - /// The default value or expression. - /// The default value if the was a filter. - /// The EdgeQL equivalent of the method. - [MethodName(nameof(Enumerable.FirstOrDefault))] - public void FirstOrDefault(QueryWriter writer, TranslatedParameter source, TranslatedParameter filterOrDefault, TranslatedParameter? defaultValue) + writer.Append(expressive); + } + + /// + /// Translates the method . + /// + /// The query string writer to append the translated method to. + /// The source collection. + /// The default value or expression. + /// The default value if the was a filter. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Enumerable.FirstOrDefault))] + public void FirstOrDefault(QueryWriter writer, TranslatedParameter source, TranslatedParameter filterOrDefault, + TranslatedParameter? defaultValue) + { + if (filterOrDefault.IsScalarType) { - if (filterOrDefault.IsScalarType) - { - ElementAt( - writer, - source, - new TranslatedParameter( - typeof(long), - Expression.Constant(0L), - source.Context - ) - ); - writer - .Append(" ?? ") - .Append(filterOrDefault); + ElementAt( + writer, + source, + new TranslatedParameter( + typeof(long), + Expression.Constant(0L), + source.Context + ) + ); + writer + .Append(" ?? ") + .Append(filterOrDefault); - return; - } + return; + } - var name = ((LambdaExpression)filterOrDefault.RawValue).Parameters[0].Name; - var set = source.IsScalarArrayType ? $"array_unpack({source})" : source.ToString(); - var returnType = ((LambdaExpression)filterOrDefault.RawValue).Parameters[0].Type; + var name = ((LambdaExpression)filterOrDefault.RawValue).Parameters[0].Name; + var set = source.IsScalarArrayType ? $"array_unpack({source})" : source.ToString(); + var returnType = ((LambdaExpression)filterOrDefault.RawValue).Parameters[0].Type; - writer - .TypeCast(EdgeDBTypeUtils.GetEdgeDBScalarOrTypeName(returnType)) - .Function( - "array_get", - Token.Of(writer => writer - .Function( - "array_agg", - Token.Of(writer => writer - .Wrapped(writer => writer - .Append("select ") - .Assignment(name!, set) - .Append(" filter ") - .Append(filterOrDefault) - ) + writer + .TypeCast(EdgeDBTypeUtils.GetEdgeDBScalarOrTypeName(returnType)) + .Function( + "array_get", + Token.Of(writer => writer + .Function( + "array_agg", + Token.Of(writer => writer + .Wrapped(writer => writer + .Append("select ") + .Assignment(name!, set) + .Append(" filter ") + .Append(filterOrDefault) ) ) - ), - "0" - ); + ) + ), + "0" + ); - if (defaultValue is not null) - writer - .Append(" ?? ") - .Append(defaultValue); - } + if (defaultValue is not null) + writer + .Append(" ?? ") + .Append(defaultValue); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/GuidMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/GuidMethodTranslator.cs index 4314ad05..5c765640 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/GuidMethodTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/GuidMethodTranslator.cs @@ -1,23 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.Translators.Methods; -namespace EdgeDB.Translators.Methods +/// +/// Represents a translator for translating methods within the struct. +/// +internal class GuidMethodTranslator : MethodTranslator { /// - /// Represents a translator for translating methods within the struct. + /// Translates the method . /// - internal class GuidMethodTranslator : MethodTranslator - { - /// - /// Translates the method . - /// - /// The query string writer to append the translated method to. - /// The EdgeQL equivalent of the method. - [MethodName(nameof(Guid.NewGuid))] - public void Generate(QueryWriter writer) - => writer.Function("uuid_generate_v4"); - } + /// The query string writer to append the translated method to. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Guid.NewGuid))] + public void Generate(QueryWriter writer) + => writer.Function("uuid_generate_v4"); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslator.cs index 02e344a7..63d066c1 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslator.cs @@ -1,142 +1,134 @@ using EdgeDB.Translators.Methods; -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.Translators +namespace EdgeDB.Translators; + +/// +/// Marks this method as a valid method used to translate a . +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +internal class MethodNameAttribute : Attribute { + internal readonly bool IsParameterAware; + /// - /// Marks this method as a valid method used to translate a . + /// The method name that the current target can translate. /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - internal class MethodNameAttribute : Attribute - { - /// - /// The method name that the current target can translate. - /// - internal readonly string MethodName; + internal readonly string MethodName; - internal readonly Type[] MethodParameters; + internal readonly Type[] MethodParameters; - internal readonly bool IsParameterAware; - - /// - /// Marks this method as a valid method used to translate a . - /// - /// The name of the method that this method can translate. - /// The parameters as types of the method. - public MethodNameAttribute(string methodName, params Type[] methodParameters) - { - MethodName = methodName; - MethodParameters = methodParameters; - IsParameterAware = methodParameters.Length > 0; - } + /// + /// Marks this method as a valid method used to translate a . + /// + /// The name of the method that this method can translate. + /// The parameters as types of the method. + public MethodNameAttribute(string methodName, params Type[] methodParameters) + { + MethodName = methodName; + MethodParameters = methodParameters; + IsParameterAware = methodParameters.Length > 0; + } - public MethodNameAttribute(string methodName, bool isParameterAware) - { - MethodName = methodName; - MethodParameters = Array.Empty(); - IsParameterAware = isParameterAware; - } + public MethodNameAttribute(string methodName, bool isParameterAware) + { + MethodName = methodName; + MethodParameters = Array.Empty(); + IsParameterAware = isParameterAware; } +} + +/// +/// Represents a base method translator for a given type . +/// +/// +/// The base type containing the methods that this translator can translate. +/// +internal abstract class MethodTranslator : MethodTranslator +{ + /// + public override Type TranslatorTargetType => typeof(TBase); +} +internal abstract class MethodTranslator +{ /// - /// Represents a base method translator for a given type . + /// The dictionary containing all of the methods that the current translator + /// can translate. /// - /// - /// The base type containing the methods that this translator can translate. - /// - internal abstract class MethodTranslator : MethodTranslator - { - /// - public override Type TranslatorTargetType => typeof(TBase); - } + internal ConcurrentDictionary MethodTranslators; - internal abstract class MethodTranslator + /// + /// Constructs a new and populates + /// . + /// + public MethodTranslator() { - /// - /// Gets the base type that contains the methods the current translator can - /// translate. - /// - public abstract Type TranslatorTargetType { get; } - - /// - /// The dictionary containing all of the methods that the current translator - /// can translate. - /// - internal ConcurrentDictionary MethodTranslators; - - /// - /// Constructs a new and populates - /// . - /// - public MethodTranslator() - { - // get all methods within the current type that have at least one MethodName attribute - var methods = GetType().GetMethods().Where(x => - x.GetCustomAttributes().Any(x => x.GetType() == typeof(MethodNameAttribute))); + // get all methods within the current type that have at least one MethodName attribute + var methods = GetType().GetMethods().Where(x => + x.GetCustomAttributes().Any(x => x.GetType() == typeof(MethodNameAttribute))); - var tempDict = new Dictionary(); + var tempDict = new Dictionary(); - // iterate over the methods and add them to the temp dictionary - foreach (var method in methods) + // iterate over the methods and add them to the temp dictionary + foreach (var method in methods) + { + foreach (var att in method.GetCustomAttributes().Where(x => x is MethodNameAttribute)) { - foreach (var att in method.GetCustomAttributes().Where(x => x is MethodNameAttribute)) - { - tempDict.Add(((MethodNameAttribute)att).MethodName, method); - } + tempDict.Add(((MethodNameAttribute)att).MethodName, method); } - - // create a new concurrent dictionary from our temp one - MethodTranslators = new(tempDict); } - internal virtual bool CanTranslate(Type type) => type == TranslatorTargetType; - - internal static bool TryGetTranslator(MethodCallExpression methodCall, - out MethodTranslatorLookupTable.Handle handle) - => MethodTranslatorLookupTable.TryGetTranslator(methodCall.Method, out handle); - - /// - /// Translates the given into a edgeql equivalent expression. - /// - /// The query string writer to write the translated method to. - /// The method call expression to translate. - /// The current context for the method call expression. - /// No translator could be found for the given method expression. - public static void TranslateMethod(QueryWriter writer, MethodCallExpression methodCall, - ExpressionContext context) - { - if(!TryGetTranslator(methodCall, out var translator)) - throw new NotSupportedException($"Cannot use method {methodCall.Method} as there is no translator for it"); - - writer.LabelVerbose( - $"method_translation_{translator.GetType().Name}", - Defer.This(() => $"Translator type is {translator} picked for {methodCall.Method}"), - Token.Of(writer => translator.Translate(writer, methodCall, context)) - ); - } + // create a new concurrent dictionary from our temp one + MethodTranslators = new ConcurrentDictionary(tempDict); + } - /// - /// Includes an argument if its . - /// - /// The argument to include. - /// - /// The argument with the prefix if its ; - /// otherwise an empty string. - /// - protected Token OptionalArg(TranslatedParameter? arg) - { - if (arg is null || arg.IsNullValue) - return Token.Empty; - else - return arg; - } + /// + /// Gets the base type that contains the methods the current translator can + /// translate. + /// + public abstract Type TranslatorTargetType { get; } + + internal virtual bool CanTranslate(Type type) => type == TranslatorTargetType; + + internal static bool TryGetTranslator(MethodCallExpression methodCall, + out MethodTranslatorLookupTable.Handle handle) + => MethodTranslatorLookupTable.TryGetTranslator(methodCall.Method, out handle); + + /// + /// Translates the given into a edgeql equivalent expression. + /// + /// The query string writer to write the translated method to. + /// The method call expression to translate. + /// The current context for the method call expression. + /// No translator could be found for the given method expression. + public static void TranslateMethod(QueryWriter writer, MethodCallExpression methodCall, + ExpressionContext context) + { + if (!TryGetTranslator(methodCall, out var translator)) + throw new NotSupportedException($"Cannot use method {methodCall.Method} as there is no translator for it"); + + writer.LabelVerbose( + $"method_translation_{translator.GetType().Name}", + Defer.This(() => $"Translator type is {translator} picked for {methodCall.Method}"), + Token.Of(writer => translator.Translate(writer, methodCall, context)) + ); + } + + /// + /// Includes an argument if its . + /// + /// The argument to include. + /// + /// The argument with the prefix if its ; + /// otherwise an empty string. + /// + protected Token OptionalArg(TranslatedParameter? arg) + { + if (arg is null || arg.IsNullValue) + return Token.Empty; + return arg; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslatorLookupTable.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslatorLookupTable.cs index 130bb2f4..7b416863 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslatorLookupTable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslatorLookupTable.cs @@ -1,13 +1,136 @@ -using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; -using System.Text.RegularExpressions; namespace EdgeDB.Translators.Methods; internal static class MethodTranslatorLookupTable { + private static readonly Dictionary _quickLookupTable; + + private static readonly Dictionary> _translatorsByTargetType; + private static readonly List _translators; + + static MethodTranslatorLookupTable() + { + _translators = new List(); + _translatorsByTargetType = new Dictionary>(); + _quickLookupTable = new Dictionary(); + + var types = Assembly.GetExecutingAssembly().GetTypes(); + + // load current translators + var translators = types.Where(x => + x.BaseType?.Name == "MethodTranslator`1" || + (x.BaseType == typeof(MethodTranslator) && x.Name != "MethodTranslator`1")); + + foreach (var translator in translators) + { + var instance = (MethodTranslator)Activator.CreateInstance(translator)!; + var translatorTargetMethods = instance.TranslatorTargetType.GetMethods(); + + var methods = translator.GetMethods(BindingFlags.Public | BindingFlags.Instance); + + foreach (var method in methods) + { + var targetAttributes = method.GetCustomAttributes() as MethodNameAttribute[]; + + if (targetAttributes is null) + continue; + + foreach (var targetAttribute in targetAttributes) + { + var targetMethods = translatorTargetMethods.Where(x => x.Name == targetAttribute.MethodName); + + if (targetAttribute.IsParameterAware) + { + targetMethods = targetMethods + .Where(x => IsTranslationMatch(method, x)); + } + + foreach (var targetMethod in targetMethods) + { + _quickLookupTable.TryAdd(targetMethod, new Handle(instance, method)); + } + } + } + + _translators.Add(instance); + + if (!_translatorsByTargetType.TryGetValue(instance.TranslatorTargetType, out var translatorsByType)) + translatorsByType = _translatorsByTargetType[instance.TranslatorTargetType] = + new List(); + + translatorsByType.Add(instance); + //_translatorsByTargetType.Add(instance.TranslatorTargetType, instance); + } + } + + public static bool TryGetTranslator(MethodInfo target, [MaybeNullWhen(false)] out Handle translator) + { + lock (_quickLookupTable) + { + return _quickLookupTable.TryGetValue(target, out translator) || + TrySearchAndCacheTranslator(target, out translator); + } + } + + private static bool TrySearchAndCacheTranslator(MethodInfo target, [MaybeNullWhen(false)] out Handle translator) + { + var baseType = target.DeclaringType!; + + if (_translatorsByTargetType.TryGetValue(baseType, out var translators)) + { + var targetTranslator = translators + .SelectMany(x => x.MethodTranslators.Select(y => (methodTranslatorInfo: y, translators: x))) + .FirstOrDefault(x => x.methodTranslatorInfo.Key == target.Name && + IsTranslationMatch(x.methodTranslatorInfo.Value, target)); + + if (targetTranslator.translators is not null) + { + translator = _quickLookupTable[target] = new Handle( + targetTranslator.translators, + targetTranslator.methodTranslatorInfo.Value + ); + return true; + } + } + + foreach (var methodTranslator in _translators) + { + if (!methodTranslator.CanTranslate(baseType)) + continue; + + if (methodTranslator.MethodTranslators.TryGetValue(target.Name, out var info)) + { + translator = _quickLookupTable[target] = new Handle( + methodTranslator, + info + ); + return true; + } + } + + translator = default; + return false; + } + + private static bool IsTranslationMatch(MethodInfo translator, MethodInfo target) + { + var parameters = target.GetParameters(); + var translatorMethodAttributes = translator.GetCustomAttributes() as MethodNameAttribute[]; + + if (translatorMethodAttributes is null) + return false; + + return translatorMethodAttributes + .Any(attr => parameters.Length == attr.MethodParameters.Length && + parameters + .Select((x, i) => attr.MethodParameters[i] == x.ParameterType) + .Aggregate((a, b) => a && b) + ); + } + internal readonly struct Handle { public readonly MethodTranslator Translator; @@ -29,7 +152,8 @@ public WriterProxy Proxy(MethodCallExpression expression, ExpressionContext cont public void Translate(QueryWriter writer, MethodCallExpression expression, ExpressionContext context) => Translate(writer, Translator, _translatorMethod, expression, context); - private static void Translate(QueryWriter writer, MethodTranslator translator, MethodInfo translatorMethod, MethodCallExpression methodCall, ExpressionContext context) + private static void Translate(QueryWriter writer, MethodTranslator translator, MethodInfo translatorMethod, + MethodCallExpression methodCall, ExpressionContext context) { // TODO: this initialization logic can be refactored and extrapolated into a constructor. @@ -72,7 +196,8 @@ private static void Translate(QueryWriter writer, MethodTranslator translator, M break; } - else if (parameterInfo.ParameterType == typeof(ExpressionContext)) + + if (parameterInfo.ParameterType == typeof(ExpressionContext)) { // set the context parsedParameters[i] = context; @@ -128,127 +253,4 @@ private static void Translate(QueryWriter writer, MethodTranslator translator, M ); } } - - private static readonly Dictionary _quickLookupTable; - - private static readonly Dictionary> _translatorsByTargetType; - private static readonly List _translators; - - static MethodTranslatorLookupTable() - { - _translators = new(); - _translatorsByTargetType = new(); - _quickLookupTable = new(); - - var types = Assembly.GetExecutingAssembly().GetTypes(); - - // load current translators - var translators = types.Where(x => - x.BaseType?.Name == "MethodTranslator`1" || - (x.BaseType == typeof(MethodTranslator) && x.Name != "MethodTranslator`1")); - - foreach (var translator in translators) - { - var instance = (MethodTranslator)Activator.CreateInstance(translator)!; - var translatorTargetMethods = instance.TranslatorTargetType.GetMethods(); - - var methods = translator.GetMethods(BindingFlags.Public | BindingFlags.Instance); - - foreach (var method in methods) - { - var targetAttributes = method.GetCustomAttributes() as MethodNameAttribute[]; - - if(targetAttributes is null) - continue; - - foreach (var targetAttribute in targetAttributes) - { - var targetMethods = translatorTargetMethods.Where(x => x.Name == targetAttribute.MethodName); - - if (targetAttribute.IsParameterAware) - { - targetMethods = targetMethods - .Where(x => IsTranslationMatch(method, x)); - } - - foreach (var targetMethod in targetMethods) - { - _quickLookupTable.TryAdd(targetMethod, new Handle(instance, method)); - } - } - } - - _translators.Add(instance); - - if (!_translatorsByTargetType.TryGetValue(instance.TranslatorTargetType, out var translatorsByType)) - translatorsByType = _translatorsByTargetType[instance.TranslatorTargetType] = new(); - - translatorsByType.Add(instance); - //_translatorsByTargetType.Add(instance.TranslatorTargetType, instance); - } - } - - public static bool TryGetTranslator(MethodInfo target, [MaybeNullWhen(false)] out Handle translator) - { - lock (_quickLookupTable) - { - return _quickLookupTable.TryGetValue(target, out translator) || TrySearchAndCacheTranslator(target, out translator); - } - } - - private static bool TrySearchAndCacheTranslator(MethodInfo target, [MaybeNullWhen(false)] out Handle translator) - { - var baseType = target.DeclaringType!; - - if (_translatorsByTargetType.TryGetValue(baseType, out var translators)) - { - var targetTranslator = translators - .SelectMany(x => x.MethodTranslators.Select(y => (methodTranslatorInfo: y, translators: x))) - .FirstOrDefault(x => x.methodTranslatorInfo.Key == target.Name && - IsTranslationMatch(x.methodTranslatorInfo.Value, target)); - - if (targetTranslator.translators is not null) - { - translator = _quickLookupTable[target] = new Handle( - targetTranslator.translators, - targetTranslator.methodTranslatorInfo.Value - ); - return true; - } - } - - foreach (var methodTranslator in _translators) - { - if(!methodTranslator.CanTranslate(baseType)) - continue; - - if (methodTranslator.MethodTranslators.TryGetValue(target.Name, out var info)) - { - translator = _quickLookupTable[target] = new Handle( - methodTranslator, - info - ); - return true; - } - } - - translator = default; - return false; - } - - private static bool IsTranslationMatch(MethodInfo translator, MethodInfo target) - { - var parameters = target.GetParameters(); - var translatorMethodAttributes = translator.GetCustomAttributes() as MethodNameAttribute[]; - - if (translatorMethodAttributes is null) - return false; - - return translatorMethodAttributes - .Any(attr => parameters.Length == attr.MethodParameters.Length && - parameters - .Select((x, i) => attr.MethodParameters[i] == x.ParameterType) - .Aggregate((a, b) => a && b) - ); - } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/ObjectMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/ObjectMethodTranslator.cs index c967a950..3b4eb36e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/ObjectMethodTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/ObjectMethodTranslator.cs @@ -1,25 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EdgeDB.Translators.Methods; -namespace EdgeDB.Translators.Methods +/// +/// Represents a translator for translating methods within the class. +/// +internal class ObjectMethodTranslator : MethodTranslator { /// - /// Represents a translator for translating methods within the class. + /// Translates the method . /// - internal class ObjectMethodTranslator : MethodTranslator - { - /// - /// Translates the method . - /// - /// The query string writer to append the translated method to. - /// The instance of the object. - /// The optional format for the tostring func. - /// The EdgeQL equivalent of the method. - [MethodName(nameof(object.ToString))] - public void ToStr(QueryWriter writer, TranslatedParameter instance, TranslatedParameter? format) - => writer.Function("to_str", instance, OptionalArg(format)); - } + /// The query string writer to append the translated method to. + /// The instance of the object. + /// The optional format for the tostring func. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(ToString))] + public void ToStr(QueryWriter writer, TranslatedParameter instance, TranslatedParameter? format) + => writer.Function("to_str", instance, OptionalArg(format)); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/QueryContextTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/QueryContextTranslator.cs index 94fe3a4d..f8e1aeaf 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/QueryContextTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/QueryContextTranslator.cs @@ -20,13 +20,11 @@ public void Type(QueryWriter writer, MethodCallExpression method, TranslatedPara } [MethodName(nameof(QueryContext.QueryArgument))] - public void QueryArgument(QueryWriter writer, TranslatedParameter param) - { + public void QueryArgument(QueryWriter writer, TranslatedParameter param) => writer.QueryArgument( EdgeDBTypeUtils.GetEdgeDBScalarOrTypeName(param.RawValue.Type), ExpressionTranslator.UnsafeExpressionAsString(param.RawValue) ); - } [MethodName(nameof(QueryContext.Global))] public void Global(QueryWriter writer, TranslatedParameter param, ExpressionContext context) @@ -39,7 +37,7 @@ public void Global(QueryWriter writer, TranslatedParameter param, ExpressionCont public void Local(QueryWriter writer, TranslatedParameter param, ExpressionContext context) { var path = ExpressionTranslator.UnsafeExpressionAsString(param.RawValue) - ?? throw new NullReferenceException("Expected parameter of 'local' to be notnull"); + ?? throw new NullReferenceException("Expected parameter of 'local' to be notnull"); var pathSegments = path.Split('.'); @@ -65,8 +63,7 @@ public void Local(QueryWriter writer, TranslatedParameter param, ExpressionConte } [MethodName(nameof(QueryContext.UnsafeLocal))] - public void UnsafeLocal(QueryWriter writer, TranslatedParameter param) - { + public void UnsafeLocal(QueryWriter writer, TranslatedParameter param) => writer.Term( TermType.Unsafe, "query_context_unsafe_local", @@ -74,11 +71,9 @@ public void UnsafeLocal(QueryWriter writer, TranslatedParameter param) writer.Append('.', ExpressionTranslator.UnsafeExpressionAsString(param.RawValue)) ) ); - } [MethodName(nameof(QueryContext.Raw))] - public void Raw(QueryWriter writer, TranslatedParameter param) - { + public void Raw(QueryWriter writer, TranslatedParameter param) => writer.Term( TermType.RawEdgeQL, "query_context_raw", @@ -86,10 +81,10 @@ public void Raw(QueryWriter writer, TranslatedParameter param) writer.Append(ExpressionTranslator.UnsafeExpressionAsString(param.RawValue)) ) ); - } [MethodName(nameof(QueryContext.BackLink))] - public void Backlink(QueryWriter writer, MethodCallExpression method, ExpressionContext context, params TranslatedParameter[] args) + public void Backlink(QueryWriter writer, MethodCallExpression method, ExpressionContext context, + params TranslatedParameter[] args) { var property = args[0]; @@ -132,7 +127,8 @@ public void Backlink(QueryWriter writer, MethodCallExpression method, Expression public void SubQuery(QueryWriter writer, TranslatedParameter param, ExpressionContext context) { var builder = (IQueryBuilder)Expression.Lambda(param.RawValue).Compile().DynamicInvoke()!; - writer.Wrapped(writer => builder.WriteTo(writer, context, compileContext: CompileContext.SubQueryContext(context.Node?.SchemaInfo, null, writer.IsDebug))); + writer.Wrapped(writer => builder.WriteTo(writer, context, + CompileContext.SubQueryContext(context.Node?.SchemaInfo, null, writer.IsDebug))); } [MethodName(nameof(QueryContext.Aggregate))] diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/RegexMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/RegexMethodTranslator.cs index 396c3b8a..a52f7afc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/RegexMethodTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/RegexMethodTranslator.cs @@ -1,39 +1,33 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; +using System.Text.RegularExpressions; -namespace EdgeDB.Translators.Methods +namespace EdgeDB.Translators.Methods; + +/// +/// Represents a translator for translating methods within the class. +/// +internal class RegexMethodTranslator : MethodTranslator { /// - /// Represents a translator for translating methods within the class. + /// Translates the method . /// - internal class RegexMethodTranslator : MethodTranslator - { - /// - /// Translates the method . - /// - /// The query string writer to append the translated method to. - /// The input string to test against. - /// The regular expression pattern. - /// The replacement value to replace matches with. - /// The EdgeQL equivalent of the method. - [MethodName(nameof(Regex.Replace))] - public void Replace(QueryWriter writer, TranslatedParameter input, TranslatedParameter pattern, - TranslatedParameter replacement) - => writer.Function("re_replace", pattern, replacement, input); + /// The query string writer to append the translated method to. + /// The input string to test against. + /// The regular expression pattern. + /// The replacement value to replace matches with. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Regex.Replace))] + public void Replace(QueryWriter writer, TranslatedParameter input, TranslatedParameter pattern, + TranslatedParameter replacement) + => writer.Function("re_replace", pattern, replacement, input); - /// - /// Translates the method . - /// - /// The query string writer to append the translated method to. - /// The string to test against. - /// The regex pattern. - /// The EdgeQL equivalent of the method. - [MethodName(nameof(Regex.IsMatch))] - public void Test(QueryWriter writer, TranslatedParameter testString, TranslatedParameter pattern) - => writer.Function("re_test", pattern, testString); - } + /// + /// Translates the method . + /// + /// The query string writer to append the translated method to. + /// The string to test against. + /// The regex pattern. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Regex.IsMatch))] + public void Test(QueryWriter writer, TranslatedParameter testString, TranslatedParameter pattern) + => writer.Function("re_test", pattern, testString); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs index 678ea607..e946167a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs @@ -227,18 +227,16 @@ public void Split(QueryWriter writer, TranslatedParameter instance, TranslatedPa => writer.Function("str_split", instance, separator); [MethodName(nameof(string.StartsWith))] - public void StartsWith(QueryWriter writer, TranslatedParameter instance, TranslatedParameter condition) - { + public void StartsWith(QueryWriter writer, TranslatedParameter instance, TranslatedParameter condition) => // multiple ways to do this, which one is more efficient? // - len(condition) <= len(instance) and instance[:len(condition)] = condition // - re_test('^' ++ condition, instance) // - find(instance, condition) == 0 - writer.Term( TermType.BinaryOp, "starts_with_check", Defer.This(() => ""), - metadata: new BinaryOpMetadata(ExpressionType.Equal), + new BinaryOpMetadata(ExpressionType.Equal), Token.Of(writer => writer.Function( "find", instance, @@ -246,7 +244,6 @@ public void StartsWith(QueryWriter writer, TranslatedParameter instance, Transla )), " = 0" ); - } [MethodName(nameof(string.EndsWith))] public void EndsWith(QueryWriter writer, TranslatedParameter instance, TranslatedParameter condition) @@ -281,7 +278,7 @@ public void EndsWith(QueryWriter writer, TranslatedParameter instance, Translate Token.Of(writer => writer .Function( "len", - Defer.This(() => $"Length check for instance on ends with"), + Defer.This(() => "Length check for instance on ends with"), metadata: null, instanceValue ) @@ -290,7 +287,7 @@ public void EndsWith(QueryWriter writer, TranslatedParameter instance, Translate Token.Of(writer => writer .Function( "len", - Defer.This(() => $"Length check for condition on ends with"), + Defer.This(() => "Length check for condition on ends with"), metadata: null, conditionValue ) @@ -302,13 +299,13 @@ public void EndsWith(QueryWriter writer, TranslatedParameter instance, Translate .Append('-') .Function( "len", - Defer.This(() => $"length call for ends with final check"), + Defer.This(() => "length call for ends with final check"), metadata: null, conditionValue ) .Append(':') ), - separator: "[]" + "[]" )), " = ", conditionValue diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs index 79ca1f27..332a7a6b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs @@ -1,76 +1,70 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; -namespace EdgeDB.Translators.Methods +namespace EdgeDB.Translators.Methods; + +/// +/// Represents a parameter used within a method translator. +/// +internal class TranslatedParameter { /// - /// Represents a parameter used within a method translator. + /// Constructs a new . /// - internal class TranslatedParameter + /// The type of the parameter. + /// The raw expression of the parameter. + /// The context under which the raw expression is to be translated. + public TranslatedParameter(Type type, Expression raw, ExpressionContext context) { - /// - /// Gets the type original type of the parameter. - /// - public Type ParameterType { get; } + ParameterType = type; + Context = context; + RawValue = raw; + } - /// - /// Gets the context for translating the . - /// - public ExpressionContext Context { get; set; } + /// + /// Gets the type original type of the parameter. + /// + public Type ParameterType { get; } - /// - /// Gets the raw expression of the parameter. - /// - public Expression RawValue { get; } + /// + /// Gets the context for translating the . + /// + public ExpressionContext Context { get; set; } - /// - /// Gets whether or not the parameter type is a scalar array. - /// - public bool IsScalarArrayType - => EdgeDBTypeUtils.TryGetScalarType(ParameterType, out var info) && info.IsArray; + /// + /// Gets the raw expression of the parameter. + /// + public Expression RawValue { get; } - /// - /// Gets whether or not the parameter is a scalar type. - /// - public bool IsScalarType - => EdgeDBTypeUtils.TryGetScalarType(ParameterType, out _); + /// + /// Gets whether or not the parameter type is a scalar array. + /// + public bool IsScalarArrayType + => EdgeDBTypeUtils.TryGetScalarType(ParameterType, out var info) && info.IsArray; - /// - /// Gets whether or not the parameter is a valid link type. - /// - public bool IsLinkType - => EdgeDBTypeUtils.IsLink(ParameterType, out _, out _); + /// + /// Gets whether or not the parameter is a scalar type. + /// + public bool IsScalarType + => EdgeDBTypeUtils.TryGetScalarType(ParameterType, out _); - /// - /// Gets whether or not the parameter is a valid multi-link type. - /// - public bool IsMultiLinkType - => EdgeDBTypeUtils.IsLink(ParameterType, out var isMulti, out _) && isMulti; + /// + /// Gets whether or not the parameter is a valid link type. + /// + public bool IsLinkType + => EdgeDBTypeUtils.IsLink(ParameterType, out _, out _); - public bool IsNullValue - => RawValue is ConstantExpression {Value: null}; + /// + /// Gets whether or not the parameter is a valid multi-link type. + /// + public bool IsMultiLinkType + => EdgeDBTypeUtils.IsLink(ParameterType, out var isMulti, out _) && isMulti; - /// - /// Constructs a new . - /// - /// The type of the parameter. - /// The raw expression of the parameter. - /// The context under which the raw expression is to be translated. - public TranslatedParameter(Type type, Expression raw, ExpressionContext context) - { - ParameterType = type; - Context = context; - RawValue = raw; - } + public bool IsNullValue + => RawValue is ConstantExpression {Value: null}; - public void WriteTo(QueryWriter writer) - => ExpressionTranslator.ContextualTranslate(RawValue, Context, writer); + public void WriteTo(QueryWriter writer) + => ExpressionTranslator.ContextualTranslate(RawValue, Context, writer); - public static implicit operator Token(TranslatedParameter param) => Token.Of(param.WriteTo); - public static implicit operator Terms.FunctionArg(TranslatedParameter param) => new(Token.Of(param.WriteTo)); - } + public static implicit operator Token(TranslatedParameter param) => Token.Of(param.WriteTo); + public static implicit operator Terms.FunctionArg(TranslatedParameter param) => new(Token.Of(param.WriteTo)); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TupleMethodTranslators.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TupleMethodTranslators.cs index abb02382..7987d05b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TupleMethodTranslators.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TupleMethodTranslators.cs @@ -1,22 +1,18 @@ -using System.Runtime.CompilerServices; - -namespace EdgeDB.Translators.Methods; +namespace EdgeDB.Translators.Methods; internal sealed class TupleMethodTranslators : MethodTranslator { public override Type TranslatorTargetType => typeof(Tuple); [MethodName(nameof(Tuple.Create))] - public void Create(QueryWriter writer, params TranslatedParameter[] args) - { + public void Create(QueryWriter writer, params TranslatedParameter[] args) => writer.Wrapped(Token.Of(writer => { - for (int i = 0; i != args.Length - 1; i++) + for (var i = 0; i != args.Length - 1; i++) { writer.Append(args[i], ", "); } writer.Append(args[^1]); })); - } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/PropertyTranslationTable.cs b/src/EdgeDB.Net.QueryBuilder/Translators/PropertyTranslationTable.cs index 81061c72..f119da8d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/PropertyTranslationTable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/PropertyTranslationTable.cs @@ -5,14 +5,13 @@ namespace EdgeDB.Translators; internal static class PropertyTranslationTable { - internal delegate void TableTranslator(QueryWriter writer, MemberExpression expression, ExpressionContext context); - private static readonly Dictionary Table = new() { {nameof(string.Length), ([typeof(string), typeof(Array)], TranslateLength)} }; - public static bool TryGetTranslator(MemberExpression expression, [MaybeNullWhen(false)] out TableTranslator translator) + public static bool TryGetTranslator(MemberExpression expression, + [MaybeNullWhen(false)] out TableTranslator translator) { if (Table.TryGetValue(expression.Member.Name, out var entry)) { @@ -34,8 +33,10 @@ private static void TranslateLength(QueryWriter writer, MemberExpression express writer.Function( "std::len", - Defer.This(() => $"Auto .Length access converted to std::len()"), + Defer.This(() => "Auto .Length access converted to std::len()"), ExpressionTranslator.Proxy(expression.Expression, context) ); } + + internal delegate void TableTranslator(QueryWriter writer, MemberExpression expression, ExpressionContext context); } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs index f5e378ce..2ce9ef65 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs @@ -1,58 +1,52 @@ using EdgeDB.Schema.DataTypes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +/// +/// A class of utility functions to help with conflict statements. +/// +internal static class ConflictUtils { /// - /// A class of utility functions to help with conflict statements. + /// Generates an 'UNLESS CONFLICT [ON expr]' statement for the given object type. /// - internal static class ConflictUtils + /// The query string writer to append the exclusive constraint to. + /// The object type to generate the conflict for. + /// Whether or not the query has an else statement. + /// + /// The generated 'UNLESS CONFLICT' statement. + /// + /// + /// The conflict statement cannot be generated because of query grammar limitations. + /// + public static void GenerateExclusiveConflictStatement(QueryWriter writer, ObjectType type, bool hasElse) { - /// - /// Generates an 'UNLESS CONFLICT [ON expr]' statement for the given object type. - /// - /// The query string writer to append the exclusive constraint to. - /// The object type to generate the conflict for. - /// Whether or not the query has an else statement. - /// - /// The generated 'UNLESS CONFLICT' statement. - /// - /// - /// The conflict statement cannot be generated because of query grammar limitations. - /// - public static void GenerateExclusiveConflictStatement(QueryWriter writer, ObjectType type, bool hasElse) + // does the type have any object level exclusive constraints? + if (type.Constraints?.Any(x => x.IsExclusive) ?? false) { - // does the type have any object level exclusive constraints? - if (type.Constraints?.Any(x => x.IsExclusive) ?? false) - { - writer - .Append(" unless conflict on ") - .Append(type.Constraints?.First(x => x.IsExclusive).SubjectExpression); - return; - } - - // does the type have a single property that is exclusive? - if(type.Properties!.Count(x => x.Name != "id" && x.IsExclusive) == 1) - { - writer - .Append(" unless conflict on .") - .Append(type.Properties!.First(x => x.Name != "id" && x.IsExclusive).Name); + writer + .Append(" unless conflict on ") + .Append(type.Constraints?.First(x => x.IsExclusive).SubjectExpression); + return; + } - return; - } + // does the type have a single property that is exclusive? + if (type.Properties!.Count(x => x.Name != "id" && x.IsExclusive) == 1) + { + writer + .Append(" unless conflict on .") + .Append(type.Properties!.First(x => x.Name != "id" && x.IsExclusive).Name); - // if it doesn't have an else statement we can simply add 'UNLESS CONFLICT' - if (!hasElse) - { - writer.Append(" unless conflict"); - return; - } + return; + } - throw new InvalidOperationException($"Cannot find a valid exclusive constraint on type {type.Name}"); + // if it doesn't have an else statement we can simply add 'UNLESS CONFLICT' + if (!hasElse) + { + writer.Append(" unless conflict"); + return; } + + throw new InvalidOperationException($"Cannot find a valid exclusive constraint on type {type.Name}"); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/Deferrable.cs b/src/EdgeDB.Net.QueryBuilder/Utils/Deferrable.cs index 9619732c..41e1e1d6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/Deferrable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/Deferrable.cs @@ -2,15 +2,15 @@ internal static class Defer { - public static Deferrable This(Func value) => new Deferrable(value); + public static Deferrable This(Func value) => new(value); } internal sealed class Deferrable { - private T _value; private readonly Func? _getValue; private bool _isDeferred; + private T _value; public Deferrable(T value) { @@ -33,7 +33,6 @@ public T Get() if (!_isDeferred) return _value; _isDeferred = false; return _value = _getValue!(); - } } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs index 1e081d00..8dc7906c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs @@ -1,199 +1,201 @@ using EdgeDB.Binary; -using EdgeDB.Binary.Codecs; -using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +/// +/// A class of utility functions for edgedb types. +/// +internal static class EdgeDBTypeUtils { + private static readonly ConcurrentDictionary _typeCache = new(); + /// - /// A class of utility functions for edgedb types. + /// Gets either a scalar type name or edgedb type name for the current type. /// - internal static class EdgeDBTypeUtils + /// + /// string -> std::str. + /// + /// The dotnet type to get the equivalent edgedb type. + /// + /// The equivalent edgedb type. + /// + public static string GetEdgeDBScalarOrTypeName(Type type) { - private static readonly ConcurrentDictionary _typeCache = new(); + if (TryGetScalarType(type, out var info)) + return info.ToString(); - /// - /// Represents type info about a compatable edgedb type. - /// - internal class EdgeDBTypeInfo + return type.GetEdgeDBTypeName(); + } + + /// + /// Attempts to get a scalar type for the given dotnet type. + /// + /// The dotnet type to get the scalar type for. + /// The out parameter containing the type info. + /// + /// if the edgedb scalar type could be found; otherwise . + /// + public static bool TryGetScalarType(Type type, [MaybeNullWhen(false)] out EdgeDBTypeInfo info) + { + if (_typeCache.TryGetValue(type, out info)) + return true; + + info = null; + + var enumerableType = ReflectionUtils.IsSubclassOfRawGeneric(typeof(IEnumerable<>), type) + ? type + : type.GetInterfaces() + .FirstOrDefault(x => ReflectionUtils.IsSubclassOfRawGeneric(typeof(IEnumerable<>), x)); + + EdgeDBTypeInfo? child = null; + var hasChild = enumerableType != null && TryGetScalarType(enumerableType.GenericTypeArguments[0], out child); + var scalar = PacketSerializer.GetEdgeQLType(type); + + if (scalar != null) + info = new EdgeDBTypeInfo(type, scalar, false, child); + else if (hasChild) + info = new EdgeDBTypeInfo(type, "array", true, child); + else if (type.IsEnum) + info = new EdgeDBTypeInfo(type, type.GetEdgeDBTypeName(), false, child); + + if (info is not null) + _typeCache.TryAdd(type, info); + + return info != null; + } + + /// + /// Checks whether or not a type is a valid link type. + /// + /// The type to check whether or not its a link. + /// + /// The out parameter which is + /// if the type is a 'multi link'; otherwise a 'single link'. + /// + /// + /// The inner type of the multi link if is + /// ; otherwise . + /// + /// + /// if the given type is a link; otherwise . + /// + public static bool IsLink(Type type, out bool isMultiLink, [NotNullWhen(true)] out Type? innerLinkType) + { + innerLinkType = null; + isMultiLink = false; + + if (CodecBuilder.ContainsScalarCodec(type)) + return false; + + var enumerableType = ReflectionUtils.IsSubclassOfRawGeneric(typeof(IEnumerable<>), type) ? type : null; + if (type != typeof(string) && (enumerableType is not null || (enumerableType = type.GetInterfaces() + .FirstOrDefault(x => ReflectionUtils.IsSubclassOfRawGeneric(typeof(IEnumerable<>), x))) != null)) { - /// - /// The dotnet type of the edgedb type. - /// - public readonly Type DotnetType; - - /// - /// The name of the edgedb type. - /// - public readonly string EdgeDBType; - - /// - /// Whether or not the type is an array. - /// - public readonly bool IsArray; - - /// - /// The child of the current type. - /// - public readonly EdgeDBTypeInfo? Child; - - /// - /// Constructs a new . - /// - /// The dotnet type. - /// The edgedb type. - /// Whether or not the type is an array. - /// The child type. - public EdgeDBTypeInfo(Type dotnetType, string edgedbType, bool isArray, EdgeDBTypeInfo? child) - { - DotnetType = dotnetType; - EdgeDBType = edgedbType; - IsArray = isArray; - Child = child; - } - - /// - /// Turns the current to the equivalent edgedb type. - /// - /// - /// The equivalent edgedb type. - /// - public override string ToString() - { - if (IsArray) - return $"array<{Child}>"; - return EdgeDBType; - } + innerLinkType = enumerableType.GenericTypeArguments[0]; + isMultiLink = true; + var result = IsLink(innerLinkType, out _, out var linkType); + innerLinkType = linkType ?? innerLinkType; + return result; } - /// - /// Gets either a scalar type name or edgedb type name for the current type. - /// - /// - /// string -> std::str. - /// - /// The dotnet type to get the equivalent edgedb type. - /// - /// The equivalent edgedb type. - /// - public static string GetEdgeDBScalarOrTypeName(Type type) - { - if (TryGetScalarType(type, out var info)) - return info.ToString(); + return TypeBuilder.IsValidObjectType(type) && !TryGetScalarType(type, out _); + } - return type.GetEdgeDBTypeName(); - } + /// + /// Checks whether or not a type is a valid link type. + /// + /// The property info to check whether or not its a link. + /// + /// The out parameter which is + /// if the type is a 'multi link'; otherwise a 'single link'. + /// + /// + /// The inner type of the multi link if is + /// ; otherwise . + /// + /// + /// if the given type is a link; otherwise . + /// + public static bool IsLink(EdgeDBPropertyInfo info, out bool isMultiLink, + [MaybeNullWhen(false)] out Type? innerLinkType) + { + // check for custom converter + if (info.CustomConverter is not null) + return IsLink(info.CustomConverter.Target, out isMultiLink, out innerLinkType); - /// - /// Attempts to get a scalar type for the given dotnet type. - /// - /// The dotnet type to get the scalar type for. - /// The out parameter containing the type info. - /// - /// if the edgedb scalar type could be found; otherwise . - /// - public static bool TryGetScalarType(Type type, [MaybeNullWhen(false)] out EdgeDBTypeInfo info) - { - if (_typeCache.TryGetValue(type, out info)) - return true; + return IsLink(info.Type, out isMultiLink, out innerLinkType); + } - info = null; + public static bool CompareEdgeDBTypes(string a, string b) + { + if (a == b) + return true; - Type? enumerableType = ReflectionUtils.IsSubclassOfRawGeneric(typeof(IEnumerable<>), type) - ? type - : type.GetInterfaces().FirstOrDefault(x => ReflectionUtils.IsSubclassOfRawGeneric(typeof(IEnumerable<>), x)); + var aSpl = a.Split("::"); + var bSpl = b.Split("::"); - EdgeDBTypeInfo? child = null; - var hasChild = enumerableType != null && TryGetScalarType(enumerableType.GenericTypeArguments[0], out child); - var scalar = PacketSerializer.GetEdgeQLType(type); + if (aSpl.Length == 2 && bSpl.Length == 1 && aSpl[0] == "std") + return aSpl[1] == b; - if (scalar != null) - info = new(type, scalar, false, child); - else if (hasChild) - info = new(type, "array", true, child); - else if (type.IsEnum) - info = new(type, type.GetEdgeDBTypeName(), false, child); + if (bSpl.Length == 2 && aSpl.Length == 1 && bSpl[0] == "std") + return bSpl[1] == a; - if(info is not null) - _typeCache.TryAdd(type, info); + return false; + } - return info != null; - } + /// + /// Represents type info about a compatable edgedb type. + /// + internal class EdgeDBTypeInfo + { + /// + /// The child of the current type. + /// + public readonly EdgeDBTypeInfo? Child; /// - /// Checks whether or not a type is a valid link type. + /// The dotnet type of the edgedb type. /// - /// The type to check whether or not its a link. - /// - /// The out parameter which is - /// if the type is a 'multi link'; otherwise a 'single link'. - /// - /// The inner type of the multi link if is ; otherwise . - /// - /// if the given type is a link; otherwise . - /// - public static bool IsLink(Type type, out bool isMultiLink, [NotNullWhen(true)] out Type? innerLinkType) + public readonly Type DotnetType; + + /// + /// The name of the edgedb type. + /// + public readonly string EdgeDBType; + + /// + /// Whether or not the type is an array. + /// + public readonly bool IsArray; + + /// + /// Constructs a new . + /// + /// The dotnet type. + /// The edgedb type. + /// Whether or not the type is an array. + /// The child type. + public EdgeDBTypeInfo(Type dotnetType, string edgedbType, bool isArray, EdgeDBTypeInfo? child) { - innerLinkType = null; - isMultiLink = false; - - if (CodecBuilder.ContainsScalarCodec(type)) - return false; - - Type? enumerableType = ReflectionUtils.IsSubclassOfRawGeneric(typeof(IEnumerable<>), type) ? type : null; - if (type != typeof(string) && (enumerableType is not null || (enumerableType = type.GetInterfaces().FirstOrDefault(x => ReflectionUtils.IsSubclassOfRawGeneric(typeof(IEnumerable<>), x))) != null)) - { - innerLinkType = enumerableType.GenericTypeArguments[0]; - isMultiLink = true; - var result = IsLink(innerLinkType, out _, out var linkType); - innerLinkType = linkType ?? innerLinkType; - return result; - } - - return TypeBuilder.IsValidObjectType(type) && !TryGetScalarType(type, out _); + DotnetType = dotnetType; + EdgeDBType = edgedbType; + IsArray = isArray; + Child = child; } /// - /// Checks whether or not a type is a valid link type. + /// Turns the current to the equivalent edgedb type. /// - /// The property info to check whether or not its a link. - /// - /// The out parameter which is - /// if the type is a 'multi link'; otherwise a 'single link'. - /// - /// The inner type of the multi link if is ; otherwise . /// - /// if the given type is a link; otherwise . + /// The equivalent edgedb type. /// - public static bool IsLink(EdgeDBPropertyInfo info, out bool isMultiLink, [MaybeNullWhen(false)] out Type? innerLinkType) + public override string ToString() { - // check for custom converter - if (info.CustomConverter is not null) - return IsLink(info.CustomConverter.Target, out isMultiLink, out innerLinkType); - - return IsLink(info.Type, out isMultiLink, out innerLinkType); - } - - public static bool CompareEdgeDBTypes(string a, string b) - { - if (a == b) - return true; - - var aSpl = a.Split("::"); - var bSpl = b.Split("::"); - - if (aSpl.Length == 2 && bSpl.Length == 1 && aSpl[0] == "std") - return aSpl[1] == b; - - if (bSpl.Length == 2 && aSpl.Length == 1 && bSpl[0] == "std") - return bSpl[1] == a; - - return false; + if (IsArray) + return $"array<{Child}>"; + return EdgeDBType; } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/ExpressionUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/ExpressionUtils.cs index 28e2ea20..74962227 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/ExpressionUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/ExpressionUtils.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; +using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace EdgeDB; diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs index 312a264b..47ba4f36 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs @@ -1,198 +1,190 @@ using Newtonsoft.Json.Linq; -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +/// +/// Represents a node within a depth map. +/// +internal readonly struct DepthNode { /// - /// Represents a node within a depth map. + /// The type of the node, this type represents the nodes value. + /// + public readonly Type Type; + + /// + /// The value of the node. + /// + public readonly JObject JsonNode; + + /// + /// Gets the 0-based depth of the current node. /// - internal readonly struct DepthNode + public readonly int Depth; + + /// + /// Constructs a new . + /// + /// The type of the node. + /// The node containing the value. + /// The depth of the node. + public DepthNode(Type type, JObject node, int depth) { - /// - /// The type of the node, this type represents the nodes value. - /// - public readonly Type Type; - - /// - /// The value of the node. - /// - public readonly JObject JsonNode; - - /// - /// Gets the 0-based depth of the current node. - /// - public readonly int Depth; - - /// - /// Constructs a new . - /// - /// The type of the node. - /// The node containing the value. - /// The depth of the node. - public DepthNode(Type type, JObject node, int depth) - { - Type = type; - JsonNode = node; - Depth = depth; - } + Type = type; + JsonNode = node; + Depth = depth; } +} - internal struct NodeCollection : IEnumerable +internal struct NodeCollection : IEnumerable +{ + private readonly Dictionary _depthIndex; + private readonly List _nodes; + + public NodeCollection() { - private readonly Dictionary _depthIndex; - private readonly List _nodes; + _nodes = new List(); + _depthIndex = new Dictionary(); + } - public NodeCollection() - { - _nodes = new(); - _depthIndex = new(); - } + public void Add(DepthNode node) + { + if (_depthIndex.ContainsKey(node.Depth)) + _depthIndex[node.Depth]++; + else + _depthIndex[node.Depth] = 0; - public void Add(DepthNode node) - { - if (_depthIndex.ContainsKey(node.Depth)) - _depthIndex[node.Depth]++; - else - _depthIndex[node.Depth] = 0; + _nodes.Add(node); + } - _nodes.Add(node); - } + public void AddRange(IEnumerable nodes) + => _nodes.AddRange(nodes); - public void AddRange(IEnumerable nodes) - => _nodes.AddRange(nodes); + public int GetNodeRelativeDepthIndex(DepthNode node) + => _depthIndex[node.Depth]; - public int GetNodeRelativeDepthIndex(DepthNode node) - => _depthIndex[node.Depth]; + public int GetCurrentDepthIndex(int depth) + => _depthIndex.TryGetValue(depth, out var v) ? v : 0; - public int GetCurrentDepthIndex(int depth) - => _depthIndex.TryGetValue(depth, out var v) ? v : 0; + public IEnumerator GetEnumerator() + => _nodes.GetEnumerator(); - public IEnumerator GetEnumerator() - => _nodes.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() + => _nodes.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - => _nodes.GetEnumerator(); + public List ToList() + => _nodes; +} - public List ToList() - => _nodes; - } +internal class JsonUtils +{ + /// + /// The regex used to resolve json paths. + /// + private static readonly Regex _pathResolverRegex = new(@"\[\d+?](?>\.(.*?)$|$)"); - internal class JsonUtils + public static List BuildDepthMap(IJsonVariable jsonVariable) { - /// - /// The regex used to resolve json paths. - /// - private static readonly Regex _pathResolverRegex = new(@"\[\d+?](?>\.(.*?)$|$)"); + var elements = jsonVariable.GetObjectsAtDepth(0); - public static List BuildDepthMap(IJsonVariable jsonVariable) + var nodes = new NodeCollection(); + + foreach (var element in elements) { - var elements = jsonVariable.GetObjectsAtDepth(0); + var type = ResolveTypeFromPath(jsonVariable.InnerType, element.Path); + var node = new DepthNode(type, element, 0); + nodes.Add(node); + GetNodes(node, jsonVariable, nodes); + } - var nodes = new NodeCollection(); + return nodes.ToList(); + } - foreach (var element in elements) + private static void GetNodes(DepthNode node, IJsonVariable jsonValue, NodeCollection nodes) + { + var currentDepth = node.Depth; + + foreach (var prop in node.JsonNode.Properties()) + { + if (prop.Value is JObject jObject) { - var type = ResolveTypeFromPath(jsonVariable.InnerType, element.Path); - var node = new DepthNode(type, element, 0); - nodes.Add(node); - GetNodes(node, jsonVariable, nodes); - } + // if its a sub-object, add it to the next depth level + var mapIndex = currentDepth + 1; - return nodes.ToList(); - } + // resolve the objects link type from its path + var type = ResolveTypeFromPath(jsonValue.InnerType, prop.Path); - private static void GetNodes(DepthNode node, IJsonVariable jsonValue, NodeCollection nodes) - { - var currentDepth = node.Depth; + var childNode = new DepthNode(type, jObject, mapIndex); + nodes.Add(childNode); - foreach (var prop in node.JsonNode.Properties()) - { - if (prop.Value is JObject jObject) - { - // if its a sub-object, add it to the next depth level - var mapIndex = currentDepth + 1; + // get each sub node of the child + GetNodes(childNode, jsonValue, nodes); - // resolve the objects link type from its path - var type = ResolveTypeFromPath(jsonValue.InnerType, prop.Path); + // mutate the node + node.JsonNode[prop.Name] = new JObject + { + new JProperty("index", nodes.GetNodeRelativeDepthIndex(childNode)) + }; + } + else if (prop.Value is JArray jArray && jArray.All(x => x is JObject)) + { + // if its an array, add it to the next depth level + var mapIndex = currentDepth + 1; - var childNode = new DepthNode(type, jObject, mapIndex); - nodes.Add(childNode); + // resolve the objects link type from its path + var type = ResolveTypeFromPath(jsonValue.InnerType, prop.Path); - // get each sub node of the child - GetNodes(childNode, jsonValue, nodes); + var indx = nodes.GetCurrentDepthIndex(mapIndex); - // mutate the node - node.JsonNode[prop.Name] = new JObject() - { - new JProperty("index", nodes.GetNodeRelativeDepthIndex(childNode)), - }; - } - else if (prop.Value is JArray jArray && jArray.All(x => x is JObject)) + foreach (var element in jArray) { - // if its an array, add it to the next depth level - var mapIndex = currentDepth + 1; - - // resolve the objects link type from its path - var type = ResolveTypeFromPath(jsonValue.InnerType, prop.Path); - - var indx = nodes.GetCurrentDepthIndex(mapIndex); - - foreach(var element in jArray) - { - var subNode = new DepthNode(type, (JObject)element, mapIndex); - nodes.Add(subNode); - GetNodes(subNode, jsonValue, nodes); - } - - // populate the mutable one with the location of the nested object - node.JsonNode[prop.Name] = new JObject() - { - new JProperty($"from", indx), - new JProperty($"to", indx + jArray.Count) - }; + var subNode = new DepthNode(type, (JObject)element, mapIndex); + nodes.Add(subNode); + GetNodes(subNode, jsonValue, nodes); } + + // populate the mutable one with the location of the nested object + node.JsonNode[prop.Name] = new JObject + { + new JProperty("from", indx), new JProperty("to", indx + jArray.Count) + }; } } + } - /// - /// Resolves the type of a property given the string json path. - /// - /// The root type of the json variable - /// The path used to resolve the type of the property. - /// - public static Type ResolveTypeFromPath(Type rootType, string path) - { - // match our path resolving regex - var match = _pathResolverRegex.Match(path); + /// + /// Resolves the type of a property given the string json path. + /// + /// The root type of the json variable + /// The path used to resolve the type of the property. + /// + public static Type ResolveTypeFromPath(Type rootType, string path) + { + // match our path resolving regex + var match = _pathResolverRegex.Match(path); - // if the first group is empty, were dealing with a index - // only. We can safely return the root type. - if (string.IsNullOrEmpty(match.Groups[1].Value)) - return rootType; + // if the first group is empty, were dealing with a index + // only. We can safely return the root type. + if (string.IsNullOrEmpty(match.Groups[1].Value)) + return rootType; - // split the main path up - var pathSections = match.Groups[1].Value.Split('.'); + // split the main path up + var pathSections = match.Groups[1].Value.Split('.'); - // iterate over it, pulling each member out and getting the member type. - Type result = rootType; - for (int i = 0; i != pathSections.Length; i++) - { - result = ResolveTypeFromPath(result, pathSections[i]); - //result = result.GetMember(pathSections[i]).First(x => x is PropertyInfo or FieldInfo)!.GetMemberType(); - } + // iterate over it, pulling each member out and getting the member type. + var result = rootType; + for (var i = 0; i != pathSections.Length; i++) + { + result = ResolveTypeFromPath(result, pathSections[i]); + //result = result.GetMember(pathSections[i]).First(x => x is PropertyInfo or FieldInfo)!.GetMemberType(); + } - if (EdgeDBTypeUtils.IsLink(result, out var isMultiLink, out var innerType) && isMultiLink) - return innerType!; + if (EdgeDBTypeUtils.IsLink(result, out var isMultiLink, out var innerType) && isMultiLink) + return innerType!; - // return the final type. - return result; - } + // return the final type. + return result; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/LooseLinkedList.cs b/src/EdgeDB.Net.QueryBuilder/Utils/LooseLinkedList.cs index e08cb1fd..443ec173 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/LooseLinkedList.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/LooseLinkedList.cs @@ -1,13 +1,10 @@ using System.Collections; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace EdgeDB; #pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type - /// /// A non-circular linked list implementation. /// @@ -16,72 +13,6 @@ public sealed class LooseLinkedList : IDisposable, IEnumerable { public delegate void NodeAction(Node? node); - /// - /// Represents a node inside of the current linked list. - /// - [DebuggerDisplay("{DebugDisplay}"), StructLayout(LayoutKind.Sequential)] - public sealed class Node(T value, LooseLinkedList list) - { - [DebuggerHidden] - private string DebugDisplay - => IsAlive ? $"Value({Value}) HasNext={Next is not null} HasPrevious={Previous is not null}" : "(Dead)"; - - /// - /// Gets the next node within the list. - /// - public Node? Next { get; private set; } - - /// - /// Gets the previous node within the list. - /// - public Node? Previous { get; private set; } - - /// - /// The list that owns this node. - /// - internal LooseLinkedList List = list; - - /// - /// The value within the node. - /// - public T Value { get; private set; } = value; - - internal void SetNext(Node? head) - => Next = head; - - internal void SetPrevious(Node? previous) - => Previous = previous; - - internal bool IsAlive { get; private set; } = true; - - public void Destroy() - { - if (!IsAlive) return; - IsAlive = false; - - Next = null; - Value = default!; - Previous = null; - List = null!; - } - } - - public sealed class NodeSlice(Node? head, Node? tail) - { - public static readonly NodeSlice Empty = new(null, null); - - public static NodeSlice Create(Node? head, Node? tail) - { - if (head is null && tail is null) - return Empty; - - return new(head, tail); - } - - public Node? Head { get; set; } = head; - public Node? Tail { get; set; } = tail; - } - /// /// Gets the number of elements within this list. /// @@ -97,14 +28,28 @@ public static NodeSlice Create(Node? head, Node? tail) /// public Node? Last { get; private set; } + /// + /// Clears all nodes within this list. + /// + public void Dispose() => Clear(); + + public IEnumerator GetEnumerator() + => new Enumerator(this); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + /// /// Strips out a slice from the list, whilst keeping an inner slice. /// /// The slice to remove. /// The inner portion to keep. - /// The size of the slice. - /// The size of the slice. - /// The offset on which the slice starts within . + /// The size of the slice. + /// The size of the slice. + /// + /// The offset on which the slice starts within + /// . + /// public void Strip(NodeSlice slice, NodeSlice keep, out int sliceSize, out int keepSize, out int keepOffset) { ValidateNodeSlice(slice); @@ -143,13 +88,14 @@ public void Strip(NodeSlice slice, NodeSlice keep, out int sliceSize, out int ke goto continuation; } - if(isInKeepSlice) + if (isInKeepSlice) goto continuation; node!.Destroy(); Count--; - continuation: node = temp; + continuation: + node = temp; } leftBounds?.SetNext(keep.Head); @@ -441,7 +387,6 @@ public bool Remove(in T value) Remove(node); return true; - } /// @@ -628,11 +573,70 @@ private Node CreateNode(in T value) => new(value, this); /// - /// Clears all nodes within this list. + /// Represents a node inside of the current linked list. /// - public void Dispose() + [DebuggerDisplay("{DebugDisplay}")] + [StructLayout(LayoutKind.Sequential)] + public sealed class Node(T value, LooseLinkedList list) { - Clear(); + /// + /// The list that owns this node. + /// + internal LooseLinkedList List = list; + + [DebuggerHidden] + private string DebugDisplay + => IsAlive ? $"Value({Value}) HasNext={Next is not null} HasPrevious={Previous is not null}" : "(Dead)"; + + /// + /// Gets the next node within the list. + /// + public Node? Next { get; private set; } + + /// + /// Gets the previous node within the list. + /// + public Node? Previous { get; private set; } + + /// + /// The value within the node. + /// + public T Value { get; private set; } = value; + + internal bool IsAlive { get; private set; } = true; + + internal void SetNext(Node? head) + => Next = head; + + internal void SetPrevious(Node? previous) + => Previous = previous; + + public void Destroy() + { + if (!IsAlive) return; + IsAlive = false; + + Next = null; + Value = default!; + Previous = null; + List = null!; + } + } + + public sealed class NodeSlice(Node? head, Node? tail) + { + public static readonly NodeSlice Empty = new(null, null); + + public Node? Head { get; set; } = head; + public Node? Tail { get; set; } = tail; + + public static NodeSlice Create(Node? head, Node? tail) + { + if (head is null && tail is null) + return Empty; + + return new NodeSlice(head, tail); + } } public struct Enumerator : IEnumerator @@ -687,11 +691,5 @@ public void Dispose() { } } } } - - public IEnumerator GetEnumerator() - => new Enumerator(this); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); } #pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs index 038a1d00..16aaf39b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs @@ -1,181 +1,179 @@ using EdgeDB.Schema; using EdgeDB.Schema.DataTypes; -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB; + +internal static class QueryGenerationUtils { - internal static class QueryGenerationUtils + /// + /// Gets a collection of properties based on flags. + /// + /// The type to get the properties on. + /// A client to preform introspection with. + /// + /// to return only exclusive properties. + /// to exclude exclusive properties. + /// to include either or. + /// + /// + /// to return only readonly properties. + /// to exclude readonly properties. + /// to include either or. + /// + /// A cancellation token used to cancel the introspection query. + /// + /// A ValueTask representing the (a)sync operation of preforming the introspection query. + /// The result of the task is a collection of . + /// + public static async ValueTask> GetPropertiesAsync(IEdgeDBQueryable edgedb, + bool? exclusive = null, bool? @readonly = null, CancellationToken token = default) { - /// - /// Gets a collection of properties based on flags. - /// - /// The type to get the properties on. - /// A client to preform introspection with. - /// - /// to return only exclusive properties. - /// to exclude exclusive properties. - /// to include either or. - /// - /// - /// to return only readonly properties. - /// to exclude readonly properties. - /// to include either or. - /// - /// A cancellation token used to cancel the introspection query. - /// - /// A ValueTask representing the (a)sync operation of preforming the introspection query. - /// The result of the task is a collection of . - /// - public static async ValueTask> GetPropertiesAsync(IEdgeDBQueryable edgedb, bool? exclusive = null, bool? @readonly = null, CancellationToken token = default) - { - var introspection = await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(edgedb, token).ConfigureAwait(false); - - return GetProperties(introspection, typeof(TType), exclusive, @readonly); - } - - /// - /// Gets a collection of properties based on flags. - /// - /// - /// The introspection data on which to cross reference property data. - /// - /// The type to get the properties on. - /// - /// to return only exclusive properties. - /// to exclude exclusive properties. - /// to include either or. - /// - /// - /// to return only readonly properties. - /// to exclude readonly properties. - /// to include either or. - /// - /// Whether or not to include the 'id' property. - /// A collection of . - /// - /// The given type was not found within the introspection data. - /// - public static IEnumerable GetProperties(SchemaInfo schemaInfo, Type type, bool? exclusive = null, bool? @readonly = null, bool includeId = false) - { - if (!schemaInfo.TryGetObjectInfo(type, out var info)) - throw new NotSupportedException($"Cannot use {type.Name} as there is no schema information for it."); + var introspection = await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(edgedb, token) + .ConfigureAwait(false); - var props = type.GetProperties().Where(x => x.GetCustomAttribute() == null); - return props.Where(x => - { - var edgedbName = x.GetEdgeDBPropertyName(); - if (!includeId && edgedbName == "id") - return false; - return info.Properties!.Any(x => x.Name == edgedbName && - (!exclusive.HasValue || x.IsExclusive == exclusive.Value) && - (!@readonly.HasValue || x.IsReadonly == @readonly.Value)); - }); - } - - public static Dictionary MapProperties(SchemaInfo schemaInfo, Type type) - { - var map = EdgeDBPropertyMapInfo.Create(type); - - if (!schemaInfo.TryGetObjectInfo(type, out var info)) - throw new NotSupportedException($"Cannot use {type.Name} as there is no schema information for it."); - - return map.Properties.ToDictionary(x => x, - x => info.Properties!.First(y => y.Name == x.EdgeDBName)!); - } - - /// - /// Generates a default insert shape expression for the given type and value. - /// - /// The value of which to do member lookups on. - /// The type to generate the shape for. - /// - /// An that contains the insert shape for the given type. - /// - public static Expression GenerateInsertShapeExpression(object? value, Type type) - { - var props = type.GetProperties() - .Where(x => - x.GetCustomAttribute() == null && - x.GetValue(value) != ReflectionUtils.GetDefault(x.PropertyType)); - - return Expression.MemberInit( - Expression.New(type), - props.Select(x => - Expression.Bind(x, Expression.MakeMemberAccess(Expression.Constant(value), x)) - ) - ); - } - - /// - /// Generates a default update factory expression for the given type and value. - /// - /// The type to generate the shape for. - /// A client used to preform introspection with. - /// The value of which to do member lookups on. - /// A cancellation token used to cancel the introspection query. - /// - /// A ValueTask representing the (a)sync operation of preforming the introspection query. - /// The result of the task is a generated update factory expression. - /// - public static async ValueTask>> GenerateUpdateFactoryAsync(IEdgeDBQueryable edgedb, TType value, CancellationToken token = default) + return GetProperties(introspection, typeof(TType), exclusive, @readonly); + } + + /// + /// Gets a collection of properties based on flags. + /// + /// + /// The introspection data on which to cross reference property data. + /// + /// The type to get the properties on. + /// + /// to return only exclusive properties. + /// to exclude exclusive properties. + /// to include either or. + /// + /// + /// to return only readonly properties. + /// to exclude readonly properties. + /// to include either or. + /// + /// Whether or not to include the 'id' property. + /// A collection of . + /// + /// The given type was not found within the introspection data. + /// + public static IEnumerable GetProperties(SchemaInfo schemaInfo, Type type, bool? exclusive = null, + bool? @readonly = null, bool includeId = false) + { + if (!schemaInfo.TryGetObjectInfo(type, out var info)) + throw new NotSupportedException($"Cannot use {type.Name} as there is no schema information for it."); + + var props = type.GetProperties().Where(x => x.GetCustomAttribute() == null); + return props.Where(x => { - var props = await GetPropertiesAsync(edgedb, @readonly: false, token: token).ConfigureAwait(false); + var edgedbName = x.GetEdgeDBPropertyName(); + if (!includeId && edgedbName == "id") + return false; + return info.Properties!.Any(x => x.Name == edgedbName && + (!exclusive.HasValue || x.IsExclusive == exclusive.Value) && + (!@readonly.HasValue || x.IsReadonly == @readonly.Value)); + }); + } + + public static Dictionary MapProperties(SchemaInfo schemaInfo, Type type) + { + var map = EdgeDBPropertyMapInfo.Create(type); + + if (!schemaInfo.TryGetObjectInfo(type, out var info)) + throw new NotSupportedException($"Cannot use {type.Name} as there is no schema information for it."); - props = props.Where(x => x.GetValue(value) != ReflectionUtils.GetDefault(x.PropertyType)); + return map.Properties.ToDictionary(x => x, + x => info.Properties!.First(y => y.Name == x.EdgeDBName)!); + } + + /// + /// Generates a default insert shape expression for the given type and value. + /// + /// The value of which to do member lookups on. + /// The type to generate the shape for. + /// + /// An that contains the insert shape for the given type. + /// + public static Expression GenerateInsertShapeExpression(object? value, Type type) + { + var props = type.GetProperties() + .Where(x => + x.GetCustomAttribute() == null && + x.GetValue(value) != ReflectionUtils.GetDefault(x.PropertyType)); + + return Expression.MemberInit( + Expression.New(type), + props.Select(x => + Expression.Bind(x, Expression.MakeMemberAccess(Expression.Constant(value), x)) + ) + ); + } - return Expression.Lambda>( - Expression.MemberInit( - Expression.New(typeof(TType)), props.Select(x => - Expression.Bind(x, Expression.MakeMemberAccess(Expression.Constant(value), x))) + /// + /// Generates a default update factory expression for the given type and value. + /// + /// The type to generate the shape for. + /// A client used to preform introspection with. + /// The value of which to do member lookups on. + /// A cancellation token used to cancel the introspection query. + /// + /// A ValueTask representing the (a)sync operation of preforming the introspection query. + /// The result of the task is a generated update factory expression. + /// + public static async ValueTask>> GenerateUpdateFactoryAsync( + IEdgeDBQueryable edgedb, TType value, CancellationToken token = default) + { + var props = await GetPropertiesAsync(edgedb, @readonly: false, token: token).ConfigureAwait(false); + + props = props.Where(x => x.GetValue(value) != ReflectionUtils.GetDefault(x.PropertyType)); + + return Expression.Lambda>( + Expression.MemberInit( + Expression.New(typeof(TType)), props.Select(x => + Expression.Bind(x, Expression.MakeMemberAccess(Expression.Constant(value), x))) + ), + Expression.Parameter(typeof(TType), "x") + ); + } + + /// + /// Generates a default filter for the given type. + /// + /// The type to generate the filter for. + /// A client used to preform introspection with. + /// The value of which to do member lookups on. + /// A cancellation token used to cancel the introspection query. + /// + /// A ValueTask representing the (a)sync operation of preforming the introspection query. + /// The result of the task is a generated filter expression. + /// + public static async ValueTask, bool>>> + GenerateUpdateFilterAsync(IEdgeDBQueryable edgedb, TType value, CancellationToken token = default) + { + // TODO: revisit references + // try and get object id + //if (QueryObjectManager.TryGetObjectId(value, out var id)) + // return (_, ctx) => ctx.UnsafeLocal("id") == id; + + // get exclusive properties. + var exclusiveProperties = await GetPropertiesAsync(edgedb, true, token: token).ConfigureAwait(false); + + var unsafeLocalMethod = typeof(QueryContextSelf).GetMethod("UnsafeLocal", 0, new[] {typeof(string)})!; + return Expression.Lambda, bool>>( + exclusiveProperties.Select(x => + { + return Expression.Equal( + Expression.Call( + Expression.Parameter(typeof(QueryContextSelf), "ctx"), + unsafeLocalMethod, + Expression.Constant(x.GetEdgeDBPropertyName()) ), - Expression.Parameter(typeof(TType), "x") - ); - } - - /// - /// Generates a default filter for the given type. - /// - /// The type to generate the filter for. - /// A client used to preform introspection with. - /// The value of which to do member lookups on. - /// A cancellation token used to cancel the introspection query. - /// - /// A ValueTask representing the (a)sync operation of preforming the introspection query. - /// The result of the task is a generated filter expression. - /// - public static async ValueTask, bool>>> GenerateUpdateFilterAsync(IEdgeDBQueryable edgedb, TType value, CancellationToken token = default) - { - // TODO: revisit references - // try and get object id - //if (QueryObjectManager.TryGetObjectId(value, out var id)) - // return (_, ctx) => ctx.UnsafeLocal("id") == id; - - // get exclusive properties. - var exclusiveProperties = await GetPropertiesAsync(edgedb, exclusive: true, token: token).ConfigureAwait(false); - - var unsafeLocalMethod = typeof(QueryContextSelf).GetMethod("UnsafeLocal", genericParameterCount: 0, new Type[] {typeof(string)})!; - return Expression.Lambda, bool>>( - exclusiveProperties.Select(x => - { - - return Expression.Equal( - Expression.Call( - Expression.Parameter(typeof(QueryContextSelf), "ctx"), - unsafeLocalMethod, - Expression.Constant(x.GetEdgeDBPropertyName()) - ), - Expression.MakeMemberAccess(Expression.Constant(value), x) - ); - }).Aggregate((x, y) => Expression.And(x, y)), - Expression.Parameter(typeof(TType), "x"), - Expression.Parameter(typeof(QueryContextSelf), "ctx") - ); - } + Expression.MakeMemberAccess(Expression.Constant(value), x) + ); + }).Aggregate((x, y) => Expression.And(x, y)), + Expression.Parameter(typeof(TType), "x"), + Expression.Parameter(typeof(QueryContextSelf), "ctx") + ); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs index c9d05e57..731d469d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs @@ -1,84 +1,83 @@ using System.Reflection; -namespace EdgeDB +namespace EdgeDB; + +/// +/// A class containing useful utilities for building queries. +/// +internal static class QueryUtils { + private const string VARIABLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static readonly Random _rng = new(); + /// - /// A class containing useful utilities for building queries. + /// Parses a given object into its equivilant edgeql form. /// - internal static class QueryUtils + /// The writer to append the result to. + /// The object to parse. + internal static void ParseObject(QueryWriter writer, object? obj) { - private const string VARIABLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - private static readonly Random _rng = new(); - - /// - /// Parses a given object into its equivilant edgeql form. - /// - /// The writer to append the result to. - /// The object to parse. - internal static void ParseObject(QueryWriter writer, object? obj) + switch (obj) { - switch (obj) + case null: + writer.Append("{}"); + return; + case WriterProxy proxy: + writer.Append(proxy); + break; + case Enum enm: { - case null: - writer.Append("{}"); - return; - case WriterProxy proxy: - writer.Append(proxy); - break; - case Enum enm: - { - var type = enm.GetType(); - var att = type.GetCustomAttribute(); + var type = enm.GetType(); + var att = type.GetCustomAttribute(); - if (att is not null) + if (att is not null) + { + switch (att.Method) { - switch (att.Method) - { - case SerializationMethod.Lower: - writer.SingleQuoted(obj?.ToString()?.ToLower()); - break; - case SerializationMethod.Numeric: - writer.Append(Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}"); - break; - default: - writer.Append("{}"); - break; - } - - return; + case SerializationMethod.Lower: + writer.SingleQuoted(obj?.ToString()?.ToLower()); + break; + case SerializationMethod.Numeric: + writer.Append(Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}"); + break; + default: + writer.Append("{}"); + break; } - writer.SingleQuoted(obj.ToString()); return; } - case SubQuery subQuery: - if (subQuery.RequiresIntrospection) - throw new InvalidOperationException("Subquery required introspection to build"); - subQuery.Build(writer); - break; - case string str: - writer.SingleQuoted(str); - break; - case char ch: - writer.SingleQuoted(ch); - break; - case Type type: - writer.Append(EdgeDBTypeUtils.TryGetScalarType(type, out var info) - ? info.ToString() - : type.GetEdgeDBTypeName()); - break; - default: - writer.Append(obj.ToString()); - break; + writer.SingleQuoted(obj.ToString()); + return; } - } + case SubQuery subQuery: + if (subQuery.RequiresIntrospection) + throw new InvalidOperationException("Subquery required introspection to build"); - /// - /// Generates a random valid variable name for use in queries. - /// - /// A 12 character long random string. - public static string GenerateRandomVariableName() - => new string(Enumerable.Repeat(VARIABLE_CHARS, 12).Select(x => x[_rng.Next(x.Length)]).ToArray()); + subQuery.Build(writer); + break; + case string str: + writer.SingleQuoted(str); + break; + case char ch: + writer.SingleQuoted(ch); + break; + case Type type: + writer.Append(EdgeDBTypeUtils.TryGetScalarType(type, out var info) + ? info.ToString() + : type.GetEdgeDBTypeName()); + break; + default: + writer.Append(obj.ToString()); + break; + } } + + /// + /// Generates a random valid variable name for use in queries. + /// + /// A 12 character long random string. + public static string GenerateRandomVariableName() + => new(Enumerable.Repeat(VARIABLE_CHARS, 12).Select(x => x[_rng.Next(x.Length)]).ToArray()); } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/Union.cs b/src/EdgeDB.Net.QueryBuilder/Utils/Union.cs index afea201f..b468ff7c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/Union.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/Union.cs @@ -36,15 +36,13 @@ internal sealed class Union : Union public Union(T value) : base(value) { } public Union(U value) : base(value) { } - public static Union From(object o, Func> fallback) - { - return o switch + public static Union From(object o, Func> fallback) => + o switch { - T a => new(a), + T a => new Union(a), U b => new Union(b), _ => fallback() }; - } public static implicit operator Union(T a) => new(a); public static implicit operator Union(U b) => new(b); @@ -59,16 +57,14 @@ public Union(T value) : base(value) { } public Union(U value) : base(value) { } public Union(V value) : base(value) { } - public static Union From(object o, Func> fallback) - { - return o switch + public static Union From(object o, Func> fallback) => + o switch { - T a => new(a), - U b => new(b), - V c => new(c), + T a => new Union(a), + U b => new Union(b), + V c => new Union(c), _ => fallback() }; - } public static implicit operator Union(T a) => new(a); public static implicit operator Union(U b) => new(b);