From 179878c0c16792ec12c3355abd5c2791d923d146 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 21 Jun 2022 03:35:11 -0300 Subject: [PATCH 01/70] init --- .../EdgeDB.Examples.ExampleApp/Program.cs | 4 + src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs | 9 +- src/EdgeDB.Net.QueryBuilder/BuiltQuery.cs | 55 -- src/EdgeDB.Net.QueryBuilder/ComputedValue.cs | 35 - src/EdgeDB.Net.QueryBuilder/EdgeQL.cs | 21 - .../InvalidQueryOperationException.cs | 26 - .../Extensions/EdgeDBClientExtensions.cs | 20 - .../Extensions/MemberInfoExtensions.cs | 25 - .../Extensions/TypeExtensions.cs | 15 + .../Operators/Array/Aggregate.g.cs | 2 +- .../Operators/Array/Concat.g.cs | 2 +- .../Operators/Array/Index.g.cs | 2 +- .../Operators/Array/IndexOrDefault.g.cs | 2 +- .../Operators/Array/Join.g.cs | 2 +- .../Operators/Array/Slice.g.cs | 2 +- .../Operators/Array/UnpackArray.g.cs | 2 +- .../Operators/Boolean/All.g.cs | 2 +- .../Operators/Boolean/And.g.cs | 2 +- .../Operators/Boolean/Any.g.cs | 2 +- .../Operators/Boolean/Not.g.cs | 2 +- .../Operators/Boolean/Or.g.cs | 2 +- .../Operators/Bytes/Concat.g.cs | 2 +- .../Operators/Bytes/GetBit.g.cs | 2 +- .../Operators/Bytes/Index.g.cs | 2 +- .../Operators/Bytes/Slice.g.cs | 2 +- .../EnumSerializer.cs} | 8 +- .../Operators/Enums/DateTimeElement.g.cs | 2 +- .../Operators/Enums/DateTimeTruncateUnit.g.cs | 2 +- .../Operators/Enums/DurationTruncateUnit.g.cs | 2 +- .../Operators/Enums/LocalDateElement.g.cs | 2 +- .../Operators/Enums/TimeSpanElement.g.cs | 2 +- ...ilantOperator.cs => EquivalentOperator.cs} | 8 +- .../Operators/Generic/Contains.g.cs | 2 +- .../Operators/Generic/Equals.g.cs | 2 +- .../Operators/Generic/Find.g.cs | 2 +- .../Operators/Generic/GreaterThan.g.cs | 2 +- .../Operators/Generic/GreaterThanOrEqual.g.cs | 2 +- .../Operators/Generic/Length.g.cs | 2 +- .../Operators/Generic/LessThan.g.cs | 2 +- .../Operators/Generic/LessThanOrEqual.g.cs | 2 +- .../Operators/Generic/NotEqual.g.cs | 2 +- .../Operators/IEdgeQLOperator.cs | 11 +- .../Operators/Json/Concat.g.cs | 2 +- .../Operators/Json/Index.g.cs | 2 +- .../Operators/Json/JsonGet.g.cs | 2 +- .../Operators/Json/JsonTypeof.g.cs | 2 +- .../Operators/Json/Slice.g.cs | 2 +- .../Operators/Json/ToJson.g.cs | 2 +- .../Operators/Json/UnpackJsonArray.g.cs | 2 +- .../Operators/Json/UnpackJsonObject.g.cs | 2 +- .../Operators/Links/AddLink.g.cs | 2 +- .../Operators/Links/RemoveLink.g.cs | 2 +- .../Operators/Math/Abs.g.cs | 2 +- .../Operators/Math/Ceil.g.cs | 2 +- .../Operators/Math/Floor.g.cs | 2 +- .../Operators/Math/Logarithm.g.cs | 2 +- .../Operators/Math/Mean.g.cs | 2 +- .../Operators/Math/NaturalLog.g.cs | 2 +- .../Operators/Math/StandardDeviation.g.cs | 2 +- .../Operators/Math/StandardDeviationPop.g.cs | 2 +- .../Operators/Math/Variance.g.cs | 2 +- .../Operators/Math/VariancePop.g.cs | 2 +- .../Operators/Numbers/Add.g.cs | 2 +- .../Operators/Numbers/Divide.g.cs | 2 +- .../Operators/Numbers/Floor.g.cs | 2 +- .../Operators/Numbers/Modulo.g.cs | 2 +- .../Operators/Numbers/Multiply.g.cs | 2 +- .../Operators/Numbers/Negative.g.cs | 2 +- .../Operators/Numbers/Power.g.cs | 2 +- .../Operators/Numbers/Random.g.cs | 2 +- .../Operators/Numbers/Round.g.cs | 2 +- .../Operators/Numbers/Subtract.g.cs | 2 +- .../Operators/Numbers/Sum.g.cs | 2 +- .../Operators/Numbers/ToBigInteger.g.cs | 2 +- .../Operators/Numbers/ToDecimal.g.cs | 2 +- .../Operators/Numbers/ToDouble.g.cs | 2 +- .../Operators/Numbers/ToFloat.g.cs | 2 +- .../Operators/Numbers/ToInt.g.cs | 2 +- .../Operators/Numbers/ToLong.g.cs | 2 +- .../Operators/Numbers/ToShort.g.cs | 2 +- .../{Attributes => Operators}/ParameterMap.cs | 2 +- .../Operators/Sequence/IncrementSequence.g.cs | 2 +- .../Operators/Sequence/ResetSequence.g.cs | 2 +- .../Operators/Sets/AssertDistinct.g.cs | 2 +- .../Operators/Sets/AssertNotNull.g.cs | 2 +- .../Operators/Sets/AssertSingle.g.cs | 2 +- .../Operators/Sets/CastIfTypeIs.g.cs | 2 +- .../Operators/Sets/Coalesce.g.cs | 2 +- .../Operators/Sets/Conditional.g.cs | 2 +- .../Operators/Sets/Contains.g.cs | 2 +- .../Operators/Sets/Count.g.cs | 2 +- .../Operators/Sets/Detached.g.cs | 2 +- .../Operators/Sets/Distinct.g.cs | 2 +- .../Operators/Sets/Enumerate.g.cs | 2 +- .../Operators/Sets/Max.g.cs | 2 +- .../Operators/Sets/Min.g.cs | 2 +- .../Operators/Sets/NotNull.g.cs | 2 +- .../Operators/Sets/Union.g.cs | 2 +- .../Operators/String/Concat.g.cs | 2 +- .../Operators/String/Contains.g.cs | 2 +- .../Operators/String/Find.g.cs | 2 +- .../Operators/String/ILike.g.cs | 2 +- .../Operators/String/Index.g.cs | 2 +- .../Operators/String/IsMatch.g.cs | 2 +- .../Operators/String/Length.g.cs | 2 +- .../Operators/String/Like.g.cs | 2 +- .../Operators/String/Match.g.cs | 2 +- .../Operators/String/MatchAll.g.cs | 2 +- .../Operators/String/PadLeft.g.cs | 2 +- .../Operators/String/PadRight.g.cs | 2 +- .../Operators/String/Repeat.g.cs | 2 +- .../Operators/String/Replace.g.cs | 2 +- .../Operators/String/Slice.g.cs | 2 +- .../Operators/String/Split.g.cs | 2 +- .../Operators/String/ToLower.g.cs | 2 +- .../Operators/String/ToString.g.cs | 2 +- .../Operators/String/ToTitle.g.cs | 2 +- .../Operators/String/ToUpper.g.cs | 2 +- .../Operators/String/Trim.g.cs | 2 +- .../Operators/String/TrimEnd.g.cs | 2 +- .../Operators/String/TrimStart.g.cs | 2 +- .../Operators/Temporal/Add.g.cs | 2 +- .../Temporal/GetCurrentDateTime.g.cs | 2 +- .../Temporal/GetDatetimeElement.g.cs | 2 +- .../Temporal/GetLocalDateElement.g.cs | 2 +- .../Temporal/GetStatementDateTime.g.cs | 2 +- .../Temporal/GetTimespanElement.g.cs | 2 +- .../Temporal/GetTransactionDateTime.g.cs | 2 +- .../Operators/Temporal/Subtract.g.cs | 2 +- .../Operators/Temporal/TimeSpanToSeconds.g.cs | 2 +- .../Operators/Temporal/ToDateTime.g.cs | 2 +- .../Operators/Temporal/ToDateTimeOffset.g.cs | 2 +- .../Operators/Temporal/ToLocalDate.g.cs | 2 +- .../Operators/Temporal/ToLocalTime.g.cs | 2 +- .../Temporal/ToRelativeDuration.g.cs | 2 +- .../Operators/Temporal/ToTimeSpan.g.cs | 2 +- .../Temporal/TruncateDateTimeOffset.g.cs | 2 +- .../Operators/Temporal/TruncateTimeSpan.g.cs | 2 +- .../Operators/Types/Cast.g.cs | 2 +- .../Operators/Types/GetType.g.cs | 2 +- .../Operators/Types/Introspect.g.cs | 2 +- .../Operators/Types/Is.g.cs | 2 +- .../Operators/Types/IsNot.g.cs | 2 +- .../Operators/Types/IsNotTypeOf.g.cs | 2 +- .../Operators/Types/IsTypeOf.g.cs | 2 +- .../Operators/Types/TypeUnion.g.cs | 2 +- .../Operators/Uuid/GenerateGuid.g.cs | 2 +- .../Operators/Variables/Reference.g.cs | 2 +- .../QueryBuilder.Static.cs | 917 ------------------ src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 730 +------------- .../QueryBuilderContext.cs | 63 -- src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 91 +- .../QueryNodes/FilterNode.cs | 12 + .../QueryNodes/QueryNode.cs | 30 + .../QueryNodes/SelectNode.cs | 47 + .../QueryableCollection.cs | 51 + .../Expressions/BinaryExpressionTranslator.cs | 23 + .../ConstantExpressionTranslator.cs | 17 + .../Expressions/ExpressionContext.cs | 22 + .../Expressions/ExpressionTranslator.cs | 94 ++ .../Expressions/MemberExpressionTranslator.cs | 12 + .../Program.cs | 4 +- 162 files changed, 522 insertions(+), 2100 deletions(-) delete mode 100644 src/EdgeDB.Net.QueryBuilder/BuiltQuery.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/ComputedValue.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/EdgeQL.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/Exceptions/InvalidQueryOperationException.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBClientExtensions.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/Extensions/MemberInfoExtensions.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs rename src/EdgeDB.Net.QueryBuilder/{Attributes/EnumSerializerAttribute.cs => Operators/EnumSerializer.cs} (86%) rename src/EdgeDB.Net.QueryBuilder/Operators/{EquivilantOperator.cs => EquivalentOperator.cs} (79%) rename src/EdgeDB.Net.QueryBuilder/{Attributes => Operators}/ParameterMap.cs (94%) delete mode 100644 src/EdgeDB.Net.QueryBuilder/QueryBuilder.Static.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/QueryBuilderContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Program.cs b/examples/EdgeDB.Examples.ExampleApp/Program.cs index e8f593a1..ee18a0f0 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Program.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Program.cs @@ -1,9 +1,13 @@ using EdgeDB; using EdgeDB.ExampleApp; +using EdgeDB.QueryNodes; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; +using static EdgeDB.ExampleApp.Examples.JsonResults; + +ExpressionTranslator.Translate>(x => x.Name == "John"); Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() diff --git a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs index a8ed8186..da29e064 100644 --- a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs +++ b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs @@ -1,3 +1,10 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("EdgeDB.Driver")] +[assembly: InternalsVisibleTo("EdgeDB.Examples.ExampleApp")] +[assembly: InternalsVisibleTo("EdgeDB.DotnetTool")] +[assembly: InternalsVisibleTo("EdgeDB.Tests.Unit")] +[assembly: InternalsVisibleTo("EdgeDB.Tests.Integration")] +[assembly: InternalsVisibleTo("EdgeDB.Tests.Benchmarks")] +[assembly: InternalsVisibleTo("EdgeDB.BinaryDebugger")] +[assembly: InternalsVisibleTo("EdgeDB.Serializer.Experiments")] + diff --git a/src/EdgeDB.Net.QueryBuilder/BuiltQuery.cs b/src/EdgeDB.Net.QueryBuilder/BuiltQuery.cs deleted file mode 100644 index 646fc37a..00000000 --- a/src/EdgeDB.Net.QueryBuilder/BuiltQuery.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Text.RegularExpressions; - -namespace EdgeDB -{ - public sealed class BuiltQuery - { - public string QueryText { get; set; } = ""; - public IEnumerable> Parameters { get; set; } = new Dictionary(); - - public string Prettify() - { - // add newlines - var result = Regex.Replace(QueryText, @"({|\(|\)|}|,)", m => - { - switch (m.Groups[1].Value) - { - case "{" or "(" or ",": - if (m.Groups[1].Value == "{" && QueryText[m.Index + 1] == '}') - return m.Groups[1].Value; - - return $"{m.Groups[1].Value}\n"; - - default: - return $"{((m.Groups[1].Value == "}" && (QueryText[m.Index - 1] == '{' || QueryText[m.Index - 1] == '}')) ? "" : "\n")}{m.Groups[1].Value}{((QueryText.Length != m.Index + 1 && (QueryText[m.Index + 1] != ',')) ? "\n" : "")}"; - } - }).Trim().Replace("\n ", "\n"); - - // clean up newline func - result = Regex.Replace(result, "\n\n", m => "\n"); - - // add indentation - result = Regex.Replace(result, "^", m => - { - int indent = 0; - - foreach (var c in result[..m.Index]) - { - if (c is '(' or '{') - indent++; - if (c is ')' or '}') - indent--; - } - - var next = result.Length != m.Index ? result[m.Index] : '\0'; - - if (next is '}' or ')') - indent--; - - return "".PadLeft(indent * 2); - }, RegexOptions.Multiline); - - return result; - } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/ComputedValue.cs b/src/EdgeDB.Net.QueryBuilder/ComputedValue.cs deleted file mode 100644 index 9f3e39d7..00000000 --- a/src/EdgeDB.Net.QueryBuilder/ComputedValue.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace EdgeDB -{ - public struct ComputedValue : IComputedValue - { - public TInner? Value { get; } - - internal QueryBuilder? Builder { get; } = null; - - internal ComputedValue(TInner? value) - { - Value = value; - } - - internal ComputedValue(TInner? value, QueryBuilder builder) - : this(value) - { - Builder = builder; - } - - public static implicit operator ComputedValue(TInner? value) - { - return new(value); - } - - object? IComputedValue.Value => Value; - QueryBuilder? IComputedValue.Builder => Builder; - } - - public interface IComputedValue - { - public object? Value { get; } - - internal QueryBuilder? Builder { get; } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs b/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs deleted file mode 100644 index 74e7be15..00000000 --- a/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs +++ /dev/null @@ -1,21 +0,0 @@ -using EdgeDB.Operators; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB -{ - public sealed partial class EdgeQL - { - [EquivalentOperator(typeof(VariablesReference))] - public static object? Var(string name) => default; - - [EquivalentOperator(typeof(VariablesReference))] - public static TType? Var(string name) - { - return QueryBuilder.StaticLiteral(name, QueryExpressionType.Variable).SubQuery(); - } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/Exceptions/InvalidQueryOperationException.cs b/src/EdgeDB.Net.QueryBuilder/Exceptions/InvalidQueryOperationException.cs deleted file mode 100644 index 7babf0d7..00000000 --- a/src/EdgeDB.Net.QueryBuilder/Exceptions/InvalidQueryOperationException.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace EdgeDB -{ - /// - /// Thrown when the current method would result in a invalid query being constructed - /// - public class InvalidQueryOperationException : Exception - { - public IReadOnlyCollection ExpressionValidAfter { get; } - public QueryExpressionType Expression { get; } - - public InvalidQueryOperationException(QueryExpressionType expression, string message) - : base(message) - { - Expression = expression; - ExpressionValidAfter = Array.Empty(); - } - - public InvalidQueryOperationException(QueryExpressionType expression, QueryExpressionType[] validAfter) - : base($"Expression {expression} is only valid after {string.Join(", ", validAfter)}") - { - Expression = expression; - ExpressionValidAfter = validAfter; - } - - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBClientExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBClientExtensions.cs deleted file mode 100644 index b7a56970..00000000 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBClientExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using EdgeDB.Models; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB -{ - public static class EdgeDBClientExtensions - { - //public static Task QueryAsync(this EdgeDBClient client, BuiltQuery query, Cardinality? card = null) - // => client.QueryAsync(query.QueryText, query.Parameters.ToDictionary(x => x.Key, x => x.Value), card); - - //public static Task QueryAsync(this EdgeDBClient client, BuiltQuery query, Cardinality? card = null) - // => client.QueryAsync(query.QueryText, query.Parameters.ToDictionary(x => x.Key, x => x.Value), card); - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/MemberInfoExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/MemberInfoExtensions.cs deleted file mode 100644 index 948f1bac..00000000 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/MemberInfoExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB -{ - internal static class MemberInfoExtensions - { - public static Type? GetMemberType(this MemberInfo info) - { - switch (info.MemberType) - { - case MemberTypes.Field: - return ((FieldInfo)info).FieldType; - case MemberTypes.Property: - return ((PropertyInfo)info).PropertyType; - } - - return null; - } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs new file mode 100644 index 00000000..1238ca56 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + internal static class TypeExtensions + { + public static string GetEdgeDBTypeName(this Type type) + => type.GetCustomAttribute()?.Name ?? type.Name; + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Array/Aggregate.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Array/Aggregate.g.cs index 70b6c3ca..891d45aa 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Array/Aggregate.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Array/Aggregate.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class ArrayAggregate : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "array_agg({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Array/Concat.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Array/Concat.g.cs index 180f53d2..59c42b7b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Array/Concat.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Array/Concat.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class ArrayConcat : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} ++ {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Array/Index.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Array/Index.g.cs index 800f36f2..669e68a2 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Array/Index.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Array/Index.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class ArrayIndex : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Index; + public ExpressionType? Expression => ExpressionType.Index; public string EdgeQLOperator => "{0}[{1}]"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Array/IndexOrDefault.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Array/IndexOrDefault.g.cs index 202ba760..99b322f5 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Array/IndexOrDefault.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Array/IndexOrDefault.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class ArrayIndexOrDefault : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "array_get({0}, {1}, )"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Array/Join.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Array/Join.g.cs index f7463849..6f710d32 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Array/Join.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Array/Join.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class ArrayJoin : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "array_join({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Array/Slice.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Array/Slice.g.cs index 9db43160..ee6db3dc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Array/Slice.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Array/Slice.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class ArraySlice : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0}[{1}:{2?}]"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Array/UnpackArray.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Array/UnpackArray.g.cs index cf5cd877..eca15074 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Array/UnpackArray.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Array/UnpackArray.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class ArrayUnpackArray : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "array_unpack({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/All.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/All.g.cs index d4d9752c..d94852af 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/All.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/All.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class BooleanAll : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "all({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/And.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/And.g.cs index 0090af83..7fd7ec4d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/And.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/And.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class BooleanAnd : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.And; + public ExpressionType? Expression => ExpressionType.And; public string EdgeQLOperator => "{0} and {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Any.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Any.g.cs index c61f301d..279c7fc9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Any.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Any.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class BooleanAny : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "any({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Not.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Not.g.cs index 3ac2044a..1441742a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Not.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Not.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class BooleanNot : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Not; + public ExpressionType? Expression => ExpressionType.Not; public string EdgeQLOperator => "not {0}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Or.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Or.g.cs index 7d4fe429..ba78d5da 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Or.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Boolean/Or.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class BooleanOr : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Or; + public ExpressionType? Expression => ExpressionType.Or; public string EdgeQLOperator => "{0} or {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Concat.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Concat.g.cs index b4d81513..ba48ca75 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Concat.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Concat.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class BytesConcat : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} ++ {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/GetBit.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/GetBit.g.cs index 014c22f1..77ca0137 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/GetBit.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/GetBit.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class BytesGetBit : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "bytes_get_bit({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Index.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Index.g.cs index 24b92805..d8a5cb27 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Index.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Index.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class BytesIndex : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Index; + public ExpressionType? Expression => ExpressionType.Index; public string EdgeQLOperator => "{0}[{1}]"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Slice.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Slice.g.cs index 22c81c13..585ea742 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Slice.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Bytes/Slice.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class BytesSlice : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0}[{1}:{2?}]"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Attributes/EnumSerializerAttribute.cs b/src/EdgeDB.Net.QueryBuilder/Operators/EnumSerializer.cs similarity index 86% rename from src/EdgeDB.Net.QueryBuilder/Attributes/EnumSerializerAttribute.cs rename to src/EdgeDB.Net.QueryBuilder/Operators/EnumSerializer.cs index 20687f0b..d7e7331c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Attributes/EnumSerializerAttribute.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/EnumSerializer.cs @@ -1,4 +1,10 @@ -namespace EdgeDB +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB { [AttributeUsage(AttributeTargets.Enum)] public class EnumSerializerAttribute : Attribute diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DateTimeElement.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DateTimeElement.g.cs index d7b927de..66db7e30 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DateTimeElement.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DateTimeElement.g.cs @@ -1,6 +1,6 @@ namespace EdgeDB { - [EnumSerializerAttribute(SerializationMethod.Lower)] + [EnumSerializer(SerializationMethod.Lower)] public enum DateTimeElement { EpochSeconds, diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DateTimeTruncateUnit.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DateTimeTruncateUnit.g.cs index 90b2004e..64592631 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DateTimeTruncateUnit.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DateTimeTruncateUnit.g.cs @@ -1,6 +1,6 @@ namespace EdgeDB { - [EnumSerializerAttribute(SerializationMethod.Lower)] + [EnumSerializer(SerializationMethod.Lower)] public enum DateTimeTruncateUnit { Microseconds, diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DurationTruncateUnit.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DurationTruncateUnit.g.cs index f84e28f7..3e568b8d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DurationTruncateUnit.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Enums/DurationTruncateUnit.g.cs @@ -1,6 +1,6 @@ namespace EdgeDB { - [EnumSerializerAttribute(SerializationMethod.Lower)] + [EnumSerializer(SerializationMethod.Lower)] public enum DurationTruncateUnit { Microseconds, diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Enums/LocalDateElement.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Enums/LocalDateElement.g.cs index d12dd2bc..54d07a9b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Enums/LocalDateElement.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Enums/LocalDateElement.g.cs @@ -1,6 +1,6 @@ namespace EdgeDB { - [EnumSerializerAttribute(SerializationMethod.Lower)] + [EnumSerializer(SerializationMethod.Lower)] public enum LocalDateElement { Century, diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Enums/TimeSpanElement.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Enums/TimeSpanElement.g.cs index b649c46c..0a6b0fa6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Enums/TimeSpanElement.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Enums/TimeSpanElement.g.cs @@ -1,6 +1,6 @@ namespace EdgeDB { - [EnumSerializerAttribute(SerializationMethod.Lower)] + [EnumSerializer(SerializationMethod.Lower)] public enum TimeSpanElement { MidnightSeconds, diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/EquivilantOperator.cs b/src/EdgeDB.Net.QueryBuilder/Operators/EquivalentOperator.cs similarity index 79% rename from src/EdgeDB.Net.QueryBuilder/Operators/EquivilantOperator.cs rename to src/EdgeDB.Net.QueryBuilder/Operators/EquivalentOperator.cs index 94da7a94..6e8ea4e5 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/EquivilantOperator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/EquivalentOperator.cs @@ -1,4 +1,10 @@ -namespace EdgeDB.Operators +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Operators { [AttributeUsage(AttributeTargets.Method)] internal class EquivalentOperator : Attribute diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Contains.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Contains.g.cs index 63e62a30..63274f4a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Contains.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Contains.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class GenericContains : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "contains({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Equals.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Equals.g.cs index 398b4f4c..842d8886 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Equals.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Equals.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class GenericEquals : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Equal; + public ExpressionType? Expression => ExpressionType.Equal; public string EdgeQLOperator => "{0} ?= {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Find.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Find.g.cs index 86491372..54dc60d5 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Find.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Find.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class GenericFind : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "find({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/GreaterThan.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/GreaterThan.g.cs index 70e329f9..0c084a97 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/GreaterThan.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/GreaterThan.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class GenericGreaterThan : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.GreaterThan; + public ExpressionType? Expression => ExpressionType.GreaterThan; public string EdgeQLOperator => "{0} > {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/GreaterThanOrEqual.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/GreaterThanOrEqual.g.cs index 00948237..69f6b9bc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/GreaterThanOrEqual.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/GreaterThanOrEqual.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class GenericGreaterThanOrEqual : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.GreaterThanOrEqual; + public ExpressionType? Expression => ExpressionType.GreaterThanOrEqual; public string EdgeQLOperator => "{0} >= {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Length.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Length.g.cs index 9d850c60..d7f0770b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Length.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/Length.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class GenericLength : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "len({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/LessThan.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/LessThan.g.cs index 01a8662e..49ff8263 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/LessThan.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/LessThan.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class GenericLessThan : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.LessThan; + public ExpressionType? Expression => ExpressionType.LessThan; public string EdgeQLOperator => "{0} < {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/LessThanOrEqual.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/LessThanOrEqual.g.cs index 85f029d6..0c1b5257 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/LessThanOrEqual.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/LessThanOrEqual.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class GenericLessThanOrEqual : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.LessThanOrEqual; + public ExpressionType? Expression => ExpressionType.LessThanOrEqual; public string EdgeQLOperator => "{0} <= {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/NotEqual.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/NotEqual.g.cs index bd2c4dd7..12ea3bb3 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Generic/NotEqual.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Generic/NotEqual.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class GenericNotEqual : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.NotEqual; + public ExpressionType? Expression => ExpressionType.NotEqual; public string EdgeQLOperator => "{0} ?!= {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/IEdgeQLOperator.cs b/src/EdgeDB.Net.QueryBuilder/Operators/IEdgeQLOperator.cs index 16baff97..47eebbee 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/IEdgeQLOperator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/IEdgeQLOperator.cs @@ -1,11 +1,16 @@ -using System.Linq.Expressions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace EdgeDB.Operators { - public interface IEdgeQLOperator + internal interface IEdgeQLOperator { - ExpressionType? ExpressionType { get; } + ExpressionType? Expression { get; } string EdgeQLOperator { get; } string Build(params object[] args) diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Json/Concat.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Json/Concat.g.cs index a1ec56bf..5ad508f6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Json/Concat.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Json/Concat.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class JsonConcat : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} ++ {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Json/Index.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Json/Index.g.cs index 1ed0be7c..4ca03f96 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Json/Index.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Json/Index.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class JsonIndex : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0}[{1}]"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Json/JsonGet.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Json/JsonGet.g.cs index d9ddc74b..a9e64be9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Json/JsonGet.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Json/JsonGet.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class JsonJsonGet : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "json_get({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Json/JsonTypeof.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Json/JsonTypeof.g.cs index 46a6cd62..8e8442a1 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Json/JsonTypeof.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Json/JsonTypeof.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class JsonJsonTypeof : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "json_typeof({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Json/Slice.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Json/Slice.g.cs index 700b9f4c..6fe97c69 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Json/Slice.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Json/Slice.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class JsonSlice : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0}[{1}:{2?}]"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Json/ToJson.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Json/ToJson.g.cs index 6023fdbd..c33aebbc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Json/ToJson.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Json/ToJson.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class JsonToJson : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_json({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Json/UnpackJsonArray.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Json/UnpackJsonArray.g.cs index e42d3d81..94874585 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Json/UnpackJsonArray.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Json/UnpackJsonArray.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class JsonUnpackJsonArray : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "json_array_unpack({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Json/UnpackJsonObject.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Json/UnpackJsonObject.g.cs index aedb3aee..78d86bc8 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Json/UnpackJsonObject.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Json/UnpackJsonObject.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class JsonUnpackJsonObject : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "json_object_unpack({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Links/AddLink.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Links/AddLink.g.cs index df15bc4d..5a1af636 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Links/AddLink.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Links/AddLink.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class LinksAddLink : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "+= {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Links/RemoveLink.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Links/RemoveLink.g.cs index 1c81b64f..5ea65832 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Links/RemoveLink.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Links/RemoveLink.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class LinksRemoveLink : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "-= {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Abs.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Abs.g.cs index d4c8ff33..29ec71bf 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Abs.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Abs.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class MathAbs : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "math::abs({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Ceil.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Ceil.g.cs index ef091e83..917fdaad 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Ceil.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Ceil.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class MathCeil : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "math::ceil({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Floor.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Floor.g.cs index 08a36120..cfb2fa1e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Floor.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Floor.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class MathFloor : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "math::floor({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Logarithm.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Logarithm.g.cs index 157f03ff..bf093a37 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Logarithm.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Logarithm.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class MathLogarithm : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "math::log({0} )"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Mean.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Mean.g.cs index 75759e17..c9ca2e92 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Mean.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Mean.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class MathMean : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "math::mean({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Math/NaturalLog.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Math/NaturalLog.g.cs index 3699fb4e..b5817405 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Math/NaturalLog.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Math/NaturalLog.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class MathNaturalLog : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "math::ln({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Math/StandardDeviation.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Math/StandardDeviation.g.cs index 12efaa03..02d0b35f 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Math/StandardDeviation.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Math/StandardDeviation.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class MathStandardDeviation : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "math::stddev({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Math/StandardDeviationPop.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Math/StandardDeviationPop.g.cs index fcd0aeeb..90f6cdc1 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Math/StandardDeviationPop.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Math/StandardDeviationPop.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class MathStandardDeviationPop : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "math::stddev_pop({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Variance.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Variance.g.cs index 1002e2b7..e92066f3 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Math/Variance.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Math/Variance.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class MathVariance : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "math::var({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Math/VariancePop.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Math/VariancePop.g.cs index e9698312..4dd73d7c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Math/VariancePop.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Math/VariancePop.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class MathVariancePop : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "math::var_pop({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Add.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Add.g.cs index cc650c50..c9d4da3f 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Add.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Add.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersAdd : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Add; + public ExpressionType? Expression => ExpressionType.Add; public string EdgeQLOperator => "{0} + {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Divide.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Divide.g.cs index 4bb9974f..1d5a6613 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Divide.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Divide.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersDivide : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Divide; + public ExpressionType? Expression => ExpressionType.Divide; public string EdgeQLOperator => "{0} / {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Floor.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Floor.g.cs index b78374d7..6b560837 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Floor.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Floor.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersFloor : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} // {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Modulo.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Modulo.g.cs index 3f1fd005..22f3a7dc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Modulo.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Modulo.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersModulo : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Modulo; + public ExpressionType? Expression => ExpressionType.Modulo; public string EdgeQLOperator => "{0} % {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Multiply.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Multiply.g.cs index 7330c65f..467c8aa2 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Multiply.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Multiply.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersMultiply : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Multiply; + public ExpressionType? Expression => ExpressionType.Multiply; public string EdgeQLOperator => "{0} * {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Negative.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Negative.g.cs index 1b786580..f5f351a8 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Negative.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Negative.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersNegative : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Negate; + public ExpressionType? Expression => ExpressionType.Negate; public string EdgeQLOperator => "-{0}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Power.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Power.g.cs index 3886fd86..1c9c7a12 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Power.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Power.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersPower : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} ^ {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Random.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Random.g.cs index 60687136..4166540c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Random.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Random.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersRandom : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "random()"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Round.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Round.g.cs index 36ebb835..142e87ae 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Round.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Round.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersRound : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "round({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Subtract.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Subtract.g.cs index fa2ded78..06c9e920 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Subtract.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Subtract.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersSubtract : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Subtract; + public ExpressionType? Expression => ExpressionType.Subtract; public string EdgeQLOperator => "{0} - {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Sum.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Sum.g.cs index fe96b8b9..ec7a4812 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Sum.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/Sum.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersSum : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "sum({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToBigInteger.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToBigInteger.g.cs index f7f163b7..01161546 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToBigInteger.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToBigInteger.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersToBigInteger : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_bigint({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToDecimal.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToDecimal.g.cs index 5cd0ad1d..13bae736 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToDecimal.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToDecimal.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersToDecimal : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_decimal({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToDouble.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToDouble.g.cs index 20cbb85f..a318766d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToDouble.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToDouble.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersToDouble : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_float64({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToFloat.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToFloat.g.cs index 0e607d2e..8091555d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToFloat.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToFloat.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersToFloat : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_float32({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToInt.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToInt.g.cs index 93943a67..a521e61a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToInt.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToInt.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersToInt : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_int32({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToLong.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToLong.g.cs index ab14c272..a6db7136 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToLong.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToLong.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersToLong : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_int64({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToShort.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToShort.g.cs index 3cf7969f..079b012c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToShort.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Numbers/ToShort.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class NumbersToShort : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_int16({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Attributes/ParameterMap.cs b/src/EdgeDB.Net.QueryBuilder/Operators/ParameterMap.cs similarity index 94% rename from src/EdgeDB.Net.QueryBuilder/Attributes/ParameterMap.cs rename to src/EdgeDB.Net.QueryBuilder/Operators/ParameterMap.cs index bb841537..ba67f042 100644 --- a/src/EdgeDB.Net.QueryBuilder/Attributes/ParameterMap.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/ParameterMap.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB.Operators { [AttributeUsage(AttributeTargets.Method)] internal class ParameterMap : Attribute diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sequence/IncrementSequence.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sequence/IncrementSequence.g.cs index 9b31ffd3..752b133f 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sequence/IncrementSequence.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sequence/IncrementSequence.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SequenceIncrementSequence : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "sequence_next()"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sequence/ResetSequence.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sequence/ResetSequence.g.cs index f950f651..9ea49f46 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sequence/ResetSequence.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sequence/ResetSequence.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SequenceResetSequence : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "sequence_reset(, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertDistinct.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertDistinct.g.cs index 3bcb5ed1..93e91e03 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertDistinct.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertDistinct.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsAssertDistinct : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "assert_distinct({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertNotNull.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertNotNull.g.cs index 7cc12942..6454c64d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertNotNull.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertNotNull.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsAssertNotNull : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "assert_exists({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertSingle.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertSingle.g.cs index 7121d02e..3f52d0b6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertSingle.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/AssertSingle.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsAssertSingle : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "assert_single({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/CastIfTypeIs.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/CastIfTypeIs.g.cs index 9364a338..c0a9b8dc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/CastIfTypeIs.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/CastIfTypeIs.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsCastIfTypeIs : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.TypeIs; + public ExpressionType? Expression => ExpressionType.TypeIs; public string EdgeQLOperator => "{0}[is {1}]"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Coalesce.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Coalesce.g.cs index 0e08568d..755d876e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Coalesce.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Coalesce.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsCoalesce : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Coalesce; + public ExpressionType? Expression => ExpressionType.Coalesce; public string EdgeQLOperator => "{0} ?? {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Conditional.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Conditional.g.cs index 31fef010..b50b5ab2 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Conditional.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Conditional.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsConditional : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Conditional; + public ExpressionType? Expression => ExpressionType.Conditional; public string EdgeQLOperator => "{1} if {0} else {2}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Contains.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Contains.g.cs index 34c8eb59..044df9a9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Contains.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Contains.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsContains : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{1} in {0}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Count.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Count.g.cs index 612171e5..a5956f5d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Count.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Count.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsCount : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "count({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Detached.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Detached.g.cs index da259eb2..905c31c5 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Detached.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Detached.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsDetached : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "detached {0}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Distinct.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Distinct.g.cs index 262c14d6..4076c7da 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Distinct.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Distinct.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsDistinct : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "distinct {0}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Enumerate.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Enumerate.g.cs index a403cdd6..c9311014 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Enumerate.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Enumerate.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsEnumerate : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "enumerate({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Max.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Max.g.cs index 1c9bfc10..f06ce64b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Max.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Max.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsMax : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "max({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Min.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Min.g.cs index 6d14dfea..3c5c5647 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Min.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Min.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsMin : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "min({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/NotNull.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/NotNull.g.cs index dcb09a2e..d57c3b1f 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/NotNull.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/NotNull.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsNotNull : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "exists {0}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Union.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Union.g.cs index 2b932b03..b7075eac 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Union.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Sets/Union.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class SetsUnion : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} union {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Concat.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Concat.g.cs index 4c19cc67..acab2492 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Concat.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Concat.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringConcat : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} ++ {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Contains.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Contains.g.cs index c8d61597..838a4d53 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Contains.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Contains.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringContains : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "contains({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Find.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Find.g.cs index 88b95ead..6c443b92 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Find.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Find.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringFind : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "find({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/ILike.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/ILike.g.cs index 3c74fcfc..22f9f8f9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/ILike.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/ILike.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringILike : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} ilike {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Index.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Index.g.cs index 52b4d69b..71d4c2e4 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Index.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Index.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringIndex : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Index; + public ExpressionType? Expression => ExpressionType.Index; public string EdgeQLOperator => "{0}[{1}]"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/IsMatch.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/IsMatch.g.cs index ec5dd31a..65483c36 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/IsMatch.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/IsMatch.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringIsMatch : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "re_test({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Length.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Length.g.cs index 64f1d590..3136776b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Length.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Length.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringLength : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "len({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Like.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Like.g.cs index 8a8093d7..6b0ea536 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Like.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Like.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringLike : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} like {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Match.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Match.g.cs index 79e1e37b..a48c4a80 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Match.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Match.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringMatch : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "re_match({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/MatchAll.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/MatchAll.g.cs index d4971cf9..2873ab59 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/MatchAll.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/MatchAll.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringMatchAll : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "re_match_all({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/PadLeft.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/PadLeft.g.cs index 66d5ddb7..ab94af4d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/PadLeft.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/PadLeft.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringPadLeft : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "str_pad_start({0}, {1}, {2?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/PadRight.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/PadRight.g.cs index 158165cd..d9298cd5 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/PadRight.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/PadRight.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringPadRight : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "str_pad_end({0}, {1}, {2?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Repeat.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Repeat.g.cs index 287dbe50..1af7b528 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Repeat.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Repeat.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringRepeat : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "str_repeat({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Replace.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Replace.g.cs index dc27d42e..68fe6619 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Replace.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Replace.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringReplace : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "re_replace({0}, {1}, {2}, )"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Slice.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Slice.g.cs index 31a4cab0..0a7ad2ee 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Slice.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Slice.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringSlice : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0}[{1}:{2?}]"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Split.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Split.g.cs index a7a13ffe..c888c315 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Split.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Split.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringSplit : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "str_split({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/ToLower.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/ToLower.g.cs index fc6b4591..e3dae82b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/ToLower.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/ToLower.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringToLower : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "str_lower({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/ToString.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/ToString.g.cs index 927f8014..86d54ee7 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/ToString.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/ToString.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringToString : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_str({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/ToTitle.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/ToTitle.g.cs index 2e82f830..5ab4333a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/ToTitle.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/ToTitle.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringToTitle : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "str_title({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/ToUpper.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/ToUpper.g.cs index 47e71a65..ff4ca518 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/ToUpper.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/ToUpper.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringToUpper : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "str_upper({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/Trim.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/Trim.g.cs index 7d7a4eba..345b948a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/Trim.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/Trim.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringTrim : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "str_trim({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/TrimEnd.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/TrimEnd.g.cs index c9923612..301eda17 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/TrimEnd.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/TrimEnd.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringTrimEnd : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "str_trim_end({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/String/TrimStart.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/String/TrimStart.g.cs index 52937c4a..b2beb00d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/String/TrimStart.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/String/TrimStart.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class StringTrimStart : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "str_trim_start({0}, {1?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/Add.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/Add.g.cs index f84e245e..4e3c6470 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/Add.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/Add.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalAdd : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Add; + public ExpressionType? Expression => ExpressionType.Add; public string EdgeQLOperator => "{0} + {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetCurrentDateTime.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetCurrentDateTime.g.cs index 48e7594e..57d3ec69 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetCurrentDateTime.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetCurrentDateTime.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalGetCurrentDateTime : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "std::datetime_current()"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetDatetimeElement.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetDatetimeElement.g.cs index 118b79c9..87324ef7 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetDatetimeElement.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetDatetimeElement.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalGetDatetimeElement : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "datetime_get({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetLocalDateElement.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetLocalDateElement.g.cs index 659c329c..5e9f3b1e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetLocalDateElement.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetLocalDateElement.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalGetLocalDateElement : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "cal::date_get({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetStatementDateTime.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetStatementDateTime.g.cs index be41a791..207a3f59 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetStatementDateTime.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetStatementDateTime.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalGetStatementDateTime : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "std::datetime_of_statement()"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetTimespanElement.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetTimespanElement.g.cs index 6c51d01d..b719392c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetTimespanElement.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetTimespanElement.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalGetTimespanElement : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "cal::time_get({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetTransactionDateTime.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetTransactionDateTime.g.cs index ac9a9082..baabb5bc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetTransactionDateTime.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/GetTransactionDateTime.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalGetTransactionDateTime : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "std::datetime_of_transaction()"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/Subtract.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/Subtract.g.cs index 348e90d6..f3c78a9b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/Subtract.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/Subtract.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalSubtract : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Subtract; + public ExpressionType? Expression => ExpressionType.Subtract; public string EdgeQLOperator => "{0} - {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TimeSpanToSeconds.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TimeSpanToSeconds.g.cs index f0dc4c9e..f7861abc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TimeSpanToSeconds.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TimeSpanToSeconds.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalTimeSpanToSeconds : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "std::duration_to_seconds({0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToDateTime.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToDateTime.g.cs index 32564f84..7e3c759e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToDateTime.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToDateTime.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalToDateTime : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "cal::to_local_datetime({0}, {1?}, {2?}, {3?}, {4?}, {5?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToDateTimeOffset.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToDateTimeOffset.g.cs index 710048bf..9ec21eb9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToDateTimeOffset.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToDateTimeOffset.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalToDateTimeOffset : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_datetime({0}, {1?}, {2?}, {3?}, {4?}, {5?}, {6?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToLocalDate.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToLocalDate.g.cs index 91e148dc..626d28f8 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToLocalDate.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToLocalDate.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalToLocalDate : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "cal::to_local_date({0}, {1?}, {2?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToLocalTime.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToLocalTime.g.cs index 46e33a9e..7527f0c5 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToLocalTime.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToLocalTime.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalToLocalTime : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "cal::to_local_time({0}, {1?}, {2?})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToRelativeDuration.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToRelativeDuration.g.cs index 95646a99..039dc8b2 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToRelativeDuration.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToRelativeDuration.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalToRelativeDuration : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "cal::to_relative_duration(, , , , , , )"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToTimeSpan.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToTimeSpan.g.cs index 030cb639..e2d98cce 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToTimeSpan.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/ToTimeSpan.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalToTimeSpan : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "to_duration(, , )"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TruncateDateTimeOffset.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TruncateDateTimeOffset.g.cs index 45e662f3..e2d1efdf 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TruncateDateTimeOffset.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TruncateDateTimeOffset.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalTruncateDateTimeOffset : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "datetime_truncate({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TruncateTimeSpan.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TruncateTimeSpan.g.cs index 23d67cbe..e86ed0ed 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TruncateTimeSpan.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Temporal/TruncateTimeSpan.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TemporalTruncateTimeSpan : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "duration_truncate({0}, {1})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Types/Cast.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Types/Cast.g.cs index 3a425b08..a8bcd315 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Types/Cast.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Types/Cast.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TypesCast : IEdgeQLOperator { - public ExpressionType? ExpressionType => System.Linq.Expressions.ExpressionType.Convert; + public ExpressionType? Expression => ExpressionType.Convert; public string EdgeQLOperator => "<{0}>{1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Types/GetType.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Types/GetType.g.cs index 38614f1e..84226f24 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Types/GetType.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Types/GetType.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TypesGetType : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "introspect (typeof {0})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Types/Introspect.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Types/Introspect.g.cs index fe989ff2..2eeece15 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Types/Introspect.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Types/Introspect.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TypesIntrospect : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "introspect {0}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Types/Is.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Types/Is.g.cs index 3b7a8e9b..9efb40d6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Types/Is.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Types/Is.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TypesIs : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} is {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsNot.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsNot.g.cs index fa377be5..8789b9e5 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsNot.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsNot.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TypesIsNot : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} is not {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsNotTypeOf.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsNotTypeOf.g.cs index bed02f6c..6b901e03 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsNotTypeOf.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsNotTypeOf.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TypesIsNotTypeOf : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} is not typeof {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsTypeOf.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsTypeOf.g.cs index 579cf747..28a9d8fc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsTypeOf.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Types/IsTypeOf.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TypesIsTypeOf : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0} is typeof {1}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Types/TypeUnion.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Types/TypeUnion.g.cs index 61fdedd8..8234971e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Types/TypeUnion.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Types/TypeUnion.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class TypesTypeUnion : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "({0} | {1} { | :2+})"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Uuid/GenerateGuid.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Uuid/GenerateGuid.g.cs index 8526c883..9f82b61d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Uuid/GenerateGuid.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Uuid/GenerateGuid.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class UuidGenerateGuid : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "uuid_generate_v1mc()"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Variables/Reference.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Variables/Reference.g.cs index 0512bf04..323e282d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Variables/Reference.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Variables/Reference.g.cs @@ -4,7 +4,7 @@ namespace EdgeDB.Operators { internal class VariablesReference : IEdgeQLOperator { - public ExpressionType? ExpressionType => null; + public ExpressionType? Expression => null; public string EdgeQLOperator => "{0}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.Static.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.Static.cs deleted file mode 100644 index f99828d8..00000000 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.Static.cs +++ /dev/null @@ -1,917 +0,0 @@ -using EdgeDB.DataTypes; -using EdgeDB.Operators; -using System.Collections; -using System.Linq.Expressions; -using System.Reflection; -using System.Text.RegularExpressions; - -namespace EdgeDB -{ - public partial class QueryBuilder - { - private static readonly Dictionary _converters; - private static readonly Dictionary _operators = new(); - - private static readonly Dictionary _reservedPropertiesOperators = new() - { - { "String.Length", new StringLength() }, - }; - - private static readonly Dictionary _reservedFunctionOperators = new(EdgeQL.FunctionOperators) - { - { "ICollection.IndexOf", new GenericFind() }, - { "IEnumerable.IndexOf", new GenericFind() }, - - { "ICollection.Contains", new GenericContains() }, - { "IEnumerable.Contains", new GenericContains() }, - - { "String.get_Chars", new StringIndex() }, - { "Sring.Substring", new StringSlice() }, - }; - - private static readonly Dictionary> _reservedSubQueryFunctions = new() - { - { - "IEnumerable.OrderBy", - (ctx, source, param) => - { - var sourceElement = ConvertExpression(source!, ctx); - return new QueryBuilder(); - } - } - }; - - static QueryBuilder() - { - var types = Assembly.GetExecutingAssembly().GetTypes().Where(x => x.GetInterfaces().Contains(typeof(IEdgeQLOperator))); - - var converters = new Dictionary(); - - foreach (var type in types) - { - var inst = (IEdgeQLOperator)Activator.CreateInstance(type)!; - - if (inst.ExpressionType.HasValue && !converters.ContainsKey(inst.ExpressionType.Value)) - converters.Add(inst.ExpressionType.Value, inst); - } - - // get the funcs - var methods = typeof(EdgeQL).GetMethods(); - - _operators = methods.Select(x => - { - var att = x.GetCustomAttribute(); - return att == null ? ((MethodInfo? Info, IEdgeQLOperator? Operator))(null, null) : ((MethodInfo? Info, IEdgeQLOperator? Operator))(x, att.Operator); - }).Where(x => x.Info != null && x.Operator != null).ToDictionary(x => x.Operator!, x => x.Info!.ReturnType); - - _converters = converters; - } - - public static Type? ReverseLookupFunction(string funcText) - { - // check if its a function - var funcMatch = Regex.Match(funcText, @"^(\w+)\("); - - if (funcMatch.Success) - { - var funcName = funcMatch.Groups[1].Value; - // lookup in our defined ops for this func - return _operators.FirstOrDefault(x => Regex.IsMatch(x.Key.EdgeQLOperator, @"^\w+\(") && x.Key.EdgeQLOperator.StartsWith($"{funcName}(")).Value; - } - return null; - } - - internal static (string Query, Dictionary Arguments) SerializeQueryObject(TType obj, QueryBuilderContext? context = null) - { - var props = typeof(TType).GetProperties().Where(x => x.GetCustomAttribute() == null && x.GetCustomAttribute()?.IsComputed == false); - var propertySet = new List(); - var args = new Dictionary(); - - foreach (var prop in props) - { - var name = GetPropertyName(prop); - var result = SerializeProperty(prop.PropertyType, prop.GetValue(obj), IsLink(prop), context); - - if (!(context?.IncludeEmptySets ?? true) && result.Property == "{}") - continue; - - propertySet.Add($"{name} := {result.Property}"); - args = args.Concat(result.Arguments).ToDictionary(x => x.Key, x => x.Value); // TODO: optimize? - } - - - - return ($"{{ {string.Join(", ", propertySet)} }}", args); - } - - internal static (string Property, Dictionary Arguments) SerializeProperty(TType value, bool isLink, QueryBuilderContext? context = null) - => SerializeProperty(typeof(TType), value, isLink, context); - internal static (string Property, Dictionary Arguments) SerializeProperty(Type type, object? value, bool isLink, QueryBuilderContext? context = null) - { - var args = new Dictionary(); - var queryValue = ""; - var varName = $"p_{Guid.NewGuid().ToString().Replace("-", "")}"; - - if (isLink) - { - // removed with set. - //if (value is ISet set && set.IsSubQuery && set.QueryBuilder is QueryBuilder builder) - //{ - // var result = builder.Build(context?.Enter(x => x.UseDetached = type == set.GetInnerType()) ?? new()); - // args = args.Concat(result.Parameters).ToDictionary(x => x.Key, x => x.Value); - // queryValue = $"({result.QueryText})"; - //} - if (value is IEnumerable enm) - { - List values = new(); - // enumerate object links for mock objects - foreach (var val in enm) - { - if (val is not ISubQueryType sub) - throw new InvalidOperationException($"Expected a sub query for object type, but got {val.GetType()}"); - - values.Add(sub.Builder); - } - - var vals = values.Select(x => - { - args = x.Arguments.Concat(args).ToDictionary(x => x.Key, x => x.Value); - return $"({x})"; - }); - - queryValue = $"{{ {string.Join(", ", vals)} }}"; - } - else if (value is ISubQueryType sub) - { - var result = sub.Builder.Build(context?.Enter(x => x.UseDetached = type == sub.Builder.QuerySelectorType) ?? new()); - args = args.Concat(result.Parameters).ToDictionary(x => x.Key, x => x.Value); - queryValue = $"({result.QueryText})"; - } - else if (value == null) - { - queryValue = "{}"; // TODO: change empty set? - } - else throw new ArgumentException("Unresolved link parser"); - } - else if (value is ISubQueryType sub) - { - var result = sub.Builder.Build(context?.Enter(x => x.UseDetached = sub.Builder.QuerySelectorType == type) ?? new()); - args = args.Concat(result.Parameters).ToDictionary(x => x.Key, x => x.Value); - queryValue = $"({result.QueryText})"; - } - else if (value is IQueryResultObject obj && (context?.IntrospectObjectIds ?? false)) - { - // generate a select query - queryValue = $"(select {GetTypeName(type)} filter .id = \"{obj.GetObjectId()}\")"; - } - else if (value is QueryBuilder builder) - { - var result = builder.Build(context?.Enter(x => x.UseDetached = builder.QuerySelectorType == type) ?? new()); - args = args.Concat(result.Parameters).ToDictionary(x => x.Key, x => x.Value); - queryValue = $"({result.QueryText})"; - } - else if (value == null) - { - queryValue = "{}";// TODO: change empty set? - } - else - { - queryValue = $"<{PacketSerializer.GetEdgeQLType(type)}>${varName}"; - args.Add(varName, value); - } - - return (queryValue, args); - } - - internal static List ParseShapeDefinition(object? shape, Type parentType, bool referenceObject = false) - { - List shapeProps = new(); - - if (shape == null) - return shapeProps; - - // extract all props - var type = shape.GetType(); - - var props = type.GetProperties(); - - foreach (var prop in props) - { - // TODO: - //var isMultiLink = false; //ReflectionUtils.TryGetRawGeneric(typeof(MultiLink<>), parentType, out var multiLink); - - var referenceProp = parentType.GetProperty(prop.Name) ?? parentType.GetProperty(prop.Name); //(isMultiLink ? multiLink!.GenericTypeArguments[0]!.GetProperty(prop.Name) : ; - - if (referenceProp == null) - continue; - - var propName = GetPropertyName(referenceProp); - - if (prop.PropertyType == typeof(bool)) - { - if ((bool)prop.GetValue(shape)!) - shapeProps.Add(propName); - } - else if (prop.PropertyType.IsAssignableTo(typeof(QueryBuilder))) - { - var val = (QueryBuilder)prop.GetValue(shape)!; - - var query = val.Build(new QueryBuilderContext().Enter(x => x.ExplicitShapeDefinition = true)); - - shapeProps.Add($"{propName}:{query.QueryText}"); - } - else - { - // assume anon type - var val = ParseShapeDefinition(prop.GetValue(shape), referenceProp.PropertyType, referenceObject); - shapeProps.Add($"{propName}: {{ {string.Join(", ", val)} }}"); - } - } - - return shapeProps; - } - - internal static List ParseShapeDefinition(LambdaExpression lambda, QueryBuilderContext builderContext) - { - List props = new(); - - var body = lambda.Body; - - if (body is not NewExpression bodyExpression) - throw new ArgumentException($"Cannot infer shape from {body.NodeType}"); - - var type = lambda.Body.Type; - - var anonProperties = type.GetProperties(); - - for (int i = 0; i != anonProperties.Length; i++) - { - var prop = anonProperties[i]; - var param = bodyExpression.Arguments[i]; - - switch (param) - { - case ConstantExpression constant: - // TODO: extend to non booleans in the future? - - if (constant.Value is not bool val) - throw new InvalidDataException($"Expected boolean but got {constant.Type}"); - - if (val) props.Add($"{GetPropertyName(prop)}"); - break; - case MethodCallExpression mce: - // TODO: handle weird method calls - var source = mce.Arguments[0]; - - var exp = ConvertExpression(mce, new QueryContext - { - AllowSubQueryGeneration = true, - BuilderContext = builderContext.Enter(x => x.ExplicitShapeDefinition = true), - }); - - break; - - default: - break; - } - } - - - return props; - } - - internal static List ParseShapeDefinition(QueryBuilderContext context, bool referenceObject = false, params Expression[] shape) - { - List props = new(); - - foreach (var exp in shape) - { - var selector = exp; - - LambdaExpression? lam = null; - - if (selector is LambdaExpression lamd) - { - selector = lamd.Body; - lam = lamd; - - context.ParentQueryTypeName = lamd.Parameters[0].Name; - context.ParentQueryType = lamd.Parameters[0].Type; - - if (lamd.Body.Type.Name.StartsWith("<>f__AnonymousType")) - { - // anon type: parse that - props.AddRange(ParseShapeDefinition(lamd, context)); - continue; - } - } - - if (selector is MemberExpression mbs) - { - List innerExpression = new(); - - void AddExp(MemberExpression m) - { - innerExpression.Add(m); - - if (m.Expression is not null and MemberExpression mb) - AddExp(mb); - } - - AddExp(mbs); - - innerExpression.Reverse(); - - if (mbs.Type.GetCustomAttribute() != null) - { - props.Add($"{mbs.Member.GetCustomAttribute()?.Name ?? mbs.Member.Name}: {{ {string.Join(", ", ParseShapeDefinition(context, referenceObject, new Expression[] { mbs.Expression! }))} }}"); - } - - var name = RecurseNameLookup(mbs); - - if (lam != null) - { - name = referenceObject - ? name[lam.Parameters[0].Name!.Length..] - : name.Substring(lam.Parameters[0].Name!.Length + 1, name.Length - 1 - lam.Parameters[0].Name!.Length); - props.Add(name); - } - } - else if (selector is MethodCallExpression mc) - { - // allow select only - if (mc.Method.Name != "Select") - { - throw new ArgumentException("Only Select method is allowed on property selectors"); - } - - // create a dynamic version of this method - var funcInner = mc.Arguments[1].Type.GenericTypeArguments[0]; - var funcSelect = mc.Arguments[1].Type.GenericTypeArguments[1]; - var method = typeof(QueryBuilder).GetRuntimeMethods().First(x => x.Name == nameof(ParseShapeDefinition)).MakeGenericMethod(funcInner, funcSelect); - - // make the array arg - var arr = Array.CreateInstance(typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(funcInner, funcSelect)), 1); - arr.SetValue(mc.Arguments[1], 0); - - props.Add($"{GetTypeName(funcInner)}: {{ {string.Join(", ", (List)method.Invoke(null, new object[] { referenceObject, arr })!)} }}"); - } - else if (selector is UnaryExpression unary && unary.Type == typeof(object) && unary.Operand is MemberExpression mb && lam != null) - { - var name = RecurseNameLookup(mb); - - name = referenceObject && (!name.Contains(".@")) - ? name[lam.Parameters[0].Name!.Length..] - : name.Substring(lam.Parameters[0].Name!.Length + 1, name.Length - 1 - lam.Parameters[0].Name!.Length); - - props.Add(name); - } - else throw new KeyNotFoundException("Cannot resolve converter"); - } - - return props; - } - - internal static (List Properties, List> Arguments) GetTypePropertyNames(Type t, QueryBuilderContext context, ArgumentAggregationContext? aggregationContext = null) - { - List returnProps = new(); - var props = t.GetProperties().Where(x => x.GetCustomAttribute() == null); - var instance = Activator.CreateInstance(t); - var args = new List>(); - // get inner props on types - foreach (var prop in props) - { - var name = prop.GetCustomAttribute()?.Name ?? prop.Name; - var type = prop.PropertyType; - - if (ReflectionUtils.IsSubclassOfRawGeneric(typeof(ComputedValue<>), type)) - { - if (!context.AllowComputedValues) - continue; - - // its a computed value with a query, expose it - var val = (IComputedValue)prop.GetValue(instance)!; - returnProps.Add($"{name} := ({val.Builder})"); - args = val.Builder!.Arguments; - continue; - } - - if (TryGetEnumerableType(prop.PropertyType, out var i) && i.GetCustomAttribute() != null) - type = i; - - var edgeqlType = type.GetCustomAttribute(); - - if (edgeqlType != null) - { - if (type == t && aggregationContext?.PropertyType == type) - { - continue; - } - - if (context.MaxAggregationDepth.HasValue && context.MaxAggregationDepth.Value == aggregationContext?.Depth) - continue; - - var result = GetTypePropertyNames(type, context, aggregationContext?.Enter(type) ?? new ArgumentAggregationContext(type)); - args.AddRange(result.Arguments); - returnProps.Add($"{name}: {{ {string.Join(", ", result.Properties)} }}"); - } - else - { - returnProps.Add(name); - } - } - - return (returnProps, args); - } - - internal class ArgumentAggregationContext - { - public Type PropertyType { get; } - public ArgumentAggregationContext? Parent { get; private set; } - public int Depth { get; set; } - - public ArgumentAggregationContext(Type propType) - { - PropertyType = propType; - Depth = 0; - } - - public ArgumentAggregationContext Enter(Type propType) - { - return new ArgumentAggregationContext(propType) - { - Parent = this, - Depth = Depth + 1 - }; - } - } - - // TODO: add node checks when in Char context, using int converters while in char context will result in the int being converted to a character. - internal static (string Filter, Dictionary Arguments) ConvertExpression(Expression s, QueryContext context) - { - if (s is MemberInitExpression init) - { - var result = new List<(string Filter, Dictionary Arguments)>(); - foreach (MemberAssignment binding in init.Bindings) - { - var innerContext = context.Enter(binding.Expression, modifier: x => x.BindingType = binding.Member.GetMemberType()); - var value = ConvertExpression(binding.Expression, innerContext); - var name = binding.Member.GetCustomAttribute()?.Name ?? binding.Member.Name; - result.Add(($"{name}{(innerContext.IncludeSetOperand ? " :=" : "")} {value.Filter}", value.Arguments)); - } - - return (string.Join(", ", result.Select(x => x.Filter)), result.SelectMany(x => x.Arguments).ToDictionary(x => x.Key, x => x.Value)); - } - - if (s is BinaryExpression bin) - { - // compute left and right - var left = ConvertExpression(bin.Left, context.Enter(bin.Left)); - var right = ConvertExpression(bin.Right, context.Enter(bin.Right)); - - // reset char context - context.IsCharContext = false; - - // get converter - return _converters.TryGetValue(s.NodeType, out var conv) - ? ((string Filter, Dictionary Arguments))(conv.Build(left.Filter, right.Filter), left.Arguments.Concat(right.Arguments).ToDictionary(x => x.Key, x => x.Value)) - : throw new NotSupportedException($"Couldn't find operator for {s.NodeType}"); - - } - - if (s is UnaryExpression una) - { - // TODO: nullable converts? - - // get the value - var val = ConvertExpression(una.Operand, context); - - // cast only if not char - var edgeqlType = una.Operand.Type == typeof(char) ? "str" : PacketSerializer.GetEdgeQLType(una.Type); - - // set char context - context.IsCharContext = una.Operand.Type == typeof(char); - - return edgeqlType == null - ? throw new NotSupportedException($"No edgeql type map found for type {una.Type}") - : ((string Filter, Dictionary Arguments))($"<{edgeqlType}>{val.Filter}", val.Arguments); - } - - if (s is MethodCallExpression mc) - { - // check for query builder - if (TryResolveQueryBuilder(mc, out var innerBuilder)) - { - var result = innerBuilder!.Build(context.BuilderContext?.Enter(x => - { - x.LimitToOne = !TryGetEnumerableType(context.BindingType ?? mc.Type, out var _); - - }) ?? new()); - - return ($"({result.QueryText})", result.Parameters.ToDictionary(x => x.Key, x => x.Value)); - } - - - List<(string Filter, Dictionary Arguments)>? arguments = new(); - Dictionary parameterMap = new(); - - // check if we have a reserved operator for it - if (_reservedFunctionOperators.TryGetValue($"{mc.Method.DeclaringType!.Name}.{mc.Method.Name}", out IEdgeQLOperator? op) || (mc.Method.DeclaringType?.GetInterfaces().Any(i => _reservedFunctionOperators.TryGetValue($"{i.Name}.{mc.Method.Name}", out op)) ?? false)) - { - // add the object as a param - var objectInst = mc.Object; - if (objectInst == null && !context.AllowStaticOperators) - throw new ArgumentException("Cannot use static methods that require an instance to build"); - else if (objectInst != null) - { - var inst = ConvertExpression(objectInst, context.Enter(objectInst)); - arguments.Add(inst); - } - } - else if (mc.Method.DeclaringType == typeof(EdgeQL)) - { - // get the equivilant operator - op = mc.Method.GetCustomAttribute()?.Operator; - - // check for parameter map - parameterMap = new Dictionary(mc.Method.GetCustomAttributes().ToDictionary(x => x.Index, x => x.Name)); - } - else if (_reservedSubQueryFunctions.TryGetValue($"{mc.Method.DeclaringType!.Name}.{mc.Method.Name}", out var factory)) - { - // get source - var source = mc.Arguments[0]; - var builder = factory(context, source, mc.Arguments.Skip(1).ToArray()); - - var subQuery = builder.Build(context.BuilderContext!); - return (subQuery.QueryText, subQuery.Parameters.ToDictionary(x => x.Key, x => x.Value)); // TODO: dict init - } - - if (op == null) - throw new NotSupportedException($"Couldn't find operator for method {mc.Method}"); - - // parse the arguments - arguments.AddRange(mc.Arguments.SelectMany((x, i) => - { - return x is NewArrayExpression newArr - ? newArr.Expressions.Select((x, i) => ConvertExpression(x, context.Enter(x, i))) - : (IEnumerable<(string Filter, Dictionary Arguments)>)(new (string Filter, Dictionary Arguments)[] { ConvertExpression(x, context.Enter(x)) })!; - })); - - // add our parameter map - if (parameterMap.Any()) - { - var genericMethod = mc.Method.GetGenericMethodDefinition(); - var genericTypeArgs = mc.Method.GetGenericArguments(); - var genericDict = genericMethod.GetGenericArguments().Select((x, i) => new KeyValuePair(x.Name, genericTypeArgs[i])).ToDictionary(x => x.Key, x => x.Value); - foreach (var item in parameterMap) - { - if (genericDict.TryGetValue(item.Value, out var strongType)) - { - // convert the strong type - var typename = PacketSerializer.GetEdgeQLType(strongType) ?? GetTypeName(strongType); - - // insert into arguments - arguments.Insert((int)item.Key, (typename, new())); - } - } - } - - try - { - string builtOperator = op.Build(arguments.Select(x => x.Filter).ToArray()); - - switch (op) - { - case LinksAddLink or LinksRemoveLink: - { - context.IncludeSetOperand = false; - } - break; - case VariablesReference: - { - context.BuilderContext?.AddTrackedVariable(builtOperator); - } - break; - default: - builtOperator = $"({builtOperator})"; - break; - } - - return (builtOperator, arguments.SelectMany(x => x.Arguments).ToDictionary(x => x.Key, x => x.Value)); - } - catch (Exception x) - { - throw new NotSupportedException($"Failed to convert {mc.Method} to a EdgeQL expression", x); - } - } - - if (s is MemberExpression mbs && s.NodeType == ExpressionType.MemberAccess) - { - if (mbs.Expression is ConstantExpression innerConstant) - { - if (IsEdgeQLType(innerConstant.Type)) - { - // assume its a reference to another property and use the self reference context - var name = mbs.Member.GetCustomAttribute()?.Name ?? mbs.Member.Name; - return ($".{name}", new()); - } - - object? value = null; - Dictionary arguments = new(); - - switch (mbs.Member.MemberType) - { - case MemberTypes.Field: - value = ((FieldInfo)mbs.Member).GetValue(innerConstant.Value); - break; - case MemberTypes.Property: - value = ((PropertyInfo)mbs.Member).GetValue(innerConstant.Value); - break; - } - - arguments.Add(mbs.Member.Name, value); - - var edgeqlType = PacketSerializer.GetEdgeQLType(mbs.Type); - - return edgeqlType == null - ? throw new NotSupportedException($"No edgeql type map found for type {mbs.Type}") - : ((string Filter, Dictionary Arguments))($"<{edgeqlType}>${mbs.Member.Name}", arguments); - } - // TODO: optimize this - else if (mbs.Expression is MemberExpression innermbs && _reservedPropertiesOperators.TryGetValue($"{innermbs.Type.Name}.{mbs.Member.Name}", out var op)) - { - // convert the entire expression with the func - var ts = RecurseNameLookup(mbs, true); - if (ts.StartsWith($"{context.ParameterName}.")) - { - return (op.Build(ts.Substring(context.ParameterName!.Length, ts.Length - context.ParameterName.Length)), new()); - } - } - else - { - // check for variable access with recursion - // tostring it and check the starter accesser for our parameter - var ts = RecurseNameLookup(mbs); - if (ts.StartsWith($"{context.ParameterName}.")) - { - return (ts.Substring(context.ParameterName!.Length, ts.Length - context.ParameterName.Length), new()); - } - - if (TryResolveOperator(mbs, out var opr, out var exp) && opr is VariablesReference) - { - if (exp == null || opr == null) - throw new InvalidOperationException("Got faulty operator resolve results"); - - var varName = ConvertExpression(exp, context.Enter(exp)); - - var param = Expression.Parameter(exp.Method.ReturnType, "x"); - var newExp = mbs.Update(param); - var func = Expression.Lambda(newExp, param); - - var accessors = ConvertExpression(func.Body, new QueryContext - { - Body = func.Body, - ParameterName = "x", - ParameterType = exp.Method.ReturnType - }); - - context.BuilderContext?.AddTrackedVariable($"{varName.Filter}{accessors.Filter}"); - - // TODO: optimize dict - return ($"{varName.Filter}{accessors.Filter}", varName.Arguments.Concat(accessors.Arguments).ToDictionary(x => x.Key, x => x.Value)); - } - - throw new NotSupportedException($"Unknown handler for member access: {mbs}"); - } - } - - if (s is ConstantExpression constant && s.NodeType == ExpressionType.Constant) - { - return (ParseArgument(constant.Value, context), new()); - } - - if (s is NewArrayExpression newArr) - { - IEnumerable<(string Filter, Dictionary Arguments)>? values; - // check if its a 'params' array - if (context.ParameterIndex.HasValue && context.Parent?.Body is MethodCallExpression callExpression) - { - var p = callExpression.Method.GetParameters(); - - if (p[context.ParameterIndex.Value].GetCustomAttribute() != null) - { - // return joined by , - values = newArr.Expressions.Select((x, i) => ConvertExpression(x, context.Enter(x, i))); - - return (string.Join(", ", values.Select(x => x.Filter)), values.SelectMany(x => x.Arguments).ToDictionary(x => x.Key, x => x.Value)); - } - } - - // return normal array - values = newArr.Expressions.Select((x, i) => ConvertExpression(x, context.Enter(x, i))); - - return ($"[{string.Join(", ", values.Select(x => x.Filter))}]", values.SelectMany(x => x.Arguments).ToDictionary(x => x.Key, x => x.Value)); - } - - return ("", new()); - } - - internal static bool TryResolveQueryBuilder(MethodCallExpression mc, out QueryBuilder? builder) - { - builder = null; - - if (mc.Object is not MethodCallExpression obj) - return false; - - while (obj is MethodCallExpression innermc && innermc.Object != null && innermc.Object is MethodCallExpression innerInnermc && (!obj?.Type.IsAssignableTo(typeof(QueryBuilder)) ?? true)) - { - obj = innerInnermc; - } - - if (obj?.Type.IsAssignableTo(typeof(QueryBuilder)) ?? false) - { - // execute it - builder = Expression.Lambda>(obj).Compile()(); - return true; - } - - return false; - - } - - internal static bool TryResolveOperator(MemberExpression mc, out IEdgeQLOperator? edgeQLOperator, out MethodCallExpression? expression) - { - edgeQLOperator = null; - expression = null; - - Expression? currentExpression = mc; - - while (currentExpression != null) - { - if (currentExpression is MethodCallExpression mcs && mcs.Method.DeclaringType == typeof(EdgeQL) && mcs.Method.Name == nameof(EdgeQL.Var)) - { - edgeQLOperator = mcs.Method.GetCustomAttribute()!.Operator; - expression = mcs; - return true; - } - - if (currentExpression is MemberExpression mcin) - currentExpression = mcin.Expression; - else - break; - } - - return false; - - } - - internal static string RecurseNameLookup(MemberExpression expression, bool skipStart = false) - { - List tree = new(); - - if (!skipStart) - tree.Add(GetPropertyName(expression.Member)); - - if (expression.Expression is MemberExpression innerExp) - tree.Add(RecurseNameLookup(innerExp)); - if (expression.Expression is ParameterExpression param) - tree.Add(param.Name); - - tree.Reverse(); - return string.Join('.', tree); - } - - internal static string ParseArgument(object? arg, QueryContext context) - { - if (arg is string str) - return context.IsVariableReference ? str : $"\"{str}\""; - - if (arg is char chr) - return $"\"{chr}\""; - - if (context.IsCharContext && arg is int c) - { - return $"\"{char.ConvertFromUtf32(c)}\""; - } - - if (arg is Type t) - { - return PacketSerializer.GetEdgeQLType(t) ?? GetTypeName(t) ?? t.Name; - } - - if (arg != null) - { - var type = arg.GetType(); - - if (type.IsEnum) - { - // check for the serialization method attribute - var att = type.GetCustomAttribute(); - return att != null - ? att.Method switch - { - SerializationMethod.Lower => $"\"{arg.ToString()?.ToLower()}\"", - SerializationMethod.Numeric => Convert.ChangeType(arg, type.BaseType ?? typeof(int)).ToString() ?? "{}", - _ => "{}" - } - : Convert.ChangeType(arg, type.BaseType ?? typeof(int)).ToString() ?? "{}"; - } - } - - - // empy set for null - return arg?.ToString() ?? "{}"; - } - - internal static bool IsEdgeQLType(Type t) - => t.GetCustomAttribute() != null; - - internal static string GetTypeName(Type t) - => t.GetCustomAttribute()?.Name ?? t.Name; - - internal static string GetPropertyName(MemberInfo t) - { - var name = t.GetCustomAttribute()?.Name ?? t.Name; - - //if (ReflectionUtils.IsSubclassOfRawGeneric(typeof(MultiLink<>), t.DeclaringType)) - //{ - // name = $"@{name}"; - //} - - return name; - } - - internal static string GetTypePrefix(Type t) - { - var edgeqlType = PacketSerializer.GetEdgeQLType(t); - - if (edgeqlType == null) - throw new NotSupportedException($"No edgeql type map found for type {t}"); - - return $"<{edgeqlType}>"; - } - - internal static bool TryGetEnumerableType(Type t, out Type type) - { - type = t; - - if (t.Name == typeof(IEnumerable<>).Name) - { - type = t.GenericTypeArguments[0]; - return true; - } - - if (t.GetInterfaces().Any(x => x.Name == typeof(IEnumerable<>).Name)) - { - var i = t.GetInterface(typeof(IEnumerable<>).Name)!; - type = i.GenericTypeArguments[0]; - return true; - } - - return false; - } - - internal static bool IsLink(PropertyInfo? info) - { - if (info == null) - return false; - - return - (info.GetCustomAttribute()?.IsLink ?? false) || - info.PropertyType.GetCustomAttribute() != null || - (TryGetEnumerableType(info.PropertyType, out var inner) && inner.GetCustomAttribute() != null); - } - - internal static Type CreateMockedType(Type mock) - { - //if (mock.IsValueType || mock.IsSealed) - // throw new InvalidOperationException($"Cannot create mocked type from {mock}"); - - //var tb = ReflectionUtils.GetTypeBuilder($"SubQuery{mock.Name}_{Guid.NewGuid().ToString().Replace("-", "")}", - // TypeAttributes.Public | - // TypeAttributes.Class | - // TypeAttributes.AutoClass | - // TypeAttributes.AnsiClass | - // TypeAttributes.BeforeFieldInit | - // TypeAttributes.AutoLayout); - - //tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); - //var get = typeof(ISubQueryType).GetMethod("get_Builder"); - //var set = typeof(ISubQueryType).GetMethod("set_Builder"); - //ReflectionUtils.CreateProperty(tb, "Builder", typeof(QueryBuilder), get, set); - //tb.SetParent(mock); - //tb.AddInterfaceImplementation(typeof(ISubQueryType)); - - //Type objectType = tb.CreateType()!; - - //return objectType; - - throw new NotSupportedException(); - } - } - - public interface ISubQueryType - { - QueryBuilder Builder { get; set; } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 7c4df258..47dd7db5 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -1,724 +1,30 @@ -using EdgeDB.DataTypes; -using System.Linq.Expressions; +using EdgeDB.QueryNodes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace EdgeDB { - public partial class QueryBuilder + internal class QueryBuilder { - internal virtual Type QuerySelectorType => typeof(object); + public StringBuilder Query { get; } + public IReadOnlyCollection Nodes { get; } + public QueryContext Context { get; } + public Dictionary QueryVariables { get; } = new(); - internal List QueryNodes = new(); - - internal QueryExpressionType PreviousNodeType - => CurrentRootNode.Children.LastOrDefault().Type; - - internal QueryNode CurrentRootNode - { - get - { - var lastNode = QueryNodes.LastOrDefault(); - - if (lastNode == null) - { - lastNode = new QueryNode() { Type = QueryExpressionType.Start }; - QueryNodes.Add(lastNode); - } - - return lastNode; - } - } - - public List> Arguments { get; set; } = new(); - - #region Static keyword proxies - public static QueryBuilder Select(object shape) => new QueryBuilder().Select(shape); - public static QueryBuilder Select(Expression> selector) => new QueryBuilder().Select(selector); - public static QueryBuilder Select() => new QueryBuilder().Select(); - public static QueryBuilder Select(params Expression>[] properties) => new QueryBuilder().Select(properties); - public static QueryBuilder Select(QueryBuilder value, params Expression>[] shape) => new QueryBuilder().Select(value, shape); - - public static QueryBuilder Insert(TType value) => new QueryBuilder().Insert(value); - public static QueryBuilder Update(TType obj) => new QueryBuilder().Update(obj); - public static QueryBuilder Update(Expression> builder) => new QueryBuilder().Update(builder); - public static QueryBuilder Update(TType? reference, Expression> builder) => new QueryBuilder().Update(reference, builder); - - public static QueryBuilder Delete() => new QueryBuilder().Delete(); - public static QueryBuilder With(string moduleName) => new QueryBuilder().With(moduleName); - public static QueryBuilder With(string name, TType value) => new QueryBuilder().With(name, value); - public static QueryBuilder With(params (string Name, object? Value)[] variables) => new QueryBuilder().With(variables); - - public static QueryBuilder For(Expression, QueryBuilder>> iterator) => new QueryBuilder().For(iterator); - - internal static QueryBuilder StaticLiteral(string query, QueryExpressionType type) => new QueryBuilder().Literal(query, type); - - internal static QueryBuilder StaticLiteral(string query, QueryExpressionType type) => new QueryBuilder().Literal(query, type); - - internal QueryBuilder Literal(string query, QueryExpressionType type) - { - QueryNodes.Add(new QueryNode - { - Query = query, - Type = type, - }); - - return this; - } - - #endregion - - /// - /// Turns this query builder into a edgeql representation. - /// - /// A edgeql query. - public override string? ToString() => Build().QueryText; - - /// - /// Turns this query builder into a edgeql representation where each - /// statement is seperated by newlines. - /// - /// A prettified version of the current query. - public string ToPrettyString() => Build().Prettify(); - - public BuiltQuery Build() => Build(new()); - - internal BuiltQuery Build(QueryBuilderContext config) - { - // reverse for building. - var results = QueryNodes.Reverse().Select(x => x.Build(config)).ToArray(); - return new BuiltQuery - { - Parameters = results.SelectMany(x => x.Parameters), - QueryText = string.Join(" ", results.Select(x => x.QueryText).Reverse()) - }; - } - } - - public class QueryBuilder : QueryBuilder - { - internal override Type QuerySelectorType => typeof(TType); - - public QueryBuilder() : this(null) { } - internal QueryBuilder(List? query = null) - { - QueryNodes = query ?? new List(); - } - - internal QueryBuilder ConvertTo() - => typeof(TTarget) == typeof(TType) ? (this as QueryBuilder)! : new QueryBuilder(QueryNodes); - - public new QueryBuilder Select(object shape) - { - return SelectInternal(context => - { - return context.DontSelectProperties ? null : (ParseShapeDefinition(shape, typeof(TTarget), false), null); - }); - } - - public new QueryBuilder Select(QueryBuilder value, params Expression>[] shape) - { - EnterRootNode(QueryExpressionType.Select, (QueryNode node, ref QueryBuilderContext context) => - { - var innerQuery = value.Build(context); - List parsedShape; - - if (shape.Length > 0) - parsedShape = ParseShapeDefinition(context, shape: shape); - else - { - var result = GetTypePropertyNames(typeof(TTarget), context.Enter(x => - { - x.AllowComputedValues = false; - x.MaxAggregationDepth = 0; - })); - parsedShape = result.Properties; - node.AddArguments(result.Arguments); - } - - node.Query = $"select ({innerQuery.QueryText}){(parsedShape != null && parsedShape.Count != 0 ? $" {{ {string.Join(", ", parsedShape)} }}" : "")}"; - node.AddArguments(innerQuery.Parameters); - }); - - return ConvertTo(); - } - - public new QueryBuilder Select(Expression> selector) - { - EnterRootNode(QueryExpressionType.Select, (QueryNode node, ref QueryBuilderContext context) => - { - var query = ConvertExpression(selector.Body, new QueryContext(selector) { BuilderContext = context }); - node.Query = $"select {query.Filter}"; - node.AddArguments(query.Arguments); - }); - - return typeof(TTarget) == typeof(TType) ? (this as QueryBuilder)! : ConvertTo(); - } - - public new QueryBuilder Select(params Expression>[] shape) - { - return SelectInternal(context => - { - return context.DontSelectProperties ? null : (ParseShapeDefinition(context, shape: shape), null); - }); - } - - public QueryBuilder Select() => Select(); - - public new QueryBuilder Select() - { - return SelectInternal(context => - { - if (context.DontSelectProperties) - { - return null; - } - var result = GetTypePropertyNames(typeof(TTarget), context); - - if (context.IsVariable) - { - result.Properties = new(); - } - - return (result.Properties, result.Arguments); - }); - } - - internal QueryBuilder SelectInternal(Func? Properties, IEnumerable>? Arguments)?> argumentBuilder) - { - EnterRootNode(QueryExpressionType.Select, (QueryNode node, ref QueryBuilderContext context) => - { - var selectArgs = argumentBuilder(context); - - IEnumerable? properties = selectArgs?.Properties; - IEnumerable>? args = selectArgs?.Arguments; - - node.Query = $"{(!context.ExplicitShapeDefinition ? $"select {(context.UseDetached ? "detached " : "")}{GetTypeName(typeof(TTarget))} " : "")}{(properties != null && properties.Any() ? $"{{ {string.Join(", ", properties)} }}" : "")}"; - if (context.LimitToOne || (context.UseDetached && PreviousNodeType != QueryExpressionType.Limit)) - node.AddChild(QueryExpressionType.Limit, (ref QueryBuilderContext _) => new BuiltQuery { QueryText = "limit 1" }); - - if (args != null) - node.AddArguments(args); - }); - return ConvertTo(); - } - - - public QueryBuilder Filter(Expression> filter) - => Filter(filter); - public QueryBuilder Filter(Expression> filter) - { - EnterNode(QueryExpressionType.Filter, (ref QueryBuilderContext builderContext) => - { - var context = new QueryContext(filter) { BuilderContext = builderContext }; - var builtFilter = ConvertExpression(filter.Body, context); - - return new BuiltQuery - { - Parameters = builtFilter.Arguments, - QueryText = $"filter {builtFilter.Filter}" - }; - }); - return ConvertTo(); - } - - public QueryBuilder OrderBy(Expression> selector, NullPlacement? nullPlacement = null) - => OrderByInternal("asc", selector, nullPlacement); - - public QueryBuilder OrderByDescending(Expression> selector, NullPlacement? nullPlacement = null) - => OrderByInternal("desc", selector, nullPlacement); - - internal QueryBuilder OrderByInternal(string direction, Expression> selector, NullPlacement? nullPlacement = null) - { - EnterNode(QueryExpressionType.OrderBy, (ref QueryBuilderContext context) => - { - var builtSelector = ParseShapeDefinition(context, true, selector).FirstOrDefault(); - string orderByExp = ""; - if (CurrentRootNode.Type == QueryExpressionType.OrderBy) - orderByExp += $"then {builtSelector} {direction}"; - else - orderByExp += $"order by {builtSelector} {direction}"; - - if (nullPlacement.HasValue) - orderByExp += $" empty {nullPlacement.Value.ToString().ToLower()}"; - - return new BuiltQuery - { - QueryText = orderByExp - }; - }); - return this; - } - - public QueryBuilder Offset(ulong count) - { - AssertValid(QueryExpressionType.Offset); - EnterNode(QueryExpressionType.Offset, (ref QueryBuilderContext context) => - { - return new BuiltQuery - { - QueryText = $"offset {count}" - }; - }); return this; - } - - public QueryBuilder Limit(ulong count) + public QueryBuilder(QueryContext context, IReadOnlyCollection? nodes = null) { - AssertValid(QueryExpressionType.Limit); - EnterNode(QueryExpressionType.Limit, (ref QueryBuilderContext context) => - { - return new BuiltQuery - { - QueryText = $"limit {count}" - }; - }); - return this; + Query = new(); + Nodes = nodes ?? Array.Empty(); + Context = context; } - - public QueryBuilder For(Expression, QueryBuilder>> iterator) - { - EnterRootNode(QueryExpressionType.For, (QueryNode node, ref QueryBuilderContext context) => - { - var builder = new QueryBuilder(); - var builtIterator = iterator.Compile()(builder); - - node.Query = $"for {iterator.Parameters[0].Name} in {GetTypeName(typeof(TType))}"; - node.AddChild(QueryExpressionType.Union, (ref QueryBuilderContext innerContext) => - { - var result = builtIterator.Build(innerContext); - result.QueryText = $"union ({result.QueryText})"; - return result; - }); - }); - return this; - } - - public new QueryBuilder Insert(TTarget value) - { - EnterRootNode(QueryExpressionType.Insert, (QueryNode node, ref QueryBuilderContext context) => - { - var obj = SerializeQueryObject(value, context.Enter(x => - { - x.DontSelectProperties = true; - x.IncludeEmptySets = true; - })); - node.Query = $"insert{(context.UseDetached ? " detached" : "")} {GetTypeName(typeof(TTarget))} {obj.Query}"; - node.AddArguments(obj.Arguments); - }); - - return ConvertTo(); - } - - public QueryBuilder UnlessConflictOn(params Expression>[] selectors) - { - EnterNode(QueryExpressionType.UnlessConflictOn, (ref QueryBuilderContext innerContext) => - { - var props = ParseShapeDefinition(innerContext, true, selectors); - - return new BuiltQuery - { - QueryText = props.Count > 1 - ? $"unless conflict on ({string.Join(", ", props)})" - : $"unless conflict on {props[0]}" - }; - }); - - return this; - } - - public new QueryBuilder Update(TTarget obj) - { - EnterRootNode(QueryExpressionType.Update, (QueryNode node, ref QueryBuilderContext context) => - { - node.Query = $"update {GetTypeName(typeof(TTarget))}"; - var serializedObj = SerializeQueryObject(obj, context.Enter(x => x.DontSelectProperties = true)); - node.SetChild(0, QueryExpressionType.Set, (ref QueryBuilderContext innerContext) => - { - return new BuiltQuery - { - QueryText = $"set {serializedObj.Query}", - Parameters = serializedObj.Arguments, - }; - }); - }); - return ConvertTo(); - } - - public new QueryBuilder Update(TTarget? reference, Expression> builder) - { - EnterRootNode(QueryExpressionType.Update, (QueryNode node, ref QueryBuilderContext context) => - { - string? refName = ""; - - switch (reference) - { - case ISubQueryType sub: - if (sub.Builder.CurrentRootNode.Type == QueryExpressionType.Variable) - refName = sub.Builder.ToString(); - else - { - var result = sub.Builder.Build(context); - node.AddArguments(result.Parameters); - refName = $"({result.QueryText})"; - } - break; - case IQueryResultObject obj: - refName = $"(select {GetTypeName(typeof(TTarget))} filter .id = \"{obj.GetObjectId()}\" limit 1)"; - break; - - default: - throw new ArgumentException($"Cannot use {typeof(TTarget)} as a reference, no suitable reference extraction found"); - } - - var serializedObj = ConvertExpression(builder.Body, new QueryContext(builder) { AllowStaticOperators = true, BuilderContext = context.Enter(x => x.DontSelectProperties = true) }); - - node.Query = $"update {refName}"; - node.SetChild(0, QueryExpressionType.Set, (ref QueryBuilderContext innerContext) => - { - return new BuiltQuery - { - QueryText = $"set {{ {serializedObj.Filter} }}", - Parameters = serializedObj.Arguments - }; - }); - }); - - return ConvertTo(); - } - - public new QueryBuilder Update(Expression> builder) - { - EnterRootNode(QueryExpressionType.Update, (QueryNode node, ref QueryBuilderContext context) => - { - var serializedObj = ConvertExpression(builder.Body, new QueryContext(builder) { AllowStaticOperators = true, BuilderContext = context.Enter(x => x.DontSelectProperties = true) }); - - node.Query = $"update {GetTypeName(typeof(TTarget))}"; - - node.SetChild(node.Children.Any() ? 1 : 0, QueryExpressionType.Set, (ref QueryBuilderContext innerContext) => - { - return new BuiltQuery - { - QueryText = $"set {{ {serializedObj.Filter} }}", - Parameters = serializedObj.Arguments - }; - }); - - }); - - return ConvertTo(); - } - - public QueryBuilder Delete() - { - EnterRootNode(QueryExpressionType.Delete, (QueryNode node, ref QueryBuilderContext context) => - { - node.Query = $"delete {GetTypeName(typeof(TType))}"; - }); - return this; - } - - public QueryBuilder With(string moduleName) - { - EnterRootNode(QueryExpressionType.With, (QueryNode node, ref QueryBuilderContext context) => - { - node.Query = $"with module {moduleName}"; - }); - return this; - } - - public new QueryBuilder With(params (string Name, object? Value)[] variables) - { - EnterRootNode(QueryExpressionType.With, (QueryNode node, ref QueryBuilderContext context) => - { - List statements = new(); - - context.IntrospectObjectIds = true; - foreach (var (Name, Value) in variables) - { - var converted = SerializeProperty(Value?.GetType() ?? typeof(object), Value, false, context.Enter(x => - { - x.IsVariable = true; - x.VariableName = Name; - })); - node.AddArguments(converted.Arguments); - - statements.Add($"{Name} := {converted.Property}"); - } - - node.Query = $"with {string.Join(", ", statements)}"; - }); - - return this; - } - - public new QueryBuilder With(string name, TTarget value) - { - if (PreviousNodeType == QueryExpressionType.With) - { - EnterNode(QueryExpressionType.With, (ref QueryBuilderContext context) => - { - context.IntrospectObjectIds = true; - var converted = SerializeProperty(value, false, context); - - return new BuiltQuery - { - QueryText = $", {name} := {converted.Property}", - Parameters = converted.Arguments - }; - }); - } - else - { - EnterRootNode(QueryExpressionType.With, (QueryNode node, ref QueryBuilderContext context) => - { - context.IntrospectObjectIds = true; - var converted = SerializeProperty(value, false, context); - node.AddArguments(converted.Arguments); - node.Query = $"with {name} := {converted.Property}"; - }); - } - - return ConvertTo(); - } - - public QueryBuilder Else() - { - EnterRootNode(QueryExpressionType.Else, (QueryNode node, ref QueryBuilderContext context) => - { - node.Query = "else"; - }); - - return this; - } - - public QueryBuilder Else() - { - EnterRootNode(QueryExpressionType.Else, (QueryNode node, ref QueryBuilderContext context) => - { - node.Query = $"else {GetTypeName(typeof(TTarget))}"; - }); - - return ConvertTo(); - } - - public QueryBuilder Else(QueryBuilder builder) - { - Else(builder as QueryBuilder); - return ConvertTo(); - } - - public QueryBuilder Else(QueryBuilder builder) - { - EnterRootNode(QueryExpressionType.Else, (QueryNode node, ref QueryBuilderContext context) => - { - node.Query = "else"; - - foreach (var childNode in builder.QueryNodes) - { - node.AddChild(node.Type, (ref QueryBuilderContext innerContext) => - { - return childNode.Build(innerContext); - }); - } - }); - - return this; - } - - internal QueryBuilder Literal(string query, QueryExpressionType type) - { - QueryNodes.Add(new QueryNode - { - Query = query, - Type = type - - }); - - return ConvertTo(); - } - - private QueryNode EnterRootNode(QueryExpressionType type, RootNodeBuilder builder) - { - AssertValid(type); - var node = new QueryNode(type, builder); - QueryNodes.Add(node); - return node; - } - - private void EnterNode(QueryExpressionType type, ChildNodeBuilder builder) => CurrentRootNode.AddChild(type, builder); - - private void AssertValid(QueryExpressionType currentExpression) - { - if (_validExpressions.TryGetValue(currentExpression, out var exp) && !exp.Contains(CurrentRootNode.Type)) - { - throw new InvalidQueryOperationException(currentExpression, _validExpressions[currentExpression]); - } - } - - private readonly Dictionary _validExpressions = new() - { - { QueryExpressionType.With, new QueryExpressionType[] { QueryExpressionType.With, QueryExpressionType.Start } }, - { QueryExpressionType.Select, new QueryExpressionType[] { QueryExpressionType.Else, QueryExpressionType.With, QueryExpressionType.Start } }, - { QueryExpressionType.OrderBy, new QueryExpressionType[] { QueryExpressionType.Delete, QueryExpressionType.Filter, QueryExpressionType.Select } }, - { QueryExpressionType.Offset, new QueryExpressionType[] { QueryExpressionType.Delete, QueryExpressionType.OrderBy, QueryExpressionType.Select, QueryExpressionType.Filter } }, - { QueryExpressionType.Limit, new QueryExpressionType[] { QueryExpressionType.Delete, QueryExpressionType.OrderBy, QueryExpressionType.Select, QueryExpressionType.Filter, QueryExpressionType.Offset } }, - { QueryExpressionType.For, new QueryExpressionType[] { QueryExpressionType.Else, QueryExpressionType.With, QueryExpressionType.Start } }, - { QueryExpressionType.Insert, new QueryExpressionType[] { QueryExpressionType.Else, QueryExpressionType.With, QueryExpressionType.Start } }, - { QueryExpressionType.Update, new QueryExpressionType[] { QueryExpressionType.Else, QueryExpressionType.With, QueryExpressionType.Start } }, - { QueryExpressionType.Delete, new QueryExpressionType[] { QueryExpressionType.Else, QueryExpressionType.With, QueryExpressionType.Start } }, - { QueryExpressionType.Transaction, new QueryExpressionType[] { QueryExpressionType.Start } } - }; - - //public static implicit operator Set(QueryBuilder v) => new Set(v); - public static implicit operator ComputedValue(QueryBuilder v) - { - return new ComputedValue(default, v); - } - - public static implicit operator TType(QueryBuilder v) - { - return v.SubQuery(); - } - - //public Set SubQuerySet() - // => (Set)this; - - public TType SubQuery() - { - var obj = (ISubQueryType)Activator.CreateInstance(CreateMockedType(typeof(TType)))!; - obj.Builder = this; - return (TType)obj; - } - } - - internal delegate BuiltQuery ChildNodeBuilder(ref QueryBuilderContext context); - internal delegate void RootNodeBuilder(QueryNode node, ref QueryBuilderContext context); - - - internal class QueryNode - { - public QueryExpressionType Type { get; set; } - public List<(ChildNodeBuilder Builder, QueryExpressionType Type)> Children { get; set; } = new(); - public QueryNode? Parent { get; set; } - public string? Query { get; set; } - public IEnumerable> Arguments { get; set; } = new Dictionary(); - - private readonly object _lock = new(); - - private readonly RootNodeBuilder? _builder; - - public QueryNode() { } - - public QueryNode(QueryExpressionType type, RootNodeBuilder builder) - { - _builder = builder; - Type = type; - } - - public void AddChild(QueryExpressionType type, ChildNodeBuilder builder) - => Children.Add((builder, type)); - public void SetChild(int index, QueryExpressionType type, ChildNodeBuilder builder) - { - if (Children.Count > index) - Children[index] = (builder, type); - else Children.Insert(index, (builder, type)); - } - - public void AddArguments(IEnumerable> args) - { - lock (_lock) - { - Arguments = Arguments.Concat(args); - } - } - - public BuiltQuery Build(QueryBuilderContext config) - { - // remove current arguments incase of building twice - Arguments = new Dictionary(); - - if (_builder != null) - _builder.Invoke(this, ref config); - - var result = $"{Query}"; - - if (Children.Any()) - { - var results = Children.Select(x => x.Builder.Invoke(ref config)).ToArray(); - result += $" {string.Join(" ", results.Select(x => x.QueryText))}"; - AddArguments(results.SelectMany(x => x.Parameters)); - } - - return new BuiltQuery - { - Parameters = Arguments, - QueryText = result - }; - } - } - - public enum QueryExpressionType - { - Start, - Select, - Insert, - Update, - Delete, - With, - For, - Filter, - OrderBy, - Offset, - Limit, - Set, - Transaction, - Union, - UnlessConflictOn, - Rollback, - Commit, - Else, - - // internal - Variable, - } - - public enum IsolationMode - { - /// - /// All statements of the current transaction can only see data changes committed before the first query - /// or data-modification statement was executed in this transaction. If a pattern of reads and writes among - /// concurrent serializable transactions would create a situation which could not have occurred for any serial - /// (one-at-a-time) execution of those transactions, one of them will be rolled back with a serialization_failure error. - /// - Serializable, - /// - /// All statements of the current transaction can only see data committed before the first query or data-modification - /// statement was executed in this transaction. - /// - /// - /// This is the default isolation mode. - /// - RepeatableRead - } - - public enum AccessMode - { - /// - /// Sets the transaction access mode to read/write. - /// - /// - /// This is the default transaction access mode. - /// - ReadWrite, - - /// - /// Sets the transaction access mode to read-only. Any data modifications with insert, update, or delete - /// are disallowed. Schema mutations via DDL are also disallowed. - /// - ReadOnly } - public enum NullPlacement + internal class CompiledQueryNode { - First, - Last, + public QueryNode? Node { get; init; } + public StringBuilder? Query { get; init; } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilderContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilderContext.cs deleted file mode 100644 index dd293969..00000000 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilderContext.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace EdgeDB -{ - internal class QueryBuilderContext - { - public bool DontSelectProperties { get; set; } - public bool UseDetached { get; set; } - public bool IntrospectObjectIds { get; set; } - public QueryBuilderContext? Parent { get; set; } - public List TrackedVariables { get; set; } = new(); - public bool IncludeEmptySets { get; set; } = true; - public bool IsVariable { get; set; } - public string? VariableName { get; set; } - public bool AllowComputedValues { get; set; } = true; - public int? MaxAggregationDepth { get; set; } = 10; - public bool LimitToOne { get; set; } - public List TrackedSubQueries { get; set; } = new(); - public bool ExplicitShapeDefinition { get; set; } - - - // sub query type info - public Type? ParentQueryType { get; set; } - public string? ParentQueryTypeName { get; set; } - - public QueryBuilderContext Enter(Action modifier) - { - var context = new QueryBuilderContext - { - Parent = this, - DontSelectProperties = DontSelectProperties, - UseDetached = UseDetached, - IntrospectObjectIds = IntrospectObjectIds, - TrackedVariables = TrackedVariables, - TrackedSubQueries = TrackedSubQueries, - IsVariable = IsVariable, - VariableName = VariableName, - IncludeEmptySets = IncludeEmptySets, - AllowComputedValues = AllowComputedValues, - MaxAggregationDepth = MaxAggregationDepth, - ParentQueryType = ParentQueryType, - ParentQueryTypeName = ParentQueryTypeName, - }; - - modifier(context); - return context; - } - - public void AddTrackedVariable(string var) - { - if (Parent != null) - Parent.AddTrackedVariable(var); - else - TrackedVariables.Add(var); - } - - public void AddTrackedSubQuery(QueryBuilder builder) - { - if (Parent != null) - Parent.AddTrackedSubQuery(builder); - else - TrackedSubQueries.Add(builder); - } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index 7380cb75..e3ece8ca 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -1,93 +1,20 @@ -using System.Linq.Expressions; -using System.Reflection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace EdgeDB { internal class QueryContext { - public Type? ParameterType { get; set; } - public string? ParameterName { get; set; } - public Expression? Body { get; set; } - public QueryContext? Parent { get; set; } - public int? ParameterIndex { get; set; } - public bool IsCharContext { get; set; } - public bool AllowStaticOperators { get; set; } - public bool IncludeSetOperand { get; set; } = true; - public QueryBuilderContext? BuilderContext { get; set; } - public Type? BindingType { get; set; } - public bool AllowSubQueryGeneration { get; set; } + public Type CurrentType { get; init; } - public bool IsVariableReference - => Parent?.Body is MethodCallExpression mc && mc.Method.GetCustomAttribute()?.Operator?.GetType() == typeof(Operators.VariablesReference); + public object? Value { get; init; } - public QueryContext() { } - - public virtual QueryContext Enter(Expression x, int? paramIndex = null, Action? modifier = null) - { - var context = new QueryContext() - { - Body = x, - ParameterName = ParameterName, - ParameterType = ParameterType, - IsCharContext = IsCharContext, - ParameterIndex = paramIndex, - Parent = this, - BuilderContext = BuilderContext, - }; - - modifier?.Invoke(context); - - return context; - } - } - - internal class QueryContext : QueryContext - { - public QueryContext() { } - public QueryContext(Expression> func) : base() - { - Body = func.Body; - ParameterType = func.Parameters[0].Type; - ParameterName = func.Parameters[0].Name; - } - - public QueryContext Enter(Expression x, int? paramIndex = null, Action>? modifier = null) - { - var context = new QueryContext() - { - Body = x, - ParameterName = ParameterName, - ParameterType = ParameterType, - IsCharContext = IsCharContext, - ParameterIndex = paramIndex, - Parent = this - }; - - modifier?.Invoke(context); - - return context; - } - } - - internal class QueryContext : QueryContext - { - public QueryContext() { } - public QueryContext(Expression> func) : base() - { - Body = func.Body; - } - - public QueryContext Enter(Expression x, int? paramIndex = null) + public QueryContext(Type currentType) { - return new QueryContext() - { - Body = x, - ParameterName = ParameterName, - ParameterType = ParameterType, - IsCharContext = IsCharContext, - ParameterIndex = paramIndex, - Parent = this - }; + CurrentType = currentType; } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs new file mode 100644 index 00000000..4fac2755 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class FilterNode + { + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs new file mode 100644 index 00000000..2c2e5592 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal abstract class QueryNode + { + protected readonly QueryBuilder Builder; + + protected StringBuilder Query + => Builder.Query; + + protected QueryContext Context + => Builder.Context; + + public QueryNode(QueryBuilder builder) + { + Builder = builder; + } + + protected abstract void Visit(); + protected abstract void FinalizeQuery(); + + protected void SetVariable(string name, object? value) + => Builder.QueryVariables[name] = value; + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs new file mode 100644 index 00000000..26336236 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -0,0 +1,47 @@ +using EdgeDB.Serializer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class SelectNode : QueryNode + { + public SelectNode(QueryBuilder builder) : base(builder) + { + + } + + protected virtual string GetShape() + { + var properties = Context.CurrentType.GetProperties().Where(x => x.GetCustomAttribute() == null); + + var propertyNames = properties.Select(x => x.GetCustomAttribute()?.Name ?? TypeBuilder.NamingStrategy.GetName(x)); + + return $"{{ {string.Join(", ", propertyNames)} }}"; + } + + protected override void Visit() + { + var shape = GetShape(); + Query.Append($"select {Context.CurrentType.GetEdgeDBTypeName()} {shape}"); + } + + protected override void FinalizeQuery() + { + // check if theres a child select with the same type + if(Builder.Nodes.Any(x => x.Node is SelectNode select && select.Context.CurrentType == Context.CurrentType)) + { + MakeDetatched(); + } + } + + private void MakeDetatched() + { + Query.Insert(7, "detached "); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs new file mode 100644 index 00000000..71613972 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + public sealed class QueryableCollection + { + private readonly IEdgeDBQueryable _edgedb; + + internal QueryableCollection(IEdgeDBQueryable edgedb) + { + _edgedb = edgedb; + } + + public QueryableCollection Where(Expression> condition) + { + return this; + } + + + } + + public enum QueryExpressionType + { + Start, + Select, + Insert, + Update, + Delete, + With, + For, + Filter, + OrderBy, + Offset, + Limit, + Set, + Transaction, + Union, + UnlessConflictOn, + Rollback, + Commit, + Else, + + // internal + Variable, + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs new file mode 100644 index 00000000..16d9f877 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Expressions +{ + internal class BinaryExpressionTranslator : ExpressionTranslator + { + public override string Translate(BinaryExpression expression, ExpressionContext context) + { + var left = TranslateExpression(expression, context); + var right = TranslateExpression(expression, context); + + if (!TryGetExpressionOperator(expression.NodeType, out var op)) + throw new NotSupportedException($"Failed to find operator for node type {expression.NodeType}"); + + return op.Build(left, right); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs new file mode 100644 index 00000000..d260db49 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Expressions +{ + internal class ConstantExpressionTranslator : ExpressionTranslator + { + public override string Translate(ConstantExpression expression, ExpressionContext context) + { + return ParseObject(expression.Value); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs new file mode 100644 index 00000000..57424e96 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + internal class ExpressionContext + { + public LambdaExpression RootExpression { get; } + public Dictionary Parameters { get; } + + public ExpressionContext(LambdaExpression rootExpression) + { + RootExpression = rootExpression; + + Parameters = rootExpression.Parameters.ToDictionary(x => x.Name!, x => x.Type); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs new file mode 100644 index 00000000..0779cfa0 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -0,0 +1,94 @@ +using EdgeDB.Operators; +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 +{ + internal abstract class ExpressionTranslator : ExpressionTranslator + where TExpression : Expression + { + public abstract string Translate(TExpression expression, ExpressionContext context); + + public override string Translate(Expression expression, ExpressionContext context) + { + return Translate((TExpression)expression, context); + } + } + + internal abstract class ExpressionTranslator + { + private static readonly Dictionary _translators = new(); + private static readonly IEdgeQLOperator[] _operators; + private static readonly Dictionary _expressionOperators; + + static ExpressionTranslator() + { + var types = Assembly.GetExecutingAssembly().DefinedTypes; + // 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)!; + } + + // load operators + _operators = types.Where(x => x.ImplementedInterfaces.Any(x => x == typeof(IEdgeQLOperator))).Select(x => (IEdgeQLOperator)Activator.CreateInstance(x)!).ToArray(); + + // set the expression operators + _expressionOperators = _operators.Where(x => x.Expression is not null).DistinctBy(x => x.Expression).ToDictionary(x => (ExpressionType)x.Expression!, x => x); + } + + protected static bool TryGetExpressionOperator(ExpressionType type, [MaybeNullWhen(false)] out IEdgeQLOperator edgeqlOperator) + => _expressionOperators.TryGetValue(type, out edgeqlOperator); + + + public abstract string Translate(Expression expression, ExpressionContext context); + + public static string Translate(Expression expression) + { + var context = new ExpressionContext(expression); + return TranslateExpression(expression.Body, context); + } + + protected static string TranslateExpression(Expression expression, ExpressionContext context) + { + if (_translators.TryGetValue(expression.GetType(), out var translator)) + return translator.Translate(expression, context); + + throw new Exception("AAAA"); + } + + protected static string ParseObject(object? obj) + { + if (obj is null) + return "{}"; + + if(obj is Enum enm) + { + var type = enm.GetType(); + var att = type.GetCustomAttribute(); + return att != null ? att.Method switch + { + SerializationMethod.Lower => $"\"{obj.ToString()?.ToLower()}\"", + SerializationMethod.Numeric => Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}", + _ => "{}" + } : Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}"; + } + + return obj switch + { + string str => $"\"{str}\"", + char chr => $"\"{chr}\"", + Type type => PacketSerializer.GetEdgeQLType(type) ?? type.GetEdgeDBTypeName(), + _ => obj.ToString()! + }; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs new file mode 100644 index 00000000..e17800cd --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Expressions +{ + internal class MemberExpressionTranslator + { + } +} diff --git a/tools/EdgeDB.QueryBuilder.OperatorGenerator/Program.cs b/tools/EdgeDB.QueryBuilder.OperatorGenerator/Program.cs index 1bf627b9..6063e498 100644 --- a/tools/EdgeDB.QueryBuilder.OperatorGenerator/Program.cs +++ b/tools/EdgeDB.QueryBuilder.OperatorGenerator/Program.cs @@ -5,7 +5,7 @@ using EdgeDB.QueryBuilder.OperatorGenerator; using System.Text.RegularExpressions; -const string OperatorsOutputDir = "../../../../EdgeDB.Net.QueryBuilder/Operators"; +const string OperatorsOutputDir = "../../../../../src/EdgeDB.Net.QueryBuilder/Operators"; const string EdgeQLOutput = "../../../../../src/EdgeDB.Net.QueryBuilder"; const string OperatorDefinitionFile = "../../../operators.yml"; const string ParamaterNames = "abcdefghijklmnopqrstuvwxyz"; @@ -190,7 +190,7 @@ void BuildSingleOperator(string section, EdgeQLOperator op) opValue = $"ExpressionType.{op.Expression}"; } - writer.AppendLine($"public ExpressionType? Operator => {opValue};"); + writer.AppendLine($"public ExpressionType? Expression => {opValue};"); writer.AppendLine($"public string EdgeQLOperator => \"{op.Operator}\";"); } } From 83db0345ec20b0a91f019aba559eef73c6276184 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 21 Jun 2022 04:30:36 -0300 Subject: [PATCH 02/70] node context and more work --- .../EdgeDB.Examples.ExampleApp/Program.cs | 2 -- .../AttributeNamingStrategy.cs | 2 +- .../CamelCaseNamingStrategy.cs | 2 +- .../NamingStrategies/INamingStrategy.cs | 2 +- .../NamingStrategies/PascalNamingStrategy.cs | 2 +- .../SnakeCaseNamingStrategy.cs | 2 +- .../Extensions/TypeExtensions.cs | 5 ++- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 6 ++-- src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 5 ++- .../QueryNodes/Contexts/FilterContext.cs | 19 ++++++++++ .../QueryNodes/Contexts/OrderByContext.cs | 33 +++++++++++++++++ .../QueryNodes/Contexts/SelectContext.cs | 17 +++++++++ .../QueryNodes/FilterNode.cs | 12 ++++++- .../QueryNodes/OrderByNode.cs | 35 +++++++++++++++++++ .../QueryNodes/QueryNode.cs | 14 +++++--- .../QueryNodes/SelectNode.cs | 14 ++------ .../QueryableCollection.cs | 5 ++- .../Expressions/BinaryExpressionTranslator.cs | 4 +-- .../Expressions/ExpressionTranslator.cs | 9 ++++- .../Expressions/MemberExpressionTranslator.cs | 21 ++++++++++- 20 files changed, 176 insertions(+), 35 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Program.cs b/examples/EdgeDB.Examples.ExampleApp/Program.cs index ee18a0f0..0aa07e61 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Program.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Program.cs @@ -7,8 +7,6 @@ using Serilog; using static EdgeDB.ExampleApp.Examples.JsonResults; -ExpressionTranslator.Translate>(x => x.Name == "John"); - Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.Debug() diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/AttributeNamingStrategy.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/AttributeNamingStrategy.cs index ba8ada9f..7f081b21 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/AttributeNamingStrategy.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/AttributeNamingStrategy.cs @@ -9,7 +9,7 @@ namespace EdgeDB.Serializer { public sealed class AttributeNamingStrategy : INamingStrategy { - public string GetName(PropertyInfo property) + public string GetName(MemberInfo property) { return property.GetCustomAttribute()?.Name ?? property.Name; } diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/CamelCaseNamingStrategy.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/CamelCaseNamingStrategy.cs index 31ef7bfe..1f4f4c8b 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/CamelCaseNamingStrategy.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/CamelCaseNamingStrategy.cs @@ -9,7 +9,7 @@ namespace EdgeDB.Serializer { public sealed class CamelCaseNamingStrategy : INamingStrategy { - public string GetName(PropertyInfo property) + public string GetName(MemberInfo property) { var name = property.Name; return $"{char.ToLowerInvariant(name[0])}{name[1..].Replace("_", string.Empty)}"; diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/INamingStrategy.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/INamingStrategy.cs index 1d0d326c..46bb1480 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/INamingStrategy.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/INamingStrategy.cs @@ -45,6 +45,6 @@ public static INamingStrategy SnakeCaseNamingStrategy /// /// The property info of which to convert its name. /// The name defined in the schema. - public string GetName(PropertyInfo property); + public string GetName(MemberInfo property); } } diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/PascalNamingStrategy.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/PascalNamingStrategy.cs index 2d4e4f57..cfb2cb04 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/PascalNamingStrategy.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/PascalNamingStrategy.cs @@ -9,7 +9,7 @@ namespace EdgeDB.Serializer { public sealed class PascalNamingStrategy : INamingStrategy { - public string GetName(PropertyInfo property) + public string GetName(MemberInfo property) { var str = property.Name; diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/SnakeCaseNamingStrategy.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/SnakeCaseNamingStrategy.cs index b92abed6..3b75117c 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/SnakeCaseNamingStrategy.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/NamingStrategies/SnakeCaseNamingStrategy.cs @@ -10,7 +10,7 @@ namespace EdgeDB.Serializer { public sealed class SnakeCaseNamingStrategy : INamingStrategy { - public string GetName(PropertyInfo property) + public string GetName(MemberInfo property) { return Regex.Replace(property.Name, "(? $"_{x.Value}").ToLower(); } diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs index 1238ca56..acbb221e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs @@ -1,4 +1,5 @@ -using System; +using EdgeDB.Serializer; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -11,5 +12,7 @@ internal static class TypeExtensions { public static string GetEdgeDBTypeName(this Type type) => type.GetCustomAttribute()?.Name ?? type.Name; + public static string GetEdgeDBPropertyName(this MemberInfo info) + => info.GetCustomAttribute()?.Name ?? TypeBuilder.NamingStrategy.GetName(info); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 47dd7db5..35f18f29 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -10,14 +10,14 @@ namespace EdgeDB internal class QueryBuilder { public StringBuilder Query { get; } - public IReadOnlyCollection Nodes { get; } + public List Nodes { get; } public QueryContext Context { get; } public Dictionary QueryVariables { get; } = new(); - public QueryBuilder(QueryContext context, IReadOnlyCollection? nodes = null) + public QueryBuilder(QueryContext context, List? nodes = null) { Query = new(); - Nodes = nodes ?? Array.Empty(); + Nodes = nodes ?? new(); Context = context; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index e3ece8ca..35a0123f 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -1,17 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace EdgeDB { - internal class QueryContext + internal abstract class QueryContext { public Type CurrentType { get; init; } - public object? Value { get; init; } - public QueryContext(Type currentType) { CurrentType = currentType; diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs new file mode 100644 index 00000000..76a4f502 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class FilterContext : QueryContext + { + public LambdaExpression Expression { get; init; } + + public FilterContext(Type currentType, LambdaExpression expression) : base(currentType) + { + Expression = expression; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs new file mode 100644 index 00000000..0824f20b --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class OrderByContext : QueryContext + { + public OrderByDirection Direction { get; init; } + public EmptyPlacement? EmptyPlacement { get; init; } + public LambdaExpression Expression { get; init; } + + public OrderByContext(Type currentType, LambdaExpression expression) : base(currentType) + { + Expression = expression; + } + } + + internal enum OrderByDirection + { + Ascending, + Descending, + } + + internal enum EmptyPlacement + { + First, + Last, + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs new file mode 100644 index 00000000..6227df4e --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class SelectContext : QueryContext + { + public object? Shape { get; init; } + + public SelectContext(Type currentType) : base(currentType) + { + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs index 4fac2755..3acf6074 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs @@ -6,7 +6,17 @@ namespace EdgeDB.QueryNodes { - internal class FilterNode + internal class FilterNode : QueryNode { + public FilterNode(QueryBuilder builder) : base(builder) { } + + protected override void Visit() + { + if (Context.Expression is null) + throw new ArgumentNullException("No expression was passed in for a filter node"); + + var parsedExpression = ExpressionTranslator.Translate(Context.Expression); + Query.Append($"filter {parsedExpression}"); + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs new file mode 100644 index 00000000..bab6d52f --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class OrderByNode : QueryNode + { + public OrderByNode(QueryBuilder builder) : base(builder) { } + + protected override void FinalizeQuery() + { + // check if previous node was a order by, if so change to 'then' + if (Builder.Nodes.FirstOrDefault()?.Node is OrderByNode) + { + Query.Remove(0, 8); + Query.Insert(0, "then"); + } + } + + protected override void Visit() + { + var expression = ExpressionTranslator.Translate(Context.Expression); + var direction = Context.Direction == OrderByDirection.Ascending ? "asc" : "desc"; + Query.Append($"order by {expression} {direction}"); + + if (Context.EmptyPlacement.HasValue) + { + Query.Append($" empty {Context.EmptyPlacement.Value.ToString().ToLowerInvariant()}"); + } + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index 2c2e5592..92dab915 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -6,6 +6,15 @@ namespace EdgeDB.QueryNodes { + internal abstract class QueryNode : QueryNode + where TContext : QueryContext + { + protected QueryNode(QueryBuilder builder) : base(builder) { } + + protected TContext Context + => (TContext)Builder.Context; + } + internal abstract class QueryNode { protected readonly QueryBuilder Builder; @@ -13,16 +22,13 @@ internal abstract class QueryNode protected StringBuilder Query => Builder.Query; - protected QueryContext Context - => Builder.Context; - public QueryNode(QueryBuilder builder) { Builder = builder; } protected abstract void Visit(); - protected abstract void FinalizeQuery(); + protected virtual void FinalizeQuery() { } protected void SetVariable(string name, object? value) => Builder.QueryVariables[name] = value; diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 26336236..3bb9fa42 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -8,12 +8,9 @@ namespace EdgeDB.QueryNodes { - internal class SelectNode : QueryNode + internal class SelectNode : QueryNode { - public SelectNode(QueryBuilder builder) : base(builder) - { - - } + public SelectNode(QueryBuilder builder) : base(builder) { } protected virtual string GetShape() { @@ -35,13 +32,8 @@ protected override void FinalizeQuery() // check if theres a child select with the same type if(Builder.Nodes.Any(x => x.Node is SelectNode select && select.Context.CurrentType == Context.CurrentType)) { - MakeDetatched(); + Query.Insert(7, "detached "); } } - - private void MakeDetatched() - { - Query.Insert(7, "detached "); - } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs index 71613972..9898556a 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -1,4 +1,5 @@ -using System; +using EdgeDB.QueryNodes; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -10,10 +11,12 @@ namespace EdgeDB public sealed class QueryableCollection { private readonly IEdgeDBQueryable _edgedb; + private readonly List _nodes; internal QueryableCollection(IEdgeDBQueryable edgedb) { _edgedb = edgedb; + _nodes = new(); } public QueryableCollection Where(Expression> condition) diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs index 16d9f877..fa84cca9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs @@ -11,8 +11,8 @@ internal class BinaryExpressionTranslator : ExpressionTranslator(Expression expression) + => Translate(expression); + + public static string Translate(LambdaExpression expression) { var context = new ExpressionContext(expression); return TranslateExpression(expression.Body, context); @@ -59,7 +62,11 @@ public static string Translate(Expression ex protected static string TranslateExpression(Expression expression, ExpressionContext context) { - if (_translators.TryGetValue(expression.GetType(), out var translator)) + var expType = expression.GetType(); + while (!expType.IsPublic) + expType = expType.BaseType!; + + if (_translators.TryGetValue(expType, out var translator)) return translator.Translate(expression, context); throw new Exception("AAAA"); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index e17800cd..a023087c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -1,12 +1,31 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace EdgeDB.Translators.Expressions { - internal class MemberExpressionTranslator + internal class MemberExpressionTranslator : ExpressionTranslator { + public override string Translate(MemberExpression expression, ExpressionContext context) + { + return ParseMemberExpression(expression, expression.Expression is not ParameterExpression); + } + + private static string ParseMemberExpression(MemberExpression expression, bool includeParameter = true) + { + List tree = new(); + + tree.Add(expression.Member.GetEdgeDBPropertyName()); + if (expression.Expression is MemberExpression innerExp) + tree.Add(ParseMemberExpression(innerExp)); + if (expression.Expression is ParameterExpression param) + tree.Add(includeParameter ? param.Name : string.Empty); + + tree.Reverse(); + return string.Join('.', tree); + } } } From c4a4a417dbefd5d2dbd3c8d06084e4a9fe67cd1d Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 22 Jun 2022 05:59:06 -0300 Subject: [PATCH 03/70] more work on qb --- dbschema/default.esdl | 9 ++ dbschema/migrations/00009.edgeql | 11 ++ .../Examples/QueryBuilder.cs | 95 +++++++++++ .../EdgeDB.Examples.ExampleApp/Program.cs | 1 - .../Serializer/PacketSerializer.cs | 2 +- .../SchemaTypeBuilders/TypeBuilder.cs | 18 ++- .../Extensions/EdgeDBExtensions.cs | 16 ++ src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 11 +- src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 2 +- .../QueryNodes/Contexts/FilterContext.cs | 7 +- .../QueryNodes/Contexts/InsertContext.cs | 18 +++ .../QueryNodes/Contexts/OrderByContext.cs | 7 +- .../QueryNodes/Contexts/SelectContext.cs | 2 +- .../Contexts/UnlessConflictOnContext.cs | 16 ++ .../QueryNodes/Contexts/WithContext.cs | 16 ++ .../QueryNodes/FilterNode.cs | 4 +- .../QueryNodes/InsertNode.cs | 101 ++++++++++++ .../QueryNodes/OrderByNode.cs | 9 +- .../QueryNodes/QueryNode.cs | 27 +++- .../QueryNodes/SelectNode.cs | 13 +- .../QueryNodes/UnlessConflictOnNode.cs | 20 +++ .../QueryNodes/WithNode.cs | 29 ++++ .../QueryObjectManager.cs | 53 +++++++ .../QueryableArgCollection.cs | 22 +++ .../QueryableCollection.EdgeQLFunctions.cs | 78 +++++++++ .../QueryableCollection.cs | 148 +++++++++++++++--- src/EdgeDB.Net.QueryBuilder/SubQuery.cs | 18 +++ .../ConstantExpressionTranslator.cs | 2 +- .../Expressions/ExpressionTranslator.cs | 26 --- .../MethodCallExpressionTranslator.cs | 38 +++++ src/EdgeDB.Net.QueryBuilder/Utils.cs | 39 +++++ src/EdgeDB.Net.QueryBuilder/With.cs | 24 +++ 32 files changed, 795 insertions(+), 87 deletions(-) create mode 100644 dbschema/migrations/00009.edgeql create mode 100644 examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UnlessConflictOnContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/UnlessConflictOnNode.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryObjectManager.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryableArgCollection.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryableCollection.EdgeQLFunctions.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/SubQuery.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Utils.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/With.cs diff --git a/dbschema/default.esdl b/dbschema/default.esdl index 46c6679f..687ed8bd 100644 --- a/dbschema/default.esdl +++ b/dbschema/default.esdl @@ -37,4 +37,13 @@ module default { type OtherThing extending AbstractThing { required property attribute -> str; } + + # for query builder + type LinkPerson { + required property name -> str; + required property email -> str { + constraint exclusive; + } + link best_friend -> LinkPerson; + } } diff --git a/dbschema/migrations/00009.edgeql b/dbschema/migrations/00009.edgeql new file mode 100644 index 00000000..75e36a46 --- /dev/null +++ b/dbschema/migrations/00009.edgeql @@ -0,0 +1,11 @@ +CREATE MIGRATION m1ckoojrq2xwxscsdfrsrrc3rem4afqpvjit65vawiavkhsfaigzna + ONTO m1isiclyxqa32luj6hdazr4mft2mvvq4tmmuoygl7m7k2iimxm5y3a +{ + CREATE TYPE default::LinkPerson { + CREATE LINK best_friend -> default::LinkPerson; + CREATE REQUIRED PROPERTY email -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY name -> std::str; + }; +}; diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs new file mode 100644 index 00000000..6304e2bf --- /dev/null +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -0,0 +1,95 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace EdgeDB.ExampleApp.Examples +{ + internal class QueryBuilder : IExample + { + public ILogger? Logger { get; set; } + + public class LinkPerson + { + public string? Name { get; set; } + public string? Email { get; set; } + public LinkPerson? BestFriend { get; set; } + } + + public class Person + { + public string? Name { get; set; } + public string? Email { get; set; } + public Guid Id { get; set; } + } + + public async Task ExecuteAsync(EdgeDBClient client) + { + var collection = client.GetCollection(); + + var result = await collection.Filter(x => EdgeQL.ILike(x.Name, "j%")).ExecuteAsync(); + + var insertResult = collection.Insert(new LinkPerson + { + Email = "email@example.com", + Name = "ExampleName", + BestFriend = new() + { + Email = "email2@example2.com", + Name = "ExampleName2" + } + }).UnlessConflictOn(x => x.Email).Build(); + + var pretty = Prettify(insertResult.Query); + + } + + public static string Prettify(string queryText) + { + // add newlines + var result = Regex.Replace(queryText, @"({|\(|\)|}|,)", m => + { + switch (m.Groups[1].Value) + { + case "{" or "(" or ",": + if (m.Groups[1].Value == "{" && queryText[m.Index + 1] == '}') + return m.Groups[1].Value; + + return $"{m.Groups[1].Value}\n"; + + default: + return $"{((m.Groups[1].Value == "}" && (queryText[m.Index - 1] == '{' || queryText[m.Index - 1] == '}')) ? "" : "\n")}{m.Groups[1].Value}{((queryText.Length != m.Index + 1 && (queryText[m.Index + 1] != ',')) ? "\n" : "")}"; + } + }).Trim().Replace("\n ", "\n"); + + // clean up newline func + result = Regex.Replace(result, "\n\n", m => "\n"); + + // add indentation + result = Regex.Replace(result, "^", m => + { + int indent = 0; + + foreach (var c in result[..m.Index]) + { + if (c is '(' or '{') + indent++; + if (c is ')' or '}') + indent--; + } + + var next = result.Length != m.Index ? result[m.Index] : '\0'; + + if (next is '}' or ')') + indent--; + + return "".PadLeft(indent * 2); + }, RegexOptions.Multiline); + + return result; + } + } +} diff --git a/examples/EdgeDB.Examples.ExampleApp/Program.cs b/examples/EdgeDB.Examples.ExampleApp/Program.cs index 0aa07e61..7a4124d1 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Program.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Program.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; -using static EdgeDB.ExampleApp.Examples.JsonResults; Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() diff --git a/src/EdgeDB.Net.Driver/Serializer/PacketSerializer.cs b/src/EdgeDB.Net.Driver/Serializer/PacketSerializer.cs index 0375dad0..1efba493 100644 --- a/src/EdgeDB.Net.Driver/Serializer/PacketSerializer.cs +++ b/src/EdgeDB.Net.Driver/Serializer/PacketSerializer.cs @@ -15,7 +15,7 @@ internal class PacketSerializer public static string? GetEdgeQLType(Type t) { - if (t.Name is not "Nullable`1") + if (t.Name == "Nullable`1") t = t.GenericTypeArguments[0]; return _scalarTypeMap.TryGetValue(t, out var result) ? result : null; } diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs index 1c9c0d4f..463720f5 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs @@ -5,6 +5,7 @@ namespace EdgeDB.Serializer { + internal delegate void OnObjectCreated(object obj, Guid id); /// /// Represents the class used to build types from edgedb query results. /// @@ -20,10 +21,14 @@ public static class TypeBuilder /// public static INamingStrategy NamingStrategy { get; set; } - private readonly static ConcurrentDictionary _typeInfo = new(); internal static readonly INamingStrategy AttributeNamingStrategy; + internal static event OnObjectCreated? OnObjectCreated; + + private readonly static ConcurrentDictionary _typeInfo = new(); private readonly static List _scannedAssemblies; + + static TypeBuilder() { _scannedAssemblies = new(); @@ -250,6 +255,11 @@ private static void ScanForAbstractTypes(Assembly assembly) } } #endregion + + internal static void DispatchObjectCreate(object obj, Guid id) + { + OnObjectCreated?.Invoke(obj, id); + } } public delegate object TypeDeserializerFactory(IDictionary args); @@ -385,7 +395,11 @@ private TypeDeserializerFactory CreateDefaultFactory() } public object Deserialize(IDictionary args) - => _factory(args); + { + var result = _factory(args); + TypeBuilder.DispatchObjectCreate(result, (Guid)args["id"]!); + return result; + } public static implicit operator TypeDeserializerFactory(TypeDeserializeInfo info) => info._factory; } diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs new file mode 100644 index 00000000..268ee50e --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + public static class EdgeDBExtensions + { + public static QueryableCollection GetCollection(this IEdgeDBQueryable edgedb) + { + return new QueryableCollection(edgedb); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 35f18f29..3c43b3f0 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -10,21 +10,16 @@ namespace EdgeDB internal class QueryBuilder { public StringBuilder Query { get; } - public List Nodes { get; } + public List Nodes { get; } public QueryContext Context { get; } public Dictionary QueryVariables { get; } = new(); + public Dictionary QueryGlobals { get; } = new(); - public QueryBuilder(QueryContext context, List? nodes = null) + public QueryBuilder(QueryContext context, List? nodes = null) { Query = new(); Nodes = nodes ?? new(); Context = context; } } - - internal class CompiledQueryNode - { - public QueryNode? Node { get; init; } - public StringBuilder? Query { get; init; } - } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index 35a0123f..8c2c1946 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -7,7 +7,7 @@ namespace EdgeDB { - internal abstract class QueryContext + internal class QueryContext { public Type CurrentType { get; init; } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs index 76a4f502..c934bc81 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs @@ -9,11 +9,8 @@ namespace EdgeDB.QueryNodes { internal class FilterContext : QueryContext { - public LambdaExpression Expression { get; init; } + public LambdaExpression? Expression { get; init; } - public FilterContext(Type currentType, LambdaExpression expression) : base(currentType) - { - Expression = expression; - } + public FilterContext(Type currentType) : base(currentType) { } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs new file mode 100644 index 00000000..be404e0b --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class InsertContext : QueryContext + { + public object? Value { get; init; } + public bool StoreAsGlobal { get; init; } + public string? GlobalName { get; set; } + public InsertContext(Type currentType) : base(currentType) + { + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs index 0824f20b..e9c9ebc2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs @@ -11,12 +11,9 @@ internal class OrderByContext : QueryContext { public OrderByDirection Direction { get; init; } public EmptyPlacement? EmptyPlacement { get; init; } - public LambdaExpression Expression { get; init; } + public LambdaExpression? Expression { get; init; } - public OrderByContext(Type currentType, LambdaExpression expression) : base(currentType) - { - Expression = expression; - } + public OrderByContext(Type currentType) : base(currentType) { } } internal enum OrderByDirection diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs index 6227df4e..9a2bf8cb 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs @@ -9,7 +9,7 @@ namespace EdgeDB.QueryNodes internal class SelectContext : QueryContext { public object? Shape { get; init; } - + public string? SelectName { get; init; } public SelectContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UnlessConflictOnContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UnlessConflictOnContext.cs new file mode 100644 index 00000000..7ba8150f --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UnlessConflictOnContext.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class UnlessConflictOnContext : QueryContext + { + public LambdaExpression? Selector { get; init; } + + public UnlessConflictOnContext(Type currentType) : base(currentType) { } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs new file mode 100644 index 00000000..8a11273b --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class WithContext : QueryContext + { + public Dictionary? Values { get; init; } + public WithContext(Type currentType) : base(currentType) + { + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs index 3acf6074..f4bf6153 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs @@ -8,9 +8,11 @@ namespace EdgeDB.QueryNodes { internal class FilterNode : QueryNode { + public override bool IsRootNode => false; + public FilterNode(QueryBuilder builder) : base(builder) { } - protected override void Visit() + public override void Visit() { if (Context.Expression is null) throw new ArgumentNullException("No expression was passed in for a filter node"); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs new file mode 100644 index 00000000..b35af906 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -0,0 +1,101 @@ +using EdgeDB.Serializer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class InsertNode : QueryNode + { + private const string VARIABLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static readonly Random _rng = new(); + + public override bool IsRootNode => true; + + public override QueryExpressionType? ValidChildren + => QueryExpressionType.UnlessConflictOn | QueryExpressionType.Else; + + public InsertNode(QueryBuilder builder) : base(builder) { } + + private string BuildInsertShape(Type? shapeType = null, object? shapeValue = null) + { + List shape = new(); + + var type = shapeType ?? Context.CurrentType; + var value = shapeValue ?? Context.Value; + + var properties = type.GetProperties().Where(x => x.GetCustomAttribute() == null); + + foreach(var property in properties) + { + var propertyName = property.GetEdgeDBPropertyName(); + + var edgeqlType = PacketSerializer.GetEdgeQLType(property.PropertyType); + + if(edgeqlType != null) + { + var varName = GenerateRandomVariableName(); + SetVariable(varName, property.GetValue(Context.Value)); + shape.Add($"{propertyName} := <{edgeqlType}>${varName}"); + continue; + } + + // TODO: sub queries! + // might be a link? + if (TypeBuilder.IsValidObjectType(property.PropertyType)) + { + // is it a object we've seen before? + var subValue = property.GetValue(value); + if(QueryObjectManager.TryGetObjectId(subValue, out var id)) + { + // insert a sub query + var globalName = GenerateRandomVariableName(); + SetGlobal(globalName, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = \"{id}\")")); + shape.Add($"{propertyName} := {globalName}"); + continue; + } + else + { + if (subValue is null) + shape.Add($"{propertyName} := {{}}"); + else + { + var globalName = GenerateRandomVariableName(); + SetGlobal(globalName, new SubQuery($"(insert {property.PropertyType.GetEdgeDBTypeName()} {BuildInsertShape(property.PropertyType, subValue)})")); + shape.Add($"{propertyName} := {globalName}"); + } + + continue; + } + } + + throw new Exception($"Failed to find method to serialize the property \"{property.PropertyType.Name}\" on type {type.Name}"); + } + + return $"{{ {string.Join(", ", shape)} }}"; + } + + private static string GenerateRandomVariableName() + { + return new string(Enumerable.Repeat(VARIABLE_CHARS, 12).Select(x => x[_rng.Next(x.Length)]).ToArray()); + } + + public override void Visit() + { + var shape = BuildInsertShape(); + var insert = $"insert {Context.CurrentType.GetEdgeDBTypeName()} {shape}"; + + if (Context.StoreAsGlobal) + { + var globalName = GenerateRandomVariableName(); + SetGlobal(globalName, new SubQuery(insert)); + Context.GlobalName = globalName; + } + else + Query.Append(insert); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs index bab6d52f..da9a2dc4 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs @@ -8,21 +8,22 @@ namespace EdgeDB.QueryNodes { internal class OrderByNode : QueryNode { + public override bool IsRootNode => false; public OrderByNode(QueryBuilder builder) : base(builder) { } - protected override void FinalizeQuery() + public override void FinalizeQuery() { // check if previous node was a order by, if so change to 'then' - if (Builder.Nodes.FirstOrDefault()?.Node is OrderByNode) + if (Builder.Nodes.FirstOrDefault() is OrderByNode) { Query.Remove(0, 8); Query.Insert(0, "then"); } } - protected override void Visit() + public override void Visit() { - var expression = ExpressionTranslator.Translate(Context.Expression); + var expression = ExpressionTranslator.Translate(Context.Expression!); var direction = Context.Direction == OrderByDirection.Ascending ? "asc" : "desc"; Query.Append($"order by {expression} {direction}"); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index 92dab915..d5083b8b 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -19,6 +19,9 @@ internal abstract class QueryNode { protected readonly QueryBuilder Builder; + public virtual QueryExpressionType? ValidChildren { get; } + public abstract bool IsRootNode { get; } + protected StringBuilder Query => Builder.Query; @@ -27,10 +30,30 @@ public QueryNode(QueryBuilder builder) Builder = builder; } - protected abstract void Visit(); - protected virtual void FinalizeQuery() { } + public abstract void Visit(); + public virtual void FinalizeQuery() { } protected void SetVariable(string name, object? value) => Builder.QueryVariables[name] = value; + + protected void SetGlobal(string name, object? value) + => Builder.QueryGlobals[name] = value; + + internal BuiltQueryNode Build() + => new BuiltQueryNode(Query.ToString(), Builder.QueryVariables, Builder.QueryGlobals); + } + + internal class BuiltQueryNode + { + public string Query { get; init; } + public IDictionary Parameters { get; init; } + public IDictionary Globals { get; init; } + + public BuiltQueryNode(string query, IDictionary parameters, IDictionary globals) + { + Query = query; + Parameters = parameters; + Globals = globals; + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 3bb9fa42..6734967c 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -10,6 +10,11 @@ namespace EdgeDB.QueryNodes { internal class SelectNode : QueryNode { + public override bool IsRootNode => true; + + public override QueryExpressionType? ValidChildren + => QueryExpressionType.Filter | QueryExpressionType.OrderBy | QueryExpressionType.Offset | QueryExpressionType.Limit; + public SelectNode(QueryBuilder builder) : base(builder) { } protected virtual string GetShape() @@ -21,16 +26,16 @@ protected virtual string GetShape() return $"{{ {string.Join(", ", propertyNames)} }}"; } - protected override void Visit() + public override void Visit() { var shape = GetShape(); - Query.Append($"select {Context.CurrentType.GetEdgeDBTypeName()} {shape}"); + Query.Append($"select {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()} {shape}"); } - protected override void FinalizeQuery() + public override void FinalizeQuery() { // check if theres a child select with the same type - if(Builder.Nodes.Any(x => x.Node is SelectNode select && select.Context.CurrentType == Context.CurrentType)) + if(Builder.Nodes.Any(x => x is SelectNode select && select.Context.CurrentType == Context.CurrentType)) { Query.Insert(7, "detached "); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UnlessConflictOnNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UnlessConflictOnNode.cs new file mode 100644 index 00000000..e4b0f152 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UnlessConflictOnNode.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class UnlessConflictOnNode : QueryNode + { + public override bool IsRootNode => false; + + public UnlessConflictOnNode(QueryBuilder builder) : base(builder) { } + + public override void Visit() + { + Query.Append($"unless conflict on {ExpressionTranslator.Translate(Context.Selector!)}"); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs new file mode 100644 index 00000000..5c23a28e --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class WithNode : QueryNode + { + public override bool IsRootNode => true; + public bool HasVisited { get; private set; } + + public override QueryExpressionType? ValidChildren + => QueryExpressionType.Select | QueryExpressionType.Insert | QueryExpressionType.Update | QueryExpressionType.Delete; + + public WithNode(QueryBuilder builder) : base(builder) { } + + public override void Visit() + { + HasVisited = true; + + if (Context.Values is null || !Context.Values.Any()) + return; + + Query.Append($"with {string.Join(", ", Context.Values.Select(x => $"{x.Key} := {QueryUtils.ParseObject(x.Value)}"))}"); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryObjectManager.cs b/src/EdgeDB.Net.QueryBuilder/QueryObjectManager.cs new file mode 100644 index 00000000..32d667e2 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryObjectManager.cs @@ -0,0 +1,53 @@ +using EdgeDB.Serializer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + /// + /// The general purpouse of this class is to manage references to objects which + /// have been returned from a query. It's used to check if an object has to be inserted + /// or can be referenced + /// + internal class QueryObjectManager + { + private static HashSet _references = new(); + + public static void Initialize() + { + TypeBuilder.OnObjectCreated += TypeBuilder_OnObjectCreated; + } + + public static bool TryGetObjectId(object? obj, out Guid id) + { + id = default; + if (obj == null) + return false; + + var reference = _references.FirstOrDefault(x => x.Reference.IsAlive && x.Reference.Target == obj); + id = reference?.ObjectId ?? default; + return reference != null; + } + + private static void TypeBuilder_OnObjectCreated(object obj, Guid id) + { + var reference = new QueryObjectReference(id, new WeakReference(obj)); + _references.Add(reference); + } + + private class QueryObjectReference + { + public readonly Guid ObjectId; + public readonly WeakReference Reference; + + public QueryObjectReference(Guid objectId, WeakReference reference) + { + ObjectId = objectId; + Reference = reference; + } + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableArgCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableArgCollection.cs new file mode 100644 index 00000000..fd2f2a21 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryableArgCollection.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + internal class QueryableArgCollection : QueryableCollection + where TTuple : ITuple + { + private readonly TTuple _tuple; + + internal QueryableArgCollection(IEdgeDBQueryable edgedb, TTuple tuple) : base(edgedb) + { + _tuple = tuple; + } + + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.EdgeQLFunctions.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.EdgeQLFunctions.cs new file mode 100644 index 00000000..868f965f --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.EdgeQLFunctions.cs @@ -0,0 +1,78 @@ +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 partial class QueryableCollection + { + public QueryableCollection Select(object? shape = null) + { + AddNode(new SelectContext(typeof(TQueryResult)) + { + Shape = shape + }); + return this; + } + + public QueryableCollection Filter(Expression> condition) + { + if (CurrentRootNode == null) + Select(); + + AddNode(new FilterContext(typeof(TQueryResult)) + { + Expression = condition + }); + + return this; + } + + public QueryableCollection Insert(TQueryResult value, bool returnValue = true) + { + var context = new InsertContext(typeof(TQueryResult)) + { + Value = value, + StoreAsGlobal = returnValue + }; + + AddNode(context); + + // fix this: select isn't valid after insert which is true but the insert + // should cause a with block to be added instead. + if (returnValue) + { + AddNode(new SelectContext(typeof(TQueryResult)) + { + SelectName = context.GlobalName + }, false); + } + + return this; + } + + public QueryableCollection UnlessConflictOn(Expression> selector) + { + AddNode(new UnlessConflictOnContext(typeof(TQueryResult)) + { + Selector = selector + }); + + return this; + } + + public Task> ExecuteAsync(CancellationToken token = default) + { + var builtQuery = Build(); + + // clear the current query + _nodes.Clear(); + + return _edgedb.QueryAsync(builtQuery.Query, builtQuery.Parameters, token); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs index 9898556a..17b0b6db 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -3,15 +3,33 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Text; using System.Threading.Tasks; namespace EdgeDB { - public sealed class QueryableCollection + public partial class QueryableCollection { private readonly IEdgeDBQueryable _edgedb; - private readonly List _nodes; + private readonly List> _nodes; + private List? CurrentNodeGroup => _nodes.LastOrDefault(); + private QueryNode? CurrentRootNode => CurrentNodeGroup?.FirstOrDefault(x => x.IsRootNode); + + private static readonly Dictionary _nodeTypes = new() + { + {typeof(SelectNode), QueryExpressionType.Select }, + {typeof(FilterNode), QueryExpressionType.Filter }, + {typeof(OrderByNode), QueryExpressionType.OrderBy }, + {typeof(InsertNode), QueryExpressionType.Insert }, + {typeof(WithNode), QueryExpressionType.With }, + {typeof(UnlessConflictOnNode), QueryExpressionType.UnlessConflictOn } + }; + + static QueryableCollection() + { + QueryObjectManager.Initialize(); + } internal QueryableCollection(IEdgeDBQueryable edgedb) { @@ -19,36 +37,116 @@ internal QueryableCollection(IEdgeDBQueryable edgedb) _nodes = new(); } - public QueryableCollection Where(Expression> condition) + private void AddNode(QueryContext context, bool validate = true) + where TNode : QueryNode + { + if(validate) + ValidateNode(); + + // create the node and a builder + var builder = new QueryBuilder(context, CurrentNodeGroup); + var node = (QueryNode)Activator.CreateInstance(typeof(TNode), builder)!; + + // visit the node + node.Visit(); + + // create a new group if its a root node + if (node.IsRootNode) + _nodes.Add(new List() { node }); + else if (CurrentNodeGroup != null) + CurrentNodeGroup.Add(node); + else + throw new Exception("No node group found! this is a bug"); + } + + private void ValidateNode() + where TNode : QueryNode { - return this; + if (!_nodeTypes.TryGetValue(typeof(TNode), out var type)) + throw new ArgumentException($"No matching node found for {typeof(TNode).Name}"); + + if (CurrentRootNode == null) + return; + + if ((CurrentRootNode.ValidChildren & type) == 0) + throw new ArgumentException($"{type} cannot follow a {CurrentRootNode}"); } + public (string Query, IDictionary Parameters) Build() + { + List query = new(); + List> parameters = new(); + Dictionary globals = new(); + + var nodes = _nodes; + + // extract a with block or create one + var withBlock = nodes.FirstOrDefault()?.FirstOrDefault(); + + if(withBlock is not null and not WithNode) + { + var context = new WithContext(typeof(TQueryResult)) + { + Values = globals + }; + nodes = nodes.Prepend(new List() { new WithNode(new QueryBuilder(context)) }).ToList(); + } + + for (int i = nodes.Count - 1; i >= 0; i--) + { + var subNodes = nodes[i]; + for (int j = subNodes.Count - 1; j >= 0; j--) + { + var node = subNodes[j]; + if(node is WithNode withNode) + { + if (!withNode.HasVisited) + withNode.Visit(); + } + + node.FinalizeQuery(); + + var result = node.Build(); + + if(!string.IsNullOrEmpty(result.Query)) + query.Add(result.Query); + + parameters.Add(result.Parameters); + + foreach (var global in result.Globals) + globals[global.Key] = global.Value; + } + } + + query.Reverse(); + + + + return (string.Join(' ', query), parameters.SelectMany(x => x).DistinctBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value)); + } } + [Flags] public enum QueryExpressionType { - Start, - Select, - Insert, - Update, - Delete, - With, - For, - Filter, - OrderBy, - Offset, - Limit, - Set, - Transaction, - Union, - UnlessConflictOn, - Rollback, - Commit, - Else, - - // internal - Variable, + Start = 0, + Select = 1 << 0, + Insert = 1 << 1, + Update = 1 << 2, + Delete = 1 << 3, + With = 1 << 4, + For = 1 << 5, + Filter = 1 << 6, + OrderBy = 1 << 7, + Offset = 1 << 8, + Limit = 1 << 9, + Set = 1 << 10, + Transaction = 1 << 11, + Union = 1 << 12, + UnlessConflictOn = 1 << 13, + Rollback = 1 << 14, + Commit = 1 << 15, + Else = 1 << 16, } } diff --git a/src/EdgeDB.Net.QueryBuilder/SubQuery.cs b/src/EdgeDB.Net.QueryBuilder/SubQuery.cs new file mode 100644 index 00000000..20a83843 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/SubQuery.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + internal class SubQuery + { + public string Query { get; init; } + + public SubQuery(string query) + { + Query = query; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs index d260db49..3569a9b4 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs @@ -11,7 +11,7 @@ internal class ConstantExpressionTranslator : ExpressionTranslator(); - return att != null ? att.Method switch - { - SerializationMethod.Lower => $"\"{obj.ToString()?.ToLower()}\"", - SerializationMethod.Numeric => Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}", - _ => "{}" - } : Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}"; - } - - return obj switch - { - string str => $"\"{str}\"", - char chr => $"\"{chr}\"", - Type type => PacketSerializer.GetEdgeQLType(type) ?? type.GetEdgeDBTypeName(), - _ => obj.ToString()! - }; - } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs new file mode 100644 index 00000000..81def945 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -0,0 +1,38 @@ +using EdgeDB.Operators; +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 +{ + internal class MethodCallExpressionTranslator : ExpressionTranslator + { + public override string Translate(MethodCallExpression expression, ExpressionContext context) + { + // check if the method has an 'EquivalentOperator' attribute + var edgeqlOperator = expression.Method.GetCustomAttribute()?.Operator; + + if (edgeqlOperator != null) + { + // parse the parameters + var argsArray = new object[expression.Arguments.Count]; + for(int i = 0; i != argsArray.Length; i++) + argsArray[i] = TranslateExpression(expression.Arguments[i], context); + return edgeqlOperator.Build(argsArray); + } + + // check if its a known method + if(EdgeQL.FunctionOperators.TryGetValue($"{expression.Method.DeclaringType?.Name}.{expression.Method.Name}", out edgeqlOperator)) + { + var parsedArgs = expression.Arguments.Select(x => TranslateExpression(x, context)); + return edgeqlOperator.Build(parsedArgs); + } + + throw new Exception($"Couldn't find translator for {expression.Method.Name}"); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Utils.cs b/src/EdgeDB.Net.QueryBuilder/Utils.cs new file mode 100644 index 00000000..b7a91b0e --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + internal class QueryUtils + { + internal static string ParseObject(object? obj) + { + if (obj is null) + return "{}"; + + if (obj is Enum enm) + { + var type = enm.GetType(); + var att = type.GetCustomAttribute(); + return att != null ? att.Method switch + { + SerializationMethod.Lower => $"\"{obj.ToString()?.ToLower()}\"", + SerializationMethod.Numeric => Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}", + _ => "{}" + } : Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}"; + } + + return obj switch + { + SubQuery query => query.Query, + string str => $"\"{str}\"", + char chr => $"\"{chr}\"", + Type type => PacketSerializer.GetEdgeQLType(type) ?? type.GetEdgeDBTypeName(), + _ => obj.ToString()! + }; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/With.cs b/src/EdgeDB.Net.QueryBuilder/With.cs new file mode 100644 index 00000000..4fa5ad73 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/With.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + public class With + { + + } + + public class With : With + { + private readonly QueryableCollection _queryCollection; + public With(QueryableCollection queryCollection) + { + _queryCollection = queryCollection; + } + + public static implicit operator With(QueryableCollection query) => new(query); + } +} From 55ce7bae7e18dac5b076c078b6e5f23800456f1f Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sat, 25 Jun 2022 17:12:16 -0300 Subject: [PATCH 04/70] more query builder work --- .../Examples/QueryBuilder.cs | 35 ++- .../Interfaces/IMultiCardinalityExecutable.cs | 13 + .../ISingleCardinalityExecutable.cs | 13 + .../Interfaces/Queries/IDeleteQuery.cs | 18 ++ .../Interfaces/Queries/IInsertQuery.cs | 14 + .../Interfaces/Queries/ISelectQuery.cs | 18 ++ .../Interfaces/Queries/IUpdateQuery.cs | 14 + .../InsertChildren/IUnlessConflictOn.cs | 14 + .../OrderByNullPlacement.cs | 14 + src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 280 +++++++++++++++++- .../QueryNodes/Contexts/DeleteContext.cs | 15 + .../QueryNodes/Contexts/FilterContext.cs | 16 - .../QueryNodes/Contexts/InsertContext.cs | 4 +- .../QueryNodes/Contexts/OrderByContext.cs | 30 -- .../QueryNodes/Contexts/SelectContext.cs | 5 +- .../Contexts/UnlessConflictOnContext.cs | 16 - .../QueryNodes/Contexts/UpdateContext.cs | 19 ++ .../QueryNodes/Contexts/WithContext.cs | 2 +- .../QueryNodes/DeleteNode.cs | 20 ++ .../QueryNodes/FilterNode.cs | 24 -- .../QueryNodes/InsertNode.cs | 40 ++- .../QueryNodes/NodeBuilder.cs | 27 ++ .../NodeContext.cs} | 6 +- .../QueryNodes/OrderByNode.cs | 36 --- .../QueryNodes/QueryNode.cs | 20 +- .../QueryNodes/SelectNode.cs | 43 ++- .../QueryNodes/UnlessConflictOnNode.cs | 20 -- .../QueryNodes/UpdateNode.cs | 40 +++ .../QueryNodes/WithNode.cs | 27 +- .../{Utils.cs => QueryUtils.cs} | 9 + .../QueryableArgCollection.cs | 22 -- .../QueryableCollection.EdgeQLFunctions.cs | 78 ----- .../QueryableCollection.cs | 136 +-------- .../Expressions/ExpressionContext.cs | 11 +- .../Expressions/ExpressionTranslator.cs | 4 +- .../Expressions/MemberExpressionTranslator.cs | 15 + .../MemberInitExpressionTranslator.cs | 32 ++ .../MethodCallExpressionTranslator.cs | 4 +- .../EdgeDB.DotnetTool/Schemas/ClassBuilder.cs | 2 +- 39 files changed, 689 insertions(+), 467 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UnlessConflictOnContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs rename src/EdgeDB.Net.QueryBuilder/{QueryContext.cs => QueryNodes/NodeContext.cs} (62%) delete mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/UnlessConflictOnNode.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs rename src/EdgeDB.Net.QueryBuilder/{Utils.cs => QueryUtils.cs} (79%) delete mode 100644 src/EdgeDB.Net.QueryBuilder/QueryableArgCollection.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/QueryableCollection.EdgeQLFunctions.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 6304e2bf..b7807f3a 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -28,22 +28,31 @@ public class Person public async Task ExecuteAsync(EdgeDBClient client) { - var collection = client.GetCollection(); + var collection = client.GetCollection(); - var result = await collection.Filter(x => EdgeQL.ILike(x.Name, "j%")).ExecuteAsync(); - - var insertResult = collection.Insert(new LinkPerson - { - Email = "email@example.com", - Name = "ExampleName", - BestFriend = new() + collection + .With("john", new QueryBuilder().Select.Filter(x => x.Name == "John Smith")) + .Update(x => new Person { - Email = "email2@example2.com", - Name = "ExampleName2" - } - }).UnlessConflictOn(x => x.Email).Build(); + Email = "johnsmith@example.com", + }); + + var s = collection.Build(); + + //var result = await collection.Filter(x => EdgeQL.ILike(x.Name, "j%")).ExecuteAsync(); + + //var insertResult = collection.Insert(new LinkPerson + //{ + // Email = "email@example.com", + // Name = "ExampleName", + // BestFriend = new() + // { + // Email = "email2@example2.com", + // Name = "ExampleName2" + // } + //}).UnlessConflictOn(x => x.Email).Build(); - var pretty = Prettify(insertResult.Query); + //var pretty = Prettify(insertResult.Query); } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs new file mode 100644 index 00000000..97146730 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces +{ + public interface IMultiCardinalityExecutable + { + Task> ExecuteAsync(IEdgeDBQueryable edgedb); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs new file mode 100644 index 00000000..bcd475e2 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces +{ + public interface ISingleCardinalityExecutable + { + Task ExecuteAsync(IEdgeDBQueryable edgedb); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs new file mode 100644 index 00000000..8af38e29 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces.Queries +{ + public interface IDeleteQuery : IMultiCardinalityExecutable + { + IDeleteQuery Filter(Expression> filter); + IDeleteQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + IDeleteQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + IDeleteQuery Offset(long offset); + IDeleteQuery Limit(long limit); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs new file mode 100644 index 00000000..cdd8aa66 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces.Queries +{ + public interface IInsertQuery : ISingleCardinalityExecutable + { + IInsertQuery UnlessConflictOn(Expression> propertySelector); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs new file mode 100644 index 00000000..063826af --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces.Queries +{ + public interface ISelectQuery : IMultiCardinalityExecutable + { + ISelectQuery Filter(Expression> filter); + ISelectQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + ISelectQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + ISelectQuery Offset(long offset); + ISelectQuery Limit(long limit); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs new file mode 100644 index 00000000..2b1ba884 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces.Queries +{ + public interface IUpdateQuery : IMultiCardinalityExecutable + { + IMultiCardinalityExecutable Filter(Expression> filter); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs new file mode 100644 index 00000000..6888ecae --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces +{ + public interface IUnlessConflictOn : ISingleCardinalityExecutable + { + ISingleCardinalityExecutable ElseReturn(); + // TODO: Else expression + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs b/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs new file mode 100644 index 00000000..c79e252f --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + public enum OrderByNullPlacement + { + First, + Last + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 3c43b3f0..7d41ed56 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -1,25 +1,283 @@ -using EdgeDB.QueryNodes; +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 { - internal class QueryBuilder + public class QueryBuilder : + IDeleteQuery, + IInsertQuery, + ISelectQuery, + IUpdateQuery, + IUnlessConflictOn, + IQueryBuilder { - public StringBuilder Query { get; } - public List Nodes { get; } - public QueryContext Context { get; } - public Dictionary QueryVariables { get; } = new(); - public Dictionary QueryGlobals { get; } = new(); + private readonly List _nodes; + private QueryNode? CurrentUserNode => _nodes.LastOrDefault(x => !x.IsAutoGenerated); + private readonly Dictionary _queryGlobals; + + static QueryBuilder() + { + QueryObjectManager.Initialize(); + } + + internal QueryBuilder() + { + _nodes = new(); + _queryGlobals = new(); + } + + private TNode AddNode(NodeContext context, bool autoGenerated = false) + where TNode : QueryNode + { + // create the node and a builder + var builder = new NodeBuilder(context, _queryGlobals, _nodes) + { + IsAutoGenerated = autoGenerated + }; + + var node = (TNode)Activator.CreateInstance(typeof(TNode), builder)!; + + // visit the node + node.Visit(); + + // if the node didn't generate a query assume its only modifying + // globals so we can skip adding it to our node tree + if (builder.Query.Length == 0) + return node; + + // create a new group if its a root node + _nodes.Add(node); + + return node; + } + + public (string Query, IDictionary Parameters) Build() + { + List query = new(); + List> parameters = new(); + + var nodes = _nodes; + + foreach(var node in nodes) + node.FinalizeQuery(); + + // create a with block if we have any query globals + if (_queryGlobals.Any()) + { + var with = new WithNode(new NodeBuilder(new WithContext(typeof(TType)) + { + Values = _queryGlobals + }, _queryGlobals, null)); + with.Visit(); + nodes = nodes.Prepend(with).ToList(); + } + + for (int i = nodes.Count - 1; i >= 0; i--) + { + var node = nodes[i]; + + if (node is WithNode withNode) + { + if (!withNode.HasVisited) + withNode.Visit(); + } + + var result = node.Build(); + + if (!string.IsNullOrEmpty(result.Query)) + query.Add(result.Query); + + parameters.Add(result.Parameters); + } + + query.Reverse(); + + return (string.Join(' ', query), parameters.SelectMany(x => x).DistinctBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value)); + } + + public QueryBuilder With(string name, object? value) + { + _queryGlobals[name] = value; + return this; + } + + #region Root nodes + public ISelectQuery Select + => SelectWithShape(null!); + public ISelectQuery SelectWithShape(Expression> shape) + { + AddNode(new SelectContext(typeof(TType)) + { + Shape = shape + }); + return this; + } + + public IInsertQuery Insert(TType value) + { + AddNode(new InsertContext(typeof(TType)) + { + Value = value + }); + return this; + } + + public IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true) + { + var selectedGlobal = returnUpdatedValue ? QueryUtils.GenerateRandomVariableName() : null; + AddNode(new UpdateContext(typeof(TType)) + { + UpdateExpression = updateFunc, + SetAsGlobal = returnUpdatedValue, + GlobalName = selectedGlobal + }); + + if (returnUpdatedValue) + { + AddNode(new SelectContext(typeof(TType)) + { + SelectName = selectedGlobal, + }, true); + } + + return this; + } + + public IDeleteQuery Delete + { + get + { + AddNode(new DeleteContext(typeof(TType))); + return this; + } + } + + #endregion - public QueryBuilder(QueryContext context, List? nodes = null) + #region Generic sub-query methods + private QueryBuilder Filter(Expression> filter) { - Query = new(); - Nodes = nodes ?? new(); - Context = context; + 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; } + + private QueryBuilder OrderBy(bool asc, Expression> selector, OrderByNullPlacement? placement) + { + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot order by on a {CurrentUserNode}"); + + selectNode.OrderBy(asc, selector, placement); + + return this; + } + + private QueryBuilder Offset(long offset) + { + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot offset on a {CurrentUserNode}"); + + selectNode.Offset(offset); + + return this; + } + + private QueryBuilder Limit(long limit) + { + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot limit on a {CurrentUserNode}"); + + selectNode.Limit(limit); + + return this; + } + + private QueryBuilder UnlessConflictOn(Expression> selector) + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); + + insertNode.UnlessConflictOn(selector); + + return this; + } + #endregion + + #region ISelectQuery + ISelectQuery ISelectQuery.Filter(Expression> filter) + => Filter(filter); + ISelectQuery ISelectQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(true, propertySelector, nullPlacement); + ISelectQuery ISelectQuery.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(false, propertySelector, nullPlacement); + ISelectQuery ISelectQuery.Offset(long offset) + => Offset(offset); + ISelectQuery ISelectQuery.Limit(long limit) + => Limit(limit); + #endregion + + #region IInsertQuery + IInsertQuery IInsertQuery.UnlessConflictOn(Expression> propertySelector) + => UnlessConflictOn(propertySelector); + #endregion + + #region IUpdateQuery + IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) + => Filter(filter); + #endregion + + #region IUnlessConflictOn + ISingleCardinalityExecutable IUnlessConflictOn.ElseReturn() + { + throw new NotImplementedException(); + } + #endregion + + #region IDeleteQuery + IDeleteQuery IDeleteQuery.Filter(Expression> filter) + => Filter(filter); + IDeleteQuery IDeleteQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(true, propertySelector, nullPlacement); + + IDeleteQuery IDeleteQuery.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(false, propertySelector, nullPlacement); + + IDeleteQuery IDeleteQuery.Offset(long offset) + => Offset(offset); + + IDeleteQuery IDeleteQuery.Limit(long limit) + => Limit(limit); + #endregion + + Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb) + { + throw new NotImplementedException(); + } + + Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb) + { + throw new NotImplementedException(); + } + } + + internal interface IQueryBuilder + { + (string Query, IDictionary Parameters) Build(); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs new file mode 100644 index 00000000..3362976a --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class DeleteContext : SelectContext + { + public DeleteContext(Type currentType) : base(currentType) + { + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs deleted file mode 100644 index c934bc81..00000000 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/FilterContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.QueryNodes -{ - internal class FilterContext : QueryContext - { - public LambdaExpression? Expression { get; init; } - - public FilterContext(Type currentType) : base(currentType) { } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs index be404e0b..5869b44d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs @@ -6,11 +6,9 @@ namespace EdgeDB.QueryNodes { - internal class InsertContext : QueryContext + internal class InsertContext : NodeContext { public object? Value { get; init; } - public bool StoreAsGlobal { get; init; } - public string? GlobalName { get; set; } public InsertContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs deleted file mode 100644 index e9c9ebc2..00000000 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/OrderByContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.QueryNodes -{ - internal class OrderByContext : QueryContext - { - public OrderByDirection Direction { get; init; } - public EmptyPlacement? EmptyPlacement { get; init; } - public LambdaExpression? Expression { get; init; } - - public OrderByContext(Type currentType) : base(currentType) { } - } - - internal enum OrderByDirection - { - Ascending, - Descending, - } - - internal enum EmptyPlacement - { - First, - Last, - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs index 9a2bf8cb..0cb86804 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs @@ -1,14 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace EdgeDB.QueryNodes { - internal class SelectContext : QueryContext + internal class SelectContext : NodeContext { - public object? Shape { get; init; } + public Expression>? Shape { get; init; } public string? SelectName { get; init; } public SelectContext(Type currentType) : base(currentType) { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UnlessConflictOnContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UnlessConflictOnContext.cs deleted file mode 100644 index 7ba8150f..00000000 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UnlessConflictOnContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.QueryNodes -{ - internal class UnlessConflictOnContext : QueryContext - { - public LambdaExpression? Selector { get; init; } - - public UnlessConflictOnContext(Type currentType) : base(currentType) { } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs new file mode 100644 index 00000000..65d387b4 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class UpdateContext : NodeContext + { + public string? UpdateName { get; init; } + public LambdaExpression? UpdateExpression { get; init; } + + public UpdateContext(Type currentType) : base(currentType) + { + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs index 8a11273b..069ced70 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs @@ -6,7 +6,7 @@ namespace EdgeDB.QueryNodes { - internal class WithContext : QueryContext + internal class WithContext : NodeContext { public Dictionary? Values { get; init; } public WithContext(Type currentType) : base(currentType) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs new file mode 100644 index 00000000..60e7f8cf --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class DeleteNode : SelectNode + { + public DeleteNode(NodeBuilder builder) : base(builder) + { + } + + public override void Visit() + { + Query.Append($"delete {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()}"); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs deleted file mode 100644 index f4bf6153..00000000 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/FilterNode.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.QueryNodes -{ - internal class FilterNode : QueryNode - { - public override bool IsRootNode => false; - - public FilterNode(QueryBuilder builder) : base(builder) { } - - public override void Visit() - { - if (Context.Expression is null) - throw new ArgumentNullException("No expression was passed in for a filter node"); - - var parsedExpression = ExpressionTranslator.Translate(Context.Expression); - Query.Append($"filter {parsedExpression}"); - } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index b35af906..93d8a7ab 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -10,15 +11,7 @@ namespace EdgeDB.QueryNodes { internal class InsertNode : QueryNode { - private const string VARIABLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - private static readonly Random _rng = new(); - - public override bool IsRootNode => true; - - public override QueryExpressionType? ValidChildren - => QueryExpressionType.UnlessConflictOn | QueryExpressionType.Else; - - public InsertNode(QueryBuilder builder) : base(builder) { } + public InsertNode(NodeBuilder builder) : base(builder) { } private string BuildInsertShape(Type? shapeType = null, object? shapeValue = null) { @@ -37,7 +30,7 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul if(edgeqlType != null) { - var varName = GenerateRandomVariableName(); + var varName = QueryUtils.GenerateRandomVariableName(); SetVariable(varName, property.GetValue(Context.Value)); shape.Add($"{propertyName} := <{edgeqlType}>${varName}"); continue; @@ -52,7 +45,7 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul if(QueryObjectManager.TryGetObjectId(subValue, out var id)) { // insert a sub query - var globalName = GenerateRandomVariableName(); + var globalName = QueryUtils.GenerateRandomVariableName(); SetGlobal(globalName, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = \"{id}\")")); shape.Add($"{propertyName} := {globalName}"); continue; @@ -63,7 +56,7 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul shape.Add($"{propertyName} := {{}}"); else { - var globalName = GenerateRandomVariableName(); + var globalName = QueryUtils.GenerateRandomVariableName(); SetGlobal(globalName, new SubQuery($"(insert {property.PropertyType.GetEdgeDBTypeName()} {BuildInsertShape(property.PropertyType, subValue)})")); shape.Add($"{propertyName} := {globalName}"); } @@ -77,25 +70,28 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul return $"{{ {string.Join(", ", shape)} }}"; } - - private static string GenerateRandomVariableName() - { - return new string(Enumerable.Repeat(VARIABLE_CHARS, 12).Select(x => x[_rng.Next(x.Length)]).ToArray()); - } - + public override void Visit() { var shape = BuildInsertShape(); var insert = $"insert {Context.CurrentType.GetEdgeDBTypeName()} {shape}"; - if (Context.StoreAsGlobal) + if (Context.SetAsGlobal && Context.GlobalName != null) { - var globalName = GenerateRandomVariableName(); - SetGlobal(globalName, new SubQuery(insert)); - Context.GlobalName = globalName; + SetGlobal(Context.GlobalName, new SubQuery(insert)); } else Query.Append(insert); } + + public void UnlessConflictOn(LambdaExpression selector) + { + Query.Append($" unless conflict on {ExpressionTranslator.Translate(selector, Builder.QueryVariables)}"); + } + + public void ElseDefault() + { + Query.Append($" else (select {Context.CurrentType.GetEdgeDBTypeName()})"); + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs new file mode 100644 index 00000000..628e1862 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs @@ -0,0 +1,27 @@ +using EdgeDB.QueryNodes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + internal class NodeBuilder + { + public StringBuilder Query { get; } + public List Nodes { get; } + public NodeContext Context { get; } + public Dictionary QueryVariables { get; } = new(); + public Dictionary QueryGlobals { get; } + public bool IsAutoGenerated { get; init; } + + public NodeBuilder(NodeContext context, Dictionary globals, List? nodes = null) + { + Query = new(); + Nodes = nodes ?? new(); + Context = context; + QueryGlobals = globals; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeContext.cs similarity index 62% rename from src/EdgeDB.Net.QueryBuilder/QueryContext.cs rename to src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeContext.cs index 8c2c1946..548ff4ff 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeContext.cs @@ -7,11 +7,13 @@ namespace EdgeDB { - internal class QueryContext + internal class NodeContext { + public bool SetAsGlobal { get; init; } + public string? GlobalName { get; init; } public Type CurrentType { get; init; } - public QueryContext(Type currentType) + public NodeContext(Type currentType) { CurrentType = currentType; } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs deleted file mode 100644 index da9a2dc4..00000000 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/OrderByNode.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.QueryNodes -{ - internal class OrderByNode : QueryNode - { - public override bool IsRootNode => false; - public OrderByNode(QueryBuilder builder) : base(builder) { } - - public override void FinalizeQuery() - { - // check if previous node was a order by, if so change to 'then' - if (Builder.Nodes.FirstOrDefault() is OrderByNode) - { - Query.Remove(0, 8); - Query.Insert(0, "then"); - } - } - - public override void Visit() - { - var expression = ExpressionTranslator.Translate(Context.Expression!); - var direction = Context.Direction == OrderByDirection.Ascending ? "asc" : "desc"; - Query.Append($"order by {expression} {direction}"); - - if (Context.EmptyPlacement.HasValue) - { - Query.Append($" empty {Context.EmptyPlacement.Value.ToString().ToLowerInvariant()}"); - } - } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index d5083b8b..f2bf7542 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -7,9 +7,9 @@ namespace EdgeDB.QueryNodes { internal abstract class QueryNode : QueryNode - where TContext : QueryContext + where TContext : NodeContext { - protected QueryNode(QueryBuilder builder) : base(builder) { } + protected QueryNode(NodeBuilder builder) : base(builder) { } protected TContext Context => (TContext)Builder.Context; @@ -17,15 +17,13 @@ protected TContext Context internal abstract class QueryNode { - protected readonly QueryBuilder Builder; - - public virtual QueryExpressionType? ValidChildren { get; } - public abstract bool IsRootNode { get; } - + public bool IsAutoGenerated + => Builder.IsAutoGenerated; + protected readonly NodeBuilder Builder; protected StringBuilder Query => Builder.Query; - public QueryNode(QueryBuilder builder) + public QueryNode(NodeBuilder builder) { Builder = builder; } @@ -40,20 +38,18 @@ protected void SetGlobal(string name, object? value) => Builder.QueryGlobals[name] = value; internal BuiltQueryNode Build() - => new BuiltQueryNode(Query.ToString(), Builder.QueryVariables, Builder.QueryGlobals); + => new(Query.ToString(), Builder.QueryVariables); } internal class BuiltQueryNode { public string Query { get; init; } public IDictionary Parameters { get; init; } - public IDictionary Globals { get; init; } - public BuiltQueryNode(string query, IDictionary parameters, IDictionary globals) + public BuiltQueryNode(string query, IDictionary parameters) { Query = query; Parameters = parameters; - Globals = globals; } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 6734967c..b9a8b75d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -1,7 +1,9 @@ -using EdgeDB.Serializer; +using EdgeDB.Interfaces.Queries; +using EdgeDB.Serializer; using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -10,12 +12,7 @@ namespace EdgeDB.QueryNodes { internal class SelectNode : QueryNode { - public override bool IsRootNode => true; - - public override QueryExpressionType? ValidChildren - => QueryExpressionType.Filter | QueryExpressionType.OrderBy | QueryExpressionType.Offset | QueryExpressionType.Limit; - - public SelectNode(QueryBuilder builder) : base(builder) { } + public SelectNode(NodeBuilder builder) : base(builder) { } protected virtual string GetShape() { @@ -32,13 +29,33 @@ public override void Visit() Query.Append($"select {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()} {shape}"); } - public override void FinalizeQuery() + public void Filter(LambdaExpression expression) + { + if (expression is null) + throw new ArgumentNullException(nameof(expression), "No expression was passed in for a filter node"); + + var parsedExpression = ExpressionTranslator.Translate(expression, Builder.QueryVariables); + Query.Append($" filter {parsedExpression}"); + } + + public void OrderBy(bool asc, LambdaExpression selector, OrderByNullPlacement? nullPlacement) + { + if (selector is null) + throw new ArgumentNullException(nameof(selector), "No expression was passed in for an order by node"); + + var parsedExpression = ExpressionTranslator.Translate(selector, Builder.QueryVariables); + var direction = asc ? "asc" : "desc"; + Query.Append($" order by {parsedExpression} {direction}{(nullPlacement.HasValue ? $" {nullPlacement.Value.ToString().ToLowerInvariant()}" : "")}"); + } + + internal void Offset(long offset) + { + Query.Append($" offset {offset}"); + } + + internal void Limit(long limit) { - // check if theres a child select with the same type - if(Builder.Nodes.Any(x => x is SelectNode select && select.Context.CurrentType == Context.CurrentType)) - { - Query.Insert(7, "detached "); - } + Query.Append($" limit {limit}"); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UnlessConflictOnNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UnlessConflictOnNode.cs deleted file mode 100644 index e4b0f152..00000000 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UnlessConflictOnNode.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.QueryNodes -{ - internal class UnlessConflictOnNode : QueryNode - { - public override bool IsRootNode => false; - - public UnlessConflictOnNode(QueryBuilder builder) : base(builder) { } - - public override void Visit() - { - Query.Append($"unless conflict on {ExpressionTranslator.Translate(Context.Selector!)}"); - } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs new file mode 100644 index 00000000..aaede6fc --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class UpdateNode : QueryNode + { + public UpdateNode(NodeBuilder builder) : base(builder) { } + + public override void Visit() + { + Query.Append($"update {Context.UpdateName ?? Context.CurrentType.GetEdgeDBTypeName()}"); + } + + public override void FinalizeQuery() + { + var exp = ExpressionTranslator.Translate(Context.UpdateExpression!, Builder.QueryVariables); + Query.Append($" set {{ {exp} }}"); + + if (Context.SetAsGlobal && Context.GlobalName != null) + { + SetGlobal(Context.GlobalName, new SubQuery($"({Query})")); + Query.Clear(); + } + } + + public void Filter(LambdaExpression filter) + { + if (filter is null) + throw new ArgumentNullException(nameof(filter), "No expression was passed in for a filter node"); + + var parsedExpression = ExpressionTranslator.Translate(filter, Builder.QueryVariables); + Query.Append($" filter {parsedExpression}"); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs index 5c23a28e..a82c2945 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs @@ -8,13 +8,9 @@ namespace EdgeDB.QueryNodes { internal class WithNode : QueryNode { - public override bool IsRootNode => true; public bool HasVisited { get; private set; } - - public override QueryExpressionType? ValidChildren - => QueryExpressionType.Select | QueryExpressionType.Insert | QueryExpressionType.Update | QueryExpressionType.Delete; - - public WithNode(QueryBuilder builder) : base(builder) { } + + public WithNode(NodeBuilder builder) : base(builder) { } public override void Visit() { @@ -23,7 +19,24 @@ public override void Visit() if (Context.Values is null || !Context.Values.Any()) return; - Query.Append($"with {string.Join(", ", Context.Values.Select(x => $"{x.Key} := {QueryUtils.ParseObject(x.Value)}"))}"); + List values = new(); + + foreach(var kvp in Context.Values) + { + var value = kvp.Value; + + if (value is IQueryBuilder queryBuilder) + { + var subQuery = queryBuilder.Build(); + value = new SubQuery($"({subQuery.Query})"); + foreach (var variable in subQuery.Parameters) + SetVariable(variable.Key, variable.Value); + } + + values.Add($"{kvp.Key} := {QueryUtils.ParseObject(value)}"); + } + + Query.Append($"with {string.Join(", ", values)}"); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils.cs b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs similarity index 79% rename from src/EdgeDB.Net.QueryBuilder/Utils.cs rename to src/EdgeDB.Net.QueryBuilder/QueryUtils.cs index b7a91b0e..ce680f23 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs @@ -9,6 +9,9 @@ namespace EdgeDB { internal class QueryUtils { + private const string VARIABLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static readonly Random _rng = new(); + internal static string ParseObject(object? obj) { if (obj is null) @@ -35,5 +38,11 @@ internal static string ParseObject(object? obj) _ => obj.ToString()! }; } + + public static string GenerateRandomVariableName() + { + return new string(Enumerable.Repeat(VARIABLE_CHARS, 12).Select(x => x[_rng.Next(x.Length)]).ToArray()); + } + } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableArgCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableArgCollection.cs deleted file mode 100644 index fd2f2a21..00000000 --- a/src/EdgeDB.Net.QueryBuilder/QueryableArgCollection.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB -{ - internal class QueryableArgCollection : QueryableCollection - where TTuple : ITuple - { - private readonly TTuple _tuple; - - internal QueryableArgCollection(IEdgeDBQueryable edgedb, TTuple tuple) : base(edgedb) - { - _tuple = tuple; - } - - - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.EdgeQLFunctions.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.EdgeQLFunctions.cs deleted file mode 100644 index 868f965f..00000000 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.EdgeQLFunctions.cs +++ /dev/null @@ -1,78 +0,0 @@ -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 partial class QueryableCollection - { - public QueryableCollection Select(object? shape = null) - { - AddNode(new SelectContext(typeof(TQueryResult)) - { - Shape = shape - }); - return this; - } - - public QueryableCollection Filter(Expression> condition) - { - if (CurrentRootNode == null) - Select(); - - AddNode(new FilterContext(typeof(TQueryResult)) - { - Expression = condition - }); - - return this; - } - - public QueryableCollection Insert(TQueryResult value, bool returnValue = true) - { - var context = new InsertContext(typeof(TQueryResult)) - { - Value = value, - StoreAsGlobal = returnValue - }; - - AddNode(context); - - // fix this: select isn't valid after insert which is true but the insert - // should cause a with block to be added instead. - if (returnValue) - { - AddNode(new SelectContext(typeof(TQueryResult)) - { - SelectName = context.GlobalName - }, false); - } - - return this; - } - - public QueryableCollection UnlessConflictOn(Expression> selector) - { - AddNode(new UnlessConflictOnContext(typeof(TQueryResult)) - { - Selector = selector - }); - - return this; - } - - public Task> ExecuteAsync(CancellationToken token = default) - { - var builtQuery = Build(); - - // clear the current query - _nodes.Clear(); - - return _edgedb.QueryAsync(builtQuery.Query, builtQuery.Parameters, token); - } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs index 17b0b6db..8089520d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -1,4 +1,5 @@ -using EdgeDB.QueryNodes; +using EdgeDB.Interfaces.Queries; +using EdgeDB.QueryNodes; using System; using System.Collections.Generic; using System.Linq; @@ -9,144 +10,13 @@ namespace EdgeDB { - public partial class QueryableCollection + public sealed class QueryableCollection : QueryBuilder { private readonly IEdgeDBQueryable _edgedb; - private readonly List> _nodes; - private List? CurrentNodeGroup => _nodes.LastOrDefault(); - private QueryNode? CurrentRootNode => CurrentNodeGroup?.FirstOrDefault(x => x.IsRootNode); - - private static readonly Dictionary _nodeTypes = new() - { - {typeof(SelectNode), QueryExpressionType.Select }, - {typeof(FilterNode), QueryExpressionType.Filter }, - {typeof(OrderByNode), QueryExpressionType.OrderBy }, - {typeof(InsertNode), QueryExpressionType.Insert }, - {typeof(WithNode), QueryExpressionType.With }, - {typeof(UnlessConflictOnNode), QueryExpressionType.UnlessConflictOn } - }; - - static QueryableCollection() - { - QueryObjectManager.Initialize(); - } internal QueryableCollection(IEdgeDBQueryable edgedb) { _edgedb = edgedb; - _nodes = new(); - } - - private void AddNode(QueryContext context, bool validate = true) - where TNode : QueryNode - { - if(validate) - ValidateNode(); - - // create the node and a builder - var builder = new QueryBuilder(context, CurrentNodeGroup); - var node = (QueryNode)Activator.CreateInstance(typeof(TNode), builder)!; - - // visit the node - node.Visit(); - - // create a new group if its a root node - if (node.IsRootNode) - _nodes.Add(new List() { node }); - else if (CurrentNodeGroup != null) - CurrentNodeGroup.Add(node); - else - throw new Exception("No node group found! this is a bug"); - } - - private void ValidateNode() - where TNode : QueryNode - { - if (!_nodeTypes.TryGetValue(typeof(TNode), out var type)) - throw new ArgumentException($"No matching node found for {typeof(TNode).Name}"); - - if (CurrentRootNode == null) - return; - - if ((CurrentRootNode.ValidChildren & type) == 0) - throw new ArgumentException($"{type} cannot follow a {CurrentRootNode}"); } - - public (string Query, IDictionary Parameters) Build() - { - List query = new(); - List> parameters = new(); - Dictionary globals = new(); - - var nodes = _nodes; - - // extract a with block or create one - var withBlock = nodes.FirstOrDefault()?.FirstOrDefault(); - - if(withBlock is not null and not WithNode) - { - var context = new WithContext(typeof(TQueryResult)) - { - Values = globals - }; - nodes = nodes.Prepend(new List() { new WithNode(new QueryBuilder(context)) }).ToList(); - } - - for (int i = nodes.Count - 1; i >= 0; i--) - { - var subNodes = nodes[i]; - for (int j = subNodes.Count - 1; j >= 0; j--) - { - var node = subNodes[j]; - - if(node is WithNode withNode) - { - if (!withNode.HasVisited) - withNode.Visit(); - } - - node.FinalizeQuery(); - - var result = node.Build(); - - if(!string.IsNullOrEmpty(result.Query)) - query.Add(result.Query); - - parameters.Add(result.Parameters); - - foreach (var global in result.Globals) - globals[global.Key] = global.Value; - } - } - - query.Reverse(); - - - - return (string.Join(' ', query), parameters.SelectMany(x => x).DistinctBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value)); - } - } - - [Flags] - public enum QueryExpressionType - { - Start = 0, - Select = 1 << 0, - Insert = 1 << 1, - Update = 1 << 2, - Delete = 1 << 3, - With = 1 << 4, - For = 1 << 5, - Filter = 1 << 6, - OrderBy = 1 << 7, - Offset = 1 << 8, - Limit = 1 << 9, - Set = 1 << 10, - Transaction = 1 << 11, - Union = 1 << 12, - UnlessConflictOn = 1 << 13, - Rollback = 1 << 14, - Commit = 1 << 15, - Else = 1 << 16, } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index 57424e96..4da802eb 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -11,12 +11,21 @@ internal class ExpressionContext { public LambdaExpression RootExpression { get; } public Dictionary Parameters { get; } + private readonly IDictionary _queryObjects; - public ExpressionContext(LambdaExpression rootExpression) + public ExpressionContext(LambdaExpression rootExpression, IDictionary queryArguments) { RootExpression = rootExpression; + _queryObjects = queryArguments; Parameters = rootExpression.Parameters.ToDictionary(x => x.Name!, x => x.Type); } + + public string AddVariable(object? value) + { + var name = QueryUtils.GenerateRandomVariableName(); + _queryObjects[name] = value; + return name; + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs index 7fa79214..97b3adb2 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -54,9 +54,9 @@ protected static bool TryGetExpressionOperator(ExpressionType type, [MaybeNullWh public static string Translate(Expression expression) => Translate(expression); - public static string Translate(LambdaExpression expression) + public static string Translate(LambdaExpression expression, IDictionary queryArguments) { - var context = new ExpressionContext(expression); + var context = new ExpressionContext(expression, queryArguments); return TranslateExpression(expression.Body, context); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index a023087c..e1f675c4 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -11,6 +12,20 @@ internal class MemberExpressionTranslator : ExpressionTranslator field.GetValue(constant.Value), + PropertyInfo property => property.GetValue(constant.Value), + _ => throw new InvalidOperationException("Cannot resolve constant member expression") + }; + + var varName = context.AddVariable(value); + var type = PacketSerializer.GetEdgeQLType(expression.Type); + return $"<{type}>${varName}"; + } + return ParseMemberExpression(expression, expression.Expression is not ParameterExpression); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs new file mode 100644 index 00000000..adcd29f0 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Expressions +{ + internal class MemberInitExpressionTranslator : ExpressionTranslator + { + public override string Translate(MemberInitExpression expression, ExpressionContext context) + { + List initializations = new(); + + foreach(var binding in expression.Bindings) + { + switch (binding) + { + case MemberAssignment assignment: + { + var value = TranslateExpression(assignment.Expression, context); + initializations.Add($"{assignment.Member.GetEdgeDBPropertyName()} := {value}"); + } + break; + } + } + + return string.Join(", ", initializations); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index 81def945..7cd46ef1 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -28,8 +28,8 @@ public override string Translate(MethodCallExpression expression, ExpressionCont // check if its a known method if(EdgeQL.FunctionOperators.TryGetValue($"{expression.Method.DeclaringType?.Name}.{expression.Method.Name}", out edgeqlOperator)) { - var parsedArgs = expression.Arguments.Select(x => TranslateExpression(x, context)); - return edgeqlOperator.Build(parsedArgs); + var args = (expression.Object != null ? new string[] { TranslateExpression(expression.Object, context) } : Array.Empty()).Concat(expression.Arguments.Select(x => TranslateExpression(x, context))); + return edgeqlOperator.Build(args.ToArray()); } throw new Exception($"Couldn't find translator for {expression.Method.Name}"); diff --git a/tools/EdgeDB.DotnetTool/Schemas/ClassBuilder.cs b/tools/EdgeDB.DotnetTool/Schemas/ClassBuilder.cs index 81dfe3f7..4fdea87a 100644 --- a/tools/EdgeDB.DotnetTool/Schemas/ClassBuilder.cs +++ b/tools/EdgeDB.DotnetTool/Schemas/ClassBuilder.cs @@ -141,7 +141,7 @@ public void GenerateType(Type t, string dir, ClassBuilderContext context) { // do a reverse lookup on the root function to see if we can decipher the type computed = Regex.Replace(computed, @"^.+?::", _ => ""); - var returnType = QueryBuilder.ReverseLookupFunction(computed); + var returnType = typeof(string);//NodeBuilder.ReverseLookupFunction(computed); if (returnType != null) type = returnType.FullName; From 8cecb675d08ee93dc1dc9d6e4a1243b2c75fe769 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 26 Jun 2022 11:52:12 -0300 Subject: [PATCH 05/70] Alot more work on the query builder --- .../Examples/QueryBuilder.cs | 74 ++++-- .../SchemaTypeBuilders/ObjectBuilder.cs | 12 - .../SchemaTypeBuilders/TypeBuilder.cs | 47 +++- .../Interfaces/IMultiCardinalityExecutable.cs | 4 +- .../ISingleCardinalityExecutable.cs | 4 +- .../Interfaces/Queries/IDeleteQuery.cs | 5 + .../Interfaces/Queries/IInsertQuery.cs | 3 +- .../Interfaces/Queries/ISelectQuery.cs | 5 + .../Interfaces/Queries/IUpdateQuery.cs | 1 + .../InsertChildren/IUnlessConflictOn.cs | 6 +- .../Operators/Variables/LocalReference.cs | 20 ++ .../Operators/Variables/Reference.g.cs | 10 - .../Operators/Variables/VariableReference.cs | 21 ++ src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 240 +++++++++++++++--- src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 23 ++ .../QueryNodes/{ => Contexts}/NodeContext.cs | 0 .../QueryNodes/Contexts/SelectContext.cs | 4 +- .../QueryNodes/InsertNode.cs | 31 ++- .../QueryNodes/SelectNode.cs | 60 ++++- .../QueryNodes/UpdateNode.cs | 4 +- .../Expressions/BinaryExpressionTranslator.cs | 8 + .../ConstantExpressionTranslator.cs | 2 +- .../Expressions/ExpressionContext.cs | 16 +- .../Expressions/ExpressionTranslator.cs | 4 +- .../MethodCallExpressionTranslator.cs | 22 ++ .../Expressions/NewExpressionTranslator.cs | 56 ++++ .../operators.yml | 5 - 27 files changed, 573 insertions(+), 114 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/Operators/Variables/LocalReference.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/Operators/Variables/Reference.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Operators/Variables/VariableReference.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryContext.cs rename src/EdgeDB.Net.QueryBuilder/QueryNodes/{ => Contexts}/NodeContext.cs (100%) create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index b7807f3a..9a383c06 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -23,37 +24,58 @@ public class Person { public string? Name { get; set; } public string? Email { get; set; } - public Guid Id { get; set; } } public async Task ExecuteAsync(EdgeDBClient client) { - var collection = client.GetCollection(); - - collection - .With("john", new QueryBuilder().Select.Filter(x => x.Name == "John Smith")) - .Update(x => new Person + var builder = new QueryBuilder(); + + // get or create john + var john = await builder + .Insert(new LinkPerson { Email = "johndoe@email.com", Name = "John Doe" }) + .UnlessConflictOn(x => x.Email) + .ElseReturn() + .ExecuteAsync(client); + + // get or create jane + var jane = await builder + .With("john", john) + .Insert(ctx => new LinkPerson + { + Name = "Jane Doe", + Email = "janedoe@email.com", + BestFriend = ctx.Global("john") + }) + .UnlessConflictOn(x => x.Email) + .ElseReturn() + .ExecuteAsync(client); + + Logger!.LogInformation("John result: {@john}", john); + Logger!.LogInformation("Jane result: {@jane}", jane); + + // anon types + var anonPerson = await builder + .Select(ctx => new { - Email = "johnsmith@example.com", - }); - - var s = collection.Build(); - - //var result = await collection.Filter(x => EdgeQL.ILike(x.Name, "j%")).ExecuteAsync(); - - //var insertResult = collection.Insert(new LinkPerson - //{ - // Email = "email@example.com", - // Name = "ExampleName", - // BestFriend = new() - // { - // Email = "email2@example2.com", - // Name = "ExampleName2" - // } - //}).UnlessConflictOn(x => x.Email).Build(); - - //var pretty = Prettify(insertResult.Query); - + Name = ctx.Include(), + Email = ctx.Include(), + HasFriend = ctx.Local("BestFriend") != null + }) + .ExecuteAsync(client); + + var test = builder + .Insert(new LinkPerson() + { + Name = "upsert demo", + Email = "upsert@mail.com" + }) + .UnlessConflictOn(x => x.Email) + .Else(q => + q.Update(old => new LinkPerson + { + Name = old.Name!.ToUpper() + }) + ); } public static string Prettify(string queryText) diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs index ce881970..9aae5573 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs @@ -119,17 +119,5 @@ internal class ObjectBuilder } } } - - private static bool IsValidProperty(PropertyInfo type) - { - var shouldIgnore = type.GetCustomAttribute() is not null; - - return !shouldIgnore && type.GetSetMethod() is not null; - } - - private static bool IsValidTargetType(Type type) => - (type.IsClass || type.IsValueType) && - !type.IsSealed && - type.GetConstructor(Array.Empty()) != null; } } diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs index 463720f5..e7192a00 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Runtime.CompilerServices; namespace EdgeDB.Serializer { @@ -130,8 +131,8 @@ internal static bool IsValidObjectType(Type type) type.GetConstructor(new Type[] { typeof(IDictionary) }) ?.GetCustomAttribute() != null; - // allow abstract passthru - return type.IsAbstract ? true : (type.IsClass || type.IsValueType) && !type.IsSealed && validConstructor; + // allow abstract & anon types passthru + return type.IsAbstract || type.GetCustomAttribute() != null ? true : (type.IsClass || type.IsValueType) && !type.IsSealed && validConstructor; } internal static bool TryGetCollectionParser(Type type, out Func? builder) @@ -306,6 +307,48 @@ public void UpdateFactory(TypeDeserializerFactory factory) private TypeDeserializerFactory CreateDefaultFactory() { + // if type is anon type + if (_type.GetCustomAttribute() != null) + { + var props = _type.GetProperties(); + + return (data) => + { + object?[] ctorParams = new object[props.Length]; + + for (int i = 0; i != ctorParams.Length; i++) + { + var prop = props[i]; + + if(!data.TryGetValue(TypeBuilder.NamingStrategy.GetName(prop), out var value)) + { + ctorParams[i] = ReflectionUtils.GetDefault(prop.PropertyType); + } + + var valueType = value?.GetType(); + + if (valueType == null) + { + ctorParams[i] = ReflectionUtils.GetDefault(prop.PropertyType); + continue; + } + + if (valueType.IsAssignableTo(prop.PropertyType)) + { + ctorParams[i] = value; + } + else if (prop.PropertyType.IsEnum && value is string str) // enums + { + ctorParams[i] = Enum.Parse(prop.PropertyType, str); + } + else + throw new InvalidOperationException($"Cannot assign property {prop.Name} with type {valueType}"); + } + + return Activator.CreateInstance(_type, ctorParams)!; + }; + } + // if type has custom method builder if (_type.TryGetCustomBuilder(out var methodInfo)) { diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs index 97146730..d2c0b56e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs @@ -6,8 +6,8 @@ namespace EdgeDB.Interfaces { - public interface IMultiCardinalityExecutable + public interface IMultiCardinalityExecutable : IQueryBuilder { - Task> ExecuteAsync(IEdgeDBQueryable edgedb); + Task> ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token = default); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs index bcd475e2..ae103651 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs @@ -6,8 +6,8 @@ namespace EdgeDB.Interfaces { - public interface ISingleCardinalityExecutable + public interface ISingleCardinalityExecutable : IQueryBuilder { - Task ExecuteAsync(IEdgeDBQueryable edgedb); + Task ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token = default); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs index 8af38e29..a21177ff 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs @@ -10,9 +10,14 @@ namespace EdgeDB.Interfaces.Queries public interface IDeleteQuery : IMultiCardinalityExecutable { IDeleteQuery Filter(Expression> filter); + IDeleteQuery Filter(Expression> filter); IDeleteQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + IDeleteQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); IDeleteQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + IDeleteQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); IDeleteQuery Offset(long offset); + IDeleteQuery Offset(Expression> offset); IDeleteQuery Limit(long limit); + IDeleteQuery Limit(Expression> limit); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs index cdd8aa66..08b300da 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs @@ -9,6 +9,7 @@ namespace EdgeDB.Interfaces.Queries { public interface IInsertQuery : ISingleCardinalityExecutable { - IInsertQuery 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 063826af..42a85e7d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs @@ -10,9 +10,14 @@ namespace EdgeDB.Interfaces.Queries public interface ISelectQuery : IMultiCardinalityExecutable { ISelectQuery Filter(Expression> filter); + ISelectQuery Filter(Expression> filter); ISelectQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + ISelectQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); ISelectQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + ISelectQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); ISelectQuery Offset(long offset); + ISelectQuery Offset(Expression> offset); ISelectQuery Limit(long limit); + 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 2b1ba884..38295a48 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs @@ -10,5 +10,6 @@ namespace EdgeDB.Interfaces.Queries public interface IUpdateQuery : IMultiCardinalityExecutable { IMultiCardinalityExecutable Filter(Expression> filter); + IMultiCardinalityExecutable Filter(Expression> filter); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs index 6888ecae..f2e6254d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs @@ -9,6 +9,10 @@ namespace EdgeDB.Interfaces public interface IUnlessConflictOn : ISingleCardinalityExecutable { ISingleCardinalityExecutable ElseReturn(); - // TODO: Else expression + + IMultiCardinalityExecutable Else(Func, IMultiCardinalityExecutable> elseQuery); + ISingleCardinalityExecutable Else(Func, ISingleCardinalityExecutable> elseQuery); + IQueryBuilder Else(TQueryBuilder elseQuery) + where TQueryBuilder : IQueryBuilder; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Variables/LocalReference.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Variables/LocalReference.cs new file mode 100644 index 00000000..709ffe90 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Variables/LocalReference.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Operators +{ + internal class LocalReference : IEdgeQLOperator + { + public ExpressionType? Expression => null; + public string EdgeQLOperator => ".{0}"; + + public string Build(params object[] args) + { + throw new NotImplementedException("LocalReference does not have an implementation and should never be called"); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Variables/Reference.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Variables/Reference.g.cs deleted file mode 100644 index 323e282d..00000000 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Variables/Reference.g.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Linq.Expressions; - -namespace EdgeDB.Operators -{ - internal class VariablesReference : IEdgeQLOperator - { - public ExpressionType? Expression => null; - public string EdgeQLOperator => "{0}"; - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Variables/VariableReference.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Variables/VariableReference.cs new file mode 100644 index 00000000..b02700f2 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Variables/VariableReference.cs @@ -0,0 +1,21 @@ +using System.Linq.Expressions; + +namespace EdgeDB.Operators +{ + internal class VariablesReference : IEdgeQLOperator + { + public ExpressionType? Expression => null; + public string EdgeQLOperator => "{0}"; + + public string Build(params object[] args) + { + if (args.Length != 1) + throw new InvalidOperationException("Cannot use variable with more or less than one argument"); + + if (args[0] is not string str) + throw new InvalidOperationException($"Cannot use {args[0].GetType()} as an argument name"); + + return str[1..^1]; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 7d41ed56..c01691fa 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -10,13 +10,7 @@ namespace EdgeDB { - public class QueryBuilder : - IDeleteQuery, - IInsertQuery, - ISelectQuery, - IUpdateQuery, - IUnlessConflictOn, - IQueryBuilder + public class QueryBuilder : IQueryBuilder { private readonly List _nodes; private QueryNode? CurrentUserNode => _nodes.LastOrDefault(x => !x.IsAutoGenerated); @@ -33,6 +27,15 @@ internal QueryBuilder() _queryGlobals = new(); } + internal QueryBuilder(List nodes, Dictionary globals) + { + _nodes = nodes; + _queryGlobals = globals; + } + + private QueryBuilder EnterNewType() + => new(_nodes, _queryGlobals); + private TNode AddNode(NodeContext context, bool autoGenerated = false) where TNode : QueryNode { @@ -47,12 +50,6 @@ private TNode AddNode(NodeContext context, bool autoGenerated = false) // visit the node node.Visit(); - // if the node didn't generate a query assume its only modifying - // globals so we can skip adding it to our node tree - if (builder.Query.Length == 0) - return node; - - // create a new group if its a root node _nodes.Add(node); return node; @@ -104,14 +101,30 @@ private TNode AddNode(NodeContext context, bool autoGenerated = false) public QueryBuilder With(string name, object? value) { + if (QueryObjectManager.TryGetObjectId(value, out var id)) + value = new SubQuery($"(select {value!.GetType().GetEdgeDBTypeName()} filter .id = \"{id}\")"); + _queryGlobals[name] = value; return this; } #region Root nodes - public ISelectQuery Select - => SelectWithShape(null!); - public ISelectQuery SelectWithShape(Expression> shape) + public ISelectQuery Select() + { + AddNode(new SelectContext(typeof(TType))); + return this; + } + + public ISelectQuery Select(Expression>? shape = null) + { + AddNode(new SelectContext(typeof(TNewType)) + { + Shape = shape + }); + return EnterNewType(); + } + + public ISelectQuery Select(Expression> shape) { AddNode(new SelectContext(typeof(TType)) { @@ -120,12 +133,54 @@ public ISelectQuery SelectWithShape(Expression> shape) return this; } - public IInsertQuery Insert(TType value) + public ISelectQuery Select(Expression> shape) { + AddNode(new SelectContext(typeof(TType)) + { + Shape = shape + }); + return EnterNewType(); + } + + public IInsertQuery Insert(TType value, bool returnInsertedValue = true) + { + var selectedGlobal = returnInsertedValue ? QueryUtils.GenerateRandomVariableName() : null; AddNode(new InsertContext(typeof(TType)) { - Value = value + Value = value, + SetAsGlobal = returnInsertedValue, + GlobalName = selectedGlobal }); + + if (returnInsertedValue) + { + AddNode(new SelectContext(typeof(TType)) + { + SelectName = selectedGlobal, + }, true); + } + + return this; + } + + public IInsertQuery Insert(Expression> value, bool returnInsertedValue = true) + { + var selectedGlobal = returnInsertedValue ? QueryUtils.GenerateRandomVariableName() : null; + AddNode(new InsertContext(typeof(TType)) + { + Value = value, + SetAsGlobal = returnInsertedValue, + GlobalName = selectedGlobal + }); + + if (returnInsertedValue) + { + AddNode(new SelectContext(typeof(TType)) + { + SelectName = selectedGlobal, + }, true); + } + return this; } @@ -162,7 +217,7 @@ public IDeleteQuery Delete #endregion #region Generic sub-query methods - private QueryBuilder Filter(Expression> filter) + private QueryBuilder Filter(LambdaExpression filter) { switch (CurrentUserNode) { @@ -178,7 +233,7 @@ private QueryBuilder Filter(Expression> filter) return this; } - private QueryBuilder OrderBy(bool asc, Expression> selector, OrderByNullPlacement? placement) + private QueryBuilder OrderBy(bool asc, LambdaExpression selector, OrderByNullPlacement? placement) { if (CurrentUserNode is not SelectNode selectNode) throw new InvalidOperationException($"Cannot order by on a {CurrentUserNode}"); @@ -198,6 +253,16 @@ private QueryBuilder Offset(long offset) return this; } + private QueryBuilder OffsetExp(LambdaExpression offset) + { + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot offset on a {CurrentUserNode}"); + + selectNode.OffsetExpression(offset); + + return this; + } + private QueryBuilder Limit(long limit) { if (CurrentUserNode is not SelectNode selectNode) @@ -208,7 +273,17 @@ private QueryBuilder Limit(long limit) return this; } - private QueryBuilder UnlessConflictOn(Expression> selector) + private QueryBuilder LimitExp(LambdaExpression limit) + { + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot limit on a {CurrentUserNode}"); + + selectNode.LimitExpression(limit); + + return this; + } + + private QueryBuilder UnlessConflictOn(LambdaExpression selector) { if (CurrentUserNode is not InsertNode insertNode) throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); @@ -217,6 +292,51 @@ private QueryBuilder UnlessConflictOn(Expression> se return this; } + + private QueryBuilder ElseReturnDefault() + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot else return on a {CurrentUserNode}"); + + insertNode.ElseDefault(); + + return this; + } + + private IQueryBuilder ElseJoint(IQueryBuilder builder) + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); + + insertNode.Else(builder); + + return EnterNewType(); + } + + private QueryBuilder Else(Func, IMultiCardinalityExecutable> func) + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); + + var builder = new QueryBuilder(); + func(builder); + insertNode.Else(builder); + + return this; + } + + private QueryBuilder Else(Func, ISingleCardinalityExecutable> func) + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); + + var builder = new QueryBuilder(); + func(builder); + insertNode.Else(builder); + + return this; + } + #endregion #region ISelectQuery @@ -230,23 +350,41 @@ 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.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(false, propertySelector, nullPlacement); + ISelectQuery ISelectQuery.Offset(Expression> offset) + => OffsetExp(offset); + ISelectQuery ISelectQuery.Limit(Expression> limit) + => LimitExp(limit); #endregion #region IInsertQuery - IInsertQuery IInsertQuery.UnlessConflictOn(Expression> propertySelector) + IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) + => UnlessConflictOn(propertySelector); + IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) => UnlessConflictOn(propertySelector); #endregion #region IUpdateQuery IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) => Filter(filter); + IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) + => Filter(filter); #endregion #region IUnlessConflictOn ISingleCardinalityExecutable IUnlessConflictOn.ElseReturn() - { - throw new NotImplementedException(); - } + => ElseReturnDefault(); + IQueryBuilder IUnlessConflictOn.Else(TQueryBuilder elseQuery) + => ElseJoint(elseQuery); + IMultiCardinalityExecutable IUnlessConflictOn.Else(Func, IMultiCardinalityExecutable> elseQuery) + => Else(elseQuery); + ISingleCardinalityExecutable IUnlessConflictOn.Else(Func, ISingleCardinalityExecutable> elseQuery) + => Else(elseQuery); #endregion #region IDeleteQuery @@ -254,29 +392,63 @@ IDeleteQuery IDeleteQuery.Filter(Expression> fil => Filter(filter); IDeleteQuery IDeleteQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) => OrderBy(true, propertySelector, nullPlacement); - IDeleteQuery IDeleteQuery.OrderByDesending(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.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + => OrderBy(false, propertySelector, nullPlacement); + IDeleteQuery IDeleteQuery.Offset(Expression> offset) + => OffsetExp(offset); + IDeleteQuery IDeleteQuery.Limit(Expression> limit) + => LimitExp(limit); #endregion - Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb) + Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) { - throw new NotImplementedException(); + var (Query, Parameters) = Build(); + _nodes.Clear(); + _queryGlobals.Clear(); + return edgedb.QueryAsync(Query, Parameters, token); } - - Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb) + Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) { - throw new NotImplementedException(); + var (Query, Parameters) = Build(); + _nodes.Clear(); + _queryGlobals.Clear(); + return edgedb.QuerySingleAsync(Query, Parameters, token); } + + #region IQueryBuilder + IQueryBuilder IQueryBuilder.With(string name, object? value) => With(name, value); + #endregion } - internal interface IQueryBuilder + public interface IQueryBuilder : + IQueryBuilder, + ISelectQuery, + IUpdateQuery, + IDeleteQuery, + IUnlessConflictOn, + IInsertQuery + { + IQueryBuilder With(string name, object? value); + ISelectQuery Select(); + ISelectQuery Select(Expression>? shape = null); + ISelectQuery Select(Expression> shape); + ISelectQuery Select(Expression> shape); + IInsertQuery Insert(TType value, bool returnInsertedValue = true); + IInsertQuery Insert(Expression> value, bool returnInsertedValue = true); + IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true); + IDeleteQuery Delete { get; } + } + public interface IQueryBuilder { (string Query, IDictionary Parameters) Build(); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs new file mode 100644 index 00000000..4901423f --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -0,0 +1,23 @@ +using EdgeDB.Operators; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + public sealed class QueryContext + { + [EquivalentOperator(typeof(VariablesReference))] + public TType Global(string name) + => default!; + + [EquivalentOperator(typeof(LocalReference))] + public TType Local(string name) + => default!; + + public TType Include() + => default!; + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs similarity index 100% rename from src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeContext.cs rename to src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs index 0cb86804..69d7fb3b 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs @@ -9,8 +9,8 @@ namespace EdgeDB.QueryNodes { internal class SelectContext : NodeContext { - public Expression>? Shape { get; init; } - public string? SelectName { get; init; } + public LambdaExpression? Shape { get; init; } + public string? SelectName { get; set; } public SelectContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 93d8a7ab..81f3933b 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -13,6 +13,11 @@ internal class InsertNode : QueryNode { public InsertNode(NodeBuilder builder) : base(builder) { } + private string BuildInsertLambdaShape(LambdaExpression expression) + { + return $"{{ {ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context)} }}"; + } + private string BuildInsertShape(Type? shapeType = null, object? shapeValue = null) { List shape = new(); @@ -20,6 +25,9 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul var type = shapeType ?? Context.CurrentType; var value = shapeValue ?? Context.Value; + if (value is LambdaExpression expression) + return BuildInsertLambdaShape(expression); + var properties = type.GetProperties().Where(x => x.GetCustomAttribute() == null); foreach(var property in properties) @@ -74,24 +82,35 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul public override void Visit() { var shape = BuildInsertShape(); - var insert = $"insert {Context.CurrentType.GetEdgeDBTypeName()} {shape}"; + Query.Append($"insert {Context.CurrentType.GetEdgeDBTypeName()} {shape}"); + } - if (Context.SetAsGlobal && Context.GlobalName != null) + public override void FinalizeQuery() + { + if(Context.SetAsGlobal && Context.GlobalName != null) { - SetGlobal(Context.GlobalName, new SubQuery(insert)); + SetGlobal(Context.GlobalName, new SubQuery($"({Query})")); + Query.Clear(); } - else - Query.Append(insert); } public void UnlessConflictOn(LambdaExpression selector) { - Query.Append($" unless conflict on {ExpressionTranslator.Translate(selector, Builder.QueryVariables)}"); + Query.Append($" unless conflict on {ExpressionTranslator.Translate(selector, Builder.QueryVariables, Context)}"); } public void ElseDefault() { Query.Append($" else (select {Context.CurrentType.GetEdgeDBTypeName()})"); } + + public void Else(IQueryBuilder builder) + { + var result = builder.Build(); + + Query.Append($" else ({result.Query})"); + foreach (var variable in result.Parameters) + SetVariable(variable.Key, variable.Value); + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index b9a8b75d..052841c2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -12,17 +12,57 @@ namespace EdgeDB.QueryNodes { internal class SelectNode : QueryNode { + public const int MAX_DEPTH = 1; public SelectNode(NodeBuilder builder) : base(builder) { } - protected virtual string GetShape() + private string GetShape(Type type, int currentDepth = 0) { - var properties = Context.CurrentType.GetProperties().Where(x => x.GetCustomAttribute() == null); + var properties = type.GetProperties().Where(x => x.GetCustomAttribute() == null); - var propertyNames = properties.Select(x => x.GetCustomAttribute()?.Name ?? TypeBuilder.NamingStrategy.GetName(x)); + var propertyNames = properties.Select(x => + { + var name = x.GetCustomAttribute()?.Name ?? TypeBuilder.NamingStrategy.GetName(x); + if (TypeBuilder.IsValidObjectType(x.PropertyType)) + { + if(currentDepth < MAX_DEPTH) + return $"{name}: {GetShape(x.PropertyType, currentDepth + 1)}"; + return null; + } + else + { + return name; + } + }).Where(x => x != null); return $"{{ {string.Join(", ", propertyNames)} }}"; } + private string GetDefaultShape() + => GetShape(Context.CurrentType); + + private string GetShape() + { + if(Context.Shape == null) + { + return GetDefaultShape(); + } + + // if its a call to a global + if(Context.Shape.Body is MethodCallExpression) + { + var exp = ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context); + Context.SelectName = exp; + return GetDefaultShape(); + } + else if (Context.Shape.Body is NewExpression) + { + return ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context); + } + + return ""; + + } + public override void Visit() { var shape = GetShape(); @@ -34,7 +74,7 @@ public void Filter(LambdaExpression expression) if (expression is null) throw new ArgumentNullException(nameof(expression), "No expression was passed in for a filter node"); - var parsedExpression = ExpressionTranslator.Translate(expression, Builder.QueryVariables); + var parsedExpression = ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context); Query.Append($" filter {parsedExpression}"); } @@ -43,7 +83,7 @@ public void OrderBy(bool asc, LambdaExpression selector, OrderByNullPlacement? n if (selector is null) throw new ArgumentNullException(nameof(selector), "No expression was passed in for an order by node"); - var parsedExpression = ExpressionTranslator.Translate(selector, Builder.QueryVariables); + var parsedExpression = ExpressionTranslator.Translate(selector, Builder.QueryVariables, Context); var direction = asc ? "asc" : "desc"; Query.Append($" order by {parsedExpression} {direction}{(nullPlacement.HasValue ? $" {nullPlacement.Value.ToString().ToLowerInvariant()}" : "")}"); } @@ -53,9 +93,19 @@ internal void Offset(long offset) Query.Append($" offset {offset}"); } + internal void OffsetExpression(LambdaExpression exp) + { + Query.Append($" offset {exp}"); + } + internal void Limit(long limit) { Query.Append($" limit {limit}"); } + + internal void LimitExpression(LambdaExpression exp) + { + Query.Append($" limit {exp}"); + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs index aaede6fc..1f6aa3f8 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs @@ -18,7 +18,7 @@ public override void Visit() public override void FinalizeQuery() { - var exp = ExpressionTranslator.Translate(Context.UpdateExpression!, Builder.QueryVariables); + var exp = ExpressionTranslator.Translate(Context.UpdateExpression!, Builder.QueryVariables, Context); Query.Append($" set {{ {exp} }}"); if (Context.SetAsGlobal && Context.GlobalName != null) @@ -33,7 +33,7 @@ public void Filter(LambdaExpression filter) if (filter is null) throw new ArgumentNullException(nameof(filter), "No expression was passed in for a filter node"); - var parsedExpression = ExpressionTranslator.Translate(filter, Builder.QueryVariables); + var parsedExpression = ExpressionTranslator.Translate(filter, Builder.QueryVariables, Context); Query.Append($" filter {parsedExpression}"); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs index fa84cca9..71e3cbe9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs @@ -14,6 +14,14 @@ public override string Translate(BinaryExpression expression, ExpressionContext var left = TranslateExpression(expression.Left, context); var right = TranslateExpression(expression.Right, context); + // special case for exists keyword + if ((expression.Right is ConstantExpression rightConst && rightConst.Value is null || + expression.Left is ConstantExpression leftConst && leftConst.Value is null) && + expression.NodeType is ExpressionType.Equal or ExpressionType.NotEqual) + { + return $"{(expression.NodeType is ExpressionType.Equal ? "not exists" : "exists")} {(right == "{}" ? left : right)}"; + } + if (!TryGetExpressionOperator(expression.NodeType, out var op)) throw new NotSupportedException($"Failed to find operator for node type {expression.NodeType}"); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs index 3569a9b4..9f1299fd 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs @@ -11,7 +11,7 @@ internal class ConstantExpressionTranslator : ExpressionTranslator Parameters { get; } private readonly IDictionary _queryObjects; - public ExpressionContext(LambdaExpression rootExpression, IDictionary queryArguments) + public bool IsTypeReference { get; set; } + public Type? LocalScope { get; set; } + + + public ExpressionContext(NodeContext context, LambdaExpression rootExpression, IDictionary queryArguments) { RootExpression = rootExpression; _queryObjects = queryArguments; + NodeContext = context; Parameters = rootExpression.Parameters.ToDictionary(x => x.Name!, x => x.Type); } @@ -27,5 +34,12 @@ public string AddVariable(object? value) _queryObjects[name] = value; return name; } + + public ExpressionContext Enter(Action func) + { + 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 97b3adb2..fb889519 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -54,9 +54,9 @@ protected static bool TryGetExpressionOperator(ExpressionType type, [MaybeNullWh public static string Translate(Expression expression) => Translate(expression); - public static string Translate(LambdaExpression expression, IDictionary queryArguments) + public static string Translate(LambdaExpression expression, IDictionary queryArguments, NodeContext nodeContext) { - var context = new ExpressionContext(expression, queryArguments); + var context = new ExpressionContext(nodeContext, expression, queryArguments); return TranslateExpression(expression.Body, context); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index 7cd46ef1..502aea58 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -13,6 +13,28 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator x.IsTypeReference = true)); + var rawPath = rawArg.Split('.'); + string[] parsedPath = new string[rawPath.Length]; + + for(int i = 0; i != rawPath.Length; i++) + { + var prop = (MemberInfo?)context.LocalScope?.GetProperty(rawPath[i]) ?? + context.LocalScope?.GetField(rawPath[i]) ?? + (MemberInfo?)context.NodeContext.CurrentType.GetProperty(rawPath[i]) ?? + context.NodeContext.CurrentType.GetField(rawPath[i]); + if (prop is null) + throw new InvalidOperationException($"The property \"{rawPath[i]}\" within \"{rawArg}\" is out of scope"); + parsedPath[i] = prop.GetEdgeDBPropertyName(); + } + + return $".{string.Join('.', parsedPath)}"; + } + // check if the method has an 'EquivalentOperator' attribute var edgeqlOperator = expression.Method.GetCustomAttribute()?.Operator; diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs new file mode 100644 index 00000000..edffae35 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs @@ -0,0 +1,56 @@ +using EdgeDB.Operators; +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 +{ + internal class NewExpressionTranslator : ExpressionTranslator + { + public override string Translate(NewExpression expression, ExpressionContext context) + { + string[] shape = new string[expression.Arguments.Count]; + + for(int i = 0; i != expression.Arguments.Count; i++) + { + var member = expression.Members![i]; + var arg = expression.Arguments[i]; + var edgedbName = member.GetEdgeDBPropertyName(); + + if(arg is MethodCallExpression mcex && mcex.Method.DeclaringType == typeof(QueryContext) && mcex.Method.Name == "Include") + { + shape[i] = edgedbName; + continue; + } + + var type = member switch + { + PropertyInfo property => property.PropertyType, + FieldInfo field => field.FieldType, + _ => throw new NotSupportedException($"Cannot use {member} in anonomous shape selects") + }; + + string? value = null; + bool isSetter = true; + // local reference, verify in the anon obj + if (arg is MethodCallExpression mcall) + { + value = TranslateExpression(mcall, context.Enter(x => x.LocalScope = expression.Type)); + } + else + { + isSetter = context.NodeContext.CurrentType.GetProperty(member.Name) == null; + value = TranslateExpression(expression.Arguments[i], context.Enter(x => x.LocalScope = expression.Type)); + } + + shape[i] = $"{edgedbName}{(isSetter ? " :=" : "")} {value}"; + } + + return $"{{ {string.Join(", ", shape)} }}"; + } + } +} diff --git a/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml b/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml index 668cd2b0..dfc8baeb 100644 --- a/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml +++ b/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml @@ -1471,11 +1471,6 @@ links: - TType element filter: "where TSource : IEnumerable?" -variables: - - operator: "{0}" - name: Reference - - enums: - name: DurationTruncateUnit serialize_method: Lower From 578a74b399d28418b938178ac87920ea0c43cd7b Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 27 Jun 2022 12:04:46 -0300 Subject: [PATCH 06/70] janky sub queries & schema introspection --- dbschema/lexertest.esdl | 146 ++++++++++++++++++ dbschema/migrations/00010.edgeql | 122 +++++++++++++++ .../Examples/QueryBuilder.cs | 59 +------ src/EdgeDB.Net.Driver/ClientPacketDuplexer.cs | 2 +- .../SchemaTypeBuilders/TypeBuilder.cs | 39 +++-- .../EdgeDB.Net.QueryBuilder.csproj | 15 ++ .../Interfaces/Queries/IInsertQuery.cs | 1 + .../InsertChildren/IUnlessConflictOn.cs | 1 - .../Properties/Resources.Designer.cs | 84 ++++++++++ .../Properties/Resources.resx | 138 +++++++++++++++++ src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 127 +++++++++++++-- .../QueryNodes/Contexts/NodeContext.cs | 2 +- .../QueryNodes/InsertNode.cs | 66 +++++++- .../QueryNodes/QueryNode.cs | 28 +++- .../QueryNodes/WithNode.cs | 10 +- .../Schema/DataTypes/ObjectType.cs | 20 +++ .../Schema/DataTypes/Property.cs | 28 ++++ .../Schema/SchemaInfo.cs | 23 +++ .../Schema/SchemaIntrospector.cs | 30 ++++ src/EdgeDB.Net.QueryBuilder/introspect.edgeql | 16 ++ 20 files changed, 863 insertions(+), 94 deletions(-) create mode 100644 dbschema/lexertest.esdl create mode 100644 dbschema/migrations/00010.edgeql create mode 100644 src/EdgeDB.Net.QueryBuilder/Properties/Resources.Designer.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Properties/Resources.resx create mode 100644 src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/introspect.edgeql diff --git a/dbschema/lexertest.esdl b/dbschema/lexertest.esdl new file mode 100644 index 00000000..ba52c6d3 --- /dev/null +++ b/dbschema/lexertest.esdl @@ -0,0 +1,146 @@ +module lexertest { + + scalar type Genre extending enum; + + abstract link movie_character { + property character_name -> str; + } + + abstract type Person { + required property name -> str { + constraint exclusive; + }; + } + + type Villain extending Person { + link nemesis -> Hero; + } + + type Hero extending Person { + property secret_identity -> str; + property number_of_movies -> int64; + multi link villains := . Genre; + property rating -> float64; + required property title -> str { + constraint exclusive; + }; + required property release_year -> int16 { + default := datetime_get(datetime_current(), 'year'); + } + multi link characters extending movie_character -> Person; + link profile -> Profile { + constraint exclusive; + } + } + + type Profile { + property plot_summary -> str; + property slug -> str { + readonly := true; + } + } + + type User { + required property username -> str; + required multi link favourite_movies -> Movie; + } + + type MovieShape { + } + + abstract type HasName { + property name -> str; + } + abstract type HasAge { + property age -> int64; + } + + scalar type bag_seq extending sequence; + + type Bag extending HasName, HasAge { + property secret_identity -> str; + property genre -> Genre; + property boolField -> bool; + property datetimeField -> datetime; + property localDateField -> cal::local_date; + property localTimeField -> cal::local_time; + property localDateTimeField -> cal::local_datetime; + property durationField -> duration; + property decimalField -> decimal; + property int64Field -> int64; + property int32Field -> int32; + property int16Field -> int16; + property float32Field -> float32; + property float64Field -> float64; + property bigintField -> bigint; + required multi property stringsMulti -> str; + property stringsArr -> array; + multi property stringMultiArr -> array; + property namedTuple -> tuple; + property unnamedTuple -> tuple; + property enumArr -> array; + property seqField -> bag_seq; + property jsonField -> json; + } + + type Simple extending HasName, HasAge {} + + # Unicode handling + # https://github.com/edgedb/edgedb/blob/master/tests/schemas/dump02_default.esdl + + abstract annotation `🍿`; + + abstract constraint `🚀🍿`(max: int64) extending max_len_value; + + function `💯`(NAMED ONLY `🙀`: int64) -> int64 { + using ( + SELECT 100 - `🙀` + ); + + annotation `🍿` := 'fun!🚀'; + volatility := 'Immutable'; + } + + type `S p a M` { + required property `🚀` -> int32; + property c100 := (SELECT `💯`(`🙀` := .`🚀`)); + } + + type A { + required link `s p A m 🤞` -> `S p a M`; + } + + scalar type 你好 extending str; + + scalar type مرحبا extending 你好 { + constraint `🚀🍿`(100); + }; + + scalar type `🚀🚀🚀` extending مرحبا; + + type Łukasz { + required property `Ł🤞` -> `🚀🚀🚀` { + default := <`🚀🚀🚀`>'你好🤞' + } + index on (.`Ł🤞`); + + link `Ł💯` -> A { + property `🙀🚀🚀🚀🙀` -> `🚀🚀🚀`; + property `🙀مرحبا🙀` -> مرحبا { + constraint `🚀🍿`(200); + } + }; + } + +}; + +module `💯💯💯` { + function `🚀🙀🚀`(`🤞`: lexertest::`🚀🚀🚀`) -> lexertest::`🚀🚀🚀` + using ( + SELECT (`🤞` ++ 'Ł🙀') + ); +}; \ No newline at end of file diff --git a/dbschema/migrations/00010.edgeql b/dbschema/migrations/00010.edgeql new file mode 100644 index 00000000..1d045f57 --- /dev/null +++ b/dbschema/migrations/00010.edgeql @@ -0,0 +1,122 @@ +CREATE MIGRATION m1hk5lddsbraets3p3xtsj2rumcpydkdpbyoy3ssh3vqmudexna3ya + ONTO m1ckoojrq2xwxscsdfrsrrc3rem4afqpvjit65vawiavkhsfaigzna +{ + CREATE MODULE lexertest IF NOT EXISTS; + CREATE MODULE `💯💯💯` IF NOT EXISTS; + CREATE ABSTRACT ANNOTATION lexertest::`🍿`; + CREATE FUNCTION lexertest::`💯`(NAMED ONLY `🙀`: std::int64) -> std::int64 { + SET volatility := 'Immutable'; + CREATE ANNOTATION lexertest::`🍿` := 'fun!🚀'; + USING (SELECT + (100 - `🙀`) + ) + ;}; + CREATE SCALAR TYPE lexertest::Genre EXTENDING enum; + CREATE ABSTRACT TYPE lexertest::HasAge { + CREATE PROPERTY age -> std::int64; + }; + CREATE ABSTRACT TYPE lexertest::HasName { + CREATE PROPERTY name -> std::str; + }; + CREATE SCALAR TYPE lexertest::bag_seq EXTENDING std::sequence; + CREATE TYPE lexertest::Bag EXTENDING lexertest::HasName, lexertest::HasAge { + CREATE PROPERTY enumArr -> array; + CREATE PROPERTY bigintField -> std::bigint; + CREATE PROPERTY boolField -> std::bool; + CREATE PROPERTY datetimeField -> std::datetime; + CREATE PROPERTY decimalField -> std::decimal; + CREATE PROPERTY durationField -> std::duration; + CREATE PROPERTY float32Field -> std::float32; + CREATE PROPERTY float64Field -> std::float64; + CREATE PROPERTY genre -> lexertest::Genre; + CREATE PROPERTY int16Field -> std::int16; + CREATE PROPERTY int32Field -> std::int32; + CREATE PROPERTY int64Field -> std::int64; + CREATE PROPERTY jsonField -> std::json; + CREATE PROPERTY localDateField -> cal::local_date; + CREATE PROPERTY localDateTimeField -> cal::local_datetime; + CREATE PROPERTY localTimeField -> cal::local_time; + CREATE PROPERTY namedTuple -> tuple; + CREATE PROPERTY secret_identity -> std::str; + CREATE PROPERTY seqField -> lexertest::bag_seq; + CREATE MULTI PROPERTY stringMultiArr -> array; + CREATE PROPERTY stringsArr -> array; + CREATE REQUIRED MULTI PROPERTY stringsMulti -> std::str; + CREATE PROPERTY unnamedTuple -> tuple; + }; + CREATE ABSTRACT CONSTRAINT lexertest::`🚀🍿`(max: std::int64) EXTENDING std::max_len_value; + CREATE TYPE lexertest::A; + CREATE SCALAR TYPE lexertest::你好 EXTENDING std::str; + CREATE SCALAR TYPE lexertest::مرحبا EXTENDING lexertest::你好 { + CREATE CONSTRAINT lexertest::`🚀🍿`(100); + }; + CREATE SCALAR TYPE lexertest::`🚀🚀🚀` EXTENDING lexertest::مرحبا; + CREATE TYPE lexertest::Łukasz { + CREATE LINK `Ł💯` -> lexertest::A { + CREATE PROPERTY `🙀مرحبا🙀` -> lexertest::مرحبا { + CREATE CONSTRAINT lexertest::`🚀🍿`(200); + }; + CREATE PROPERTY `🙀🚀🚀🚀🙀` -> lexertest::`🚀🚀🚀`; + }; + CREATE REQUIRED PROPERTY `Ł🤞` -> lexertest::`🚀🚀🚀` { + SET default := ('你好🤞'); + }; + CREATE INDEX ON (.`Ł🤞`); + }; + CREATE TYPE lexertest::`S p a M` { + CREATE REQUIRED PROPERTY `🚀` -> std::int32; + CREATE PROPERTY c100 := (SELECT + lexertest::`💯`(`🙀` := .`🚀`) + ); + }; + CREATE FUNCTION `💯💯💯`::`🚀🙀🚀`(`🤞`: lexertest::`🚀🚀🚀`) -> lexertest::`🚀🚀🚀` USING (SELECT + (`🤞` ++ 'Ł🙀') + ); + CREATE ABSTRACT LINK lexertest::movie_character { + CREATE PROPERTY character_name -> std::str; + }; + CREATE ABSTRACT TYPE lexertest::Person { + CREATE REQUIRED PROPERTY name -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + }; + CREATE TYPE lexertest::Profile { + CREATE PROPERTY plot_summary -> std::str; + CREATE PROPERTY slug -> std::str { + SET readonly := true; + }; + }; + CREATE TYPE lexertest::Movie { + CREATE MULTI LINK characters EXTENDING lexertest::movie_character -> lexertest::Person; + CREATE LINK profile -> lexertest::Profile { + CREATE CONSTRAINT std::exclusive; + }; + CREATE PROPERTY genre -> lexertest::Genre; + CREATE PROPERTY rating -> std::float64; + CREATE REQUIRED PROPERTY release_year -> std::int16 { + SET default := (std::datetime_get(std::datetime_current(), 'year')); + }; + CREATE REQUIRED PROPERTY title -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + }; + ALTER TYPE lexertest::A { + CREATE REQUIRED LINK `s p A m 🤞` -> lexertest::`S p a M`; + }; + CREATE TYPE lexertest::Simple EXTENDING lexertest::HasName, lexertest::HasAge; + CREATE TYPE lexertest::Hero EXTENDING lexertest::Person { + CREATE PROPERTY number_of_movies -> std::int64; + CREATE PROPERTY secret_identity -> std::str; + }; + CREATE TYPE lexertest::Villain EXTENDING lexertest::Person { + CREATE LINK nemesis -> lexertest::Hero; + }; + ALTER TYPE lexertest::Hero { + CREATE MULTI LINK villains := (. lexertest::Movie; + CREATE REQUIRED PROPERTY username -> std::str; + }; + CREATE TYPE lexertest::MovieShape; +}; diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 9a383c06..acb5a54c 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.Logging; +using EdgeDB.Schema; +using EdgeDB.Serializer; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -33,7 +35,7 @@ public async Task ExecuteAsync(EdgeDBClient client) // get or create john var john = await builder .Insert(new LinkPerson { Email = "johndoe@email.com", Name = "John Doe" }) - .UnlessConflictOn(x => x.Email) + .UnlessConflict() .ElseReturn() .ExecuteAsync(client); @@ -46,7 +48,7 @@ public async Task ExecuteAsync(EdgeDBClient client) Email = "janedoe@email.com", BestFriend = ctx.Global("john") }) - .UnlessConflictOn(x => x.Email) + .UnlessConflict() .ElseReturn() .ExecuteAsync(client); @@ -69,58 +71,13 @@ public async Task ExecuteAsync(EdgeDBClient client) Name = "upsert demo", Email = "upsert@mail.com" }) - .UnlessConflictOn(x => x.Email) + .UnlessConflict() .Else(q => q.Update(old => new LinkPerson { - Name = old.Name!.ToUpper() + Name = old!.Name!.ToUpper() }) - ); - } - - public static string Prettify(string queryText) - { - // add newlines - var result = Regex.Replace(queryText, @"({|\(|\)|}|,)", m => - { - switch (m.Groups[1].Value) - { - case "{" or "(" or ",": - if (m.Groups[1].Value == "{" && queryText[m.Index + 1] == '}') - return m.Groups[1].Value; - - return $"{m.Groups[1].Value}\n"; - - default: - return $"{((m.Groups[1].Value == "}" && (queryText[m.Index - 1] == '{' || queryText[m.Index - 1] == '}')) ? "" : "\n")}{m.Groups[1].Value}{((queryText.Length != m.Index + 1 && (queryText[m.Index + 1] != ',')) ? "\n" : "")}"; - } - }).Trim().Replace("\n ", "\n"); - - // clean up newline func - result = Regex.Replace(result, "\n\n", m => "\n"); - - // add indentation - result = Regex.Replace(result, "^", m => - { - int indent = 0; - - foreach (var c in result[..m.Index]) - { - if (c is '(' or '{') - indent++; - if (c is ')' or '}') - indent--; - } - - var next = result.Length != m.Index ? result[m.Index] : '\0'; - - if (next is '}' or ')') - indent--; - - return "".PadLeft(indent * 2); - }, RegexOptions.Multiline); - - return result; + ).Build().Prettify(); } } } diff --git a/src/EdgeDB.Net.Driver/ClientPacketDuplexer.cs b/src/EdgeDB.Net.Driver/ClientPacketDuplexer.cs index 6c46d526..971780d8 100644 --- a/src/EdgeDB.Net.Driver/ClientPacketDuplexer.cs +++ b/src/EdgeDB.Net.Driver/ClientPacketDuplexer.cs @@ -239,7 +239,7 @@ public async Task NextAsync(Predicate? predicate = n } }; - var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(token, _disconnectTokenSource.Token, GetTimeoutToken()); + var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(token, _disconnectTokenSource.Token); linkedToken.Token.Register(() => tcs.TrySetCanceled(linkedToken.Token)); diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs index e7192a00..9541d69d 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs @@ -27,8 +27,6 @@ public static class TypeBuilder private readonly static ConcurrentDictionary _typeInfo = new(); private readonly static List _scannedAssemblies; - - static TypeBuilder() { @@ -132,7 +130,7 @@ internal static bool IsValidObjectType(Type type) ?.GetCustomAttribute() != null; // allow abstract & anon types passthru - return type.IsAbstract || type.GetCustomAttribute() != null ? true : (type.IsClass || type.IsValueType) && !type.IsSealed && validConstructor; + return type.IsAbstract || type.IsInterface || type.GetCustomAttribute() != null || (type.IsClass || type.IsValueType) && !type.IsSealed && validConstructor; } internal static bool TryGetCollectionParser(Type type, out Func? builder) @@ -234,7 +232,12 @@ internal static void ScanAssemblyForTypes(Assembly assembly) // register them with the default builder foreach (var type in types) - _typeInfo.TryAdd(type, new(type)); + { + var info = new TypeDeserializeInfo(type); + _typeInfo.TryAdd(type, info); + foreach (var parentType in _typeInfo.Where(x => (x.Key.IsInterface || x.Key.IsAbstract) && x.Key != type && type.IsAssignableTo(x.Key))) + parentType.Value.AddOrUpdateChildren(info); + } // mark this assembly as scanned _scannedAssemblies.Add(identifier); @@ -251,7 +254,7 @@ private static void ScanForAbstractTypes(Assembly assembly) // look for any types that inherit already defined abstract types foreach (var abstractType in _typeInfo.Where(x => x.Value.IsAbtractType)) { - var childTypes = assembly.DefinedTypes.Where(x => !_typeInfo.ContainsKey(x) && x.IsSubclassOf(abstractType.Key)); + var childTypes = assembly.DefinedTypes.Where(x => (x.IsSubclassOf(abstractType.Key) || x.ImplementedInterfaces.Contains(abstractType.Key) || x.IsAssignableTo(abstractType.Key))); abstractType.Value.AddOrUpdateChildren(childTypes.Select(x => new TypeDeserializeInfo(x))); } } @@ -270,7 +273,7 @@ internal class TypeDeserializeInfo public string EdgeDBTypeName { get; } public bool IsAbtractType - => _type.IsAbstract; + => _type.IsAbstract || _type.IsInterface; public Dictionary Children { get; } = new(); @@ -292,12 +295,13 @@ public TypeDeserializeInfo(Type type, TypeDeserializerFactory factory) EdgeDBTypeName = _type.GetCustomAttribute()?.Name ?? _type.Name; } + public void AddOrUpdateChildren(TypeDeserializeInfo child) + => Children[child._type] = child; + public void AddOrUpdateChildren(IEnumerable children) { - foreach(var child in children) - { - Children[child._type] = child; - } + foreach (var child in children) + AddOrUpdateChildren(child); } public void UpdateFactory(TypeDeserializerFactory factory) @@ -429,8 +433,19 @@ private TypeDeserializerFactory CreateDefaultFactory() prop.SetValue(instance, Enum.Parse(prop.PropertyType, str)); continue; } - - prop.SetValue(instance, prop.PropertyType.ConvertValue(value)); + else + { + try + { + prop.SetValue(instance, prop.PropertyType.ConvertValue(value)); + } + catch (Exception x) + { + var valueTypeName = value is IDictionary dict ? dict["__tname__"]?.ToString() : valueType.Name; + throw new InvalidOperationException($"Failed to set {_type.Name}.{prop.Name} with type {valueTypeName} to {prop.PropertyType}.", x); + } + } + } return instance; diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj index 5a2fb385..05e1ab2b 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj +++ b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj @@ -35,4 +35,19 @@ + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs index 08b300da..d82f7232 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs @@ -9,6 +9,7 @@ namespace EdgeDB.Interfaces.Queries { public interface IInsertQuery : ISingleCardinalityExecutable { + IUnlessConflictOn UnlessConflict(); IUnlessConflictOn UnlessConflictOn(Expression> propertySelector); IUnlessConflictOn UnlessConflictOn(Expression> propertySelector); } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs index f2e6254d..e5782082 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs @@ -9,7 +9,6 @@ namespace EdgeDB.Interfaces public interface IUnlessConflictOn : ISingleCardinalityExecutable { ISingleCardinalityExecutable ElseReturn(); - IMultiCardinalityExecutable Else(Func, IMultiCardinalityExecutable> elseQuery); ISingleCardinalityExecutable Else(Func, ISingleCardinalityExecutable> elseQuery); IQueryBuilder Else(TQueryBuilder elseQuery) diff --git a/src/EdgeDB.Net.QueryBuilder/Properties/Resources.Designer.cs b/src/EdgeDB.Net.QueryBuilder/Properties/Resources.Designer.cs new file mode 100644 index 00000000..5beeb452 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Properties/Resources.Designer.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace EdgeDB.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EdgeDB.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to select schema::ObjectType { + /// id, + /// name, + /// is_abstract, + /// pointers: { + /// real_cardinality := ("One" IF .required ELSE "AtMostOne") IF <str>.cardinality = "One" ELSE ("AtLeastOne" IF .required ELSE "Many"), + /// name, + /// target_id := .target.id, + /// is_link := exists [IS schema::Link], + /// is_exclusive := exists (select .constraints filter .name = 'std::exclusive'), + /// is_computed := len(.computed_fields) != 0, + /// is_readonly := .readonly, + /// has_default := EXISTS .default or ("std::sequence [rest of string was truncated]";. + /// + internal static string INTROSPECT_QUERY { + get { + return ResourceManager.GetString("INTROSPECT_QUERY", resourceCulture); + } + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Properties/Resources.resx b/src/EdgeDB.Net.QueryBuilder/Properties/Resources.resx new file mode 100644 index 00000000..683dbf83 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Properties/Resources.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + select schema::ObjectType { + id, + name, + is_abstract, + pointers: { + real_cardinality := ("One" IF .required ELSE "AtMostOne") IF <str>.cardinality = "One" ELSE ("AtLeastOne" IF .required ELSE "Many"), + name, + target_id := .target.id, + is_link := exists [IS schema::Link], + is_exclusive := exists (select .constraints filter .name = 'std::exclusive'), + is_computed := len(.computed_fields) != 0, + is_readonly := .readonly, + has_default := EXISTS .default or ("std::sequence" in .target[IS schema::ScalarType].ancestors.name), + } +} +filter not .builtin; + + \ No newline at end of file diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index c01691fa..f141aaeb 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -1,11 +1,13 @@ using EdgeDB.Interfaces; using EdgeDB.Interfaces.Queries; using EdgeDB.QueryNodes; +using EdgeDB.Schema; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace EdgeDB @@ -15,6 +17,7 @@ public class QueryBuilder : IQueryBuilder private readonly List _nodes; private QueryNode? CurrentUserNode => _nodes.LastOrDefault(x => !x.IsAutoGenerated); private readonly Dictionary _queryGlobals; + private SchemaInfo? _schemaInfo; static QueryBuilder() { @@ -54,19 +57,22 @@ private TNode AddNode(NodeContext context, bool autoGenerated = false) return node; } - - public (string Query, IDictionary Parameters) Build() + + internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) { List query = new(); List> parameters = new(); var nodes = _nodes; - foreach(var node in nodes) + foreach (var node in nodes) + { + node.SchemaInfo ??= _schemaInfo; node.FinalizeQuery(); - + } + // create a with block if we have any query globals - if (_queryGlobals.Any()) + if (includeGlobalsInQuery && _queryGlobals.Any()) { var with = new WithNode(new NodeBuilder(new WithContext(typeof(TType)) { @@ -96,9 +102,20 @@ private TNode AddNode(NodeContext context, bool autoGenerated = false) query.Reverse(); - return (string.Join(' ', query), parameters.SelectMany(x => x).DistinctBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value)); + return new BuiltQuery(string.Join(' ', query)) + { + Parameters = parameters.SelectMany(x => x).DistinctBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value), + Globals = !includeGlobalsInQuery ? _queryGlobals : null + }; } + public BuiltQuery Build() + => InternalBuild(); + + public BuiltQuery BuildWithGlobals() + => InternalBuild(false); + + #region Root nodes public QueryBuilder With(string name, object? value) { if (QueryObjectManager.TryGetObjectId(value, out var id)) @@ -107,8 +124,7 @@ public QueryBuilder With(string name, object? value) _queryGlobals[name] = value; return this; } - - #region Root nodes + public ISelectQuery Select() { AddNode(new SelectContext(typeof(TType))); @@ -283,6 +299,16 @@ private QueryBuilder LimitExp(LambdaExpression limit) return this; } + private QueryBuilder UnlessConflict() + { + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); + + insertNode.UnlessConflict(); + + return this; + } + private QueryBuilder UnlessConflictOn(LambdaExpression selector) { if (CurrentUserNode is not InsertNode insertNode) @@ -363,6 +389,8 @@ ISelectQuery ISelectQuery.Limit(Expression IInsertQuery.UnlessConflict() + => UnlessConflict(); IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) => UnlessConflictOn(propertySelector); IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) @@ -410,22 +438,28 @@ IDeleteQuery IDeleteQuery.Limit(Expression LimitExp(limit); #endregion - Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) + async Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) { - var (Query, Parameters) = Build(); + _schemaInfo ??= await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(edgedb, token).ConfigureAwait(false); + + var result = Build(); _nodes.Clear(); _queryGlobals.Clear(); - return edgedb.QueryAsync(Query, Parameters, token); + return await edgedb.QueryAsync(result.Query, result.Parameters, token).ConfigureAwait(false); } - Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) + async Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) { - var (Query, Parameters) = Build(); + _schemaInfo ??= await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(edgedb, token).ConfigureAwait(false); + + var result = Build(); _nodes.Clear(); _queryGlobals.Clear(); - return edgedb.QuerySingleAsync(Query, Parameters, token); + return await edgedb.QuerySingleAsync(result.Query, result.Parameters, token).ConfigureAwait(false); } #region IQueryBuilder + IReadOnlyCollection IQueryBuilder.Nodes => _nodes; + IReadOnlyDictionary IQueryBuilder.Globals => _queryGlobals; IQueryBuilder IQueryBuilder.With(string name, object? value) => With(name, value); #endregion } @@ -450,6 +484,69 @@ public interface IQueryBuilder : } public interface IQueryBuilder { - (string Query, IDictionary Parameters) Build(); + internal IReadOnlyCollection Nodes { get; } + internal IReadOnlyDictionary Globals { get; } + + BuiltQuery Build(); + + internal BuiltQuery BuildWithGlobals(); + } + + [System.Diagnostics.DebuggerDisplay(@"{Query,nq}")] + public class BuiltQuery + { + public string Query { get; init; } + public IDictionary? Parameters { get; init; } + internal IDictionary? Globals { get; init; } + + public BuiltQuery(string query) + { + Query = query; + } + + public string Prettify() + { + // add newlines + var result = Regex.Replace(Query, @"({|\(|\)|}|,)", m => + { + switch (m.Groups[1].Value) + { + case "{" or "(" or ",": + if (m.Groups[1].Value == "{" && Query[m.Index + 1] == '}') + return m.Groups[1].Value; + + 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" : "")}"; + } + }).Trim().Replace("\n ", "\n"); + + // clean up newline func + result = Regex.Replace(result, "\n\n", m => "\n"); + + // add indentation + result = Regex.Replace(result, "^", m => + { + int indent = 0; + + foreach (var c in result[..m.Index]) + { + if (c is '(' or '{') + indent++; + if (c is ')' or '}') + indent--; + } + + var next = result.Length != m.Index ? result[m.Index] : '\0'; + + if (next is '}' or ')') + indent--; + + return "".PadLeft(indent * 2); + }, RegexOptions.Multiline); + + return result; + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs index 548ff4ff..bef5e558 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs @@ -9,7 +9,7 @@ namespace EdgeDB { internal class NodeContext { - public bool SetAsGlobal { get; init; } + public bool SetAsGlobal { get; set; } public string? GlobalName { get; init; } public Type CurrentType { get; init; } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 81f3933b..312ffa12 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -11,7 +11,12 @@ namespace EdgeDB.QueryNodes { internal class InsertNode : QueryNode { - public InsertNode(NodeBuilder builder) : base(builder) { } + private bool _autogenerateUnlessConflict; + private readonly StringBuilder _children; + public InsertNode(NodeBuilder builder) : base(builder) + { + _children = new(); + } private string BuildInsertLambdaShape(LambdaExpression expression) { @@ -87,6 +92,28 @@ public override void Visit() public override void FinalizeQuery() { + if(_autogenerateUnlessConflict) + { + if (SchemaInfo is null) + throw new NotSupportedException("Cannot use autogenerated unless conflict on without schema interpolation"); + + if (!SchemaInfo.TryGetObjectInfo(Context.CurrentType, out var typeInfo)) + throw new NotSupportedException($"Could not find type info for {Context.CurrentType}"); + + var exclusiveProperties = typeInfo.Properties?.Where(x => x.IsExclusive && x.Name != "id"); + + if (exclusiveProperties == null || !exclusiveProperties.Any()) + throw new NotSupportedException($"The type {typeInfo.Name} does not have any user defined exclusive properties"); + + var constraint = exclusiveProperties.Count() > 1 ? + $"({string.Join(", ", exclusiveProperties.Select(x => $".{x.Name}"))})" : + $".{exclusiveProperties.First().Name}"; + + Query.Append($" unless conflict on {constraint}"); + } + + Query.Append(_children); + if(Context.SetAsGlobal && Context.GlobalName != null) { SetGlobal(Context.GlobalName, new SubQuery($"({Query})")); @@ -94,6 +121,11 @@ public override void FinalizeQuery() } } + public void UnlessConflict() + { + _autogenerateUnlessConflict = true; + } + public void UnlessConflictOn(LambdaExpression selector) { Query.Append($" unless conflict on {ExpressionTranslator.Translate(selector, Builder.QueryVariables, Context)}"); @@ -101,16 +133,40 @@ public void UnlessConflictOn(LambdaExpression selector) public void ElseDefault() { - Query.Append($" else (select {Context.CurrentType.GetEdgeDBTypeName()})"); + _children.Append($" else (select {Context.CurrentType.GetEdgeDBTypeName()})"); } public void Else(IQueryBuilder builder) { - var result = builder.Build(); + var userNodes = builder.Nodes.Where(x => !x.IsAutoGenerated); + + // TODO: better checks for this, future should add a callback to add the + // node with its context so any parent builder can change contexts for nodes + foreach (var node in userNodes) + node.Context.SetAsGlobal = false; + + var globals = userNodes.SelectMany(x => + x.ReferencedGlobals.Select(y => + new KeyValuePair(y, x.Builder.QueryGlobals[y]) + ) + ).ToDictionary(x => x.Key, x => x.Value); + + var variables = userNodes.SelectMany(x => + x.ReferencedVariables.Select(y => + new KeyValuePair(y, x.Builder.QueryVariables[y]) + ) + ); + + + var newBuilder = new QueryBuilder(userNodes.ToList(), globals); + + var result = newBuilder.BuildWithGlobals(); + _children.Append($" else ({result.Query})"); - Query.Append($" else ({result.Query})"); - foreach (var variable in result.Parameters) + foreach (var variable in variables) SetVariable(variable.Key, variable.Value); + foreach (var global in globals) + SetGlobal(global.Key, global.Value); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index f2bf7542..f061864a 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -1,4 +1,5 @@ -using System; +using EdgeDB.Schema; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -11,7 +12,7 @@ internal abstract class QueryNode : QueryNode { protected QueryNode(NodeBuilder builder) : base(builder) { } - protected TContext Context + internal new TContext Context => (TContext)Builder.Context; } @@ -19,10 +20,19 @@ internal abstract class QueryNode { public bool IsAutoGenerated => Builder.IsAutoGenerated; - protected readonly NodeBuilder Builder; - protected StringBuilder Query + + public SchemaInfo? SchemaInfo { get; set; } + internal List ReferencedGlobals { get; } = new(); + internal List ReferencedVariables { get; } = new(); + + internal readonly NodeBuilder Builder; + internal StringBuilder Query => Builder.Query; + internal NodeContext Context + => Builder.Context; + + public QueryNode(NodeBuilder builder) { Builder = builder; @@ -32,10 +42,16 @@ public QueryNode(NodeBuilder builder) public virtual void FinalizeQuery() { } protected void SetVariable(string name, object? value) - => Builder.QueryVariables[name] = value; + { + ReferencedVariables.Add(name); + Builder.QueryVariables[name] = value; + } protected void SetGlobal(string name, object? value) - => Builder.QueryGlobals[name] = value; + { + ReferencedGlobals.Add(name); + Builder.QueryGlobals[name] = value; + } internal BuiltQueryNode Build() => new(Query.ToString(), Builder.QueryVariables); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs index a82c2945..6dcbb010 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs @@ -29,8 +29,14 @@ public override void Visit() { var subQuery = queryBuilder.Build(); value = new SubQuery($"({subQuery.Query})"); - foreach (var variable in subQuery.Parameters) - SetVariable(variable.Key, variable.Value); + + if(subQuery.Parameters is not null) + foreach (var variable in subQuery.Parameters) + SetVariable(variable.Key, variable.Value); + + if (subQuery.Globals is not null) + foreach (var global in subQuery.Globals) + SetGlobal(global.Key, global.Value); } values.Add($"{kvp.Key} := {QueryUtils.ParseObject(value)}"); diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs new file mode 100644 index 00000000..63eba99e --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Schema.DataTypes +{ + internal class ObjectType + { + [EdgeDBIgnore] + public string CleanedName + => Name!.Split("::")[1]; + public Guid Id { get; set; } + public string? Name { get; set; } + public bool IsAbstract { get; set; } + [EdgeDBProperty("pointers")] + public Property[]? Properties { get; set; } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs new file mode 100644 index 00000000..59955ab5 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Schema.DataTypes +{ + public enum Cardinality + { + One, + AtMostOne, + AtLeastOne, + Many, + } + internal class Property + { + [EdgeDBProperty("real_cardinality")] + public Cardinality Cardinality { get; set; } + public string? Name { get; set; } + public Guid? TargetId { get; set; } + public bool IsLink { get; set; } + public bool IsExclusive { get; set; } + public bool IsComputed { get; set; } + public bool IsReadonly { get; set; } + public bool HasDefault { get; set; } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs new file mode 100644 index 00000000..4a676306 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs @@ -0,0 +1,23 @@ +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 +{ + internal class SchemaInfo + { + public IReadOnlyCollection Types { get; } + public SchemaInfo(IReadOnlyCollection types) + { + Types = types!; + } + + public bool TryGetObjectInfo(Type type, [MaybeNullWhen(false)] out ObjectType info) + => (info = Types.FirstOrDefault(x => x.CleanedName == type.GetEdgeDBTypeName())) != null; + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs new file mode 100644 index 00000000..4c9b31f3 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs @@ -0,0 +1,30 @@ +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 +{ + internal class SchemaIntrospector + { + private static readonly ConcurrentDictionary _schemas; + + static SchemaIntrospector() + { + _schemas = new ConcurrentDictionary(); + } + + 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)); + } + + private static async Task IntrospectSchemaAsync(IEdgeDBQueryable edgedb, CancellationToken token) + => _schemas[edgedb] = new SchemaInfo(await edgedb.QueryAsync(Properties.Resources.INTROSPECT_QUERY, token: token).ConfigureAwait(false)); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/introspect.edgeql b/src/EdgeDB.Net.QueryBuilder/introspect.edgeql new file mode 100644 index 00000000..62f5268d --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/introspect.edgeql @@ -0,0 +1,16 @@ +select schema::ObjectType { + id, + name, + is_abstract, + pointers: { + real_cardinality := ("One" IF .required ELSE "AtMostOne") IF .cardinality = "One" ELSE ("AtLeastOne" IF .required ELSE "Many"), + name, + target_id := .target.id, + is_link := exists [IS schema::Link], + is_exclusive := exists (select .constraints filter .name = 'std::exclusive'), + is_computed := len(.computed_fields) != 0, + is_readonly := .readonly, + has_default := EXISTS .default or ("std::sequence" in .target[IS schema::ScalarType].ancestors.name), + } +} +filter not .builtin; \ No newline at end of file From df99871aec4858d25775de8b870db4786e243c68 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 27 Jun 2022 12:06:39 -0300 Subject: [PATCH 07/70] fix exposed internal method --- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index f141aaeb..474cd14c 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -112,7 +112,7 @@ internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) public BuiltQuery Build() => InternalBuild(); - public BuiltQuery BuildWithGlobals() + internal BuiltQuery BuildWithGlobals() => InternalBuild(false); #region Root nodes @@ -461,6 +461,7 @@ IDeleteQuery IDeleteQuery.Limit(Expression IQueryBuilder.Nodes => _nodes; IReadOnlyDictionary IQueryBuilder.Globals => _queryGlobals; IQueryBuilder IQueryBuilder.With(string name, object? value) => With(name, value); + BuiltQuery IQueryBuilder.BuildWithGlobals() => BuildWithGlobals(); #endregion } From 21121665f06803d94b6e8d08bdcf6850e51cef33 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 27 Jun 2022 14:48:59 -0300 Subject: [PATCH 08/70] document the query builder --- src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs | 1 - src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs | 4 - .../Interfaces/IMultiCardinalityExecutable.cs | 12 ++ .../ISingleCardinalityExecutable.cs | 12 ++ .../Interfaces/Queries/IDeleteQuery.cs | 53 ++++++ .../Interfaces/Queries/IInsertQuery.cs | 23 +++ .../Interfaces/Queries/ISelectQuery.cs | 53 ++++++ .../Interfaces/Queries/IUpdateQuery.cs | 11 ++ .../InsertChildren/IUnlessConflictOn.cs | 31 ++++ .../OrderByNullPlacement.cs | 6 + src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 166 +++++++++++++++--- src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 26 +++ .../QueryNodes/QueryNode.cs | 3 +- src/EdgeDB.Net.QueryBuilder/With.cs | 24 --- 14 files changed, 375 insertions(+), 50 deletions(-) delete mode 100644 src/EdgeDB.Net.QueryBuilder/With.cs diff --git a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs index da29e064..6413d68f 100644 --- a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs +++ b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs @@ -1,6 +1,5 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("EdgeDB.Examples.ExampleApp")] [assembly: InternalsVisibleTo("EdgeDB.DotnetTool")] [assembly: InternalsVisibleTo("EdgeDB.Tests.Unit")] [assembly: InternalsVisibleTo("EdgeDB.Tests.Integration")] diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs b/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs index 385fda3e..a4b51a35 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs @@ -1709,10 +1709,6 @@ public sealed partial class EdgeQL #endregion links - #region variables - - #endregion variables - internal static Dictionary FunctionOperators = new() { { "String.ToLower", new StringToLower()}, diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs index d2c0b56e..df8d5429 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs @@ -6,8 +6,20 @@ namespace EdgeDB.Interfaces { + /// + /// Represents an executable query with one or more returning objects. + /// + /// The object the query will return. public interface IMultiCardinalityExecutable : IQueryBuilder { + /// + /// Executes the current query. + /// + /// The client to preform the query on. + /// A cancellation token to cancel the asynchronous operation. + /// + /// A read-only collection of . + /// Task> ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token = default); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs index ae103651..791797ed 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs @@ -6,8 +6,20 @@ namespace EdgeDB.Interfaces { + /// + /// Represents an executable query with at most one returning objects. + /// + /// The object the query will return. public interface ISingleCardinalityExecutable : IQueryBuilder { + /// + /// Executes the current query. + /// + /// The client to preform the query on. + /// A cancellation token to cancel the asynchronous operation. + /// + /// A or <>. + /// Task ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token = default); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs index a21177ff..cc363eff 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs @@ -7,17 +7,70 @@ namespace EdgeDB.Interfaces.Queries { + /// + /// Represents a generic DELETE query used within a . + /// + /// The type which this DELETE query is querying against. public interface IDeleteQuery : IMultiCardinalityExecutable { + /// + /// 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); + + /// 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); + + /// IDeleteQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + + /// + /// Orders the current s by the given property desending first. + /// + /// The property to order by. + /// The order of which null values should occor. + /// The current query. IDeleteQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + + /// IDeleteQuery OrderByDesending(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. + /// + /// 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. + /// + /// A callback returning the amount to limit to. + /// The current query. IDeleteQuery Limit(Expression> limit); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs index d82f7232..e826ffb0 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs @@ -7,10 +7,33 @@ namespace EdgeDB.Interfaces.Queries { + /// + /// Represents a generic INSERT query used within a . + /// + /// The type which this INSERT query is querying against. public interface IInsertQuery : ISingleCardinalityExecutable { + /// + /// 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(); + + /// + /// 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); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs index 42a85e7d..6d58f7c2 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs @@ -7,17 +7,70 @@ namespace EdgeDB.Interfaces.Queries { + /// + /// Represents a generic SELECT query used within a . + /// + /// The type which this SELECT query is querying against. public interface ISelectQuery : IMultiCardinalityExecutable { + /// + /// 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); + + /// ISelectQuery 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. ISelectQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + + /// ISelectQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + + /// + /// Orders the current s by the given property desending first. + /// + /// The property to order by. + /// The order of which null values should occor. + /// The current query. ISelectQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + + /// ISelectQuery OrderByDesending(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. + /// + /// 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. + /// + /// 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 38295a48..cdee206d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs @@ -7,9 +7,20 @@ namespace EdgeDB.Interfaces.Queries { + /// + /// Represents a generic UPDATE query used within a . + /// + /// The type which this UPDATE query is querying against. public interface IUpdateQuery : IMultiCardinalityExecutable { + /// + /// Filters the current update query by the given predicate. + /// + /// The filter to apply to the current update query. + /// The current query. IMultiCardinalityExecutable Filter(Expression> filter); + + /// IMultiCardinalityExecutable Filter(Expression> filter); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs index e5782082..2580b7a6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs @@ -6,11 +6,42 @@ namespace EdgeDB.Interfaces { + /// + /// Represents a generic UNLESS CONFLICT ON query used within a . + /// + /// The type which this UNLESS CONFLICT ON query is querying against. public interface IUnlessConflictOn : ISingleCardinalityExecutable { + /// + /// Adds an ELSE (SELECT ...) statment to the current query returning the conflicting object. + /// + /// 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-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; } diff --git a/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs b/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs index c79e252f..9ab47364 100644 --- a/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs +++ b/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs @@ -6,8 +6,14 @@ namespace EdgeDB { + /// + /// An enum representing the placement of null values within queries. + /// public enum OrderByNullPlacement { + /// + /// Places values at the front of the ordered set. + /// First, Last } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 474cd14c..99c6bcec 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -12,6 +12,10 @@ namespace EdgeDB { + /// + /// Represents a query builder used to build queries against . + /// + /// public class QueryBuilder : IQueryBuilder { private readonly List _nodes; @@ -109,6 +113,13 @@ internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) }; } + /// + /// Builds the current query into a . + /// + /// + /// This method resets this query builder. + /// + /// A built query containing the query itself and any query variables used within the query. public BuiltQuery Build() => InternalBuild(); @@ -116,6 +127,7 @@ internal BuiltQuery BuildWithGlobals() => InternalBuild(false); #region Root nodes + /// public QueryBuilder With(string name, object? value) { if (QueryObjectManager.TryGetObjectId(value, out var id)) @@ -124,22 +136,15 @@ public QueryBuilder With(string name, object? value) _queryGlobals[name] = value; return this; } - + + /// public ISelectQuery Select() { AddNode(new SelectContext(typeof(TType))); return this; } - public ISelectQuery Select(Expression>? shape = null) - { - AddNode(new SelectContext(typeof(TNewType)) - { - Shape = shape - }); - return EnterNewType(); - } - + /// public ISelectQuery Select(Expression> shape) { AddNode(new SelectContext(typeof(TType)) @@ -149,6 +154,7 @@ public ISelectQuery Select(Expression> shape) return this; } + /// public ISelectQuery Select(Expression> shape) { AddNode(new SelectContext(typeof(TType)) @@ -158,6 +164,7 @@ public ISelectQuery Select(Expression(); } + /// public IInsertQuery Insert(TType value, bool returnInsertedValue = true) { var selectedGlobal = returnInsertedValue ? QueryUtils.GenerateRandomVariableName() : null; @@ -179,6 +186,7 @@ public IInsertQuery Insert(TType value, bool returnInsertedValue = true) return this; } + /// public IInsertQuery Insert(Expression> value, bool returnInsertedValue = true) { var selectedGlobal = returnInsertedValue ? QueryUtils.GenerateRandomVariableName() : null; @@ -200,6 +208,7 @@ public IInsertQuery Insert(Expression> value, b return this; } + /// public IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true) { var selectedGlobal = returnUpdatedValue ? QueryUtils.GenerateRandomVariableName() : null; @@ -220,7 +229,8 @@ public IUpdateQuery Update(Expression> updateFunc, boo return this; } - + + /// public IDeleteQuery Delete { get @@ -229,7 +239,6 @@ public IDeleteQuery Delete return this; } } - #endregion #region Generic sub-query methods @@ -438,22 +447,26 @@ IDeleteQuery IDeleteQuery.Limit(Expression LimitExp(limit); #endregion - async Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) + private async ValueTask IntrospectAndBuildAsync(IEdgeDBQueryable edgedb, CancellationToken token) { - _schemaInfo ??= await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(edgedb, token).ConfigureAwait(false); + if(_nodes.Any(x => x.RequiresIntrospection)) + _schemaInfo ??= await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(edgedb, token).ConfigureAwait(false); var result = Build(); _nodes.Clear(); _queryGlobals.Clear(); + + return result; + } + + async Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) + { + var result = await IntrospectAndBuildAsync(edgedb, token).ConfigureAwait(false); return await edgedb.QueryAsync(result.Query, result.Parameters, token).ConfigureAwait(false); } async Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) { - _schemaInfo ??= await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(edgedb, token).ConfigureAwait(false); - - var result = Build(); - _nodes.Clear(); - _queryGlobals.Clear(); + var result = await IntrospectAndBuildAsync(edgedb, token).ConfigureAwait(false); return await edgedb.QuerySingleAsync(result.Query, result.Parameters, token).ConfigureAwait(false); } @@ -465,6 +478,10 @@ IDeleteQuery IDeleteQuery.Limit(Expression + /// Represents a generic query builder for querying against . + /// + /// The type of which queries will be preformed with. public interface IQueryBuilder : IQueryBuilder, ISelectQuery, @@ -473,38 +490,147 @@ public interface IQueryBuilder : IUnlessConflictOn, IInsertQuery { + /// + /// Adds a value to a WITH statement. + /// + /// + /// This value can be used later within queries by using a lambda with the object, + /// then calling . + /// + /// The name of the value. + /// The value to add. + /// + /// The current query builder. + /// IQueryBuilder With(string name, object? value); + + /// + /// Adds a SELECT statement selecting the current with a autogenerated shape. + /// + /// + /// A . + /// ISelectQuery Select(); - ISelectQuery Select(Expression>? shape = null); + + /// + /// Adds a SELECT statement selecting the current with the given shape. + /// + /// + /// To define a shape, use to include a property. any other + /// methods/values will be treated as computed values. + /// + /// The type to select. + /// The shape to select. + /// + /// A . + /// ISelectQuery Select(Expression> shape); + + /// + /// Adds a SELECT statement selecting the type with the given shape. + /// + /// + /// To define a shape, use to include a property. any other + /// methods/values will be treated as computed values. + /// + /// The type to select. + /// The shape to select. + /// + /// A . + /// ISelectQuery Select(Expression> shape); + + /// + /// Adds a INSERT statement inserting an instance of . + /// + /// The value to insert. + /// + /// whether or not to implicitly add a select statement to return the inserted value. + /// + /// A . IInsertQuery Insert(TType value, bool returnInsertedValue = true); + + /// + /// Adds a INSERT statement inserting an instance of . + /// + /// 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 = true); + + /// + /// Adds a UPDATE statement updating an instance of . + /// + /// + /// The callback used to update . The first parameter is the old value. + /// + /// + /// whether or not to implicitly add a select statement to return the inserted value. + /// + /// A . IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true); + + /// + /// Adds a DELETE statement deleting an instance of . + /// IDeleteQuery Delete { get; } } + + /// + /// Represents a generic query builder with a build function. + /// public interface IQueryBuilder { internal IReadOnlyCollection Nodes { get; } internal IReadOnlyDictionary Globals { get; } + /// + /// Builds the current query. + /// + /// + /// A . + /// BuiltQuery Build(); internal BuiltQuery BuildWithGlobals(); } + /// + /// Represents a built query. + /// [System.Diagnostics.DebuggerDisplay(@"{Query,nq}")] public class BuiltQuery { + /// + /// Gets the query text. + /// public string Query { get; init; } + + /// + /// Gets a collection of parameters for the query. + /// public IDictionary? Parameters { get; init; } internal IDictionary? Globals { get; init; } + /// + /// Creates a new built query. + /// + /// The query text. public BuiltQuery(string query) { Query = query; } + /// + /// Prettifies the query text. + /// + /// + /// This method uses alot of regex and can be unreliable, if + /// you're using this in a production setting please use with care. + /// + /// A prettified version of . public string Prettify() { // add newlines diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index 4901423f..b3dd7ba8 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -7,16 +7,42 @@ namespace EdgeDB { + /// + /// Represents context used within query functions. + /// public sealed class QueryContext { + /// + /// 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 . + /// [EquivalentOperator(typeof(VariablesReference))] public TType Global(string name) => default!; + /// + /// References a contextual local. + /// + /// The type of the local. + /// The name of the local. + /// + /// A mock reference to a local with the given . + /// [EquivalentOperator(typeof(LocalReference))] public TType Local(string name) => default!; + /// + /// Includes a property within a shape. + /// + /// The type of the property. + /// + /// A mock reference to the property that this include statement is being assigned to. + /// public TType Include() => default!; } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index f061864a..77a30c46 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -20,6 +20,8 @@ internal abstract class QueryNode { public bool IsAutoGenerated => Builder.IsAutoGenerated; + + public bool RequiresIntrospection { get; protected set; } public SchemaInfo? SchemaInfo { get; set; } internal List ReferencedGlobals { get; } = new(); @@ -32,7 +34,6 @@ internal StringBuilder Query internal NodeContext Context => Builder.Context; - public QueryNode(NodeBuilder builder) { Builder = builder; diff --git a/src/EdgeDB.Net.QueryBuilder/With.cs b/src/EdgeDB.Net.QueryBuilder/With.cs deleted file mode 100644 index 4fa5ad73..00000000 --- a/src/EdgeDB.Net.QueryBuilder/With.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB -{ - public class With - { - - } - - public class With : With - { - private readonly QueryableCollection _queryCollection; - public With(QueryableCollection queryCollection) - { - _queryCollection = queryCollection; - } - - public static implicit operator With(QueryableCollection query) => new(query); - } -} From 310f93dac10c10f30f608c301f260eade1208ced Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 27 Jun 2022 16:48:30 -0300 Subject: [PATCH 09/70] fix bug with introspect in insert nodes --- src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs | 1 + src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs | 4 ++++ src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs | 1 + 3 files changed, 6 insertions(+) diff --git a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs index 6413d68f..da29e064 100644 --- a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs +++ b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("EdgeDB.Examples.ExampleApp")] [assembly: InternalsVisibleTo("EdgeDB.DotnetTool")] [assembly: InternalsVisibleTo("EdgeDB.Tests.Unit")] [assembly: InternalsVisibleTo("EdgeDB.Tests.Integration")] diff --git a/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs b/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs index 9ab47364..e2a752c0 100644 --- a/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs +++ b/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs @@ -15,6 +15,10 @@ public enum OrderByNullPlacement /// Places values at the front of the ordered set. /// First, + + /// + /// Places values at the end of the ordered set. + /// Last } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 312ffa12..a05b3a0f 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -124,6 +124,7 @@ public override void FinalizeQuery() public void UnlessConflict() { _autogenerateUnlessConflict = true; + RequiresIntrospection = true; } public void UnlessConflictOn(LambdaExpression selector) From 03ebbe2bcc73c68201abaa9c7564c351da1dc18b Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sat, 2 Jul 2022 22:37:17 -0300 Subject: [PATCH 10/70] more query builder work (close to first RC :D) --- dbschema/lexertest.esdl | 146 ------------- dbschema/migrations/00011.edgeql | 90 ++++++++ .../Examples/QueryBuilder.cs | 49 +---- .../EdgeDB.Examples.ExampleApp/Program.cs | 6 +- src/EdgeDB.Net.Driver/Codecs/Object.cs | 2 +- .../Attributes/EdgeDBTypeAttribute.cs | 4 + .../Utils/ReflectionUtils.cs | 15 ++ src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs | 8 + .../Extensions/TypeExtensions.cs | 6 +- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 86 ++++++-- src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 21 +- .../QueryNodes/Contexts/SelectContext.cs | 1 + .../QueryNodes/InsertNode.cs | 11 +- .../QueryNodes/QueryNode.cs | 2 +- .../QueryNodes/SelectNode.cs | 19 +- src/EdgeDB.Net.QueryBuilder/QueryUtils.cs | 2 +- .../QueryableCollection.cs | 203 +++++++++++++++++- .../Schema/DataTypes/ObjectType.cs | 1 + .../Schema/SchemaIntrospector.cs | 25 ++- .../Expressions/BinaryExpressionTranslator.cs | 2 +- .../ConditionalExpressionTranslator.cs | 22 ++ .../ConstantExpressionTranslator.cs | 4 +- .../Expressions/ExpressionContext.cs | 3 +- .../Expressions/ExpressionTranslator.cs | 10 +- .../Expressions/MemberExpressionTranslator.cs | 2 +- .../MemberInitExpressionTranslator.cs | 14 +- .../MethodCallExpressionTranslator.cs | 84 ++++++-- .../Expressions/NewExpressionTranslator.cs | 2 +- .../Expressions/UnaryExpressionTranslator.cs | 47 ++++ .../operators.yml | 7 + 30 files changed, 636 insertions(+), 258 deletions(-) delete mode 100644 dbschema/lexertest.esdl create mode 100644 dbschema/migrations/00011.edgeql create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConditionalExpressionTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs diff --git a/dbschema/lexertest.esdl b/dbschema/lexertest.esdl deleted file mode 100644 index ba52c6d3..00000000 --- a/dbschema/lexertest.esdl +++ /dev/null @@ -1,146 +0,0 @@ -module lexertest { - - scalar type Genre extending enum; - - abstract link movie_character { - property character_name -> str; - } - - abstract type Person { - required property name -> str { - constraint exclusive; - }; - } - - type Villain extending Person { - link nemesis -> Hero; - } - - type Hero extending Person { - property secret_identity -> str; - property number_of_movies -> int64; - multi link villains := . Genre; - property rating -> float64; - required property title -> str { - constraint exclusive; - }; - required property release_year -> int16 { - default := datetime_get(datetime_current(), 'year'); - } - multi link characters extending movie_character -> Person; - link profile -> Profile { - constraint exclusive; - } - } - - type Profile { - property plot_summary -> str; - property slug -> str { - readonly := true; - } - } - - type User { - required property username -> str; - required multi link favourite_movies -> Movie; - } - - type MovieShape { - } - - abstract type HasName { - property name -> str; - } - abstract type HasAge { - property age -> int64; - } - - scalar type bag_seq extending sequence; - - type Bag extending HasName, HasAge { - property secret_identity -> str; - property genre -> Genre; - property boolField -> bool; - property datetimeField -> datetime; - property localDateField -> cal::local_date; - property localTimeField -> cal::local_time; - property localDateTimeField -> cal::local_datetime; - property durationField -> duration; - property decimalField -> decimal; - property int64Field -> int64; - property int32Field -> int32; - property int16Field -> int16; - property float32Field -> float32; - property float64Field -> float64; - property bigintField -> bigint; - required multi property stringsMulti -> str; - property stringsArr -> array; - multi property stringMultiArr -> array; - property namedTuple -> tuple; - property unnamedTuple -> tuple; - property enumArr -> array; - property seqField -> bag_seq; - property jsonField -> json; - } - - type Simple extending HasName, HasAge {} - - # Unicode handling - # https://github.com/edgedb/edgedb/blob/master/tests/schemas/dump02_default.esdl - - abstract annotation `🍿`; - - abstract constraint `🚀🍿`(max: int64) extending max_len_value; - - function `💯`(NAMED ONLY `🙀`: int64) -> int64 { - using ( - SELECT 100 - `🙀` - ); - - annotation `🍿` := 'fun!🚀'; - volatility := 'Immutable'; - } - - type `S p a M` { - required property `🚀` -> int32; - property c100 := (SELECT `💯`(`🙀` := .`🚀`)); - } - - type A { - required link `s p A m 🤞` -> `S p a M`; - } - - scalar type 你好 extending str; - - scalar type مرحبا extending 你好 { - constraint `🚀🍿`(100); - }; - - scalar type `🚀🚀🚀` extending مرحبا; - - type Łukasz { - required property `Ł🤞` -> `🚀🚀🚀` { - default := <`🚀🚀🚀`>'你好🤞' - } - index on (.`Ł🤞`); - - link `Ł💯` -> A { - property `🙀🚀🚀🚀🙀` -> `🚀🚀🚀`; - property `🙀مرحبا🙀` -> مرحبا { - constraint `🚀🍿`(200); - } - }; - } - -}; - -module `💯💯💯` { - function `🚀🙀🚀`(`🤞`: lexertest::`🚀🚀🚀`) -> lexertest::`🚀🚀🚀` - using ( - SELECT (`🤞` ++ 'Ł🙀') - ); -}; \ No newline at end of file diff --git a/dbschema/migrations/00011.edgeql b/dbschema/migrations/00011.edgeql new file mode 100644 index 00000000..667f5b7d --- /dev/null +++ b/dbschema/migrations/00011.edgeql @@ -0,0 +1,90 @@ +CREATE MIGRATION m1vs5zqu667wwcqfbuugfklx4jcnnu5wnzigwcvyohumszc2mddjeq + ONTO m1hk5lddsbraets3p3xtsj2rumcpydkdpbyoy3ssh3vqmudexna3ya +{ + ALTER FUNCTION lexertest::`💯`(NAMED ONLY `🙀`: std::int64) { + DROP ANNOTATION lexertest::`🍿`; + }; + DROP ABSTRACT ANNOTATION lexertest::`🍿`; + ALTER TYPE lexertest::Bag { + DROP PROPERTY enumArr; + DROP PROPERTY bigintField; + DROP PROPERTY boolField; + DROP PROPERTY datetimeField; + DROP PROPERTY decimalField; + DROP PROPERTY durationField; + DROP PROPERTY float32Field; + DROP PROPERTY float64Field; + DROP PROPERTY genre; + DROP PROPERTY int16Field; + DROP PROPERTY int32Field; + DROP PROPERTY int64Field; + DROP PROPERTY jsonField; + DROP PROPERTY localDateField; + DROP PROPERTY localDateTimeField; + DROP PROPERTY localTimeField; + DROP PROPERTY namedTuple; + DROP PROPERTY secret_identity; + DROP PROPERTY seqField; + DROP PROPERTY stringMultiArr; + DROP PROPERTY stringsArr; + DROP PROPERTY stringsMulti; + DROP PROPERTY unnamedTuple; + }; + DROP TYPE lexertest::Łukasz; + ALTER SCALAR TYPE lexertest::مرحبا { + DROP CONSTRAINT lexertest::`🚀🍿`(100); + }; + DROP ABSTRACT CONSTRAINT lexertest::`🚀🍿`; + ALTER TYPE lexertest::`S p a M` { + DROP PROPERTY c100; + DROP PROPERTY `🚀`; + }; + DROP FUNCTION lexertest::`💯`(NAMED ONLY `🙀`: std::int64); + DROP FUNCTION `💯💯💯`::`🚀🙀🚀`(`🤞`: lexertest::`🚀🚀🚀`); + ALTER ABSTRACT LINK lexertest::movie_character { + DROP PROPERTY character_name; + }; + ALTER TYPE lexertest::Movie { + DROP LINK characters; + DROP LINK profile; + DROP PROPERTY genre; + DROP PROPERTY rating; + DROP PROPERTY release_year; + DROP PROPERTY title; + }; + DROP ABSTRACT LINK lexertest::movie_character; + DROP TYPE lexertest::A; + ALTER TYPE lexertest::HasAge { + DROP PROPERTY age; + }; + ALTER TYPE lexertest::HasName { + DROP PROPERTY name; + }; + DROP TYPE lexertest::Bag; + DROP TYPE lexertest::Simple; + DROP TYPE lexertest::HasAge; + DROP TYPE lexertest::HasName; + ALTER TYPE lexertest::Person { + DROP PROPERTY name; + }; + ALTER TYPE lexertest::Hero { + DROP LINK villains; + DROP PROPERTY number_of_movies; + DROP PROPERTY secret_identity; + }; + DROP TYPE lexertest::Villain; + DROP TYPE lexertest::Hero; + DROP TYPE lexertest::User; + DROP TYPE lexertest::Movie; + DROP TYPE lexertest::MovieShape; + DROP TYPE lexertest::Person; + DROP TYPE lexertest::Profile; + DROP TYPE lexertest::`S p a M`; + DROP SCALAR TYPE lexertest::Genre; + DROP SCALAR TYPE lexertest::bag_seq; + DROP SCALAR TYPE lexertest::`🚀🚀🚀`; + DROP SCALAR TYPE lexertest::مرحبا; + DROP SCALAR TYPE lexertest::你好; + DROP MODULE lexertest; + DROP MODULE `💯💯💯`; +}; diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index acb5a54c..ed37307a 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -30,54 +30,11 @@ public class Person public async Task ExecuteAsync(EdgeDBClient client) { - var builder = new QueryBuilder(); + var collection = client.GetCollection(); - // get or create john - var john = await builder - .Insert(new LinkPerson { Email = "johndoe@email.com", Name = "John Doe" }) - .UnlessConflict() - .ElseReturn() - .ExecuteAsync(client); + var wasDeleted = await collection.DeleteAsync(new LinkPerson { Email = "test@mail.com", Name = "test" }); - // get or create jane - var jane = await builder - .With("john", john) - .Insert(ctx => new LinkPerson - { - Name = "Jane Doe", - Email = "janedoe@email.com", - BestFriend = ctx.Global("john") - }) - .UnlessConflict() - .ElseReturn() - .ExecuteAsync(client); - - Logger!.LogInformation("John result: {@john}", john); - Logger!.LogInformation("Jane result: {@jane}", jane); - - // anon types - var anonPerson = await builder - .Select(ctx => new - { - Name = ctx.Include(), - Email = ctx.Include(), - HasFriend = ctx.Local("BestFriend") != null - }) - .ExecuteAsync(client); - - var test = builder - .Insert(new LinkPerson() - { - Name = "upsert demo", - Email = "upsert@mail.com" - }) - .UnlessConflict() - .Else(q => - q.Update(old => new LinkPerson - { - Name = old!.Name!.ToUpper() - }) - ).Build().Prettify(); + var t = new QueryBuilder().Select(() => EdgeQL.Count(new QueryBuilder().Delete)).Build(); } } } diff --git a/examples/EdgeDB.Examples.ExampleApp/Program.cs b/examples/EdgeDB.Examples.ExampleApp/Program.cs index 7a4124d1..6367b056 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Program.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Program.cs @@ -1,10 +1,14 @@ using EdgeDB; using EdgeDB.ExampleApp; -using EdgeDB.QueryNodes; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; +using System.Linq.Expressions; +using static EdgeDB.ExampleApp.Examples.JsonResults; + +var t = "TTT"; +Expression> s = (x) => x.Name == t; Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() diff --git a/src/EdgeDB.Net.Driver/Codecs/Object.cs b/src/EdgeDB.Net.Driver/Codecs/Object.cs index 1a96c921..28b05ec0 100644 --- a/src/EdgeDB.Net.Driver/Codecs/Object.cs +++ b/src/EdgeDB.Net.Driver/Codecs/Object.cs @@ -60,7 +60,7 @@ public void SerializeArguments(PacketWriter writer, object? value) object?[]? values = null; if (value is IDictionary dict) - values = dict.Values.ToArray(); + values = _propertyNames.Select(x => dict[x]).ToArray(); else if (value is object?[] arr) value = arr; diff --git a/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs b/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs index 6767cc0d..9cd074a8 100644 --- a/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs +++ b/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs @@ -6,6 +6,10 @@ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class EdgeDBTypeAttribute : Attribute { + /// + /// Gets or sets the module name for this type. + /// + public string? ModuleName { get; init; } internal readonly string? Name; /// diff --git a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs index 708500aa..b262be76 100644 --- a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs +++ b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs @@ -11,6 +11,21 @@ namespace EdgeDB { internal class ReflectionUtils { + public static bool IsInstanceOfGenericType(Type genericType, Type toCheck) + { + Type? type = toCheck; + while (type != null) + { + if (type.IsGenericType && + type.GetGenericTypeDefinition() == genericType) + { + return true; + } + type = type.BaseType; + } + return false; + } + public static bool IsSubclassOfRawGeneric(Type generic, Type? toCheck) { while (toCheck is not null && toCheck != typeof(object)) diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs b/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs index a4b51a35..24ac1bd1 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs @@ -1367,6 +1367,14 @@ public sealed partial class EdgeQL public static long Count(IEnumerable a) { return default!; } #endregion + #region Count + /// + /// A function that represents the EdgeQL version of: count() + /// + [EquivalentOperator(typeof(EdgeDB.Operators.SetsCount))] + public static long Count(IQueryBuilder a) { return default!; } + #endregion + #region Enumerate /// /// A function that represents the EdgeQL version of: enumerate() diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs index acbb221e..c67866ba 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs @@ -11,7 +11,11 @@ namespace EdgeDB internal static class TypeExtensions { public static string GetEdgeDBTypeName(this Type type) - => type.GetCustomAttribute()?.Name ?? type.Name; + { + var attr = type.GetCustomAttribute(); + var name = attr?.Name ?? type.Name; + return attr != null ? $"{(attr.ModuleName != null ? $"{attr.ModuleName}::" : "")}{name}" : name; + } public static string GetEdgeDBPropertyName(this MemberInfo info) => info.GetCustomAttribute()?.Name ?? TypeBuilder.NamingStrategy.GetName(info); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 99c6bcec..d9b1f058 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -12,17 +12,51 @@ namespace EdgeDB { + /// + /// A static class providing methods for building queries. + /// + public static class QueryBuilder + { + /// + public static ISelectQuery Select() + => new QueryBuilder().Select(); + + /// + public static ISelectQuery Select(Expression> selectFunc) + => new QueryBuilder().Select(selectFunc); + + /// + public static ISelectQuery Select(Expression> shape) + => new QueryBuilder().Select(shape); + + /// + public static IInsertQuery Insert(TType value, bool returnInsertedValue = true) + => new QueryBuilder().Insert(value, returnInsertedValue); + + /// + public static IInsertQuery Insert(Expression> value, bool returnInsertedValue = true) + => new QueryBuilder().Insert(value, returnInsertedValue); + + /// + public static IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true) + => new QueryBuilder().Update(updateFunc, returnUpdatedValue); + + /// + public static IDeleteQuery Delete() + => new QueryBuilder().Delete; + } + /// /// Represents a query builder used to build queries against . /// /// - public class QueryBuilder : IQueryBuilder + public sealed class QueryBuilder : IQueryBuilder { private readonly List _nodes; private QueryNode? CurrentUserNode => _nodes.LastOrDefault(x => !x.IsAutoGenerated); private readonly Dictionary _queryGlobals; private SchemaInfo? _schemaInfo; - + private readonly Dictionary _queryVariables; static QueryBuilder() { QueryObjectManager.Initialize(); @@ -32,18 +66,23 @@ internal QueryBuilder() { _nodes = new(); _queryGlobals = new(); + _queryVariables = new(); } - internal QueryBuilder(List nodes, Dictionary globals) + internal QueryBuilder(List nodes, Dictionary globals, Dictionary variables) { _nodes = nodes; _queryGlobals = globals; + _queryVariables = variables; } + internal void AddQueryVariable(string name, object? value) + => _queryVariables[name] = value; + private QueryBuilder EnterNewType() - => new(_nodes, _queryGlobals); + => new(_nodes, _queryGlobals, _queryVariables); - private TNode AddNode(NodeContext context, bool autoGenerated = false) + private TNode AddNode(NodeContext context, bool autoGenerated = false, QueryNode? parent = null) where TNode : QueryNode { // create the node and a builder @@ -59,6 +98,8 @@ private TNode AddNode(NodeContext context, bool autoGenerated = false) _nodes.Add(node); + parent?.SubNodes.Add(node); + return node; } @@ -108,7 +149,12 @@ internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) return new BuiltQuery(string.Join(' ', query)) { - Parameters = parameters.SelectMany(x => x).DistinctBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value), + Parameters = parameters + .SelectMany(x => x) + .DistinctBy(x => x.Key) + .Concat(_queryVariables) + .ToDictionary(x => x.Key, x => x.Value), + Globals = !includeGlobalsInQuery ? _queryGlobals : null }; } @@ -144,6 +190,12 @@ public ISelectQuery Select() return this; } + public ISelectQuery Select(Expression> selectFunc) + { + AddNode(new SelectContext(typeof(TResult)) { SelectExpressional = true, Shape = selectFunc }, true); + return EnterNewType(); + } + /// public ISelectQuery Select(Expression> shape) { @@ -168,7 +220,7 @@ public ISelectQuery Select(Expression Insert(TType value, bool returnInsertedValue = true) { var selectedGlobal = returnInsertedValue ? QueryUtils.GenerateRandomVariableName() : null; - AddNode(new InsertContext(typeof(TType)) + var insertNode = AddNode(new InsertContext(typeof(TType)) { Value = value, SetAsGlobal = returnInsertedValue, @@ -180,7 +232,7 @@ public IInsertQuery Insert(TType value, bool returnInsertedValue = true) AddNode(new SelectContext(typeof(TType)) { SelectName = selectedGlobal, - }, true); + }, true, insertNode); } return this; @@ -190,7 +242,7 @@ public IInsertQuery Insert(TType value, bool returnInsertedValue = true) public IInsertQuery Insert(Expression> value, bool returnInsertedValue = true) { var selectedGlobal = returnInsertedValue ? QueryUtils.GenerateRandomVariableName() : null; - AddNode(new InsertContext(typeof(TType)) + var insertNode = AddNode(new InsertContext(typeof(TType)) { Value = value, SetAsGlobal = returnInsertedValue, @@ -202,7 +254,7 @@ public IInsertQuery Insert(Expression> value, b AddNode(new SelectContext(typeof(TType)) { SelectName = selectedGlobal, - }, true); + }, true, insertNode); } return this; @@ -212,7 +264,7 @@ public IInsertQuery Insert(Expression> value, b public IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true) { var selectedGlobal = returnUpdatedValue ? QueryUtils.GenerateRandomVariableName() : null; - AddNode(new UpdateContext(typeof(TType)) + var updateNode = AddNode(new UpdateContext(typeof(TType)) { UpdateExpression = updateFunc, SetAsGlobal = returnUpdatedValue, @@ -224,7 +276,7 @@ public IUpdateQuery Update(Expression> updateFunc, boo AddNode(new SelectContext(typeof(TType)) { SelectName = selectedGlobal, - }, true); + }, true, updateNode); } return this; @@ -512,6 +564,16 @@ public interface IQueryBuilder : /// ISelectQuery Select(); + /// + /// Adds a SELECT statement selecting the provided expression. + /// + /// The return result of the select expression. + /// The selecting expression. + /// + /// A . + /// + ISelectQuery Select(Expression> selectFunc); + /// /// Adds a SELECT statement selecting the current with the given shape. /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index b3dd7ba8..763ecb71 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -1,7 +1,9 @@ -using EdgeDB.Operators; +using EdgeDB.Interfaces.Queries; +using EdgeDB.Operators; using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; @@ -36,6 +38,13 @@ public TType Global(string name) public TType Local(string name) => default!; + [EquivalentOperator(typeof(LocalReference))] + public TType UnsafeLocal(string name) + => default!; + + public TType Raw(string query) + => default!; + /// /// Includes a property within a shape. /// @@ -45,5 +54,15 @@ public TType Local(string name) /// public TType Include() => default!; + + public TType IncludeLink(Expression> shape) + => default!; + + public TType[] IncludeMultiLink(Expression> shape) + => default!; + + public TCollection IncludeMultiLink(Expression> shape) + where TCollection : IEnumerable + => default!; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs index 69d7fb3b..47d0d81d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs @@ -11,6 +11,7 @@ internal class SelectContext : NodeContext { public LambdaExpression? Shape { get; init; } public string? SelectName { get; set; } + public bool SelectExpressional { get; set; } public SelectContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index a05b3a0f..6eeecc96 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -139,7 +139,8 @@ public void ElseDefault() public void Else(IQueryBuilder builder) { - var userNodes = builder.Nodes.Where(x => !x.IsAutoGenerated); + // remove addon & autogen nodes. + var userNodes = builder.Nodes.Where(x => !builder.Nodes.Any(y => y.SubNodes.Contains(x)) || !x.IsAutoGenerated); // TODO: better checks for this, future should add a callback to add the // node with its context so any parent builder can change contexts for nodes @@ -158,16 +159,10 @@ public void Else(IQueryBuilder builder) ) ); - - var newBuilder = new QueryBuilder(userNodes.ToList(), globals); + var newBuilder = new QueryBuilder(userNodes.ToList(), globals, variables.ToDictionary(x => x.Key, x=> x.Value)); var result = newBuilder.BuildWithGlobals(); _children.Append($" else ({result.Query})"); - - foreach (var variable in variables) - SetVariable(variable.Key, variable.Value); - foreach (var global in globals) - SetGlobal(global.Key, global.Value); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index 77a30c46..e015b72f 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -22,10 +22,10 @@ public bool IsAutoGenerated => Builder.IsAutoGenerated; public bool RequiresIntrospection { get; protected set; } - public SchemaInfo? SchemaInfo { get; set; } internal List ReferencedGlobals { get; } = new(); internal List ReferencedVariables { get; } = new(); + internal List SubNodes { get; } = new(); internal readonly NodeBuilder Builder; internal StringBuilder Query diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 052841c2..9ae49b3a 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -47,26 +47,33 @@ private string GetShape() return GetDefaultShape(); } + if (Context.SelectExpressional) + { + return ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context); + } + // if its a call to a global - if(Context.Shape.Body is MethodCallExpression) + if (Context.Shape.Body is MethodCallExpression) { var exp = ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context); Context.SelectName = exp; return GetDefaultShape(); } - else if (Context.Shape.Body is NewExpression) + else if (Context.Shape.Body is NewExpression or MemberInitExpression) { - return ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context); + return $"{{ {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context)} }}"; } - return ""; - + throw new NotSupportedException($"Cannot use {Context.Shape.GetType().Name} as a shape"); } public override void Visit() { var shape = GetShape(); - Query.Append($"select {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()} {shape}"); + if (Context.SelectExpressional) + Query.Append($"select {shape}"); + else + Query.Append($"select {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()} {shape}"); } public void Filter(LambdaExpression expression) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs index ce680f23..0ec1b14d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs @@ -26,7 +26,7 @@ internal static string ParseObject(object? obj) SerializationMethod.Lower => $"\"{obj.ToString()?.ToLower()}\"", SerializationMethod.Numeric => Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}", _ => "{}" - } : Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}"; + } : $"\"{obj.ToString()}\""; } return obj switch diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs index 8089520d..6bebdf02 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -1,5 +1,6 @@ using EdgeDB.Interfaces.Queries; using EdgeDB.QueryNodes; +using EdgeDB.Schema; using System; using System.Collections.Generic; using System.Linq; @@ -10,13 +11,213 @@ namespace EdgeDB { - public sealed class QueryableCollection : QueryBuilder + public sealed class QueryableCollection { + /// + /// Gets a query builder for . + /// + public QueryBuilder QueryBuilder + => new(); + private readonly IEdgeDBQueryable _edgedb; internal QueryableCollection(IEdgeDBQueryable edgedb) { _edgedb = edgedb; } + + /// + /// Adds a value to s' collection. + /// + /// The value to add. + /// A cancellation token to cancel the asynchronous insert operation. + /// The item to add violates an exclusive constraint. + /// The added value. + public Task AddAsync(TType item, CancellationToken token = default) + => QueryBuilder.Insert(item).ExecuteAsync(_edgedb, token); + + /// + /// Adds or updates an existing value based on the types unique constraints. + /// + /// + /// This method requires introspection and can preform more than one query + /// to cache the current clients schema. + /// + /// The value to add + /// The factory to update the item. + /// A cancellation token to cancel the asynchronous insert operation. + /// The added value. + public Task AddOrUpdateAsync(TType item, Expression> updateFactory, CancellationToken token = default) + => QueryBuilder + .Insert(item) + .UnlessConflict() + .Else(b => + (Interfaces.ISingleCardinalityExecutable)b.Update(updateFactory) + ) + .ExecuteAsync(_edgedb, token); + + /// + /// Adds or updates an existing value based on the types unique constraints. + /// If a conflict is met, this function will generate a default update factory that + /// updates non-readonly and non-exclusive properties. + /// + /// + /// This method requires introspection and can preform more than one query + /// to cache the current clients schema. + /// + /// The value to add and use to update any existing values. + /// A cancellation token to cancel the asynchronous insert operation. + /// The added or updated value. + public async Task AddOrUpdateAsync(TType item, CancellationToken token = default) + { + var props = await GetPropertiesAsync(false, false, token).ConfigureAwait(false); + + // create the update factory. + var updateFactory = Expression.Lambda>( + Expression.MemberInit( + Expression.New(typeof(TType)), props.Select(x => + Expression.Bind(x, Expression.MakeMemberAccess(Expression.Constant(item), x))) + ), + Expression.Parameter(typeof(TType), "x") + ); + + return await QueryBuilder + .Insert(item) + .UnlessConflict() + .Else(q => + (Interfaces.ISingleCardinalityExecutable)q.Update(updateFactory, false) + ).ExecuteAsync(_edgedb, token).ConfigureAwait(false); + } + + /// + /// Gets or adds a value to the collection. + /// + /// + /// This method requires introspection and can preform more than one query + /// to cache the current clients schema. + /// The method will attempt to insert the value if it does not exist + /// based on any properties with exclusive constraints, if a conflict is met + /// then the conflicting object will be returned. + /// + /// The item to get or add. + /// A cancellation token to cancel the asynchronous insert operation. + /// The inserted or conflicting item. + public Task GetOrAddAsync(TType item, CancellationToken token = default) + => QueryBuilder + .Insert(item) + .UnlessConflict() + .ElseReturn() + .ExecuteAsync(_edgedb, token); + + /// + /// Deletes a value from the collection. + /// + /// + /// This method may require introspection of the schema to + /// determine the filter for the delete statement. + /// + /// The value to delete. + /// A cancellation token to cancel the asynchronous insert operation. + /// Whether or not the value was deleted. + /// No unique constraints found to generate filter condition. + public async Task DeleteAsync(TType item, CancellationToken token = default) + { + // try get the objects id + if (QueryObjectManager.TryGetObjectId(item, out var id)) + return ( + await QueryBuilder + .Delete + .Filter((_, ctx) => ctx.UnsafeLocal("id") == id) + .ExecuteAsync(_edgedb, token).ConfigureAwait(false) + ).Any(); + + // try to get exclusive property set on the instance + var props = await GetPropertiesAsync(exclusive: true, token: token).ConfigureAwait(false); + + if (!props.Any()) + throw new NotSupportedException("No unique constraints found to generate filter condition."); + + // remove non defaults + props = props.Where(x => ReflectionUtils.GetDefault(x.PropertyType) != x.GetValue(item)); + + + Dictionary variables = new(); + // generate the expression + var expr = Expression.Lambda>(props.Select(x => + { + var name = QueryUtils.GenerateRandomVariableName(); + var typeCast = PacketSerializer.GetEdgeQLType(x.PropertyType); + var e = Expression.Equal( + Expression.MakeMemberAccess(Expression.Parameter(typeof(TType), "x"), x), + Expression.Constant($"<{typeCast}>{name}") + ); + + variables[x] = name; + + return e; + } + ).Aggregate((x, y) => Expression.And(x, y)), Expression.Parameter(typeof(TType), "x"), Expression.Parameter(typeof(QueryContext), "ctx")); + + var builder = QueryBuilder; + foreach (var (prop, name) in variables) + builder.AddQueryVariable(name, prop.GetValue(item)); + return (await builder.Delete.Filter(expr).ExecuteAsync(_edgedb, token).ConfigureAwait(false)).Any(); + } + + /// + /// Attempts to add a value to this collection. + /// + /// + /// This method requires introspection and can preform more than one query + /// to cache the current clients schema. + /// + /// The value to add. + /// A cancellation token to cancel the asynchronous insert operation. + /// + /// if the value was added successfully, otherwise . + /// + public async Task TryAddAsync(TType item, CancellationToken token = default) + { + var query = QueryBuilder.Insert(item, false).UnlessConflict(); + var result = await query.ExecuteAsync(_edgedb, token); + return result != null; + } + + /// + /// Deletes all values that match a given predicate. + /// + /// The predicate which will determine if a value will be deleted. + /// A cancellation token to cancel the asynchronous insert operation. + /// The number of values deleted. + public Task DeleteWhereAsync(Expression> filter, CancellationToken token = default) + => ((Interfaces.ISingleCardinalityExecutable)QueryBuilder.Select(() => EdgeQL.Count(QueryBuilder.Delete.Filter(filter)))).ExecuteAsync(_edgedb, token); + + /// + /// Filters the current collection by a predicate. + /// + /// The predicate to filter by. + /// A cancellation token to cancel the asynchronous select operation. + /// A collection of that match the provided predicate. + public async Task> WhereAsync(Expression> filter, + CancellationToken token = default) + => await QueryBuilder.Select().Filter(filter).ExecuteAsync(_edgedb, token); + + private async ValueTask> GetPropertiesAsync(bool? exclusive = null, bool? @readonly = null, CancellationToken token = default) + { + var props = typeof(TType).GetProperties().Where(x => x.GetCustomAttribute() == null); + + var introspection = await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(_edgedb, token).ConfigureAwait(false); + + if (!introspection.TryGetObjectInfo(typeof(TType), out var info)) + throw new NotSupportedException($"Cannot use {typeof(TType).Name} as there is no schema information for it."); + + return props.Where(x => + { + var edgedbName = x.GetEdgeDBPropertyName(); + return info.Properties!.Any(x => x.Name == edgedbName && + (!exclusive.HasValue || x.IsExclusive == exclusive.Value) && + (!@readonly.HasValue || x.IsReadonly == @readonly.Value)); + }); + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs index 63eba99e..69c7da87 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs @@ -6,6 +6,7 @@ namespace EdgeDB.Schema.DataTypes { + [EdgeDBType(ModuleName = "schema")] internal class ObjectType { [EdgeDBIgnore] diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs index 4c9b31f3..a529e302 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs @@ -25,6 +25,29 @@ public static ValueTask GetOrCreateSchemaIntrospectionAsync(IEdgeDBQ } private static async Task IntrospectSchemaAsync(IEdgeDBQueryable edgedb, CancellationToken token) - => _schemas[edgedb] = new SchemaInfo(await edgedb.QueryAsync(Properties.Resources.INTROSPECT_QUERY, token: token).ConfigureAwait(false)); + { + var result = await QueryBuilder.Select(ctx => new ObjectType + { + Id = ctx.Include(), + IsAbstract = ctx.Include(), + Name = ctx.Include(), + Properties = ctx.IncludeMultiLink(() => new Property + { + Cardinality = (string)ctx.UnsafeLocal("cardinality") == "One" + ? ctx.UnsafeLocal("required") ? DataTypes.Cardinality.One : DataTypes.Cardinality.AtMostOne + : ctx.UnsafeLocal("required") ? DataTypes.Cardinality.AtLeastOne : DataTypes.Cardinality.Many, + Name = ctx.Include(), + TargetId = ctx.UnsafeLocal("target.id"), + IsLink = ctx.Raw("[IS schema::Link]") != null, + IsExclusive = ctx.Raw("exists (select .constraints filter .name = 'std::exclusive')"), + IsComputed = EdgeQL.Length(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); + + return _schemas[edgedb] = new SchemaInfo(result); + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs index 71e3cbe9..13acbf7e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs @@ -9,7 +9,7 @@ namespace EdgeDB.Translators.Expressions { internal class BinaryExpressionTranslator : ExpressionTranslator { - public override string Translate(BinaryExpression expression, ExpressionContext context) + public override string? Translate(BinaryExpression expression, ExpressionContext context) { var left = TranslateExpression(expression.Left, context); var right = TranslateExpression(expression.Right, context); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConditionalExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConditionalExpressionTranslator.cs new file mode 100644 index 00000000..51850ebe --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConditionalExpressionTranslator.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Expressions +{ + internal class ConditionalExpressionTranslator : ExpressionTranslator + { + public override string? Translate(ConditionalExpression expression, ExpressionContext context) + { + var condition = TranslateExpression(expression.Test, context); + var ifTrue = TranslateExpression(expression.IfTrue, context); + var ifFalse = TranslateExpression(expression.IfFalse, context); + + + return $"{ifTrue} if {condition} else {ifFalse}"; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs index 9f1299fd..ce1c0e8e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs @@ -9,9 +9,9 @@ namespace EdgeDB.Translators.Expressions { internal class ConstantExpressionTranslator : ExpressionTranslator { - public override string Translate(ConstantExpression expression, ExpressionContext context) + public override string? Translate(ConstantExpression expression, ExpressionContext context) { - return context.IsTypeReference && expression.Value is string str ? str : QueryUtils.ParseObject(expression.Value); + return context.StringWithoutQuotes && expression.Value is string str ? str : QueryUtils.ParseObject(expression.Value); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index 1b203e06..c89f77b1 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -15,8 +15,9 @@ internal class ExpressionContext public Dictionary Parameters { get; } private readonly IDictionary _queryObjects; - public bool IsTypeReference { get; set; } + public bool StringWithoutQuotes { get; set; } public Type? LocalScope { get; set; } + public bool IsShape { get; set; } public ExpressionContext(NodeContext context, LambdaExpression rootExpression, IDictionary queryArguments) diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs index fb889519..48a27d2e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -13,9 +13,9 @@ namespace EdgeDB internal abstract class ExpressionTranslator : ExpressionTranslator where TExpression : Expression { - public abstract string Translate(TExpression expression, ExpressionContext context); + public abstract string? Translate(TExpression expression, ExpressionContext context); - public override string Translate(Expression expression, ExpressionContext context) + public override string? Translate(Expression expression, ExpressionContext context) { return Translate((TExpression)expression, context); } @@ -49,7 +49,7 @@ protected static bool TryGetExpressionOperator(ExpressionType type, [MaybeNullWh => _expressionOperators.TryGetValue(type, out edgeqlOperator); - public abstract string Translate(Expression expression, ExpressionContext context); + public abstract string? Translate(Expression expression, ExpressionContext context); public static string Translate(Expression expression) => Translate(expression); @@ -67,9 +67,9 @@ protected static string TranslateExpression(Expression expression, ExpressionCon expType = expType.BaseType!; if (_translators.TryGetValue(expType, out var translator)) - return translator.Translate(expression, context); + return translator.Translate(expression, context)!; - throw new Exception("AAAA"); + throw new NotSupportedException($"Failed to find translator for expression type: {expType.Name}.{expression.NodeType}"); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index e1f675c4..52898856 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -10,7 +10,7 @@ namespace EdgeDB.Translators.Expressions { internal class MemberExpressionTranslator : ExpressionTranslator { - public override string Translate(MemberExpression expression, ExpressionContext context) + public override string? Translate(MemberExpression expression, ExpressionContext context) { if(expression.Expression is ConstantExpression constant) { diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs index adcd29f0..63956831 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs @@ -9,7 +9,7 @@ namespace EdgeDB.Translators.Expressions { internal class MemberInitExpressionTranslator : ExpressionTranslator { - public override string Translate(MemberInitExpression expression, ExpressionContext context) + public override string? Translate(MemberInitExpression expression, ExpressionContext context) { List initializations = new(); @@ -20,7 +20,17 @@ public override string Translate(MemberInitExpression expression, ExpressionCont case MemberAssignment assignment: { var value = TranslateExpression(assignment.Expression, context); - initializations.Add($"{assignment.Member.GetEdgeDBPropertyName()} := {value}"); + + if (value is null) + initializations.Add($"{assignment.Member.GetEdgeDBPropertyName()}"); + else if (context.IsShape) + { + initializations.Add($"{assignment.Member.GetEdgeDBPropertyName()}: {{ {value} }}"); + context.IsShape = false; + } + else + initializations.Add($"{assignment.Member.GetEdgeDBPropertyName()} := {value}"); + } break; } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index 502aea58..7cf8a58c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -11,28 +11,57 @@ namespace EdgeDB.Translators.Expressions { internal class MethodCallExpressionTranslator : ExpressionTranslator { - public override string Translate(MethodCallExpression expression, ExpressionContext context) + public override string? Translate(MethodCallExpression expression, ExpressionContext context) { // special case for local - if(expression.Method.DeclaringType == typeof(QueryContext) && expression.Method.Name == "Local") + if(expression.Method.DeclaringType == typeof(QueryContext)) { - // check arg scope - var rawArg = TranslateExpression(expression.Arguments[0], context.Enter(x => x.IsTypeReference = true)); - var rawPath = rawArg.Split('.'); - string[] parsedPath = new string[rawPath.Length]; - - for(int i = 0; i != rawPath.Length; i++) + switch (expression.Method.Name) { - var prop = (MemberInfo?)context.LocalScope?.GetProperty(rawPath[i]) ?? - context.LocalScope?.GetField(rawPath[i]) ?? - (MemberInfo?)context.NodeContext.CurrentType.GetProperty(rawPath[i]) ?? - context.NodeContext.CurrentType.GetField(rawPath[i]); - if (prop is null) - throw new InvalidOperationException($"The property \"{rawPath[i]}\" within \"{rawArg}\" is out of scope"); - parsedPath[i] = prop.GetEdgeDBPropertyName(); - } + case nameof(QueryContext.Local): + { + // check arg scope + var rawArg = TranslateExpression(expression.Arguments[0], context.Enter(x => x.StringWithoutQuotes = true)); + var rawPath = rawArg.Split('.'); + string[] parsedPath = new string[rawPath.Length]; - return $".{string.Join('.', parsedPath)}"; + for (int i = 0; i != rawPath.Length; i++) + { + var prop = (MemberInfo?)context.LocalScope?.GetProperty(rawPath[i]) ?? + context.LocalScope?.GetField(rawPath[i]) ?? + (MemberInfo?)context.NodeContext.CurrentType.GetProperty(rawPath[i]) ?? + context.NodeContext.CurrentType.GetField(rawPath[i]); + if (prop is null) + throw new InvalidOperationException($"The property \"{rawPath[i]}\" within \"{rawArg}\" is out of scope"); + parsedPath[i] = prop.GetEdgeDBPropertyName(); + } + + return $".{string.Join('.', parsedPath)}"; + } + case nameof(QueryContext.UnsafeLocal): + { + return $".{TranslateExpression(expression.Arguments[0], context.Enter(x => x.StringWithoutQuotes = true))}"; + } + case nameof(QueryContext.Include): + { + // do nothing here + return null; + } + case nameof(QueryContext.IncludeLink) or nameof(QueryContext.IncludeMultiLink): + { + // parse the inner shape + var shape = TranslateExpression(expression.Arguments[0], context); + context.IsShape = true; + return shape; + } + case nameof(QueryContext.Raw): + { + return TranslateExpression(expression.Arguments[0], context.Enter(x => x.StringWithoutQuotes = true)); + } + default: + throw new NotImplementedException($"{expression.Method.Name} does not have an implementation. This is a bug, please file a github issue with your query to reproduce this exception."); + + } } // check if the method has an 'EquivalentOperator' attribute @@ -42,8 +71,25 @@ public override string Translate(MethodCallExpression expression, ExpressionCont { // parse the parameters var argsArray = new object[expression.Arguments.Count]; - for(int i = 0; i != argsArray.Length; i++) - argsArray[i] = TranslateExpression(expression.Arguments[i], context); + var parameters = expression.Method.GetParameters(); + for (int i = 0; i != argsArray.Length; i++) + { + var arg = expression.Arguments[i]; + if (parameters[i].ParameterType.IsAssignableTo(typeof(IQueryBuilder))) + { + // compile and run the value + var builder = (IQueryBuilder)Expression.Lambda(arg).Compile().DynamicInvoke()!; + + // TODO: support variables & globals + var result = builder.BuildWithGlobals(); + if ((result.Parameters?.Any() ?? false) || (result.Globals?.Any() ?? false)) + throw new NotSupportedException("Cannot use queries with parameters or globals within a sub-query expression"); + + argsArray[i] = $"({result.Query})"; + } + else + argsArray[i] = TranslateExpression(arg, context); + } return edgeqlOperator.Build(argsArray); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs index edffae35..4b75b79d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs @@ -11,7 +11,7 @@ namespace EdgeDB.Translators.Expressions { internal class NewExpressionTranslator : ExpressionTranslator { - public override string Translate(NewExpression expression, ExpressionContext context) + public override string? Translate(NewExpression expression, ExpressionContext context) { string[] shape = new string[expression.Arguments.Count]; diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs new file mode 100644 index 00000000..e6e2d934 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Expressions +{ + internal class UnaryExpressionTranslator : ExpressionTranslator + { + public override string? Translate(UnaryExpression expression, ExpressionContext context) + { + switch (expression.NodeType) + { + case ExpressionType.Quote when expression.Operand is LambdaExpression lambda: + return TranslateExpression(lambda.Body, context.Enter(x => x.StringWithoutQuotes = false)); + case ExpressionType.Convert: + { + var value = TranslateExpression(expression.Operand, context); + + if (value is null) + return null; // nullable converters for include, ex Guid? -> Guid + + // dotnet nullable check + if (ReflectionUtils.IsInstanceOfGenericType(typeof(Nullable<>), expression.Type) && + expression.Type.GenericTypeArguments[0] == expression.Operand.Type) + { + // no need to cast in edgedb, return the value + return value; + } + + var type = PacketSerializer.GetEdgeQLType(expression.Type) ?? expression.Type.GetEdgeDBTypeName(); + + return $"<{type}>{value}"; + } + + default: + if (!TryGetExpressionOperator(expression.NodeType, out var op)) + throw new NotSupportedException($"Failed to find operator for node type {expression.NodeType}"); + return op.Build(TranslateExpression(expression.Operand, context)); + } + + throw new NotSupportedException($"Failed to find converter for {expression.NodeType}!"); + } + } +} diff --git a/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml b/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml index dfc8baeb..e9efafed 100644 --- a/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml +++ b/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml @@ -1223,6 +1223,13 @@ sets: - parameters: - IEnumerable + - operator: "count({0})" + name: Count + return: long + functions: + - parameters: + - IQueryBuilder + - operator: "enumerate({0})" name: Enumerate return: IEnumerable> From ac8df85235497b096626fb131a9691c85132777b Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sat, 2 Jul 2022 22:38:01 -0300 Subject: [PATCH 11/70] remove test code --- examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs | 4 +--- examples/EdgeDB.Examples.ExampleApp/Program.cs | 5 ----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index ed37307a..3ea6d3ca 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -32,9 +32,7 @@ public async Task ExecuteAsync(EdgeDBClient client) { var collection = client.GetCollection(); - var wasDeleted = await collection.DeleteAsync(new LinkPerson { Email = "test@mail.com", Name = "test" }); - - var t = new QueryBuilder().Select(() => EdgeQL.Count(new QueryBuilder().Delete)).Build(); + // To be written } } } diff --git a/examples/EdgeDB.Examples.ExampleApp/Program.cs b/examples/EdgeDB.Examples.ExampleApp/Program.cs index 6367b056..e8f593a1 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Program.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Program.cs @@ -4,11 +4,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; -using System.Linq.Expressions; -using static EdgeDB.ExampleApp.Examples.JsonResults; - -var t = "TTT"; -Expression> s = (x) => x.Name == t; Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() From 0d53c55d751bef9dc30400723071fccdfe1ec61f Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 5 Jul 2022 13:50:56 -0300 Subject: [PATCH 12/70] QB demo and global ref reuse in queries --- .../Examples/QueryBuilder.cs | 169 +++++++++++++++++- .../Extensions/EdgeDBExtensions.cs | 5 + .../Extensions/TypeExtensions.cs | 23 +++ .../Interfaces/Queries/IInsertQuery.cs | 2 +- .../Interfaces/Queries/IUpdateQuery.cs | 2 +- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 65 ++++--- src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 30 ++++ src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs | 28 +++ .../QueryNodes/Contexts/UpdateContext.cs | 3 + .../QueryNodes/Contexts/WithContext.cs | 2 +- .../QueryNodes/InsertNode.cs | 38 ++-- .../QueryNodes/NodeBuilder.cs | 7 +- .../QueryNodes/QueryNode.cs | 19 +- .../QueryNodes/SelectNode.cs | 10 +- .../QueryNodes/UpdateNode.cs | 19 +- .../QueryNodes/WithNode.cs | 27 +-- src/EdgeDB.Net.QueryBuilder/QueryUtils.cs | 89 ++++++++- .../QueryableCollection.cs | 107 +++++------ src/EdgeDB.Net.QueryBuilder/SubQuery.cs | 18 +- .../Expressions/ExpressionContext.cs | 13 +- .../Expressions/ExpressionTranslator.cs | 4 +- .../Expressions/MemberExpressionTranslator.cs | 7 +- .../MemberInitExpressionTranslator.cs | 89 ++++++++- .../MethodCallExpressionTranslator.cs | 10 +- .../Expressions/NewExpressionTranslator.cs | 2 +- 25 files changed, 635 insertions(+), 153 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 3ea6d3ca..83dee67a 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -1,4 +1,5 @@ using EdgeDB.Schema; +using EdgeDB.Schema.DataTypes; using EdgeDB.Serializer; using Microsoft.Extensions.Logging; using System; @@ -11,7 +12,7 @@ namespace EdgeDB.ExampleApp.Examples { - internal class QueryBuilder : IExample + internal class QueryBuilderExample : IExample { public ILogger? Logger { get; set; } @@ -22,17 +23,173 @@ public class LinkPerson public LinkPerson? BestFriend { get; set; } } - public class Person + + public async Task ExecuteAsync(EdgeDBClient client) { - public string? Name { get; set; } - public string? Email { get; set; } + await QueryBuilderDemo(client); + await QueryableCollectionDemo(client); } - public async Task ExecuteAsync(EdgeDBClient client) + private static async Task QueryBuilderDemo(EdgeDBClient client) { + // Selecting a type with autogen shape + var query = QueryBuilder.Select().Build().Prettify(); + + // Adding a filter, orderby, offset, and limit + query = QueryBuilder + .Select() + .Filter(x => EdgeQL.ILike(x.Name, "e%")) + .OrderBy(x => x.Name) + .Offset(2) + .Limit(10) + .Build() + .Prettify(); + + // Specifying a shape + query = QueryBuilder.Select((ctx) => new LinkPerson + { + Email = ctx.Include(), + Name = ctx.Include(), + BestFriend = ctx.IncludeLink(() => new LinkPerson + { + Email = ctx.Include(), + }) + }).Build().Prettify(); + + // Adding computed properties in our shape + // Note: we need to use a new instance of query builder to provide the + // 'LinkPerson' type as a generic, since its being used for local context + // in the anon type. + query = new QueryBuilder().Select((ctx) => new + { + Name = ctx.Include(), + Email = ctx.Include(), + HasBestfriend = ctx.Local("BestFriend") != null + }).Build().Prettify(); + + // selecting things that are not types + query = QueryBuilder.Select(() => + EdgeQL.Count( + QueryBuilder.Select() + ) + ).Build().Prettify(); + + // Inserting a new type + var person = new LinkPerson + { + Email = "example@example.com", + Name = "example" + }; + + query = QueryBuilder.Insert(person).Build().Prettify(); + + // Complex insert with links & dealing with conflicts + query = (await QueryBuilder + .Insert(new LinkPerson + { + BestFriend = person, + Name = "example2", + Email = "example2@example.com" + }) + .UnlessConflict() + .ElseReturn() + .BuildAsync(client)) + .Prettify(); + + // Manual conflicts + query = QueryBuilder + .Insert(person) + .UnlessConflictOn(x => x.Email) + .ElseReturn() + .Build() + .Prettify(); + + // Autogenerating unless conflict with introspection + query = (await QueryBuilder + .Insert(person) + .UnlessConflict() + .ElseReturn() + .BuildAsync(client)) + .Prettify(); + + // Else statements (upsert demo) + query = (await QueryBuilder + .Insert(person) + .UnlessConflict() + .Else(q => + q.Update(old => new LinkPerson + { + Name = old!.Name!.ToUpper() + }) + ) + .BuildAsync(client)) + .Prettify(); + + // Updating a type + query = QueryBuilder + .Update(old => new LinkPerson + { + Name = "example new name" + }) + .Filter(x => x.Email == "example@example.com") + .Build() + .Prettify(); + + // Deleting types + query = QueryBuilder + .Delete() + .Filter(x => EdgeQL.ILike(x.Name, "e%")) + .Build() + .Prettify(); + } + + private static async Task QueryableCollectionDemo(EdgeDBClient client) + { + // Get a 'collection' object, this class wraps the query + // builder and provides simple CRUD methods. var collection = client.GetCollection(); - // To be written + // Get or add a value + var person = await collection.GetOrAddAsync(new LinkPerson + { + Email = "example@example.com", + Name = "example" + }); + + // we can change properties locally and then call UpdateAsync to update the type in the database. + person.Name = "example new name"; + + await collection.UpdateAsync(person); + + // or we can provide an update function + person = await collection.UpdateAsync(person, old => new LinkPerson + { + Name = "example" + }); + + // we can select types based on a filter + var people = await collection.WhereAsync(x => EdgeQL.ILike(x.Name, "e%")); + + // we can add or update a type + var otherPerson = await collection.AddOrUpdateAsync(new LinkPerson + { + Name = "example2", + Email = "example2@example.com", + BestFriend = person + }); + + // we can delete types + var toBeDeleted = await collection.GetOrAddAsync(new LinkPerson + { + Email = "example3@example.com", + Name = "example3" + }); + + // the result of this delete functions is whether or not it was deleted. + var success = await collection.DeleteAsync(toBeDeleted); + + // we can also delete types based on a filter + var count = await collection.DeleteWhereAsync(x => EdgeQL.ILike(x.Name, "e%")); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs index 268ee50e..a62605ed 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs @@ -12,5 +12,10 @@ public static QueryableCollection GetCollection(this IEdgeDBQuerya { return new QueryableCollection(edgedb); } + + internal static SubQuery SelectSubQuery(this Guid id, Type queryType) + { + return new SubQuery($"(select {queryType.GetEdgeDBTypeName()} filter .id = \"{id}\")"); + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs index c67866ba..91e4ecef 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs @@ -18,5 +18,28 @@ public static string GetEdgeDBTypeName(this Type type) } public static string GetEdgeDBPropertyName(this MemberInfo info) => info.GetCustomAttribute()?.Name ?? TypeBuilder.NamingStrategy.GetName(info); + + public static Type GetMemberType(this MemberInfo info) + { + switch (info) + { + 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) + { + return info switch + { + 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/Interfaces/Queries/IInsertQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs index e826ffb0..9bf4a241 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs @@ -34,6 +34,6 @@ public interface IInsertQuery : ISingleCardinalityExecutable IUnlessConflictOn UnlessConflictOn(Expression> propertySelector); /// - IUnlessConflictOn UnlessConflictOn(Expression> propertySelector); + IUnlessConflictOn UnlessConflictOn(Expression> propertySelector); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs index cdee206d..b81b36c6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs @@ -21,6 +21,6 @@ public interface IUpdateQuery : IMultiCardinalityExecutable IMultiCardinalityExecutable Filter(Expression> filter); /// - IMultiCardinalityExecutable Filter(Expression> filter); + IMultiCardinalityExecutable Filter(Expression> filter); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index d9b1f058..afcc83f4 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -54,7 +54,7 @@ public sealed class QueryBuilder : IQueryBuilder { private readonly List _nodes; private QueryNode? CurrentUserNode => _nodes.LastOrDefault(x => !x.IsAutoGenerated); - private readonly Dictionary _queryGlobals; + private readonly List _queryGlobals; private SchemaInfo? _schemaInfo; private readonly Dictionary _queryVariables; static QueryBuilder() @@ -69,7 +69,7 @@ internal QueryBuilder() _queryVariables = new(); } - internal QueryBuilder(List nodes, Dictionary globals, Dictionary variables) + internal QueryBuilder(List nodes, List globals, Dictionary variables) { _nodes = nodes; _queryGlobals = globals; @@ -86,7 +86,7 @@ private TNode AddNode(NodeContext context, bool autoGenerated = false, Qu where TNode : QueryNode { // create the node and a builder - var builder = new NodeBuilder(context, _queryGlobals, _nodes) + var builder = new NodeBuilder(context, _queryGlobals, _nodes, _queryVariables) { IsAutoGenerated = autoGenerated }; @@ -122,7 +122,8 @@ internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) var with = new WithNode(new NodeBuilder(new WithContext(typeof(TType)) { Values = _queryGlobals - }, _queryGlobals, null)); + }, _queryGlobals, null, _queryVariables)); + with.SchemaInfo = _schemaInfo; with.Visit(); nodes = nodes.Prepend(with).ToList(); } @@ -147,28 +148,29 @@ internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) query.Reverse(); + var variables = parameters + .SelectMany(x => x) + .DistinctBy(x => x.Key); + + variables = variables.Concat(_queryVariables.Where(x => !variables.Any(x => x.Key == x.Key))); + return new BuiltQuery(string.Join(' ', query)) { - Parameters = parameters - .SelectMany(x => x) - .DistinctBy(x => x.Key) - .Concat(_queryVariables) + Parameters = variables .ToDictionary(x => x.Key, x => x.Value), Globals = !includeGlobalsInQuery ? _queryGlobals : null }; } - /// - /// Builds the current query into a . - /// - /// - /// This method resets this query builder. - /// - /// A built query containing the query itself and any query variables used within the query. + /// public BuiltQuery Build() => InternalBuild(); + /// + public ValueTask BuildAsync(IEdgeDBQueryable edgedb, CancellationToken token = default) + => IntrospectAndBuildAsync(edgedb, token); + internal BuiltQuery BuildWithGlobals() => InternalBuild(false); @@ -177,9 +179,9 @@ internal BuiltQuery BuildWithGlobals() public QueryBuilder With(string name, object? value) { if (QueryObjectManager.TryGetObjectId(value, out var id)) - value = new SubQuery($"(select {value!.GetType().GetEdgeDBTypeName()} filter .id = \"{id}\")"); - - _queryGlobals[name] = value; + _queryGlobals.Add(new QueryGlobal(name, new SubQuery($"(select {value!.GetType().GetEdgeDBTypeName()} filter .id = \"{id}\")"), value)); + else + _queryGlobals.Add(new(name, value)); return this; } @@ -405,7 +407,7 @@ private QueryBuilder Else(Func, IMultiCardinalityExe if (CurrentUserNode is not InsertNode insertNode) throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); - var builder = new QueryBuilder(); + var builder = new QueryBuilder(new(), _queryGlobals, new()); func(builder); insertNode.Else(builder); @@ -417,7 +419,7 @@ private QueryBuilder Else(Func, ISingleCardinalityEx if (CurrentUserNode is not InsertNode insertNode) throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); - var builder = new QueryBuilder(); + var builder = new QueryBuilder(new(), _queryGlobals, new()); func(builder); insertNode.Else(builder); @@ -454,14 +456,14 @@ ISelectQuery ISelectQuery.Limit(Expression UnlessConflict(); IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) => UnlessConflictOn(propertySelector); - IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) + IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) => UnlessConflictOn(propertySelector); #endregion #region IUpdateQuery IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) => Filter(filter); - IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) + IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) => Filter(filter); #endregion @@ -524,7 +526,8 @@ private async ValueTask IntrospectAndBuildAsync(IEdgeDBQueryable edg #region IQueryBuilder IReadOnlyCollection IQueryBuilder.Nodes => _nodes; - IReadOnlyDictionary IQueryBuilder.Globals => _queryGlobals; + IReadOnlyCollection IQueryBuilder.Globals => _queryGlobals; + IReadOnlyDictionary IQueryBuilder.Variables => _queryVariables; IQueryBuilder IQueryBuilder.With(string name, object? value) => With(name, value); BuiltQuery IQueryBuilder.BuildWithGlobals() => BuildWithGlobals(); #endregion @@ -646,16 +649,28 @@ public interface IQueryBuilder : public interface IQueryBuilder { internal IReadOnlyCollection Nodes { get; } - internal IReadOnlyDictionary Globals { get; } + internal IReadOnlyCollection Globals { get; } + internal IReadOnlyDictionary Variables { get; } /// /// Builds the current query. /// + /// + /// If the query requires introspection please use . + /// /// /// A . /// BuiltQuery Build(); + /// + /// Builds the current query asynchronously, allowing database introspection. + /// + /// The client to preform introspection with. + /// A cancellation token to cancel the asynchronous operation. + /// A . + ValueTask BuildAsync(IEdgeDBQueryable edgedb, CancellationToken token = default); + internal BuiltQuery BuildWithGlobals(); } @@ -674,7 +689,7 @@ public class BuiltQuery /// Gets a collection of parameters for the query. /// public IDictionary? Parameters { get; init; } - internal IDictionary? Globals { get; init; } + internal List? Globals { get; init; } /// /// Creates a new built query. diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index 763ecb71..c3c6c3a8 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -38,10 +38,40 @@ public TType Global(string name) public TType Local(string name) => default!; + /// + /// References a contextual local. + /// + /// The name of the local. + /// + /// A mock reference to a local with the given . + /// + [EquivalentOperator(typeof(LocalReference))] + public object? Local(string name) + => default!; + + /// + /// 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 . + /// [EquivalentOperator(typeof(LocalReference))] public TType UnsafeLocal(string name) => default!; + /// + /// References a contextual local without checking the local context. + /// + /// The name of the local. + /// + /// A mock reference to a local with the given . + /// + [EquivalentOperator(typeof(LocalReference))] + public object? UnsafeLocal(string name) + => default!; + public TType Raw(string query) => default!; diff --git a/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs b/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs new file mode 100644 index 00000000..7910c11f --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + internal class QueryGlobal + { + public object? Value { get; init; } + public object? Reference { get; init; } + public string Name { get; init; } + + public QueryGlobal(string name, object? value) + { + Name = name; + Value = value; + } + + public QueryGlobal(string name, object? value, object? reference) + { + Name = name; + Value = value; + Reference = reference; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs index 65d387b4..3ba97dee 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs @@ -11,6 +11,9 @@ internal class UpdateContext : NodeContext { public string? UpdateName { get; init; } public LambdaExpression? UpdateExpression { get; init; } + + internal Dictionary ChildQueries { get; } = new(); + public UpdateContext(Type currentType) : base(currentType) { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs index 069ced70..0670af88 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs @@ -8,7 +8,7 @@ namespace EdgeDB.QueryNodes { internal class WithContext : NodeContext { - public Dictionary? Values { get; init; } + public List? Values { get; init; } public WithContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 6eeecc96..017348c6 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -20,7 +20,7 @@ public InsertNode(NodeBuilder builder) : base(builder) private string BuildInsertLambdaShape(LambdaExpression expression) { - return $"{{ {ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context)} }}"; + return $"{{ {ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; } private string BuildInsertShape(Type? shapeType = null, object? shapeValue = null) @@ -58,8 +58,7 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul if(QueryObjectManager.TryGetObjectId(subValue, out var id)) { // insert a sub query - var globalName = QueryUtils.GenerateRandomVariableName(); - SetGlobal(globalName, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = \"{id}\")")); + var globalName = GetOrAddGlobal(subValue, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = \"{id}\")")); shape.Add($"{propertyName} := {globalName}"); continue; } @@ -69,8 +68,16 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul shape.Add($"{propertyName} := {{}}"); else { - var globalName = QueryUtils.GenerateRandomVariableName(); - SetGlobal(globalName, new SubQuery($"(insert {property.PropertyType.GetEdgeDBTypeName()} {BuildInsertShape(property.PropertyType, subValue)})")); + RequiresIntrospection = true; + var globalName = GetOrAddGlobal(subValue, new SubQuery((info) => + { + var name = property.PropertyType.GetEdgeDBTypeName(); + var exclusiveProps = QueryUtils.GetProperties(info, property.PropertyType, true); + var exclusiveCondition = exclusiveProps.Any() ? + $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {name})" + : string.Empty; + return $"(insert {name} {BuildInsertShape(property.PropertyType, subValue)}{exclusiveCondition})"; + })); shape.Add($"{propertyName} := {globalName}"); } @@ -116,7 +123,7 @@ public override void FinalizeQuery() if(Context.SetAsGlobal && Context.GlobalName != null) { - SetGlobal(Context.GlobalName, new SubQuery($"({Query})")); + SetGlobal(Context.GlobalName, new SubQuery($"({Query})"), null); Query.Clear(); } } @@ -129,7 +136,7 @@ public void UnlessConflict() public void UnlessConflictOn(LambdaExpression selector) { - Query.Append($" unless conflict on {ExpressionTranslator.Translate(selector, Builder.QueryVariables, Context)}"); + Query.Append($" unless conflict on {ExpressionTranslator.Translate(selector, Builder.QueryVariables, Context, Builder.QueryGlobals)}"); } public void ElseDefault() @@ -147,19 +154,12 @@ public void Else(IQueryBuilder builder) foreach (var node in userNodes) node.Context.SetAsGlobal = false; - var globals = userNodes.SelectMany(x => - x.ReferencedGlobals.Select(y => - new KeyValuePair(y, x.Builder.QueryGlobals[y]) - ) - ).ToDictionary(x => x.Key, x => x.Value); - - var variables = userNodes.SelectMany(x => - x.ReferencedVariables.Select(y => - new KeyValuePair(y, x.Builder.QueryVariables[y]) - ) - ); + foreach(var variable in builder.Variables) + { + Builder.QueryVariables[variable.Key] = variable.Value; + } - var newBuilder = new QueryBuilder(userNodes.ToList(), globals, variables.ToDictionary(x => x.Key, x=> x.Value)); + var newBuilder = new QueryBuilder(userNodes.ToList(), builder.Globals.ToList(), builder.Variables.ToDictionary(x => x.Key, x=> x.Value)); var result = newBuilder.BuildWithGlobals(); _children.Append($" else ({result.Query})"); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs index 628e1862..b9a5c8b8 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs @@ -12,16 +12,17 @@ internal class NodeBuilder public StringBuilder Query { get; } public List Nodes { get; } public NodeContext Context { get; } - public Dictionary QueryVariables { get; } = new(); - public Dictionary QueryGlobals { get; } + public Dictionary QueryVariables { get; } + public List QueryGlobals { get; } public bool IsAutoGenerated { get; init; } - public NodeBuilder(NodeContext context, Dictionary globals, List? nodes = null) + public NodeBuilder(NodeContext context, List globals, List? nodes = null, Dictionary? variables = null) { Query = new(); Nodes = nodes ?? new(); Context = context; QueryGlobals = globals; + QueryVariables = variables ?? new(); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index e015b72f..dc54706f 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -23,7 +23,7 @@ public bool IsAutoGenerated public bool RequiresIntrospection { get; protected set; } public SchemaInfo? SchemaInfo { get; set; } - internal List ReferencedGlobals { get; } = new(); + internal List ReferencedGlobals { get; } = new(); internal List ReferencedVariables { get; } = new(); internal List SubNodes { get; } = new(); @@ -48,10 +48,21 @@ protected void SetVariable(string name, object? value) Builder.QueryVariables[name] = value; } - protected void SetGlobal(string name, object? value) + protected void SetGlobal(string name, object? value, object? reference) { - ReferencedGlobals.Add(name); - Builder.QueryGlobals[name] = value; + var global = new QueryGlobal(name, value, reference); + Builder.QueryGlobals.Add(global); + ReferencedGlobals.Add(Builder.QueryGlobals.IndexOf(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; } internal BuiltQueryNode Build() diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 9ae49b3a..bb910f1e 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -49,19 +49,19 @@ private string GetShape() if (Context.SelectExpressional) { - return ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context); + return ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals); } // if its a call to a global if (Context.Shape.Body is MethodCallExpression) { - var exp = ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context); + var exp = ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals); Context.SelectName = exp; return GetDefaultShape(); } else if (Context.Shape.Body is NewExpression or MemberInitExpression) { - return $"{{ {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context)} }}"; + return $"{{ {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; } throw new NotSupportedException($"Cannot use {Context.Shape.GetType().Name} as a shape"); @@ -81,7 +81,7 @@ public void Filter(LambdaExpression expression) if (expression is null) throw new ArgumentNullException(nameof(expression), "No expression was passed in for a filter node"); - var parsedExpression = ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context); + var parsedExpression = ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals); Query.Append($" filter {parsedExpression}"); } @@ -90,7 +90,7 @@ public void OrderBy(bool asc, LambdaExpression selector, OrderByNullPlacement? n if (selector is null) throw new ArgumentNullException(nameof(selector), "No expression was passed in for an order by node"); - var parsedExpression = ExpressionTranslator.Translate(selector, Builder.QueryVariables, Context); + var parsedExpression = ExpressionTranslator.Translate(selector, Builder.QueryVariables, Context, Builder.QueryGlobals); var direction = asc ? "asc" : "desc"; Query.Append($" order by {parsedExpression} {direction}{(nullPlacement.HasValue ? $" {nullPlacement.Value.ToString().ToLowerInvariant()}" : "")}"); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs index 1f6aa3f8..3053bcb7 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs @@ -11,19 +11,30 @@ internal class UpdateNode : QueryNode { public UpdateNode(NodeBuilder builder) : base(builder) { } + private string? _translatedExpression; + public override void Visit() { Query.Append($"update {Context.UpdateName ?? Context.CurrentType.GetEdgeDBTypeName()}"); + _translatedExpression = ExpressionTranslator.Translate(Context.UpdateExpression!, Builder.QueryVariables, Context, Builder.QueryGlobals); + + RequiresIntrospection = Context.ChildQueries.Any(x => x.Value.RequiresIntrospection); } public override void FinalizeQuery() { - var exp = ExpressionTranslator.Translate(Context.UpdateExpression!, Builder.QueryVariables, Context); - Query.Append($" set {{ {exp} }}"); + Query.Append($" set {{ {_translatedExpression} }}"); + + if (RequiresIntrospection && SchemaInfo is null) + throw new InvalidOperationException("This node requires schema introspection but none was provided"); + + foreach (var child in Context.ChildQueries) + SetGlobal(child.Key, child.Value, null); // sub query will be built with introspection by the 'With' node. + if (Context.SetAsGlobal && Context.GlobalName != null) { - SetGlobal(Context.GlobalName, new SubQuery($"({Query})")); + SetGlobal(Context.GlobalName, new SubQuery($"({Query})"), null); Query.Clear(); } } @@ -33,7 +44,7 @@ public void Filter(LambdaExpression filter) if (filter is null) throw new ArgumentNullException(nameof(filter), "No expression was passed in for a filter node"); - var parsedExpression = ExpressionTranslator.Translate(filter, Builder.QueryVariables, Context); + var parsedExpression = ExpressionTranslator.Translate(filter, Builder.QueryVariables, Context, Builder.QueryGlobals); Query.Append($" filter {parsedExpression}"); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs index 6dcbb010..e4835283 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs @@ -21,25 +21,32 @@ public override void Visit() List values = new(); - foreach(var kvp in Context.Values) + foreach(var global in Context.Values) { - var value = kvp.Value; + var value = global.Value; if (value is IQueryBuilder queryBuilder) { - var subQuery = queryBuilder.Build(); - value = new SubQuery($"({subQuery.Query})"); + var query = queryBuilder.Build(); + value = new SubQuery($"({query.Query})"); - if(subQuery.Parameters is not null) - foreach (var variable in subQuery.Parameters) + if(query.Parameters is not null) + foreach (var variable in query.Parameters) SetVariable(variable.Key, variable.Value); - if (subQuery.Globals is not null) - foreach (var global in subQuery.Globals) - SetGlobal(global.Key, global.Value); + if (query.Globals is not null) + foreach (var queryGlobal in query.Globals) + SetGlobal(queryGlobal.Name, queryGlobal.Value, null); } - values.Add($"{kvp.Key} := {QueryUtils.ParseObject(value)}"); + if(value is SubQuery subQuery && subQuery.RequiresIntrospection) + { + if (subQuery.RequiresIntrospection && SchemaInfo is null) + throw new InvalidOperationException("Cannot build without introspection! A node requires query introspection."); + value = subQuery.Build(SchemaInfo!); + } + + values.Add($"{global.Name} := {QueryUtils.ParseObject(value)}"); } Query.Append($"with {string.Join(", ", values)}"); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs index 0ec1b14d..07a31dd9 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs @@ -1,6 +1,10 @@ -using System; +using EdgeDB.Interfaces; +using EdgeDB.Interfaces.Queries; +using EdgeDB.Schema; +using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -26,12 +30,12 @@ internal static string ParseObject(object? obj) SerializationMethod.Lower => $"\"{obj.ToString()?.ToLower()}\"", SerializationMethod.Numeric => Convert.ChangeType(obj, type.BaseType ?? typeof(int)).ToString() ?? "{}", _ => "{}" - } : $"\"{obj.ToString()}\""; + } : $"\"{obj}\""; } return obj switch { - SubQuery query => query.Query, + SubQuery query when !query.RequiresIntrospection => query.Query!, string str => $"\"{str}\"", char chr => $"\"{chr}\"", Type type => PacketSerializer.GetEdgeQLType(type) ?? type.GetEdgeDBTypeName(), @@ -44,5 +48,84 @@ public static string GenerateRandomVariableName() return new string(Enumerable.Repeat(VARIABLE_CHARS, 12).Select(x => x[_rng.Next(x.Length)]).ToArray()); } + 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); + } + + public static IEnumerable GetProperties(SchemaInfo schemaInfo, Type type, bool? exclusive = null, bool? @readonly = null) + { + 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 edgedbName = x.GetEdgeDBPropertyName(); + return info.Properties!.Any(x => x.Name == edgedbName && + (!exclusive.HasValue || x.IsExclusive == exclusive.Value) && + (!@readonly.HasValue || x.IsReadonly == @readonly.Value)); + }); + } + + 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)) + ) + ); + } + + public static async ValueTask>> GenerateUpdateFactoryAsync(IEdgeDBQueryable edgedb, TType inst, CancellationToken token = default) + { + var props = await GetPropertiesAsync(edgedb, @readonly: false, token: token).ConfigureAwait(false); + + props = props.Where(x => x.GetValue(inst) != ReflectionUtils.GetDefault(x.PropertyType)); + + return Expression.Lambda>( + Expression.MemberInit( + Expression.New(typeof(TType)), props.Select(x => + Expression.Bind(x, Expression.MakeMemberAccess(Expression.Constant(inst), x))) + ), + Expression.Parameter(typeof(TType), "x") + ); + } + + public static async ValueTask>> GenerateUpdateFilterAsync(IEdgeDBQueryable edgedb, TType inst, CancellationToken token = default) + { + // try and get object id + if (QueryObjectManager.TryGetObjectId(inst, 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(QueryContext).GetMethod("UnsafeLocal")!; + return Expression.Lambda>( + exclusiveProperties.Select(x => + { + + return Expression.Equal( + Expression.Call( + Expression.Parameter(typeof(QueryContext), "ctx"), + unsafeLocalMethod, + Expression.Constant(x.GetEdgeDBPropertyName()) + ), + Expression.MakeMemberAccess(Expression.Parameter(typeof(TType), "x"), x) + ); + }).Aggregate((x, y) => Expression.And(x, y)), + Expression.Parameter(typeof(QueryContext), "ctx"), + Expression.Parameter(typeof(TType), "x") + ); + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs index 6bebdf02..2023204a 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -1,4 +1,5 @@ -using EdgeDB.Interfaces.Queries; +using EdgeDB.Interfaces; +using EdgeDB.Interfaces.Queries; using EdgeDB.QueryNodes; using EdgeDB.Schema; using System; @@ -70,25 +71,35 @@ internal QueryableCollection(IEdgeDBQueryable edgedb) /// The added or updated value. public async Task AddOrUpdateAsync(TType item, CancellationToken token = default) { - var props = await GetPropertiesAsync(false, false, token).ConfigureAwait(false); - - // create the update factory. - var updateFactory = Expression.Lambda>( - Expression.MemberInit( - Expression.New(typeof(TType)), props.Select(x => - Expression.Bind(x, Expression.MakeMemberAccess(Expression.Constant(item), x))) - ), - Expression.Parameter(typeof(TType), "x") - ); + var updateFactory = await QueryUtils.GenerateUpdateFactoryAsync(_edgedb, item, token).ConfigureAwait(false); return await QueryBuilder .Insert(item) .UnlessConflict() .Else(q => - (Interfaces.ISingleCardinalityExecutable)q.Update(updateFactory, false) + (Interfaces.ISingleCardinalityExecutable)q.Update(updateFactory!, false) ).ExecuteAsync(_edgedb, token).ConfigureAwait(false); } + /// + /// Attempts to add a value to this collection. + /// + /// + /// This method requires introspection and can preform more than one query + /// to cache the current clients schema. + /// + /// The value to add. + /// A cancellation token to cancel the asynchronous insert operation. + /// + /// if the value was added successfully, otherwise . + /// + public async Task TryAddAsync(TType item, CancellationToken token = default) + { + var query = QueryBuilder.Insert(item, false).UnlessConflict(); + var result = await query.ExecuteAsync(_edgedb, token); + return result != null; + } + /// /// Gets or adds a value to the collection. /// @@ -102,12 +113,12 @@ internal QueryableCollection(IEdgeDBQueryable edgedb) /// The item to get or add. /// A cancellation token to cancel the asynchronous insert operation. /// The inserted or conflicting item. - public Task GetOrAddAsync(TType item, CancellationToken token = default) + public Task GetOrAddAsync(TType item, CancellationToken token = default) => QueryBuilder .Insert(item) .UnlessConflict() .ElseReturn() - .ExecuteAsync(_edgedb, token); + .ExecuteAsync(_edgedb, token)!; /// /// Deletes a value from the collection. @@ -132,7 +143,7 @@ await QueryBuilder ).Any(); // try to get exclusive property set on the instance - var props = await GetPropertiesAsync(exclusive: true, token: token).ConfigureAwait(false); + var props = await QueryUtils.GetPropertiesAsync(_edgedb, exclusive: true, token: token).ConfigureAwait(false); if (!props.Any()) throw new NotSupportedException("No unique constraints found to generate filter condition."); @@ -164,25 +175,6 @@ await QueryBuilder return (await builder.Delete.Filter(expr).ExecuteAsync(_edgedb, token).ConfigureAwait(false)).Any(); } - /// - /// Attempts to add a value to this collection. - /// - /// - /// This method requires introspection and can preform more than one query - /// to cache the current clients schema. - /// - /// The value to add. - /// A cancellation token to cancel the asynchronous insert operation. - /// - /// if the value was added successfully, otherwise . - /// - public async Task TryAddAsync(TType item, CancellationToken token = default) - { - var query = QueryBuilder.Insert(item, false).UnlessConflict(); - var result = await query.ExecuteAsync(_edgedb, token); - return result != null; - } - /// /// Deletes all values that match a given predicate. /// @@ -202,22 +194,37 @@ public Task DeleteWhereAsync(Expression> filter, Cancell CancellationToken token = default) => await QueryBuilder.Select().Filter(filter).ExecuteAsync(_edgedb, token); - private async ValueTask> GetPropertiesAsync(bool? exclusive = null, bool? @readonly = null, CancellationToken token = default) - { - var props = typeof(TType).GetProperties().Where(x => x.GetCustomAttribute() == null); - - var introspection = await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(_edgedb, token).ConfigureAwait(false); - - if (!introspection.TryGetObjectInfo(typeof(TType), out var info)) - throw new NotSupportedException($"Cannot use {typeof(TType).Name} as there is no schema information for it."); + /// + /// Updates a given value in the collection with an update factory. + /// + /// + /// This method may require introspection of the schema to + /// generate the filter for the update statement. + /// + /// The value to update. + /// The factory containing the updated version. + /// A cancellation token to cancel the asynchronous select operation. + /// The updated item. + public async Task UpdateAsync(TType value, Expression> updateFunc, CancellationToken token = default) + => (await QueryBuilder + .Update(updateFunc) + .Filter(await QueryUtils.GenerateUpdateFilterAsync(_edgedb, value, token)) + .ExecuteAsync(_edgedb, token).ConfigureAwait(false)).FirstOrDefault(); - return props.Where(x => - { - var edgedbName = x.GetEdgeDBPropertyName(); - return info.Properties!.Any(x => x.Name == edgedbName && - (!exclusive.HasValue || x.IsExclusive == exclusive.Value) && - (!@readonly.HasValue || x.IsReadonly == @readonly.Value)); - }); - } + /// + /// Updates a given value in the collection. + /// + /// + /// This method may require introspection of the schema to + /// generate the filter and update factory for the update statement. + /// + /// The value to update. + /// A cancellation token to cancel the asynchronous select operation. + /// The updated item. + public async Task UpdateAsync(TType value, CancellationToken token = default) + => (await QueryBuilder + .Update(await QueryUtils.GenerateUpdateFactoryAsync(_edgedb, value, token)) + .Filter(await QueryUtils.GenerateUpdateFilterAsync(_edgedb, value, token)) + .ExecuteAsync(_edgedb, token).ConfigureAwait(false)).FirstOrDefault(); } } diff --git a/src/EdgeDB.Net.QueryBuilder/SubQuery.cs b/src/EdgeDB.Net.QueryBuilder/SubQuery.cs index 20a83843..108a2cb1 100644 --- a/src/EdgeDB.Net.QueryBuilder/SubQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/SubQuery.cs @@ -1,4 +1,5 @@ -using System; +using EdgeDB.Schema; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,11 +9,24 @@ namespace EdgeDB { internal class SubQuery { - public string Query { get; init; } + public string? Query { get; init; } + public bool RequiresIntrospection { get; init; } + public Func? Builder { get; init; } + + public SubQuery(Func builder) + { + RequiresIntrospection = true; + Builder = builder; + } public SubQuery(string query) { Query = query; } + + public SubQuery Build(SchemaInfo info) + { + return new SubQuery(Builder!(info)); + } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index c89f77b1..ecc67507 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -14,17 +15,19 @@ internal class ExpressionContext public LambdaExpression RootExpression { get; } public Dictionary Parameters { get; } private readonly IDictionary _queryObjects; - + private readonly List _globals; public bool StringWithoutQuotes { get; set; } public Type? LocalScope { get; set; } public bool IsShape { get; set; } - public ExpressionContext(NodeContext context, LambdaExpression rootExpression, IDictionary queryArguments) + public ExpressionContext(NodeContext context, LambdaExpression rootExpression, + IDictionary queryArguments, List globals) { RootExpression = rootExpression; _queryObjects = queryArguments; NodeContext = context; + _globals = globals; Parameters = rootExpression.Parameters.ToDictionary(x => x.Name!, x => x.Type); } @@ -36,6 +39,12 @@ public string AddVariable(object? value) return name; } + public bool TryGetGlobal(object? value, [MaybeNullWhen(false)]out QueryGlobal global) + { + global = _globals.FirstOrDefault(x => x.Reference == value); + return global != null; + } + public ExpressionContext Enter(Action func) { var exp = (ExpressionContext)MemberwiseClone(); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs index 48a27d2e..321ba759 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -54,9 +54,9 @@ protected static bool TryGetExpressionOperator(ExpressionType type, [MaybeNullWh public static string Translate(Expression expression) => Translate(expression); - public static string Translate(LambdaExpression expression, IDictionary queryArguments, NodeContext nodeContext) + public static string Translate(LambdaExpression expression, IDictionary queryArguments, NodeContext nodeContext, List globals) { - var context = new ExpressionContext(nodeContext, expression, queryArguments); + var context = new ExpressionContext(nodeContext, expression, queryArguments, globals); return TranslateExpression(expression.Body, context); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index 52898856..f188e376 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -14,12 +14,7 @@ internal class MemberExpressionTranslator : ExpressionTranslator field.GetValue(constant.Value), - PropertyInfo property => property.GetValue(constant.Value), - _ => throw new InvalidOperationException("Cannot resolve constant member expression") - }; + object? value = expression.Member.GetMemberValue(constant.Value); var varName = context.AddVariable(value); var type = PacketSerializer.GetEdgeQLType(expression.Type); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs index 63956831..4bbec3ce 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs @@ -1,7 +1,10 @@ -using System; +using EdgeDB.QueryNodes; +using EdgeDB.Serializer; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -19,17 +22,95 @@ internal class MemberInitExpressionTranslator : ExpressionTranslator + { + // generate an insert shape + var insertShape = TranslateExpression(QueryUtils.GenerateInsertShapeExpression(memberValue, memberType), context); + + var exclusiveProps = QueryUtils.GetProperties(info, memberType, true); + var exclusiveCondition = exclusiveProps.Any() ? + $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {typeName})" + : string.Empty; + + return $"(insert {typeName} {{ {insertShape} }}{exclusiveCondition})"; + }); + } + break; + default: + throw new NotSupportedException("Cannot set links with the current expression"); + } + + if (!isSubQuery) + break; + + if (subQuery is null) + throw new NotSupportedException($"Cannot set links to the expression {assignment.Expression.NodeType}"); + + var name = QueryUtils.GenerateRandomVariableName(); + updateContext.ChildQueries.Add(name, subQuery); + initializations.Add($"{memberName} := {name}"); + break; + } + var value = TranslateExpression(assignment.Expression, context); + if (value is null) - initializations.Add($"{assignment.Member.GetEdgeDBPropertyName()}"); + initializations.Add($"{memberName}"); else if (context.IsShape) { - initializations.Add($"{assignment.Member.GetEdgeDBPropertyName()}: {{ {value} }}"); + initializations.Add($"{memberName}: {{ {value} }}"); context.IsShape = false; } else - initializations.Add($"{assignment.Member.GetEdgeDBPropertyName()} := {value}"); + initializations.Add($"{memberName} := {value}"); } break; diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index 7cf8a58c..a50c4956 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -14,7 +14,7 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator()).Concat(expression.Arguments.Select(x => TranslateExpression(x, context))); + var args = (expression.Object != null ? new string[] { TranslateExpression(expression.Object, context) } : Array.Empty()).Concat(expression.Arguments.Select(x => TranslateExpression(x, context))); return edgeqlOperator.Build(args.ToArray()); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs index 4b75b79d..3f37515a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs @@ -50,7 +50,7 @@ internal class NewExpressionTranslator : ExpressionTranslator shape[i] = $"{edgedbName}{(isSetter ? " :=" : "")} {value}"; } - return $"{{ {string.Join(", ", shape)} }}"; + return string.Join(", ", shape); } } } From 815ad50cfa88fee109aae87366512da096a21a35 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 6 Jul 2022 17:13:23 -0300 Subject: [PATCH 13/70] remove newline --- .../Examples/QueryBuilder.cs | 3 +-- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 11 +++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 83dee67a..233ba57c 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -23,7 +23,6 @@ public class LinkPerson public LinkPerson? BestFriend { get; set; } } - public async Task ExecuteAsync(EdgeDBClient client) { await QueryBuilderDemo(client); @@ -119,7 +118,7 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) .Else(q => q.Update(old => new LinkPerson { - Name = old!.Name!.ToUpper() + Name = EdgeQL.ToUpper(old.Name) }) ) .BuildAsync(client)) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index afcc83f4..1449970e 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -45,7 +45,7 @@ public static IUpdateQuery Update(Expression> u public static IDeleteQuery Delete() => new QueryBuilder().Delete; } - + /// /// Represents a query builder used to build queries against . /// @@ -121,9 +121,12 @@ internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) { var with = new WithNode(new NodeBuilder(new WithContext(typeof(TType)) { - Values = _queryGlobals - }, _queryGlobals, null, _queryVariables)); - with.SchemaInfo = _schemaInfo; + Values = _queryGlobals, + }, _queryGlobals, null, _queryVariables)) + { + SchemaInfo = _schemaInfo + }; + with.Visit(); nodes = nodes.Prepend(with).ToList(); } From 5ed7762dd915a1676ce50b0bfa21ce94e03522be Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Fri, 8 Jul 2022 01:10:51 -0300 Subject: [PATCH 14/70] fix property access instance in insert node --- src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 017348c6..5a2317dc 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -44,7 +44,7 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul if(edgeqlType != null) { var varName = QueryUtils.GenerateRandomVariableName(); - SetVariable(varName, property.GetValue(Context.Value)); + SetVariable(varName, property.GetValue(value)); shape.Add($"{propertyName} := <{edgeqlType}>${varName}"); continue; } From 2e86eaa858420f500ed2fd810d4481e1868084a1 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 10 Jul 2022 05:02:42 -0300 Subject: [PATCH 15/70] Multi links --- dbschema/default.esdl | 8 + dbschema/migrations/00012.edgeql | 11 ++ dbschema/migrations/00013.edgeql | 11 ++ .../Examples/QueryBuilder.cs | 16 ++ .../SchemaTypeBuilders/TypeBuilder.cs | 2 +- .../Utils/ReflectionUtils.cs | 14 -- .../EdgeDB.Net.QueryBuilder.csproj | 15 -- src/EdgeDB.Net.QueryBuilder/EdgeQL.cs | 48 ++++++ src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs | 19 --- .../Interfaces/IMultiCardinalityExecutable.cs | 2 +- .../Interfaces/IQuery.cs | 16 ++ .../ISingleCardinalityExecutable.cs | 2 +- .../Operators/Links/AddLink.g.cs | 2 +- .../Operators/Links/RemoveLink.g.cs | 2 +- .../Properties/Resources.Designer.cs | 84 ----------- .../Properties/Resources.resx | 138 ------------------ .../QueryNodes/InsertNode.cs | 91 ++++++++---- .../QueryNodes/SelectNode.cs | 5 +- src/EdgeDB.Net.QueryBuilder/QueryUtils.cs | 18 +++ .../Expressions/ExpressionContext.cs | 25 ++++ .../MemberInitExpressionTranslator.cs | 5 + .../MethodCallExpressionTranslator.cs | 15 +- src/EdgeDB.Net.QueryBuilder/introspect.edgeql | 16 -- .../operators.yml | 18 --- 24 files changed, 241 insertions(+), 342 deletions(-) create mode 100644 dbschema/migrations/00012.edgeql create mode 100644 dbschema/migrations/00013.edgeql create mode 100644 src/EdgeDB.Net.QueryBuilder/EdgeQL.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/Properties/Resources.Designer.cs delete mode 100644 src/EdgeDB.Net.QueryBuilder/Properties/Resources.resx delete mode 100644 src/EdgeDB.Net.QueryBuilder/introspect.edgeql diff --git a/dbschema/default.esdl b/dbschema/default.esdl index 687ed8bd..bdb435b9 100644 --- a/dbschema/default.esdl +++ b/dbschema/default.esdl @@ -46,4 +46,12 @@ module default { } link best_friend -> LinkPerson; } + + type MultiLinkPerson { + required property name -> str; + required property email -> str { + constraint exclusive; + } + multi link best_friends -> MultiLinkPerson; + } } diff --git a/dbschema/migrations/00012.edgeql b/dbschema/migrations/00012.edgeql new file mode 100644 index 00000000..ce2a14ce --- /dev/null +++ b/dbschema/migrations/00012.edgeql @@ -0,0 +1,11 @@ +CREATE MIGRATION m1qocyoinu75piizgrcf5veo5cq3tw5jzywpjf7exlhrb6rmz4ngua + ONTO m1vs5zqu667wwcqfbuugfklx4jcnnu5wnzigwcvyohumszc2mddjeq +{ + CREATE TYPE default::MultiLinkPerson { + CREATE MULTI LINK best_friends -> default::LinkPerson; + CREATE REQUIRED PROPERTY email -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY name -> std::str; + }; +}; diff --git a/dbschema/migrations/00013.edgeql b/dbschema/migrations/00013.edgeql new file mode 100644 index 00000000..9a7c13e3 --- /dev/null +++ b/dbschema/migrations/00013.edgeql @@ -0,0 +1,11 @@ +CREATE MIGRATION m1tcypfnfc5mkkldxbjqz5awkngj6gjleg7w3qrbagyq5k47uys6na + ONTO m1qocyoinu75piizgrcf5veo5cq3tw5jzywpjf7exlhrb6rmz4ngua +{ + ALTER TYPE default::MultiLinkPerson { + ALTER LINK best_friends { + SET TYPE default::MultiLinkPerson USING (SELECT + default::MultiLinkPerson + ); + }; + }; +}; diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 233ba57c..45f1a3ae 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -23,6 +23,13 @@ public class LinkPerson public LinkPerson? BestFriend { get; set; } } + public class MultiLinkPerson + { + public string? Name { get; set; } + public string? Email { get; set; } + public MultiLinkPerson[]? BestFriends { get; set; } + } + public async Task ExecuteAsync(EdgeDBClient client) { await QueryBuilderDemo(client); @@ -31,6 +38,15 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { + var test = QueryBuilder.Update(old => new MultiLinkPerson + { + BestFriends = EdgeQL.AddLink(QueryBuilder.Insert(new MultiLinkPerson + { + Email = "test5@mail.com", + Name = "test5" + }, false)) + }).Build(); + // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs index 9541d69d..5d16065f 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs @@ -137,7 +137,7 @@ internal static bool TryGetCollectionParser(Type type, out Func), type)) + if (ReflectionUtils.IsInstanceOfGenericType(typeof(List<>), type)) builder = CreateDynamicList; return builder != null; diff --git a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs index b262be76..7a573270 100644 --- a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs +++ b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs @@ -26,20 +26,6 @@ public static bool IsInstanceOfGenericType(Type genericType, Type toCheck) return false; } - public static bool IsSubclassOfRawGeneric(Type generic, Type? toCheck) - { - while (toCheck is not null && toCheck != typeof(object)) - { - var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; - if (generic == cur) - { - return true; - } - toCheck = toCheck.BaseType; - } - return false; - } - public static bool TryGetRawGeneric(Type generic, Type? toCheck, out Type? genericReference) { genericReference = null; diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj index 05e1ab2b..5a2fb385 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj +++ b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj @@ -35,19 +35,4 @@ - - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs b/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs new file mode 100644 index 00000000..3a4dead9 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs @@ -0,0 +1,48 @@ +using EdgeDB.Operators; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + public sealed partial class EdgeQL + { + [EquivalentOperator(typeof(EdgeDB.Operators.LinksAddLink))] + public static TType[] AddLink(IQuery element) + => default!; + + [EquivalentOperator(typeof(EdgeDB.Operators.LinksAddLink))] + public static TType[] AddLinkRef(TType element) + => default!; + + [EquivalentOperator(typeof(EdgeDB.Operators.LinksAddLink))] + public static TSource AddLink(TType element) + where TSource : IEnumerable + => default!; + + [EquivalentOperator(typeof(EdgeDB.Operators.LinksAddLink))] + public static TSource AddLinkRef(IQuery element) + where TSource : IEnumerable + => default!; + + [EquivalentOperator(typeof(EdgeDB.Operators.LinksRemoveLink))] + public static TType[] RemoveLinkRef(TType element) + => default!; + + [EquivalentOperator(typeof(EdgeDB.Operators.LinksRemoveLink))] + public static TType[] RemoveLink(IQuery element) + => default!; + + [EquivalentOperator(typeof(EdgeDB.Operators.LinksRemoveLink))] + public static TSource RemoveLinkRef(TType element) + where TSource : IEnumerable + => default!; + + [EquivalentOperator(typeof(EdgeDB.Operators.LinksRemoveLink))] + public static TSource RemoveLink(IQuery element) + where TSource : IEnumerable + => default!; + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs b/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs index 24ac1bd1..9799f566 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/EdgeQL.g.cs @@ -1697,25 +1697,6 @@ public sealed partial class EdgeQL #endregion math - #region links - - #region AddLink - /// - /// A function that represents the EdgeQL version of: += - /// - [EquivalentOperator(typeof(EdgeDB.Operators.LinksAddLink))] - public static TSource AddLink(TSource source, TType element) where TSource : IEnumerable? { return default!; } - #endregion - - #region RemoveLink - /// - /// A function that represents the EdgeQL version of: -= - /// - [EquivalentOperator(typeof(EdgeDB.Operators.LinksRemoveLink))] - public static TSource RemoveLink(TSource source, TType element) where TSource : IEnumerable? { return default!; } - #endregion - - #endregion links internal static Dictionary FunctionOperators = new() { diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs index df8d5429..cb660874 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs @@ -10,7 +10,7 @@ namespace EdgeDB.Interfaces /// Represents an executable query with one or more returning objects. /// /// The object the query will return. - public interface IMultiCardinalityExecutable : IQueryBuilder + public interface IMultiCardinalityExecutable : IQueryBuilder, IQuery { /// /// Executes the current query. diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs new file mode 100644 index 00000000..a5a6625e --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + /// + /// Represents a generic query. + /// + /// The inner 'working' type of the query. + public interface IQuery : IQueryBuilder + { + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs index 791797ed..aa2589a0 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs @@ -10,7 +10,7 @@ namespace EdgeDB.Interfaces /// Represents an executable query with at most one returning objects. /// /// The object the query will return. - public interface ISingleCardinalityExecutable : IQueryBuilder + public interface ISingleCardinalityExecutable : IQueryBuilder, IQuery { /// /// Executes the current query. diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Links/AddLink.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Links/AddLink.g.cs index 5a1af636..9dcd7343 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Links/AddLink.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Links/AddLink.g.cs @@ -5,6 +5,6 @@ namespace EdgeDB.Operators internal class LinksAddLink : IEdgeQLOperator { public ExpressionType? Expression => null; - public string EdgeQLOperator => "+= {1}"; + public string EdgeQLOperator => "+= {0}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Operators/Links/RemoveLink.g.cs b/src/EdgeDB.Net.QueryBuilder/Operators/Links/RemoveLink.g.cs index 5ea65832..2528aa49 100644 --- a/src/EdgeDB.Net.QueryBuilder/Operators/Links/RemoveLink.g.cs +++ b/src/EdgeDB.Net.QueryBuilder/Operators/Links/RemoveLink.g.cs @@ -5,6 +5,6 @@ namespace EdgeDB.Operators internal class LinksRemoveLink : IEdgeQLOperator { public ExpressionType? Expression => null; - public string EdgeQLOperator => "-= {1}"; + public string EdgeQLOperator => "-= {0}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Properties/Resources.Designer.cs b/src/EdgeDB.Net.QueryBuilder/Properties/Resources.Designer.cs deleted file mode 100644 index 5beeb452..00000000 --- a/src/EdgeDB.Net.QueryBuilder/Properties/Resources.Designer.cs +++ /dev/null @@ -1,84 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EdgeDB.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EdgeDB.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to select schema::ObjectType { - /// id, - /// name, - /// is_abstract, - /// pointers: { - /// real_cardinality := ("One" IF .required ELSE "AtMostOne") IF <str>.cardinality = "One" ELSE ("AtLeastOne" IF .required ELSE "Many"), - /// name, - /// target_id := .target.id, - /// is_link := exists [IS schema::Link], - /// is_exclusive := exists (select .constraints filter .name = 'std::exclusive'), - /// is_computed := len(.computed_fields) != 0, - /// is_readonly := .readonly, - /// has_default := EXISTS .default or ("std::sequence [rest of string was truncated]";. - /// - internal static string INTROSPECT_QUERY { - get { - return ResourceManager.GetString("INTROSPECT_QUERY", resourceCulture); - } - } - } -} diff --git a/src/EdgeDB.Net.QueryBuilder/Properties/Resources.resx b/src/EdgeDB.Net.QueryBuilder/Properties/Resources.resx deleted file mode 100644 index 683dbf83..00000000 --- a/src/EdgeDB.Net.QueryBuilder/Properties/Resources.resx +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - select schema::ObjectType { - id, - name, - is_abstract, - pointers: { - real_cardinality := ("One" IF .required ELSE "AtMostOne") IF <str>.cardinality = "One" ELSE ("AtLeastOne" IF .required ELSE "Many"), - name, - target_id := .target.id, - is_link := exists [IS schema::Link], - is_exclusive := exists (select .constraints filter .name = 'std::exclusive'), - is_computed := len(.computed_fields) != 0, - is_readonly := .readonly, - has_default := EXISTS .default or ("std::sequence" in .target[IS schema::ScalarType].ancestors.name), - } -} -filter not .builtin; - - \ No newline at end of file diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 5a2317dc..58aeba15 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -1,5 +1,6 @@ using EdgeDB.Serializer; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -13,6 +14,8 @@ internal class InsertNode : QueryNode { private bool _autogenerateUnlessConflict; private readonly StringBuilder _children; + private readonly List _subQueryMap = new(); + public InsertNode(NodeBuilder builder) : base(builder) { _children = new(); @@ -37,9 +40,13 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul foreach(var property in properties) { + // might be multi link + var propType = property.PropertyType; + var isLink = QueryUtils.IsLink(property.PropertyType, out var isArray, out var innerType); + var propertyName = property.GetEdgeDBPropertyName(); - var edgeqlType = PacketSerializer.GetEdgeQLType(property.PropertyType); + var edgeqlType = PacketSerializer.GetEdgeQLType(propType); if(edgeqlType != null) { @@ -49,40 +56,27 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul continue; } - // TODO: sub queries! // might be a link? - if (TypeBuilder.IsValidObjectType(property.PropertyType)) + if (isLink) { - // is it a object we've seen before? var subValue = property.GetValue(value); - if(QueryObjectManager.TryGetObjectId(subValue, out var id)) - { - // insert a sub query - var globalName = GetOrAddGlobal(subValue, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = \"{id}\")")); - shape.Add($"{propertyName} := {globalName}"); - continue; - } - else + + if (subValue is null) + shape.Add($"{propertyName} := {{}}"); + else if (isArray) { - if (subValue is null) - shape.Add($"{propertyName} := {{}}"); - else + List subShape = new(); + foreach (var item in (IEnumerable)subValue!) { - RequiresIntrospection = true; - var globalName = GetOrAddGlobal(subValue, new SubQuery((info) => - { - var name = property.PropertyType.GetEdgeDBTypeName(); - var exclusiveProps = QueryUtils.GetProperties(info, property.PropertyType, true); - var exclusiveCondition = exclusiveProps.Any() ? - $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {name})" - : string.Empty; - return $"(insert {name} {BuildInsertShape(property.PropertyType, subValue)}{exclusiveCondition})"; - })); - shape.Add($"{propertyName} := {globalName}"); + subShape.Add(BuildLinkResolver(innerType!, item)); } - - continue; + + shape.Add($"{propertyName} := {{ {string.Join(", ", subShape)} }}"); } + else + shape.Add($"{propertyName} := {BuildLinkResolver(propType, subValue)}"); + + continue; } throw new Exception($"Failed to find method to serialize the property \"{property.PropertyType.Name}\" on type {type.Name}"); @@ -90,9 +84,48 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul return $"{{ {string.Join(", ", shape)} }}"; } - + + private string BuildLinkResolver(Type type, object? value) + { + if (value is null) + return "{}"; + + if (QueryObjectManager.TryGetObjectId(value, out var id)) + { + return InlineOrGlobal( + type, + new SubQuery($"(select {type.GetEdgeDBTypeName()} filter .id = \"{id}\")"), + value); + } + else + { + RequiresIntrospection = true; + return InlineOrGlobal(type, new SubQuery((info) => + { + var name = type.GetEdgeDBTypeName(); + var exclusiveProps = QueryUtils.GetProperties(info, type, true); + var exclusiveCondition = exclusiveProps.Any() ? + $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {name})" + : string.Empty; + return $"(insert {name} {BuildInsertShape(type, value)}{exclusiveCondition})"; + }), value); + } + } + + private string InlineOrGlobal(Type type, object value, object? reference) + { + if (_subQueryMap.Contains(type) || (value is SubQuery sq && sq.RequiresIntrospection)) + return GetOrAddGlobal(reference, value); + + _subQueryMap.Add(type); + return value is SubQuery subQuery && subQuery.Query != null + ? subQuery.Query + : value.ToString()!; + } + public override void Visit() { + _subQueryMap.Add(Context.CurrentType); var shape = BuildInsertShape(); Query.Append($"insert {Context.CurrentType.GetEdgeDBTypeName()} {shape}"); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index bb910f1e..dc37d6b8 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -22,10 +22,11 @@ private string GetShape(Type type, int currentDepth = 0) var propertyNames = properties.Select(x => { var name = x.GetCustomAttribute()?.Name ?? TypeBuilder.NamingStrategy.GetName(x); - if (TypeBuilder.IsValidObjectType(x.PropertyType)) + if (QueryUtils.IsLink(x.PropertyType, out var isArray, out var innerType)) { + var shapeType = isArray ? innerType! : x.PropertyType; if(currentDepth < MAX_DEPTH) - return $"{name}: {GetShape(x.PropertyType, currentDepth + 1)}"; + return $"{name}: {GetShape(shapeType, currentDepth + 1)}"; return null; } else diff --git a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs index 07a31dd9..2e2e94bd 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs @@ -1,8 +1,10 @@ using EdgeDB.Interfaces; using EdgeDB.Interfaces.Queries; using EdgeDB.Schema; +using EdgeDB.Serializer; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -16,6 +18,22 @@ internal class QueryUtils private const string VARIABLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static readonly Random _rng = new(); + public static bool IsLink(Type type, out bool isMultiLink, [MaybeNullWhen(false)]out Type? innerLinkType) + { + innerLinkType = null; + isMultiLink = false; + + Type? enumerableType = null; + if (type != typeof(string) && (enumerableType = type.GetInterfaces().FirstOrDefault(x => ReflectionUtils.IsInstanceOfGenericType(typeof(IEnumerable<>), x))) != null) + { + innerLinkType = enumerableType.GenericTypeArguments[0]; + isMultiLink = true; + return true; + } + + return TypeBuilder.IsValidObjectType(type); + } + internal static string ParseObject(object? obj) { if (obj is null) diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index ecc67507..52e32054 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -19,6 +19,8 @@ internal class ExpressionContext public bool StringWithoutQuotes { get; set; } public Type? LocalScope { get; set; } public bool IsShape { get; set; } + + public bool HasInitializationOperator { get; set; } public ExpressionContext(NodeContext context, LambdaExpression rootExpression, @@ -39,12 +41,35 @@ public string AddVariable(object? value) return name; } + public void SetVariable(string name, object? value) + => _queryObjects[name] = value; + public bool TryGetGlobal(object? value, [MaybeNullWhen(false)]out QueryGlobal global) { global = _globals.FirstOrDefault(x => x.Reference == value); return global != null; } + public string GetOrAddGlobal(object? reference, object? value) + { + if(reference is not null) + { + var global = _globals.FirstOrDefault(x => x.Value == value); + if (global != null) + return global.Name; + } + + var name = QueryUtils.GenerateRandomVariableName(); + SetGlobal(name, value, reference); + return name; + } + + public void SetGlobal(string name, object? value, object? reference) + { + var global = new QueryGlobal(name, value, reference); + _globals.Add(global); + } + public ExpressionContext Enter(Action func) { var exp = (ExpressionContext)MemberwiseClone(); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs index 4bbec3ce..63e9453a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs @@ -104,6 +104,11 @@ assignment.Expression is MethodCallExpression mcx && if (value is null) initializations.Add($"{memberName}"); + else if (context.HasInitializationOperator) + { + initializations.Add($"{memberName} {value}"); + context.HasInitializationOperator = false; + } else if (context.IsShape) { initializations.Add($"{memberName}: {{ {value} }}"); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index a50c4956..f43a0cb6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -84,14 +84,25 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator true, + _ => false + }; + return edgeqlOperator.Build(argsArray); } diff --git a/src/EdgeDB.Net.QueryBuilder/introspect.edgeql b/src/EdgeDB.Net.QueryBuilder/introspect.edgeql deleted file mode 100644 index 62f5268d..00000000 --- a/src/EdgeDB.Net.QueryBuilder/introspect.edgeql +++ /dev/null @@ -1,16 +0,0 @@ -select schema::ObjectType { - id, - name, - is_abstract, - pointers: { - real_cardinality := ("One" IF .required ELSE "AtMostOne") IF .cardinality = "One" ELSE ("AtLeastOne" IF .required ELSE "Many"), - name, - target_id := .target.id, - is_link := exists [IS schema::Link], - is_exclusive := exists (select .constraints filter .name = 'std::exclusive'), - is_computed := len(.computed_fields) != 0, - is_readonly := .readonly, - has_default := EXISTS .default or ("std::sequence" in .target[IS schema::ScalarType].ancestors.name), - } -} -filter not .builtin; \ No newline at end of file diff --git a/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml b/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml index e9efafed..a5cf0af9 100644 --- a/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml +++ b/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml @@ -1460,24 +1460,6 @@ math: - IEnumerable return: decimal -links: - - operator: "+= {1}" - name: AddLink - return: TSource - functions: - - parameters: - - TSource source - - TType element - filter: "where TSource : IEnumerable?" - - operator: "-= {1}" - name: RemoveLink - return: TSource - functions: - - parameters: - - TSource source - - TType element - filter: "where TSource : IEnumerable?" - enums: - name: DurationTruncateUnit serialize_method: Lower From dad0f402301aabf3fafdb30fd83332e77a7d5743 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 11 Jul 2022 04:53:11 -0300 Subject: [PATCH 16/70] backlinks --- .../Examples/QueryBuilder.cs | 13 ++++++------ .../SchemaTypeBuilders/ObjectBuilder.cs | 15 ++++++++++---- .../SchemaTypeBuilders/TypeBuilder.cs | 4 ++++ src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs | 20 +++++++++++++++++++ src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 17 ++++++++++++++++ .../Expressions/ExpressionContext.cs | 3 +-- .../Expressions/MemberExpressionTranslator.cs | 15 ++++++++------ .../MethodCallExpressionTranslator.cs | 19 ++++++++++++++++++ 8 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 45f1a3ae..57a8b706 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -38,14 +38,15 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var test = QueryBuilder.Update(old => new MultiLinkPerson + var result = await new QueryBuilder().Select(ctx => new { - BestFriends = EdgeQL.AddLink(QueryBuilder.Insert(new MultiLinkPerson + Name = ctx.Include(), + Email = ctx.Include(), + BestFriendsBacklink = ctx.BackLink(x => x.BestFriends, () => new MultiLinkPerson { - Email = "test5@mail.com", - Name = "test5" - }, false)) - }).Build(); + Name = ctx.Include() + }) + }).ExecuteAsync(client); // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs index 9aae5573..b01a928f 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs @@ -80,10 +80,10 @@ internal class ObjectBuilder } } - private static object? ConvertCollection(Type targetType, Type valueType, object value) + internal static object? ConvertCollection(Type targetType, Type valueType, object value) { List converted = new(); - var strongInnerType = targetType.GenericTypeArguments.FirstOrDefault(); + var strongInnerType = targetType.IsArray ? targetType.GetElementType()! : targetType.GenericTypeArguments.FirstOrDefault(); foreach (var val in (IEnumerable)value) { @@ -96,8 +96,15 @@ internal class ObjectBuilder } - var arr = Array.CreateInstance(strongInnerType ?? valueType.GenericTypeArguments[0], converted.Count); - Array.Copy(converted.ToArray(), arr, converted.Count); + Array arr; + + if (converted.Any()) + { + arr = Array.CreateInstance(strongInnerType ?? valueType.GenericTypeArguments[0], converted.Count); + Array.Copy(converted.ToArray(), arr, converted.Count); + } + else + arr = (Array)typeof(Array).GetMethod("Empty")!.MakeGenericMethod(strongInnerType!).Invoke(null, null)!; switch (targetType) { diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs index 5d16065f..427eeb52 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs @@ -345,6 +345,10 @@ private TypeDeserializerFactory CreateDefaultFactory() { ctorParams[i] = Enum.Parse(prop.PropertyType, str); } + else if (value is object?[] objArray && prop.PropertyType.IsAssignableTo(typeof(IEnumerable))) + { + ctorParams[i] = ObjectBuilder.ConvertCollection(prop.PropertyType, valueType, value); + } else throw new InvalidOperationException($"Cannot assign property {prop.Name} with type {valueType}"); } diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs b/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs new file mode 100644 index 00000000..393d86e0 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + public class EdgeDBObject + { + [EdgeDBProperty("id")] + public Guid Id { get; } + + [EdgeDBDeserializer] + internal EdgeDBObject(IDictionary data) + { + Id = (Guid)data["id"]!; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index c3c6c3a8..6b46ec7c 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -94,5 +94,22 @@ public TType[] IncludeMultiLink(Expression> shape) public TCollection IncludeMultiLink(Expression> shape) where TCollection : IEnumerable => default!; + + public EdgeDBObject[] BackLink(string property) + => default!; + + public TCollection BackLink(string property) + where TCollection : IEnumerable + => default!; + + public TType[] BackLink(Expression> propertySelector) + => default!; + + public TType[] BackLink(Expression> propertySelector, Expression> shape) + => default!; + + public TCollection BackLink(Expression> propertySelector, Expression> shape) + where TCollection : IEnumerable + => default!; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index 52e32054..1aa677cf 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -19,9 +19,8 @@ internal class ExpressionContext public bool StringWithoutQuotes { get; set; } public Type? LocalScope { get; set; } public bool IsShape { get; set; } - public bool HasInitializationOperator { get; set; } - + public bool IncludeSelfReference { get; set; } = true; public ExpressionContext(NodeContext context, LambdaExpression rootExpression, IDictionary queryArguments, List globals) diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index f188e376..898eef0e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -21,18 +21,21 @@ internal class MemberExpressionTranslator : ExpressionTranslator${varName}"; } - return ParseMemberExpression(expression, expression.Expression is not ParameterExpression); + return ParseMemberExpression(expression, expression.Expression is not ParameterExpression, context.IncludeSelfReference); } - private static string ParseMemberExpression(MemberExpression expression, bool includeParameter = true) + private static string ParseMemberExpression(MemberExpression expression, bool includeParameter = true, bool includeSelfReference = true) { - List tree = new(); - - tree.Add(expression.Member.GetEdgeDBPropertyName()); + List tree = new() + { + expression.Member.GetEdgeDBPropertyName() + }; + if (expression.Expression is MemberExpression innerExp) tree.Add(ParseMemberExpression(innerExp)); if (expression.Expression is ParameterExpression param) - tree.Add(includeParameter ? param.Name : string.Empty); + if(includeSelfReference) + tree.Add(includeParameter ? param.Name : string.Empty); tree.Reverse(); return string.Join('.', tree); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index f43a0cb6..d059df9d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -60,6 +60,25 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator x.StringWithoutQuotes = true)); } + case nameof(QueryContext.BackLink): + { + var isRawPropertyName = expression.Arguments[0].Type == typeof(string); + var hasShape = !isRawPropertyName && expression.Arguments.Count > 1; + var property = TranslateExpression(expression.Arguments[0], + isRawPropertyName + ? context.Enter(x => x.StringWithoutQuotes = true) + : context.Enter(x => x.IncludeSelfReference = false)); + + var backlink = $".<{property}"; + + if (!isRawPropertyName) + backlink += $"[is {expression.Method.GetGenericArguments()[0].GetEdgeDBTypeName()}]"; + + if (hasShape) + backlink += $"{{{TranslateExpression(expression.Arguments[1], context)}}}"; + + return backlink; + } default: throw new NotImplementedException($"{expression.Method.Name} does not have an implementation. This is a bug, please file a github issue with your query to reproduce this exception."); From b35f54529575fbff157b47ccb2bebd159f2e4919 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 11 Jul 2022 04:58:14 -0300 Subject: [PATCH 17/70] backlink in demo --- .../Examples/QueryBuilder.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 57a8b706..315d143c 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -38,16 +38,6 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var result = await new QueryBuilder().Select(ctx => new - { - Name = ctx.Include(), - Email = ctx.Include(), - BestFriendsBacklink = ctx.BackLink(x => x.BestFriends, () => new MultiLinkPerson - { - Name = ctx.Include() - }) - }).ExecuteAsync(client); - // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); @@ -90,6 +80,26 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) ) ).Build().Prettify(); + // Backlinks + query = new QueryBuilder().Select(ctx => new + { + Name = ctx.Include(), + Email = ctx.Include(), + BestFriends = ctx.IncludeLink(() => new MultiLinkPerson + { + Name = ctx.Include(), + Email = ctx.Include(), + }), + // The 'ReferencedFriends' will be equal to '. x.BestFriends, () => new MultiLinkPerson + { + Name = ctx.Include(), + Email = ctx.Include(), + }) + }).Build().Prettify(); + // Inserting a new type var person = new LinkPerson { From 5ae22c6e0030ae43d645caae5be4fc814c5f217b Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 11 Jul 2022 05:42:29 -0300 Subject: [PATCH 18/70] free objects & dotnet array set initializer translator --- .../Examples/QueryBuilder.cs | 9 +++++++++ .../Extensions/TypeExtensions.cs | 8 ++++++++ .../Interfaces/IMultiCardinalityExecutable.cs | 2 +- .../Interfaces/IMultiCardinalityQuery.cs | 12 ++++++++++++ .../Interfaces/ISingleCardinalityExecutable.cs | 2 +- .../Interfaces/ISingleCardinalityQuery.cs | 10 ++++++++++ src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 10 ++++++++-- src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 13 ++++++++++++- .../QueryNodes/Contexts/SelectContext.cs | 4 +++- .../QueryNodes/SelectNode.cs | 5 +++-- .../Expressions/ExpressionContext.cs | 9 ++++++++- .../Expressions/ExpressionTranslator.cs | 5 ++++- .../MethodCallExpressionTranslator.cs | 16 ++++++++++++++++ .../Expressions/NewArrayExpressionTranslator.cs | 17 +++++++++++++++++ .../Expressions/NewExpressionTranslator.cs | 2 +- 15 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 315d143c..55140165 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -80,6 +80,15 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) ) ).Build().Prettify(); + // selecting 'free objects' + query = QueryBuilder.Select(ctx => new + { + MyString = "This is a string", + MyNumber = 42, + SeveralNumbers = new long[] { 1, 2, 3 }, + People = ctx.SubQuery(QueryBuilder.Select()) + }).Build().Prettify(); + // Backlinks query = new QueryBuilder().Select(ctx => new { diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs index 91e4ecef..24e0c241 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; @@ -10,6 +11,13 @@ namespace EdgeDB { internal static class TypeExtensions { + public static bool IsAnonymousType(this Type type) + { + return + type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length > 0 && + type.FullName!.Contains("AnonymousType"); + } + public static string GetEdgeDBTypeName(this Type type) { var attr = type.GetCustomAttribute(); diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs index cb660874..1f673f30 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs @@ -10,7 +10,7 @@ namespace EdgeDB.Interfaces /// Represents an executable query with one or more returning objects. /// /// The object the query will return. - public interface IMultiCardinalityExecutable : IQueryBuilder, IQuery + public interface IMultiCardinalityExecutable : IQueryBuilder, IMultiCardinalityQuery { /// /// Executes the current query. diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs new file mode 100644 index 00000000..e53b0f8a --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces +{ + public interface IMultiCardinalityQuery : IQuery + { + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs index aa2589a0..4658b946 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs @@ -10,7 +10,7 @@ namespace EdgeDB.Interfaces /// Represents an executable query with at most one returning objects. /// /// The object the query will return. - public interface ISingleCardinalityExecutable : IQueryBuilder, IQuery + public interface ISingleCardinalityExecutable : IQueryBuilder, ISingleCardinalityQuery { /// /// Executes the current query. diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs new file mode 100644 index 00000000..2b254680 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces +{ + public interface ISingleCardinalityQuery : IQuery { } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 1449970e..e57600b2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -197,7 +197,12 @@ public ISelectQuery Select() public ISelectQuery Select(Expression> selectFunc) { - AddNode(new SelectContext(typeof(TResult)) { SelectExpressional = true, Shape = selectFunc }, true); + AddNode(new SelectContext(typeof(TResult)) + { + SelectExpressional = true, + Shape = selectFunc, + IsFreeObject = typeof(TResult).IsAnonymousType() + }); return EnterNewType(); } @@ -206,7 +211,8 @@ public ISelectQuery Select(Expression> shape) { AddNode(new SelectContext(typeof(TType)) { - Shape = shape + Shape = shape, + IsFreeObject = typeof(TType).IsAnonymousType(), }); return this; } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index 6b46ec7c..0e36450e 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -1,4 +1,5 @@ -using EdgeDB.Interfaces.Queries; +using EdgeDB.Interfaces; +using EdgeDB.Interfaces.Queries; using EdgeDB.Operators; using System; using System.Collections.Generic; @@ -111,5 +112,15 @@ public TType[] BackLink(Expression> propertySelector public TCollection BackLink(Expression> propertySelector, Expression> shape) where TCollection : IEnumerable => default!; + + public TType SubQuery(ISingleCardinalityQuery query) + => default!; + + public TType[] SubQuery(IMultiCardinalityQuery query) + => default!; + + public TCollection SubQuery(IMultiCardinalityQuery query) + where TCollection : IEnumerable + => default!; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs index 47d0d81d..4c59c912 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs @@ -11,7 +11,9 @@ internal class SelectContext : NodeContext { public LambdaExpression? Shape { get; init; } public string? SelectName { get; set; } - public bool SelectExpressional { get; set; } + public bool SelectExpressional { get; init; } + public bool IsFreeObject { get; init; } + public SelectContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index dc37d6b8..42d42789 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -48,7 +48,7 @@ private string GetShape() return GetDefaultShape(); } - if (Context.SelectExpressional) + if (Context.SelectExpressional && !Context.IsFreeObject) { return ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals); } @@ -71,7 +71,8 @@ private string GetShape() public override void Visit() { var shape = GetShape(); - if (Context.SelectExpressional) + + if (Context.SelectExpressional || Context.IsFreeObject) Query.Append($"select {shape}"); else Query.Append($"select {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()} {shape}"); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index 1aa677cf..e87f4c86 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -1,4 +1,5 @@ -using System; +using EdgeDB.QueryNodes; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -11,6 +12,8 @@ namespace EdgeDB { internal class ExpressionContext { + public List ExpressionTree { get; set; } = new(); + public NodeContext NodeContext { get; } public LambdaExpression RootExpression { get; } public Dictionary Parameters { get; } @@ -21,10 +24,13 @@ internal class ExpressionContext public bool IsShape { get; set; } public bool HasInitializationOperator { get; set; } public bool IncludeSelfReference { get; set; } = true; + public bool IsFreeObject + => NodeContext is SelectContext selectContext && selectContext.IsFreeObject; public ExpressionContext(NodeContext context, LambdaExpression rootExpression, IDictionary queryArguments, List globals) { + ExpressionTree.Add(rootExpression); RootExpression = rootExpression; _queryObjects = queryArguments; NodeContext = context; @@ -73,6 +79,7 @@ public ExpressionContext Enter(Action func) { var exp = (ExpressionContext)MemberwiseClone(); func(exp); + exp.ExpressionTree = ExpressionTree.ToList(); // creates a new instance as we dont want to copy ref of this contexts tree. return exp; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs index 321ba759..a6c2ea58 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -67,7 +67,10 @@ protected static string TranslateExpression(Expression expression, ExpressionCon expType = expType.BaseType!; if (_translators.TryGetValue(expType, out var translator)) - return translator.Translate(expression, context)!; + { + + return translator.Translate(expression, context.Enter(x => x.ExpressionTree.Add(expression)))!; + } throw new NotSupportedException($"Failed to find translator for expression type: {expType.Name}.{expression.NodeType}"); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index d059df9d..d955ed20 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -79,6 +79,22 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator + { + public override string? Translate(NewArrayExpression expression, ExpressionContext context) + { + return $"{{ {string.Join(", ", expression.Expressions.Select(x => TranslateExpression(x, context)))} }}"; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs index 3f37515a..b04658ad 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs @@ -47,7 +47,7 @@ internal class NewExpressionTranslator : ExpressionTranslator value = TranslateExpression(expression.Arguments[i], context.Enter(x => x.LocalScope = expression.Type)); } - shape[i] = $"{edgedbName}{(isSetter ? " :=" : "")} {value}"; + shape[i] = $"{edgedbName}{(isSetter || context.IsFreeObject ? " :=" : "")} {value}"; } return string.Join(", ", shape); From 74c53fda6e1de3e0150106633616dfd67564657b Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 12 Jul 2022 08:18:28 -0300 Subject: [PATCH 19/70] internal documentation & minor refactor --- dbschema/default.esdl | 2 +- dbschema/migrations/00001.edgeql | 50 +++- dbschema/migrations/00002.edgeql | 9 - dbschema/migrations/00003.edgeql | 20 -- dbschema/migrations/00004.edgeql | 9 - dbschema/migrations/00005.edgeql | 11 - dbschema/migrations/00006.edgeql | 9 - dbschema/migrations/00007.edgeql | 13 - dbschema/migrations/00008.edgeql | 9 - dbschema/migrations/00009.edgeql | 11 - dbschema/migrations/00010.edgeql | 122 --------- dbschema/migrations/00011.edgeql | 90 ------- dbschema/migrations/00012.edgeql | 11 - dbschema/migrations/00013.edgeql | 11 - src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs | 12 +- .../Interfaces/IMultiCardinalityQuery.cs | 8 +- .../Interfaces/IQuery.cs | 4 +- .../Interfaces/ISingleCardinalityQuery.cs | 4 + src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 250 ++++++++++++++++-- src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 100 +++++++ src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs | 31 +++ .../QueryNodes/Contexts/DeleteContext.cs | 4 + .../QueryNodes/Contexts/InsertContext.cs | 8 + .../QueryNodes/Contexts/NodeContext.cs | 23 +- .../QueryNodes/Contexts/SelectContext.cs | 15 +- .../QueryNodes/Contexts/UpdateContext.cs | 12 +- .../QueryNodes/Contexts/WithContext.cs | 8 + .../QueryNodes/DeleteNode.cs | 5 + .../QueryNodes/InsertNode.cs | 125 +++++++-- .../QueryNodes/NodeBuilder.cs | 33 +++ .../QueryNodes/QueryNode.cs | 106 +++++++- .../QueryNodes/SelectNode.cs | 109 +++++--- .../QueryNodes/UpdateNode.cs | 39 ++- .../QueryNodes/WithNode.cs | 15 +- .../QueryObjectManager.cs | 46 +++- src/EdgeDB.Net.QueryBuilder/QueryUtils.cs | 115 +++++++- .../QueryableCollection.cs | 11 + .../Schema/DataTypes/ObjectType.cs | 22 ++ .../Schema/DataTypes/Property.cs | 36 ++- .../Schema/SchemaInfo.cs | 22 ++ .../Schema/SchemaIntrospector.cs | 29 ++ src/EdgeDB.Net.QueryBuilder/SubQuery.cs | 32 +++ .../Expressions/BinaryExpressionTranslator.cs | 7 + .../ConditionalExpressionTranslator.cs | 9 +- .../ConstantExpressionTranslator.cs | 10 +- .../Expressions/ExpressionContext.cs | 120 ++++++++- .../Expressions/ExpressionTranslator.cs | 74 +++++- .../Expressions/MemberExpressionTranslator.cs | 18 +- .../MemberInitExpressionTranslator.cs | 46 +++- .../MethodCallExpressionTranslator.cs | 36 ++- .../NewArrayExpressionTranslator.cs | 6 + .../Expressions/NewExpressionTranslator.cs | 33 +-- .../Expressions/UnaryExpressionTranslator.cs | 10 + 53 files changed, 1467 insertions(+), 503 deletions(-) delete mode 100644 dbschema/migrations/00002.edgeql delete mode 100644 dbschema/migrations/00003.edgeql delete mode 100644 dbschema/migrations/00004.edgeql delete mode 100644 dbschema/migrations/00005.edgeql delete mode 100644 dbschema/migrations/00006.edgeql delete mode 100644 dbschema/migrations/00007.edgeql delete mode 100644 dbschema/migrations/00008.edgeql delete mode 100644 dbschema/migrations/00009.edgeql delete mode 100644 dbschema/migrations/00010.edgeql delete mode 100644 dbschema/migrations/00011.edgeql delete mode 100644 dbschema/migrations/00012.edgeql delete mode 100644 dbschema/migrations/00013.edgeql diff --git a/dbschema/default.esdl b/dbschema/default.esdl index bdb435b9..4f6a125f 100644 --- a/dbschema/default.esdl +++ b/dbschema/default.esdl @@ -54,4 +54,4 @@ module default { } multi link best_friends -> MultiLinkPerson; } -} +} \ No newline at end of file diff --git a/dbschema/migrations/00001.edgeql b/dbschema/migrations/00001.edgeql index 5865ffc4..0b7626fc 100644 --- a/dbschema/migrations/00001.edgeql +++ b/dbschema/migrations/00001.edgeql @@ -1,8 +1,52 @@ -CREATE MIGRATION m1gajj2cjvikxjfhwnczhcfrcdlycg5pbg7vvehuaohazd2ltajlnq +CREATE MIGRATION m1fmzelzxxda652eddles3g56rysvccxzivewujttti4radwepsy5q ONTO initial { + CREATE ABSTRACT TYPE default::AbstractThing { + CREATE REQUIRED PROPERTY name -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + }; + CREATE TYPE default::OtherThing EXTENDING default::AbstractThing { + CREATE REQUIRED PROPERTY attribute -> std::str; + }; + CREATE TYPE default::Thing EXTENDING default::AbstractThing { + CREATE REQUIRED PROPERTY description -> std::str; + }; + CREATE TYPE default::LinkPerson { + CREATE LINK best_friend -> default::LinkPerson; + CREATE REQUIRED PROPERTY email -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY name -> std::str; + }; CREATE TYPE default::Person { - CREATE PROPERTY email -> std::str; - CREATE PROPERTY name -> std::str; + CREATE REQUIRED PROPERTY email -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY name -> std::str; + }; + CREATE TYPE default::Movie { + CREATE REQUIRED MULTI LINK actors -> default::Person; + CREATE REQUIRED LINK director -> default::Person; + CREATE REQUIRED PROPERTY title -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY year -> std::int32; + }; + CREATE TYPE default::MultiLinkPerson { + CREATE MULTI LINK best_friends -> default::MultiLinkPerson; + CREATE REQUIRED PROPERTY email -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY name -> std::str; + }; + CREATE SCALAR TYPE default::State EXTENDING enum; + CREATE TYPE default::TODO { + CREATE REQUIRED PROPERTY date_created -> std::datetime { + SET default := (std::datetime_current()); + }; + CREATE REQUIRED PROPERTY description -> std::str; + CREATE REQUIRED PROPERTY state -> default::State; + CREATE REQUIRED PROPERTY title -> std::str; }; }; diff --git a/dbschema/migrations/00002.edgeql b/dbschema/migrations/00002.edgeql deleted file mode 100644 index 2f42c09a..00000000 --- a/dbschema/migrations/00002.edgeql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE MIGRATION m1vqxxs4ie4so3x35nk2wyu3jy3hmolscpyxu6bccucaofjtnrtj3a - ONTO m1gajj2cjvikxjfhwnczhcfrcdlycg5pbg7vvehuaohazd2ltajlnq -{ - ALTER TYPE default::Person { - ALTER PROPERTY email { - CREATE CONSTRAINT std::exclusive; - }; - }; -}; diff --git a/dbschema/migrations/00003.edgeql b/dbschema/migrations/00003.edgeql deleted file mode 100644 index 0d296276..00000000 --- a/dbschema/migrations/00003.edgeql +++ /dev/null @@ -1,20 +0,0 @@ -CREATE MIGRATION m1tpouzupcrd2nkhl45qsdykw3dzjbk4ecndr73tmatxskaxkixu3q - ONTO m1vqxxs4ie4so3x35nk2wyu3jy3hmolscpyxu6bccucaofjtnrtj3a -{ - CREATE TYPE default::Movie { - CREATE REQUIRED MULTI LINK actors -> default::Person; - CREATE REQUIRED LINK director -> default::Person; - CREATE REQUIRED PROPERTY title -> std::str; - CREATE REQUIRED PROPERTY year -> std::int32; - }; - ALTER TYPE default::Person { - ALTER PROPERTY email { - SET REQUIRED USING ('e'); - }; - }; - ALTER TYPE default::Person { - ALTER PROPERTY name { - SET REQUIRED USING ('e'); - }; - }; -}; diff --git a/dbschema/migrations/00004.edgeql b/dbschema/migrations/00004.edgeql deleted file mode 100644 index 043ec31a..00000000 --- a/dbschema/migrations/00004.edgeql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE MIGRATION m1xp6ukrooc4chjfnhmaky4ywsoip5wvbz4ffm36tsqosspiyqjy6q - ONTO m1tpouzupcrd2nkhl45qsdykw3dzjbk4ecndr73tmatxskaxkixu3q -{ - ALTER TYPE default::Movie { - ALTER PROPERTY title { - CREATE CONSTRAINT std::exclusive; - }; - }; -}; diff --git a/dbschema/migrations/00005.edgeql b/dbschema/migrations/00005.edgeql deleted file mode 100644 index ee5b2c04..00000000 --- a/dbschema/migrations/00005.edgeql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE MIGRATION m1rgfzvgm77nvwkjt5i4hw3ruxtxoed4osu2hv7uj6huagohxxuu2q - ONTO m1xp6ukrooc4chjfnhmaky4ywsoip5wvbz4ffm36tsqosspiyqjy6q -{ - CREATE SCALAR TYPE default::State EXTENDING enum; - CREATE TYPE default::TODO { - CREATE REQUIRED PROPERTY date_created -> std::datetime; - CREATE REQUIRED PROPERTY description -> std::str; - CREATE REQUIRED PROPERTY state -> default::State; - CREATE REQUIRED PROPERTY title -> std::str; - }; -}; diff --git a/dbschema/migrations/00006.edgeql b/dbschema/migrations/00006.edgeql deleted file mode 100644 index 8296f5b3..00000000 --- a/dbschema/migrations/00006.edgeql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE MIGRATION m1bthirxb7a7lq7mjtrdjsxdcjcy4or3tlprzh74azy44h7n3re6zq - ONTO m1rgfzvgm77nvwkjt5i4hw3ruxtxoed4osu2hv7uj6huagohxxuu2q -{ - ALTER TYPE default::TODO { - ALTER PROPERTY date_created { - SET default := (std::datetime_current()); - }; - }; -}; diff --git a/dbschema/migrations/00007.edgeql b/dbschema/migrations/00007.edgeql deleted file mode 100644 index d75eb2bf..00000000 --- a/dbschema/migrations/00007.edgeql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE MIGRATION m1dntdma2rrziv35tbt7mfl7qeg3wpzaqk73edwaw5i4kkz5fg6z6a - ONTO m1bthirxb7a7lq7mjtrdjsxdcjcy4or3tlprzh74azy44h7n3re6zq -{ - CREATE ABSTRACT TYPE default::AbstractThing { - CREATE REQUIRED PROPERTY name -> std::str; - }; - CREATE TYPE default::OtherThing EXTENDING default::AbstractThing { - CREATE REQUIRED PROPERTY attribute -> std::str; - }; - CREATE TYPE default::Thing EXTENDING default::AbstractThing { - CREATE REQUIRED PROPERTY description -> std::str; - }; -}; diff --git a/dbschema/migrations/00008.edgeql b/dbschema/migrations/00008.edgeql deleted file mode 100644 index 92be5cd4..00000000 --- a/dbschema/migrations/00008.edgeql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE MIGRATION m1isiclyxqa32luj6hdazr4mft2mvvq4tmmuoygl7m7k2iimxm5y3a - ONTO m1dntdma2rrziv35tbt7mfl7qeg3wpzaqk73edwaw5i4kkz5fg6z6a -{ - ALTER TYPE default::AbstractThing { - ALTER PROPERTY name { - CREATE CONSTRAINT std::exclusive; - }; - }; -}; diff --git a/dbschema/migrations/00009.edgeql b/dbschema/migrations/00009.edgeql deleted file mode 100644 index 75e36a46..00000000 --- a/dbschema/migrations/00009.edgeql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE MIGRATION m1ckoojrq2xwxscsdfrsrrc3rem4afqpvjit65vawiavkhsfaigzna - ONTO m1isiclyxqa32luj6hdazr4mft2mvvq4tmmuoygl7m7k2iimxm5y3a -{ - CREATE TYPE default::LinkPerson { - CREATE LINK best_friend -> default::LinkPerson; - CREATE REQUIRED PROPERTY email -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - CREATE REQUIRED PROPERTY name -> std::str; - }; -}; diff --git a/dbschema/migrations/00010.edgeql b/dbschema/migrations/00010.edgeql deleted file mode 100644 index 1d045f57..00000000 --- a/dbschema/migrations/00010.edgeql +++ /dev/null @@ -1,122 +0,0 @@ -CREATE MIGRATION m1hk5lddsbraets3p3xtsj2rumcpydkdpbyoy3ssh3vqmudexna3ya - ONTO m1ckoojrq2xwxscsdfrsrrc3rem4afqpvjit65vawiavkhsfaigzna -{ - CREATE MODULE lexertest IF NOT EXISTS; - CREATE MODULE `💯💯💯` IF NOT EXISTS; - CREATE ABSTRACT ANNOTATION lexertest::`🍿`; - CREATE FUNCTION lexertest::`💯`(NAMED ONLY `🙀`: std::int64) -> std::int64 { - SET volatility := 'Immutable'; - CREATE ANNOTATION lexertest::`🍿` := 'fun!🚀'; - USING (SELECT - (100 - `🙀`) - ) - ;}; - CREATE SCALAR TYPE lexertest::Genre EXTENDING enum; - CREATE ABSTRACT TYPE lexertest::HasAge { - CREATE PROPERTY age -> std::int64; - }; - CREATE ABSTRACT TYPE lexertest::HasName { - CREATE PROPERTY name -> std::str; - }; - CREATE SCALAR TYPE lexertest::bag_seq EXTENDING std::sequence; - CREATE TYPE lexertest::Bag EXTENDING lexertest::HasName, lexertest::HasAge { - CREATE PROPERTY enumArr -> array; - CREATE PROPERTY bigintField -> std::bigint; - CREATE PROPERTY boolField -> std::bool; - CREATE PROPERTY datetimeField -> std::datetime; - CREATE PROPERTY decimalField -> std::decimal; - CREATE PROPERTY durationField -> std::duration; - CREATE PROPERTY float32Field -> std::float32; - CREATE PROPERTY float64Field -> std::float64; - CREATE PROPERTY genre -> lexertest::Genre; - CREATE PROPERTY int16Field -> std::int16; - CREATE PROPERTY int32Field -> std::int32; - CREATE PROPERTY int64Field -> std::int64; - CREATE PROPERTY jsonField -> std::json; - CREATE PROPERTY localDateField -> cal::local_date; - CREATE PROPERTY localDateTimeField -> cal::local_datetime; - CREATE PROPERTY localTimeField -> cal::local_time; - CREATE PROPERTY namedTuple -> tuple; - CREATE PROPERTY secret_identity -> std::str; - CREATE PROPERTY seqField -> lexertest::bag_seq; - CREATE MULTI PROPERTY stringMultiArr -> array; - CREATE PROPERTY stringsArr -> array; - CREATE REQUIRED MULTI PROPERTY stringsMulti -> std::str; - CREATE PROPERTY unnamedTuple -> tuple; - }; - CREATE ABSTRACT CONSTRAINT lexertest::`🚀🍿`(max: std::int64) EXTENDING std::max_len_value; - CREATE TYPE lexertest::A; - CREATE SCALAR TYPE lexertest::你好 EXTENDING std::str; - CREATE SCALAR TYPE lexertest::مرحبا EXTENDING lexertest::你好 { - CREATE CONSTRAINT lexertest::`🚀🍿`(100); - }; - CREATE SCALAR TYPE lexertest::`🚀🚀🚀` EXTENDING lexertest::مرحبا; - CREATE TYPE lexertest::Łukasz { - CREATE LINK `Ł💯` -> lexertest::A { - CREATE PROPERTY `🙀مرحبا🙀` -> lexertest::مرحبا { - CREATE CONSTRAINT lexertest::`🚀🍿`(200); - }; - CREATE PROPERTY `🙀🚀🚀🚀🙀` -> lexertest::`🚀🚀🚀`; - }; - CREATE REQUIRED PROPERTY `Ł🤞` -> lexertest::`🚀🚀🚀` { - SET default := ('你好🤞'); - }; - CREATE INDEX ON (.`Ł🤞`); - }; - CREATE TYPE lexertest::`S p a M` { - CREATE REQUIRED PROPERTY `🚀` -> std::int32; - CREATE PROPERTY c100 := (SELECT - lexertest::`💯`(`🙀` := .`🚀`) - ); - }; - CREATE FUNCTION `💯💯💯`::`🚀🙀🚀`(`🤞`: lexertest::`🚀🚀🚀`) -> lexertest::`🚀🚀🚀` USING (SELECT - (`🤞` ++ 'Ł🙀') - ); - CREATE ABSTRACT LINK lexertest::movie_character { - CREATE PROPERTY character_name -> std::str; - }; - CREATE ABSTRACT TYPE lexertest::Person { - CREATE REQUIRED PROPERTY name -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - }; - CREATE TYPE lexertest::Profile { - CREATE PROPERTY plot_summary -> std::str; - CREATE PROPERTY slug -> std::str { - SET readonly := true; - }; - }; - CREATE TYPE lexertest::Movie { - CREATE MULTI LINK characters EXTENDING lexertest::movie_character -> lexertest::Person; - CREATE LINK profile -> lexertest::Profile { - CREATE CONSTRAINT std::exclusive; - }; - CREATE PROPERTY genre -> lexertest::Genre; - CREATE PROPERTY rating -> std::float64; - CREATE REQUIRED PROPERTY release_year -> std::int16 { - SET default := (std::datetime_get(std::datetime_current(), 'year')); - }; - CREATE REQUIRED PROPERTY title -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - }; - ALTER TYPE lexertest::A { - CREATE REQUIRED LINK `s p A m 🤞` -> lexertest::`S p a M`; - }; - CREATE TYPE lexertest::Simple EXTENDING lexertest::HasName, lexertest::HasAge; - CREATE TYPE lexertest::Hero EXTENDING lexertest::Person { - CREATE PROPERTY number_of_movies -> std::int64; - CREATE PROPERTY secret_identity -> std::str; - }; - CREATE TYPE lexertest::Villain EXTENDING lexertest::Person { - CREATE LINK nemesis -> lexertest::Hero; - }; - ALTER TYPE lexertest::Hero { - CREATE MULTI LINK villains := (. lexertest::Movie; - CREATE REQUIRED PROPERTY username -> std::str; - }; - CREATE TYPE lexertest::MovieShape; -}; diff --git a/dbschema/migrations/00011.edgeql b/dbschema/migrations/00011.edgeql deleted file mode 100644 index 667f5b7d..00000000 --- a/dbschema/migrations/00011.edgeql +++ /dev/null @@ -1,90 +0,0 @@ -CREATE MIGRATION m1vs5zqu667wwcqfbuugfklx4jcnnu5wnzigwcvyohumszc2mddjeq - ONTO m1hk5lddsbraets3p3xtsj2rumcpydkdpbyoy3ssh3vqmudexna3ya -{ - ALTER FUNCTION lexertest::`💯`(NAMED ONLY `🙀`: std::int64) { - DROP ANNOTATION lexertest::`🍿`; - }; - DROP ABSTRACT ANNOTATION lexertest::`🍿`; - ALTER TYPE lexertest::Bag { - DROP PROPERTY enumArr; - DROP PROPERTY bigintField; - DROP PROPERTY boolField; - DROP PROPERTY datetimeField; - DROP PROPERTY decimalField; - DROP PROPERTY durationField; - DROP PROPERTY float32Field; - DROP PROPERTY float64Field; - DROP PROPERTY genre; - DROP PROPERTY int16Field; - DROP PROPERTY int32Field; - DROP PROPERTY int64Field; - DROP PROPERTY jsonField; - DROP PROPERTY localDateField; - DROP PROPERTY localDateTimeField; - DROP PROPERTY localTimeField; - DROP PROPERTY namedTuple; - DROP PROPERTY secret_identity; - DROP PROPERTY seqField; - DROP PROPERTY stringMultiArr; - DROP PROPERTY stringsArr; - DROP PROPERTY stringsMulti; - DROP PROPERTY unnamedTuple; - }; - DROP TYPE lexertest::Łukasz; - ALTER SCALAR TYPE lexertest::مرحبا { - DROP CONSTRAINT lexertest::`🚀🍿`(100); - }; - DROP ABSTRACT CONSTRAINT lexertest::`🚀🍿`; - ALTER TYPE lexertest::`S p a M` { - DROP PROPERTY c100; - DROP PROPERTY `🚀`; - }; - DROP FUNCTION lexertest::`💯`(NAMED ONLY `🙀`: std::int64); - DROP FUNCTION `💯💯💯`::`🚀🙀🚀`(`🤞`: lexertest::`🚀🚀🚀`); - ALTER ABSTRACT LINK lexertest::movie_character { - DROP PROPERTY character_name; - }; - ALTER TYPE lexertest::Movie { - DROP LINK characters; - DROP LINK profile; - DROP PROPERTY genre; - DROP PROPERTY rating; - DROP PROPERTY release_year; - DROP PROPERTY title; - }; - DROP ABSTRACT LINK lexertest::movie_character; - DROP TYPE lexertest::A; - ALTER TYPE lexertest::HasAge { - DROP PROPERTY age; - }; - ALTER TYPE lexertest::HasName { - DROP PROPERTY name; - }; - DROP TYPE lexertest::Bag; - DROP TYPE lexertest::Simple; - DROP TYPE lexertest::HasAge; - DROP TYPE lexertest::HasName; - ALTER TYPE lexertest::Person { - DROP PROPERTY name; - }; - ALTER TYPE lexertest::Hero { - DROP LINK villains; - DROP PROPERTY number_of_movies; - DROP PROPERTY secret_identity; - }; - DROP TYPE lexertest::Villain; - DROP TYPE lexertest::Hero; - DROP TYPE lexertest::User; - DROP TYPE lexertest::Movie; - DROP TYPE lexertest::MovieShape; - DROP TYPE lexertest::Person; - DROP TYPE lexertest::Profile; - DROP TYPE lexertest::`S p a M`; - DROP SCALAR TYPE lexertest::Genre; - DROP SCALAR TYPE lexertest::bag_seq; - DROP SCALAR TYPE lexertest::`🚀🚀🚀`; - DROP SCALAR TYPE lexertest::مرحبا; - DROP SCALAR TYPE lexertest::你好; - DROP MODULE lexertest; - DROP MODULE `💯💯💯`; -}; diff --git a/dbschema/migrations/00012.edgeql b/dbschema/migrations/00012.edgeql deleted file mode 100644 index ce2a14ce..00000000 --- a/dbschema/migrations/00012.edgeql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE MIGRATION m1qocyoinu75piizgrcf5veo5cq3tw5jzywpjf7exlhrb6rmz4ngua - ONTO m1vs5zqu667wwcqfbuugfklx4jcnnu5wnzigwcvyohumszc2mddjeq -{ - CREATE TYPE default::MultiLinkPerson { - CREATE MULTI LINK best_friends -> default::LinkPerson; - CREATE REQUIRED PROPERTY email -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - CREATE REQUIRED PROPERTY name -> std::str; - }; -}; diff --git a/dbschema/migrations/00013.edgeql b/dbschema/migrations/00013.edgeql deleted file mode 100644 index 9a7c13e3..00000000 --- a/dbschema/migrations/00013.edgeql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE MIGRATION m1tcypfnfc5mkkldxbjqz5awkngj6gjleg7w3qrbagyq5k47uys6na - ONTO m1qocyoinu75piizgrcf5veo5cq3tw5jzywpjf7exlhrb6rmz4ngua -{ - ALTER TYPE default::MultiLinkPerson { - ALTER LINK best_friends { - SET TYPE default::MultiLinkPerson USING (SELECT - default::MultiLinkPerson - ); - }; - }; -}; diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs b/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs index 393d86e0..371ecd18 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs +++ b/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs @@ -6,11 +6,21 @@ namespace EdgeDB { - public class EdgeDBObject + /// + /// Represents a generic object within EdgeDB. + /// + public sealed class EdgeDBObject { + /// + /// 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) { diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs index e53b0f8a..e15c468c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs @@ -6,7 +6,9 @@ namespace EdgeDB.Interfaces { - 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 a5a6625e..d7437308 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs @@ -10,7 +10,5 @@ namespace EdgeDB /// Represents a generic query. /// /// The inner 'working' type of the query. - public interface IQuery : IQueryBuilder - { - } + public interface IQuery : IQueryBuilder { } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs index 2b254680..4959487d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs @@ -6,5 +6,9 @@ namespace EdgeDB.Interfaces { + /// + /// Represents a query with a cardinality of . + /// + /// The result type of the query. public interface ISingleCardinalityQuery : IQuery { } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index e57600b2..ece5c0b2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -49,19 +49,45 @@ public static IDeleteQuery Delete() /// /// Represents a query builder used to build queries against . /// - /// + /// The type that this query builder is currently building queries for. public sealed class QueryBuilder : IQueryBuilder { + /// + /// A list of query nodes that make up the current query builder. + /// private readonly List _nodes; + + /// + /// The current user defined query node. + /// private QueryNode? CurrentUserNode => _nodes.LastOrDefault(x => !x.IsAutoGenerated); + + /// + /// A list of query globals used by this query builder. + /// private readonly List _queryGlobals; + + /// + /// The current schema introspection info if it has been fetched. + /// private SchemaInfo? _schemaInfo; + + /// + /// A dictionary of query variables used by the . + /// private readonly Dictionary _queryVariables; + + /// + /// Initializes the . + /// static QueryBuilder() { QueryObjectManager.Initialize(); } + /// + /// Constructs an empty query builder. + /// internal QueryBuilder() { _nodes = new(); @@ -69,6 +95,12 @@ internal QueryBuilder() _queryVariables = new(); } + /// + /// Constructs a query builder with the given nodes, globals, and variables. + /// + /// The query nodes to initialize with. + /// The query globals to initialize with. + /// The query variables to initialize with. internal QueryBuilder(List nodes, List globals, Dictionary variables) { _nodes = nodes; @@ -76,33 +108,67 @@ internal QueryBuilder(List nodes, List globals, Dictiona _queryVariables = variables; } + /// + /// 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() => new(_nodes, _queryGlobals, _queryVariables); + /// + /// 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 parent node for the newly added node. + /// An instance of the specified . private TNode AddNode(NodeContext context, bool autoGenerated = false, QueryNode? parent = null) where TNode : QueryNode { - // create the node and a builder + // 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)!; // visit the node node.Visit(); - + _nodes.Add(node); parent?.SubNodes.Add(node); return node; } - + + /// + /// Builds the current query builder into its form. + /// + /// + /// Whether or not to include globals in the query string. + /// + /// + /// A which is the current query this builder has constructed. + /// internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) { List query = new(); @@ -110,53 +176,58 @@ internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) var nodes = _nodes; + // reference the introspection and finalize all nodes. foreach (var node in nodes) { node.SchemaInfo ??= _schemaInfo; node.FinalizeQuery(); } - // create a with block if we have any query globals + // create a with block if we have any globals if (includeGlobalsInQuery && _queryGlobals.Any()) { - var with = new WithNode(new NodeBuilder(new WithContext(typeof(TType)) + var builder = new NodeBuilder(new WithContext(typeof(TType)) { Values = _queryGlobals, - }, _queryGlobals, null, _queryVariables)) + }, _queryGlobals, null, _queryVariables); + + var with = new WithNode(builder) { SchemaInfo = _schemaInfo }; + // visit the with node and add it to the front of our local collection of nodes. with.Visit(); nodes = nodes.Prepend(with).ToList(); } + // build each node starting at the last node. for (int i = nodes.Count - 1; i >= 0; i--) { var node = nodes[i]; - if (node is WithNode withNode) - { - if (!withNode.HasVisited) - withNode.Visit(); - } - var result = node.Build(); + // add the nodes query string if its not null or empty. if (!string.IsNullOrEmpty(result.Query)) query.Add(result.Query); - + + // add any parameters the node has. parameters.Add(result.Parameters); } + // reverse our query string since we built our nodes in reverse. query.Reverse(); + // flatten our parameters into a single collection and make it distinct. var variables = parameters .SelectMany(x => x) .DistinctBy(x => x.Key); + // add any variables that might have been added by other builders in a sub-query context. variables = variables.Concat(_queryVariables.Where(x => !variables.Any(x => x.Key == x.Key))); + // construct a built query with our query text, variables, and globals. return new BuiltQuery(string.Join(' ', query)) { Parameters = variables @@ -174,6 +245,7 @@ public BuiltQuery Build() public ValueTask BuildAsync(IEdgeDBQueryable edgedb, CancellationToken token = default) => IntrospectAndBuildAsync(edgedb, token); + /// internal BuiltQuery BuildWithGlobals() => InternalBuild(false); @@ -195,11 +267,11 @@ public ISelectQuery Select() return this; } + /// public ISelectQuery Select(Expression> selectFunc) { AddNode(new SelectContext(typeof(TResult)) { - SelectExpressional = true, Shape = selectFunc, IsFreeObject = typeof(TResult).IsAnonymousType() }); @@ -305,6 +377,14 @@ public IDeleteQuery Delete #endregion #region Generic sub-query methods + /// + /// 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) @@ -321,6 +401,18 @@ private QueryBuilder Filter(LambdaExpression filter) 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) @@ -331,6 +423,14 @@ private QueryBuilder OrderBy(bool asc, LambdaExpression selector, OrderBy 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) @@ -341,6 +441,14 @@ private QueryBuilder Offset(long offset) 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) @@ -351,6 +459,14 @@ private QueryBuilder OffsetExp(LambdaExpression offset) 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) { if (CurrentUserNode is not SelectNode selectNode) @@ -361,6 +477,14 @@ private QueryBuilder Limit(long limit) return this; } + /// + /// 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) @@ -371,6 +495,16 @@ private QueryBuilder LimitExp(LambdaExpression limit) return this; } + /// + /// 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) @@ -381,6 +515,16 @@ private QueryBuilder LimitExp(LambdaExpression limit) return this; } + /// + /// 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) @@ -391,6 +535,13 @@ private QueryBuilder UnlessConflictOn(LambdaExpression selector) return this; } + /// + /// 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) @@ -401,6 +552,14 @@ private QueryBuilder ElseReturnDefault() return this; } + /// + /// 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) @@ -411,7 +570,17 @@ private QueryBuilder ElseReturnDefault() return EnterNewType(); } - private QueryBuilder Else(Func, IMultiCardinalityExecutable> func) + /// + /// 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}"); @@ -423,7 +592,17 @@ private QueryBuilder Else(Func, IMultiCardinalityExe return this; } - private QueryBuilder Else(Func, ISingleCardinalityExecutable> func) + /// + /// 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}"); @@ -510,6 +689,15 @@ IDeleteQuery IDeleteQuery.Limit(Expression LimitExp(limit); #endregion + /// + /// Preforms introspection and then builds this query builder into a . + /// + /// The client to preform introspection with. + /// A cancellation token to cancel the introspection query. + /// + /// A ValueTask representing the (a)sync introspection and building operation. + /// The result is the built form of this query builder. + /// private async ValueTask IntrospectAndBuildAsync(IEdgeDBQueryable edgedb, CancellationToken token) { if(_nodes.Any(x => x.RequiresIntrospection)) @@ -522,11 +710,14 @@ private async ValueTask IntrospectAndBuildAsync(IEdgeDBQueryable edg return result; } + /// async Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) { var result = await IntrospectAndBuildAsync(edgedb, token).ConfigureAwait(false); return await edgedb.QueryAsync(result.Query, result.Parameters, token).ConfigureAwait(false); } + + /// async Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) { var result = await IntrospectAndBuildAsync(edgedb, token).ConfigureAwait(false); @@ -657,8 +848,19 @@ public interface IQueryBuilder : /// public interface IQueryBuilder { + /// + /// 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 IReadOnlyCollection Globals { get; } + + /// + /// Gets a read-only dictionary of query variables defined within the query builder. + /// internal IReadOnlyDictionary Variables { get; } /// @@ -680,6 +882,14 @@ public interface IQueryBuilder /// A . ValueTask BuildAsync(IEdgeDBQueryable edgedb, CancellationToken token = default); + /// + /// Builds the current query builder into its + /// form and exlcudes globals from the query text and puts them in + /// . + /// + /// + /// A which is the current query this builder has constructed. + /// internal BuiltQuery BuildWithGlobals(); } @@ -692,19 +902,19 @@ public class BuiltQuery /// /// Gets the query text. /// - public string Query { get; init; } + public string Query { get; internal init; } /// /// Gets a collection of parameters for the query. /// - public IDictionary? Parameters { get; init; } + public IDictionary? Parameters { get; internal init; } internal List? Globals { get; init; } /// /// Creates a new built query. /// /// The query text. - public BuiltQuery(string query) + internal BuiltQuery(string query) { Query = query; } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index 0e36450e..d62d1af0 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -73,6 +73,14 @@ public TType UnsafeLocal(string name) public object? UnsafeLocal(string name) => default!; + /// + /// 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 TType Raw(string query) => default!; @@ -86,39 +94,131 @@ public TType Raw(string query) public TType Include() => default!; + /// + /// Includes a link property with a given shape. + /// + /// The type of the link property. + /// The shape of the link property. + /// + /// A mock reference to the property that this include statement is being assigned to. + /// public TType IncludeLink(Expression> shape) => default!; + /// + /// Includes a multi link property with a given shape. + /// + /// The type of the multi link property. + /// The shape of the multi link property. + /// + /// A mock reference to the property that this include statement is being assigned to. + /// public TType[] IncludeMultiLink(Expression> shape) => default!; + /// + /// Includes a multi link property with a given shape. + /// + /// The type of the multi link property. + /// A collection that should be returned instead of an array + /// The shape of the multi link property. + /// + /// A mock reference to the property that this include statement is being assigned to. + /// public TCollection IncludeMultiLink(Expression> shape) where TCollection : IEnumerable => default!; + /// + /// 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 EdgeDBObject[] BackLink(string property) => default!; + /// + /// 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 TCollection BackLink(string property) where TCollection : IEnumerable => default!; + /// + /// 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 TType[] BackLink(Expression> propertySelector) => default!; + /// + /// 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 TType[] BackLink(Expression> propertySelector, Expression> shape) => default!; + /// + /// 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 TCollection BackLink(Expression> propertySelector, Expression> shape) where TCollection : IEnumerable => default!; + /// + /// 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 TType SubQuery(ISingleCardinalityQuery query) => default!; + /// + /// 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 TType[] SubQuery(IMultiCardinalityQuery query) => default!; + /// + /// 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 TCollection SubQuery(IMultiCardinalityQuery query) where TCollection : IEnumerable => default!; diff --git a/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs b/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs index 7910c11f..f0d134f3 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs @@ -6,18 +6,49 @@ namespace EdgeDB { + /// + /// Represents a globally defined variables contained within a 'WITH' statement. + /// internal class QueryGlobal { + /// + /// Gets the value that was included in the 'WITH' statement. + /// public object? Value { get; init; } + + /// + /// 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; } + + /// + /// Gets the name of the global. + /// public string Name { 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; } + /// + /// 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; diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs index 3362976a..56925020 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs @@ -6,8 +6,12 @@ namespace EdgeDB.QueryNodes { + /// + /// Represents the context for a . + /// internal class DeleteContext : SelectContext { + /// public DeleteContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs index 5869b44d..f423fbfe 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs @@ -6,9 +6,17 @@ namespace EdgeDB.QueryNodes { + /// + /// Represents context for a . + /// internal class InsertContext : NodeContext { + /// + /// Gets the value that is to be inserted. + /// public object? Value { get; init; } + + /// public InsertContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs index bef5e558..f558858d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs @@ -5,14 +5,33 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB +namespace EdgeDB.QueryNodes { - internal class NodeContext + /// + /// Represents a 's context. + /// + internal abstract class NodeContext { + /// + /// Gets or sets whether or not the node should be set as a global sub query. + /// public bool SetAsGlobal { get; set; } + + /// + /// Gets the name of the global variable the node should be set to + /// if is true. + /// public string? GlobalName { get; init; } + + /// + /// 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; diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs index 4c59c912..7bd97626 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs @@ -7,11 +7,24 @@ namespace EdgeDB.QueryNodes { + /// + /// Represents the context for a . + /// internal class SelectContext : NodeContext { + /// + /// Gets the shape of the select statement. + /// public LambdaExpression? Shape { get; init; } + + /// + /// Gets or sets the name that is to be selected. + /// public string? SelectName { get; set; } - public bool SelectExpressional { get; init; } + + /// + /// Gets whether or not the select statement is selecting a free object. + /// public bool IsFreeObject { get; init; } public SelectContext(Type currentType) : base(currentType) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs index 3ba97dee..9863cc46 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs @@ -7,14 +7,22 @@ namespace EdgeDB.QueryNodes { + /// + /// Represents context for a . + /// internal class UpdateContext : NodeContext { - public string? UpdateName { get; init; } + /// + /// Gets the update factory used within the 'SET' statement. + /// public LambdaExpression? UpdateExpression { get; init; } + /// + /// Gets a collection of child queries. + /// internal Dictionary ChildQueries { get; } = new(); - + /// public UpdateContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs index 0670af88..c342b692 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs @@ -6,9 +6,17 @@ namespace EdgeDB.QueryNodes { + /// + /// Represents context for a . + /// internal class WithContext : NodeContext { + /// + /// Gets the global variables that are included in the 'WITH' statement. + /// public List? Values { get; init; } + + /// public WithContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs index 60e7f8cf..63ec62f4 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs @@ -6,12 +6,17 @@ namespace EdgeDB.QueryNodes { + /// + /// Represents a 'DELETE' node + /// internal class DeleteNode : SelectNode { + /// public DeleteNode(NodeBuilder builder) : base(builder) { } + /// public override void Visit() { Query.Append($"delete {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()}"); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 58aeba15..5bc4ac58 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -10,88 +10,132 @@ namespace EdgeDB.QueryNodes { + /// + /// Represents a 'INSERT' node. + /// internal class InsertNode : QueryNode { + /// + /// Whether or not to autogenerate the unless conflict clause. + /// private bool _autogenerateUnlessConflict; - private readonly StringBuilder _children; + + /// + /// The else clause if any. + /// + private readonly StringBuilder _elseStatement; + + /// + /// The list of currently inserted types used to determine if + /// a nested query can be preformed. + /// private readonly List _subQueryMap = new(); + /// public InsertNode(NodeBuilder builder) : base(builder) { - _children = new(); + _elseStatement = new(); } - private string BuildInsertLambdaShape(LambdaExpression expression) - { - return $"{{ {ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; - } - + /// + /// 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 string BuildInsertShape(Type? shapeType = null, object? shapeValue = null) { List shape = new(); + // use the provide shape and value if they're not null, otherwise + // use the ones defined in context var type = shapeType ?? Context.CurrentType; var value = shapeValue ?? Context.Value; + // if the value is an expression we can just directly translate it if (value is LambdaExpression expression) - return BuildInsertLambdaShape(expression); + return $"{{ {ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; + // get all properties that aren't marked with the EdgeDBIgnore attribute var properties = type.GetProperties().Where(x => x.GetCustomAttribute() == null); foreach(var property in properties) { - // might be multi link + // define the type and whether or not it's a link var propType = property.PropertyType; var isLink = QueryUtils.IsLink(property.PropertyType, out var isArray, out var innerType); + // get the equivalent edgedb property name var propertyName = property.GetEdgeDBPropertyName(); + // get the scalar type var edgeqlType = PacketSerializer.GetEdgeQLType(propType); + // if a scalar type is found for the property type if(edgeqlType != null) { + // set it as a variable and continue the iteration var varName = QueryUtils.GenerateRandomVariableName(); SetVariable(varName, property.GetValue(value)); shape.Add($"{propertyName} := <{edgeqlType}>${varName}"); continue; } - // might be a link? + // if the property is a link if (isLink) { + // get the value var subValue = property.GetValue(value); + // if its null we can append an empty set if (subValue is null) shape.Add($"{propertyName} := {{}}"); - else if (isArray) + else if (isArray) // if its a multi link { List subShape = new(); + + // iterate over all values and generate their resolver foreach (var item in (IEnumerable)subValue!) { subShape.Add(BuildLinkResolver(innerType!, item)); } + // append the sub-shape shape.Add($"{propertyName} := {{ {string.Join(", ", subShape)} }}"); } - else + else // generate the link resolver and append it shape.Add($"{propertyName} := {BuildLinkResolver(propType, subValue)}"); continue; } - throw new Exception($"Failed to find method to serialize the property \"{property.PropertyType.Name}\" on type {type.Name}"); + throw new InvalidOperationException($"Failed to find method to serialize the property \"{property.PropertyType.Name}\" on type {type.Name}"); } return $"{{ {string.Join(", ", shape)} }}"; } + /// + /// Resolves a sub query for a link. + /// + /// 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 string BuildLinkResolver(Type type, object? value) { + // if the value is null we can just return an empty set if (value is null) return "{}"; + // is it a value thats 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}\")"), @@ -100,6 +144,8 @@ private string BuildLinkResolver(Type type, object? value) else { RequiresIntrospection = true; + + // add a insert select statement return InlineOrGlobal(type, new SubQuery((info) => { var name = type.GetEdgeDBTypeName(); @@ -112,26 +158,46 @@ private string BuildLinkResolver(Type type, object? value) } } - private string InlineOrGlobal(Type type, object value, object? reference) + /// + /// 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 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 string InlineOrGlobal(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 is SubQuery sq && sq.RequiresIntrospection)) return GetOrAddGlobal(reference, value); + // add it to our sub query map and return the inlined version _subQueryMap.Add(type); return value is SubQuery subQuery && subQuery.Query != null ? subQuery.Query : value.ToString()!; } + /// public override void Visit() { + // add the current type to the sub query map _subQueryMap.Add(Context.CurrentType); + + // build the insert shape var shape = BuildInsertShape(); + + // append it to our query Query.Append($"insert {Context.CurrentType.GetEdgeDBTypeName()} {shape}"); } + /// public override void FinalizeQuery() { + // if we require autogeneration of the unless conflict statement if(_autogenerateUnlessConflict) { if (SchemaInfo is null) @@ -140,43 +206,64 @@ public override void FinalizeQuery() if (!SchemaInfo.TryGetObjectInfo(Context.CurrentType, out var typeInfo)) throw new NotSupportedException($"Could not find type info for {Context.CurrentType}"); + // get all exclusive properties that aren't the id property var exclusiveProperties = typeInfo.Properties?.Where(x => x.IsExclusive && x.Name != "id"); if (exclusiveProperties == null || !exclusiveProperties.Any()) throw new NotSupportedException($"The type {typeInfo.Name} does not have any user defined exclusive properties"); + // build the constraints + // TODO: (.prop1, .prop2) isn't valid for multiple contraints. var constraint = exclusiveProperties.Count() > 1 ? $"({string.Join(", ", exclusiveProperties.Select(x => $".{x.Name}"))})" : $".{exclusiveProperties.First().Name}"; Query.Append($" unless conflict on {constraint}"); } + else + Query.Append(_elseStatement); - Query.Append(_children); - - if(Context.SetAsGlobal && Context.GlobalName != null) + // if the query builder wants this node as a global + if (Context.SetAsGlobal && Context.GlobalName != null) { SetGlobal(Context.GlobalName, new SubQuery($"({Query})"), null); Query.Clear(); } } + /// + /// Adds a unless conflict on (...) statement to the insert node + /// + /// + /// This method requires introspection on the step. + /// public void UnlessConflict() { _autogenerateUnlessConflict = 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) { Query.Append($" unless conflict on {ExpressionTranslator.Translate(selector, Builder.QueryVariables, Context, Builder.QueryGlobals)}"); } + /// + /// Adds the default else clause to the insert node that returns the conflicting object. + /// public void ElseDefault() { - _children.Append($" else (select {Context.CurrentType.GetEdgeDBTypeName()})"); + _elseStatement.Append($" else (select {Context.CurrentType.GetEdgeDBTypeName()})"); } + /// + /// Adds a else statement to the insert node. + /// + /// The builder that contains the else statement. public void Else(IQueryBuilder builder) { // remove addon & autogen nodes. @@ -195,7 +282,7 @@ public void Else(IQueryBuilder builder) var newBuilder = new QueryBuilder(userNodes.ToList(), builder.Globals.ToList(), builder.Variables.ToDictionary(x => x.Key, x=> x.Value)); var result = newBuilder.BuildWithGlobals(); - _children.Append($" else ({result.Query})"); + _elseStatement.Append($" else ({result.Query})"); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs index b9a5c8b8..ed3e52de 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs @@ -7,15 +7,48 @@ namespace EdgeDB { + /// + /// Represents a builder used by s to build a section of a query. + /// internal class NodeBuilder { + /// + /// Gets the string builder for the current nodes query. + /// public StringBuilder Query { get; } + + /// + /// Gets a collection of nodes currently within the builder. + /// public List Nodes { get; } + + /// + /// Gets the node context for the current builder. + /// public NodeContext Context { get; } + + /// + /// Gets the query variable collection used to add new variables. + /// public Dictionary QueryVariables { get; } + + /// + /// Gets the query global collection used to add new globals. + /// public List QueryGlobals { get; } + + /// + /// Gets whether or not the current node is auto generated. + /// public bool IsAutoGenerated { get; init; } + /// + /// 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) { Query = new(); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index dc54706f..29f41fef 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -7,47 +7,122 @@ 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 { + /// + /// 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 bool RequiresIntrospection { get; protected set; } + + /// + /// Gets or sets the schema introspection data. + /// public SchemaInfo? SchemaInfo { get; set; } + + /// + /// Gets a collection of referenced globals set by this node. + /// internal List ReferencedGlobals { get; } = new(); + + /// + /// Gets a collection of referenced variables set by this node. + /// internal List ReferencedVariables { get; } = new(); + + /// + /// Gets a collection of child nodes. + /// internal List SubNodes { get; } = new(); - internal readonly NodeBuilder Builder; + + /// + /// Gets the query string for this node. + /// internal StringBuilder Query => Builder.Query; + /// + /// Gets the context for this node. + /// internal NodeContext Context => Builder.Context; + /// + /// The builder used to build this node. + /// + internal readonly NodeBuilder Builder; + + /// + /// Constructs a new query node with the given builder. + /// + /// the builder used to build this node. public QueryNode(NodeBuilder builder) { Builder = builder; } + /// + /// 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 abstract void Visit(); + + /// + /// Finalizes the nodes query, completing the second and final phase of this + /// nodes build process. can now be safely called. + /// public virtual void FinalizeQuery() { } + /// + /// 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) { ReferencedVariables.Add(name); 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 void SetGlobal(string name, object? value, object? reference) { var global = new QueryGlobal(name, value, reference); @@ -55,6 +130,12 @@ protected void SetGlobal(string name, object? value, object? reference) ReferencedGlobals.Add(Builder.QueryGlobals.IndexOf(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); @@ -65,15 +146,38 @@ protected string GetOrAddGlobal(object? reference, object? value) return name; } + /// + /// Builds the current node, returning the built form . + /// + /// + /// Both and must be called + /// before this function in order to ensure this node has finished generating. + /// + /// A . internal BuiltQueryNode Build() => new(Query.ToString(), Builder.QueryVariables); } + /// + /// Represents the built form of a + /// internal class BuiltQueryNode { + /// + /// Gets the query text the node generated. + /// public string Query { get; init; } + + /// + /// Gets the collection of variables this node is using. + /// public IDictionary Parameters { get; init; } + /// + /// Constructs a new . + /// + /// The query text the node generated. + /// The collection of variables this node is using. public BuiltQueryNode(string query, IDictionary parameters) { Query = query; diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 42d42789..1875b642 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -10,18 +10,37 @@ namespace EdgeDB.QueryNodes { + /// + /// Represents a 'SELECT' node + /// internal class SelectNode : QueryNode { + /// + /// The max recursion depth for generating default shapes. + /// public const int MAX_DEPTH = 1; + + /// public SelectNode(NodeBuilder builder) : base(builder) { } + /// + /// Gets a shape for the given type. + /// + /// The type to get the shape for. + /// The current depth of the shape. + /// The shape of the given type. private string GetShape(Type type, int currentDepth = 0) { + // get all properties that dont have the 'EdgeDBIgnore' attribute var properties = type.GetProperties().Where(x => x.GetCustomAttribute() == null); + // map each property to its shape form var propertyNames = properties.Select(x => { - var name = x.GetCustomAttribute()?.Name ?? TypeBuilder.NamingStrategy.GetName(x); + // get the edgedb name equivalent + var name = x.GetEdgeDBPropertyName(); + + // if its a link, build a nested shape if we're not past our max depth if (QueryUtils.IsLink(x.PropertyType, out var isArray, out var innerType)) { var shapeType = isArray ? innerType! : x.PropertyType; @@ -29,89 +48,113 @@ private string GetShape(Type type, int currentDepth = 0) return $"{name}: {GetShape(shapeType, currentDepth + 1)}"; return null; } - else - { + else // return just the name return name; - } }).Where(x => x != null); + // join our properties by commas and wrap it in braces return $"{{ {string.Join(", ", propertyNames)} }}"; } + /// + /// Gets the default shape for the current contextual type. + /// + /// The default shape for the current contextual type. private string GetDefaultShape() => GetShape(Context.CurrentType); + /// + /// Gets the shape based on the context of the current node. + /// + /// + /// private string GetShape() { + // if no user-defined shape was passed in, generate the default shape if(Context.Shape == null) { return GetDefaultShape(); } - if (Context.SelectExpressional && !Context.IsFreeObject) - { - return ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals); - } + // generate the shape based on the contexts' expression. + return $"{{ {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; + } - // if its a call to a global - if (Context.Shape.Body is MethodCallExpression) + public override void Visit() + { + // if our shape is 'new {...}' or null then parse the shape + if (Context.Shape?.Body is NewExpression or MemberInitExpression || Context.Shape is null) { - var exp = ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals); - Context.SelectName = exp; - return GetDefaultShape(); + var shape = GetShape(); + + if (Context.IsFreeObject) + Query.Append($"select {shape}"); + else + Query.Append($"select {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()} {shape}"); } - else if (Context.Shape.Body is NewExpression or MemberInitExpression) + else { - return $"{{ {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; + // else we can just translate the shape and append it. + Query.Append($"select {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals)}"); } - - throw new NotSupportedException($"Cannot use {Context.Shape.GetType().Name} as a shape"); - } - - public override void Visit() - { - var shape = GetShape(); - - if (Context.SelectExpressional || Context.IsFreeObject) - Query.Append($"select {shape}"); - else - Query.Append($"select {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()} {shape}"); } + /// + /// Adds a filter to the select node. + /// + /// The filter predicate to add. public void Filter(LambdaExpression expression) { - if (expression is null) - throw new ArgumentNullException(nameof(expression), "No expression was passed in for a filter node"); - var parsedExpression = ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals); Query.Append($" filter {parsedExpression}"); } + /// + /// Adds a ordery 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) { - if (selector is null) - throw new ArgumentNullException(nameof(selector), "No expression was passed in for an order by node"); - var parsedExpression = ExpressionTranslator.Translate(selector, Builder.QueryVariables, Context, Builder.QueryGlobals); var direction = asc ? "asc" : "desc"; Query.Append($" order by {parsedExpression} {direction}{(nullPlacement.HasValue ? $" {nullPlacement.Value.ToString().ToLowerInvariant()}" : "")}"); } + /// + /// Adds a offest statement to the select node. + /// + /// The number of elements to offset by. internal void Offset(long offset) { Query.Append($" offset {offset}"); } + /// + /// Adds a offest statement to the select node. + /// + /// The expression returing the number of elements to offset by. internal void OffsetExpression(LambdaExpression exp) { Query.Append($" offset {exp}"); } + /// + /// Adds a limit statement to the select node. + /// + /// The number of element to limit to. internal void Limit(long limit) { Query.Append($" limit {limit}"); } + /// + /// Adds a limit statement to the select node. + /// + /// The expression returing the number of elements to limit to. internal void LimitExpression(LambdaExpression exp) { Query.Append($" limit {exp}"); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs index 3053bcb7..cbcd7982 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs @@ -7,31 +7,50 @@ namespace EdgeDB.QueryNodes { + /// + /// Represents a 'UPDATE' node. + /// internal class UpdateNode : QueryNode { - public UpdateNode(NodeBuilder builder) : base(builder) { } - + /// + /// The translated update factory. + /// private string? _translatedExpression; + + /// + public UpdateNode(NodeBuilder builder) : base(builder) { } + /// public override void Visit() { - Query.Append($"update {Context.UpdateName ?? Context.CurrentType.GetEdgeDBTypeName()}"); + // append 'update type' + Query.Append($"update {Context.CurrentType.GetEdgeDBTypeName()}"); + + // translate the update factory _translatedExpression = ExpressionTranslator.Translate(Context.UpdateExpression!, Builder.QueryVariables, Context, Builder.QueryGlobals); + // set whether or not we need introspection based on our child queries RequiresIntrospection = Context.ChildQueries.Any(x => x.Value.RequiresIntrospection); } + /// public override void FinalizeQuery() { + // add our 'set' statement to our translated update factory Query.Append($" set {{ {_translatedExpression} }}"); + // throw if we dont have introspection data when a child requires it if (RequiresIntrospection && SchemaInfo is null) throw new InvalidOperationException("This node requires schema introspection but none was provided"); + // set each child as a global foreach (var child in Context.ChildQueries) - SetGlobal(child.Key, child.Value, null); // sub query will be built with introspection by the 'With' node. - - + { + // sub query will be built with introspection by the with node. + SetGlobal(child.Key, child.Value, null); + } + + // if the builder wants this node to be a global if (Context.SetAsGlobal && Context.GlobalName != null) { SetGlobal(Context.GlobalName, new SubQuery($"({Query})"), null); @@ -39,11 +58,13 @@ public override void FinalizeQuery() } } + /// + /// Adds a filter to the update node. + /// + /// The filter predicate to add. public void Filter(LambdaExpression filter) { - if (filter is null) - throw new ArgumentNullException(nameof(filter), "No expression was passed in for a filter node"); - + // translate the filter and append it to our query text. var parsedExpression = ExpressionTranslator.Translate(filter, Builder.QueryVariables, Context, Builder.QueryGlobals); Query.Append($" filter {parsedExpression}"); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs index e4835283..4c95bf1e 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs @@ -6,25 +6,29 @@ namespace EdgeDB.QueryNodes { + /// + /// Represents a 'WITH' node. + /// internal class WithNode : QueryNode { - public bool HasVisited { get; private set; } - + /// public WithNode(NodeBuilder builder) : base(builder) { } + /// public override void Visit() { - HasVisited = true; - + // if no values are provided we can safely stop here. if (Context.Values is null || !Context.Values.Any()) return; List values = new(); + // iterate over every global defined in our context foreach(var global in Context.Values) { var value = global.Value; + // if its a query builder, build it and add it as a sub-query. if (value is IQueryBuilder queryBuilder) { var query = queryBuilder.Build(); @@ -39,6 +43,7 @@ public override void Visit() SetGlobal(queryGlobal.Name, queryGlobal.Value, null); } + // if its a sub query that requires introspection, build it and add it. if(value is SubQuery subQuery && subQuery.RequiresIntrospection) { if (subQuery.RequiresIntrospection && SchemaInfo is null) @@ -46,9 +51,11 @@ public override void Visit() value = subQuery.Build(SchemaInfo!); } + // parse the object and add it to the values. values.Add($"{global.Name} := {QueryUtils.ParseObject(value)}"); } + // join the values seperated by commas Query.Append($"with {string.Join(", ", values)}"); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryObjectManager.cs b/src/EdgeDB.Net.QueryBuilder/QueryObjectManager.cs index 32d667e2..c2b2b169 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryObjectManager.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryObjectManager.cs @@ -8,19 +8,33 @@ namespace EdgeDB { /// - /// The general purpouse of this class is to manage references to objects which - /// have been returned from a query. It's used to check if an object has to be inserted - /// or can be referenced + /// Represents a class that contains safe references to objects returned in queries. /// internal class QueryObjectManager { - private static HashSet _references = new(); + /// + /// A containing the s. + /// + private static readonly HashSet _references = new(); + /// + /// Initializes the object manager, creating a hook to the + /// to get any objects returned from queries. + /// public static void Initialize() { - TypeBuilder.OnObjectCreated += TypeBuilder_OnObjectCreated; + TypeBuilder.OnObjectCreated += OnObjectCreated; } + /// + /// Attempts to get the EdgeDB object id for the given instance. + /// + /// The object instance to get the id from. + /// The out parameter containing the id of the provided object. + /// + /// if the object instance matched one that was + /// returned from a previous query, otherwise . + /// public static bool TryGetObjectId(object? obj, out Guid id) { id = default; @@ -32,17 +46,37 @@ public static bool TryGetObjectId(object? obj, out Guid id) return reference != null; } - private static void TypeBuilder_OnObjectCreated(object obj, Guid id) + /// + /// The callback to add new references to . + /// + /// The object returned from the query. + /// The id of the object. + private static void OnObjectCreated(object obj, Guid id) { var reference = new QueryObjectReference(id, new WeakReference(obj)); _references.Add(reference); } + /// + /// Represents a wrapped containing the reference and the object id. + /// private class QueryObjectReference { + /// + /// The id of the object within the . + /// public readonly Guid ObjectId; + + /// + /// The weak reference to a returned query object. + /// public readonly WeakReference Reference; + /// + /// Constructs a new . + /// + /// The object id of the reference. + /// The weak reference pointing to a returned query object. public QueryObjectReference(Guid objectId, WeakReference reference) { ObjectId = objectId; diff --git a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs index 2e2e94bd..1c32089a 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs @@ -13,11 +13,26 @@ namespace EdgeDB { - internal class QueryUtils + /// + /// A class containing useful utilities for building queries. + /// + internal static class QueryUtils { private const string VARIABLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static readonly Random _rng = new(); + /// + /// 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, [MaybeNullWhen(false)]out Type? innerLinkType) { innerLinkType = null; @@ -28,12 +43,17 @@ public static bool IsLink(Type type, out bool isMultiLink, [MaybeNullWhen(false) { innerLinkType = enumerableType.GenericTypeArguments[0]; isMultiLink = true; - return true; + return IsLink(innerLinkType, out _, out innerLinkType); } return TypeBuilder.IsValidObjectType(type); } + /// + /// Parses a given object into its equivilant edgeql form. + /// + /// The object to parse. + /// The string representation for the given object. internal static string ParseObject(object? obj) { if (obj is null) @@ -61,11 +81,33 @@ internal static string ParseObject(object? obj) }; } + /// + /// Generates a random valid variable name for use in queries. + /// + /// A 12 character long random string. public static string GenerateRandomVariableName() - { - return new string(Enumerable.Repeat(VARIABLE_CHARS, 12).Select(x => x[_rng.Next(x.Length)]).ToArray()); - } - + => new string(Enumerable.Repeat(VARIABLE_CHARS, 12).Select(x => x[_rng.Next(x.Length)]).ToArray()); + + /// + /// 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); @@ -73,6 +115,27 @@ public static async ValueTask> GetPropertiesAsync + /// 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. + /// + /// 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) { if (!schemaInfo.TryGetObjectInfo(type, out var info)) @@ -88,6 +151,14 @@ public static IEnumerable GetProperties(SchemaInfo schemaInfo, Typ }); } + /// + /// 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() @@ -103,25 +174,47 @@ public static Expression GenerateInsertShapeExpression(object? value, Type type) ); } - public static async ValueTask>> GenerateUpdateFactoryAsync(IEdgeDBQueryable edgedb, TType inst, CancellationToken token = default) + /// + /// 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(inst) != ReflectionUtils.GetDefault(x.PropertyType)); + 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(inst), x))) + Expression.Bind(x, Expression.MakeMemberAccess(Expression.Constant(value), x))) ), Expression.Parameter(typeof(TType), "x") ); } - public static async ValueTask>> GenerateUpdateFilterAsync(IEdgeDBQueryable edgedb, TType inst, CancellationToken token = default) + /// + /// 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>> GenerateUpdateFilterAsync(IEdgeDBQueryable edgedb, TType value, CancellationToken token = default) { // try and get object id - if (QueryObjectManager.TryGetObjectId(inst, out var id)) + if (QueryObjectManager.TryGetObjectId(value, out var id)) return (_, ctx) => ctx.UnsafeLocal("id") == id; // get exclusive properties. diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs index 2023204a..44fb2c14 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -12,6 +12,10 @@ namespace EdgeDB { + /// + /// A wrapper for the which expose basic CRUD methods. + /// + /// public sealed class QueryableCollection { /// @@ -20,8 +24,15 @@ public sealed class QueryableCollection public QueryBuilder QueryBuilder => new(); + /// + /// The client used for introspection & execution. + /// private readonly IEdgeDBQueryable _edgedb; + /// + /// Constructs a new . + /// + /// The client to introspect & execute with. internal QueryableCollection(IEdgeDBQueryable edgedb) { _edgedb = edgedb; diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs index 69c7da87..a9ef02e6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs @@ -6,15 +6,37 @@ namespace EdgeDB.Schema.DataTypes { + /// + /// Represents the partial 'schema::ObjectType' type within EdgeDB. + /// [EdgeDBType(ModuleName = "schema")] internal class ObjectType { + /// + /// Gets the cleaned name of the oject type. + /// [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 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 a collection of properties within this objec type. + /// [EdgeDBProperty("pointers")] public Property[]? Properties { get; set; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs index 59955ab5..c4dc04ea 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs @@ -6,7 +6,10 @@ namespace EdgeDB.Schema.DataTypes { - public enum Cardinality + /// + /// Represents the cardinality of a . + /// + internal enum Cardinality { One, AtMostOne, @@ -15,14 +18,45 @@ public enum Cardinality } internal class Property { + /// + /// Gets or sets the "real" cardinality of the property. + /// [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 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 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 read-only. + /// public bool IsReadonly { 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 4a676306..5145869f 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs @@ -9,14 +9,36 @@ namespace EdgeDB.Schema { + /// + /// Represents the schema info containing user-defined types. + /// internal class SchemaInfo { + /// + /// Gets a read-only collection of all user-defined types. + /// public IReadOnlyCollection Types { get; } + + /// + /// Constructs a new with the given types. + /// + /// A read-only collection of user-defined types. public SchemaInfo(IReadOnlyCollection types) { Types = types!; } + /// + /// 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 => x.CleanedName == type.GetEdgeDBTypeName())) != null; } diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs index a529e302..1d1e11a4 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs @@ -8,15 +8,33 @@ namespace EdgeDB.Schema { + /// + /// Represents a class responsible for preforming and caching schema introspection data. + /// internal class SchemaIntrospector { + /// + /// The cache of schema info key'd by the client. + /// private static readonly ConcurrentDictionary _schemas; + /// + /// 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)) @@ -24,8 +42,18 @@ public static ValueTask GetOrCreateSchemaIntrospectionAsync(IEdgeDBQ 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(ctx => new ObjectType { Id = ctx.Include(), @@ -47,6 +75,7 @@ private static async Task IntrospectSchemaAsync(IEdgeDBQueryable edg }) }).Filter((x, ctx) => !ctx.UnsafeLocal("builtin")).ExecuteAsync(edgedb, token); + // 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 108a2cb1..8735fe14 100644 --- a/src/EdgeDB.Net.QueryBuilder/SubQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/SubQuery.cs @@ -7,23 +7,55 @@ namespace EdgeDB { + /// + /// Represents a generic subquery. + /// internal class SubQuery { + /// + /// Gets the query string for this subquery. + /// + /// + /// This property is null when is . + /// public string? Query { get; init; } + + /// + /// Gets whether or not this query requires introspection to generate. + /// public bool RequiresIntrospection { get; init; } + + /// + /// Gets the builder for this subquery. + /// public Func? Builder { get; init; } + /// + /// Constructs a new . + /// + /// The builder callback to build this into a . public SubQuery(Func builder) { RequiresIntrospection = true; Builder = builder; } + /// + /// Constructs a new . + /// + /// The query string of this . public SubQuery(string query) { Query = query; } + /// + /// Builds this using the provided introspection. + /// + /// The introspection info to build this . + /// + /// A representing the built form of this queyr. + /// public SubQuery Build(SchemaInfo info) { return new SubQuery(Builder!(info)); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs index 13acbf7e..1ab15784 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs @@ -7,10 +7,15 @@ namespace EdgeDB.Translators.Expressions { + /// + /// Represents a translator for translating an expression with a binary operator. + /// internal class BinaryExpressionTranslator : ExpressionTranslator { + /// public override string? Translate(BinaryExpression expression, ExpressionContext context) { + // translate the left and right side of the binary operation var left = TranslateExpression(expression.Left, context); var right = TranslateExpression(expression.Right, context); @@ -22,9 +27,11 @@ internal class BinaryExpressionTranslator : ExpressionTranslator + /// Represents a translator for translating an expression with a conditional operator. + /// internal class ConditionalExpressionTranslator : ExpressionTranslator { + /// public override string? Translate(ConditionalExpression expression, ExpressionContext context) { + // translate the condition var condition = TranslateExpression(expression.Test, context); + + // translate both cases var ifTrue = TranslateExpression(expression.IfTrue, context); var ifFalse = TranslateExpression(expression.IfFalse, context); - + // return the edgeql equivalent return $"{ifTrue} if {condition} else {ifFalse}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs index ce1c0e8e..fefb2e8c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs @@ -7,11 +7,19 @@ namespace EdgeDB.Translators.Expressions { + /// + /// Represents a translator for translating an expression with a constant value. + /// internal class ConstantExpressionTranslator : ExpressionTranslator { + /// public override string? Translate(ConstantExpression expression, ExpressionContext context) { - return context.StringWithoutQuotes && expression.Value is string str ? str : QueryUtils.ParseObject(expression.Value); + // return the string form if the context requests its raw string + // form, otherwise parse the constant value. + return context.StringWithoutQuotes && expression.Value is string str + ? str + : QueryUtils.ParseObject(expression.Value); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index e87f4c86..d8b737dd 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -10,76 +10,168 @@ namespace EdgeDB { + /// + /// Represents context used by an . + /// internal class ExpressionContext { + /// + /// Gets the current expression tree. + /// public List ExpressionTree { get; set; } = new(); + /// + /// Gets the calling nodes context. + /// public NodeContext NodeContext { get; } + + /// + /// Gets the root lambda function that is currently being translated. + /// public LambdaExpression RootExpression { get; } + + /// + /// Gets a collection of method parameters within the . + /// public Dictionary Parameters { get; } - private readonly IDictionary _queryObjects; - private readonly List _globals; + + /// + /// 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 HasInitializationOperator { get; set; } + + /// + /// Gets or sets whether or not to include a self reference. + /// Ex: : '.name', : 'name' + /// + /// public bool IncludeSelfReference { get; set; } = true; + + /// + /// Gets whether or not the current expression tree is a free object. + /// public bool IsFreeObject => NodeContext is SelectContext selectContext && selectContext.IsFreeObject; + + /// + /// The collection of query variables. + /// + private readonly IDictionary _queryArguments; + + /// + /// The collection of query globals. + /// + private readonly List _globals; + /// + /// Constructs a new . + /// + /// The calling nodes context. + /// The root lambda expression. + /// The query arguments collection. + /// The query global collection. public ExpressionContext(NodeContext context, LambdaExpression rootExpression, IDictionary queryArguments, List globals) { ExpressionTree.Add(rootExpression); RootExpression = rootExpression; - _queryObjects = queryArguments; + _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(); - _queryObjects[name] = value; + _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) - => _queryObjects[name] = value; + => _queryArguments[name] = value; - public bool TryGetGlobal(object? value, [MaybeNullWhen(false)]out QueryGlobal global) + /// + /// 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 == value); + global = _globals.FirstOrDefault(x => x.Reference == reference); 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) - { - var global = _globals.FirstOrDefault(x => x.Value == value); - if (global != null) - return global.Name; - } + 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 void SetGlobal(string name, object? value, object? reference) { var global = new QueryGlobal(name, value, reference); _globals.Add(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); - exp.ExpressionTree = ExpressionTree.ToList(); // creates a new instance as we dont want to copy ref of this contexts tree. + // creates a new instance as we dont want to copy ref of this contexts tree. + exp.ExpressionTree = ExpressionTree.ToList(); return exp; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs index a6c2ea58..cd0d4fad 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -1,4 +1,5 @@ using EdgeDB.Operators; +using EdgeDB.QueryNodes; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -10,26 +11,59 @@ 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 { + /// + /// Translate the given into the edgeql equivalent. + /// + /// The expression to translate. + /// The context for the translation. + /// The string form of the expression. public abstract string? Translate(TExpression expression, ExpressionContext context); + /// + /// Overrides the default translation method to call the generic one. + /// + /// public override string? Translate(Expression expression, ExpressionContext context) { return Translate((TExpression)expression, context); } } + /// + /// 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(); + + /// + /// An array of all edgeql operators. + /// private static readonly IEdgeQLOperator[] _operators; + + /// + /// A collection of expression types (key) and operators (value). + /// private static readonly Dictionary _expressionOperators; + /// + /// Statically initializes the translator, setting , + /// , and . + /// static ExpressionTranslator() { var types = Assembly.GetExecutingAssembly().DefinedTypes; + // load current translators var translators = types.Where(x => x.BaseType?.Name == "ExpressionTranslator`1"); @@ -45,21 +79,59 @@ static ExpressionTranslator() _expressionOperators = _operators.Where(x => x.Expression is not null).DistinctBy(x => x.Expression).ToDictionary(x => (ExpressionType)x.Expression!, x => x); } + /// + /// Attempts to get a for the given . + /// + /// The expression type to get the operator for. + /// The out parameter containing the operator if found. + /// + /// if an operator was found for the given + /// ; otherwise . + /// protected static bool TryGetExpressionOperator(ExpressionType type, [MaybeNullWhen(false)] out IEdgeQLOperator edgeqlOperator) => _expressionOperators.TryGetValue(type, out edgeqlOperator); - + /// + /// Translate the given expression into the edgeql equivalent. + /// + /// The expression to translate. + /// The context for the translation. + /// The string form of the expression. public abstract string? Translate(Expression expression, ExpressionContext context); + /// + /// Translates a lambda function into the edgeql equivalent + /// + /// The type of the delegate that the expression represents. + /// The expression to translate. + /// The string form of the expression. public static string Translate(Expression expression) => Translate(expression); + /// + /// 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 string form of the expression. public static string Translate(LambdaExpression expression, IDictionary queryArguments, NodeContext nodeContext, List globals) { var context = new ExpressionContext(nodeContext, expression, queryArguments, globals); return TranslateExpression(expression.Body, context); } + /// + /// Translates a sub expression into its edgeql equivalent. + /// + /// The expression to translate. + /// The current context of the calling translator. + /// The string form of the expression. + /// No translator was found for the given expression. protected static string TranslateExpression(Expression expression, ExpressionContext context) { var expType = expression.GetType(); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index 898eef0e..16301325 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -8,11 +8,17 @@ namespace EdgeDB.Translators.Expressions { + /// + /// Represents a translator for translating an expression accessing a field or property. + /// internal class MemberExpressionTranslator : ExpressionTranslator { + /// public override string? Translate(MemberExpression expression, ExpressionContext context) { - if(expression.Expression is ConstantExpression constant) + // if the inner expression is a constant value we can get, assume + // were in a set-like context and add it as a variable. + if (expression.Expression is ConstantExpression constant) { object? value = expression.Member.GetMemberValue(constant.Value); @@ -20,10 +26,18 @@ internal class MemberExpressionTranslator : ExpressionTranslator${varName}"; } - + + // assume were in a access-like context and reference it in edgeql. return ParseMemberExpression(expression, expression.Expression is not ParameterExpression, context.IncludeSelfReference); } + /// + /// Parses a given member expression into a member access list. + /// + /// The expression to parse. + /// Whether or not to include the referenced parameter name. + /// Whether or not to include a self reference, ex: '.'. + /// private static string ParseMemberExpression(MemberExpression expression, bool includeParameter = true, bool includeSelfReference = true) { List tree = new() diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs index 63e9453a..d4dcbfe6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs @@ -10,8 +10,13 @@ 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 string? Translate(MemberInitExpression expression, ExpressionContext context) { List initializations = new(); @@ -20,16 +25,20 @@ internal class MemberInitExpressionTranslator : ExpressionTranslator + /// Represents a translator for translating an expression with a method + /// call to either static or an instance method. + /// internal class MethodCallExpressionTranslator : ExpressionTranslator { public override string? Translate(MethodCallExpression expression, ExpressionContext context) { - // special case for local + // if our method is within the query context class if (expression.Method.DeclaringType == typeof(QueryContext)) { switch (expression.Method.Name) { case nameof(QueryContext.Local): { - // check arg scope + // validate the type context, property should exist within the type. var rawArg = TranslateExpression(expression.Arguments[0], context.Enter(x => x.StringWithoutQuotes = true)); var rawPath = rawArg.Split('.'); string[] parsedPath = new string[rawPath.Length]; + // build a path if the property is nested for (int i = 0; i != rawPath.Length; i++) { var prop = (MemberInfo?)context.LocalScope?.GetProperty(rawPath[i]) ?? @@ -42,11 +47,12 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator x.StringWithoutQuotes = true))}"; } case nameof(QueryContext.Include): { - // do nothing here + // return nothing for scalar includes return null; } case nameof(QueryContext.IncludeLink) or nameof(QueryContext.IncludeMultiLink): @@ -58,12 +64,19 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator x.StringWithoutQuotes = true)); } case nameof(QueryContext.BackLink): { + // depending on the backlink method called, we should set some flags: + // whether or not the called function is using the string form or the lambda form var isRawPropertyName = expression.Arguments[0].Type == typeof(string); + + // whether or not a shape argument was supplied var hasShape = !isRawPropertyName && expression.Arguments.Count > 1; + + // translate the backlink property accessor var property = TranslateExpression(expression.Arguments[0], isRawPropertyName ? context.Enter(x => x.StringWithoutQuotes = true) @@ -71,18 +84,23 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator true, _ => false }; + // build the operator return edgeqlOperator.Build(argsArray); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs index e26cec49..014201d5 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs @@ -7,10 +7,16 @@ 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 { + /// public override string? Translate(NewArrayExpression expression, ExpressionContext context) { + // return out a edgeql set with each element in the dotnet array translated return $"{{ {string.Join(", ", expression.Expressions.Select(x => TranslateExpression(x, context)))} }}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs index b04658ad..1cfb7aa8 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs @@ -9,47 +9,40 @@ namespace EdgeDB.Translators.Expressions { + /// + /// Represents a translator for translating an expression with a constructor call. + /// internal class NewExpressionTranslator : ExpressionTranslator { + /// public override string? Translate(NewExpression expression, ExpressionContext context) { string[] shape = new string[expression.Arguments.Count]; + // iterate over each argument to the constructor for(int i = 0; i != expression.Arguments.Count; i++) { + // pull the member and argument out of the expression & get the edgedb name of the member var member = expression.Members![i]; var arg = expression.Arguments[i]; var edgedbName = member.GetEdgeDBPropertyName(); + // special fallthru for include if(arg is MethodCallExpression mcex && mcex.Method.DeclaringType == typeof(QueryContext) && mcex.Method.Name == "Include") { shape[i] = edgedbName; continue; } + + // translate the value and determine if were setting a value or referencing a value. + string? value = TranslateExpression(arg, context.Enter(x => x.LocalScope = expression.Type)); ; + bool isSetter = context.NodeContext.CurrentType.GetProperty(member.Name) == null || arg is MethodCallExpression; - var type = member switch - { - PropertyInfo property => property.PropertyType, - FieldInfo field => field.FieldType, - _ => throw new NotSupportedException($"Cannot use {member} in anonomous shape selects") - }; - - string? value = null; - bool isSetter = true; - // local reference, verify in the anon obj - if (arg is MethodCallExpression mcall) - { - value = TranslateExpression(mcall, context.Enter(x => x.LocalScope = expression.Type)); - } - else - { - isSetter = context.NodeContext.CurrentType.GetProperty(member.Name) == null; - value = TranslateExpression(expression.Arguments[i], context.Enter(x => x.LocalScope = expression.Type)); - } - + // add it to our shape shape[i] = $"{edgedbName}{(isSetter || context.IsFreeObject ? " :=" : "")} {value}"; } + // return our shape joined by commas return string.Join(", ", shape); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs index e6e2d934..cabba0f3 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs @@ -7,14 +7,22 @@ namespace EdgeDB.Translators.Expressions { + /// + /// Represents a translator for translating an expression with a unary operator. + /// internal class UnaryExpressionTranslator : ExpressionTranslator { + /// public override string? Translate(UnaryExpression expression, ExpressionContext context) { 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: return TranslateExpression(lambda.Body, context.Enter(x => x.StringWithoutQuotes = false)); + + // convert is a type change, so we translate the dotnet form '(type)value' to 'value' case ExpressionType.Convert: { var value = TranslateExpression(expression.Operand, context); @@ -35,6 +43,8 @@ internal class UnaryExpressionTranslator : ExpressionTranslator return $"<{type}>{value}"; } + // default case attempts to get an IEdgeQLOperator for the given + // node type, and uses that to translate the expression. default: if (!TryGetExpressionOperator(expression.NodeType, out var op)) throw new NotSupportedException($"Failed to find operator for node type {expression.NodeType}"); From 1db4807fb8addb324658b016b1a9f87e923e955c Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 12 Jul 2022 08:26:33 -0300 Subject: [PATCH 20/70] add readme to qb project --- src/EdgeDB.Net.QueryBuilder/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/EdgeDB.Net.QueryBuilder/README.md diff --git a/src/EdgeDB.Net.QueryBuilder/README.md b/src/EdgeDB.Net.QueryBuilder/README.md new file mode 100644 index 00000000..06a8b495 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/README.md @@ -0,0 +1,6 @@ +## 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 From b4d47c63df150894fd6257296a632dd3939da6b8 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 13 Jul 2022 21:59:30 -0300 Subject: [PATCH 21/70] Fix multi link null type --- src/EdgeDB.Net.QueryBuilder/QueryUtils.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs index 1c32089a..0f6b980f 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs @@ -43,7 +43,9 @@ public static bool IsLink(Type type, out bool isMultiLink, [MaybeNullWhen(false) { innerLinkType = enumerableType.GenericTypeArguments[0]; isMultiLink = true; - return IsLink(innerLinkType, out _, out innerLinkType); + var result = IsLink(innerLinkType, out _, out var linkType); + innerLinkType ??= linkType; + return result; } return TypeBuilder.IsValidObjectType(type); From cc54d0f57c1951f428172d1e7cf13214788ec9b5 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Thu, 14 Jul 2022 06:52:22 -0300 Subject: [PATCH 22/70] FOR operator support --- .../Examples/QueryBuilder.cs | 44 ++++ .../Extensions/QueryBuilderExtensions.cs | 31 +++ .../Extensions/TypeExtensions.cs | 3 + src/EdgeDB.Net.QueryBuilder/JsonVariable.cs | 137 +++++++++++ src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 11 + .../QueryNodes/Contexts/ForContext.cs | 31 +++ .../QueryNodes/Contexts/NodeContext.cs | 3 + .../QueryNodes/ForNode.cs | 116 +++++++++ .../QueryNodes/InsertNode.cs | 220 +++++++++++++++++- 9 files changed, 594 insertions(+), 2 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/Extensions/QueryBuilderExtensions.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/JsonVariable.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/ForContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 55140165..31a1c9c9 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -38,6 +38,50 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { + var set = new List() + { + new LinkPerson + { + Email = "email1@example.com", + Name = "test1", + }, + new LinkPerson + { + Email = "email2@example.com", + Name = "test2", + }, + new LinkPerson + { + Email = "email3@example.com", + Name = "test3", + BestFriend = new LinkPerson + { + Email = "email4@example.com", + Name = "test4" + } + }, + new LinkPerson + { + Email = "email5@example.com", + Name = "test5", + BestFriend = new LinkPerson + { + Email = "email6@example.com", + Name = "test6", + BestFriend = new LinkPerson + { + Email = "email7@example.com", + Name = "test7", + } + } + } + }; + + var test = (await new QueryBuilder() + .For(set, x => + QueryBuilder.Insert(x, false) + ).BuildAsync(client)).Prettify(); + // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/QueryBuilderExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/QueryBuilderExtensions.cs new file mode 100644 index 00000000..9b2f9ccf --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/QueryBuilderExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + internal static class QueryBuilderExtensions + { + public static BuiltQuery BuildWithoutAutogeneratedNodes(this IQueryBuilder builder, NodeBuilder nodeBuilder) + { + // remove addon & autogen nodes. + var userNodes = builder.Nodes.Where(x => !builder.Nodes.Any(y => y.SubNodes.Contains(x)) || !x.IsAutoGenerated); + + // TODO: better checks for this, future should add a callback to add the + // node with its context so any parent builder can change contexts for nodes + foreach (var node in userNodes) + node.Context.SetAsGlobal = false; + + foreach (var variable in builder.Variables) + { + nodeBuilder.QueryVariables[variable.Key] = variable.Value; + } + + var newBuilder = new QueryBuilder(userNodes.ToList(), builder.Globals.ToList(), builder.Variables.ToDictionary(x => x.Key, x => x.Value)); + + return newBuilder.BuildWithGlobals(); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs index 24e0c241..16c56fda 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs @@ -18,6 +18,9 @@ public static bool IsAnonymousType(this Type type) type.FullName!.Contains("AnonymousType"); } + public static IEnumerable GetEdgeDBTargetProperties(this Type type) + => type.GetProperties().Where(x => x.GetCustomAttribute() == null); + public static string GetEdgeDBTypeName(this Type type) { var attr = type.GetCustomAttribute(); diff --git a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs new file mode 100644 index 00000000..258c0632 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs @@ -0,0 +1,137 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + /// + /// Represents an abstracted form of . + /// + internal 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 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); + } + + /// + /// Represents a json value used within queries. + /// + /// The inner type that the json value represents. + public class JsonVariable : IJsonVariable + { + /// + /// 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); + + /// + /// The root containing all the json objects. + /// + private readonly JArray _array; + + /// + /// Constructs a new . + /// + /// The name of the variable. + /// The containing all the json objects. + internal JsonVariable(string name, JArray array) + { + _array = array; + Name = name; + } + + /// . + private IEnumerable GetObjectsAtDepth(int targetDepth) + { + IEnumerable GetObjects(JObject obj, int currentDepth) + { + if (targetDepth == currentDepth) + return new JObject[] { obj }; + + if (targetDepth > currentDepth) + return obj.Properties().Where(x => x.Value is JObject).SelectMany(x => GetObjects((JObject)x.Value, currentDepth + 1)); + + return Array.Empty(); + } + + return _array.Where(x => x is JObject).SelectMany(x => GetObjects((JObject)x, 0)); + } + + /// + private int CalculateDepth() + { + return _array.Max(x => + { + if (x is JObject obj) + return CalculateNodeDepth(obj, 0); + 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) + { + return node.Properties().Max(x => + { + if (x.Value is not JObject jObject) + return depth; + + return CalculateNodeDepth(jObject, depth + 1); + }); + } + + string IJsonVariable.Name => Name; + Type IJsonVariable.InnerType => typeof(T); + IEnumerable IJsonVariable.GetObjectsAtDepth(int targetDepth) => GetObjectsAtDepth(targetDepth); + int IJsonVariable.Depth => CalculateDepth(); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index ece5c0b2..55989b82 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -250,6 +250,17 @@ internal BuiltQuery BuildWithGlobals() => InternalBuild(false); #region Root nodes + public QueryBuilder For(IEnumerable collection, Expression, IQueryBuilder>> iterator) + { + AddNode(new ForContext(typeof(TType)) + { + Expression = iterator, + Set = collection + }); + + return this; + } + /// public QueryBuilder With(string name, object? value) { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/ForContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/ForContext.cs new file mode 100644 index 00000000..2356c307 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/ForContext.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + /// + /// Represents context for a . + /// + internal class ForContext : NodeContext + { + /// + /// 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; } + + /// + public ForContext(Type currentType) : base(currentType) + { + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs index f558858d..d718cbaa 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs @@ -28,6 +28,9 @@ internal abstract class NodeContext /// public Type CurrentType { get; init; } + public bool IsJsonVariable + => ReflectionUtils.IsInstanceOfGenericType(typeof(JsonVariable<>), CurrentType); + /// /// Constructs a new . /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs new file mode 100644 index 00000000..d92ec777 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs @@ -0,0 +1,116 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + /// + /// Represents a 'FOR' node. + /// + internal class ForNode : QueryNode + { + /// + public ForNode(NodeBuilder builder) : base(builder) + { + } + + /// + /// Parsed the given contextual expression into an iterator. + /// + /// The name of the root iterator. + /// The json used for iteration. + /// + /// A edgeql iterator for a 'FOR' statement; or + /// if the iterator requires introspection to build. + /// + /// + /// A type cannot be used as a parameter to a 'FOR' expression + /// + private string? ParseExpression(string name, string json) + { + // check if we're returning a query builder + if (Context.Expression!.ReturnType == typeof(IQueryBuilder)) + { + // 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 + { + _ when x.Type == typeof(QueryContext) => new QueryContext(), + _ when ReflectionUtils.IsInstanceOfGenericType(typeof(JsonVariable<>), x.Type) + => Activator.CreateInstance(typeof(JsonVariable<>).MakeGenericType(Context.CurrentType), name, 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)!; + + // copy the globals & variables to the current builder + foreach (var global in builder.Globals) + SetGlobal(global.Name, global.Value, global.Reference); + + foreach (var variable in builder.Variables) + SetVariable(variable.Key, variable.Value); + + // add all nodes as sub nodes to this node + SubNodes.AddRange(builder.Nodes); + + // return nothing indicating we need to do introspection + return null; + } + else + return ExpressionTranslator.Translate(Context.Expression!, Builder.QueryVariables, Context, Builder.QueryGlobals); + } + + /// + public override void Visit() + { + // pull the name of the value that the user has specified + var name = Context.Expression!.Parameters.First(x => x.Type != typeof(QueryContext)).Name!; + + // serialize the collection & generate a name for the json variable + var setJson = JsonConvert.SerializeObject(Context.Set); + var jsonName = QueryUtils.GenerateRandomVariableName(); + + // set the json variable + SetVariable(jsonName, setJson); + + // append the 'FOR' statement + Query.Append($"for {name} in ${jsonName} union "); + + // parse the iterator expression + var parsed = ParseExpression(name, setJson); + + // if it's not null or empty, append the union statement's content + if (!string.IsNullOrEmpty(parsed)) + Query.Append($"({parsed})"); + else + RequiresIntrospection = true; // else tell the query builder that this node needs introspection + } + + /// + public override void FinalizeQuery() + { + // finalize and build our sub nodes + var iterator = SubNodes.Select(x => + { + x.SchemaInfo = SchemaInfo; + x.FinalizeQuery(); + + // we don't need to copy variables or nodes here since we did that in the parse step + return x.Build().Query; + }).Aggregate((x, y) => $"{x} {y}"); + + // append union statement's content + Query.Append($"({iterator})"); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 5bc4ac58..53d8eb2d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -1,4 +1,6 @@ using EdgeDB.Serializer; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections; using System.Collections.Generic; @@ -6,6 +8,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace EdgeDB.QueryNodes @@ -15,6 +18,33 @@ namespace EdgeDB.QueryNodes /// internal class InsertNode : QueryNode { + /// + /// Represents a node within a depth map. + /// + private readonly struct DepthNode + { + /// + /// The type of the node, this type represents the nodes value. + /// + public readonly Type Type; + + /// + /// The value of the node. + /// + public readonly JObject Node; + + /// + /// Constructs a new . + /// + /// The type of the node. + /// The node containing the value. + public DepthNode(Type type, JObject node) + { + Type = type; + Node = node; + } + } + /// /// Whether or not to autogenerate the unless conflict clause. /// @@ -30,6 +60,11 @@ internal class InsertNode : QueryNode /// a nested query can be preformed. /// private readonly List _subQueryMap = new(); + + /// + /// The regex used to resolve json paths. + /// + private readonly Regex _pathResolverRegex = new(@"\[\d+?](?>\.(.*?)$|$)"); /// public InsertNode(NodeBuilder builder) : base(builder) @@ -37,6 +72,187 @@ public InsertNode(NodeBuilder builder) : base(builder) _elseStatement = new(); } + /// + /// 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. + /// + private 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; + + // 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 = result.GetMember(pathSections[i]).First(x => x is PropertyInfo or FieldInfo)!.GetMemberType(); + + // return the final type. + return result; + } + + /// + /// Builds a json-based insert shape + /// + /// + /// The insert shape for a json-based value. + /// + private string BuildJsonShape() + { + var mappingName = QueryUtils.GenerateRandomVariableName(); + var jsonValue = (IJsonVariable)Context.Value!; + var depth = jsonValue.Depth; + + // create a depth map that contains each nested level of types to be inserted + List[] depthMap = new List[depth + 1]; + + // iterate over the number of child types we have + for(int i = 0; i != depth; i++) + { + // get the elements at the current depth + var elements = jsonValue.GetObjectsAtDepth(i); + + var nodes = new List(); + + foreach(var element in elements) + { + // create a new json object we will use to mutate to our newer form + JObject mutableElement = new(); + + // enumerate over each property in the populated object and copy it to our mutable one + foreach(var prop in element.Properties()) + { + if (prop.Value is JObject node) + { + // if its a sub-object, add it to the next depth level + var mapIndex = i + 1; + + // resolve the objects link type from its path + var type = ResolveTypeFromPath(jsonValue.InnerType, prop.Path); + + if (depthMap[mapIndex] is null) + depthMap[mapIndex] = new List() + { new(type, node) }; + else + depthMap[mapIndex].Add(new(type, node)); + + // populate the mutable one with the location of the nested object + mutableElement[prop.Name] = new JObject() + { + new JProperty($"{mappingName}_depth_index", depthMap[mapIndex].Count - 1), + new JProperty($"{mappingName}_depth_map", mapIndex) + }; + } + else + mutableElement.Add(prop); // add the property if its scalar + } + + // add the node to our node collection + nodes.Add(new(ResolveTypeFromPath(jsonValue.InnerType, element.Path), mutableElement)); + } + + // add the nodes collection to our depth map + depthMap[i] = nodes; + } + + // generate the global maps + for(int i = depthMap.Length - 1; i != 0; i--) + { + var map = depthMap[i]; + var node = map[0]; + var iterationName = QueryUtils.GenerateRandomVariableName(); + var variableName = QueryUtils.GenerateRandomVariableName(); + var isLast = depthMap.Length == i + 1; + + // IMPORTANT: since we're using 'i' within the callback, we must + // store a local here so we dont end up calling the last iterations 'i' value + var indexCopy = i; + + // generate a introspection-dependant sub query for the insert or select + var query = new SubQuery((info) => + { + var allProps = QueryUtils.GetProperties(info, node.Type); + var typeName = node.Type.GetEdgeDBTypeName(); + + // define the insert shape + var shape = allProps.Select(x => + { + var propName = x.GetEdgeDBPropertyName(); + + // if its a link, add a ternary statement for pulling the value out of a sub-map + if (QueryUtils.IsLink(x.PropertyType, out _, out _)) + { + // if we're in the last iteration of the depth map, we know for certian there + // are no sub types within the current context, we can safely set the link to + // an empty set + if (isLast) + return $"{propName} := {{}}"; + + return $"{propName} := {mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{propName}', '{mappingName}_depth_index')] if json_typeof(json_get({iterationName}, '{propName}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; + } + + // if its a scalar type, use json_get to pull the value and cast it to our property + // type + var edgeqlType = PacketSerializer.GetEdgeQLType(x.PropertyType); + + return $"{propName} := <{edgeqlType}>json_get({iterationName}, '{propName}')"; + + }); + + // generate the 'insert .. unless conflict .. else select' query + var exclusiveProps = QueryUtils.GetProperties(info, node.Type, true); + var exclusiveCondition = exclusiveProps.Any() ? + $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {typeName})" + : string.Empty; + + var insertStatement = $"(insert {typeName} {{ {string.Join(", ", shape)} }}{exclusiveCondition})"; + + // add the iteration and turn it into an array so we can use the index operand + // during our query stage + return $"array_agg((for {iterationName} in json_array_unpack(${variableName}) union {insertStatement}))"; + }); + + // tell the builder that this query requires introspection + RequiresIntrospection = true; + + // serialize this depths values and set the variable & global for the sub-query + var iterationJson = JsonConvert.SerializeObject(map.Select(x => x.Node)); + + SetVariable(variableName, iterationJson); + SetGlobal($"{mappingName}_d{i}", query, null); + } + + // create the base insert shape + var shape = jsonValue.InnerType.GetEdgeDBTargetProperties().Select(x => + { + var propName = x.GetEdgeDBPropertyName(); + + // if its a link, add a ternary statement for pulling the value out of a sub-map + if (QueryUtils.IsLink(x.PropertyType, out _, out _)) + { + return $"{propName} := {mappingName}_d1[json_get({jsonValue.Name}, '{propName}', '{mappingName}_depth_index')] if json_typeof(json_get({jsonValue.Name}, '{propName}')) != 'null' else <{jsonValue.InnerType.GetEdgeDBTypeName()}>{{}}"; + } + + // if its a scalar type, use json_get to pull the value and cast it to our property + // type + var edgeqlType = PacketSerializer.GetEdgeQLType(x.PropertyType); + + return $"{propName} := <{edgeqlType}>json_get({jsonValue.Name}, '{propName}')"; + }); + + // return out our insert shape + return $"{{ {string.Join(", ", shape)} }}"; + } + /// /// Builds an insert shape based on the given type and value. /// @@ -188,10 +404,10 @@ public override void Visit() _subQueryMap.Add(Context.CurrentType); // build the insert shape - var shape = BuildInsertShape(); + var shape = Context.IsJsonVariable ? BuildJsonShape() : BuildInsertShape(); // append it to our query - Query.Append($"insert {Context.CurrentType.GetEdgeDBTypeName()} {shape}"); + Query.Append($"insert {(Context.IsJsonVariable ? ((IJsonVariable)Context.Value!).InnerType : Context.CurrentType).GetEdgeDBTypeName()} {shape}"); } /// From a8db46fd1e6f8a875657667a0acdc04790e23f4e Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Thu, 14 Jul 2022 09:43:55 -0300 Subject: [PATCH 23/70] Fix bugs with json depth mapping and sub-iterated-inserts --- .../Examples/QueryBuilder.cs | 4 ++-- src/EdgeDB.Net.QueryBuilder/JsonVariable.cs | 11 +++++++++- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 2 +- .../QueryNodes/ForNode.cs | 16 +++++++++----- .../QueryNodes/InsertNode.cs | 22 +++++++++++-------- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 31a1c9c9..aeb72a8e 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -78,9 +78,9 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) }; var test = (await new QueryBuilder() - .For(set, x => + . For(set, x => QueryBuilder.Insert(x, false) - ).BuildAsync(client)).Prettify(); + ).BuildAsync(client)); // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs index 258c0632..6dbbafc4 100644 --- a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs +++ b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs @@ -22,6 +22,11 @@ internal interface IJsonVariable /// string Name { get; } + /// + /// Gets the variable name representing this json value. + /// + string VariableName { get; } + /// /// Gets the inner type of the json value. /// @@ -66,6 +71,8 @@ public T Value internal bool IsObjectArray => _array.All(x => x is JObject); + internal string VariableName { get; } + /// /// The root containing all the json objects. /// @@ -76,9 +83,10 @@ internal bool IsObjectArray /// /// The name of the variable. /// The containing all the json objects. - internal JsonVariable(string name, JArray array) + internal JsonVariable(string name, string varName, JArray array) { _array = array; + VariableName = varName; Name = name; } @@ -133,5 +141,6 @@ private int CalculateNodeDepth(JObject node, int depth = 0) Type IJsonVariable.InnerType => typeof(T); IEnumerable IJsonVariable.GetObjectsAtDepth(int targetDepth) => GetObjectsAtDepth(targetDepth); int IJsonVariable.Depth => CalculateDepth(); + string IJsonVariable.VariableName => VariableName; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 55989b82..5a629b8d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -250,7 +250,7 @@ internal BuiltQuery BuildWithGlobals() => InternalBuild(false); #region Root nodes - public QueryBuilder For(IEnumerable collection, Expression, IQueryBuilder>> iterator) + public IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator) { AddNode(new ForContext(typeof(TType)) { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs index d92ec777..138ac329 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs @@ -1,8 +1,10 @@ -using Newtonsoft.Json; +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; @@ -30,7 +32,7 @@ public ForNode(NodeBuilder builder) : base(builder) /// /// A type cannot be used as a parameter to a 'FOR' expression /// - private string? ParseExpression(string name, string json) + private string? ParseExpression(string name, string varName, string json) { // check if we're returning a query builder if (Context.Expression!.ReturnType == typeof(IQueryBuilder)) @@ -45,7 +47,9 @@ public ForNode(NodeBuilder builder) : base(builder) { _ when x.Type == typeof(QueryContext) => new QueryContext(), _ when ReflectionUtils.IsInstanceOfGenericType(typeof(JsonVariable<>), x.Type) - => Activator.CreateInstance(typeof(JsonVariable<>).MakeGenericType(Context.CurrentType), name, jArray), + => typeof(JsonVariable<>).MakeGenericType(Context.CurrentType) + .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new Type[] { typeof(string), typeof(string), typeof(JArray)})! + .Invoke(new object?[] { name, varName, jArray })!, _ => throw new ArgumentException($"Cannot use {x.Type} as a parameter to a 'FOR' expression") }; }).ToArray(); @@ -81,13 +85,13 @@ public override void Visit() var jsonName = QueryUtils.GenerateRandomVariableName(); // set the json variable - SetVariable(jsonName, setJson); + SetVariable(jsonName, new Json(setJson)); // append the 'FOR' statement - Query.Append($"for {name} in ${jsonName} union "); + Query.Append($"for {name} in json_array_unpack(${jsonName}) union "); // parse the iterator expression - var parsed = ParseExpression(name, setJson); + var parsed = ParseExpression(name, jsonName, setJson); // if it's not null or empty, append the union statement's content if (!string.IsNullOrEmpty(parsed)) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 53d8eb2d..b6269a96 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -1,4 +1,5 @@ -using EdgeDB.Serializer; +using EdgeDB.DataTypes; +using EdgeDB.Serializer; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -186,7 +187,7 @@ private string BuildJsonShape() // define the insert shape var shape = allProps.Select(x => { - var propName = x.GetEdgeDBPropertyName(); + var edgedbName = x.GetEdgeDBPropertyName(); // if its a link, add a ternary statement for pulling the value out of a sub-map if (QueryUtils.IsLink(x.PropertyType, out _, out _)) @@ -195,16 +196,16 @@ private string BuildJsonShape() // are no sub types within the current context, we can safely set the link to // an empty set if (isLast) - return $"{propName} := {{}}"; + return $"{edgedbName} := {{}}"; - return $"{propName} := {mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{propName}', '{mappingName}_depth_index')] if json_typeof(json_get({iterationName}, '{propName}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; + return $"{edgedbName} := {mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{x.Name}', '{mappingName}_depth_index')] if json_typeof(json_get({iterationName}, '{x.Name}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; } // if its a scalar type, use json_get to pull the value and cast it to our property // type var edgeqlType = PacketSerializer.GetEdgeQLType(x.PropertyType); - return $"{propName} := <{edgeqlType}>json_get({iterationName}, '{propName}')"; + return $"{edgedbName} := <{edgeqlType}>json_get({iterationName}, '{x.Name}')"; }); @@ -227,26 +228,29 @@ private string BuildJsonShape() // serialize this depths values and set the variable & global for the sub-query var iterationJson = JsonConvert.SerializeObject(map.Select(x => x.Node)); - SetVariable(variableName, iterationJson); + SetVariable(variableName, new Json(iterationJson)); SetGlobal($"{mappingName}_d{i}", query, null); } + // replace the json variables content with the root depth map + SetVariable(jsonValue.VariableName, new Json(JsonConvert.SerializeObject(depthMap[0].Select(x => x.Node)))); + // create the base insert shape var shape = jsonValue.InnerType.GetEdgeDBTargetProperties().Select(x => { - var propName = x.GetEdgeDBPropertyName(); + var edgedbName = x.GetEdgeDBPropertyName(); // if its a link, add a ternary statement for pulling the value out of a sub-map if (QueryUtils.IsLink(x.PropertyType, out _, out _)) { - return $"{propName} := {mappingName}_d1[json_get({jsonValue.Name}, '{propName}', '{mappingName}_depth_index')] if json_typeof(json_get({jsonValue.Name}, '{propName}')) != 'null' else <{jsonValue.InnerType.GetEdgeDBTypeName()}>{{}}"; + return $"{edgedbName} := {mappingName}_d1[json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_index')] if json_typeof(json_get({jsonValue.Name}, '{x.Name}')) != 'null' else <{jsonValue.InnerType.GetEdgeDBTypeName()}>{{}}"; } // if its a scalar type, use json_get to pull the value and cast it to our property // type var edgeqlType = PacketSerializer.GetEdgeQLType(x.PropertyType); - return $"{propName} := <{edgeqlType}>json_get({jsonValue.Name}, '{propName}')"; + return $"{edgedbName} := <{edgeqlType}>json_get({jsonValue.Name}, '{x.Name}')"; }); // return out our insert shape From ad4118a468e86beaa9875407c6ca96427021786b Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sat, 16 Jul 2022 11:11:00 -0300 Subject: [PATCH 24/70] "Working" for iterator, see https://github.com/edgedb/edgedb/issues/4090 --- .../Examples/QueryBuilder.cs | 70 +++++++------------ src/EdgeDB.Net.QueryBuilder/JsonVariable.cs | 13 ++-- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 4 ++ .../QueryNodes/DeleteNode.cs | 2 +- .../QueryNodes/InsertNode.cs | 63 ++++++++++++----- .../QueryNodes/QueryNode.cs | 8 +++ .../QueryNodes/SelectNode.cs | 4 +- .../QueryNodes/UpdateNode.cs | 2 +- 8 files changed, 96 insertions(+), 70 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index aeb72a8e..281af843 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -38,50 +38,6 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var set = new List() - { - new LinkPerson - { - Email = "email1@example.com", - Name = "test1", - }, - new LinkPerson - { - Email = "email2@example.com", - Name = "test2", - }, - new LinkPerson - { - Email = "email3@example.com", - Name = "test3", - BestFriend = new LinkPerson - { - Email = "email4@example.com", - Name = "test4" - } - }, - new LinkPerson - { - Email = "email5@example.com", - Name = "test5", - BestFriend = new LinkPerson - { - Email = "email6@example.com", - Name = "test6", - BestFriend = new LinkPerson - { - Email = "email7@example.com", - Name = "test7", - } - } - } - }; - - var test = (await new QueryBuilder() - . For(set, x => - QueryBuilder.Insert(x, false) - ).BuildAsync(client)); - // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); @@ -191,6 +147,30 @@ . For(set, x => .BuildAsync(client)) .Prettify(); + // Bulk inserts + var data = new LinkPerson[] + { + new LinkPerson + { + Email = "test1@mail.com", + Name = "test1", + }, + new LinkPerson + { + Email = "test2@mail.com", + Name = "test2", + BestFriend = new LinkPerson + { + Email = "test3@mail.com", + Name = "test3", + } + } + }; + + query = (await QueryBuilder.For(data, + x => QueryBuilder.Insert(x, false) + ).BuildAsync(client)).Prettify(); + // Else statements (upsert demo) query = (await QueryBuilder .Insert(person) @@ -198,7 +178,7 @@ . For(set, x => .Else(q => q.Update(old => new LinkPerson { - Name = EdgeQL.ToUpper(old.Name) + Name = EdgeQL.ToUpper(old!.Name) }) ) .BuildAsync(client)) diff --git a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs index 6dbbafc4..13c01fa6 100644 --- a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs +++ b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs @@ -130,10 +130,15 @@ private int CalculateNodeDepth(JObject node, int depth = 0) { return node.Properties().Max(x => { - if (x.Value is not JObject jObject) - return depth; - - return CalculateNodeDepth(jObject, depth + 1); + switch(x.Value) + { + case JObject jObject: + return CalculateNodeDepth(jObject, depth + 1); + case JArray jArray: + return jArray.Max(x => x is JObject subNode ? CalculateNodeDepth(subNode, depth + 1) : depth); + default: + return depth; + } }); } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 5a629b8d..ecb6774a 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -17,6 +17,10 @@ namespace EdgeDB /// public static class QueryBuilder { + public static IMultiCardinalityExecutable For(IEnumerable collection, + Expression, IQueryBuilder>> iterator) + => new QueryBuilder().For(collection, iterator); + /// public static ISelectQuery Select() => new QueryBuilder().Select(); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs index 63ec62f4..938dd949 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs @@ -19,7 +19,7 @@ public DeleteNode(NodeBuilder builder) : base(builder) /// public override void Visit() { - Query.Append($"delete {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()}"); + Query.Append($"delete {Context.SelectName ?? OperatingType.GetEdgeDBTypeName()}"); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index b6269a96..e9b42155 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -97,6 +97,9 @@ private Type ResolveTypeFromPath(Type rootType, string path) for (int i = 0; i != pathSections.Length; i++) result = result.GetMember(pathSections[i]).First(x => x is PropertyInfo or FieldInfo)!.GetMemberType(); + if (QueryUtils.IsLink(result, out var isMultiLink, out var innerType) && isMultiLink) + return innerType!; + // return the final type. return result; } @@ -132,7 +135,7 @@ private string BuildJsonShape() // enumerate over each property in the populated object and copy it to our mutable one foreach(var prop in element.Properties()) { - if (prop.Value is JObject node) + if (prop.Value is JObject jObject) { // if its a sub-object, add it to the next depth level var mapIndex = i + 1; @@ -142,15 +145,34 @@ private string BuildJsonShape() if (depthMap[mapIndex] is null) depthMap[mapIndex] = new List() - { new(type, node) }; + { new(type, jObject) }; else - depthMap[mapIndex].Add(new(type, node)); + depthMap[mapIndex].Add(new(type, jObject)); // populate the mutable one with the location of the nested object mutableElement[prop.Name] = new JObject() { new JProperty($"{mappingName}_depth_index", depthMap[mapIndex].Count - 1), - new JProperty($"{mappingName}_depth_map", mapIndex) + }; + } + 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 = i + 1; + + // resolve the objects link type from its path + var type = ResolveTypeFromPath(jsonValue.InnerType, prop.Path); + + if (depthMap[mapIndex] is null) + depthMap[mapIndex] = new List(jArray.Select(x => new DepthNode(type, (JObject)x))); + else + depthMap[mapIndex].AddRange(jArray.Select(x => new DepthNode(type, (JObject)x))); + + // populate the mutable one with the location of the nested object + mutableElement[prop.Name] = new JObject() + { + new JProperty($"{mappingName}_depth_from", (depthMap[mapIndex].Count) - jArray.Count), + new JProperty($"{mappingName}_depth_to", depthMap[mapIndex].Count) }; } else @@ -190,7 +212,7 @@ private string BuildJsonShape() var edgedbName = x.GetEdgeDBPropertyName(); // if its a link, add a ternary statement for pulling the value out of a sub-map - if (QueryUtils.IsLink(x.PropertyType, out _, out _)) + if (QueryUtils.IsLink(x.PropertyType, out var isArray, out _)) { // if we're in the last iteration of the depth map, we know for certian there // are no sub types within the current context, we can safely set the link to @@ -198,7 +220,10 @@ private string BuildJsonShape() if (isLast) return $"{edgedbName} := {{}}"; - return $"{edgedbName} := {mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{x.Name}', '{mappingName}_depth_index')] if json_typeof(json_get({iterationName}, '{x.Name}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; + // return a slice operator for multi links or a index operator for single links + return isArray + ? $"{edgedbName} := distinct array_unpack({mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{x.Name}', '{mappingName}_depth_from') ?? 0:json_get({iterationName}, '{x.Name}', '{mappingName}_depth_to') ?? 0])" + : $"{edgedbName} := {mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{x.Name}', '{mappingName}_depth_index')] if json_typeof(json_get({iterationName}, '{x.Name}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; } // if its a scalar type, use json_get to pull the value and cast it to our property @@ -229,6 +254,7 @@ private string BuildJsonShape() var iterationJson = JsonConvert.SerializeObject(map.Select(x => x.Node)); SetVariable(variableName, new Json(iterationJson)); + SetGlobal($"{mappingName}_d{i}", query, null); } @@ -241,9 +267,12 @@ private string BuildJsonShape() var edgedbName = x.GetEdgeDBPropertyName(); // if its a link, add a ternary statement for pulling the value out of a sub-map - if (QueryUtils.IsLink(x.PropertyType, out _, out _)) + if (QueryUtils.IsLink(x.PropertyType, out var isArray, out _)) { - return $"{edgedbName} := {mappingName}_d1[json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_index')] if json_typeof(json_get({jsonValue.Name}, '{x.Name}')) != 'null' else <{jsonValue.InnerType.GetEdgeDBTypeName()}>{{}}"; + // return a slice operator for multi links or a index operator for single links + return isArray + ? $"{edgedbName} := distinct array_unpack({mappingName}_d1[json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_from') ?? 0:json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_to') ?? 0])" + : $"{edgedbName} := {mappingName}_d1[json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_index')] if json_typeof(json_get({jsonValue.Name}, '{x.Name}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; } // if its a scalar type, use json_get to pull the value and cast it to our property @@ -272,7 +301,7 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul // use the provide shape and value if they're not null, otherwise // use the ones defined in context - var type = shapeType ?? Context.CurrentType; + var type = shapeType ?? OperatingType; var value = shapeValue ?? Context.Value; // if the value is an expression we can just directly translate it @@ -280,7 +309,7 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul return $"{{ {ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; // get all properties that aren't marked with the EdgeDBIgnore attribute - var properties = type.GetProperties().Where(x => x.GetCustomAttribute() == null); + var properties = type.GetEdgeDBTargetProperties(); foreach(var property in properties) { @@ -405,13 +434,13 @@ private string InlineOrGlobal(Type type, SubQuery value, object? reference) public override void Visit() { // add the current type to the sub query map - _subQueryMap.Add(Context.CurrentType); + _subQueryMap.Add(OperatingType); // build the insert shape var shape = Context.IsJsonVariable ? BuildJsonShape() : BuildInsertShape(); // append it to our query - Query.Append($"insert {(Context.IsJsonVariable ? ((IJsonVariable)Context.Value!).InnerType : Context.CurrentType).GetEdgeDBTypeName()} {shape}"); + Query.Append($"insert {OperatingType.GetEdgeDBTypeName()} {shape}"); } /// @@ -423,8 +452,8 @@ public override void FinalizeQuery() if (SchemaInfo is null) throw new NotSupportedException("Cannot use autogenerated unless conflict on without schema interpolation"); - if (!SchemaInfo.TryGetObjectInfo(Context.CurrentType, out var typeInfo)) - throw new NotSupportedException($"Could not find type info for {Context.CurrentType}"); + if (!SchemaInfo.TryGetObjectInfo(OperatingType, out var typeInfo)) + throw new NotSupportedException($"Could not find type info for {OperatingType}"); // get all exclusive properties that aren't the id property var exclusiveProperties = typeInfo.Properties?.Where(x => x.IsExclusive && x.Name != "id"); @@ -440,8 +469,8 @@ public override void FinalizeQuery() Query.Append($" unless conflict on {constraint}"); } - else - Query.Append(_elseStatement); + + Query.Append(_elseStatement); // if the query builder wants this node as a global if (Context.SetAsGlobal && Context.GlobalName != null) @@ -477,7 +506,7 @@ public void UnlessConflictOn(LambdaExpression selector) /// public void ElseDefault() { - _elseStatement.Append($" else (select {Context.CurrentType.GetEdgeDBTypeName()})"); + _elseStatement.Append($" else (select {OperatingType.GetEdgeDBTypeName()})"); } /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index 29f41fef..bbe239b2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -81,6 +81,8 @@ internal NodeContext Context /// internal readonly NodeBuilder Builder; + protected readonly Type OperatingType; + /// /// Constructs a new query node with the given builder. /// @@ -88,6 +90,7 @@ internal NodeContext Context public QueryNode(NodeBuilder builder) { Builder = builder; + OperatingType = GetOperatingType(); } /// @@ -146,6 +149,11 @@ protected string GetOrAddGlobal(object? reference, object? value) return name; } + protected Type GetOperatingType() + => Context.CurrentType.IsAssignableTo(typeof(IJsonVariable)) + ? Context.CurrentType.GenericTypeArguments[0] + : Context.CurrentType; + /// /// Builds the current node, returning the built form . /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 1875b642..bc5f85a0 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -61,7 +61,7 @@ private string GetShape(Type type, int currentDepth = 0) /// /// The default shape for the current contextual type. private string GetDefaultShape() - => GetShape(Context.CurrentType); + => GetShape(OperatingType); /// /// Gets the shape based on the context of the current node. @@ -90,7 +90,7 @@ public override void Visit() if (Context.IsFreeObject) Query.Append($"select {shape}"); else - Query.Append($"select {Context.SelectName ?? Context.CurrentType.GetEdgeDBTypeName()} {shape}"); + Query.Append($"select {Context.SelectName ?? OperatingType.GetEdgeDBTypeName()} {shape}"); } else { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs index cbcd7982..551f3579 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs @@ -24,7 +24,7 @@ public UpdateNode(NodeBuilder builder) : base(builder) { } public override void Visit() { // append 'update type' - Query.Append($"update {Context.CurrentType.GetEdgeDBTypeName()}"); + Query.Append($"update {OperatingType.GetEdgeDBTypeName()}"); // translate the update factory _translatedExpression = ExpressionTranslator.Translate(Context.UpdateExpression!, Builder.QueryVariables, Context, Builder.QueryGlobals); From 9bf3bcd29335e356564641fd33b8edd57a6896ed Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 18 Jul 2022 10:18:29 -0300 Subject: [PATCH 25/70] Method translators. blocked by https://github.com/edgedb/edgedb/issues/4094 --- dbschema/default.esdl | 7 + dbschema/migrations/00002.edgeql | 11 + .../Examples/QueryBuilder.cs | 18 +- src/EdgeDB.Net.Driver/Codecs/Array.cs | 25 +- .../Serializer/PacketWriter.cs | 3 + .../SchemaTypeBuilders/ObjectBuilder.cs | 2 +- .../SchemaTypeBuilders/TypeBuilder.cs | 4 +- .../Utils/ReflectionUtils.cs | 2 +- .../QueryNodes/Contexts/NodeContext.cs | 2 +- .../QueryNodes/ForNode.cs | 2 +- .../QueryNodes/InsertNode.cs | 10 +- .../Expressions/ExpressionContext.cs | 18 +- .../Expressions/ExpressionTranslator.cs | 24 +- .../Expressions/LambdaExpressionTranslator.cs | 23 ++ .../Expressions/MemberExpressionTranslator.cs | 5 +- .../MethodCallExpressionTranslator.cs | 4 + .../NewArrayExpressionTranslator.cs | 9 +- .../Expressions/NewExpressionTranslator.cs | 2 +- .../ParameterExpressionTranslator.cs | 26 ++ .../Expressions/UnaryExpressionTranslator.cs | 12 +- .../Methods/EnumerableMethodTranslator.cs | 69 +++++ .../Methods/GuidMethodTranslator.cs | 22 ++ .../Translators/Methods/MethodTranslator.cs | 264 ++++++++++++++++++ .../Methods/ObjectMethodTranslator.cs | 24 ++ .../Methods/RegexMethodTranslator.cs | 36 +++ .../Methods/StringMethodTranslators.cs | 154 ++++++++++ .../Methods/TranslatedParameter.cs | 74 +++++ .../{ => Utils}/QueryUtils.cs | 108 ++++++- 28 files changed, 919 insertions(+), 41 deletions(-) create mode 100644 dbschema/migrations/00002.edgeql create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/LambdaExpressionTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ParameterExpressionTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/GuidMethodTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/ObjectMethodTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/RegexMethodTranslator.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs rename src/EdgeDB.Net.QueryBuilder/{ => Utils}/QueryUtils.cs (73%) diff --git a/dbschema/default.esdl b/dbschema/default.esdl index 4f6a125f..4d96da97 100644 --- a/dbschema/default.esdl +++ b/dbschema/default.esdl @@ -13,6 +13,13 @@ module default { constraint exclusive; } } + type ArrayPerson { + required property name -> str; + required property roles -> array; + required property email -> str { + constraint exclusive; + } + } # for example todo app scalar type State extending enum; diff --git a/dbschema/migrations/00002.edgeql b/dbschema/migrations/00002.edgeql new file mode 100644 index 00000000..3bbfbefb --- /dev/null +++ b/dbschema/migrations/00002.edgeql @@ -0,0 +1,11 @@ +CREATE MIGRATION m14rc3eoxb5cfgao72uec6ldnjpzsfen6jej74sv7bbwpatb7hmdva + ONTO m1fmzelzxxda652eddles3g56rysvccxzivewujttti4radwepsy5q +{ + CREATE TYPE default::ArrayPerson { + CREATE REQUIRED PROPERTY email -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY name -> std::str; + CREATE REQUIRED PROPERTY roles -> array; + }; +}; diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 281af843..31962c46 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -1,6 +1,8 @@ -using EdgeDB.Schema; +using EdgeDB.QueryNodes; +using EdgeDB.Schema; using EdgeDB.Schema.DataTypes; using EdgeDB.Serializer; +using EdgeDB.Translators.Methods; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -30,6 +32,13 @@ public class MultiLinkPerson public MultiLinkPerson[]? BestFriends { get; set; } } + public class ArrayPerson + { + public string? Name { get; set; } + public string? Email { get; set; } + public IEnumerable? Roles { get; set; } + } + public async Task ExecuteAsync(EdgeDBClient client) { await QueryBuilderDemo(client); @@ -38,6 +47,11 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { + var test = QueryBuilder.Select(() => new + { + Test = new string[] { "test", "test"}.FirstOrDefault(x => x == "test") + }).Build(); + // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); @@ -178,7 +192,7 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) .Else(q => q.Update(old => new LinkPerson { - Name = EdgeQL.ToUpper(old!.Name) + Name = old.Name.ToLower() }) ) .BuildAsync(client)) diff --git a/src/EdgeDB.Net.Driver/Codecs/Array.cs b/src/EdgeDB.Net.Driver/Codecs/Array.cs index 902d8f60..59850748 100644 --- a/src/EdgeDB.Net.Driver/Codecs/Array.cs +++ b/src/EdgeDB.Net.Driver/Codecs/Array.cs @@ -2,6 +2,15 @@ { internal class Array : ICodec> { + public static readonly byte[] EMPTY_ARRAY = new byte[] + { + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,1 + }; + private readonly ICodec _innerCodec; public Array(ICodec innerCodec) @@ -47,11 +56,7 @@ public void Serialize(PacketWriter writer, IEnumerable? value) { if(value is null) { - writer.Write(0); - writer.Write(0); // flags - writer.Write(0); // reserved - writer.Write(0); // zero upper - writer.Write(1); // one lower + writer.Write(EMPTY_ARRAY); return; } @@ -69,12 +74,16 @@ public void Serialize(PacketWriter writer, IEnumerable? value) } else { - _innerCodec.Serialize(elementWriter, element); + var subWriter = new PacketWriter(); + _innerCodec.Serialize(subWriter, element); + elementWriter.Write((int)subWriter.Length); + elementWriter.Write(subWriter); + } } - writer.Write(1); // dimensions - writer.Write(0); // flags + writer.Write(1); // num dimensions + writer.Write(0); // reserved writer.Write(0); // reserved // dimension (our length for upper and 1 for lower) diff --git a/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs b/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs index fe4e028c..a4268dc9 100644 --- a/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs +++ b/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs @@ -12,6 +12,9 @@ namespace EdgeDB { internal unsafe class PacketWriter : BinaryWriter { + public long Length + => base.OutStream.Length; + public PacketWriter() : base(new MemoryStream()) { diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs index b01a928f..df18112e 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs @@ -20,7 +20,7 @@ internal class ObjectBuilder return (TType?)ConvertTo(typeof(TType), value); } - private static object? ConvertTo(Type type, object? value) + internal static object? ConvertTo(Type type, object? value) { if (value is null) { diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs index 427eeb52..bf12f646 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs @@ -137,7 +137,7 @@ internal static bool TryGetCollectionParser(Type type, out Func), type)) + if (ReflectionUtils.IsSubTypeOfGenericType(typeof(List<>), type)) builder = CreateDynamicList; return builder != null; @@ -185,7 +185,7 @@ private static object CreateDynamicList(Array arr, Type elementType) return builder is not null ? builder(parsedArray, innerType) : parsedArray; } - throw new NoTypeConverter(target, value?.GetType() ?? typeof(object)); + return ObjectBuilder.ConvertTo(target, value); } internal static bool TryGetCustomBuilder(this Type objectType, out MethodInfo? info) diff --git a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs index 7a573270..2244acfe 100644 --- a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs +++ b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs @@ -11,7 +11,7 @@ namespace EdgeDB { internal class ReflectionUtils { - public static bool IsInstanceOfGenericType(Type genericType, Type toCheck) + public static bool IsSubTypeOfGenericType(Type genericType, Type toCheck) { Type? type = toCheck; while (type != null) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs index d718cbaa..d064e4c3 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs @@ -29,7 +29,7 @@ internal abstract class NodeContext public Type CurrentType { get; init; } public bool IsJsonVariable - => ReflectionUtils.IsInstanceOfGenericType(typeof(JsonVariable<>), CurrentType); + => ReflectionUtils.IsSubTypeOfGenericType(typeof(JsonVariable<>), CurrentType); /// /// Constructs a new . diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs index 138ac329..0ff1a885 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs @@ -46,7 +46,7 @@ public ForNode(NodeBuilder builder) : base(builder) return x switch { _ when x.Type == typeof(QueryContext) => new QueryContext(), - _ when ReflectionUtils.IsInstanceOfGenericType(typeof(JsonVariable<>), x.Type) + _ when ReflectionUtils.IsSubTypeOfGenericType(typeof(JsonVariable<>), x.Type) => typeof(JsonVariable<>).MakeGenericType(Context.CurrentType) .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new Type[] { typeof(string), typeof(string), typeof(JArray)})! .Invoke(new object?[] { name, varName, jArray })!, diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index e9b42155..c23ce805 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -228,7 +228,8 @@ private string BuildJsonShape() // if its a scalar type, use json_get to pull the value and cast it to our property // type - var edgeqlType = PacketSerializer.GetEdgeQLType(x.PropertyType); + if (!QueryUtils.TryGetScalarType(x.PropertyType, out var edgeqlType)) + throw new NotSupportedException($"Cannot use type {x.PropertyType} as there is no serializer for it"); return $"{edgedbName} := <{edgeqlType}>json_get({iterationName}, '{x.Name}')"; @@ -277,7 +278,8 @@ private string BuildJsonShape() // if its a scalar type, use json_get to pull the value and cast it to our property // type - var edgeqlType = PacketSerializer.GetEdgeQLType(x.PropertyType); + if (!QueryUtils.TryGetScalarType(x.PropertyType, out var edgeqlType)) + throw new NotSupportedException($"Cannot use type {x.PropertyType} as there is no serializer for it"); return $"{edgedbName} := <{edgeqlType}>json_get({jsonValue.Name}, '{x.Name}')"; }); @@ -320,11 +322,9 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul // get the equivalent edgedb property name var propertyName = property.GetEdgeDBPropertyName(); - // get the scalar type - var edgeqlType = PacketSerializer.GetEdgeQLType(propType); // if a scalar type is found for the property type - if(edgeqlType != null) + if(QueryUtils.TryGetScalarType(propType, out var edgeqlType)) { // set it as a variable and continue the iteration var varName = QueryUtils.GenerateRandomVariableName(); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index d8b737dd..f043e954 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -28,7 +28,7 @@ internal class ExpressionContext /// /// Gets the root lambda function that is currently being translated. /// - public LambdaExpression RootExpression { get; } + public LambdaExpression RootExpression { get; set; } /// /// Gets a collection of method parameters within the . @@ -72,12 +72,12 @@ public bool IsFreeObject /// /// The collection of query variables. /// - private readonly IDictionary _queryArguments; + internal readonly IDictionary QueryArguments; /// /// The collection of query globals. /// - private readonly List _globals; + internal readonly List Globals; /// /// Constructs a new . @@ -91,9 +91,9 @@ public ExpressionContext(NodeContext context, LambdaExpression rootExpression, { ExpressionTree.Add(rootExpression); RootExpression = rootExpression; - _queryArguments = queryArguments; + QueryArguments = queryArguments; NodeContext = context; - _globals = globals; + Globals = globals; Parameters = rootExpression.Parameters.ToDictionary(x => x.Name!, x => x.Type); } @@ -106,7 +106,7 @@ public ExpressionContext(NodeContext context, LambdaExpression rootExpression, public string AddVariable(object? value) { var name = QueryUtils.GenerateRandomVariableName(); - _queryArguments[name] = value; + QueryArguments[name] = value; return name; } @@ -116,7 +116,7 @@ public string AddVariable(object? value) /// The name of the query variable. /// The value of the query variable. public void SetVariable(string name, object? value) - => _queryArguments[name] = value; + => QueryArguments[name] = value; /// /// Attempts to fetch a query global by reference. @@ -129,7 +129,7 @@ public void SetVariable(string name, object? value) /// public bool TryGetGlobal(object? reference, [MaybeNullWhen(false)]out QueryGlobal global) { - global = _globals.FirstOrDefault(x => x.Reference == reference); + global = Globals.FirstOrDefault(x => x.Reference == reference); return global != null; } @@ -158,7 +158,7 @@ public string GetOrAddGlobal(object? reference, object? value) public void SetGlobal(string name, object? value, object? reference) { var global = new QueryGlobal(name, value, reference); - _globals.Add(global); + Globals.Add(global); } /// diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs index cd0d4fad..6beac01b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -134,17 +134,37 @@ public static string Translate(LambdaExpression expression, IDictionaryNo translator was found for the given expression. protected static string TranslateExpression(Expression expression, ExpressionContext context) { + // special fallthru for lambda functions + if (expression is LambdaExpression lambda) + return _translators[typeof(LambdaExpression)].Translate(lambda, context)!; + + // 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!; + expType = expType.BaseType!; + // if we can find a translator for the expression type, use it. if (_translators.TryGetValue(expType, out var translator)) { - return translator.Translate(expression, context.Enter(x => x.ExpressionTree.Add(expression)))!; } 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 string form of the expression. + /// No translator was found for the given expression. + internal static string ContextualTranslate(Expression expression, ExpressionContext context) + => TranslateExpression(expression, context); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/LambdaExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/LambdaExpressionTranslator.cs new file mode 100644 index 00000000..cc0c6e93 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/LambdaExpressionTranslator.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Expressions +{ + /// + /// Represents a translator for translating a lambda expression. + /// + internal class LambdaExpressionTranslator : ExpressionTranslator + { + /// + public override string? Translate(LambdaExpression expression, ExpressionContext context) + { + // create a new context and translate the body of the lambda. + var newContext = new ExpressionContext(context.NodeContext, expression, context.QueryArguments, context.Globals); + return TranslateExpression(expression.Body, newContext); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index 16301325..98538892 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -23,7 +23,10 @@ internal class MemberExpressionTranslator : ExpressionTranslator${varName}"; } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index 01628c06..70045631 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -119,6 +119,10 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator()?.Operator; diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs index 014201d5..339e4e7a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs @@ -17,7 +17,14 @@ internal class NewArrayExpressionTranslator : ExpressionTranslator TranslateExpression(x, context)))} }}"; + var elements = string.Join(", ", expression.Expressions.Select(x => TranslateExpression(x, context))); + + // if its a collection of link-valid types, serialzie it as a set + if(QueryUtils.IsLink(expression.Type, out _, out _)) + return $"{{ {elements} }}"; + + // serialize as a scalar array + return $"[{elements}]"; } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs index 1cfb7aa8..de24c75e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs @@ -35,7 +35,7 @@ internal class NewExpressionTranslator : ExpressionTranslator } // translate the value and determine if were setting a value or referencing a value. - string? value = TranslateExpression(arg, context.Enter(x => x.LocalScope = expression.Type)); ; + string? value = TranslateExpression(arg, context.Enter(x => x.LocalScope = expression.Type)); bool isSetter = context.NodeContext.CurrentType.GetProperty(member.Name) == null || arg is MethodCallExpression; // add it to our shape diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ParameterExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ParameterExpressionTranslator.cs new file mode 100644 index 00000000..1ba50bde --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ParameterExpressionTranslator.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +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 + { + /// + public override string? Translate(ParameterExpression expression, ExpressionContext context) + { + return $"{expression.Name}"; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs index cabba0f3..779bc05a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs @@ -31,17 +31,21 @@ internal class UnaryExpressionTranslator : ExpressionTranslator return null; // nullable converters for include, ex Guid? -> Guid // dotnet nullable check - if (ReflectionUtils.IsInstanceOfGenericType(typeof(Nullable<>), expression.Type) && + if (ReflectionUtils.IsSubTypeOfGenericType(typeof(Nullable<>), expression.Type) && expression.Type.GenericTypeArguments[0] == expression.Operand.Type) { // no need to cast in edgedb, return the value return value; - } + } - var type = PacketSerializer.GetEdgeQLType(expression.Type) ?? expression.Type.GetEdgeDBTypeName(); - + var type = QueryUtils.TryGetScalarType(expression.Type, out var edgedbType) + ? edgedbType.ToString() + : expression.Type.GetEdgeDBTypeName(); + return $"<{type}>{value}"; } + case ExpressionType.ArrayLength: + return $"len({TranslateExpression(expression.Operand, context)})"; // default case attempts to get an IEdgeQLOperator for the given // node type, and uses that to translate the expression. diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs new file mode 100644 index 00000000..4dfca650 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs @@ -0,0 +1,69 @@ +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 + { + /// + protected override Type TransaltorTargetType => typeof(Enumerable); + + /// + /// Translates the method . + /// + /// The source collection to count. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Enumerable.Count))] + public string Count(TranslatedParameter source) + => (source.IsScalarArrayType || source.IsScalarType) ? $"len({source})" : $"count({source})"; + + /// + /// Translates the method . + /// + /// The source collection. + /// The value to locate within the collection. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Enumerable.Contains))] + public string Contains(TranslatedParameter source, string target) + => (source.IsScalarArrayType || source.IsScalarType) ? $"contains({source}, {target})" : $"{target} in {source}"; + + /// + /// Translates the method . + /// + /// The source collection. + /// The index of the element to get + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Enumerable.ElementAt))] + public string ElementAt(TranslatedParameter source, string index) + => $"array_get({source}, {index})"; + + /// + /// Translates the method . + /// + /// 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 string FirstOrDefault(TranslatedParameter source, TranslatedParameter filterOrDefault, TranslatedParameter? defaultValue) + { + if (filterOrDefault.IsScalarType) + return $"{ElementAt(source, "0")} ?? {filterOrDefault}"; + + // get the parameter name for the filter + 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; + return $"<{QueryUtils.GetEdgeDBScalarOrTypename(returnType)}>array_get(array_agg((select {name} := {set} filter {filterOrDefault})), 0){(defaultValue != null ? $" ?? {defaultValue}" : String.Empty)}"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/GuidMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/GuidMethodTranslator.cs new file mode 100644 index 00000000..089e081a --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/GuidMethodTranslator.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Methods +{ + /// + /// Represents a translator for translating methods within the struct. + /// + internal class GuidMethodTranslator : MethodTranslator + { + /// + /// Translates the method . + /// + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Guid.NewGuid))] + public string Generate() + => $"uuid_generate_v4()"; + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslator.cs new file mode 100644 index 00000000..a991f396 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/MethodTranslator.cs @@ -0,0 +1,264 @@ +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 +{ + /// + /// Marks this method as a valid method used to translate a . + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + internal class MethodNameAttribute : Attribute + { + /// + /// The method name that the current target can translate. + /// + internal readonly string MethodName; + + /// + /// Marks this method as a valid method used to translate a . + /// + /// The name of the method that this method can translate. + public MethodNameAttribute(string methodName) + { + MethodName = methodName; + } + } + + /// + /// 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 + { + /// + protected override Type TransaltorTargetType => typeof(TBase); + } + + internal abstract class MethodTranslator + { + /// + /// Gets the base type that contains the methods the current translator can + /// translate. + /// + protected abstract Type TransaltorTargetType { get; } + + /// + /// The static dictionary containing all of the method translators. + /// + private static readonly ConcurrentDictionary _translators = new(); + + /// + /// The dictionary containing all of the methods that the current translator + /// can translate. + /// + private 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))); + + var tempDict = new Dictionary(); + + // 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)) + { + tempDict.Add(((MethodNameAttribute)att).MethodName, method); + } + } + + // create a new concurrent dictionary from our temp one + _methodTranslators = new(tempDict); + + } + + /// + /// Statically initializes the abstract method translator and populates + /// . + /// + static MethodTranslator() + { + var types = Assembly.GetExecutingAssembly().DefinedTypes; + + // load current translators + var translators = types.Where(x => x.BaseType?.Name == "MethodTranslator`1" || (x.BaseType == typeof(MethodTranslator) && x.Name != "MethodTranslator`1")); + + // iterate over the translators and initialize them and store them in the translators + // dictionary + foreach (var translator in translators) + { + var inst = (MethodTranslator)Activator.CreateInstance(translator)!; + _translators[inst.TransaltorTargetType] = inst; + } + } + + /// + /// Attempts to translate the given into a edgeql equivalent expression. + /// + /// The method call expression to translate. + /// The current context for the method call expression. + /// The out result containing the translated method. + /// + /// if the was translated; otherwise . + /// + public static bool TryTranslateMethod(MethodCallExpression methodCall, ExpressionContext context, [MaybeNullWhen(false)] out string translatedMethod) + { + translatedMethod = null; + try + { + translatedMethod = TranslateMethod(methodCall, context); + return true; + } + catch { return false; } + } + + /// + /// Translates the given into a edgeql equivalent expression. + /// + /// The method call expression to translate. + /// The current context for the method call expression. + /// + /// The translated expression. + /// + /// No translator could be found for the given method expression. + public static string TranslateMethod(MethodCallExpression methodCall, ExpressionContext context) + { + var type = methodCall.Method.DeclaringType; + MethodTranslator? translator = null; + + while(type != null && !_translators.TryGetValue(type, out translator)) + { + type = type.BaseType; + } + + if(type is null || translator is null) + throw new NotSupportedException($"Cannot use method {methodCall.Method} as there is no translator for it"); + + return translator.Translate(methodCall, context); + } + + /// + /// Includes an argument if its . + /// + /// The argument to include. + /// The prefix of the argument. + /// + /// The argument with the prefix if its ; + /// otherwise an empty string. + /// + protected string OptionalArg(string? arg, string prefix = ", ") + { + if (arg is null) + return string.Empty; + else + return $"{prefix}{arg}"; + } + + /// + /// Finds and executes a translater method for the given . + /// + /// The expression to translate. + /// The context of the expression. + /// The translated version of the method call. + /// + /// No translator could be found for the given method. + /// + protected string Translate(MethodCallExpression methodCall, ExpressionContext context) + { + // try to get a method for translating the expression + if (!_methodTranslators.TryGetValue(methodCall.Method.Name, out var methodInfo)) + throw new NotSupportedException($"Cannot use method {methodCall.Method} as there is no translator for it"); + + // get the parameters of the method and check if it references an instance parameter + var methodParameters = methodInfo.GetParameters(); + var instanceParam = methodParameters.FirstOrDefault()?.Name == "instance" ? methodParameters[0] : null; + var hasInstanceReference = instanceParam is not null; + + // slice the origional parameters array to exlude the instance parameter if its defined + if (hasInstanceReference) + methodParameters = methodParameters[1..]; + + // create a new object[] that will contain our parameters for calling the translator method + object?[] parsedParameters = new object?[methodParameters.Length]; + + // iterate over the parameters and parse them. + for (int i = 0; i != methodParameters.Length; i++) + { + var parameterInfo = methodParameters[i]; + + // if the current parameter is marked with the ParamArray attribute, set + // its value to the remaining arguments to the expression and break out of the loop + if (parameterInfo.GetCustomAttribute() != null) + { + parsedParameters[i] = methodCall.Arguments.Skip(i).Select(x + => ExpressionTranslator.ContextualTranslate(x, context) + ).ToArray(); + break; + + } + else if (methodCall.Arguments.Count > i) + { + // translate the argument expression + var translated = ExpressionTranslator.ContextualTranslate(methodCall.Arguments[i], context); + + // if the type is a TranslatedParameter, construct a new one and set it in the parsed + // parameter array + if (parameterInfo.ParameterType == typeof(TranslatedParameter)) + { + parsedParameters[i] = new TranslatedParameter(methodCall.Arguments[i].Type, translated, methodCall.Arguments[i]); + } + else // fallthru and just set the translated parameter + parsedParameters[i] = translated; + + } + else if (parameterInfo.HasDefaultValue) + { + // set the default value for the parameter + parsedParameters[i] = parameterInfo.DefaultValue; + } + else if (parameterInfo.ParameterType == typeof(ExpressionContext)) + { + // set the context + parsedParameters[i] = context; + } + else // get the default value for the parameter type + parsedParameters[i] = ReflectionUtils.GetDefault(parameterInfo.ParameterType); + } + + // if its an instance reference, recreate our parsed array to include the instance parameter + // and set the instance parameter to the translated expression + if (hasInstanceReference) + { + var newParameters = new object?[methodParameters.Length + 1]; + parsedParameters.CopyTo(newParameters, 1); + + newParameters[0] = methodCall.Object is not null + ? instanceParam?.ParameterType == typeof(TranslatedParameter) + ? new TranslatedParameter(methodCall.Object.Type, ExpressionTranslator.ContextualTranslate(methodCall.Object, context), methodCall.Object) + : ExpressionTranslator.ContextualTranslate(methodCall.Object, context) + : null; + + parsedParameters = newParameters; + } + + // invoke the translator method and return its results + return (string)methodInfo.Invoke(this, parsedParameters)!; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/ObjectMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/ObjectMethodTranslator.cs new file mode 100644 index 00000000..5ee5ee64 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/ObjectMethodTranslator.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Methods +{ + /// + /// Represents a translator for translating methods within the class. + /// + internal class ObjectMethodTranslator : MethodTranslator + { + /// + /// Translates the method . + /// + /// The instance of the object. + /// The optional format for the tostring func. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(object.ToString))] + public string ToStr(string instance, string? format) + => $"to_str({instance}{OptionalArg(format)})"; + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/RegexMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/RegexMethodTranslator.cs new file mode 100644 index 00000000..7fc2cfe7 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/RegexMethodTranslator.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Methods +{ + /// + /// Represents a translator for translating methods within the class. + /// + internal class RegexMethodTranslator : MethodTranslator + { + /// + /// Translates the method . + /// + /// 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 string Replace(string input, string pattern, string replacement) + => $"re_replace({pattern}, {replacement}, {input})"; + + /// + /// Translates the method . + /// + /// The string to test against. + /// The regex pattern. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(Regex.IsMatch))] + public string Test(string testString, string pattern) + => $"re_test({pattern}, {testString})"; + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs new file mode 100644 index 00000000..ff094da3 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Translators.Methods +{ + /// + /// Represents a translator for translating methods within the class. + /// + internal class StringMethodTranslators : MethodTranslator + { + /// + /// Translates the method . + /// + /// The instance of the string to concat agains. + /// The variable arguments that should be concatenated together. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.Concat))] + public string Concat(string? instance, params string[] variableArgs) + { + if (instance is not null) + return $"{instance} ++ {string.Join(" ++ ", variableArgs)}"; + + return string.Join(" ++ ", variableArgs); + } + + /// + /// Translates the method . + /// + /// The instance of the string to concat agains. + /// The value to check whether or not its within the instance + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.Contains))] + public string Contains(string instance, string target) + => $"contains({instance}, {target})"; + + /// + /// Translates the method . + /// + /// The instance of the string. + /// The target substring to find within the instance. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.IndexOf))] + public string Find(string instance, string target) + => $"find({instance}, {target})"; + + /// + /// Translates the method . + /// + /// The instance of the string. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.ToLower))] + [MethodName(nameof(string.ToLowerInvariant))] + public string ToLower(string instance) + => $"str_lower({instance})"; + + /// + /// Translates the method . + /// + /// The instance of the string. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.ToUpper))] + [MethodName(nameof(string.ToUpperInvariant))] + public string ToUpper(string instance) + => $"str_upper({instance})"; + + /// + /// Translates the method . + /// + /// The instance of the string. + /// The amount to pad left + /// The fill character to pad with + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.PadLeft))] + public string PadLeft(string instance, string amount, string? fill) + => $"str_pad_start({instance}, {amount}{OptionalArg(fill)})"; + + /// + /// Translates the method . + /// + /// The instance of the string. + /// The amount to pad left + /// The fill character to pad with + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.PadRight))] + public string PadRight(string instance, string amount, string? fill) + => $"str_pad_end({instance}, {amount}{OptionalArg(fill)})"; + + /// + /// Translates the method . + /// + /// The instance of the string. + /// The characters to trim. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.Trim))] + public string Trim(string instance, params string[]? trimChars) + { + if (trimChars != null && trimChars.Any()) + return $"str_trim({instance}, '{string.Join("", trimChars.Select(x => x.Replace("\"", "")))}')"; + return $"str_trim({instance})"; + } + + /// + /// Translates the method . + /// + /// The instance of the string. + /// The characters to trim. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.TrimStart))] + public string TrimStart(string instance, params string[] trimChars) + { + if (trimChars != null && trimChars.Any()) + return $"str_trim_start({instance}, '{string.Join("", trimChars.Select(x => x.Replace("\"", "")))}')"; + return $"str_trim_start({instance})"; + } + + /// + /// Translates the method . + /// + /// The instance of the string. + /// The characters to trim. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.TrimEnd))] + public string TrimEnd(string instance, params string[] trimChars) + { + if (trimChars != null && trimChars.Any()) + return $"str_trim_end({instance}, '{string.Join("", trimChars.Select(x => x.Replace("\"", "")))}')"; + return $"str_trim_end({instance})"; + } + + /// + /// Translates the method . + /// + /// The instance of the string. + /// The old string to replace. + /// The new string to replace the old one. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.Replace))] + public string Replace(string instance, string old, string newStr) + => $"str_replace({instance}, {old}, {newStr})"; + + /// + /// Translates the method . + /// + /// The instance of the string. + /// The char to split by. + /// The EdgeQL equivalent of the method. + [MethodName(nameof(string.Split))] + public string Split(string instance, string separator) + => $"str_split({instance}, {separator})"; + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs new file mode 100644 index 00000000..146e5ec2 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs @@ -0,0 +1,74 @@ +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 parameter used within a method translator. + /// + internal class TranslatedParameter + { + /// + /// Gets the type original type of the parameter. + /// + public Type ParameterType { get; } + + /// + /// Gets the translated value of the parameter. + /// + public string Value { get; } + + /// + /// Gets the raw expression of the parameter. + /// + public Expression RawValue { get; } + + /// + /// Gets whether or not the parameter type is a scalar array. + /// + public bool IsScalarArrayType + => QueryUtils.TryGetScalarType(ParameterType, out var info) && info.IsArray; + + /// + /// Gets whether or not the parameter is a scalar type. + /// + public bool IsScalarType + => QueryUtils.TryGetScalarType(ParameterType, out _); + + /// + /// Gets whether or not the parameter is a valid link type. + /// + public bool IsLinkType + => QueryUtils.IsLink(ParameterType, out _, out _); + + /// + /// Gets whether or not the parameter is a valid multi-link type. + /// + public bool IsMutliLinkType + => QueryUtils.IsLink(ParameterType, out var isMulti, out _) && isMulti; + + /// + /// Constructs a new . + /// + /// The type of the parameter. + /// The translated value of the parameter. + /// The raw expression of the parameter. + public TranslatedParameter(Type type, string value, Expression raw) + { + ParameterType = type; + Value = value; + RawValue = raw; + } + + /// + /// Converts this into the edgeql form. + /// + /// The edgeql (parsed) version of the parameter. + public override string ToString() + => Value; + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs similarity index 73% rename from src/EdgeDB.Net.QueryBuilder/QueryUtils.cs rename to src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs index 0f6b980f..2fdcfd64 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs @@ -3,7 +3,9 @@ using EdgeDB.Schema; using EdgeDB.Serializer; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; @@ -20,6 +22,108 @@ internal static class QueryUtils { private const string VARIABLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static readonly Random _rng = new(); + private static ConcurrentDictionary _typeCache = new(); + + /// + /// Represents type info about a compatable edgedb type. + /// + internal class EdgeDBTypeInfo + { + /// + /// 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; + } + } + + /// + /// 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 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; + + Type? enumerableType = type.GetInterfaces().FirstOrDefault(x => x.Name == "IEnumerable`1"); + + EdgeDBTypeInfo? child = null; + var hasChild = enumerableType != null && TryGetScalarType(enumerableType.GenericTypeArguments[0], out child); + var scalar = PacketSerializer.GetEdgeQLType(type); + + if (scalar != null) + info = new(type, scalar, false, child); + else if (hasChild) + info = new(type, "array", true, child); + + return info != null && _typeCache.TryAdd(type, info); + } /// /// Checks whether or not a type is a valid link type. @@ -39,7 +143,7 @@ public static bool IsLink(Type type, out bool isMultiLink, [MaybeNullWhen(false) isMultiLink = false; Type? enumerableType = null; - if (type != typeof(string) && (enumerableType = type.GetInterfaces().FirstOrDefault(x => ReflectionUtils.IsInstanceOfGenericType(typeof(IEnumerable<>), x))) != null) + if (type != typeof(string) && (enumerableType = type.GetInterfaces().FirstOrDefault(x => ReflectionUtils.IsSubTypeOfGenericType(typeof(IEnumerable<>), x))) != null) { innerLinkType = enumerableType.GenericTypeArguments[0]; isMultiLink = true; @@ -78,7 +182,7 @@ internal static string ParseObject(object? obj) SubQuery query when !query.RequiresIntrospection => query.Query!, string str => $"\"{str}\"", char chr => $"\"{chr}\"", - Type type => PacketSerializer.GetEdgeQLType(type) ?? type.GetEdgeDBTypeName(), + Type type => TryGetScalarType(type, out var info) ? info.ToString() : type.GetEdgeDBTypeName(), _ => obj.ToString()! }; } From 39b4967e499c98a943d69e16b759ba3293972a97 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 18 Jul 2022 10:28:05 -0300 Subject: [PATCH 26/70] add qb to myget feed --- .github/workflows/deploy-prerelease.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-prerelease.yml b/.github/workflows/deploy-prerelease.yml index 6bc7d2a8..0f394e36 100644 --- a/.github/workflows/deploy-prerelease.yml +++ b/.github/workflows/deploy-prerelease.yml @@ -19,9 +19,13 @@ jobs: include-prerelease: true - name: Install dependencies run: dotnet restore - - name: Build + - name: Build Driver run: dotnet build src/EdgeDB.Net.Driver/EdgeDB.Net.Driver.csproj --configuration Release --no-restore - - name: Pack + - name: Build Query Builder + run: dotnet build src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj --configuration Release --no-restore + - name: Pack Driver run: dotnet pack src/EdgeDB.Net.Driver/EdgeDB.Net.Driver.csproj -c Release -o ./artifacts --no-build + - name: Pack QueryBuilder + run: dotnet pack src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj -c Release -o ./artifacts --no-build - name: Upload run: dotnet nuget push ./artifacts/*.nupkg --api-key ${{ secrets.NUGET_KEY }} --source https://www.myget.org/F/edgedb-net/api/v2/package \ No newline at end of file From 6379652d3450fcb545d28dd5460e0eb941fa378e Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 18 Jul 2022 10:29:38 -0300 Subject: [PATCH 27/70] add querybuilder branch as target for pre-release action --- .github/workflows/deploy-prerelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-prerelease.yml b/.github/workflows/deploy-prerelease.yml index 0f394e36..c2406d03 100644 --- a/.github/workflows/deploy-prerelease.yml +++ b/.github/workflows/deploy-prerelease.yml @@ -2,7 +2,7 @@ name: NuGet Deploy (Prerelease) on: push: - branches: [ dev ] + branches: [ dev, feat/querybuilder-v2 ] env: BuildNumber: "${{ github.run_number }}" From 3c1a5b90611189d04cb86bdf7596f8f54f841c26 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 18 Jul 2022 10:33:26 -0300 Subject: [PATCH 28/70] fix query builder csproj file --- .../EdgeDB.Net.QueryBuilder.csproj | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj index 5a2fb385..73437cb5 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj +++ b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj @@ -1,38 +1,17 @@  - + - EdgeDB.Net.QueryBuilder - EdgeDB + EdgeDB.Net.QueryBuilder + EdgeDB + An optional extension to the base driver that adds a query builder. net6.0 enable enable - An optional extension to the base driver that allows for query building. - CS1591 - - - - 5 + CS1591 True - - - 5 - True - - - - - - - - True - \ - - - - From 7fee53090be962d424974cab7edc27b133b66f4b Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 18 Jul 2022 10:41:35 -0300 Subject: [PATCH 29/70] fix pipeline build errors --- src/EdgeDB.Net.QueryBuilder/JsonVariable.cs | 1 + src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 14 +++++++------- src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs | 1 + .../QueryNodes/SelectNode.cs | 2 +- src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs | 4 ++-- .../Translators/Methods/StringMethodTranslators.cs | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs index 13c01fa6..85f74743 100644 --- a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs +++ b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs @@ -82,6 +82,7 @@ internal bool IsObjectArray /// 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 JsonVariable(string name, string varName, JArray array) { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index ecb6774a..388ff816 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -21,7 +21,7 @@ public static IMultiCardinalityExecutable For(IEnumerable c Expression, IQueryBuilder>> iterator) => new QueryBuilder().For(collection, iterator); - /// + /// public static ISelectQuery Select() => new QueryBuilder().Select(); @@ -29,7 +29,7 @@ public static ISelectQuery Select() public static ISelectQuery Select(Expression> selectFunc) => new QueryBuilder().Select(selectFunc); - /// + /// public static ISelectQuery Select(Expression> shape) => new QueryBuilder().Select(shape); @@ -293,7 +293,7 @@ public ISelectQuery Select(Expression> selectFun return EnterNewType(); } - /// + /// public ISelectQuery Select(Expression> shape) { AddNode(new SelectContext(typeof(TType)) @@ -304,7 +304,7 @@ public ISelectQuery Select(Expression> shape) return this; } - /// + /// public ISelectQuery Select(Expression> shape) { AddNode(new SelectContext(typeof(TType)) @@ -495,7 +495,7 @@ private QueryBuilder Limit(long limit) /// /// Adds a 'LIMIT' statement to the current node. /// - /// The lambda function of which the result is the amount to limit by. + /// The lambda function of which the result is the amount to limit by. /// The current builder. /// /// The current node does not support limit statements. @@ -799,7 +799,6 @@ public interface IQueryBuilder : /// To define a shape, use to include a property. any other /// methods/values will be treated as computed values. /// - /// The type to select. /// The shape to select. /// /// A . @@ -882,7 +881,8 @@ public interface IQueryBuilder /// Builds the current query. /// /// - /// If the query requires introspection please use . + /// If the query requires introspection please use + /// . /// /// /// A . diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs index 0ff1a885..08ce247e 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs @@ -24,6 +24,7 @@ public ForNode(NodeBuilder builder) : base(builder) /// 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 edgeql iterator for a 'FOR' statement; or diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index bc5f85a0..3b43921b 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -102,7 +102,7 @@ public override void Visit() /// /// Adds a filter to the select node. /// - /// The filter predicate to add. + /// The filter predicate to add. public void Filter(LambdaExpression expression) { var parsedExpression = ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs index 44fb2c14..db392306 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -25,14 +25,14 @@ public QueryBuilder QueryBuilder => new(); /// - /// The client used for introspection & execution. + /// The client used for introspection and execution. /// private readonly IEdgeDBQueryable _edgedb; /// /// Constructs a new . /// - /// The client to introspect & execute with. + /// The client to introspect and execute with. internal QueryableCollection(IEdgeDBQueryable edgedb) { _edgedb = edgedb; diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs index ff094da3..d189c708 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/StringMethodTranslators.cs @@ -142,7 +142,7 @@ public string Replace(string instance, string old, string newStr) => $"str_replace({instance}, {old}, {newStr})"; /// - /// Translates the method . + /// Translates the method . /// /// The instance of the string. /// The char to split by. From 55f1fc2f90667cd1fe61554cf5b700c567b5afe8 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 18 Jul 2022 12:25:54 -0300 Subject: [PATCH 30/70] expose capability bitfield for executing queries --- .../Clients/BaseEdgeDBClient.cs | 19 +++++---- .../Clients/EdgeDBBinaryClient.cs | 17 ++++---- .../Clients/EdgeDBHttpClient.cs | 10 ++--- src/EdgeDB.Net.Driver/Clients/IQueryable.cs | 41 +++++++++++-------- src/EdgeDB.Net.Driver/EdgeDBClient.cs | 17 ++++---- .../Models/Shared/Capabilities.cs | 4 +- src/EdgeDB.Net.Driver/Transaction.cs | 17 ++++---- .../Interfaces/IMultiCardinalityExecutable.cs | 3 +- .../ISingleCardinalityExecutable.cs | 3 +- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 10 +++-- .../QueryableCollection.cs | 22 +++++----- .../Schema/SchemaIntrospector.cs | 2 +- 12 files changed, 92 insertions(+), 73 deletions(-) diff --git a/src/EdgeDB.Net.Driver/Clients/BaseEdgeDBClient.cs b/src/EdgeDB.Net.Driver/Clients/BaseEdgeDBClient.cs index 7f0d0f49..9103d49b 100644 --- a/src/EdgeDB.Net.Driver/Clients/BaseEdgeDBClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/BaseEdgeDBClient.cs @@ -106,11 +106,13 @@ public virtual ValueTask ConnectAsync(CancellationToken token = default) /// /// The query to execute. /// Optional collection of arguments within the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task that represents the asynchronous execution operation. /// - public abstract Task ExecuteAsync(string query, IDictionary? args = null, CancellationToken token = default); + public abstract Task ExecuteAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); /// /// Executes a given query and returns its results. @@ -118,14 +120,15 @@ public virtual ValueTask ConnectAsync(CancellationToken token = default) /// The return type of the query. /// The query to execute. /// Optional collection of arguments within the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task that represents the asynchronous execution operation; the tasks result /// is a containing the /// (s) returned in the query. /// - public abstract Task> QueryAsync(string query, IDictionary? args = null, - CancellationToken token = default); + public abstract Task> QueryAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); /// /// Executes a given query and returns the result. @@ -133,13 +136,14 @@ public virtual ValueTask ConnectAsync(CancellationToken token = default) /// The return type of the query. /// The query to execute. /// Optional collection of arguments within the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task that represents the asynchronous execution operation; the tasks result /// is an instance of . /// - public abstract Task QueryRequiredSingleAsync(string query, IDictionary? args = null, - CancellationToken token = default); + public abstract Task QueryRequiredSingleAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); /// /// Executes a given query and returns the result; or @@ -148,13 +152,14 @@ public abstract Task QueryRequiredSingleAsync(string query, ID /// The return type of the query. /// The query to execute. /// Optional collection of arguments within the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task that represents the asynchronous execution operation; the tasks result /// is an instance of . /// - public abstract Task QuerySingleAsync(string query, IDictionary? args = null, - CancellationToken token = default); + public abstract Task QuerySingleAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); /// async ValueTask IAsyncDisposable.DisposeAsync() diff --git a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs index 422aa6df..a4f86a4b 100644 --- a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs @@ -308,8 +308,9 @@ bool handler(IReceiveable msg) /// The client received an . /// The client received an unexpected message. /// A codec could not be found for the given input arguments or the result. - public override async Task ExecuteAsync(string query, IDictionary? args = null, CancellationToken token = default) - => await ExecuteInternalAsync(query, args, Cardinality.Many, token: token).ConfigureAwait(false); + public override async Task ExecuteAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) + => await ExecuteInternalAsync(query, args, Cardinality.Many, capabilities, token: token).ConfigureAwait(false); /// /// A general error occored. @@ -319,10 +320,10 @@ public override async Task ExecuteAsync(string query, IDictionaryTarget type doesn't match received type. /// Cannot construct a . public override async Task> QueryAsync(string query, IDictionary? args = null, - CancellationToken token = default) + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) where TResult : default { - var result = await ExecuteInternalAsync(query, args, Cardinality.Many, token: token); + var result = await ExecuteInternalAsync(query, args, Cardinality.Many, capabilities, token: token); List returnResults = new(); @@ -344,10 +345,10 @@ public override async Task ExecuteAsync(string query, IDictionaryTarget type doesn't match received type. /// Cannot construct a . public override async Task QuerySingleAsync(string query, IDictionary? args = null, - CancellationToken token = default) + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) where TResult : default { - var result = await ExecuteInternalAsync(query, args, Cardinality.AtMostOne, token: token); + var result = await ExecuteInternalAsync(query, args, Cardinality.AtMostOne, capabilities, token: token); if (result.Data.Count > 1) throw new ResultCardinalityMismatchException(Cardinality.AtMostOne, Cardinality.Many); @@ -369,9 +370,9 @@ public override async Task ExecuteAsync(string query, IDictionaryTarget type doesn't match received type. /// Cannot construct a . public override async Task QueryRequiredSingleAsync(string query, IDictionary? args = null, - CancellationToken token = default) + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) { - var result = await ExecuteInternalAsync(query, args, Cardinality.AtMostOne, token: token); + var result = await ExecuteInternalAsync(query, args, Cardinality.AtMostOne, capabilities, token: token); if (result.Data.Count is > 1 or 0) throw new ResultCardinalityMismatchException(Cardinality.One, result.Data.Count > 1 ? Cardinality.Many : Cardinality.AtMostOne); diff --git a/src/EdgeDB.Net.Driver/Clients/EdgeDBHttpClient.cs b/src/EdgeDB.Net.Driver/Clients/EdgeDBHttpClient.cs index 8963a0d7..dfce903e 100644 --- a/src/EdgeDB.Net.Driver/Clients/EdgeDBHttpClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/EdgeDBHttpClient.cs @@ -199,8 +199,8 @@ private async Task ExecuteInternalAsync(string query, IDictiona /// /// The server returned a status code other than 200. - public override async Task ExecuteAsync(string query, IDictionary? args = null, - CancellationToken token = default) + public override async Task ExecuteAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) { await ExecuteInternalAsync(query, args, token).ConfigureAwait(false); } @@ -208,7 +208,7 @@ public override async Task ExecuteAsync(string query, IDictionary /// The server returned a status code other than 200. public override async Task> QueryAsync(string query, IDictionary? args = null, - CancellationToken token = default) + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) where TResult : default { var result = await ExecuteInternalAsync(query, args, token); @@ -223,7 +223,7 @@ public override async Task ExecuteAsync(string query, IDictionary /// The server returned a status code other than 200. public override async Task QueryRequiredSingleAsync(string query, IDictionary? args = null, - CancellationToken token = default) + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) { var result = await ExecuteInternalAsync(query, args, token); @@ -241,7 +241,7 @@ public override async Task QueryRequiredSingleAsync(string que /// /// The server returned a status code other than 200. public override async Task QuerySingleAsync(string query, IDictionary? args = null, - CancellationToken token = default) + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) where TResult : default { var result = await ExecuteInternalAsync(query, args, token); diff --git a/src/EdgeDB.Net.Driver/Clients/IQueryable.cs b/src/EdgeDB.Net.Driver/Clients/IQueryable.cs index 5dc6eb1e..e5cdc904 100644 --- a/src/EdgeDB.Net.Driver/Clients/IQueryable.cs +++ b/src/EdgeDB.Net.Driver/Clients/IQueryable.cs @@ -17,12 +17,13 @@ public interface IEdgeDBQueryable /// /// The query to execute. /// Any arguments that are part of the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task representing the asynchronous execute operation. /// - Task ExecuteAsync(string query, IDictionary? args = null, - CancellationToken token = default); + Task ExecuteAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); /// /// Executes a given query and returns the result as a collection. @@ -33,14 +34,15 @@ Task ExecuteAsync(string query, IDictionary? args = null, /// /// The query to execute. /// Any arguments that are part of the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task representing the asynchronous query operation. The result /// of the task is the result of the query. /// - Task> QueryAsync(string query, IDictionary? args = null, - CancellationToken token = default) - => QueryAsync(query, args); + Task> QueryAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) + => QueryAsync(query, args, capabilities, token); /// /// Executes a given query and returns the result as a collection. @@ -52,13 +54,14 @@ Task ExecuteAsync(string query, IDictionary? args = null, /// The type of the return result of the query. /// The query to execute. /// Any arguments that are part of the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task representing the asynchronous query operation. The result /// of the task is the result of the query. /// - Task> QueryAsync(string query, IDictionary? args = null, - CancellationToken token = default); + Task> QueryAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); /// /// Executes a given query and returns a single result or . @@ -69,14 +72,15 @@ Task ExecuteAsync(string query, IDictionary? args = null, /// /// The query to execute. /// Any arguments that are part of the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task representing the asynchronous query operation. The result /// of the task is the result of the query. /// - Task QuerySingleAsync(string query, IDictionary? args = null, - CancellationToken token = default) - => QuerySingleAsync(query, args); + Task QuerySingleAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) + => QuerySingleAsync(query, args, capabilities, token); /// /// Executes a given query and returns a single result or . @@ -88,13 +92,14 @@ Task ExecuteAsync(string query, IDictionary? args = null, /// The return type of the query. /// The query to execute. /// Any arguments that are part of the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task representing the asynchronous query operation. The result /// of the task is the result of the query. /// - Task QuerySingleAsync(string query, IDictionary? args = null, - CancellationToken token = default); + Task QuerySingleAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); /// /// Executes a given query and returns a single result. @@ -105,14 +110,15 @@ Task ExecuteAsync(string query, IDictionary? args = null, /// /// The query to execute. /// Any arguments that are part of the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task representing the asynchronous query operation. The result /// of the task is the result of the query. /// - Task QueryRequiredSingleAsync(string query, IDictionary? args = null, - CancellationToken token = default) - => QueryRequiredSingleAsync(query, args); + Task QueryRequiredSingleAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) + => QueryRequiredSingleAsync(query, args, capabilities, token); /// /// Executes a given query and returns a single result. @@ -124,12 +130,13 @@ Task QueryRequiredSingleAsync(string query, IDictionary /// The return type of the query. /// The query to execute. /// Any arguments that are part of the query. + /// The allowed capabilities for the query. /// A cancellation token used to cancel the asynchronous operation. /// /// A task representing the asynchronous query operation. The result /// of the task is the result of the query. /// - Task QueryRequiredSingleAsync(string query, IDictionary? args = null, - CancellationToken token = default); + Task QueryRequiredSingleAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); } } diff --git a/src/EdgeDB.Net.Driver/EdgeDBClient.cs b/src/EdgeDB.Net.Driver/EdgeDBClient.cs index 3c4e4b7b..2293383a 100644 --- a/src/EdgeDB.Net.Driver/EdgeDBClient.cs +++ b/src/EdgeDB.Net.Driver/EdgeDBClient.cs @@ -186,46 +186,47 @@ public async Task DisconnectAllAsync(CancellationToken token = default) } /// - public async Task ExecuteAsync(string query, IDictionary? args = null, CancellationToken token = default) + public async Task ExecuteAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) { if (!_isInitialized) await InitializeAsync(token).ConfigureAwait(false); await using var client = await GetOrCreateClientAsync(token).ConfigureAwait(false); - await client.ExecuteAsync(query, args, token).ConfigureAwait(false); + await client.ExecuteAsync(query, args, capabilities, token).ConfigureAwait(false); } /// public async Task> QueryAsync(string query, IDictionary? args = null, - CancellationToken token = default) + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) { if (!_isInitialized) await InitializeAsync(token).ConfigureAwait(false); await using var client = await GetOrCreateClientAsync(token).ConfigureAwait(false); - return await client.QueryAsync(query, args, token).ConfigureAwait(false); + return await client.QueryAsync(query, args, capabilities, token).ConfigureAwait(false); } /// public async Task QuerySingleAsync(string query, IDictionary? args = null, - CancellationToken token = default) + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) { if (!_isInitialized) await InitializeAsync(token).ConfigureAwait(false); await using var client = await GetOrCreateClientAsync(token).ConfigureAwait(false); - return await client.QuerySingleAsync(query, args, token).ConfigureAwait(false); + return await client.QuerySingleAsync(query, args, capabilities, token).ConfigureAwait(false); } /// public async Task QueryRequiredSingleAsync(string query, IDictionary? args = null, - CancellationToken token = default) + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) { if (!_isInitialized) await InitializeAsync(token).ConfigureAwait(false); await using var client = await GetOrCreateClientAsync(token).ConfigureAwait(false); - return await client.QueryRequiredSingleAsync(query, args, token).ConfigureAwait(false); + return await client.QueryRequiredSingleAsync(query, args, capabilities, token).ConfigureAwait(false); } /// diff --git a/src/EdgeDB.Net.Driver/Models/Shared/Capabilities.cs b/src/EdgeDB.Net.Driver/Models/Shared/Capabilities.cs index 72a6aea1..be51c435 100644 --- a/src/EdgeDB.Net.Driver/Models/Shared/Capabilities.cs +++ b/src/EdgeDB.Net.Driver/Models/Shared/Capabilities.cs @@ -7,7 +7,7 @@ namespace EdgeDB { /// - /// Represents a bitfield of capabilities used when executing commands. + /// Represents a bitfield of capabilities used when executing queries. /// [Flags] public enum Capabilities : ulong @@ -38,7 +38,7 @@ public enum Capabilities : ulong DDL = 1 << 3, /// - /// The command changes serve ror database configs. + /// The command changes server or database configs. /// PersistantConfig = 1 << 4, diff --git a/src/EdgeDB.Net.Driver/Transaction.cs b/src/EdgeDB.Net.Driver/Transaction.cs index 3544cf88..ec5901c3 100644 --- a/src/EdgeDB.Net.Driver/Transaction.cs +++ b/src/EdgeDB.Net.Driver/Transaction.cs @@ -87,23 +87,24 @@ internal async Task ExecuteInternalAsync(Func> f } /// - public Task ExecuteAsync(string query, IDictionary? args = null, CancellationToken token = default) - => ExecuteInternalAsync(() => _client.ExecuteAsync(query, args, token)); + public Task ExecuteAsync(string query, IDictionary? args = null, + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) + => ExecuteInternalAsync(() => _client.ExecuteAsync(query, args, capabilities, token)); /// public Task> QueryAsync(string query, IDictionary? args = null, - CancellationToken token = default) - => ExecuteInternalAsync(() => _client.QueryAsync(query, args, token)); + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) + => ExecuteInternalAsync(() => _client.QueryAsync(query, args, capabilities, token)); /// public Task QuerySingleAsync(string query, IDictionary? args = null, - CancellationToken token = default) - => ExecuteInternalAsync(() => _client.QuerySingleAsync(query, args, token)); + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) + => ExecuteInternalAsync(() => _client.QuerySingleAsync(query, args, capabilities, token)); /// public Task QueryRequiredSingleAsync(string query, IDictionary? args = null, - CancellationToken token = default) - => ExecuteInternalAsync(() => _client.QueryRequiredSingleAsync(query, args, token)); + Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default) + => ExecuteInternalAsync(() => _client.QueryRequiredSingleAsync(query, args, capabilities, token)); } public sealed class TransactionSettings diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs index 1f673f30..f399e9fb 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs @@ -16,10 +16,11 @@ public interface IMultiCardinalityExecutable : IQueryBuilder, IMultiCardi /// 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, CancellationToken token = default); + Task> ExecuteAsync(IEdgeDBQueryable edgedb, Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs index 4658b946..8aff28a1 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs @@ -16,10 +16,11 @@ public interface ISingleCardinalityExecutable : IQueryBuilder, ISingleCar /// 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, CancellationToken token = default); + Task ExecuteAsync(IEdgeDBQueryable edgedb, Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 388ff816..0a7dd86a 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -726,17 +726,19 @@ private async ValueTask IntrospectAndBuildAsync(IEdgeDBQueryable edg } /// - async Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) + async Task> IMultiCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, + Capabilities? capabilities, CancellationToken token) { var result = await IntrospectAndBuildAsync(edgedb, token).ConfigureAwait(false); - return await edgedb.QueryAsync(result.Query, result.Parameters, token).ConfigureAwait(false); + return await edgedb.QueryAsync(result.Query, result.Parameters, capabilities, token).ConfigureAwait(false); } /// - async Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, CancellationToken token) + async Task ISingleCardinalityExecutable.ExecuteAsync(IEdgeDBQueryable edgedb, + Capabilities? capabilities, CancellationToken token) { var result = await IntrospectAndBuildAsync(edgedb, token).ConfigureAwait(false); - return await edgedb.QuerySingleAsync(result.Query, result.Parameters, token).ConfigureAwait(false); + return await edgedb.QuerySingleAsync(result.Query, result.Parameters, capabilities, token).ConfigureAwait(false); } #region IQueryBuilder diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs index db392306..7a396876 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -46,7 +46,7 @@ internal QueryableCollection(IEdgeDBQueryable edgedb) /// The item to add violates an exclusive constraint. /// The added value. public Task AddAsync(TType item, CancellationToken token = default) - => QueryBuilder.Insert(item).ExecuteAsync(_edgedb, token); + => QueryBuilder.Insert(item).ExecuteAsync(_edgedb, token: token); /// /// Adds or updates an existing value based on the types unique constraints. @@ -66,7 +66,7 @@ internal QueryableCollection(IEdgeDBQueryable edgedb) .Else(b => (Interfaces.ISingleCardinalityExecutable)b.Update(updateFactory) ) - .ExecuteAsync(_edgedb, token); + .ExecuteAsync(_edgedb, token: token); /// /// Adds or updates an existing value based on the types unique constraints. @@ -89,7 +89,7 @@ internal QueryableCollection(IEdgeDBQueryable edgedb) .UnlessConflict() .Else(q => (Interfaces.ISingleCardinalityExecutable)q.Update(updateFactory!, false) - ).ExecuteAsync(_edgedb, token).ConfigureAwait(false); + ).ExecuteAsync(_edgedb, token: token).ConfigureAwait(false); } /// @@ -107,7 +107,7 @@ internal QueryableCollection(IEdgeDBQueryable edgedb) public async Task TryAddAsync(TType item, CancellationToken token = default) { var query = QueryBuilder.Insert(item, false).UnlessConflict(); - var result = await query.ExecuteAsync(_edgedb, token); + var result = await query.ExecuteAsync(_edgedb, token: token); return result != null; } @@ -129,7 +129,7 @@ public Task GetOrAddAsync(TType item, CancellationToken token = default) .Insert(item) .UnlessConflict() .ElseReturn() - .ExecuteAsync(_edgedb, token)!; + .ExecuteAsync(_edgedb, token: token)!; /// /// Deletes a value from the collection. @@ -150,7 +150,7 @@ public async Task DeleteAsync(TType item, CancellationToken token = defaul await QueryBuilder .Delete .Filter((_, ctx) => ctx.UnsafeLocal("id") == id) - .ExecuteAsync(_edgedb, token).ConfigureAwait(false) + .ExecuteAsync(_edgedb, token: token).ConfigureAwait(false) ).Any(); // try to get exclusive property set on the instance @@ -183,7 +183,7 @@ await QueryBuilder var builder = QueryBuilder; foreach (var (prop, name) in variables) builder.AddQueryVariable(name, prop.GetValue(item)); - return (await builder.Delete.Filter(expr).ExecuteAsync(_edgedb, token).ConfigureAwait(false)).Any(); + return (await builder.Delete.Filter(expr).ExecuteAsync(_edgedb, token: token).ConfigureAwait(false)).Any(); } /// @@ -193,7 +193,7 @@ await QueryBuilder /// A cancellation token to cancel the asynchronous insert operation. /// The number of values deleted. public Task DeleteWhereAsync(Expression> filter, CancellationToken token = default) - => ((Interfaces.ISingleCardinalityExecutable)QueryBuilder.Select(() => EdgeQL.Count(QueryBuilder.Delete.Filter(filter)))).ExecuteAsync(_edgedb, token); + => ((ISingleCardinalityExecutable)QueryBuilder.Select(() => EdgeQL.Count(QueryBuilder.Delete.Filter(filter)))).ExecuteAsync(_edgedb, token: token); /// /// Filters the current collection by a predicate. @@ -203,7 +203,7 @@ public Task DeleteWhereAsync(Expression> filter, Cancell /// A collection of that match the provided predicate. public async Task> WhereAsync(Expression> filter, CancellationToken token = default) - => await QueryBuilder.Select().Filter(filter).ExecuteAsync(_edgedb, token); + => await QueryBuilder.Select().Filter(filter).ExecuteAsync(_edgedb, token: token); /// /// Updates a given value in the collection with an update factory. @@ -220,7 +220,7 @@ public Task DeleteWhereAsync(Expression> filter, Cancell => (await QueryBuilder .Update(updateFunc) .Filter(await QueryUtils.GenerateUpdateFilterAsync(_edgedb, value, token)) - .ExecuteAsync(_edgedb, token).ConfigureAwait(false)).FirstOrDefault(); + .ExecuteAsync(_edgedb, token: token).ConfigureAwait(false)).FirstOrDefault(); /// /// Updates a given value in the collection. @@ -236,6 +236,6 @@ public Task DeleteWhereAsync(Expression> filter, Cancell => (await QueryBuilder .Update(await QueryUtils.GenerateUpdateFactoryAsync(_edgedb, value, token)) .Filter(await QueryUtils.GenerateUpdateFilterAsync(_edgedb, value, token)) - .ExecuteAsync(_edgedb, token).ConfigureAwait(false)).FirstOrDefault(); + .ExecuteAsync(_edgedb, token: token).ConfigureAwait(false)).FirstOrDefault(); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs index 1d1e11a4..3afef4cc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs @@ -73,7 +73,7 @@ private static async Task IntrospectSchemaAsync(IEdgeDBQueryable edg HasDefault = ctx.Raw("EXISTS .default or (\"std::sequence\" in .target[IS schema::ScalarType].ancestors.name)") }) - }).Filter((x, ctx) => !ctx.UnsafeLocal("builtin")).ExecuteAsync(edgedb, token); + }).Filter((x, ctx) => !ctx.UnsafeLocal("builtin")).ExecuteAsync(edgedb, token: token); // add to our cache return _schemas[edgedb] = new SchemaInfo(result); From 92682dd5cc29ecc6eedea8aec7ccaee9531838ad Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 19 Jul 2022 09:57:18 -0300 Subject: [PATCH 31/70] Fix multi link operators --- .../Examples/QueryBuilder.cs | 6 +++--- .../Translators/Expressions/ExpressionContext.cs | 8 -------- .../Translators/Expressions/ExpressionTranslator.cs | 2 +- .../Expressions/MemberInitExpressionTranslator.cs | 11 +++++++++++ 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 31962c46..287f7aea 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -47,10 +47,10 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var test = QueryBuilder.Select(() => new + var test = QueryBuilder.Update(old => new MultiLinkPerson { - Test = new string[] { "test", "test"}.FirstOrDefault(x => x == "test") - }).Build(); + BestFriends = EdgeQL.AddLink(QueryBuilder.Insert(new MultiLinkPerson(), false)) + }).Build().Prettify(); // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index f043e954..078c1131 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -15,11 +15,6 @@ namespace EdgeDB /// internal class ExpressionContext { - /// - /// Gets the current expression tree. - /// - public List ExpressionTree { get; set; } = new(); - /// /// Gets the calling nodes context. /// @@ -89,7 +84,6 @@ public bool IsFreeObject public ExpressionContext(NodeContext context, LambdaExpression rootExpression, IDictionary queryArguments, List globals) { - ExpressionTree.Add(rootExpression); RootExpression = rootExpression; QueryArguments = queryArguments; NodeContext = context; @@ -170,8 +164,6 @@ public ExpressionContext Enter(Action func) { var exp = (ExpressionContext)MemberwiseClone(); func(exp); - // creates a new instance as we dont want to copy ref of this contexts tree. - exp.ExpressionTree = ExpressionTree.ToList(); return exp; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs index 6beac01b..d374be63 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -147,7 +147,7 @@ protected static string TranslateExpression(Expression expression, ExpressionCon // if we can find a translator for the expression type, use it. if (_translators.TryGetValue(expType, out var translator)) { - return translator.Translate(expression, context.Enter(x => x.ExpressionTree.Add(expression)))!; + return translator.Translate(expression, context)!; } throw new NotSupportedException($"Failed to find translator for expression type: {expType.Name}.{expression.NodeType}"); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs index d4dcbfe6..c426be5f 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs @@ -98,6 +98,17 @@ assignment.Expression is MethodCallExpression mcx && }); } break; + case MethodCallExpression methodCall: + var parsed = TranslateExpression(methodCall, context); + if(context.HasInitializationOperator) + { + initializations.Add($"{memberName} {parsed}"); + context.HasInitializationOperator = false; + } + else + initializations.Add($"{memberName} := {parsed}"); + isSubQuery = false; + break; default: throw new NotSupportedException($"Cannot set links with a {assignment.Expression} expression."); } From 114366bf010534690a7da3469175853928c9ec50 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 19 Jul 2022 12:59:32 -0300 Subject: [PATCH 32/70] Add capabilities to json methods --- .../Extensions/EdgeDBClientExtensions.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/EdgeDB.Net.Driver/Extensions/EdgeDBClientExtensions.cs b/src/EdgeDB.Net.Driver/Extensions/EdgeDBClientExtensions.cs index 4cdf2ecc..2ac5cea5 100644 --- a/src/EdgeDB.Net.Driver/Extensions/EdgeDBClientExtensions.cs +++ b/src/EdgeDB.Net.Driver/Extensions/EdgeDBClientExtensions.cs @@ -20,15 +20,16 @@ public static class EdgeDBClientExtensions /// /// The query returned more than 1 datapoint. public static async Task QueryJsonAsync(this EdgeDBBinaryClient client, - string query, IDictionary? args = null) + string query, IDictionary? args = null, + Capabilities capabilities = Capabilities.Modifications) { - var result = await client.ExecuteInternalAsync(query, args, Cardinality.Many, format: IOFormat.Json).ConfigureAwait(false); + var result = await client.ExecuteInternalAsync(query, args, Cardinality.Many, capabilities, format: IOFormat.Json).ConfigureAwait(false); if(result.Data.Count >= 2) { throw new ResultCardinalityMismatchException(Cardinality.AtMostOne, Cardinality.Many); } - + return result.Data.Count == 1 ? (string)result.Deserializer.Deserialize(result.Data[0].PayloadBuffer)! : "[]"; @@ -45,9 +46,10 @@ public static async Task QueryJsonAsync(this EdgeDBBinaryClient client, /// the json result of the query. /// public static async Task QueryJsonElementsAsync(this EdgeDBBinaryClient client, - string query, IDictionary? args = null) + string query, IDictionary? args = null, + Capabilities capabilities = Capabilities.Modifications) { - var result = await client.ExecuteInternalAsync(query, args, Cardinality.Many, format: IOFormat.JsonElements).ConfigureAwait(false); + var result = await client.ExecuteInternalAsync(query, args, Cardinality.Many, capabilities, format: IOFormat.JsonElements).ConfigureAwait(false); string[] elements = new string[result.Data.Count]; From 4ee5526b8da38f03a16ac57b0134d51219ba801d Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 20 Jul 2022 09:16:21 -0300 Subject: [PATCH 33/70] fix pipeline build errors --- src/EdgeDB.Net.Driver/Extensions/EdgeDBClientExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/EdgeDB.Net.Driver/Extensions/EdgeDBClientExtensions.cs b/src/EdgeDB.Net.Driver/Extensions/EdgeDBClientExtensions.cs index 2ac5cea5..c33b3c87 100644 --- a/src/EdgeDB.Net.Driver/Extensions/EdgeDBClientExtensions.cs +++ b/src/EdgeDB.Net.Driver/Extensions/EdgeDBClientExtensions.cs @@ -14,6 +14,7 @@ public static class EdgeDBClientExtensions /// The client on which to preform the query on. /// The query to execute. /// Optional collection of arguments within the query. + /// The allowed capabilities for the query. /// /// A task representing the asynchronous query operation. The tasks result is /// the json result of the query. @@ -41,6 +42,7 @@ public static async Task QueryJsonAsync(this EdgeDBBinaryClient client, /// The client on which to preform the query on. /// The query to execute. /// Optional collection of arguments within the query. + /// The allowed capabilities for the query. /// /// A task representing the asynchronous query operation. The tasks result is /// the json result of the query. From a51afbadee34bc99fe7482809d47f602d2835516 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 20 Jul 2022 09:36:21 -0300 Subject: [PATCH 34/70] Allow dynamic type passthru for deserialization --- examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs | 2 ++ .../Serializer/SchemaTypeBuilders/TypeBuilder.cs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 287f7aea..01839ae8 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -47,6 +47,8 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { + var test2 = await client.QueryAsync("select Person { name }"); + var test = QueryBuilder.Update(old => new MultiLinkPerson { BestFriends = EdgeQL.AddLink(QueryBuilder.Insert(new MultiLinkPerson(), false)) diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs index bf12f646..b844fa5c 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs @@ -311,6 +311,9 @@ public void UpdateFactory(TypeDeserializerFactory factory) private TypeDeserializerFactory CreateDefaultFactory() { + if (_type == typeof(object)) + return (data) => data; + // if type is anon type if (_type.GetCustomAttribute() != null) { From d105b587466b949b5c5725e19b8f92df57d80adc Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 26 Jul 2022 16:30:49 -0300 Subject: [PATCH 35/70] unit test framework --- EdgeDB.Net.sln | 7 ++++ EdgeDB.Tests.QueryBuilder.Unit/Consts.cs | 14 ++++++++ .../EdgeDB.Tests.QueryBuilder.Unit.csproj | 23 +++++++++++++ EdgeDB.Tests.QueryBuilder.Unit/Selects.cs | 26 +++++++++++++++ EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs | 18 ++++++++++ EdgeDB.Tests.QueryBuilder.Unit/Types.cs | 33 +++++++++++++++++++ EdgeDB.Tests.QueryBuilder.Unit/Usings.cs | 1 + 7 files changed, 122 insertions(+) create mode 100644 EdgeDB.Tests.QueryBuilder.Unit/Consts.cs create mode 100644 EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj create mode 100644 EdgeDB.Tests.QueryBuilder.Unit/Selects.cs create mode 100644 EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs create mode 100644 EdgeDB.Tests.QueryBuilder.Unit/Types.cs create mode 100644 EdgeDB.Tests.QueryBuilder.Unit/Usings.cs diff --git a/EdgeDB.Net.sln b/EdgeDB.Net.sln index 5e490f2b..f4c91975 100644 --- a/EdgeDB.Net.sln +++ b/EdgeDB.Net.sln @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Serializer.Experimen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Examples.ExampleTODOApi", "examples\EdgeDB.Examples.ExampleTODOApi\EdgeDB.Examples.ExampleTODOApi.csproj", "{E38429C6-53A5-4311-8189-1F78238666DC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdgeDB.Tests.QueryBuilder.Unit", "EdgeDB.Tests.QueryBuilder.Unit\EdgeDB.Tests.QueryBuilder.Unit.csproj", "{69285B60-7817-4DBE-83CF-0C15ED825721}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -85,6 +87,10 @@ Global {E38429C6-53A5-4311-8189-1F78238666DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {E38429C6-53A5-4311-8189-1F78238666DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {E38429C6-53A5-4311-8189-1F78238666DC}.Release|Any CPU.Build.0 = Release|Any CPU + {69285B60-7817-4DBE-83CF-0C15ED825721}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69285B60-7817-4DBE-83CF-0C15ED825721}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69285B60-7817-4DBE-83CF-0C15ED825721}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69285B60-7817-4DBE-83CF-0C15ED825721}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -101,6 +107,7 @@ Global {3A4AAAA0-9948-43D3-B838-8EFAC130240C} = {67ED9EF0-7828-44C0-8CB0-DEBD69EC94CA} {6FA68DEA-D398-4A5B-8025-5F15C728F04C} = {49B6FB80-A675-4ECA-802C-2337A4F37566} {E38429C6-53A5-4311-8189-1F78238666DC} = {6FC214F5-C912-4D99-91B1-3E9F52A4E11B} + {69285B60-7817-4DBE-83CF-0C15ED825721} = {E6B9FABC-241B-4561-9A94-E67B6BE380E2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4E90C94F-D693-4411-82F3-2051DE1BE052} diff --git a/EdgeDB.Tests.QueryBuilder.Unit/Consts.cs b/EdgeDB.Tests.QueryBuilder.Unit/Consts.cs new file mode 100644 index 00000000..9bfd22f6 --- /dev/null +++ b/EdgeDB.Tests.QueryBuilder.Unit/Consts.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Tests.Unit +{ + public class Consts + { + public const string SCALAR_AUTOGEN_SHAPE = "select ScalarType { string_prop, long_prop, date_time_offset_prop }"; + public const string SINGLE_LINK_AUTOGEN_SHAPE = "select LinkType { string_prop, link_prop: { string_prop } }"; + } +} diff --git a/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj b/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj new file mode 100644 index 00000000..98273a68 --- /dev/null +++ b/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/EdgeDB.Tests.QueryBuilder.Unit/Selects.cs b/EdgeDB.Tests.QueryBuilder.Unit/Selects.cs new file mode 100644 index 00000000..814c30d7 --- /dev/null +++ b/EdgeDB.Tests.QueryBuilder.Unit/Selects.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Tests.Unit +{ + [TestClass] + public class Selects : TestBase + { + [TestMethod] + public void SelectScalarAutogenShape() + { + var result = QueryBuilder.Select(); + AssertResultIs(result.Build(), Consts.SCALAR_AUTOGEN_SHAPE); + } + + [TestMethod] + public void SelectLinkAutogenShape() + { + var result = QueryBuilder.Select(); + AssertResultIs(result.Build(), Consts.SINGLE_LINK_AUTOGEN_SHAPE); + } + } +} diff --git a/EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs b/EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs new file mode 100644 index 00000000..42c8b89d --- /dev/null +++ b/EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Tests.Unit +{ + public abstract class TestBase + { + protected void AssertResultIs(BuiltQuery result, string matching) + { + // TODO: variable inference with matching since variables are randomly generated. + var queryText = result.Query; + Assert.Equals(queryText, matching); + } + } +} diff --git a/EdgeDB.Tests.QueryBuilder.Unit/Types.cs b/EdgeDB.Tests.QueryBuilder.Unit/Types.cs new file mode 100644 index 00000000..e96b0b5a --- /dev/null +++ b/EdgeDB.Tests.QueryBuilder.Unit/Types.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Tests.Unit +{ + public class ScalarType + { + public string? StringProp { get; set; } + public long LongProp { get; set; } + public DateTimeOffset DateTimeOffsetProp { get; set; } + } + + public class LinkType + { + public string? StringProp { get; set; } + public LinkType? LinkProp { get; set; } + } + + public class MultiLinkType + { + public string? StringProp { get; set; } + public LinkType? LinkProp { get; set; } + public LinkType[]? MultiLinkProp { get; set; } + } + + public class InheritanceType : ScalarType + { + public LinkType? LinkProp { get; set; } + } +} diff --git a/EdgeDB.Tests.QueryBuilder.Unit/Usings.cs b/EdgeDB.Tests.QueryBuilder.Unit/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/EdgeDB.Tests.QueryBuilder.Unit/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file From 9fc2159a8d4c6609c5138fbda7b3954409c9256a Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 26 Jul 2022 17:01:11 -0300 Subject: [PATCH 36/70] contextual method invoking within expressions --- .../Examples/QueryBuilder.cs | 7 --- .../Models/Sendables/Execute.cs | 2 +- .../Models/Sendables/Parse.cs | 2 +- .../SchemaTypeBuilders/TypeBuilder.cs | 3 +- .../QueryNodes/InsertNode.cs | 1 - .../MethodCallExpressionTranslator.cs | 55 +++++++++++++++++-- 6 files changed, 55 insertions(+), 15 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 01839ae8..cc0f2421 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -47,13 +47,6 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var test2 = await client.QueryAsync("select Person { name }"); - - var test = QueryBuilder.Update(old => new MultiLinkPerson - { - BestFriends = EdgeQL.AddLink(QueryBuilder.Insert(new MultiLinkPerson(), false)) - }).Build().Prettify(); - // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.Driver/Models/Sendables/Execute.cs b/src/EdgeDB.Net.Driver/Models/Sendables/Execute.cs index 9d4006a1..94784733 100644 --- a/src/EdgeDB.Net.Driver/Models/Sendables/Execute.cs +++ b/src/EdgeDB.Net.Driver/Models/Sendables/Execute.cs @@ -51,7 +51,7 @@ protected override void BuildPacket(PacketWriter writer, EdgeDBBinaryClient clie compilationFlags |= 1 << 0; if (ImplicitTypeNames) compilationFlags |= 1 << 1; - if (ExplicitObjectIds) + if (!ExplicitObjectIds) compilationFlags |= 1 << 2; writer.Write(compilationFlags); writer.Write(ImplicitLimit); diff --git a/src/EdgeDB.Net.Driver/Models/Sendables/Parse.cs b/src/EdgeDB.Net.Driver/Models/Sendables/Parse.cs index e7d83c4d..fbc3ba1b 100644 --- a/src/EdgeDB.Net.Driver/Models/Sendables/Parse.cs +++ b/src/EdgeDB.Net.Driver/Models/Sendables/Parse.cs @@ -54,7 +54,7 @@ protected override void BuildPacket(PacketWriter writer, EdgeDBBinaryClient clie compilationFlags |= 1 << 0; if (ImplicitTypeNames) compilationFlags |= 1 << 1; - if (ExplicitObjectIds) + if (!ExplicitObjectIds) compilationFlags |= 1 << 2; writer.Write(compilationFlags); writer.Write(ImplicitLimit); diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs index fe241351..a30bca2b 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs @@ -456,7 +456,8 @@ private TypeDeserializerFactory CreateDefaultFactory() public object Deserialize(IDictionary args) { var result = _factory(args); - TypeBuilder.DispatchObjectCreate(result, (Guid)args["id"]!); + if (args.TryGetValue("id", out var rawGuid)) + TypeBuilder.DispatchObjectCreate(result, (Guid)rawGuid!); return result; } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index c23ce805..b4884551 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -322,7 +322,6 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul // get the equivalent edgedb property name var propertyName = property.GetEdgeDBPropertyName(); - // if a scalar type is found for the property type if(QueryUtils.TryGetScalarType(propType, out var edgeqlType)) { diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index 70045631..bcbd146f 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -16,6 +16,53 @@ namespace EdgeDB.Translators.Expressions internal class MethodCallExpressionTranslator : ExpressionTranslator { public override string? Translate(MethodCallExpression expression, ExpressionContext context) + { + // figure out if the method is something we should translate or somthing that we should + // call to pull the result from. + if(ShouldTranslate(expression, context)) + return TranslateToEdgeQL(expression, context); + + // invoke and translate the result + var result = Expression.Lambda(expression).Compile().DynamicInvoke(); + + // attempt to get the scalar type of the result of the method. + if (!QueryUtils.TryGetScalarType(expression.Type, out var type)) + throw new InvalidOperationException($"Cannot use {expression.Type} as a result in an un-translated context"); + + // return the variable name containing the result of the method. + return $"<{type}>{context.AddVariable(result)}"; + } + + private bool ShouldTranslate(MethodCallExpression expression, ExpressionContext context) + { + // if the method references context or a parameter to our current root lambda + var disassembledInstance = expression.Object is null + ? Array.Empty () + : DisassembleInstance(expression.Object).ToArray(); + + var isInstanceReferenceToContext = expression.Object?.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(x => disassembledInstance.Contains(x)); + var isParameterReferenceToContext = expression.Arguments.Any(x => x.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(y => y == x)); + return isParameterReferenceToContext || isInstanceReferenceToContext; + } + + private IEnumerable DisassembleInstance(Expression expression) + { + yield return expression; + + var temp = expression; + while(temp is MemberExpression memberExpression) + { + if (memberExpression.Expression is not null) + { + yield return memberExpression.Expression; + temp = memberExpression.Expression; + } + else + break; + } + } + + private string? TranslateToEdgeQL(MethodCallExpression expression, ExpressionContext context) { // if our method is within the query context class if (expression.Method.DeclaringType == typeof(QueryContext)) @@ -72,16 +119,16 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator 1; // translate the backlink property accessor var property = TranslateExpression(expression.Arguments[0], isRawPropertyName - ? context.Enter(x => x.StringWithoutQuotes = true) + ? context.Enter(x => x.StringWithoutQuotes = true) : context.Enter(x => x.IncludeSelfReference = false)); - + var backlink = $".<{property}"; // if its a lambda, add the corresponding generic type as a [is x] statement @@ -103,7 +150,7 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator Date: Tue, 26 Jul 2022 20:58:43 -0300 Subject: [PATCH 37/70] init of v2 generator for stdlib --- EdgeDB.Net.sln | 9 ++- dbschema/migrations/00001.edgeql | 52 ++------------ dbschema/migrations/00002.edgeql | 12 ++-- dbschema/migrations/00003.edgeql | 20 ++++++ dbschema/migrations/00004.edgeql | 9 +++ dbschema/migrations/00005.edgeql | 11 +++ dbschema/migrations/00006.edgeql | 9 +++ dbschema/migrations/00007.edgeql | 13 ++++ dbschema/migrations/00008.edgeql | 9 +++ dbschema/migrations/00010.edgeql | 25 +++++++ .../Clients/EdgeDBBinaryClient.cs | 29 +++++--- .../Attributes/EdgeDBPropertyAttribute.cs | 19 +---- .../Serializer/CodecBuilder.cs | 40 ++++++++--- .../Extensions/TypeExtensions.cs | 6 +- .../QueryNodes/SelectNode.cs | 4 +- .../MethodCallExpressionTranslator.cs | 8 ++- .../EdgeDB.Tests.QueryBuilder.Unit}/Consts.cs | 0 .../EdgeDB.Tests.QueryBuilder.Unit.csproj | 4 +- .../Selects.cs | 0 .../TestBase.cs | 0 .../EdgeDB.Tests.QueryBuilder.Unit}/Types.cs | 0 .../EdgeDB.Tests.QueryBuilder.Unit}/Usings.cs | 0 .../CodeWriter.cs | 70 +++++++++++++++++++ ...B.QueryBuilder.StandardLibGenerator.csproj | 15 ++++ .../FunctionGenerator.cs | 36 ++++++++++ .../Models/Annotation.cs | 21 ++++++ .../Models/Function.cs | 25 +++++++ .../Models/Operator.cs | 35 ++++++++++ .../Models/Parameter.cs | 34 +++++++++ .../Models/Type.cs | 18 +++++ .../Models/TypeModifier.cs | 15 ++++ .../Models/Volatility.cs | 15 ++++ .../OperatorGenerator.cs | 18 +++++ .../Program.cs | 12 ++++ .../TypeUtils.cs | 47 +++++++++++++ 35 files changed, 539 insertions(+), 101 deletions(-) create mode 100644 dbschema/migrations/00003.edgeql create mode 100644 dbschema/migrations/00004.edgeql create mode 100644 dbschema/migrations/00005.edgeql create mode 100644 dbschema/migrations/00006.edgeql create mode 100644 dbschema/migrations/00007.edgeql create mode 100644 dbschema/migrations/00008.edgeql create mode 100644 dbschema/migrations/00010.edgeql rename {EdgeDB.Tests.QueryBuilder.Unit => tests/EdgeDB.Tests.QueryBuilder.Unit}/Consts.cs (100%) rename {EdgeDB.Tests.QueryBuilder.Unit => tests/EdgeDB.Tests.QueryBuilder.Unit}/EdgeDB.Tests.QueryBuilder.Unit.csproj (75%) rename {EdgeDB.Tests.QueryBuilder.Unit => tests/EdgeDB.Tests.QueryBuilder.Unit}/Selects.cs (100%) rename {EdgeDB.Tests.QueryBuilder.Unit => tests/EdgeDB.Tests.QueryBuilder.Unit}/TestBase.cs (100%) rename {EdgeDB.Tests.QueryBuilder.Unit => tests/EdgeDB.Tests.QueryBuilder.Unit}/Types.cs (100%) rename {EdgeDB.Tests.QueryBuilder.Unit => tests/EdgeDB.Tests.QueryBuilder.Unit}/Usings.cs (100%) create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/EdgeDB.QueryBuilder.StandardLibGenerator.csproj create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Annotation.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Function.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Operator.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Parameter.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/TypeModifier.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Volatility.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/OperatorGenerator.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs create mode 100644 tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs diff --git a/EdgeDB.Net.sln b/EdgeDB.Net.sln index f4c91975..379b5f04 100644 --- a/EdgeDB.Net.sln +++ b/EdgeDB.Net.sln @@ -35,7 +35,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Serializer.Experimen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Examples.ExampleTODOApi", "examples\EdgeDB.Examples.ExampleTODOApi\EdgeDB.Examples.ExampleTODOApi.csproj", "{E38429C6-53A5-4311-8189-1F78238666DC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdgeDB.Tests.QueryBuilder.Unit", "EdgeDB.Tests.QueryBuilder.Unit\EdgeDB.Tests.QueryBuilder.Unit.csproj", "{69285B60-7817-4DBE-83CF-0C15ED825721}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Tests.QueryBuilder.Unit", "tests\EdgeDB.Tests.QueryBuilder.Unit\EdgeDB.Tests.QueryBuilder.Unit.csproj", "{69285B60-7817-4DBE-83CF-0C15ED825721}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdgeDB.QueryBuilder.StandardLibGenerator", "tools\EdgeDB.QueryBuilder.StandardLibGenerator\EdgeDB.QueryBuilder.StandardLibGenerator.csproj", "{48C73144-9A14-442D-BFCE-C010CC017972}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -91,6 +93,10 @@ Global {69285B60-7817-4DBE-83CF-0C15ED825721}.Debug|Any CPU.Build.0 = Debug|Any CPU {69285B60-7817-4DBE-83CF-0C15ED825721}.Release|Any CPU.ActiveCfg = Release|Any CPU {69285B60-7817-4DBE-83CF-0C15ED825721}.Release|Any CPU.Build.0 = Release|Any CPU + {48C73144-9A14-442D-BFCE-C010CC017972}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48C73144-9A14-442D-BFCE-C010CC017972}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48C73144-9A14-442D-BFCE-C010CC017972}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48C73144-9A14-442D-BFCE-C010CC017972}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -108,6 +114,7 @@ Global {6FA68DEA-D398-4A5B-8025-5F15C728F04C} = {49B6FB80-A675-4ECA-802C-2337A4F37566} {E38429C6-53A5-4311-8189-1F78238666DC} = {6FC214F5-C912-4D99-91B1-3E9F52A4E11B} {69285B60-7817-4DBE-83CF-0C15ED825721} = {E6B9FABC-241B-4561-9A94-E67B6BE380E2} + {48C73144-9A14-442D-BFCE-C010CC017972} = {67ED9EF0-7828-44C0-8CB0-DEBD69EC94CA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4E90C94F-D693-4411-82F3-2051DE1BE052} diff --git a/dbschema/migrations/00001.edgeql b/dbschema/migrations/00001.edgeql index 0b7626fc..0ab4501c 100644 --- a/dbschema/migrations/00001.edgeql +++ b/dbschema/migrations/00001.edgeql @@ -1,52 +1,8 @@ -CREATE MIGRATION m1fmzelzxxda652eddles3g56rysvccxzivewujttti4radwepsy5q +CREATE MIGRATION m1gajj2cjvikxjfhwnczhcfrcdlycg5pbg7vvehuaohazd2ltajlnq ONTO initial { - CREATE ABSTRACT TYPE default::AbstractThing { - CREATE REQUIRED PROPERTY name -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - }; - CREATE TYPE default::OtherThing EXTENDING default::AbstractThing { - CREATE REQUIRED PROPERTY attribute -> std::str; - }; - CREATE TYPE default::Thing EXTENDING default::AbstractThing { - CREATE REQUIRED PROPERTY description -> std::str; - }; - CREATE TYPE default::LinkPerson { - CREATE LINK best_friend -> default::LinkPerson; - CREATE REQUIRED PROPERTY email -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - CREATE REQUIRED PROPERTY name -> std::str; - }; CREATE TYPE default::Person { - CREATE REQUIRED PROPERTY email -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - CREATE REQUIRED PROPERTY name -> std::str; - }; - CREATE TYPE default::Movie { - CREATE REQUIRED MULTI LINK actors -> default::Person; - CREATE REQUIRED LINK director -> default::Person; - CREATE REQUIRED PROPERTY title -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - CREATE REQUIRED PROPERTY year -> std::int32; - }; - CREATE TYPE default::MultiLinkPerson { - CREATE MULTI LINK best_friends -> default::MultiLinkPerson; - CREATE REQUIRED PROPERTY email -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - CREATE REQUIRED PROPERTY name -> std::str; - }; - CREATE SCALAR TYPE default::State EXTENDING enum; - CREATE TYPE default::TODO { - CREATE REQUIRED PROPERTY date_created -> std::datetime { - SET default := (std::datetime_current()); - }; - CREATE REQUIRED PROPERTY description -> std::str; - CREATE REQUIRED PROPERTY state -> default::State; - CREATE REQUIRED PROPERTY title -> std::str; + CREATE PROPERTY email -> std::str; + CREATE PROPERTY name -> std::str; }; -}; +}; \ No newline at end of file diff --git a/dbschema/migrations/00002.edgeql b/dbschema/migrations/00002.edgeql index 3bbfbefb..b2128644 100644 --- a/dbschema/migrations/00002.edgeql +++ b/dbschema/migrations/00002.edgeql @@ -1,11 +1,9 @@ -CREATE MIGRATION m14rc3eoxb5cfgao72uec6ldnjpzsfen6jej74sv7bbwpatb7hmdva - ONTO m1fmzelzxxda652eddles3g56rysvccxzivewujttti4radwepsy5q +CREATE MIGRATION m1vqxxs4ie4so3x35nk2wyu3jy3hmolscpyxu6bccucaofjtnrtj3a + ONTO m1gajj2cjvikxjfhwnczhcfrcdlycg5pbg7vvehuaohazd2ltajlnq { - CREATE TYPE default::ArrayPerson { - CREATE REQUIRED PROPERTY email -> std::str { + ALTER TYPE default::Person { + ALTER PROPERTY email { CREATE CONSTRAINT std::exclusive; }; - CREATE REQUIRED PROPERTY name -> std::str; - CREATE REQUIRED PROPERTY roles -> array; }; -}; +}; \ No newline at end of file diff --git a/dbschema/migrations/00003.edgeql b/dbschema/migrations/00003.edgeql new file mode 100644 index 00000000..ad33efcd --- /dev/null +++ b/dbschema/migrations/00003.edgeql @@ -0,0 +1,20 @@ +CREATE MIGRATION m1tpouzupcrd2nkhl45qsdykw3dzjbk4ecndr73tmatxskaxkixu3q + ONTO m1vqxxs4ie4so3x35nk2wyu3jy3hmolscpyxu6bccucaofjtnrtj3a +{ + CREATE TYPE default::Movie { + CREATE REQUIRED MULTI LINK actors -> default::Person; + CREATE REQUIRED LINK director -> default::Person; + CREATE REQUIRED PROPERTY title -> std::str; + CREATE REQUIRED PROPERTY year -> std::int32; + }; + ALTER TYPE default::Person { + ALTER PROPERTY email { + SET REQUIRED USING ('e'); + }; + }; + ALTER TYPE default::Person { + ALTER PROPERTY name { + SET REQUIRED USING ('e'); + }; + }; +}; \ No newline at end of file diff --git a/dbschema/migrations/00004.edgeql b/dbschema/migrations/00004.edgeql new file mode 100644 index 00000000..d335f7fe --- /dev/null +++ b/dbschema/migrations/00004.edgeql @@ -0,0 +1,9 @@ +CREATE MIGRATION m1xp6ukrooc4chjfnhmaky4ywsoip5wvbz4ffm36tsqosspiyqjy6q + ONTO m1tpouzupcrd2nkhl45qsdykw3dzjbk4ecndr73tmatxskaxkixu3q +{ + ALTER TYPE default::Movie { + ALTER PROPERTY title { + CREATE CONSTRAINT std::exclusive; + }; + }; +}; \ No newline at end of file diff --git a/dbschema/migrations/00005.edgeql b/dbschema/migrations/00005.edgeql new file mode 100644 index 00000000..a1d75de9 --- /dev/null +++ b/dbschema/migrations/00005.edgeql @@ -0,0 +1,11 @@ +CREATE MIGRATION m1rgfzvgm77nvwkjt5i4hw3ruxtxoed4osu2hv7uj6huagohxxuu2q + ONTO m1xp6ukrooc4chjfnhmaky4ywsoip5wvbz4ffm36tsqosspiyqjy6q +{ + CREATE SCALAR TYPE default::State EXTENDING enum; + CREATE TYPE default::TODO { + CREATE REQUIRED PROPERTY date_created -> std::datetime; + CREATE REQUIRED PROPERTY description -> std::str; + CREATE REQUIRED PROPERTY state -> default::State; + CREATE REQUIRED PROPERTY title -> std::str; + }; +}; \ No newline at end of file diff --git a/dbschema/migrations/00006.edgeql b/dbschema/migrations/00006.edgeql new file mode 100644 index 00000000..f63b397b --- /dev/null +++ b/dbschema/migrations/00006.edgeql @@ -0,0 +1,9 @@ +CREATE MIGRATION m1bthirxb7a7lq7mjtrdjsxdcjcy4or3tlprzh74azy44h7n3re6zq + ONTO m1rgfzvgm77nvwkjt5i4hw3ruxtxoed4osu2hv7uj6huagohxxuu2q +{ + ALTER TYPE default::TODO { + ALTER PROPERTY date_created { + SET default := (std::datetime_current()); + }; + }; +}; \ No newline at end of file diff --git a/dbschema/migrations/00007.edgeql b/dbschema/migrations/00007.edgeql new file mode 100644 index 00000000..04883369 --- /dev/null +++ b/dbschema/migrations/00007.edgeql @@ -0,0 +1,13 @@ +CREATE MIGRATION m1dntdma2rrziv35tbt7mfl7qeg3wpzaqk73edwaw5i4kkz5fg6z6a + ONTO m1bthirxb7a7lq7mjtrdjsxdcjcy4or3tlprzh74azy44h7n3re6zq +{ + CREATE ABSTRACT TYPE default::AbstractThing { + CREATE REQUIRED PROPERTY name -> std::str; + }; + CREATE TYPE default::OtherThing EXTENDING default::AbstractThing { + CREATE REQUIRED PROPERTY attribute -> std::str; + }; + CREATE TYPE default::Thing EXTENDING default::AbstractThing { + CREATE REQUIRED PROPERTY description -> std::str; + }; +}; \ No newline at end of file diff --git a/dbschema/migrations/00008.edgeql b/dbschema/migrations/00008.edgeql new file mode 100644 index 00000000..833a6a82 --- /dev/null +++ b/dbschema/migrations/00008.edgeql @@ -0,0 +1,9 @@ +CREATE MIGRATION m1isiclyxqa32luj6hdazr4mft2mvvq4tmmuoygl7m7k2iimxm5y3a + ONTO m1dntdma2rrziv35tbt7mfl7qeg3wpzaqk73edwaw5i4kkz5fg6z6a +{ + ALTER TYPE default::AbstractThing { + ALTER PROPERTY name { + CREATE CONSTRAINT std::exclusive; + }; + }; +}; \ No newline at end of file diff --git a/dbschema/migrations/00010.edgeql b/dbschema/migrations/00010.edgeql new file mode 100644 index 00000000..8c5028c8 --- /dev/null +++ b/dbschema/migrations/00010.edgeql @@ -0,0 +1,25 @@ +CREATE MIGRATION m1um3yt7qj7ewz7tlflovxejbnkefpq2he5fwfvms2ucodqo5rupfq + ONTO m1yp62tfavybznmg6ywpi7xkbf77ue7ezaubpye7btyv2pco4okf7q +{ + CREATE TYPE default::ArrayPerson { + CREATE REQUIRED PROPERTY email -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY name -> std::str; + CREATE REQUIRED PROPERTY roles -> array; + }; + CREATE TYPE default::LinkPerson { + CREATE LINK best_friend -> default::LinkPerson; + CREATE REQUIRED PROPERTY email -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY name -> std::str; + }; + CREATE TYPE default::MultiLinkPerson { + CREATE MULTI LINK best_friends -> default::MultiLinkPerson; + CREATE REQUIRED PROPERTY email -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY name -> std::str; + }; +}; diff --git a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs index d7350578..ecc38f4e 100644 --- a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs @@ -186,7 +186,7 @@ internal async Task ExecuteInternalAsync(string query, IDictio List p = new(); - if (!CodecBuilder.TryGetCodecs(cacheKey, out var inCodec, out var outCodec)) + if (!CodecBuilder.TryGetCodecs(cacheKey, out var inCodecInfo, out var outCodecInfo)) { bool parseHandlerPredicate(IReceiveable? packet) { @@ -197,8 +197,11 @@ bool parseHandlerPredicate(IReceiveable? packet) throw new EdgeDBErrorException(err); case CommandDataDescription descriptor: { - outCodec = CodecBuilder.BuildCodec(descriptor.OutputTypeDescriptorId, descriptor.OutputTypeDescriptorBuffer); - inCodec = CodecBuilder.BuildCodec(descriptor.InputTypeDescriptorId, descriptor.InputTypeDescriptorBuffer); + outCodecInfo = new(descriptor.OutputTypeDescriptorId, + CodecBuilder.BuildCodec(descriptor.OutputTypeDescriptorId, descriptor.OutputTypeDescriptorBuffer)); + + inCodecInfo = new(descriptor.InputTypeDescriptorId, + CodecBuilder.BuildCodec(descriptor.InputTypeDescriptorId, descriptor.InputTypeDescriptorBuffer)); CodecBuilder.UpdateKeyMap(cacheKey, descriptor.InputTypeDescriptorId, descriptor.OutputTypeDescriptorId); } @@ -227,6 +230,7 @@ bool parseHandlerPredicate(IReceiveable? packet) { Capabilities = capabilities, Query = query, + ImplicitLimit = _config.ImplicitLimit, Format = format, ExpectedCardinality = cardinality ?? Cardinality.Many, ExplicitObjectIds = _config.ExplicitObjectIds, @@ -236,17 +240,18 @@ bool parseHandlerPredicate(IReceiveable? packet) ImplicitTypeIds = true, // used for type builder }, parseHandlerPredicate, alwaysReturnError: false).ConfigureAwait(false)); - if (outCodec is null) + if (outCodecInfo is null) throw new MissingCodecException("Couldn't find a valid output codec"); - if (inCodec is null) + if (inCodecInfo is null) throw new MissingCodecException("Couldn't find a valid input codec"); } - if (inCodec is not IArgumentCodec argumentCodec) - throw new MissingCodecException($"Cannot encode arguments, {inCodec} is not a registered argument codec"); + if (inCodecInfo.Codec is not IArgumentCodec argumentCodec) + throw new MissingCodecException($"Cannot encode arguments, {inCodecInfo.Codec} is not a registered argument codec"); List receivedData = new(); + CommandDataDescription d = default!; bool handler(IReceiveable msg) { @@ -255,6 +260,9 @@ bool handler(IReceiveable msg) case Data data: receivedData.Add(data); break; + case CommandDataDescription dataDescription: + d = dataDescription; + break; case ErrorResponse err when err.ErrorCode is not ServerErrorCodes.ParameterTypeMismatchError: throw new EdgeDBErrorException(err); case ReadyForCommand ready: @@ -268,6 +276,7 @@ bool handler(IReceiveable msg) var executeResult = await Duplexer.DuplexAndSyncAsync(new Execute() { Capabilities = capabilities, + ImplicitLimit = _config.ImplicitLimit, Query = query, Format = format, ExpectedCardinality = cardinality ?? Cardinality.Many, @@ -276,14 +285,16 @@ bool handler(IReceiveable msg) StateData = _stateCodec?.Serialize(serializedState), ImplicitTypeNames = true, // used for type builder ImplicitTypeIds = true, // used for type builder - Arguments = argumentCodec?.SerializeArguments(args) + Arguments = argumentCodec?.SerializeArguments(args), + InputTypeDescriptorId = inCodecInfo.Id, + OutputTypeDescriptorId = outCodecInfo.Id, }, handler, alwaysReturnError: false, token: linkedToken).ConfigureAwait(false); executeResult.ThrowIfErrrorResponse(); execResult = new ExecuteResult(true, null, null, query); - return new RawExecuteResult(outCodec!, receivedData); + return new RawExecuteResult(outCodecInfo.Codec!, receivedData); } catch (OperationCanceledException) { diff --git a/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBPropertyAttribute.cs b/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBPropertyAttribute.cs index 229b808d..63569f89 100644 --- a/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBPropertyAttribute.cs +++ b/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBPropertyAttribute.cs @@ -7,24 +7,9 @@ public class EdgeDBPropertyAttribute : Attribute { /// - /// Gets or sets whether or not this member is a link. + /// Gets or sets whether or not the property is on a link. /// - internal bool IsLink { get; set; } - - /// - /// Gets or sets whether or not this member is required. - /// - internal bool IsRequired { get; set; } - - /// - /// Gets or sets whether or not this member is a computed value. - /// - internal bool IsComputed { get; set; } - - /// - /// Gets or sets whether or not this member is read-only. - /// - internal bool IsReadOnly { get; set; } + public bool IsLinkProperty { get; set; } internal readonly string? Name; diff --git a/src/EdgeDB.Net.Driver/Serializer/CodecBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/CodecBuilder.cs index 3df9fe5c..605de7f5 100644 --- a/src/EdgeDB.Net.Driver/Serializer/CodecBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/CodecBuilder.cs @@ -10,6 +10,18 @@ namespace EdgeDB { + internal class CodecInfo + { + public Guid Id { get; } + public ICodec Codec { get; } + + public CodecInfo(Guid id, ICodec codec) + { + Id = id; + Codec = codec; + } + } + internal class CodecBuilder { public static readonly Guid NullCodec = Guid.Empty; @@ -20,18 +32,24 @@ internal class CodecBuilder public static ulong GetCacheHashKey(string query, Cardinality cardinality, IOFormat format) => unchecked(CalculateKnuthHash(query)* (ulong) cardinality * (ulong) format); - - + public static bool TryGetCodecs(ulong hash, - [MaybeNullWhen(false)] out ICodec inCodec, - [MaybeNullWhen(false)] out ICodec outCodec) + [MaybeNullWhen(false)] out CodecInfo inCodecInfo, + [MaybeNullWhen(false)] out CodecInfo outCodecInfo) { - inCodec = null; - outCodec = null; + inCodecInfo = null; + outCodecInfo = null; + + if(_codecKeyMap.TryGetValue(hash, out var codecIds) + && _codecCache.TryGetValue(codecIds.InCodec, out var inCodec) + && _codecCache.TryGetValue(codecIds.OutCodec, out var outCodec)) + { + inCodecInfo = new(codecIds.InCodec, inCodec); + outCodecInfo = new(codecIds.OutCodec, outCodec); + return true; + } - return _codecKeyMap.TryGetValue(hash, out var codecIds) - && _codecCache.TryGetValue(codecIds.InCodec, out inCodec) - && _codecCache.TryGetValue(codecIds.OutCodec, out outCodec); + return false; } public static void UpdateKeyMap(ulong hash, Guid inCodec, Guid outCodec) @@ -40,13 +58,13 @@ public static void UpdateKeyMap(ulong hash, Guid inCodec, Guid outCodec) public static ICodec? GetCodec(Guid id) => _codecCache.TryGetValue(id, out var codec) ? codec : GetScalarCodec(id); - public static ICodec? BuildCodec(Guid id, byte[] buff) + public static ICodec BuildCodec(Guid id, byte[] buff) { var reader = new PacketReader(buff.AsSpan()); return BuildCodec(id, ref reader); } - public static ICodec? BuildCodec(Guid id, ref PacketReader reader) + public static ICodec BuildCodec(Guid id, ref PacketReader reader) { if (id == NullCodec) return new NullCodec(); diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs index 16c56fda..8ecce62c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs @@ -28,7 +28,11 @@ public static string GetEdgeDBTypeName(this Type type) return attr != null ? $"{(attr.ModuleName != null ? $"{attr.ModuleName}::" : "")}{name}" : name; } public static string GetEdgeDBPropertyName(this MemberInfo info) - => info.GetCustomAttribute()?.Name ?? TypeBuilder.NamingStrategy.GetName(info); + { + var att = info.GetCustomAttribute(); + + return $"{((att?.IsLinkProperty ?? false) ? "@" : "")}{att?.Name ?? TypeBuilder.NamingStrategy.GetName(info)}"; + } public static Type GetMemberType(this MemberInfo info) { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 3b43921b..8ee05ad1 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -18,7 +18,7 @@ internal class SelectNode : QueryNode /// /// The max recursion depth for generating default shapes. /// - public const int MAX_DEPTH = 1; + public const int MAX_DEPTH = 2; /// public SelectNode(NodeBuilder builder) : base(builder) { } @@ -50,7 +50,7 @@ private string GetShape(Type type, int currentDepth = 0) } else // return just the name return name; - }).Where(x => x != null); + }).Where(x => x is not null); // join our properties by commas and wrap it in braces return $"{{ {string.Join(", ", propertyNames)} }}"; diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index bcbd146f..e3e53ad7 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -23,7 +23,7 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator disassembledInstance.Contains(x)); - var isParameterReferenceToContext = expression.Arguments.Any(x => x.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(y => y == x)); - return isParameterReferenceToContext || isInstanceReferenceToContext; + var isParameterReferenceToContext = expression.Arguments.Any(x => x.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(y => DisassembleInstance(x).Contains(y))); + var isExplicitTranslatorMethod = expression.Method.GetCustomAttribute() is not null; + var isStdLib = expression.Method.DeclaringType == typeof(EdgeQL); + return isStdLib || isExplicitTranslatorMethod || isParameterReferenceToContext || isInstanceReferenceToContext; } private IEnumerable DisassembleInstance(Expression expression) diff --git a/EdgeDB.Tests.QueryBuilder.Unit/Consts.cs b/tests/EdgeDB.Tests.QueryBuilder.Unit/Consts.cs similarity index 100% rename from EdgeDB.Tests.QueryBuilder.Unit/Consts.cs rename to tests/EdgeDB.Tests.QueryBuilder.Unit/Consts.cs diff --git a/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj b/tests/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj similarity index 75% rename from EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj rename to tests/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj index 98273a68..b876d78d 100644 --- a/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj +++ b/tests/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/EdgeDB.Tests.QueryBuilder.Unit/Selects.cs b/tests/EdgeDB.Tests.QueryBuilder.Unit/Selects.cs similarity index 100% rename from EdgeDB.Tests.QueryBuilder.Unit/Selects.cs rename to tests/EdgeDB.Tests.QueryBuilder.Unit/Selects.cs diff --git a/EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs b/tests/EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs similarity index 100% rename from EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs rename to tests/EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs diff --git a/EdgeDB.Tests.QueryBuilder.Unit/Types.cs b/tests/EdgeDB.Tests.QueryBuilder.Unit/Types.cs similarity index 100% rename from EdgeDB.Tests.QueryBuilder.Unit/Types.cs rename to tests/EdgeDB.Tests.QueryBuilder.Unit/Types.cs diff --git a/EdgeDB.Tests.QueryBuilder.Unit/Usings.cs b/tests/EdgeDB.Tests.QueryBuilder.Unit/Usings.cs similarity index 100% rename from EdgeDB.Tests.QueryBuilder.Unit/Usings.cs rename to tests/EdgeDB.Tests.QueryBuilder.Unit/Usings.cs diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs new file mode 100644 index 00000000..9f6e2237 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator +{ + internal class CodeWriter + { + public readonly StringBuilder Content = new(); + public int IndentLevel { get; private set; } + + private readonly ScopeTracker _scopeTracker; //We only need one. It can be reused. + + public CodeWriter() + { + _scopeTracker = new(this); //We only need one. It can be reused. + } + + public void Append(string line) + => Content.Append(line); + + public void AppendLine(string line) + => Content.Append(new string(' ', IndentLevel)).AppendLine(line); + + public void AppendLine() + => Content.AppendLine(); + + public IDisposable BeginScope(string line) + { + AppendLine(line); + return BeginScope(); + } + + public IDisposable BeginScope() + { + Content.Append(new string(' ', IndentLevel)).AppendLine("{"); + IndentLevel += 4; + return _scopeTracker; + } + + public void EndLine() + => Content.AppendLine(); + + public void EndScope() + { + IndentLevel -= 4; + Content.Append(new string(' ', IndentLevel)).AppendLine("}"); + } + + public void StartLine() + => Content.Append(new string(' ', IndentLevel)); + + public override string ToString() + => Content.ToString(); + + class ScopeTracker : IDisposable + { + public ScopeTracker(CodeWriter parent) + { + Parent = parent; + } + public CodeWriter Parent { get; } + + public void Dispose() + { + Parent.EndScope(); + } + } + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/EdgeDB.QueryBuilder.StandardLibGenerator.csproj b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/EdgeDB.QueryBuilder.StandardLibGenerator.csproj new file mode 100644 index 00000000..48f48ac3 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/EdgeDB.QueryBuilder.StandardLibGenerator.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs new file mode 100644 index 00000000..3bc9b9a6 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs @@ -0,0 +1,36 @@ +using EdgeDB.QueryBuilder.StandardLibGenerator.Models; +using System.Globalization; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator +{ + internal class FunctionGenerator + { + private const string OUTPUT_PATH = @"C:\Users\lynch\source\repos\EdgeDB\src\EdgeDB.Net.QueryBuilder\Translators\Methods\Generated"; + public static void Generate(IReadOnlyCollection functions) + { + if (!Directory.Exists(OUTPUT_PATH)) + Directory.CreateDirectory(OUTPUT_PATH); + + var grouped = functions.GroupBy(x => x.ReturnType.Name); + foreach(var item in grouped) + { + ProcessGroup(item.Key!, item); + } + } + + private static void ProcessGroup(string groupType, IEnumerable funcs) + { + var writer = new CodeWriter(); + + using (var namespaceScope = writer.BeginScope("namespace EdgeDB.Translators")) + using (var classScope = writer.BeginScope($"internal partial class {new CultureInfo("en-US").TextInfo.ToTitleCase(groupType)} : MethodTranslator")) + { + + foreach (var func in funcs) + { + + } + } + } + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Annotation.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Annotation.cs new file mode 100644 index 00000000..b27483a6 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Annotation.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +{ + [EdgeDBType(ModuleName = "schema")] + internal class Annotation + { + public Annotation[]? Annotations { get; set; } + public string? Name { get; set; } + public bool Internal { get; set; } + public bool Inheritable { get; set; } + [EdgeDBProperty("builtin")] + public bool BuiltIn { get; set; } + [EdgeDBProperty(IsLinkProperty = true)] + public string? Value { get; set; } + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Function.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Function.cs new file mode 100644 index 00000000..2437f676 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Function.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +{ + [EdgeDBType(ModuleName = "schema")] + internal class Function + { + [EdgeDBProperty("params")] + public Parameter[]? Parameters { get; set; } + public Type? ReturnType { get; set; } + public Annotation[]? Annotations { get; set; } + public string? Name { get; set; } + [EdgeDBProperty("builtin")] + public bool BuiltIn { get; set; } + public bool Internal { get; set; } + [EdgeDBProperty("return_typemod")] + public TypeModifier ReturnTypeModifier { get; set; } + public Volatility Volatility { get; set; } + public string[]? ComputedFields { get; set; } + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Operator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Operator.cs new file mode 100644 index 00000000..1f048b91 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Operator.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +{ + public enum OperatorKind + { + Infix, + Postfix, + Prefix, + Ternary + } + + [EdgeDBType(ModuleName = "schema")] + internal class Operator + { + [EdgeDBProperty("params")] + public Parameter[]? Parameters { get; set; } + public Type? ReturnType { get; set; } + public Annotation[]? Annotations { get; set; } + public string? Name { get; set; } + public OperatorKind OperatorKind { get; set; } + [EdgeDBProperty("builtin")] + public bool BuiltIn { get; set; } + public bool Internal { get; set; } + public bool IsAbstract { get; set; } + [EdgeDBProperty("return_typemod")] + public TypeModifier ReturnTypeModifier { get; set; } + public Volatility Volatility { get; set; } + public string[]? ComputedFields { get; set; } + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Parameter.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Parameter.cs new file mode 100644 index 00000000..47ab7400 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Parameter.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +{ + public enum ParameterKind + { + VariadicParam, + NamedOnlyParam, + PositionalParam + } + + [EdgeDBType(ModuleName = "schema")] + internal class Parameter + { + public string? Name { get; set; } + public string? Default { get; set; } + public string[]? ComputedFields { get; set; } + public ParameterKind Kind { get; set; } + public Type? Type { get; set; } + + [EdgeDBProperty("num")] + public long Index { get; set; } + + [EdgeDBProperty("typemod")] + public TypeModifier TypeModifier { get; set; } + + [EdgeDBProperty("builtin")] + public bool BuiltIn { get; set; } + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs new file mode 100644 index 00000000..fb9fa9d0 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +{ + [EdgeDBType(ModuleName = "schema")] + internal class Type + { + public string? Name { get; set; } + public bool IsAbstract { get; set; } + + [EdgeDBProperty("expr")] + public string? Expression { get; set; } + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/TypeModifier.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/TypeModifier.cs new file mode 100644 index 00000000..3715869d --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/TypeModifier.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +{ + public enum TypeModifier + { + SetOfType, + OptionalType, + SingletonType + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Volatility.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Volatility.cs new file mode 100644 index 00000000..f84cb5b5 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Volatility.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +{ + public enum Volatility + { + Immutable, + Stable, + Volatile + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/OperatorGenerator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/OperatorGenerator.cs new file mode 100644 index 00000000..2130e921 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/OperatorGenerator.cs @@ -0,0 +1,18 @@ +using EdgeDB.QueryBuilder.StandardLibGenerator.Models; +using System.Linq.Expressions; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator +{ + internal class OperatorGenerator + { + public Dictionary ExpressionMap = new Dictionary + { + + }; + + public static void GenerateOperators(IReadOnlyCollection operators) + { + + } + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs new file mode 100644 index 00000000..f9b5a0fb --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs @@ -0,0 +1,12 @@ +using EdgeDB; +using EdgeDB.QueryBuilder.StandardLibGenerator; +using EdgeDB.QueryBuilder.StandardLibGenerator.Models; + +var edgedb = new EdgeDBClient(); + +//var operators = await QueryBuilder.Select().Filter(x => !x.IsAbstract).ExecuteAsync(edgedb); +var functions = await QueryBuilder.Select().Filter(x => x.BuiltIn).ExecuteAsync(edgedb)!; + +FunctionGenerator.Generate(functions); + +await Task.Delay(-1); \ No newline at end of file diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs new file mode 100644 index 00000000..ca416491 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs @@ -0,0 +1,47 @@ +using EdgeDB.DataTypes; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryBuilder.StandardLibGenerator +{ + public class TypeUtils + { + public static Type GetType(string t) + { + return t switch + { + "std::anytype" => typeof(object), + "std::set" => typeof(IEnumerable), + "std::anytuple" => typeof(ITuple), + "std::anyenum" => typeof(Enum), + "std::Object" => typeof(object), + "std::bool" => typeof(bool), + "std::bytes" => typeof(byte[]), + "std::str" => typeof(string), + "cal::local_date" => typeof(DateOnly), + "cal::local_time" => typeof(TimeSpan), + "cal::local_datetime" => typeof(DateTime), + "cal::relative_duration" => typeof(TimeSpan), + "std::datetime" => typeof(DateTimeOffset), + "std::duration" => typeof(TimeSpan), + "std::float32" => typeof(float), + "std::float64" => typeof(double), + "std::int8" => typeof(sbyte), + "std::int16" => typeof(short), + "std::int32" => typeof(int), + "std::int64" => typeof(long), + "std::bigint" => typeof(BigInteger), + "std::decimal" => typeof(decimal), + "std::uuid" => typeof(Guid), + "std::json" => typeof(Json), + _ => throw new Exception($"Type {t} not found") + }; + } + } +} From 85c03bd38639f57f4bf40d09351152b5f3b9456d Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Fri, 29 Jul 2022 21:23:48 -0300 Subject: [PATCH 38/70] fix a few bugs --- .../Examples/QueryBuilder.cs | 10 ++++++---- .../Extensions/TypeExtensions.cs | 4 ++-- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 15 ++++++++++----- .../QueryNodes/Contexts/SelectContext.cs | 2 ++ .../QueryNodes/InsertNode.cs | 4 ++-- .../QueryNodes/QueryNode.cs | 7 ++++++- .../QueryNodes/SelectNode.cs | 12 +++++++++++- .../MethodCallExpressionTranslator.cs | 13 ++++++++++++- .../Expressions/NewExpressionTranslator.cs | 17 ++++++++--------- .../Expressions/UnaryExpressionTranslator.cs | 5 +++++ 10 files changed, 64 insertions(+), 25 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index cc0f2421..38f0148d 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -20,6 +20,7 @@ internal class QueryBuilderExample : IExample public class LinkPerson { + public Guid Id { get; set; } public string? Name { get; set; } public string? Email { get; set; } public LinkPerson? BestFriend { get; set; } @@ -27,6 +28,7 @@ public class LinkPerson public class MultiLinkPerson { + public Guid Id { get; set; } public string? Name { get; set; } public string? Email { get; set; } public MultiLinkPerson[]? BestFriends { get; set; } @@ -54,7 +56,7 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) query = QueryBuilder .Select() .Filter(x => EdgeQL.ILike(x.Name, "e%")) - .OrderBy(x => x.Name) + .OrderByDesending(x => x.Name) .Offset(2) .Limit(10) .Build() @@ -176,9 +178,9 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) } }; - query = (await QueryBuilder.For(data, + var tquery = (await QueryBuilder.For(data, x => QueryBuilder.Insert(x, false) - ).BuildAsync(client)).Prettify(); + ).BuildAsync(client)); // Else statements (upsert demo) query = (await QueryBuilder @@ -187,7 +189,7 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) .Else(q => q.Update(old => new LinkPerson { - Name = old.Name.ToLower() + Name = old!.Name!.ToLower() }) ) .BuildAsync(client)) diff --git a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs index 8ecce62c..f81388e5 100644 --- a/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs @@ -18,8 +18,8 @@ public static bool IsAnonymousType(this Type type) type.FullName!.Contains("AnonymousType"); } - public static IEnumerable GetEdgeDBTargetProperties(this Type type) - => type.GetProperties().Where(x => x.GetCustomAttribute() == null); + 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) { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 0a7dd86a..e2024d96 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -173,7 +173,7 @@ private TNode AddNode(NodeContext context, bool autoGenerated = false, Qu /// /// A which is the current query this builder has constructed. /// - internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) + internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true, Action? preFinalizerModifier = null) { List query = new(); List> parameters = new(); @@ -184,6 +184,8 @@ internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true) foreach (var node in nodes) { node.SchemaInfo ??= _schemaInfo; + if (preFinalizerModifier is not null) + preFinalizerModifier(node); node.FinalizeQuery(); } @@ -250,8 +252,8 @@ public ValueTask BuildAsync(IEdgeDBQueryable edgedb, CancellationTok => IntrospectAndBuildAsync(edgedb, token); /// - internal BuiltQuery BuildWithGlobals() - => InternalBuild(false); + internal BuiltQuery BuildWithGlobals(Action? preFinalizerModifier = null) + => InternalBuild(false, preFinalizerModifier); #region Root nodes public IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator) @@ -746,7 +748,7 @@ private async ValueTask IntrospectAndBuildAsync(IEdgeDBQueryable edg IReadOnlyCollection IQueryBuilder.Globals => _queryGlobals; IReadOnlyDictionary IQueryBuilder.Variables => _queryVariables; IQueryBuilder IQueryBuilder.With(string name, object? value) => With(name, value); - BuiltQuery IQueryBuilder.BuildWithGlobals() => BuildWithGlobals(); + BuiltQuery IQueryBuilder.BuildWithGlobals(Action? preFinalizerModifier) => BuildWithGlobals(preFinalizerModifier); #endregion } @@ -904,10 +906,13 @@ public interface IQueryBuilder /// form and exlcudes globals from the query text and puts them in /// . /// + /// + /// A modifier delegate to change nodes behaviour before the finalizer is called. + /// /// /// A which is the current query this builder has constructed. /// - internal BuiltQuery BuildWithGlobals(); + internal BuiltQuery BuildWithGlobals(Action? preFinalizerModifier = null); } /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs index 7bd97626..499f9076 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs @@ -27,6 +27,8 @@ internal class SelectContext : NodeContext /// public bool IsFreeObject { get; init; } + public bool IncludeShape { get; set; } = true; + public SelectContext(Type currentType) : base(currentType) { } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index b4884551..4c5b128f 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -263,7 +263,7 @@ private string BuildJsonShape() SetVariable(jsonValue.VariableName, new Json(JsonConvert.SerializeObject(depthMap[0].Select(x => x.Node)))); // create the base insert shape - var shape = jsonValue.InnerType.GetEdgeDBTargetProperties().Select(x => + var shape = jsonValue.InnerType.GetEdgeDBTargetProperties(excludeId: true).Select(x => { var edgedbName = x.GetEdgeDBPropertyName(); @@ -311,7 +311,7 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul return $"{{ {ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; // get all properties that aren't marked with the EdgeDBIgnore attribute - var properties = type.GetEdgeDBTargetProperties(); + var properties = type.GetEdgeDBTargetProperties(excludeId: true); foreach(var property in properties) { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index bbe239b2..276eb979 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -63,7 +63,6 @@ public bool IsAutoGenerated /// internal List SubNodes { get; } = new(); - /// /// Gets the query string for this node. /// @@ -81,6 +80,9 @@ internal NodeContext Context /// internal readonly NodeBuilder Builder; + /// + /// The operating type within the context of the query builder. + /// protected readonly Type OperatingType; /// @@ -149,6 +151,9 @@ protected string GetOrAddGlobal(object? reference, object? value) return name; } + /// + /// Gets the current operating type in the context of the query builder. + /// protected Type GetOperatingType() => Context.CurrentType.IsAssignableTo(typeof(IJsonVariable)) ? Context.CurrentType.GenericTypeArguments[0] diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 8ee05ad1..14beee63 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -80,8 +80,18 @@ private string GetShape() return $"{{ {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; } - public override void Visit() + /// + public override void Visit() { } + + /// + public override void FinalizeQuery() { + if(!Context.IncludeShape) + { + Query.Insert(0, $"select {Context.SelectName ?? OperatingType.GetEdgeDBTypeName()}"); + return; + } + // if our shape is 'new {...}' or null then parse the shape if (Context.Shape?.Body is NewExpression or MemberInitExpression || Context.Shape is null) { diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index e3e53ad7..b11f32c0 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -1,4 +1,5 @@ using EdgeDB.Operators; +using EdgeDB.QueryNodes; using System; using System.Collections.Generic; using System.Linq; @@ -191,7 +192,17 @@ private IEnumerable DisassembleInstance(Expression expression) var builder = (IQueryBuilder)Expression.Lambda(arg).Compile().DynamicInvoke()!; // build it and copy its parameters to our builder, globals shoudln't be added here - var result = builder.BuildWithGlobals(); + var result = builder.BuildWithGlobals(node => + { + // TODO: better checking on when shapes are required + switch (node) + { + case SelectNode select: + select.Context.IncludeShape = false; + break; + } + }); + if (result.Globals?.Any() ?? false) throw new NotSupportedException("Cannot use queries with parameters or globals within a sub-query expression"); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs index de24c75e..de4233f7 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs @@ -27,19 +27,18 @@ internal class NewExpressionTranslator : ExpressionTranslator var arg = expression.Arguments[i]; var edgedbName = member.GetEdgeDBPropertyName(); - // special fallthru for include - if(arg is MethodCallExpression mcex && mcex.Method.DeclaringType == typeof(QueryContext) && mcex.Method.Name == "Include") - { - shape[i] = edgedbName; - continue; - } - // translate the value and determine if were setting a value or referencing a value. - string? value = TranslateExpression(arg, context.Enter(x => x.LocalScope = expression.Type)); + var newContext = context.Enter(x => x.LocalScope = expression.Type); + string? value = TranslateExpression(arg, newContext); bool isSetter = context.NodeContext.CurrentType.GetProperty(member.Name) == null || arg is MethodCallExpression; // add it to our shape - shape[i] = $"{edgedbName}{(isSetter || context.IsFreeObject ? " :=" : "")} {value}"; + if (value is null) // include + shape[i] = edgedbName; + else if (newContext.IsShape) // includelink + shape[i] = $"{edgedbName}: {{ {value} }}"; + else + shape[i] = $"{edgedbName}{(isSetter || context.IsFreeObject ? " :=" : "")} {value}"; } // return our shape joined by commas diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs index 779bc05a..b549b14e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs @@ -30,6 +30,11 @@ internal class UnaryExpressionTranslator : ExpressionTranslator if (value is null) return null; // nullable converters for include, ex Guid? -> Guid + // 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)) + return value; + // dotnet nullable check if (ReflectionUtils.IsSubTypeOfGenericType(typeof(Nullable<>), expression.Type) && expression.Type.GenericTypeArguments[0] == expression.Operand.Type) From 3d275f3c534b39565b0c649689f7cc62c53c0e60 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Fri, 29 Jul 2022 21:26:54 -0300 Subject: [PATCH 39/70] whoops selects are pre-fixed not post-fixed :D --- src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 14beee63..a1b8d25b 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -98,14 +98,14 @@ public override void FinalizeQuery() var shape = GetShape(); if (Context.IsFreeObject) - Query.Append($"select {shape}"); + Query.Insert(0, $"select {shape}"); else - Query.Append($"select {Context.SelectName ?? OperatingType.GetEdgeDBTypeName()} {shape}"); + Query.Insert(0, $"select {Context.SelectName ?? OperatingType.GetEdgeDBTypeName()} {shape}"); } else { // else we can just translate the shape and append it. - Query.Append($"select {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals)}"); + Query.Insert(0, $"select {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals)}"); } } From 9012f23d417f10aa9fb057c5bcc39d3b8c5172c9 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Fri, 29 Jul 2022 21:30:38 -0300 Subject: [PATCH 40/70] override default select logic for delete node --- src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs index 938dd949..0faeb00d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs @@ -16,6 +16,12 @@ public DeleteNode(NodeBuilder builder) : base(builder) { } + /// + /// + /// Overrides the default method and does nothing. + /// + public override void FinalizeQuery() { } + /// public override void Visit() { From 0953b139e9d22d95c15fb02d79f9b567dd5404f3 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 31 Jul 2022 15:02:30 -0300 Subject: [PATCH 41/70] reverse type lookup --- .../Models/DataTypes/TransientTuple.cs | 1 - .../FunctionGenerator.cs | 5 +- .../TypeUtils.cs | 66 +++++++++++++++++-- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/EdgeDB.Net.Driver/Models/DataTypes/TransientTuple.cs b/src/EdgeDB.Net.Driver/Models/DataTypes/TransientTuple.cs index b484ea0d..a621f9d9 100644 --- a/src/EdgeDB.Net.Driver/Models/DataTypes/TransientTuple.cs +++ b/src/EdgeDB.Net.Driver/Models/DataTypes/TransientTuple.cs @@ -81,7 +81,6 @@ private ITuple GenerateTuple(TupleBuilder builder, int offset = 0) return builder(types, values); } return builder(_types[offset..], _values[offset..]); - } public object? this[int index] diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs index 3bc9b9a6..93ff65ca 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs @@ -28,7 +28,10 @@ private static void ProcessGroup(string groupType, IEnumerable funcs) foreach (var func in funcs) { - + if(!TypeUtils.TryGetType(func.ReturnType.Name, out var _) && !func.ReturnType.Name.Contains("any")) + { + + } } } } diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs index ca416491..6cccca66 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs @@ -2,24 +2,48 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace EdgeDB.QueryBuilder.StandardLibGenerator { public class TypeUtils { - public static Type GetType(string t) + private static Regex GenericRegex = new Regex(@"(.+?)<(.+?)>$"); + + public readonly struct TypeNode + { + public readonly string EdgeDBName; + public readonly Type? DotnetType; + public readonly bool IsGeneric; + public readonly TypeNode[] Children; + + public TypeNode(string name, Type? dotnetType, bool isGeneric, params TypeNode[] children) + { + EdgeDBName = name; + DotnetType = dotnetType; + IsGeneric = isGeneric; + Children = children; + } + + public override string ToString() + { + return $"{EdgeDBName} {DotnetType?.FullName} {IsGeneric} {string.Join(", ", Children)}"; + } + } + + public static bool TryGetType(string t, [MaybeNullWhen(false)] out TypeNode type) { - return t switch + type = default; + + var dotnetType = t switch { - "std::anytype" => typeof(object), "std::set" => typeof(IEnumerable), - "std::anytuple" => typeof(ITuple), - "std::anyenum" => typeof(Enum), "std::Object" => typeof(object), "std::bool" => typeof(bool), "std::bytes" => typeof(byte[]), @@ -40,8 +64,38 @@ public static Type GetType(string t) "std::decimal" => typeof(decimal), "std::uuid" => typeof(Guid), "std::json" => typeof(Json), - _ => throw new Exception($"Type {t} not found") + _ => null }; + + if (dotnetType is not null) + type = new(t, dotnetType, false); + else if (t.StartsWith("any")) + type = new(t, null, true); + else + { + // tuple or arry? + var match = GenericRegex.Match(t); + + if (!match.Success) + return false; + + Type? wrapperType = match.Groups[1].Value switch + { + "tuple" => typeof(ITuple), + "array" => typeof(IEnumerable<>), + "set" => typeof(IEnumerable<>), + _ => null + }; + + var innerTypes = match.Groups[2].Value.Split(", ").Select(x => TryGetType(x, out var lt) ? lt : (TypeNode?)null); + + if (wrapperType is null || innerTypes.Any(x => !x.HasValue)) + throw new Exception($"Type {t} not found"); + + type = new(t, wrapperType, false, innerTypes.Select(x => x!.Value).ToArray()); + } + + return true; } } } From baf0610385edd442cc6e7f7bd7bdb738ba826e8b Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 2 Aug 2022 16:08:00 -0300 Subject: [PATCH 42/70] fix merge errors --- .../Clients/EdgeDBBinaryClient.cs | 2 -- .../Models/Receivables/DumpBlock.cs | 4 ++-- .../Models/Receivables/DumpHeader.cs | 4 ++-- .../Models/Receivables/ErrorResponse.cs | 4 ++-- src/EdgeDB.Net.Driver/Models/Shared/KeyValue.cs | 4 ++-- .../Serializer/PacketReader.cs | 8 ++++---- .../Serializer/PacketSerializer.cs | 2 -- .../Serializer/PacketWriter.cs | 17 +---------------- 8 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs index 1f0822ce..b252bd8f 100644 --- a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs @@ -236,7 +236,6 @@ bool parseHandlerPredicate(IReceiveable? packet) ExplicitObjectIds = _config.ExplicitObjectIds, StateTypeDescriptorId = _stateDescriptorId, StateData = stateBuf, - ImplicitLimit = _config.ImplicitLimit, ImplicitTypeNames = true, // used for type builder ImplicitTypeIds = true, // used for type builder }, parseHandlerPredicate, alwaysReturnError: false).ConfigureAwait(false)); @@ -287,7 +286,6 @@ bool handler(IReceiveable msg) ImplicitTypeNames = true, // used for type builder ImplicitTypeIds = true, // used for type builder Arguments = argumentCodec?.SerializeArguments(args) , - ImplicitLimit = _config.ImplicitLimit, InputTypeDescriptorId = inCodecInfo.Id, OutputTypeDescriptorId = outCodecInfo.Id, }, handler, alwaysReturnError: false, token: linkedToken).ConfigureAwait(false); diff --git a/src/EdgeDB.Net.Driver/Models/Receivables/DumpBlock.cs b/src/EdgeDB.Net.Driver/Models/Receivables/DumpBlock.cs index fb97c910..0502cc8a 100644 --- a/src/EdgeDB.Net.Driver/Models/Receivables/DumpBlock.cs +++ b/src/EdgeDB.Net.Driver/Models/Receivables/DumpBlock.cs @@ -26,14 +26,14 @@ public IReadOnlyCollection Hash /// /// Gets a collection of attributes for this packet. /// - public IReadOnlyCollection Attributes + public IReadOnlyCollection Attributes => _attributes.ToImmutableArray(); internal byte[] Raw { get; } internal byte[] HashBuffer { get; } - private readonly Annotation[] _attributes; + private readonly KeyValue[] _attributes; internal DumpBlock(ref PacketReader reader, in int length) { diff --git a/src/EdgeDB.Net.Driver/Models/Receivables/DumpHeader.cs b/src/EdgeDB.Net.Driver/Models/Receivables/DumpHeader.cs index e60c5b4c..05ae7268 100644 --- a/src/EdgeDB.Net.Driver/Models/Receivables/DumpHeader.cs +++ b/src/EdgeDB.Net.Driver/Models/Receivables/DumpHeader.cs @@ -31,7 +31,7 @@ public IReadOnlyCollection Hash /// /// Gets a collection of attributes sent with this packet. /// - public IReadOnlyCollection Attributes + public IReadOnlyCollection Attributes => _attributes.ToImmutableArray(); /// @@ -62,7 +62,7 @@ public IReadOnlyCollection Attributes internal byte[] Raw { get; } internal byte[] RawHash { get; } - private readonly Annotation[] _attributes; + private readonly KeyValue[] _attributes; internal DumpHeader(ref PacketReader reader, in int length) { diff --git a/src/EdgeDB.Net.Driver/Models/Receivables/ErrorResponse.cs b/src/EdgeDB.Net.Driver/Models/Receivables/ErrorResponse.cs index 76e26f39..29e0a309 100644 --- a/src/EdgeDB.Net.Driver/Models/Receivables/ErrorResponse.cs +++ b/src/EdgeDB.Net.Driver/Models/Receivables/ErrorResponse.cs @@ -35,10 +35,10 @@ public ServerMessageType Type /// /// Gets a collection of attributes sent with this error. /// - public IReadOnlyCollection Attributes + public IReadOnlyCollection Attributes => _attributes.ToImmutableArray(); - private readonly Annotation[] _attributes; + private readonly KeyValue[] _attributes; internal ErrorResponse(ref PacketReader reader) { diff --git a/src/EdgeDB.Net.Driver/Models/Shared/KeyValue.cs b/src/EdgeDB.Net.Driver/Models/Shared/KeyValue.cs index 3f40cc96..041d3f28 100644 --- a/src/EdgeDB.Net.Driver/Models/Shared/KeyValue.cs +++ b/src/EdgeDB.Net.Driver/Models/Shared/KeyValue.cs @@ -9,7 +9,7 @@ namespace EdgeDB.Binary /// /// Represents a dynamic header received in a . /// - public readonly struct Annotation + public readonly struct KeyValue { /// /// Gets the key code. @@ -21,7 +21,7 @@ public readonly struct Annotation /// public byte[] Value { get; init; } - internal Annotation(ushort code, byte[] value) + internal KeyValue(ushort code, byte[] value) { Code = code; Value = value; diff --git a/src/EdgeDB.Net.Driver/Serializer/PacketReader.cs b/src/EdgeDB.Net.Driver/Serializer/PacketReader.cs index 1dec6bfe..58a37857 100644 --- a/src/EdgeDB.Net.Driver/Serializer/PacketReader.cs +++ b/src/EdgeDB.Net.Driver/Serializer/PacketReader.cs @@ -69,11 +69,11 @@ public double ReadDouble() return value; } - public Annotation[] ReadKeyValues() + public KeyValue[] ReadKeyValues() { var length = ReadUInt16(); - Annotation[] arr = new Annotation[length]; + KeyValue[] arr = new KeyValue[length]; for (ushort i = 0; i != length; i++) { @@ -83,12 +83,12 @@ public Annotation[] ReadKeyValues() return arr; } - public Annotation ReadKeyValue() + public KeyValue ReadKeyValue() { var code = ReadUInt16(); var value = ReadByteArray(); - return new Annotation(code, value); + return new KeyValue(code, value); } public float ReadSingle() diff --git a/src/EdgeDB.Net.Driver/Serializer/PacketSerializer.cs b/src/EdgeDB.Net.Driver/Serializer/PacketSerializer.cs index e4ac72eb..eb385e3e 100644 --- a/src/EdgeDB.Net.Driver/Serializer/PacketSerializer.cs +++ b/src/EdgeDB.Net.Driver/Serializer/PacketSerializer.cs @@ -50,8 +50,6 @@ internal class PacketSerializer return new LogMessage(ref reader); case ServerMessageType.ParameterStatus: return new ParameterStatus(ref reader); - case ServerMessageType.ParseComplete: - return new ParseComplete(ref reader); case ServerMessageType.ReadyForCommand: return new ReadyForCommand(ref reader); case ServerMessageType.RestoreReady: diff --git a/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs b/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs index af2ed42f..d1ca673d 100644 --- a/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs +++ b/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs @@ -38,21 +38,6 @@ public byte[] GetBytes() return ms.ToArray(); } - public void Write(IEnumerable? headers) - { - // write length - Write((ushort)(headers?.Count() ?? 0)); - - if(headers is not null) - { - foreach (var header in headers) - { - Write(header.Code); - WriteArray(header.Value); - } - } - } - public void Write(IEnumerable? headers) { // write length @@ -74,7 +59,7 @@ public void Write(PacketWriter value, int offset = 0) value.BaseStream.CopyTo(base.BaseStream); } - public void Write(Annotation header) + public void Write(KeyValue header) { Write(header.Code); Write(header.Value); From 0f90b9becc97dc5d54098f18ed37d9e0a813b0f1 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 2 Aug 2022 16:11:52 -0300 Subject: [PATCH 43/70] fix annotation writer --- src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs b/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs index ea7ddd2a..d4d6302c 100644 --- a/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs +++ b/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs @@ -38,14 +38,14 @@ public byte[] GetBytes() return ms.ToArray(); } - public void Write(IEnumerable? headers) + public void Write(IEnumerable? annotations) { // write length - Write((ushort)(headers?.Count() ?? 0)); + Write((ushort)(annotations?.Count() ?? 0)); - if (headers is not null) + if (annotations is not null) { - foreach (var header in headers) + foreach (var header in annotations) { Write(header.Name); Write(header.Value); From 63a8f9cea9220340ae5f7e47b9acf51e1360bbde Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 3 Aug 2022 15:26:16 -0300 Subject: [PATCH 44/70] add finalizer delegate summary --- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index e2024d96..a1454b60 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -170,6 +170,7 @@ private TNode AddNode(NodeContext context, bool autoGenerated = false, Qu /// /// Whether or not to include globals in the query string. /// + /// A delegate to finalize each node within the query. /// /// A which is the current query this builder has constructed. /// From 36c0a305ccfa1a5c3839d8fa8c5ee20717fbb87e Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 3 Aug 2022 19:16:53 -0300 Subject: [PATCH 45/70] More work on STDLIB generator --- .../Models/DataTypes/Range.cs | 9 +- .../CodeWriter.cs | 2 +- .../FunctionGenerator.cs | 182 ++++++++++++++++-- .../Models/Annotation.cs | 2 +- .../Models/Function.cs | 2 +- .../Models/Operator.cs | 2 +- .../Models/Parameter.cs | 2 +- .../Models/Type.cs | 45 ++++- .../Models/TypeModifier.cs | 2 +- .../Models/Volatility.cs | 2 +- .../OperatorGenerator.cs | 4 +- .../Program.cs | 23 ++- .../TypeUtils.cs | 87 ++++++--- 13 files changed, 313 insertions(+), 51 deletions(-) diff --git a/src/EdgeDB.Net.Driver/Models/DataTypes/Range.cs b/src/EdgeDB.Net.Driver/Models/DataTypes/Range.cs index 3c2781cb..64f8c1c4 100644 --- a/src/EdgeDB.Net.Driver/Models/DataTypes/Range.cs +++ b/src/EdgeDB.Net.Driver/Models/DataTypes/Range.cs @@ -10,7 +10,7 @@ namespace EdgeDB.DataTypes /// Represents the Range type in EdgeDB. /// /// The inner type of the range. - public struct Range + public struct Range : IRange where T : struct { /// @@ -60,5 +60,12 @@ public Range(T? lower, T? upper, bool includeLower = true, bool includeUpper = f /// An empty range. public static Range Empty() => new(null, null); + + Type IRange.WrappingType => typeof(T); + } + + internal interface IRange + { + Type WrappingType { get; } } } diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs index 9f6e2237..9cdfdb7a 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace EdgeDB.QueryBuilder.StandardLibGenerator +namespace EdgeDB.StandardLibGenerator { internal class CodeWriter { diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs index 93ff65ca..e269a348 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs @@ -1,39 +1,199 @@ -using EdgeDB.QueryBuilder.StandardLibGenerator.Models; +using EdgeDB.DataTypes; +using EdgeDB.StandardLibGenerator.Models; +using System.Collections; using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; -namespace EdgeDB.QueryBuilder.StandardLibGenerator +namespace EdgeDB.StandardLibGenerator { internal class FunctionGenerator { private const string OUTPUT_PATH = @"C:\Users\lynch\source\repos\EdgeDB\src\EdgeDB.Net.QueryBuilder\Translators\Methods\Generated"; - public static void Generate(IReadOnlyCollection functions) + private static readonly TextInfo _textInfo = new CultureInfo("en-US").TextInfo; + private static readonly List _generatedTypes = new(); + private static readonly Regex _groupRegex = new(@"(.+?)<.+?>"); + private static CodeWriter? _edgeqlClassWriter; + private static EdgeDBClient? _client; + + public static async ValueTask GenerateAsync(CodeWriter eqlWriter, EdgeDBClient client, IReadOnlyCollection functions) { + _client = client; + _edgeqlClassWriter = eqlWriter; if (!Directory.Exists(OUTPUT_PATH)) Directory.CreateDirectory(OUTPUT_PATH); - var grouped = functions.GroupBy(x => x.ReturnType.Name); - foreach(var item in grouped) + try { - ProcessGroup(item.Key!, item); + var grouped = functions.GroupBy(x => + { + var m = _groupRegex.Match(x.ReturnType!.Name!); + return m.Success ? m.Groups[1].Value : x.ReturnType.Name; + + }); + foreach (var item in grouped) + { + await ProcessGroup(item.Key!, item); + } + } + catch(Exception x) + { + } } - private static void ProcessGroup(string groupType, IEnumerable funcs) + private static async ValueTask ProcessGroup(string groupType, IEnumerable funcs) { var writer = new CodeWriter(); - using (var namespaceScope = writer.BeginScope("namespace EdgeDB.Translators")) - using (var classScope = writer.BeginScope($"internal partial class {new CultureInfo("en-US").TextInfo.ToTitleCase(groupType)} : MethodTranslator")) + var edgedbType = funcs.FirstOrDefault(x => x.ReturnType!.Name! == groupType)?.ReturnType!; + var translatorType = TypeUtils.TryGetType(groupType, out var tInfo) ? BuildType(tInfo, edgedbType, TypeModifier.SingletonType, true) : groupType switch { + "tuple" => typeof(ITuple).Name, + "array" => typeof(Array).Name, + "set" => typeof(IEnumerable).Name, + "range" => "IRange", + _ => groupType.Contains("::") ? BuildType(new(groupType, null), edgedbType, TypeModifier.SingletonType, true) : throw new Exception($"Failed to find matching type for {groupType}") + }; + + writer.AppendLine("using EdgeDB;"); + writer.AppendLine("using EdgeDB.DataTypes;"); + writer.AppendLine("using System.Runtime.CompilerServices;"); + writer.AppendLine(); + using (var namespaceScope = writer.BeginScope("namespace EdgeDB.Translators")) + using (var classScope = writer.BeginScope($"internal partial class {_textInfo.ToTitleCase(groupType.Replace("::", " ")).Replace(" ", "")} : MethodTranslator<{translatorType}>")) + { foreach (var func in funcs) { - if(!TypeUtils.TryGetType(func.ReturnType.Name, out var _) && !func.ReturnType.Name.Contains("any")) + try + { + var funcName = _textInfo.ToTitleCase(func.Name!.Split("::")[1].Replace("_", " ")).Replace(" ", ""); + + if (!TypeUtils.TryGetType(func.ReturnType!.Name!, out var returnTypeInfo)) + throw new Exception($"Faield to get type {groupType}"); + + var dotnetReturnType = BuildType(returnTypeInfo, func.ReturnType, TypeModifier.SingletonType); + + switch (func.ReturnTypeModifier) + { + case TypeModifier.OptionalType: + dotnetReturnType += "?"; + break; + case TypeModifier.SetOfType: + dotnetReturnType = $"IEnumerable<{dotnetReturnType}>"; + break; + default: + break; + } + + var parameters = func.Parameters!.Select(x => + { + if (!TypeUtils.TryGetType(x.Type!.Name!, out var info)) + return null; + + return (x, info); + }); + + if (parameters.Any(x => !x.HasValue)) + throw new Exception("No parameter matches found"); + + string[] parsedParameters = new string[parameters.Count()]; + + for(int i = 0; i != parsedParameters.Length; i++) + { + var x = parameters.ElementAt(i); + var type = BuildType(x!.Value.Node, x.Value.Parameter!.Type!, x.Value.Parameter.TypeModifier, true); + var name = x.Value.Parameter.Name; + string @default = ""; + if (x.Value.Parameter.Default is not null) + @default = x.Value.Parameter.Default == "{}" ? "null" : await ParseDefaultAsync(x.Value.Parameter.Default); + + parsedParameters[i] = $"{type} {name}{(!string.IsNullOrEmpty(@default) ? $" = {@default}" : "")}"; + } + + var strongMappedParameters = string.Join(", ", parsedParameters); + var parsedMappedParameters = string.Join(", ", parameters.Select(x => $"string? {x!.Value.Parameter.Name}Param")); + + writer.AppendLine($"[MethodName(EdgeQL.{funcName})]"); + writer.AppendLine($"public string {funcName}({parsedMappedParameters})"); + + using(var methodScope = writer.BeginScope()) + { + var methodBody = $"return $\"{func.Name}("; + + string[] parsedParams = new string[func.Parameters.Length]; + + for(int i = 0; i != parsedParams.Length; i++) + { + var param = func.Parameters[i]; + + var value = ""; + if (param.TypeModifier != TypeModifier.OptionalType) + value = $"{{{param.Name}Param}}"; + else + value = $"{{({param.Name}Param is not null ? \"{param.Name}Param, \" : \"\")}}"; + + if (param.Kind == ParameterKind.NamedOnlyParam) + value = $"{param.Name} := {{{param.Name}Param}}"; + + parsedParams[i] = value; + } + + methodBody += string.Join(", ", parsedParams) + ")\";"; + + writer.AppendLine(methodBody); + } + writer.AppendLine(); + + _edgeqlClassWriter!.AppendLine($"public static {dotnetReturnType} {funcName}({strongMappedParameters})"); + _edgeqlClassWriter.AppendLine(" => default!"); + } + catch(Exception x) { - + Console.WriteLine(x); } } } + + try + { + File.WriteAllText(Path.Combine(OUTPUT_PATH, $"{_textInfo.ToTitleCase(groupType).Replace(":", "")}.g.cs"), writer.ToString()); + } + catch(Exception x) + { + + } + } + + private static string BuildType(TypeNode node, EdgeDB.QueryBuilder.StandardLibGenerator.Models.Type edgedbType, TypeModifier modifier, bool shouldGenerate = true) + { + var name = node.IsGeneric + ? "object" + : node.DotnetType is null && node.RequiresGeneration && shouldGenerate + ? GenerateType(node, edgedbType) + : node.DotnetType?.Name ?? "object"; + + return modifier switch + { + TypeModifier.OptionalType => $"{name}?", + TypeModifier.SingletonType => name, + TypeModifier.SetOfType => $"IEnumerable<{name}>", + _ => name + }; + } + + private static string GenerateType(TypeNode node, EdgeDB.QueryBuilder.StandardLibGenerator.Models.Type edgedbType) + { + return ""; + } + + + private static readonly Regex _typeCastOne = new(@"(<[^<]*?>)"); + private static async Task ParseDefaultAsync(string @default) + { + var result = await _client!.QuerySingleAsync($"select {@default}"); + return result?.ToString() ?? "null"; } } } diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Annotation.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Annotation.cs index b27483a6..d06d900f 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Annotation.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Annotation.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +namespace EdgeDB.StandardLibGenerator.Models { [EdgeDBType(ModuleName = "schema")] internal class Annotation diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Function.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Function.cs index 2437f676..c8016468 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Function.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Function.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +namespace EdgeDB.StandardLibGenerator.Models { [EdgeDBType(ModuleName = "schema")] internal class Function diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Operator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Operator.cs index 1f048b91..34a8794c 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Operator.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Operator.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +namespace EdgeDB.StandardLibGenerator.Models { public enum OperatorKind { diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Parameter.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Parameter.cs index 47ab7400..cce3c1d5 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Parameter.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Parameter.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +namespace EdgeDB.StandardLibGenerator.Models { public enum ParameterKind { diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs index fb9fa9d0..dbb1887e 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs @@ -4,15 +4,50 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +namespace EdgeDB.StandardLibGenerator.Models { [EdgeDBType(ModuleName = "schema")] internal class Type { - public string? Name { get; set; } + public string Name { get; set; } public bool IsAbstract { get; set; } - - [EdgeDBProperty("expr")] - public string? Expression { get; set; } + + [EdgeDBIgnore] + public string TypeOfSelf { get; set; } + + [EdgeDBIgnore] + public Guid Id { get; set; } + + [EdgeDBDeserializer] + public Type(IDictionary raw) + { + Name = (string)raw["name"]!; + IsAbstract = (bool)raw["is_abstract"]!; + TypeOfSelf = (string)raw["__tname__"]!; + Id = (Guid)raw["id"]!; + } + + public async Task GetMetaInfoAsync(EdgeDBClient client) + { + var result = await QueryBuilder.Select((ctx) => new MetaType + { + Pointers = ctx.Raw("[is schema::ObjectType].pointers { name, type: {name, is_abstract}}"), + }).Filter(x => x.Id == Id).ExecuteAsync(client); + + return result.First()!; + } + } + + [EdgeDBType("Type", ModuleName = "schema")] + internal class MetaType + { + public Guid Id { get; set; } + public Pointer[]? Pointers { get; set; } + } + + internal class Pointer + { + public string? Name { get; set; } + public Type? Type { get; set; } } } diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/TypeModifier.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/TypeModifier.cs index 3715869d..67f4eb49 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/TypeModifier.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/TypeModifier.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +namespace EdgeDB.StandardLibGenerator.Models { public enum TypeModifier { diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Volatility.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Volatility.cs index f84cb5b5..106ca804 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Volatility.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Volatility.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB.QueryBuilder.StandardLibGenerator.Models +namespace EdgeDB.StandardLibGenerator.Models { public enum Volatility { diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/OperatorGenerator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/OperatorGenerator.cs index 2130e921..b83a37a5 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/OperatorGenerator.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/OperatorGenerator.cs @@ -1,7 +1,7 @@ -using EdgeDB.QueryBuilder.StandardLibGenerator.Models; +using EdgeDB.StandardLibGenerator.Models; using System.Linq.Expressions; -namespace EdgeDB.QueryBuilder.StandardLibGenerator +namespace EdgeDB.StandardLibGenerator { internal class OperatorGenerator { diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs index f9b5a0fb..ce8025b3 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs @@ -1,12 +1,29 @@ using EdgeDB; -using EdgeDB.QueryBuilder.StandardLibGenerator; -using EdgeDB.QueryBuilder.StandardLibGenerator.Models; +using EdgeDB.StandardLibGenerator; +using EdgeDB.StandardLibGenerator.Models; var edgedb = new EdgeDBClient(); //var operators = await QueryBuilder.Select().Filter(x => !x.IsAbstract).ExecuteAsync(edgedb); var functions = await QueryBuilder.Select().Filter(x => x.BuiltIn).ExecuteAsync(edgedb)!; -FunctionGenerator.Generate(functions); +var writer = new CodeWriter(); + +writer.AppendLine("#nullable restore"); +writer.AppendLine("#pragma warning disable"); +writer.AppendLine("using EdgeDB.Operators;"); +writer.AppendLine("using EdgeDB.DataTypes;"); +writer.AppendLine("using System.Numerics;"); +writer.AppendLine(); + +using (var _ = writer.BeginScope("namespace EdgeDB")) +{ + using (var __ = writer.BeginScope("public sealed partial class EdgeQL")) + { + await FunctionGenerator.GenerateAsync(writer, edgedb, functions!); + } +} + +File.WriteAllText(Path.Combine(@"C:\Users\lynch\source\repos\EdgeDB\src\EdgeDB.Net.QueryBuilder", "EdgeQL.test.g.cs"), writer.ToString()); await Task.Delay(-1); \ No newline at end of file diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs index 6cccca66..c2d045d2 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs @@ -10,33 +10,62 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace EdgeDB.QueryBuilder.StandardLibGenerator +namespace EdgeDB.StandardLibGenerator { - public class TypeUtils + public readonly struct TypeNode { - private static Regex GenericRegex = new Regex(@"(.+?)<(.+?)>$"); + public readonly string EdgeDBName; + public readonly Type? DotnetType; + public readonly bool IsGeneric; + public readonly TypeNode[] Children; - public readonly struct TypeNode - { - public readonly string EdgeDBName; - public readonly Type? DotnetType; - public readonly bool IsGeneric; - public readonly TypeNode[] Children; + public readonly string? TupleElementName; + public readonly bool IsChildOfNamedTuple; - public TypeNode(string name, Type? dotnetType, bool isGeneric, params TypeNode[] children) - { - EdgeDBName = name; - DotnetType = dotnetType; - IsGeneric = isGeneric; - Children = children; - } + public readonly bool RequiresGeneration; - public override string ToString() - { - return $"{EdgeDBName} {DotnetType?.FullName} {IsGeneric} {string.Join(", ", Children)}"; - } + public TypeNode(string name, Type? dotnetType, bool isGeneric, params TypeNode[] children) + { + EdgeDBName = name; + DotnetType = dotnetType; + IsGeneric = isGeneric; + Children = children; + IsChildOfNamedTuple = false; + TupleElementName = null; + RequiresGeneration = false; + } + public TypeNode(string name, Type? dotnetType, string tupleName, bool isGeneric, params TypeNode[] children) + { + EdgeDBName = name; + DotnetType = dotnetType; + IsGeneric = isGeneric; + Children = children; + IsChildOfNamedTuple = true; + TupleElementName = tupleName; + RequiresGeneration = false; + } + public TypeNode(string name, string? tupleName) + { + EdgeDBName = name; + RequiresGeneration = true; + DotnetType = null; + IsGeneric = false; + Children = Array.Empty(); + IsChildOfNamedTuple = tupleName is not null; + TupleElementName = tupleName; } + public override string ToString() + { + return $"{EdgeDBName} {DotnetType?.FullName} {IsGeneric} {string.Join(", ", Children)}"; + } + } + + public class TypeUtils + { + private static readonly Regex GenericRegex = new(@"(.+?)<(.+?)>$"); + private static readonly Regex NamedTupleRegex = new(@"(.*?[^:]):([^:].*?)$"); + public static bool TryGetType(string t, [MaybeNullWhen(false)] out TypeNode type) { type = default; @@ -52,6 +81,7 @@ public static bool TryGetType(string t, [MaybeNullWhen(false)] out TypeNode type "cal::local_time" => typeof(TimeSpan), "cal::local_datetime" => typeof(DateTime), "cal::relative_duration" => typeof(TimeSpan), + "cal::date_duration" => typeof(TimeSpan), "std::datetime" => typeof(DateTimeOffset), "std::duration" => typeof(TimeSpan), "std::float32" => typeof(float), @@ -64,12 +94,13 @@ public static bool TryGetType(string t, [MaybeNullWhen(false)] out TypeNode type "std::decimal" => typeof(decimal), "std::uuid" => typeof(Guid), "std::json" => typeof(Json), + "schema::ScalarType" => typeof(Type), _ => null }; if (dotnetType is not null) type = new(t, dotnetType, false); - else if (t.StartsWith("any")) + else if (t.StartsWith("any") || t.StartsWith("std::any")) type = new(t, null, true); else { @@ -84,10 +115,22 @@ public static bool TryGetType(string t, [MaybeNullWhen(false)] out TypeNode type "tuple" => typeof(ITuple), "array" => typeof(IEnumerable<>), "set" => typeof(IEnumerable<>), + "range" => typeof(Range<>), _ => null }; - var innerTypes = match.Groups[2].Value.Split(", ").Select(x => TryGetType(x, out var lt) ? lt : (TypeNode?)null); + var innerTypes = match.Groups[2].Value.Split(", ").Select(x => + { + var t = x.Replace("|", "::"); + var m = NamedTupleRegex.Match(t); + if (!m.Success) + return TryGetType(t, out var lt) ? lt : (TypeNode?)null; + + if (!TryGetType(m.Groups[2].Value, out var type)) + return new(m.Groups[2].Value, m.Groups[1].Value); + + return new TypeNode(m.Groups[2].Value, type.DotnetType, m.Groups[1].Value, type.IsGeneric, type.Children); + }); if (wrapperType is null || innerTypes.Any(x => !x.HasValue)) throw new Exception($"Type {t} not found"); From 082610ed278053fe7effd0a9bdc859dee1233e37 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Fri, 5 Aug 2022 18:52:00 -0300 Subject: [PATCH 46/70] Fix member initialization translators --- .../Examples/QueryBuilder.cs | 11 ++ .../EdgeDB.Net.QueryBuilder.csproj | 8 + src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 4 +- .../QueryNodes/Contexts/NodeContext.cs | 8 + .../QueryNodes/Contexts/UpdateContext.cs | 5 - .../QueryNodes/QueryNode.cs | 12 -- .../Expressions/ExpressionContext.cs | 6 + .../Expressions/InitializationTranslator.cs | 114 ++++++++++++++ .../MemberInitExpressionTranslator.cs | 141 +----------------- .../MethodCallExpressionTranslator.cs | 21 +-- .../Expressions/NewExpressionTranslator.cs | 31 +--- .../Utils/QueryUtils.cs | 21 ++- .../FunctionGenerator.cs | 31 +++- .../Models/Type.cs | 24 ++- 14 files changed, 227 insertions(+), 210 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 38f0148d..b1de2e49 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -49,6 +49,17 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { + var test = (await QueryBuilder.Insert((ctx) => new LinkPerson() + { + Name = "test1", + Email = "test1@mail.com", + BestFriend = new LinkPerson() + { + Email = "test2@mail.com", + Name = "test2" + } + }).BuildAsync(client)).Prettify(); + // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj index 73437cb5..bd5db833 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj +++ b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj @@ -11,7 +11,15 @@ True 5 + + + + + + + + diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index a1454b60..74317489 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -92,7 +92,7 @@ static QueryBuilder() /// /// Constructs an empty query builder. /// - internal QueryBuilder() + public QueryBuilder() { _nodes = new(); _queryGlobals = new(); @@ -718,7 +718,7 @@ IDeleteQuery IDeleteQuery.Limit(Expression private async ValueTask IntrospectAndBuildAsync(IEdgeDBQueryable edgedb, CancellationToken token) { - if(_nodes.Any(x => x.RequiresIntrospection)) + 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 result = Build(); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs index d064e4c3..d271e618 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs @@ -28,9 +28,17 @@ internal abstract class NodeContext /// public Type CurrentType { get; init; } + /// + /// Gets whether or not the current type is a json variable. + /// public bool IsJsonVariable => ReflectionUtils.IsSubTypeOfGenericType(typeof(JsonVariable<>), CurrentType); + /// + /// Gets a collection of child queries. + /// + internal Dictionary ChildQueries { get; } = new(); + /// /// Constructs a new . /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs index 9863cc46..b60c11c2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs @@ -17,11 +17,6 @@ internal class UpdateContext : NodeContext /// public LambdaExpression? UpdateExpression { get; init; } - /// - /// Gets a collection of child queries. - /// - internal Dictionary ChildQueries { get; } = new(); - /// public UpdateContext(Type currentType) : base(currentType) { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index 276eb979..a4e6b70f 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -48,16 +48,6 @@ public bool IsAutoGenerated /// public SchemaInfo? SchemaInfo { get; set; } - /// - /// Gets a collection of referenced globals set by this node. - /// - internal List ReferencedGlobals { get; } = new(); - - /// - /// Gets a collection of referenced variables set by this node. - /// - internal List ReferencedVariables { get; } = new(); - /// /// Gets a collection of child nodes. /// @@ -118,7 +108,6 @@ public virtual void FinalizeQuery() { } /// The value of the variable to set. protected void SetVariable(string name, object? value) { - ReferencedVariables.Add(name); Builder.QueryVariables[name] = value; } @@ -132,7 +121,6 @@ protected void SetGlobal(string name, object? value, object? reference) { var global = new QueryGlobal(name, value, reference); Builder.QueryGlobals.Add(global); - ReferencedGlobals.Add(Builder.QueryGlobals.IndexOf(global)); } /// diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs index 078c1131..27959691 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -155,6 +155,12 @@ public void SetGlobal(string name, object? value, object? reference) Globals.Add(global); } + public void AddChildQuery(SubQuery query) + { + var name = QueryUtils.GenerateRandomVariableName(); + NodeContext.ChildQueries.Add(name, query); + } + /// /// Enters a new context with the given modification delegate. /// diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs new file mode 100644 index 00000000..e504af44 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs @@ -0,0 +1,114 @@ +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 +{ + internal static class InitializationTranslator + { + public static string? Translate(IDictionary expressions, ExpressionContext context) + { + List initializations = new(); + + foreach(var (Member, Expression) in expressions) + { + // get the members type and edgedb equivalent name + var memberType = Member.GetMemberType(); + var memberName = Member.GetEdgeDBPropertyName(); + var isLink = QueryUtils.IsLink(memberType, out var isMultiLink, out var innerType); + + switch (Expression) + { + case MemberExpression when isLink: + { + var disassembled = QueryUtils.DisassembleExpression(Expression).ToArray(); + if(disassembled.Last() is ConstantExpression constant && disassembled[disassembled.Length - 2] is MemberExpression constParent) + { + // get the value + var memberValue = constParent.Member.GetMemberValue(constant.Value); + + // check if its a global value we've alreay got a query for + if (context.TryGetGlobal(memberValue, out var global)) + { + initializations.Add($"{memberName} := {global.Name}"); + break; + } + + // check if its a value returned in a previous query + if (QueryObjectManager.TryGetObjectId(memberValue, out var id)) + { + var globalName = context.GetOrAddGlobal(id, id.SelectSubQuery(memberType)); + initializations.Add($"{memberName} := {globalName}"); + break; + } + + // generate an insert or select based on its unique constraints. + var name = QueryUtils.GenerateRandomVariableName(); + context.SetGlobal(name, new SubQuery((info) => + { + // generate an insert shape + var insertShape = ExpressionTranslator.ContextualTranslate(QueryUtils.GenerateInsertShapeExpression(memberValue, memberType), context); + + var exclusiveProps = QueryUtils.GetProperties(info, memberType, true); + var exclusiveCondition = exclusiveProps.Any() ? + $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {memberType.GetEdgeDBTypeName()})" + : string.Empty; + + return $"(insert {memberType.GetEdgeDBTypeName()} {{ {insertShape} }}{exclusiveCondition})"; + }), null); + initializations.Add($"{memberName} := {name}"); + } + } + break; + case MemberInitExpression or NewExpression: + { + var name = QueryUtils.GenerateRandomVariableName(); + var expression = Expression; + context.SetGlobal(name, new SubQuery((info) => + { + // generate an insert shape + var insertShape = ExpressionTranslator.ContextualTranslate(expression, context); + + var exclusiveProps = QueryUtils.GetProperties(info, memberType, true); + var exclusiveCondition = exclusiveProps.Any() ? + $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {memberType.GetEdgeDBTypeName()})" + : string.Empty; + + return $"(insert {memberType.GetEdgeDBTypeName()} {{ {insertShape} }}{exclusiveCondition})"; + }), null); + initializations.Add($"{memberName} := {name}"); + } + break; + default: + { + // translate the value and determine if were setting a value or referencing a value. + var newContext = context.Enter(x => + { + x.LocalScope = memberType; + x.IsShape = false; + }); + string? value = ExpressionTranslator.ContextualTranslate(Expression, newContext); + bool isSetter = context.NodeContext is InsertContext || context.NodeContext.CurrentType.GetProperty(Member.Name) == null || Expression is MethodCallExpression; + + // add it to our shape + if (value is null) // include + initializations.Add(memberName); + else if (newContext.IsShape) // includelink + initializations.Add($"{memberName}: {{ {value} }}"); + else + initializations.Add($"{memberName}{(isSetter || context.IsFreeObject ? " :=" : "")} {value}"); + } + break; + } + } + + context.IsShape = true; + return string.Join(", ", initializations); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs index c426be5f..f3559c65 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs @@ -19,143 +19,16 @@ internal class MemberInitExpressionTranslator : ExpressionTranslator public override string? Translate(MemberInitExpression expression, ExpressionContext context) { - List initializations = new(); + List<(MemberInfo, Expression)> expressions = new(); - foreach(var binding in expression.Bindings) - { - switch (binding) - { - // member assignment ex: 'Property = value' - case MemberAssignment assignment: - { - // get the members type and edgedb equivalent name - var memberType = assignment.Member.GetMemberType(); - var memberName = assignment.Member.GetEdgeDBPropertyName(); + 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])); - // if its a link property - if (QueryUtils.IsLink(memberType, out var isArray, out var innerType)) - { - if (isArray) - memberType = innerType!; + foreach (var binding in expression.Bindings.Where(x => x is MemberAssignment).Cast()) + expressions.Add((binding.Member, binding.Expression)); - // Check if we're setting a link property here, if we're in a select node and its a shape def - // we should let it parse. If not what should happen is globals should be - // defined for this object and the members value with an update for the current contextual object. - // to do this we need to add some callback for the context to tell the query builder our intent. - if (context.NodeContext is SelectContext && - assignment.Expression is MethodCallExpression mcx && - mcx.Method.DeclaringType == typeof(QueryContext)) - { - initializations.Add($"{memberName}: {{ {TranslateExpression(assignment.Expression, context)} }}"); - break; - } - - // get the name of the type. - var typeName = memberType.GetEdgeDBTypeName(); - - if (context.NodeContext is not UpdateContext updateContext) - throw new NotSupportedException("Cannot set links with the current node"); - - SubQuery? subQuery = null; - bool isSubQuery = true; - - // figure out what type of sub query we're going to make for this link property - switch (assignment.Expression) - { - // when its a direct value reference - case MemberExpression memberAccess when (memberAccess.Expression is ConstantExpression constant): - { - // get the value - var memberValue = memberAccess.Member.GetMemberValue(constant.Value); - - // check if its a global value we've alreay got a query for - if (context.TryGetGlobal(memberValue, out var global)) - { - initializations.Add($"{memberName} := {global.Name}"); - isSubQuery = false; - break; - } - - // check if its a value returned in a previous query - if (QueryObjectManager.TryGetObjectId(memberValue, out var id)) - { - subQuery = id.SelectSubQuery(memberType); - break; - } - - // generate an insert or select based on its unique constraints. - subQuery = new((info) => - { - // generate an insert shape - var insertShape = TranslateExpression(QueryUtils.GenerateInsertShapeExpression(memberValue, memberType), context); - - var exclusiveProps = QueryUtils.GetProperties(info, memberType, true); - var exclusiveCondition = exclusiveProps.Any() ? - $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {typeName})" - : string.Empty; - - return $"(insert {typeName} {{ {insertShape} }}{exclusiveCondition})"; - }); - } - break; - case MethodCallExpression methodCall: - var parsed = TranslateExpression(methodCall, context); - if(context.HasInitializationOperator) - { - initializations.Add($"{memberName} {parsed}"); - context.HasInitializationOperator = false; - } - else - initializations.Add($"{memberName} := {parsed}"); - isSubQuery = false; - break; - default: - throw new NotSupportedException($"Cannot set links with a {assignment.Expression} expression."); - } - - // if its not a sub query, we can safely move onto the next member - if (!isSubQuery) - break; - - // if the query was null we couldn't parse it correctly - if (subQuery is null) - throw new NotSupportedException($"Cannot set links to the expression {assignment.Expression.NodeType}"); - - // add the sub query to the collection of child queries within the update context - var name = QueryUtils.GenerateRandomVariableName(); - updateContext.ChildQueries.Add(name, subQuery); - initializations.Add($"{memberName} := {name}"); - break; - } - - // translate the scalar value - var value = TranslateExpression(assignment.Expression, context); - - // if its null the value was a include method call, we can assume were building - // a shape here and just add the property name - if (value is null) - initializations.Add($"{memberName}"); - else if (context.HasInitializationOperator) // if an initializer was added - { - initializations.Add($"{memberName} {value}"); - context.HasInitializationOperator = false; - } - else if (context.IsShape) // if its a sub shape - { - initializations.Add($"{memberName}: {{ {value} }}"); - context.IsShape = false; - } - else // default assignment - initializations.Add($"{memberName} := {value}"); - } - break; - default: - throw new NotSupportedException($"Cannot translate a {binding}, please file a gh issue with your query."); - } - } - - // join the initialization list by commas - return string.Join(", ", initializations); + return InitializationTranslator.Translate(expressions.ToDictionary(x => x.Item1, x => x.Item2), context); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index b11f32c0..8d21970c 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -39,32 +39,15 @@ private bool ShouldTranslate(MethodCallExpression expression, ExpressionContext // if the method references context or a parameter to our current root lambda var disassembledInstance = expression.Object is null ? Array.Empty () - : DisassembleInstance(expression.Object).ToArray(); + : QueryUtils.DisassembleExpression(expression.Object).ToArray(); var isInstanceReferenceToContext = expression.Object?.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(x => disassembledInstance.Contains(x)); - var isParameterReferenceToContext = expression.Arguments.Any(x => x.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(y => DisassembleInstance(x).Contains(y))); + var isParameterReferenceToContext = expression.Arguments.Any(x => x.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(y => QueryUtils.DisassembleExpression(x).Contains(y))); var isExplicitTranslatorMethod = expression.Method.GetCustomAttribute() is not null; var isStdLib = expression.Method.DeclaringType == typeof(EdgeQL); return isStdLib || isExplicitTranslatorMethod || isParameterReferenceToContext || isInstanceReferenceToContext; } - private IEnumerable DisassembleInstance(Expression expression) - { - yield return expression; - - var temp = expression; - while(temp is MemberExpression memberExpression) - { - if (memberExpression.Expression is not null) - { - yield return memberExpression.Expression; - temp = memberExpression.Expression; - } - else - break; - } - } - private string? TranslateToEdgeQL(MethodCallExpression expression, ExpressionContext context) { // if our method is within the query context class diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs index de4233f7..a0a0f61b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs @@ -16,33 +16,8 @@ internal class NewExpressionTranslator : ExpressionTranslator { /// public override string? Translate(NewExpression expression, ExpressionContext context) - { - string[] shape = new string[expression.Arguments.Count]; - - // iterate over each argument to the constructor - for(int i = 0; i != expression.Arguments.Count; i++) - { - // pull the member and argument out of the expression & get the edgedb name of the member - var member = expression.Members![i]; - var arg = expression.Arguments[i]; - var edgedbName = member.GetEdgeDBPropertyName(); - - // translate the value and determine if were setting a value or referencing a value. - var newContext = context.Enter(x => x.LocalScope = expression.Type); - string? value = TranslateExpression(arg, newContext); - bool isSetter = context.NodeContext.CurrentType.GetProperty(member.Name) == null || arg is MethodCallExpression; - - // add it to our shape - if (value is null) // include - shape[i] = edgedbName; - else if (newContext.IsShape) // includelink - shape[i] = $"{edgedbName}: {{ {value} }}"; - else - shape[i] = $"{edgedbName}{(isSetter || context.IsFreeObject ? " :=" : "")} {value}"; - } - - // return our shape joined by commas - return string.Join(", ", shape); - } + => InitializationTranslator.Translate(expression.Members!.Select((x, i) + => (x, expression.Arguments[i]) + ).ToDictionary(x => x.x, x => x.Item2), context); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs index 2fdcfd64..ae647a7a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs @@ -242,7 +242,7 @@ public static async ValueTask> GetPropertiesAsync /// The given type was not found within the introspection data. /// - public static IEnumerable GetProperties(SchemaInfo schemaInfo, Type type, bool? exclusive = null, bool? @readonly = null) + 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."); @@ -251,6 +251,8 @@ public static IEnumerable GetProperties(SchemaInfo schemaInfo, Typ 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)); @@ -344,5 +346,22 @@ public static async ValueTask>> Gener Expression.Parameter(typeof(TType), "x") ); } + + public static IEnumerable DisassembleExpression(Expression expression) + { + yield return expression; + + var temp = expression; + while (temp is MemberExpression memberExpression) + { + if (memberExpression.Expression is not null) + { + yield return memberExpression.Expression; + temp = memberExpression.Expression; + } + else + break; + } + } } } diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs index e269a348..0f91983d 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs @@ -9,6 +9,7 @@ namespace EdgeDB.StandardLibGenerator { internal class FunctionGenerator { + private const string STDLIB_PATH = @"C:\Users\lynch\source\repos\EdgeDB\src\EdgeDB.Net.QueryBuilder\stdlib"; private const string OUTPUT_PATH = @"C:\Users\lynch\source\repos\EdgeDB\src\EdgeDB.Net.QueryBuilder\Translators\Methods\Generated"; private static readonly TextInfo _textInfo = new CultureInfo("en-US").TextInfo; private static readonly List _generatedTypes = new(); @@ -47,13 +48,13 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable x.ReturnType!.Name! == groupType)?.ReturnType!; - var translatorType = TypeUtils.TryGetType(groupType, out var tInfo) ? BuildType(tInfo, edgedbType, TypeModifier.SingletonType, true) : groupType switch + var translatorType = TypeUtils.TryGetType(groupType, out var tInfo) ? await BuildType(tInfo, edgedbType, TypeModifier.SingletonType, true) : groupType switch { "tuple" => typeof(ITuple).Name, "array" => typeof(Array).Name, "set" => typeof(IEnumerable).Name, "range" => "IRange", - _ => groupType.Contains("::") ? BuildType(new(groupType, null), edgedbType, TypeModifier.SingletonType, true) : throw new Exception($"Failed to find matching type for {groupType}") + _ => groupType.Contains("::") ? await BuildType(new(groupType, null), edgedbType, TypeModifier.SingletonType, true) : throw new Exception($"Failed to find matching type for {groupType}") }; writer.AppendLine("using EdgeDB;"); @@ -73,7 +74,7 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable BuildType(TypeNode node, EdgeDB.StandardLibGenerator.Models.Type edgedbType, TypeModifier modifier, bool shouldGenerate = true) { var name = node.IsGeneric ? "object" : node.DotnetType is null && node.RequiresGeneration && shouldGenerate - ? GenerateType(node, edgedbType) + ? await GenerateType(node, edgedbType) : node.DotnetType?.Name ?? "object"; return modifier switch @@ -183,8 +184,26 @@ private static string BuildType(TypeNode node, EdgeDB.QueryBuilder.StandardLibGe }; } - private static string GenerateType(TypeNode node, EdgeDB.QueryBuilder.StandardLibGenerator.Models.Type edgedbType) + private static async Task GenerateType(TypeNode node, EdgeDB.StandardLibGenerator.Models.Type edgedbType) { + var meta = await edgedbType.GetMetaInfoAsync(_client!); + + switch (meta.Type) + { + case MetaInfoType.Object: + { + + } + break; + case MetaInfoType.Enum: + { + + } + break; + default: + throw new Exception($"Unknown stdlib builder for type {edgedbType.TypeOfSelf}"); + } + return ""; } diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs index dbb1887e..11db4fe0 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs @@ -31,10 +31,12 @@ public async Task GetMetaInfoAsync(EdgeDBClient client) { var result = await QueryBuilder.Select((ctx) => new MetaType { - Pointers = ctx.Raw("[is schema::ObjectType].pointers { name, type: {name, is_abstract}}"), - }).Filter(x => x.Id == Id).ExecuteAsync(client); + Pointers = ctx.Raw("[is schema::ObjectType].pointers { name, target: {name, is_abstract}}"), + EnumValues = ctx.Raw("[is schema::ScalarType].enum_values") + }).Filter(x => x.Id == Id).BuildAsync(client); - return result.First()!; + return null!; + //return result.First()!; } } @@ -43,6 +45,22 @@ internal class MetaType { public Guid Id { get; set; } public Pointer[]? Pointers { get; set; } + public string[]? EnumValues { get; set; } + + [EdgeDBIgnore] + public MetaInfoType Type + => Pointers is not null + ? MetaInfoType.Object + : EnumValues is not null + ? MetaInfoType.Enum + : MetaInfoType.Unknown; + } + + public enum MetaInfoType + { + Enum, + Object, + Unknown } internal class Pointer From f8906170cfd2cb3c867b6924275a7951912fa329 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Fri, 5 Aug 2022 21:47:54 -0300 Subject: [PATCH 47/70] add variac pointer resolution for member access --- .../Examples/QueryBuilder.cs | 11 - .../Attributes/EdgeDBTypeAttribute.cs | 2 +- .../Expressions/MemberExpressionTranslator.cs | 24 ++- .../stdlib/TransactionIsolation.g.cs | 9 + .../FunctionGenerator.cs | 194 +++++++++++++----- .../Models/Type.cs | 10 +- .../Program.cs | 2 +- .../TypeUtils.cs | 35 +++- 8 files changed, 217 insertions(+), 70 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/stdlib/TransactionIsolation.g.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index b1de2e49..38f0148d 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -49,17 +49,6 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var test = (await QueryBuilder.Insert((ctx) => new LinkPerson() - { - Name = "test1", - Email = "test1@mail.com", - BestFriend = new LinkPerson() - { - Email = "test2@mail.com", - Name = "test2" - } - }).BuildAsync(client)).Prettify(); - // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs b/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs index 9cd074a8..e4db7811 100644 --- a/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs +++ b/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs @@ -3,7 +3,7 @@ /// /// Marks this class or struct as a valid type to use when serializing/deserializing. /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] public class EdgeDBTypeAttribute : Attribute { /// diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index 98538892..542c987e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -16,13 +16,29 @@ internal class MemberExpressionTranslator : ExpressionTranslator public override string? Translate(MemberExpression expression, ExpressionContext context) { - // if the inner expression is a constant value we can get, assume + // deconstruct the member access tree. + var deconstructed = QueryUtils.DisassembleExpression(expression).ToArray(); + + // if the resolute expression is a constant expression, assume // were in a set-like context and add it as a variable. - if (expression.Expression is ConstantExpression constant) + if (deconstructed.LastOrDefault() is ConstantExpression constant) { - object? value = expression.Member.GetMemberValue(constant.Value); + // walk thru the reference tree, you can imagine this as a variac pointer resolution. + object? refHolder = constant.Value; + + for(int i = deconstructed.Length - 2; i >= 0; i--) + { + // if the deconstructed node is not a member expression, we have something fishy... + if (deconstructed[i] is not MemberExpression memberExp) + throw new InvalidOperationException("Member tree does not contain all members. this is a bug, please file a github issue with the query that caused this exception."); + + // gain the new reference holder to the value we're after + refHolder = memberExp.Member.GetMemberValue(refHolder); + } - var varName = context.AddVariable(value); + // at this point, 'refHolder' is now a direct reference to the property the expression resolves to, + // we can add this as our variable. + var varName = context.AddVariable(refHolder); if (!QueryUtils.TryGetScalarType(expression.Type, out var type)) throw new NotSupportedException($"Cannot use {expression.Type} as no edgeql equivalent can be found"); diff --git a/src/EdgeDB.Net.QueryBuilder/stdlib/TransactionIsolation.g.cs b/src/EdgeDB.Net.QueryBuilder/stdlib/TransactionIsolation.g.cs new file mode 100644 index 00000000..54015e08 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/stdlib/TransactionIsolation.g.cs @@ -0,0 +1,9 @@ +namespace EdgeDB +{ + [EdgeDBType(ModuleName = "sys")] + public enum TransactionIsolation + { + RepeatableRead, + Serializable, + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs index 0f91983d..115256fe 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs @@ -12,10 +12,16 @@ internal class FunctionGenerator private const string STDLIB_PATH = @"C:\Users\lynch\source\repos\EdgeDB\src\EdgeDB.Net.QueryBuilder\stdlib"; private const string OUTPUT_PATH = @"C:\Users\lynch\source\repos\EdgeDB\src\EdgeDB.Net.QueryBuilder\Translators\Methods\Generated"; private static readonly TextInfo _textInfo = new CultureInfo("en-US").TextInfo; - private static readonly List _generatedTypes = new(); private static readonly Regex _groupRegex = new(@"(.+?)<.+?>"); + private static readonly List _generatedPublicFuncs = new(); private static CodeWriter? _edgeqlClassWriter; private static EdgeDBClient? _client; + private static readonly Dictionary _keywords = new() + { + {"base", "@base" }, + {"default", "@default" }, + {"new", "@new" } + }; public static async ValueTask GenerateAsync(CodeWriter eqlWriter, EdgeDBClient client, IReadOnlyCollection functions) { @@ -23,6 +29,9 @@ public static async ValueTask GenerateAsync(CodeWriter eqlWriter, EdgeDBClient c _edgeqlClassWriter = eqlWriter; if (!Directory.Exists(OUTPUT_PATH)) Directory.CreateDirectory(OUTPUT_PATH); + + if (!Directory.Exists(STDLIB_PATH)) + Directory.CreateDirectory(STDLIB_PATH); try { @@ -43,18 +52,25 @@ public static async ValueTask GenerateAsync(CodeWriter eqlWriter, EdgeDBClient c } } + private class ParsedParameter + { + public string? Name { get; init; } + public string? Type { get; init; } + public string[] Generics { get; init; } = Array.Empty(); + } + private static async ValueTask ProcessGroup(string groupType, IEnumerable funcs) { var writer = new CodeWriter(); var edgedbType = funcs.FirstOrDefault(x => x.ReturnType!.Name! == groupType)?.ReturnType!; - var translatorType = TypeUtils.TryGetType(groupType, out var tInfo) ? await BuildType(tInfo, edgedbType, TypeModifier.SingletonType, true) : groupType switch + var translatorType = TypeUtils.TryGetType(groupType, out var tInfo) ? await BuildType(tInfo, TypeModifier.SingletonType, true) : groupType switch { "tuple" => typeof(ITuple).Name, "array" => typeof(Array).Name, "set" => typeof(IEnumerable).Name, "range" => "IRange", - _ => groupType.Contains("::") ? await BuildType(new(groupType, null), edgedbType, TypeModifier.SingletonType, true) : throw new Exception($"Failed to find matching type for {groupType}") + _ => groupType.Contains("::") ? await BuildType(new(groupType, null), TypeModifier.SingletonType, true) : throw new Exception($"Failed to find matching type for {groupType}") }; writer.AppendLine("using EdgeDB;"); @@ -70,23 +86,11 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable"; - break; - default: - break; - } + var dotnetReturnType = await ParseParameter("result", returnTypeInfo, func.ReturnType, func.ReturnTypeModifier); var parameters = func.Parameters!.Select(x => { @@ -99,21 +103,22 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable !x.HasValue)) throw new Exception("No parameter matches found"); - string[] parsedParameters = new string[parameters.Count()]; + ParsedParameter[] parsedParameters = new ParsedParameter[parameters.Count()]; for(int i = 0; i != parsedParameters.Length; i++) { var x = parameters.ElementAt(i); - var type = BuildType(x!.Value.Node, x.Value.Parameter!.Type!, x.Value.Parameter.TypeModifier, true); var name = x.Value.Parameter.Name; - string @default = ""; - if (x.Value.Parameter.Default is not null) - @default = x.Value.Parameter.Default == "{}" ? "null" : await ParseDefaultAsync(x.Value.Parameter.Default); + parsedParameters[i] = await ParseParameter(name, x.Value.Node, x.Value.Parameter.Type!, x.Value.Parameter.TypeModifier, i); + //var type = await BuildType(x!.Value.Node, x.Value.Parameter!.Type!, x.Value.Parameter.TypeModifier, true, true, $"T{_textInfo.ToTitleCase(name!.Replace("_", " ")).Replace(" ", "")}"); + //string @default = ""; + //if (x.Value.Parameter.Default is not null) + // @default = x.Value.Parameter.Default == "{}" ? "null" : await ParseDefaultAsync(x.Value.Parameter.Default); - parsedParameters[i] = $"{type} {name}{(!string.IsNullOrEmpty(@default) ? $" = {@default}" : "")}"; + //parsedParameters[i] = new ParsedParameter { Name = $"{type} {name}{(!string.IsNullOrEmpty(@default) ? $" = {@default}" : "")}" }; } - var strongMappedParameters = string.Join(", ", parsedParameters); + var strongMappedParameters = string.Join(", ", parsedParameters.Select(x => $"{x.Type} {(_keywords.TryGetValue(x.Name, out var p) ? p : x.Name)}")); var parsedMappedParameters = string.Join(", ", parameters.Select(x => $"string? {x!.Value.Parameter.Name}Param")); writer.AppendLine($"[MethodName(EdgeQL.{funcName})]"); @@ -147,8 +152,15 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable default!"); + var formattedGenerics = string.Join(", ", dotnetReturnType.Generics.Concat(parsedParameters.Where(x => x.Generics!.Any()).SelectMany(x => x.Generics!))); + + var genKey = $"{dotnetReturnType.Type}{funcName}{(formattedGenerics.Any() ? $"<{formattedGenerics}>" : "")}({string.Join(", ", parsedParameters.Select(x => x.Type))})"; + if(!_generatedPublicFuncs.Contains(genKey)) + { + _edgeqlClassWriter!.AppendLine($"public static {dotnetReturnType.Type} {funcName}{(formattedGenerics.Any() ? $"<{formattedGenerics}>" : "")}({strongMappedParameters})"); + _edgeqlClassWriter.AppendLine(" => default!;"); + _generatedPublicFuncs.Add(genKey); + } } catch(Exception x) { @@ -167,13 +179,78 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable BuildType(TypeNode node, EdgeDB.StandardLibGenerator.Models.Type edgedbType, TypeModifier modifier, bool shouldGenerate = true) + private static async ValueTask ParseParameter(string? name, TypeNode node, Models.Type type, TypeModifier? modifier, int index = 0, int subIndex = 0) + { + var genericName = name is not null ? $"T{_textInfo.ToTitleCase(name!.Replace("_", " ")).Replace(" ", "")}" : null; + if (node.IsGeneric) + { + var tname = genericName ?? $"T{index}S{subIndex}"; + if (modifier.HasValue) + { + switch (modifier.Value) + { + case TypeModifier.OptionalType: + tname += "?"; + break; + case TypeModifier.SetOfType: + tname = $"IEnumerable<{tname}>"; + break; + default: + break; + } + } + return new ParsedParameter() { Name = name, Generics = new string[] { genericName ?? $"T{index}S{subIndex}" }, Type = tname }; + } + + if(node.DotnetTypeName is null) + { + + } + + var typeName = node.DotnetTypeName ?? await GenerateType(node); + List generics = new(); + List subTypes = new(); + + if(node.Children?.Any() ?? false) + { + for (int i = 0; i != node.Children.Length; i++) + { + var child = node.Children[i]; + var parsed = await ParseParameter(null, child, type, null, index, i); + if(parsed.Generics is not null) + generics.AddRange(parsed.Generics); + if (parsed.Type is not null) + subTypes.Add(parsed.Type); + } + } + if (subTypes.Any()) + typeName += $"<{string.Join(", ", subTypes)}>"; + + if (modifier.HasValue) + { + switch (modifier.Value) + { + case TypeModifier.OptionalType: + typeName += "?"; + break; + case TypeModifier.SetOfType: + typeName = $"IEnumerable<{typeName}>"; + break; + default: + break; + } + } + + return new ParsedParameter { Name = name, Type = typeName, Generics = generics.ToArray() }; + } + + private static async ValueTask BuildType(TypeNode node, TypeModifier modifier, bool shouldGenerate = true, bool allowGenerics = false, string? genericName = null) { - var name = node.IsGeneric - ? "object" - : node.DotnetType is null && node.RequiresGeneration && shouldGenerate - ? await GenerateType(node, edgedbType) - : node.DotnetType?.Name ?? "object"; + var name = node.IsGeneric + ? allowGenerics && genericName is not null ? genericName : "object" + : node.DotnetType is null && node.RequiresGeneration && shouldGenerate + ? await GenerateType(node) + : node.ToString() ?? "object"; return modifier switch { @@ -184,27 +261,52 @@ private static async ValueTask BuildType(TypeNode node, EdgeDB.StandardL }; } - private static async Task GenerateType(TypeNode node, EdgeDB.StandardLibGenerator.Models.Type edgedbType) + private static async ValueTask GenerateType(TypeNode node) { + var t = await QueryBuilder.Select().Filter(x => x.Name == node.EdgeDBName).BuildAsync(_client); + var edgedbType = (await QueryBuilder.Select().Filter(x => x.Name == node.EdgeDBName).ExecuteAsync(_client!)).FirstOrDefault(); + + if (edgedbType is null) + throw new Exception($"Failde to find type {node.EdgeDBName}"); + + if (TypeUtils.GeneratedTypes.TryGetValue(edgedbType.Name, out var dotnetType)) + return dotnetType; + var meta = await edgedbType.GetMetaInfoAsync(_client!); + var writer = new CodeWriter(); + string typeName = ""; - switch (meta.Type) + using(_ = writer.BeginScope("namespace EdgeDB")) { - case MetaInfoType.Object: - { - - } - break; - case MetaInfoType.Enum: - { + switch (meta.Type) + { + case MetaInfoType.Object: + { - } - break; - default: - throw new Exception($"Unknown stdlib builder for type {edgedbType.TypeOfSelf}"); + } + break; + case MetaInfoType.Enum: + { + var moduleMatch = Regex.Match(edgedbType.Name, @"(.+)::(.*?)$"); + writer.AppendLine($"[EdgeDBType(ModuleName = \"{moduleMatch.Groups[1].Value}\")]"); + typeName = moduleMatch.Groups[2].Value!; + using (_ = writer.BeginScope($"public enum {typeName}")) + { + foreach (var value in meta.EnumValues!) + { + writer.AppendLine($"{value},"); + } + } + } + break; + default: + throw new Exception($"Unknown stdlib builder for type {edgedbType.TypeOfSelf}"); + } } - return ""; + File.WriteAllText(Path.Combine(STDLIB_PATH, $"{typeName}.g.cs"), writer.ToString()); + TypeUtils.GeneratedTypes.Add(edgedbType.Name, typeName); + return typeName; } diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs index 11db4fe0..c1ed5458 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs @@ -33,10 +33,8 @@ public async Task GetMetaInfoAsync(EdgeDBClient client) { Pointers = ctx.Raw("[is schema::ObjectType].pointers { name, target: {name, is_abstract}}"), EnumValues = ctx.Raw("[is schema::ScalarType].enum_values") - }).Filter(x => x.Id == Id).BuildAsync(client); - - return null!; - //return result.First()!; + }).Filter(x => x.Id == Id).ExecuteAsync(client); + return result.First()!; } } @@ -49,9 +47,9 @@ internal class MetaType [EdgeDBIgnore] public MetaInfoType Type - => Pointers is not null + => Pointers?.Any() ?? false ? MetaInfoType.Object - : EnumValues is not null + : EnumValues?.Any() ?? false ? MetaInfoType.Enum : MetaInfoType.Unknown; } diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs index ce8025b3..561f917c 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs @@ -18,7 +18,7 @@ using (var _ = writer.BeginScope("namespace EdgeDB")) { - using (var __ = writer.BeginScope("public sealed partial class EdgeQL")) + using (var __ = writer.BeginScope("public sealed partial class EdgeQLTest")) { await FunctionGenerator.GenerateAsync(writer, edgedb, functions!); } diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs index c2d045d2..0870bafb 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs @@ -14,6 +14,9 @@ namespace EdgeDB.StandardLibGenerator { public readonly struct TypeNode { + public readonly string DotnetTypeName + => DotnetType?.Name?.Replace("`1", "") ?? _dotnetName!; + public readonly string EdgeDBName; public readonly Type? DotnetType; public readonly bool IsGeneric; @@ -23,6 +26,8 @@ public readonly struct TypeNode public readonly bool IsChildOfNamedTuple; public readonly bool RequiresGeneration; + public readonly bool WasGenerated; + private readonly string? _dotnetName; public TypeNode(string name, Type? dotnetType, bool isGeneric, params TypeNode[] children) { @@ -33,6 +38,8 @@ public TypeNode(string name, Type? dotnetType, bool isGeneric, params TypeNode[] IsChildOfNamedTuple = false; TupleElementName = null; RequiresGeneration = false; + _dotnetName = null; + WasGenerated = false; } public TypeNode(string name, Type? dotnetType, string tupleName, bool isGeneric, params TypeNode[] children) { @@ -43,6 +50,8 @@ public TypeNode(string name, Type? dotnetType, string tupleName, bool isGeneric, IsChildOfNamedTuple = true; TupleElementName = tupleName; RequiresGeneration = false; + _dotnetName = null; + WasGenerated = false; } public TypeNode(string name, string? tupleName) { @@ -53,11 +62,28 @@ public TypeNode(string name, string? tupleName) Children = Array.Empty(); IsChildOfNamedTuple = tupleName is not null; TupleElementName = tupleName; + _dotnetName = null; + WasGenerated = false; + } + public TypeNode(string dotnetName, bool wasGenerated, string edgedbName) + { + EdgeDBName = edgedbName; + RequiresGeneration = true; + DotnetType = null; + IsGeneric = false; + Children = Array.Empty(); + IsChildOfNamedTuple = false; + TupleElementName = null; + _dotnetName = dotnetName; + WasGenerated = wasGenerated; } public override string ToString() { - return $"{EdgeDBName} {DotnetType?.FullName} {IsGeneric} {string.Join(", ", Children)}"; + if (Children is null || !Children.Any()) + return DotnetTypeName; + + return $"{DotnetTypeName.Replace("`1", "")}<{string.Join(", ", Children)}>"; } } @@ -65,9 +91,16 @@ public class TypeUtils { private static readonly Regex GenericRegex = new(@"(.+?)<(.+?)>$"); private static readonly Regex NamedTupleRegex = new(@"(.*?[^:]):([^:].*?)$"); + internal static readonly Dictionary GeneratedTypes = new(); public static bool TryGetType(string t, [MaybeNullWhen(false)] out TypeNode type) { + if(GeneratedTypes.TryGetValue(t, out var dotnetName)) + { + type = new(dotnetName, true, t); + return true; + } + type = default; var dotnetType = t switch From a4ffa7d7df77e10766598ec2022912ebb9cd7b5f Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sat, 6 Aug 2022 18:48:45 -0300 Subject: [PATCH 48/70] more work on func generator --- .../stdlib/VersionStage.g.cs | 12 ++++++ .../FunctionGenerator.cs | 38 ++++++++++++++----- 2 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/stdlib/VersionStage.g.cs diff --git a/src/EdgeDB.Net.QueryBuilder/stdlib/VersionStage.g.cs b/src/EdgeDB.Net.QueryBuilder/stdlib/VersionStage.g.cs new file mode 100644 index 00000000..49a0c63c --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/stdlib/VersionStage.g.cs @@ -0,0 +1,12 @@ +namespace EdgeDB +{ + [EdgeDBType(ModuleName = "sys")] + public enum VersionStage + { + dev, + alpha, + beta, + rc, + final, + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs index 115256fe..23c89ca5 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs @@ -56,7 +56,8 @@ private class ParsedParameter { public string? Name { get; init; } public string? Type { get; init; } - public string[] Generics { get; init; } = Array.Empty(); + public string[] Generics { get; set; } = Array.Empty(); + public List GenericConditions { get; set; } = new(); } private static async ValueTask ProcessGroup(string groupType, IEnumerable funcs) @@ -118,6 +119,16 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable x.Generics.Any()).SelectMany(x => x.Generics); + + if (parameterGenerics.Count() == dotnetReturnType.Generics.Count()) + { + // prefer the name of the parameters + dotnetReturnType.Generics = parameterGenerics.ToArray(); + + // TODO: change return type if its generic + } + var strongMappedParameters = string.Join(", ", parsedParameters.Select(x => $"{x.Type} {(_keywords.TryGetValue(x.Name, out var p) ? p : x.Name)}")); var parsedMappedParameters = string.Join(", ", parameters.Select(x => $"string? {x!.Value.Parameter.Name}Param")); @@ -158,6 +169,10 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable" : "")}({strongMappedParameters})"); + foreach(var c in parsedParameters.Where(x => x.GenericConditions.Any()).SelectMany(x => x.GenericConditions)) + { + _edgeqlClassWriter.AppendLine($" {c}"); + } _edgeqlClassWriter.AppendLine(" => default!;"); _generatedPublicFuncs.Add(genKey); } @@ -202,14 +217,10 @@ private static async ValueTask ParseParameter(string? name, Typ return new ParsedParameter() { Name = name, Generics = new string[] { genericName ?? $"T{index}S{subIndex}" }, Type = tname }; } - if(node.DotnetTypeName is null) - { - - } - var typeName = node.DotnetTypeName ?? await GenerateType(node); List generics = new(); List subTypes = new(); + List constraints = new(); if(node.Children?.Any() ?? false) { @@ -217,10 +228,20 @@ private static async ValueTask ParseParameter(string? name, Typ { var child = node.Children[i]; var parsed = await ParseParameter(null, child, type, null, index, i); - if(parsed.Generics is not null) + if(parsed.Generics.Any()) generics.AddRange(parsed.Generics); if (parsed.Type is not null) subTypes.Add(parsed.Type); + + if (child.IsGeneric) + { + switch (node.DotnetTypeName) + { + case "Range": + constraints.Add($"where {parsed.Type} : struct"); + break; + } + } } } if (subTypes.Any()) @@ -241,7 +262,7 @@ private static async ValueTask ParseParameter(string? name, Typ } } - return new ParsedParameter { Name = name, Type = typeName, Generics = generics.ToArray() }; + return new ParsedParameter { GenericConditions = constraints, Name = name, Type = typeName, Generics = generics.ToArray() }; } private static async ValueTask BuildType(TypeNode node, TypeModifier modifier, bool shouldGenerate = true, bool allowGenerics = false, string? genericName = null) @@ -263,7 +284,6 @@ private static async ValueTask BuildType(TypeNode node, TypeModifier mod private static async ValueTask GenerateType(TypeNode node) { - var t = await QueryBuilder.Select().Filter(x => x.Name == node.EdgeDBName).BuildAsync(_client); var edgedbType = (await QueryBuilder.Select().Filter(x => x.Name == node.EdgeDBName).ExecuteAsync(_client!)).FirstOrDefault(); if (edgedbType is null) From 5847b9ed77f83ddaf9594edca8af488eae4388dd Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 7 Aug 2022 02:11:14 -0300 Subject: [PATCH 49/70] Add support for object level constraints and code cleanup --- dbschema/default.esdl | 15 + dbschema/migrations/00011.edgeql | 9 + dbschema/migrations/00012.edgeql | 12 + .../QueryNodes/InsertNode.cs | 34 +- .../QueryNodes/SelectNode.cs | 2 +- .../QueryableCollection.cs | 10 +- .../Schema/DataTypes/Constraint.cs | 25 ++ .../Schema/DataTypes/ObjectType.cs | 7 +- .../Schema/SchemaIntrospector.cs | 4 + .../Expressions/InitializationTranslator.cs | 26 +- .../Expressions/MemberExpressionTranslator.cs | 4 +- .../MethodCallExpressionTranslator.cs | 6 +- .../NewArrayExpressionTranslator.cs | 2 +- .../Expressions/UnaryExpressionTranslator.cs | 2 +- .../Methods/EnumerableMethodTranslator.cs | 2 +- .../Methods/TranslatedParameter.cs | 8 +- .../Utils/ConflictUtils.cs | 47 +++ .../Utils/EdgeDBTypeUtils.cs | 150 +++++++++ .../Utils/ExpressionUtils.cs | 41 +++ .../Utils/QueryGenerationUtils.cs | 167 ++++++++++ .../Utils/QueryUtils.cs | 304 +----------------- 21 files changed, 519 insertions(+), 358 deletions(-) create mode 100644 dbschema/migrations/00011.edgeql create mode 100644 dbschema/migrations/00012.edgeql create mode 100644 src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Constraint.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Utils/ExpressionUtils.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs diff --git a/dbschema/default.esdl b/dbschema/default.esdl index f88728de..69bf5962 100644 --- a/dbschema/default.esdl +++ b/dbschema/default.esdl @@ -63,4 +63,19 @@ module default { } multi link best_friends -> MultiLinkPerson; } + + type ConstraintPerson { + required property name -> str; + required property email -> str; + + constraint exclusive on ((.name, .email)); + } + type PropertyConstraintPerson { + required property name -> str{ + constraint exclusive; + } + required property email -> str { + constraint exclusive; + } + } } \ No newline at end of file diff --git a/dbschema/migrations/00011.edgeql b/dbschema/migrations/00011.edgeql new file mode 100644 index 00000000..f3d8cc8e --- /dev/null +++ b/dbschema/migrations/00011.edgeql @@ -0,0 +1,9 @@ +CREATE MIGRATION m1ekxv5ihvbr5g5v6um2ubsoonwunr6u2l3nzfup2pmjgcxvpzamlq + ONTO m1um3yt7qj7ewz7tlflovxejbnkefpq2he5fwfvms2ucodqo5rupfq +{ + CREATE TYPE default::ConstraintPerson { + CREATE REQUIRED PROPERTY email -> std::str; + CREATE REQUIRED PROPERTY name -> std::str; + CREATE CONSTRAINT std::exclusive ON ((.name, .email)); + }; +}; diff --git a/dbschema/migrations/00012.edgeql b/dbschema/migrations/00012.edgeql new file mode 100644 index 00000000..207b8b1f --- /dev/null +++ b/dbschema/migrations/00012.edgeql @@ -0,0 +1,12 @@ +CREATE MIGRATION m1mqksv77zeiafcinnoo4ppb4gpoxchrogef3etb6altobsj72qvca + ONTO m1ekxv5ihvbr5g5v6um2ubsoonwunr6u2l3nzfup2pmjgcxvpzamlq +{ + CREATE TYPE default::PropertyConstraintPerson { + CREATE REQUIRED PROPERTY email -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY name -> std::str { + CREATE CONSTRAINT std::exclusive; + }; + }; +}; diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 4c5b128f..54ac28cb 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -97,7 +97,7 @@ private Type ResolveTypeFromPath(Type rootType, string path) for (int i = 0; i != pathSections.Length; i++) result = result.GetMember(pathSections[i]).First(x => x is PropertyInfo or FieldInfo)!.GetMemberType(); - if (QueryUtils.IsLink(result, out var isMultiLink, out var innerType) && isMultiLink) + if (EdgeDBTypeUtils.IsLink(result, out var isMultiLink, out var innerType) && isMultiLink) return innerType!; // return the final type. @@ -203,7 +203,7 @@ private string BuildJsonShape() // generate a introspection-dependant sub query for the insert or select var query = new SubQuery((info) => { - var allProps = QueryUtils.GetProperties(info, node.Type); + var allProps = QueryGenerationUtils.GetProperties(info, node.Type); var typeName = node.Type.GetEdgeDBTypeName(); // define the insert shape @@ -212,7 +212,7 @@ private string BuildJsonShape() var edgedbName = x.GetEdgeDBPropertyName(); // if its a link, add a ternary statement for pulling the value out of a sub-map - if (QueryUtils.IsLink(x.PropertyType, out var isArray, out _)) + if (EdgeDBTypeUtils.IsLink(x.PropertyType, out var isArray, out _)) { // if we're in the last iteration of the depth map, we know for certian there // are no sub types within the current context, we can safely set the link to @@ -228,7 +228,7 @@ private string BuildJsonShape() // if its a scalar type, use json_get to pull the value and cast it to our property // type - if (!QueryUtils.TryGetScalarType(x.PropertyType, out var edgeqlType)) + if (!EdgeDBTypeUtils.TryGetScalarType(x.PropertyType, out var edgeqlType)) throw new NotSupportedException($"Cannot use type {x.PropertyType} as there is no serializer for it"); return $"{edgedbName} := <{edgeqlType}>json_get({iterationName}, '{x.Name}')"; @@ -236,7 +236,7 @@ private string BuildJsonShape() }); // generate the 'insert .. unless conflict .. else select' query - var exclusiveProps = QueryUtils.GetProperties(info, node.Type, true); + var exclusiveProps = QueryGenerationUtils.GetProperties(info, node.Type, true); var exclusiveCondition = exclusiveProps.Any() ? $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {typeName})" : string.Empty; @@ -268,7 +268,7 @@ private string BuildJsonShape() var edgedbName = x.GetEdgeDBPropertyName(); // if its a link, add a ternary statement for pulling the value out of a sub-map - if (QueryUtils.IsLink(x.PropertyType, out var isArray, out _)) + if (EdgeDBTypeUtils.IsLink(x.PropertyType, out var isArray, out _)) { // return a slice operator for multi links or a index operator for single links return isArray @@ -278,7 +278,7 @@ private string BuildJsonShape() // if its a scalar type, use json_get to pull the value and cast it to our property // type - if (!QueryUtils.TryGetScalarType(x.PropertyType, out var edgeqlType)) + if (!EdgeDBTypeUtils.TryGetScalarType(x.PropertyType, out var edgeqlType)) throw new NotSupportedException($"Cannot use type {x.PropertyType} as there is no serializer for it"); return $"{edgedbName} := <{edgeqlType}>json_get({jsonValue.Name}, '{x.Name}')"; @@ -317,13 +317,13 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul { // define the type and whether or not it's a link var propType = property.PropertyType; - var isLink = QueryUtils.IsLink(property.PropertyType, out var isArray, out var innerType); + var isLink = EdgeDBTypeUtils.IsLink(property.PropertyType, out var isArray, out var innerType); // get the equivalent edgedb property name var propertyName = property.GetEdgeDBPropertyName(); // if a scalar type is found for the property type - if(QueryUtils.TryGetScalarType(propType, out var edgeqlType)) + if(EdgeDBTypeUtils.TryGetScalarType(propType, out var edgeqlType)) { // set it as a variable and continue the iteration var varName = QueryUtils.GenerateRandomVariableName(); @@ -397,7 +397,7 @@ private string BuildLinkResolver(Type type, object? value) return InlineOrGlobal(type, new SubQuery((info) => { var name = type.GetEdgeDBTypeName(); - var exclusiveProps = QueryUtils.GetProperties(info, type, true); + var exclusiveProps = QueryGenerationUtils.GetProperties(info, type, true); var exclusiveCondition = exclusiveProps.Any() ? $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {name})" : string.Empty; @@ -454,19 +454,7 @@ public override void FinalizeQuery() if (!SchemaInfo.TryGetObjectInfo(OperatingType, out var typeInfo)) throw new NotSupportedException($"Could not find type info for {OperatingType}"); - // get all exclusive properties that aren't the id property - var exclusiveProperties = typeInfo.Properties?.Where(x => x.IsExclusive && x.Name != "id"); - - if (exclusiveProperties == null || !exclusiveProperties.Any()) - throw new NotSupportedException($"The type {typeInfo.Name} does not have any user defined exclusive properties"); - - // build the constraints - // TODO: (.prop1, .prop2) isn't valid for multiple contraints. - var constraint = exclusiveProperties.Count() > 1 ? - $"({string.Join(", ", exclusiveProperties.Select(x => $".{x.Name}"))})" : - $".{exclusiveProperties.First().Name}"; - - Query.Append($" unless conflict on {constraint}"); + Query.Append(ConflictUtils.GenerateExclusiveConflictStatement(typeInfo, _elseStatement.Length != 0)); } Query.Append(_elseStatement); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index a1b8d25b..8e18c614 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -41,7 +41,7 @@ private string GetShape(Type type, int currentDepth = 0) var name = x.GetEdgeDBPropertyName(); // if its a link, build a nested shape if we're not past our max depth - if (QueryUtils.IsLink(x.PropertyType, out var isArray, out var innerType)) + if (EdgeDBTypeUtils.IsLink(x.PropertyType, out var isArray, out var innerType)) { var shapeType = isArray ? innerType! : x.PropertyType; if(currentDepth < MAX_DEPTH) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs index 7a396876..0b48254d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -82,7 +82,7 @@ internal QueryableCollection(IEdgeDBQueryable edgedb) /// The added or updated value. public async Task AddOrUpdateAsync(TType item, CancellationToken token = default) { - var updateFactory = await QueryUtils.GenerateUpdateFactoryAsync(_edgedb, item, token).ConfigureAwait(false); + var updateFactory = await QueryGenerationUtils.GenerateUpdateFactoryAsync(_edgedb, item, token).ConfigureAwait(false); return await QueryBuilder .Insert(item) @@ -154,7 +154,7 @@ await QueryBuilder ).Any(); // try to get exclusive property set on the instance - var props = await QueryUtils.GetPropertiesAsync(_edgedb, exclusive: true, token: token).ConfigureAwait(false); + var props = await QueryGenerationUtils.GetPropertiesAsync(_edgedb, exclusive: true, token: token).ConfigureAwait(false); if (!props.Any()) throw new NotSupportedException("No unique constraints found to generate filter condition."); @@ -219,7 +219,7 @@ public Task DeleteWhereAsync(Expression> filter, Cancell public async Task UpdateAsync(TType value, Expression> updateFunc, CancellationToken token = default) => (await QueryBuilder .Update(updateFunc) - .Filter(await QueryUtils.GenerateUpdateFilterAsync(_edgedb, value, token)) + .Filter(await QueryGenerationUtils.GenerateUpdateFilterAsync(_edgedb, value, token)) .ExecuteAsync(_edgedb, token: token).ConfigureAwait(false)).FirstOrDefault(); /// @@ -234,8 +234,8 @@ public Task DeleteWhereAsync(Expression> filter, Cancell /// The updated item. public async Task UpdateAsync(TType value, CancellationToken token = default) => (await QueryBuilder - .Update(await QueryUtils.GenerateUpdateFactoryAsync(_edgedb, value, token)) - .Filter(await QueryUtils.GenerateUpdateFilterAsync(_edgedb, value, token)) + .Update(await QueryGenerationUtils.GenerateUpdateFactoryAsync(_edgedb, value, token)) + .Filter(await QueryGenerationUtils.GenerateUpdateFilterAsync(_edgedb, value, token)) .ExecuteAsync(_edgedb, token: token).ConfigureAwait(false)).FirstOrDefault(); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Constraint.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Constraint.cs new file mode 100644 index 00000000..b860814d --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Constraint.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Schema.DataTypes +{ + [EdgeDBType(ModuleName = "schema")] + internal class Constraint + { + [EdgeDBProperty("subjectexpr")] + public string? SubjectExpression { get; set; } + + public string? Name { get; set; } + + [EdgeDBIgnore] + public bool IsExclusive + => Name == "std::exclusive"; + + [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 a9ef02e6..b4a834e2 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs @@ -35,9 +35,14 @@ public string CleanedName public bool IsAbstract { get; set; } /// - /// Gets or sets a collection of properties within this objec type. + /// 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; } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs index 3afef4cc..bc3d6781 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs @@ -59,6 +59,10 @@ private static async Task IntrospectSchemaAsync(IEdgeDBQueryable edg Id = ctx.Include(), IsAbstract = ctx.Include(), Name = ctx.Include(), + Constraints = ctx.IncludeMultiLink(() => new Constraint + { + SubjectExpression = ctx.Include() + }), Properties = ctx.IncludeMultiLink(() => new Property { Cardinality = (string)ctx.UnsafeLocal("cardinality") == "One" diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs index e504af44..d1af2f64 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs @@ -20,13 +20,14 @@ internal static class InitializationTranslator // get the members type and edgedb equivalent name var memberType = Member.GetMemberType(); var memberName = Member.GetEdgeDBPropertyName(); - var isLink = QueryUtils.IsLink(memberType, out var isMultiLink, out var innerType); + var typeName = memberType.GetEdgeDBTypeName(); + var isLink = EdgeDBTypeUtils.IsLink(memberType, out var isMultiLink, out var innerType); switch (Expression) { case MemberExpression when isLink: { - var disassembled = QueryUtils.DisassembleExpression(Expression).ToArray(); + var disassembled = ExpressionUtils.DisassembleExpression(Expression).ToArray(); if(disassembled.Last() is ConstantExpression constant && disassembled[disassembled.Length - 2] is MemberExpression constParent) { // get the value @@ -52,14 +53,13 @@ internal static class InitializationTranslator context.SetGlobal(name, new SubQuery((info) => { // generate an insert shape - var insertShape = ExpressionTranslator.ContextualTranslate(QueryUtils.GenerateInsertShapeExpression(memberValue, memberType), context); + var insertShape = ExpressionTranslator.ContextualTranslate(QueryGenerationUtils.GenerateInsertShapeExpression(memberValue, memberType), context); - var exclusiveProps = QueryUtils.GetProperties(info, memberType, true); - var exclusiveCondition = exclusiveProps.Any() ? - $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {memberType.GetEdgeDBTypeName()})" - : string.Empty; + if (!info.TryGetObjectInfo(memberType, out var objInfo)) + throw new InvalidOperationException($"No schema type found for {memberType}"); - return $"(insert {memberType.GetEdgeDBTypeName()} {{ {insertShape} }}{exclusiveCondition})"; + var exclusiveCondition = $"{ConflictUtils.GenerateExclusiveConflictStatement(objInfo, true)} else (select {typeName})"; + return $"(insert {memberType.GetEdgeDBTypeName()} {{ {insertShape} }} {exclusiveCondition})"; }), null); initializations.Add($"{memberName} := {name}"); } @@ -74,12 +74,12 @@ internal static class InitializationTranslator // generate an insert shape var insertShape = ExpressionTranslator.ContextualTranslate(expression, context); - var exclusiveProps = QueryUtils.GetProperties(info, memberType, true); - var exclusiveCondition = exclusiveProps.Any() ? - $" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {memberType.GetEdgeDBTypeName()})" - : string.Empty; + if (!info.TryGetObjectInfo(memberType, out var objInfo)) + throw new InvalidOperationException($"No schema type found for {memberType}"); - return $"(insert {memberType.GetEdgeDBTypeName()} {{ {insertShape} }}{exclusiveCondition})"; + var exclusiveCondition = $"{ConflictUtils.GenerateExclusiveConflictStatement(objInfo, true)} else (select {typeName})"; + + return $"(insert {memberType.GetEdgeDBTypeName()} {{ {insertShape} }} {exclusiveCondition})"; }), null); initializations.Add($"{memberName} := {name}"); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index 542c987e..f3f49544 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -17,7 +17,7 @@ internal class MemberExpressionTranslator : ExpressionTranslator${varName}"; diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index 8d21970c..22a64bba 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -27,7 +27,7 @@ internal class MethodCallExpressionTranslator : ExpressionTranslator () - : QueryUtils.DisassembleExpression(expression.Object).ToArray(); + : ExpressionUtils.DisassembleExpression(expression.Object).ToArray(); var isInstanceReferenceToContext = expression.Object?.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(x => disassembledInstance.Contains(x)); - var isParameterReferenceToContext = expression.Arguments.Any(x => x.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(y => QueryUtils.DisassembleExpression(x).Contains(y))); + var isParameterReferenceToContext = expression.Arguments.Any(x => x.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(y => ExpressionUtils.DisassembleExpression(x).Contains(y))); var isExplicitTranslatorMethod = expression.Method.GetCustomAttribute() is not null; var isStdLib = expression.Method.DeclaringType == typeof(EdgeQL); return isStdLib || isExplicitTranslatorMethod || isParameterReferenceToContext || isInstanceReferenceToContext; diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs index 339e4e7a..7a3a25cc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs @@ -20,7 +20,7 @@ internal class NewArrayExpressionTranslator : ExpressionTranslator TranslateExpression(x, context))); // if its a collection of link-valid types, serialzie it as a set - if(QueryUtils.IsLink(expression.Type, out _, out _)) + if(EdgeDBTypeUtils.IsLink(expression.Type, out _, out _)) return $"{{ {elements} }}"; // serialize as a scalar array diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs index b549b14e..8d01b1f4 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs @@ -43,7 +43,7 @@ internal class UnaryExpressionTranslator : ExpressionTranslator return value; } - var type = QueryUtils.TryGetScalarType(expression.Type, out var edgedbType) + var type = EdgeDBTypeUtils.TryGetScalarType(expression.Type, out var edgedbType) ? edgedbType.ToString() : expression.Type.GetEdgeDBTypeName(); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs index 4dfca650..459442e7 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs @@ -62,7 +62,7 @@ public string FirstOrDefault(TranslatedParameter source, TranslatedParameter fil 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; - return $"<{QueryUtils.GetEdgeDBScalarOrTypename(returnType)}>array_get(array_agg((select {name} := {set} filter {filterOrDefault})), 0){(defaultValue != null ? $" ?? {defaultValue}" : String.Empty)}"; + return $"<{EdgeDBTypeUtils.GetEdgeDBScalarOrTypename(returnType)}>array_get(array_agg((select {name} := {set} filter {filterOrDefault})), 0){(defaultValue != null ? $" ?? {defaultValue}" : String.Empty)}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs index 146e5ec2..7c70b995 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/TranslatedParameter.cs @@ -31,25 +31,25 @@ internal class TranslatedParameter /// Gets whether or not the parameter type is a scalar array. /// public bool IsScalarArrayType - => QueryUtils.TryGetScalarType(ParameterType, out var info) && info.IsArray; + => EdgeDBTypeUtils.TryGetScalarType(ParameterType, out var info) && info.IsArray; /// /// Gets whether or not the parameter is a scalar type. /// public bool IsScalarType - => QueryUtils.TryGetScalarType(ParameterType, out _); + => EdgeDBTypeUtils.TryGetScalarType(ParameterType, out _); /// /// Gets whether or not the parameter is a valid link type. /// public bool IsLinkType - => QueryUtils.IsLink(ParameterType, out _, out _); + => EdgeDBTypeUtils.IsLink(ParameterType, out _, out _); /// /// Gets whether or not the parameter is a valid multi-link type. /// public bool IsMutliLinkType - => QueryUtils.IsLink(ParameterType, out var isMulti, out _) && isMulti; + => EdgeDBTypeUtils.IsLink(ParameterType, out var isMulti, out _) && isMulti; /// /// Constructs a new . diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs new file mode 100644 index 00000000..4159a557 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs @@ -0,0 +1,47 @@ +using EdgeDB.Schema.DataTypes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + /// + /// A class of utility functions to help with conflict statements. + /// + internal static class ConflictUtils + { + /// + /// Generates an 'UNLESS CONFLICT [ON expr]' statement for the given object type. + /// + /// 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 string GenerateExclusiveConflictStatement(ObjectType type, bool hasElse) + { + // does the type have any object level exclusive constraints? + if (type.Constraints?.Any(x => x.IsExclusive) ?? false) + { + return $"unless conflict on {type.Constraints?.First(x => x.IsExclusive).SubjectExpression}"; + } + + // does the type have a single property that is exclusive? + if(type.Properties!.Count(x => x.IsExclusive) == 1) + { + return $"unless conflict on .{type.Properties!.First(x => x.IsExclusive).Name}"; + } + + // if it doesn't have an else statement we can simply add 'UNLESS CONFLICT' + if (!hasElse) + return "unless conflict"; + + throw new InvalidOperationException($"Cannot find a valid exclusive contraint on type {type.Name}"); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs new file mode 100644 index 00000000..6e8f1e7a --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs @@ -0,0 +1,150 @@ +using EdgeDB.Serializer; +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 +{ + /// + /// A class of utility functions for edgedb types. + /// + internal static class EdgeDBTypeUtils + { + private static readonly ConcurrentDictionary _typeCache = new(); + + /// + /// Represents type info about a compatable edgedb type. + /// + internal class EdgeDBTypeInfo + { + /// + /// 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; + } + } + + /// + /// 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 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; + + Type? enumerableType = type.GetInterfaces().FirstOrDefault(x => x.Name == "IEnumerable`1"); + + EdgeDBTypeInfo? child = null; + var hasChild = enumerableType != null && TryGetScalarType(enumerableType.GenericTypeArguments[0], out child); + var scalar = PacketSerializer.GetEdgeQLType(type); + + if (scalar != null) + info = new(type, scalar, false, child); + else if (hasChild) + info = new(type, "array", true, child); + + return info != null && _typeCache.TryAdd(type, info); + } + + /// + /// 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, [MaybeNullWhen(false)] out Type? innerLinkType) + { + innerLinkType = null; + isMultiLink = false; + + Type? enumerableType = null; + if (type != typeof(string) && (enumerableType = type.GetInterfaces().FirstOrDefault(x => ReflectionUtils.IsSubTypeOfGenericType(typeof(IEnumerable<>), x))) != null) + { + innerLinkType = enumerableType.GenericTypeArguments[0]; + isMultiLink = true; + var result = IsLink(innerLinkType, out _, out var linkType); + innerLinkType ??= linkType; + return result; + } + + return TypeBuilder.IsValidObjectType(type); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/ExpressionUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/ExpressionUtils.cs new file mode 100644 index 00000000..daa3f0d5 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils/ExpressionUtils.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + /// + /// A class of utility functions for working with expressions. + /// + internal static class ExpressionUtils + { + /// + /// Disassembles an arbitrary expression into a list of expression nodes. + /// + /// The expression to disassemble. + /// + /// A collection of expressions representing the passed in expression. + /// + public static IEnumerable DisassembleExpression(Expression expression) + { + // return the "root" expression + yield return expression; + + // while the current expression is a member expression, grab its child expression and yield it. + var temp = expression; + while (temp is MemberExpression memberExpression) + { + if (memberExpression.Expression is not null) + { + yield return memberExpression.Expression; + temp = memberExpression.Expression; + } + else + break; + } + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs new file mode 100644 index 00000000..95a16034 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs @@ -0,0 +1,167 @@ +using EdgeDB.Schema; +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 +{ + 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) + { + 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. + /// + /// 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 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)); + }); + } + + /// + /// 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) + { + 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>> GenerateUpdateFilterAsync(IEdgeDBQueryable edgedb, TType value, CancellationToken token = default) + { + // 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(QueryContext).GetMethod("UnsafeLocal")!; + return Expression.Lambda>( + exclusiveProperties.Select(x => + { + + return Expression.Equal( + Expression.Call( + Expression.Parameter(typeof(QueryContext), "ctx"), + unsafeLocalMethod, + Expression.Constant(x.GetEdgeDBPropertyName()) + ), + Expression.MakeMemberAccess(Expression.Parameter(typeof(TType), "x"), x) + ); + }).Aggregate((x, y) => Expression.And(x, y)), + Expression.Parameter(typeof(QueryContext), "ctx"), + Expression.Parameter(typeof(TType), "x") + ); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs index ae647a7a..d4682dad 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs @@ -22,138 +22,6 @@ internal static class QueryUtils { private const string VARIABLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static readonly Random _rng = new(); - private static ConcurrentDictionary _typeCache = new(); - - /// - /// Represents type info about a compatable edgedb type. - /// - internal class EdgeDBTypeInfo - { - /// - /// 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; - } - } - - /// - /// 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 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; - - Type? enumerableType = type.GetInterfaces().FirstOrDefault(x => x.Name == "IEnumerable`1"); - - EdgeDBTypeInfo? child = null; - var hasChild = enumerableType != null && TryGetScalarType(enumerableType.GenericTypeArguments[0], out child); - var scalar = PacketSerializer.GetEdgeQLType(type); - - if (scalar != null) - info = new(type, scalar, false, child); - else if (hasChild) - info = new(type, "array", true, child); - - return info != null && _typeCache.TryAdd(type, info); - } - - /// - /// 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, [MaybeNullWhen(false)]out Type? innerLinkType) - { - innerLinkType = null; - isMultiLink = false; - - Type? enumerableType = null; - if (type != typeof(string) && (enumerableType = type.GetInterfaces().FirstOrDefault(x => ReflectionUtils.IsSubTypeOfGenericType(typeof(IEnumerable<>), x))) != null) - { - innerLinkType = enumerableType.GenericTypeArguments[0]; - isMultiLink = true; - var result = IsLink(innerLinkType, out _, out var linkType); - innerLinkType ??= linkType; - return result; - } - - return TypeBuilder.IsValidObjectType(type); - } /// /// Parses a given object into its equivilant edgeql form. @@ -182,7 +50,7 @@ internal static string ParseObject(object? obj) SubQuery query when !query.RequiresIntrospection => query.Query!, string str => $"\"{str}\"", char chr => $"\"{chr}\"", - Type type => TryGetScalarType(type, out var info) ? info.ToString() : type.GetEdgeDBTypeName(), + Type type => EdgeDBTypeUtils.TryGetScalarType(type, out var info) ? info.ToString() : type.GetEdgeDBTypeName(), _ => obj.ToString()! }; } @@ -193,175 +61,5 @@ internal static string ParseObject(object? obj) /// 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()); - - /// - /// 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. - /// - /// 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 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)); - }); - } - - /// - /// 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) - { - 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>> GenerateUpdateFilterAsync(IEdgeDBQueryable edgedb, TType value, CancellationToken token = default) - { - // 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(QueryContext).GetMethod("UnsafeLocal")!; - return Expression.Lambda>( - exclusiveProperties.Select(x => - { - - return Expression.Equal( - Expression.Call( - Expression.Parameter(typeof(QueryContext), "ctx"), - unsafeLocalMethod, - Expression.Constant(x.GetEdgeDBPropertyName()) - ), - Expression.MakeMemberAccess(Expression.Parameter(typeof(TType), "x"), x) - ); - }).Aggregate((x, y) => Expression.And(x, y)), - Expression.Parameter(typeof(QueryContext), "ctx"), - Expression.Parameter(typeof(TType), "x") - ); - } - - public static IEnumerable DisassembleExpression(Expression expression) - { - yield return expression; - - var temp = expression; - while (temp is MemberExpression memberExpression) - { - if (memberExpression.Expression is not null) - { - yield return memberExpression.Expression; - temp = memberExpression.Expression; - } - else - break; - } - } } } From 42ad2f1c437195fa30151af53db4d668a27c12a4 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 8 Aug 2022 22:33:05 -0300 Subject: [PATCH 50/70] fix constraint generator --- .../Examples/QueryBuilder.cs | 14 ++++++++++++++ .../QueryNodes/InsertNode.cs | 2 +- .../Schema/SchemaIntrospector.cs | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 38f0148d..c676fd78 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -38,9 +38,22 @@ public class ArrayPerson { public string? Name { get; set; } public string? Email { get; set; } + public IEnumerable? Roles { get; set; } } + public class PropertyConstraintPerson + { + public string? Name { get; set; } + public string? Email { get; set; } + } + + public class ConstraintPerson + { + public string? Name { get; set; } + public string? Email { get; set; } + } + public async Task ExecuteAsync(EdgeDBClient client) { await QueryBuilderDemo(client); @@ -49,6 +62,7 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { + var test = await QueryBuilder.Insert(new ConstraintPerson { Email = "test", Name = "test" }).UnlessConflict().BuildAsync(client); // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 54ac28cb..05056f7f 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -454,7 +454,7 @@ public override void FinalizeQuery() if (!SchemaInfo.TryGetObjectInfo(OperatingType, out var typeInfo)) throw new NotSupportedException($"Could not find type info for {OperatingType}"); - Query.Append(ConflictUtils.GenerateExclusiveConflictStatement(typeInfo, _elseStatement.Length != 0)); + Query.Append($" {ConflictUtils.GenerateExclusiveConflictStatement(typeInfo, _elseStatement.Length != 0)}"); } Query.Append(_elseStatement); diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs index bc3d6781..fc9e0efd 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs @@ -61,7 +61,8 @@ private static async Task IntrospectSchemaAsync(IEdgeDBQueryable edg Name = ctx.Include(), Constraints = ctx.IncludeMultiLink(() => new Constraint { - SubjectExpression = ctx.Include() + SubjectExpression = ctx.Include(), + Name = ctx.Include(), }), Properties = ctx.IncludeMultiLink(() => new Property { From bfcbbfccf52cfd32f0660b7ae41f0b66020d4f00 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 8 Aug 2022 22:35:10 -0300 Subject: [PATCH 51/70] add missing summary --- src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs index 95a16034..af9f33e6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs @@ -55,6 +55,7 @@ public static async ValueTask> GetPropertiesAsync 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. From b4733e665481f39e136cd4c9f4d96fdc86ba8c6d Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 8 Aug 2022 23:57:00 -0300 Subject: [PATCH 52/70] more std lib generation work --- src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs | 1 + .../Methods/Generated/Anytype.g.cs | 52 +++++ .../Translators/Methods/Generated/Array.g.cs | 106 +++++++++ .../Methods/Generated/CalDate_Duration.g.cs | 40 ++++ .../Methods/Generated/CalLocal_Date.g.cs | 52 +++++ .../Methods/Generated/CalLocal_Datetime.g.cs | 46 ++++ .../Methods/Generated/CalLocal_Time.g.cs | 40 ++++ .../Generated/CalRelative_Duration.g.cs | 46 ++++ .../Translators/Methods/Generated/Range.g.cs | 16 ++ .../Methods/Generated/StdAnyenum.g.cs | 22 ++ .../Methods/Generated/StdAnypoint.g.cs | 22 ++ .../Methods/Generated/StdAnyreal.g.cs | 28 +++ .../Methods/Generated/StdBigint.g.cs | 40 ++++ .../Methods/Generated/StdBool.g.cs | 88 +++++++ .../Methods/Generated/StdDatetime.g.cs | 88 +++++++ .../Methods/Generated/StdDecimal.g.cs | 106 +++++++++ .../Methods/Generated/StdDuration.g.cs | 34 +++ .../Methods/Generated/StdFloat32.g.cs | 28 +++ .../Methods/Generated/StdFloat64.g.cs | 178 ++++++++++++++ .../Methods/Generated/StdInt16.g.cs | 52 +++++ .../Methods/Generated/StdInt32.g.cs | 64 +++++ .../Methods/Generated/StdInt64.g.cs | 160 +++++++++++++ .../Methods/Generated/StdJson.g.cs | 34 +++ .../Translators/Methods/Generated/StdStr.g.cs | 220 ++++++++++++++++++ .../Methods/Generated/StdUuid.g.cs | 22 ++ .../Generated/SysTransactionisolation.g.cs | 16 ++ .../Translators/Methods/Generated/Tuple.g.cs | 28 +++ .../FunctionGenerator.cs | 63 +++-- .../Program.cs | 1 + .../TypeUtils.cs | 2 +- 30 files changed, 1670 insertions(+), 25 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Anytype.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Array.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalDate_Duration.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Date.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Datetime.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Time.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalRelative_Duration.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Range.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnyenum.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnypoint.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnyreal.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdBigint.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdBool.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDatetime.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDecimal.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDuration.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdFloat32.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdFloat64.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt16.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt32.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt64.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdJson.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdStr.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdUuid.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/SysTransactionisolation.g.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Tuple.g.cs diff --git a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs index da29e064..d39321a3 100644 --- a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs +++ b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("EdgeDB.Examples.ExampleApp")] +[assembly: InternalsVisibleTo("EdgeDB.QueryBuilder.StandardLibGenerator")] [assembly: InternalsVisibleTo("EdgeDB.DotnetTool")] [assembly: InternalsVisibleTo("EdgeDB.Tests.Unit")] [assembly: InternalsVisibleTo("EdgeDB.Tests.Integration")] diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Anytype.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Anytype.g.cs new file mode 100644 index 00000000..f8deaf86 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Anytype.g.cs @@ -0,0 +1,52 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class Anytype : MethodTranslator + { + [MethodName(EdgeQL.AssertSingle)] + public string AssertSingle(string? inputParam, string? messageParam) + { + return $"std::assert_single({inputParam}, message := {messageParam})"; + } + + [MethodName(EdgeQL.AssertExists)] + public string AssertExists(string? inputParam, string? messageParam) + { + return $"std::assert_exists({inputParam}, message := {messageParam})"; + } + + [MethodName(EdgeQL.AssertDistinct)] + public string AssertDistinct(string? inputParam, string? messageParam) + { + return $"std::assert_distinct({inputParam}, message := {messageParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.ArrayUnpack)] + public string ArrayUnpack(string? arrayParam) + { + return $"std::array_unpack({arrayParam})"; + } + + [MethodName(EdgeQL.ArrayGet)] + public string ArrayGet(string? arrayParam, string? idxParam, string? defaultParam) + { + return $"std::array_get({arrayParam}, {idxParam}, default := {defaultParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Array.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Array.g.cs new file mode 100644 index 00000000..71d6f8d9 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Array.g.cs @@ -0,0 +1,106 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class Array : MethodTranslator + { + [MethodName(EdgeQL.ArrayReplace)] + public string ArrayReplace(string? arrayParam, string? oldParam, string? newParam) + { + return $"std::array_replace({arrayParam}, {oldParam}, {newParam})"; + } + + [MethodName(EdgeQL.ArrayAgg)] + public string ArrayAgg(string? sParam) + { + return $"std::array_agg({sParam})"; + } + + [MethodName(EdgeQL.ArrayFill)] + public string ArrayFill(string? valParam, string? nParam) + { + return $"std::array_fill({valParam}, {nParam})"; + } + + [MethodName(EdgeQL.ReMatch)] + public string ReMatch(string? patternParam, string? strParam) + { + return $"std::re_match({patternParam}, {strParam})"; + } + + [MethodName(EdgeQL.ReMatchAll)] + public string ReMatchAll(string? patternParam, string? strParam) + { + return $"std::re_match_all({patternParam}, {strParam})"; + } + + [MethodName(EdgeQL.StrSplit)] + public string StrSplit(string? sParam, string? delimiterParam) + { + return $"std::str_split({sParam}, {delimiterParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalDate_Duration.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalDate_Duration.g.cs new file mode 100644 index 00000000..f7494ab7 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalDate_Duration.g.cs @@ -0,0 +1,40 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class CalDate_Duration : MethodTranslator + { + [MethodName(EdgeQL.ToDateDuration)] + public string ToDateDuration(string? yearsParam, string? monthsParam, string? daysParam) + { + return $"cal::to_date_duration(years := {yearsParam}, months := {monthsParam}, days := {daysParam})"; + } + + [MethodName(EdgeQL.DurationNormalizeDays)] + public string DurationNormalizeDays(string? durParam) + { + return $"cal::duration_normalize_days({durParam})"; + } + + [MethodName(EdgeQL.DurationTruncate)] + public string DurationTruncate(string? dtParam, string? unitParam) + { + return $"std::duration_truncate({dtParam}, {unitParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Date.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Date.g.cs new file mode 100644 index 00000000..9f7b374e --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Date.g.cs @@ -0,0 +1,52 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class CalLocal_Date : MethodTranslator + { + [MethodName(EdgeQL.ToLocalDate)] + public string ToLocalDate(string? sParam, string? fmtParam) + { + return $"cal::to_local_date({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToLocalDate)] + public string ToLocalDate(string? dtParam, string? zoneParam) + { + return $"cal::to_local_date({dtParam}, {zoneParam})"; + } + + [MethodName(EdgeQL.ToLocalDate)] + public string ToLocalDate(string? yearParam, string? monthParam, string? dayParam) + { + return $"cal::to_local_date({yearParam}, {monthParam}, {dayParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam) + { + return $"std::range_unpack({valParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam, string? stepParam) + { + return $"std::range_unpack({valParam}, {stepParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Datetime.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Datetime.g.cs new file mode 100644 index 00000000..ebdd20a2 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Datetime.g.cs @@ -0,0 +1,46 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class CalLocal_Datetime : MethodTranslator + { + [MethodName(EdgeQL.ToLocalDatetime)] + public string ToLocalDatetime(string? sParam, string? fmtParam) + { + return $"cal::to_local_datetime({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToLocalDatetime)] + public string ToLocalDatetime(string? yearParam, string? monthParam, string? dayParam, string? hourParam, string? minParam, string? secParam) + { + return $"cal::to_local_datetime({yearParam}, {monthParam}, {dayParam}, {hourParam}, {minParam}, {secParam})"; + } + + [MethodName(EdgeQL.ToLocalDatetime)] + public string ToLocalDatetime(string? dtParam, string? zoneParam) + { + return $"cal::to_local_datetime({dtParam}, {zoneParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam, string? stepParam) + { + return $"std::range_unpack({valParam}, {stepParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Time.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Time.g.cs new file mode 100644 index 00000000..0830b389 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalLocal_Time.g.cs @@ -0,0 +1,40 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class CalLocal_Time : MethodTranslator + { + [MethodName(EdgeQL.ToLocalTime)] + public string ToLocalTime(string? sParam, string? fmtParam) + { + return $"cal::to_local_time({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToLocalTime)] + public string ToLocalTime(string? dtParam, string? zoneParam) + { + return $"cal::to_local_time({dtParam}, {zoneParam})"; + } + + [MethodName(EdgeQL.ToLocalTime)] + public string ToLocalTime(string? hourParam, string? minParam, string? secParam) + { + return $"cal::to_local_time({hourParam}, {minParam}, {secParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalRelative_Duration.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalRelative_Duration.g.cs new file mode 100644 index 00000000..898bde07 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/CalRelative_Duration.g.cs @@ -0,0 +1,46 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class CalRelative_Duration : MethodTranslator + { + [MethodName(EdgeQL.ToRelativeDuration)] + public string ToRelativeDuration(string? yearsParam, string? monthsParam, string? daysParam, string? hoursParam, string? minutesParam, string? secondsParam, string? microsecondsParam) + { + return $"cal::to_relative_duration(years := {yearsParam}, months := {monthsParam}, days := {daysParam}, hours := {hoursParam}, minutes := {minutesParam}, seconds := {secondsParam}, microseconds := {microsecondsParam})"; + } + + [MethodName(EdgeQL.DurationNormalizeHours)] + public string DurationNormalizeHours(string? durParam) + { + return $"cal::duration_normalize_hours({durParam})"; + } + + [MethodName(EdgeQL.DurationNormalizeDays)] + public string DurationNormalizeDays(string? durParam) + { + return $"cal::duration_normalize_days({durParam})"; + } + + [MethodName(EdgeQL.DurationTruncate)] + public string DurationTruncate(string? dtParam, string? unitParam) + { + return $"std::duration_truncate({dtParam}, {unitParam})"; + } + + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Range.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Range.g.cs new file mode 100644 index 00000000..1c2728b4 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Range.g.cs @@ -0,0 +1,16 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class Range : MethodTranslator + { + [MethodName(EdgeQL.Range)] + public string Range(string? lowerParam, string? upperParam, string? inc_lowerParam, string? inc_upperParam, string? emptyParam) + { + return $"std::range({(lowerParam is not null ? "lowerParam, " : "")}, {(upperParam is not null ? "upperParam, " : "")}, inc_lower := {inc_lowerParam}, inc_upper := {inc_upperParam}, empty := {emptyParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnyenum.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnyenum.g.cs new file mode 100644 index 00000000..2d797b25 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnyenum.g.cs @@ -0,0 +1,22 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdAnyenum : MethodTranslator + { + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnypoint.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnypoint.g.cs new file mode 100644 index 00000000..4d57bda4 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnypoint.g.cs @@ -0,0 +1,22 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdAnypoint : MethodTranslator + { + [MethodName(EdgeQL.RangeGetUpper)] + public string RangeGetUpper(string? rParam) + { + return $"std::range_get_upper({rParam})"; + } + + [MethodName(EdgeQL.RangeGetLower)] + public string RangeGetLower(string? rParam) + { + return $"std::range_get_lower({rParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnyreal.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnyreal.g.cs new file mode 100644 index 00000000..de319041 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdAnyreal.g.cs @@ -0,0 +1,28 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdAnyreal : MethodTranslator + { + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.Abs)] + public string Abs(string? xParam) + { + return $"math::abs({xParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdBigint.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdBigint.g.cs new file mode 100644 index 00000000..7170700f --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdBigint.g.cs @@ -0,0 +1,40 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdBigint : MethodTranslator + { + [MethodName(EdgeQL.Sum)] + public string Sum(string? sParam) + { + return $"std::sum({sParam})"; + } + + [MethodName(EdgeQL.Round)] + public string Round(string? valParam) + { + return $"std::round({valParam})"; + } + + [MethodName(EdgeQL.ToBigint)] + public string ToBigint(string? sParam, string? fmtParam) + { + return $"std::to_bigint({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.Ceil)] + public string Ceil(string? xParam) + { + return $"math::ceil({xParam})"; + } + + [MethodName(EdgeQL.Floor)] + public string Floor(string? xParam) + { + return $"math::floor({xParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdBool.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdBool.g.cs new file mode 100644 index 00000000..7de0d592 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdBool.g.cs @@ -0,0 +1,88 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdBool : MethodTranslator + { + [MethodName(EdgeQL.All)] + public string All(string? valsParam) + { + return $"std::all({valsParam})"; + } + + [MethodName(EdgeQL.Any)] + public string Any(string? valsParam) + { + return $"std::any({valsParam})"; + } + + [MethodName(EdgeQL.Contains)] + public string Contains(string? haystackParam, string? needleParam) + { + return $"std::contains({haystackParam}, {needleParam})"; + } + + [MethodName(EdgeQL.Contains)] + public string Contains(string? haystackParam, string? needleParam) + { + return $"std::contains({haystackParam}, {needleParam})"; + } + + [MethodName(EdgeQL.Contains)] + public string Contains(string? haystackParam, string? needleParam) + { + return $"std::contains({haystackParam}, {needleParam})"; + } + + [MethodName(EdgeQL.ReTest)] + public string ReTest(string? patternParam, string? strParam) + { + return $"std::re_test({patternParam}, {strParam})"; + } + + [MethodName(EdgeQL.RangeIsEmpty)] + public string RangeIsEmpty(string? valParam) + { + return $"std::range_is_empty({valParam})"; + } + + [MethodName(EdgeQL.RangeIsInclusiveUpper)] + public string RangeIsInclusiveUpper(string? rParam) + { + return $"std::range_is_inclusive_upper({rParam})"; + } + + [MethodName(EdgeQL.RangeIsInclusiveLower)] + public string RangeIsInclusiveLower(string? rParam) + { + return $"std::range_is_inclusive_lower({rParam})"; + } + + [MethodName(EdgeQL.Contains)] + public string Contains(string? haystackParam, string? needleParam) + { + return $"std::contains({haystackParam}, {needleParam})"; + } + + [MethodName(EdgeQL.Contains)] + public string Contains(string? haystackParam, string? needleParam) + { + return $"std::contains({haystackParam}, {needleParam})"; + } + + [MethodName(EdgeQL.Overlaps)] + public string Overlaps(string? lParam, string? rParam) + { + return $"std::overlaps({lParam}, {rParam})"; + } + + [MethodName(EdgeQL.Contains)] + public string Contains(string? haystackParam, string? needleParam) + { + return $"std::contains({haystackParam}, {needleParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDatetime.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDatetime.g.cs new file mode 100644 index 00000000..3da113fe --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDatetime.g.cs @@ -0,0 +1,88 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdDatetime : MethodTranslator + { + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.DatetimeCurrent)] + public string DatetimeCurrent() + { + return $"std::datetime_current()"; + } + + [MethodName(EdgeQL.DatetimeOfTransaction)] + public string DatetimeOfTransaction() + { + return $"std::datetime_of_transaction()"; + } + + [MethodName(EdgeQL.DatetimeOfStatement)] + public string DatetimeOfStatement() + { + return $"std::datetime_of_statement()"; + } + + [MethodName(EdgeQL.DatetimeTruncate)] + public string DatetimeTruncate(string? dtParam, string? unitParam) + { + return $"std::datetime_truncate({dtParam}, {unitParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam, string? stepParam) + { + return $"std::range_unpack({valParam}, {stepParam})"; + } + + [MethodName(EdgeQL.ToDatetime)] + public string ToDatetime(string? sParam, string? fmtParam) + { + return $"std::to_datetime({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToDatetime)] + public string ToDatetime(string? yearParam, string? monthParam, string? dayParam, string? hourParam, string? minParam, string? secParam, string? timezoneParam) + { + return $"std::to_datetime({yearParam}, {monthParam}, {dayParam}, {hourParam}, {minParam}, {secParam}, {timezoneParam})"; + } + + [MethodName(EdgeQL.ToDatetime)] + public string ToDatetime(string? epochsecondsParam) + { + return $"std::to_datetime({epochsecondsParam})"; + } + + [MethodName(EdgeQL.ToDatetime)] + public string ToDatetime(string? epochsecondsParam) + { + return $"std::to_datetime({epochsecondsParam})"; + } + + [MethodName(EdgeQL.ToDatetime)] + public string ToDatetime(string? epochsecondsParam) + { + return $"std::to_datetime({epochsecondsParam})"; + } + + [MethodName(EdgeQL.ToDatetime)] + public string ToDatetime(string? localParam, string? zoneParam) + { + return $"std::to_datetime({localParam}, {zoneParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDecimal.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDecimal.g.cs new file mode 100644 index 00000000..9c5eba06 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDecimal.g.cs @@ -0,0 +1,106 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdDecimal : MethodTranslator + { + [MethodName(EdgeQL.Sum)] + public string Sum(string? sParam) + { + return $"std::sum({sParam})"; + } + + [MethodName(EdgeQL.Round)] + public string Round(string? valParam) + { + return $"std::round({valParam})"; + } + + [MethodName(EdgeQL.Round)] + public string Round(string? valParam, string? dParam) + { + return $"std::round({valParam}, {dParam})"; + } + + [MethodName(EdgeQL.DurationToSeconds)] + public string DurationToSeconds(string? durParam) + { + return $"std::duration_to_seconds({durParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam, string? stepParam) + { + return $"std::range_unpack({valParam}, {stepParam})"; + } + + [MethodName(EdgeQL.Mean)] + public string Mean(string? valsParam) + { + return $"math::mean({valsParam})"; + } + + [MethodName(EdgeQL.ToDecimal)] + public string ToDecimal(string? sParam, string? fmtParam) + { + return $"std::to_decimal({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.Ceil)] + public string Ceil(string? xParam) + { + return $"math::ceil({xParam})"; + } + + [MethodName(EdgeQL.Floor)] + public string Floor(string? xParam) + { + return $"math::floor({xParam})"; + } + + [MethodName(EdgeQL.Ln)] + public string Ln(string? xParam) + { + return $"math::ln({xParam})"; + } + + [MethodName(EdgeQL.Lg)] + public string Lg(string? xParam) + { + return $"math::lg({xParam})"; + } + + [MethodName(EdgeQL.Log)] + public string Log(string? xParam, string? baseParam) + { + return $"math::log({xParam}, base := {baseParam})"; + } + + [MethodName(EdgeQL.Stddev)] + public string Stddev(string? valsParam) + { + return $"math::stddev({valsParam})"; + } + + [MethodName(EdgeQL.StddevPop)] + public string StddevPop(string? valsParam) + { + return $"math::stddev_pop({valsParam})"; + } + + [MethodName(EdgeQL.Var)] + public string Var(string? valsParam) + { + return $"math::var({valsParam})"; + } + + [MethodName(EdgeQL.VarPop)] + public string VarPop(string? valsParam) + { + return $"math::var_pop({valsParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDuration.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDuration.g.cs new file mode 100644 index 00000000..3a316be1 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdDuration.g.cs @@ -0,0 +1,34 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdDuration : MethodTranslator + { + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.DurationTruncate)] + public string DurationTruncate(string? dtParam, string? unitParam) + { + return $"std::duration_truncate({dtParam}, {unitParam})"; + } + + [MethodName(EdgeQL.ToDuration)] + public string ToDuration(string? hoursParam, string? minutesParam, string? secondsParam, string? microsecondsParam) + { + return $"std::to_duration(hours := {hoursParam}, minutes := {minutesParam}, seconds := {secondsParam}, microseconds := {microsecondsParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdFloat32.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdFloat32.g.cs new file mode 100644 index 00000000..d4eafec4 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdFloat32.g.cs @@ -0,0 +1,28 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdFloat32 : MethodTranslator + { + [MethodName(EdgeQL.Sum)] + public string Sum(string? sParam) + { + return $"std::sum({sParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam, string? stepParam) + { + return $"std::range_unpack({valParam}, {stepParam})"; + } + + [MethodName(EdgeQL.ToFloat32)] + public string ToFloat32(string? sParam, string? fmtParam) + { + return $"std::to_float32({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdFloat64.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdFloat64.g.cs new file mode 100644 index 00000000..87b09429 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdFloat64.g.cs @@ -0,0 +1,178 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdFloat64 : MethodTranslator + { + [MethodName(EdgeQL.Sum)] + public string Sum(string? sParam) + { + return $"std::sum({sParam})"; + } + + [MethodName(EdgeQL.Random)] + public string Random() + { + return $"std::random()"; + } + + [MethodName(EdgeQL.Round)] + public string Round(string? valParam) + { + return $"std::round({valParam})"; + } + + [MethodName(EdgeQL.DatetimeGet)] + public string DatetimeGet(string? dtParam, string? elParam) + { + return $"std::datetime_get({dtParam}, {elParam})"; + } + + [MethodName(EdgeQL.DurationGet)] + public string DurationGet(string? dtParam, string? elParam) + { + return $"std::duration_get({dtParam}, {elParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam, string? stepParam) + { + return $"std::range_unpack({valParam}, {stepParam})"; + } + + [MethodName(EdgeQL.Mean)] + public string Mean(string? valsParam) + { + return $"math::mean({valsParam})"; + } + + [MethodName(EdgeQL.Mean)] + public string Mean(string? valsParam) + { + return $"math::mean({valsParam})"; + } + + [MethodName(EdgeQL.ToFloat64)] + public string ToFloat64(string? sParam, string? fmtParam) + { + return $"std::to_float64({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.Ceil)] + public string Ceil(string? xParam) + { + return $"math::ceil({xParam})"; + } + + [MethodName(EdgeQL.Floor)] + public string Floor(string? xParam) + { + return $"math::floor({xParam})"; + } + + [MethodName(EdgeQL.Ln)] + public string Ln(string? xParam) + { + return $"math::ln({xParam})"; + } + + [MethodName(EdgeQL.Ln)] + public string Ln(string? xParam) + { + return $"math::ln({xParam})"; + } + + [MethodName(EdgeQL.Lg)] + public string Lg(string? xParam) + { + return $"math::lg({xParam})"; + } + + [MethodName(EdgeQL.Lg)] + public string Lg(string? xParam) + { + return $"math::lg({xParam})"; + } + + [MethodName(EdgeQL.Stddev)] + public string Stddev(string? valsParam) + { + return $"math::stddev({valsParam})"; + } + + [MethodName(EdgeQL.Stddev)] + public string Stddev(string? valsParam) + { + return $"math::stddev({valsParam})"; + } + + [MethodName(EdgeQL.StddevPop)] + public string StddevPop(string? valsParam) + { + return $"math::stddev_pop({valsParam})"; + } + + [MethodName(EdgeQL.StddevPop)] + public string StddevPop(string? valsParam) + { + return $"math::stddev_pop({valsParam})"; + } + + [MethodName(EdgeQL.Var)] + public string Var(string? valsParam) + { + return $"math::var({valsParam})"; + } + + [MethodName(EdgeQL.Var)] + public string Var(string? valsParam) + { + return $"math::var({valsParam})"; + } + + [MethodName(EdgeQL.VarPop)] + public string VarPop(string? valsParam) + { + return $"math::var_pop({valsParam})"; + } + + [MethodName(EdgeQL.VarPop)] + public string VarPop(string? valsParam) + { + return $"math::var_pop({valsParam})"; + } + + [MethodName(EdgeQL.TimeGet)] + public string TimeGet(string? dtParam, string? elParam) + { + return $"cal::time_get({dtParam}, {elParam})"; + } + + [MethodName(EdgeQL.DateGet)] + public string DateGet(string? dtParam, string? elParam) + { + return $"cal::date_get({dtParam}, {elParam})"; + } + + [MethodName(EdgeQL.DatetimeGet)] + public string DatetimeGet(string? dtParam, string? elParam) + { + return $"std::datetime_get({dtParam}, {elParam})"; + } + + [MethodName(EdgeQL.DurationGet)] + public string DurationGet(string? dtParam, string? elParam) + { + return $"std::duration_get({dtParam}, {elParam})"; + } + + [MethodName(EdgeQL.DurationGet)] + public string DurationGet(string? dtParam, string? elParam) + { + return $"std::duration_get({dtParam}, {elParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt16.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt16.g.cs new file mode 100644 index 00000000..b5013c87 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt16.g.cs @@ -0,0 +1,52 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdInt16 : MethodTranslator + { + [MethodName(EdgeQL.BitAnd)] + public string BitAnd(string? lParam, string? rParam) + { + return $"std::bit_and({lParam}, {rParam})"; + } + + [MethodName(EdgeQL.BitOr)] + public string BitOr(string? lParam, string? rParam) + { + return $"std::bit_or({lParam}, {rParam})"; + } + + [MethodName(EdgeQL.BitXor)] + public string BitXor(string? lParam, string? rParam) + { + return $"std::bit_xor({lParam}, {rParam})"; + } + + [MethodName(EdgeQL.BitNot)] + public string BitNot(string? rParam) + { + return $"std::bit_not({rParam})"; + } + + [MethodName(EdgeQL.BitRshift)] + public string BitRshift(string? valParam, string? nParam) + { + return $"std::bit_rshift({valParam}, {nParam})"; + } + + [MethodName(EdgeQL.BitLshift)] + public string BitLshift(string? valParam, string? nParam) + { + return $"std::bit_lshift({valParam}, {nParam})"; + } + + [MethodName(EdgeQL.ToInt16)] + public string ToInt16(string? sParam, string? fmtParam) + { + return $"std::to_int16({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt32.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt32.g.cs new file mode 100644 index 00000000..72f6b8c9 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt32.g.cs @@ -0,0 +1,64 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdInt32 : MethodTranslator + { + [MethodName(EdgeQL.BitAnd)] + public string BitAnd(string? lParam, string? rParam) + { + return $"std::bit_and({lParam}, {rParam})"; + } + + [MethodName(EdgeQL.BitOr)] + public string BitOr(string? lParam, string? rParam) + { + return $"std::bit_or({lParam}, {rParam})"; + } + + [MethodName(EdgeQL.BitXor)] + public string BitXor(string? lParam, string? rParam) + { + return $"std::bit_xor({lParam}, {rParam})"; + } + + [MethodName(EdgeQL.BitNot)] + public string BitNot(string? rParam) + { + return $"std::bit_not({rParam})"; + } + + [MethodName(EdgeQL.BitRshift)] + public string BitRshift(string? valParam, string? nParam) + { + return $"std::bit_rshift({valParam}, {nParam})"; + } + + [MethodName(EdgeQL.BitLshift)] + public string BitLshift(string? valParam, string? nParam) + { + return $"std::bit_lshift({valParam}, {nParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam) + { + return $"std::range_unpack({valParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam, string? stepParam) + { + return $"std::range_unpack({valParam}, {stepParam})"; + } + + [MethodName(EdgeQL.ToInt32)] + public string ToInt32(string? sParam, string? fmtParam) + { + return $"std::to_int32({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt64.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt64.g.cs new file mode 100644 index 00000000..c58b0f41 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdInt64.g.cs @@ -0,0 +1,160 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdInt64 : MethodTranslator + { + [MethodName(EdgeQL.Len)] + public string Len(string? strParam) + { + return $"std::len({strParam})"; + } + + [MethodName(EdgeQL.Len)] + public string Len(string? bytesParam) + { + return $"std::len({bytesParam})"; + } + + [MethodName(EdgeQL.Len)] + public string Len(string? arrayParam) + { + return $"std::len({arrayParam})"; + } + + [MethodName(EdgeQL.Sum)] + public string Sum(string? sParam) + { + return $"std::sum({sParam})"; + } + + [MethodName(EdgeQL.Sum)] + public string Sum(string? sParam) + { + return $"std::sum({sParam})"; + } + + [MethodName(EdgeQL.Count)] + public string Count(string? sParam) + { + return $"std::count({sParam})"; + } + + [MethodName(EdgeQL.Round)] + public string Round(string? valParam) + { + return $"std::round({valParam})"; + } + + [MethodName(EdgeQL.Find)] + public string Find(string? haystackParam, string? needleParam) + { + return $"std::find({haystackParam}, {needleParam})"; + } + + [MethodName(EdgeQL.Find)] + public string Find(string? haystackParam, string? needleParam) + { + return $"std::find({haystackParam}, {needleParam})"; + } + + [MethodName(EdgeQL.Find)] + public string Find(string? haystackParam, string? needleParam, string? from_posParam) + { + return $"std::find({haystackParam}, {needleParam}, {from_posParam})"; + } + + [MethodName(EdgeQL.BitAnd)] + public string BitAnd(string? lParam, string? rParam) + { + return $"std::bit_and({lParam}, {rParam})"; + } + + [MethodName(EdgeQL.BitOr)] + public string BitOr(string? lParam, string? rParam) + { + return $"std::bit_or({lParam}, {rParam})"; + } + + [MethodName(EdgeQL.BitXor)] + public string BitXor(string? lParam, string? rParam) + { + return $"std::bit_xor({lParam}, {rParam})"; + } + + [MethodName(EdgeQL.BitNot)] + public string BitNot(string? rParam) + { + return $"std::bit_not({rParam})"; + } + + [MethodName(EdgeQL.BitRshift)] + public string BitRshift(string? valParam, string? nParam) + { + return $"std::bit_rshift({valParam}, {nParam})"; + } + + [MethodName(EdgeQL.BitLshift)] + public string BitLshift(string? valParam, string? nParam) + { + return $"std::bit_lshift({valParam}, {nParam})"; + } + + [MethodName(EdgeQL.BytesGetBit)] + public string BytesGetBit(string? bytesParam, string? numParam) + { + return $"std::bytes_get_bit({bytesParam}, {numParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam) + { + return $"std::range_unpack({valParam})"; + } + + [MethodName(EdgeQL.RangeUnpack)] + public string RangeUnpack(string? valParam, string? stepParam) + { + return $"std::range_unpack({valParam}, {stepParam})"; + } + + [MethodName(EdgeQL.ToInt64)] + public string ToInt64(string? sParam, string? fmtParam) + { + return $"std::to_int64({sParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.SequenceReset)] + public string SequenceReset(string? seqParam, string? valueParam) + { + return $"std::sequence_reset({seqParam}, {valueParam})"; + } + + [MethodName(EdgeQL.SequenceReset)] + public string SequenceReset(string? seqParam) + { + return $"std::sequence_reset({seqParam})"; + } + + [MethodName(EdgeQL.SequenceNext)] + public string SequenceNext(string? seqParam) + { + return $"std::sequence_next({seqParam})"; + } + + [MethodName(EdgeQL.Ceil)] + public string Ceil(string? xParam) + { + return $"math::ceil({xParam})"; + } + + [MethodName(EdgeQL.Floor)] + public string Floor(string? xParam) + { + return $"math::floor({xParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdJson.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdJson.g.cs new file mode 100644 index 00000000..2a06855c --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdJson.g.cs @@ -0,0 +1,34 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdJson : MethodTranslator + { + [MethodName(EdgeQL.JsonArrayUnpack)] + public string JsonArrayUnpack(string? arrayParam) + { + return $"std::json_array_unpack({arrayParam})"; + } + + [MethodName(EdgeQL.JsonGet)] + public string JsonGet(string? jsonParam, string? pathParam, string? defaultParam) + { + return $"std::json_get({jsonParam}, {pathParam}, default := {defaultParam})"; + } + + [MethodName(EdgeQL.ToJson)] + public string ToJson(string? strParam) + { + return $"std::to_json({strParam})"; + } + + [MethodName(EdgeQL.GetConfigJson)] + public string GetConfigJson(string? sourcesParam, string? max_sourceParam) + { + return $"cfg::get_config_json(sources := {sourcesParam}, max_source := {max_sourceParam})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdStr.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdStr.g.cs new file mode 100644 index 00000000..379e383d --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdStr.g.cs @@ -0,0 +1,220 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdStr : MethodTranslator + { + [MethodName(EdgeQL.Min)] + public string Min(string? valsParam) + { + return $"std::min({valsParam})"; + } + + [MethodName(EdgeQL.Max)] + public string Max(string? valsParam) + { + return $"std::max({valsParam})"; + } + + [MethodName(EdgeQL.ArrayJoin)] + public string ArrayJoin(string? arrayParam, string? delimiterParam) + { + return $"std::array_join({arrayParam}, {delimiterParam})"; + } + + [MethodName(EdgeQL.JsonTypeof)] + public string JsonTypeof(string? jsonParam) + { + return $"std::json_typeof({jsonParam})"; + } + + [MethodName(EdgeQL.ReReplace)] + public string ReReplace(string? patternParam, string? subParam, string? strParam, string? flagsParam) + { + return $"std::re_replace({patternParam}, {subParam}, {strParam}, flags := {flagsParam})"; + } + + [MethodName(EdgeQL.StrRepeat)] + public string StrRepeat(string? sParam, string? nParam) + { + return $"std::str_repeat({sParam}, {nParam})"; + } + + [MethodName(EdgeQL.StrLower)] + public string StrLower(string? sParam) + { + return $"std::str_lower({sParam})"; + } + + [MethodName(EdgeQL.StrUpper)] + public string StrUpper(string? sParam) + { + return $"std::str_upper({sParam})"; + } + + [MethodName(EdgeQL.StrTitle)] + public string StrTitle(string? sParam) + { + return $"std::str_title({sParam})"; + } + + [MethodName(EdgeQL.StrPadStart)] + public string StrPadStart(string? sParam, string? nParam, string? fillParam) + { + return $"std::str_pad_start({sParam}, {nParam}, {fillParam})"; + } + + [MethodName(EdgeQL.StrLpad)] + public string StrLpad(string? sParam, string? nParam, string? fillParam) + { + return $"std::str_lpad({sParam}, {nParam}, {fillParam})"; + } + + [MethodName(EdgeQL.StrPadEnd)] + public string StrPadEnd(string? sParam, string? nParam, string? fillParam) + { + return $"std::str_pad_end({sParam}, {nParam}, {fillParam})"; + } + + [MethodName(EdgeQL.StrRpad)] + public string StrRpad(string? sParam, string? nParam, string? fillParam) + { + return $"std::str_rpad({sParam}, {nParam}, {fillParam})"; + } + + [MethodName(EdgeQL.StrTrimStart)] + public string StrTrimStart(string? sParam, string? trParam) + { + return $"std::str_trim_start({sParam}, {trParam})"; + } + + [MethodName(EdgeQL.StrLtrim)] + public string StrLtrim(string? sParam, string? trParam) + { + return $"std::str_ltrim({sParam}, {trParam})"; + } + + [MethodName(EdgeQL.StrTrimEnd)] + public string StrTrimEnd(string? sParam, string? trParam) + { + return $"std::str_trim_end({sParam}, {trParam})"; + } + + [MethodName(EdgeQL.StrRtrim)] + public string StrRtrim(string? sParam, string? trParam) + { + return $"std::str_rtrim({sParam}, {trParam})"; + } + + [MethodName(EdgeQL.StrTrim)] + public string StrTrim(string? sParam, string? trParam) + { + return $"std::str_trim({sParam}, {trParam})"; + } + + [MethodName(EdgeQL.StrReplace)] + public string StrReplace(string? sParam, string? oldParam, string? newParam) + { + return $"std::str_replace({sParam}, {oldParam}, {newParam})"; + } + + [MethodName(EdgeQL.StrReverse)] + public string StrReverse(string? sParam) + { + return $"std::str_reverse({sParam})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? dtParam, string? fmtParam) + { + return $"std::to_str({dtParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? tdParam, string? fmtParam) + { + return $"std::to_str({tdParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? iParam, string? fmtParam) + { + return $"std::to_str({iParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? fParam, string? fmtParam) + { + return $"std::to_str({fParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? dParam, string? fmtParam) + { + return $"std::to_str({dParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? dParam, string? fmtParam) + { + return $"std::to_str({dParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? arrayParam, string? delimiterParam) + { + return $"std::to_str({arrayParam}, {delimiterParam})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? jsonParam, string? fmtParam) + { + return $"std::to_str({jsonParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.GetVersionAsStr)] + public string GetVersionAsStr() + { + return $"sys::get_version_as_str()"; + } + + [MethodName(EdgeQL.GetInstanceName)] + public string GetInstanceName() + { + return $"sys::get_instance_name()"; + } + + [MethodName(EdgeQL.GetCurrentDatabase)] + public string GetCurrentDatabase() + { + return $"sys::get_current_database()"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? dtParam, string? fmtParam) + { + return $"std::to_str({dtParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? dParam, string? fmtParam) + { + return $"std::to_str({dParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? ntParam, string? fmtParam) + { + return $"std::to_str({ntParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + [MethodName(EdgeQL.ToStr)] + public string ToStr(string? rdParam, string? fmtParam) + { + return $"std::to_str({rdParam}, {(fmtParam is not null ? "fmtParam, " : "")})"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdUuid.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdUuid.g.cs new file mode 100644 index 00000000..6927eb88 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/StdUuid.g.cs @@ -0,0 +1,22 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class StdUuid : MethodTranslator + { + [MethodName(EdgeQL.UuidGenerateV1mc)] + public string UuidGenerateV1mc() + { + return $"std::uuid_generate_v1mc()"; + } + + [MethodName(EdgeQL.UuidGenerateV4)] + public string UuidGenerateV4() + { + return $"std::uuid_generate_v4()"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/SysTransactionisolation.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/SysTransactionisolation.g.cs new file mode 100644 index 00000000..a0e7d811 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/SysTransactionisolation.g.cs @@ -0,0 +1,16 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class SysTransactionisolation : MethodTranslator + { + [MethodName(EdgeQL.GetTransactionIsolation)] + public string GetTransactionIsolation() + { + return $"sys::get_transaction_isolation()"; + } + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Tuple.g.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Tuple.g.cs new file mode 100644 index 00000000..fcdd6e56 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/Generated/Tuple.g.cs @@ -0,0 +1,28 @@ +using EdgeDB; +using EdgeDB.DataTypes; +using System.Runtime.CompilerServices; + +namespace EdgeDB.Translators +{ + internal partial class Tuple : MethodTranslator + { + [MethodName(EdgeQL.Enumerate)] + public string Enumerate(string? valsParam) + { + return $"std::enumerate({valsParam})"; + } + + [MethodName(EdgeQL.JsonObjectUnpack)] + public string JsonObjectUnpack(string? objParam) + { + return $"std::json_object_unpack({objParam})"; + } + + [MethodName(EdgeQL.GetVersion)] + public string GetVersion() + { + return $"sys::get_version()"; + } + + } +} diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs index 23c89ca5..ca0ce1f2 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs @@ -58,6 +58,7 @@ private class ParsedParameter public string? Type { get; init; } public string[] Generics { get; set; } = Array.Empty(); public List GenericConditions { get; set; } = new(); + public string? DefaultValue { get; set; } = "{}"; } private static async ValueTask ProcessGroup(string groupType, IEnumerable funcs) @@ -111,25 +112,28 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable x.Generics.Any()).SelectMany(x => x.Generics); - - if (parameterGenerics.Count() == dotnetReturnType.Generics.Count()) + + var strongMappedParameters = string.Join(", ", parsedParameters.Select((x, i) => { - // prefer the name of the parameters - dotnetReturnType.Generics = parameterGenerics.ToArray(); - - // TODO: change return type if its generic - } + var t = $"{x.Type} {(_keywords.TryGetValue(x.Name!, out var p) ? p : x.Name)}"; + var param = parameters.ElementAt(i); + if (!string.IsNullOrEmpty(x.DefaultValue)) + { + t += " = " + x.DefaultValue switch + { + "{}" => x.Generics.Any() || (param.Value.Node.DotnetType?.IsValueType ?? false) ? "default" : "null", + _ => x.DefaultValue + }; + } - var strongMappedParameters = string.Join(", ", parsedParameters.Select(x => $"{x.Type} {(_keywords.TryGetValue(x.Name, out var p) ? p : x.Name)}")); + return t; + })); + var parsedMappedParameters = string.Join(", ", parameters.Select(x => $"string? {x!.Value.Parameter.Name}Param")); writer.AppendLine($"[MethodName(EdgeQL.{funcName})]"); @@ -163,13 +167,13 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable x.Generics!.Any()).SelectMany(x => x.Generics!))); + var formattedGenerics = string.Join(", ", dotnetReturnType.Generics.Concat(parsedParameters.Where(x => x.Generics!.Any()).SelectMany(x => x.Generics!)).Distinct()); - var genKey = $"{dotnetReturnType.Type}{funcName}{(formattedGenerics.Any() ? $"<{formattedGenerics}>" : "")}({string.Join(", ", parsedParameters.Select(x => x.Type))})"; + var genKey = $"{(dotnetReturnType.Generics.Any() ? "`1" : dotnetReturnType.Type)}{funcName}{(formattedGenerics.Any() ? $"<`{formattedGenerics.Count()}>" : "")}({string.Join(", ", parsedParameters.Select(x => x.Generics.Any() ? "`1" : x.Type))})"; if(!_generatedPublicFuncs.Contains(genKey)) { _edgeqlClassWriter!.AppendLine($"public static {dotnetReturnType.Type} {funcName}{(formattedGenerics.Any() ? $"<{formattedGenerics}>" : "")}({strongMappedParameters})"); - foreach(var c in parsedParameters.Where(x => x.GenericConditions.Any()).SelectMany(x => x.GenericConditions)) + foreach(var c in parsedParameters.Where(x => x.GenericConditions.Any()).SelectMany(x => x.GenericConditions).Concat(dotnetReturnType.GenericConditions).Distinct()) { _edgeqlClassWriter.AppendLine($" {c}"); } @@ -196,25 +200,25 @@ private static async ValueTask ProcessGroup(string groupType, IEnumerable ParseParameter(string? name, TypeNode node, Models.Type type, TypeModifier? modifier, int index = 0, int subIndex = 0) { - var genericName = name is not null ? $"T{_textInfo.ToTitleCase(name!.Replace("_", " ")).Replace(" ", "")}" : null; if (node.IsGeneric) { - var tname = genericName ?? $"T{index}S{subIndex}"; + var tname = $"T{_textInfo.ToTitleCase(Regex.Match(node.EdgeDBName, @"(?>.+?::|^)(.*?)$").Groups[1].Value.Replace("any", ""))}"; + var tModified = tname; if (modifier.HasValue) { switch (modifier.Value) { case TypeModifier.OptionalType: - tname += "?"; + tModified += "?"; break; case TypeModifier.SetOfType: - tname = $"IEnumerable<{tname}>"; + tModified = $"IEnumerable<{tname}>"; break; default: break; } } - return new ParsedParameter() { Name = name, Generics = new string[] { genericName ?? $"T{index}S{subIndex}" }, Type = tname }; + return new ParsedParameter() { Name = name, Generics = new string[] { tname }, Type = tModified }; } var typeName = node.DotnetTypeName ?? await GenerateType(node); @@ -333,8 +337,19 @@ private static async ValueTask GenerateType(TypeNode node) private static readonly Regex _typeCastOne = new(@"(<[^<]*?>)"); private static async Task ParseDefaultAsync(string @default) { - var result = await _client!.QuerySingleAsync($"select {@default}"); - return result?.ToString() ?? "null"; + try + { + var result = await _client!.QuerySingleAsync($"select {@default}"); + return result switch + { + bool b => b.ToString().ToLower(), + _ => QueryUtils.ParseObject(result), + }; + } + catch(Exception x) + { + throw; + } } } } diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs index 561f917c..9299c3e0 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs @@ -5,6 +5,7 @@ var edgedb = new EdgeDBClient(); //var operators = await QueryBuilder.Select().Filter(x => !x.IsAbstract).ExecuteAsync(edgedb); +var t = QueryBuilder.Select().Filter(x => x.BuiltIn).Build().Prettify(); var functions = await QueryBuilder.Select().Filter(x => x.BuiltIn).ExecuteAsync(edgedb)!; var writer = new CodeWriter(); diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs index 0870bafb..8d0cb7ce 100644 --- a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs @@ -145,7 +145,7 @@ public static bool TryGetType(string t, [MaybeNullWhen(false)] out TypeNode type Type? wrapperType = match.Groups[1].Value switch { - "tuple" => typeof(ITuple), + "tuple" => typeof(ValueTuple<>), "array" => typeof(IEnumerable<>), "set" => typeof(IEnumerable<>), "range" => typeof(Range<>), From baff43390270cfe4baad29d9475edab67e9d43a8 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 9 Aug 2022 21:11:29 -0300 Subject: [PATCH 53/70] fix update not having setters --- .../EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs | 9 ++++++++- .../Interfaces/Queries/IInsertQuery.cs | 2 +- .../Translators/Expressions/InitializationTranslator.cs | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index c676fd78..f1b54176 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -62,7 +62,14 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var test = await QueryBuilder.Insert(new ConstraintPerson { Email = "test", Name = "test" }).UnlessConflict().BuildAsync(client); + var test = (await QueryBuilder + .Insert(new ConstraintPerson { Email = "test", Name = "test" }) + .UnlessConflict() + .Else(x => x.Update(old => new ConstraintPerson + { + Name = old == null ? "test" : old.Name + "new" + })) + .BuildAsync(client)).Prettify(); // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs index 9bf4a241..5db101e7 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs @@ -22,7 +22,7 @@ public interface IInsertQuery : ISingleCardinalityExecutable /// when this query executes. /// /// The current query. - IUnlessConflictOn UnlessConflict(); + IUnlessConflictOn UnlessConflict(); /// /// Adds an UNLESS CONFLICT ON statement with the given property selector. diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs index d1af2f64..5b43fe05 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs @@ -93,7 +93,9 @@ internal static class InitializationTranslator x.IsShape = false; }); string? value = ExpressionTranslator.ContextualTranslate(Expression, newContext); - bool isSetter = context.NodeContext is InsertContext || context.NodeContext.CurrentType.GetProperty(Member.Name) == null || Expression is MethodCallExpression; + bool isSetter = context.NodeContext is InsertContext or UpdateContext || + context.NodeContext.CurrentType.GetProperty(Member.Name) == null || + Expression is MethodCallExpression; // add it to our shape if (value is null) // include From fadb080a1c2e74524f150edaa2e043b9e3bda7a5 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 9 Aug 2022 22:02:39 -0300 Subject: [PATCH 54/70] fix nullability errors --- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 4 ++-- src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 74317489..4081ff1f 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -523,7 +523,7 @@ private QueryBuilder LimitExp(LambdaExpression limit) /// /// The current node does not support unless conflict on statements. /// - private QueryBuilder UnlessConflict() + private QueryBuilder UnlessConflict() { if (CurrentUserNode is not InsertNode insertNode) throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); @@ -658,7 +658,7 @@ ISelectQuery ISelectQuery.Limit(Expression IInsertQuery.UnlessConflict() + IUnlessConflictOn IInsertQuery.UnlessConflict() => UnlessConflict(); IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) => UnlessConflictOn(propertySelector); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs index 0b48254d..7cbf2852 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -59,12 +59,12 @@ internal QueryableCollection(IEdgeDBQueryable edgedb) /// The factory to update the item. /// A cancellation token to cancel the asynchronous insert operation. /// The added value. - public Task AddOrUpdateAsync(TType item, Expression> updateFactory, CancellationToken token = default) + public Task AddOrUpdateAsync(TType item, Expression> updateFactory, CancellationToken token = default) => QueryBuilder .Insert(item) .UnlessConflict() .Else(b => - (Interfaces.ISingleCardinalityExecutable)b.Update(updateFactory) + (Interfaces.ISingleCardinalityExecutable)b.Update(updateFactory) ) .ExecuteAsync(_edgedb, token: token); @@ -88,7 +88,7 @@ internal QueryableCollection(IEdgeDBQueryable edgedb) .Insert(item) .UnlessConflict() .Else(q => - (Interfaces.ISingleCardinalityExecutable)q.Update(updateFactory!, false) + (ISingleCardinalityExecutable)q.Update(updateFactory!, false) ).ExecuteAsync(_edgedb, token: token).ConfigureAwait(false); } From 4072925c1ad3d72a7cb488a9f281264054a57ae1 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 9 Aug 2022 22:06:10 -0300 Subject: [PATCH 55/70] Move IQueryBuilder interface --- .../Interfaces/IQueryBuilder.cs | 171 ++++++++++++++++++ src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 163 ----------------- 2 files changed, 171 insertions(+), 163 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs new file mode 100644 index 00000000..7dae61a5 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs @@ -0,0 +1,171 @@ +using EdgeDB.Interfaces; +using EdgeDB.Interfaces.Queries; +using EdgeDB.QueryNodes; +using System.Linq.Expressions; + +namespace EdgeDB +{ + /// + /// Represents a generic query builder for querying against . + /// + /// The type of which queries will be preformed with. + public interface IQueryBuilder : + IQueryBuilder, + ISelectQuery, + IUpdateQuery, + IDeleteQuery, + IUnlessConflictOn, + IInsertQuery + { + /// + /// Adds a value to a WITH statement. + /// + /// + /// This value can be used later within queries by using a lambda with the object, + /// then calling . + /// + /// The name of the value. + /// The value to add. + /// + /// The current query builder. + /// + IQueryBuilder With(string name, object? value); + + /// + /// Adds a SELECT statement selecting the current with a autogenerated shape. + /// + /// + /// A . + /// + ISelectQuery Select(); + + /// + /// Adds a SELECT statement selecting the provided expression. + /// + /// The return result of the select expression. + /// The selecting expression. + /// + /// A . + /// + ISelectQuery Select(Expression> selectFunc); + + /// + /// Adds a SELECT statement selecting the current with the given shape. + /// + /// + /// To define a shape, use to include a property. any other + /// methods/values will be treated as computed values. + /// + /// The shape to select. + /// + /// A . + /// + ISelectQuery Select(Expression> shape); + + /// + /// Adds a SELECT statement selecting the type with the given shape. + /// + /// + /// To define a shape, use to include a property. any other + /// methods/values will be treated as computed values. + /// + /// The type to select. + /// The shape to select. + /// + /// A . + /// + ISelectQuery Select(Expression> shape); + + /// + /// Adds a INSERT statement inserting an instance of . + /// + /// The value to insert. + /// + /// whether or not to implicitly add a select statement to return the inserted value. + /// + /// A . + IInsertQuery Insert(TType value, bool returnInsertedValue = true); + + /// + /// Adds a INSERT statement inserting an instance of . + /// + /// 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 = true); + + /// + /// Adds a UPDATE statement updating an instance of . + /// + /// + /// The callback used to update . The first parameter is the old value. + /// + /// + /// whether or not to implicitly add a select statement to return the inserted value. + /// + /// A . + IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true); + + /// + /// Adds a DELETE statement deleting an instance of . + /// + IDeleteQuery Delete { get; } + } + + + /// + /// Represents a generic query builder with a build function. + /// + public interface IQueryBuilder + { + /// + /// 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 IReadOnlyCollection Globals { get; } + + /// + /// Gets a read-only dictionary of query variables defined within the query builder. + /// + internal IReadOnlyDictionary Variables { get; } + + /// + /// Builds the current query. + /// + /// + /// If the query requires introspection please use + /// . + /// + /// + /// A . + /// + BuiltQuery Build(); + + /// + /// Builds the current query asynchronously, allowing database introspection. + /// + /// The client to preform introspection with. + /// A cancellation token to cancel the asynchronous operation. + /// A . + ValueTask BuildAsync(IEdgeDBQueryable edgedb, CancellationToken token = default); + + /// + /// Builds the current query builder into its + /// form and exlcudes globals from the query text and puts them in + /// . + /// + /// + /// A modifier delegate to change nodes behaviour before the finalizer is called. + /// + /// + /// A which is the current query this builder has constructed. + /// + internal BuiltQuery BuildWithGlobals(Action? preFinalizerModifier = null); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 4081ff1f..36cfbcbd 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -753,169 +753,6 @@ private async ValueTask IntrospectAndBuildAsync(IEdgeDBQueryable edg #endregion } - /// - /// Represents a generic query builder for querying against . - /// - /// The type of which queries will be preformed with. - public interface IQueryBuilder : - IQueryBuilder, - ISelectQuery, - IUpdateQuery, - IDeleteQuery, - IUnlessConflictOn, - IInsertQuery - { - /// - /// Adds a value to a WITH statement. - /// - /// - /// This value can be used later within queries by using a lambda with the object, - /// then calling . - /// - /// The name of the value. - /// The value to add. - /// - /// The current query builder. - /// - IQueryBuilder With(string name, object? value); - - /// - /// Adds a SELECT statement selecting the current with a autogenerated shape. - /// - /// - /// A . - /// - ISelectQuery Select(); - - /// - /// Adds a SELECT statement selecting the provided expression. - /// - /// The return result of the select expression. - /// The selecting expression. - /// - /// A . - /// - ISelectQuery Select(Expression> selectFunc); - - /// - /// Adds a SELECT statement selecting the current with the given shape. - /// - /// - /// To define a shape, use to include a property. any other - /// methods/values will be treated as computed values. - /// - /// The shape to select. - /// - /// A . - /// - ISelectQuery Select(Expression> shape); - - /// - /// Adds a SELECT statement selecting the type with the given shape. - /// - /// - /// To define a shape, use to include a property. any other - /// methods/values will be treated as computed values. - /// - /// The type to select. - /// The shape to select. - /// - /// A . - /// - ISelectQuery Select(Expression> shape); - - /// - /// Adds a INSERT statement inserting an instance of . - /// - /// The value to insert. - /// - /// whether or not to implicitly add a select statement to return the inserted value. - /// - /// A . - IInsertQuery Insert(TType value, bool returnInsertedValue = true); - - /// - /// Adds a INSERT statement inserting an instance of . - /// - /// 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 = true); - - /// - /// Adds a UPDATE statement updating an instance of . - /// - /// - /// The callback used to update . The first parameter is the old value. - /// - /// - /// whether or not to implicitly add a select statement to return the inserted value. - /// - /// A . - IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true); - - /// - /// Adds a DELETE statement deleting an instance of . - /// - IDeleteQuery Delete { get; } - } - - /// - /// Represents a generic query builder with a build function. - /// - public interface IQueryBuilder - { - /// - /// 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 IReadOnlyCollection Globals { get; } - - /// - /// Gets a read-only dictionary of query variables defined within the query builder. - /// - internal IReadOnlyDictionary Variables { get; } - - /// - /// Builds the current query. - /// - /// - /// If the query requires introspection please use - /// . - /// - /// - /// A . - /// - BuiltQuery Build(); - - /// - /// Builds the current query asynchronously, allowing database introspection. - /// - /// The client to preform introspection with. - /// A cancellation token to cancel the asynchronous operation. - /// A . - ValueTask BuildAsync(IEdgeDBQueryable edgedb, CancellationToken token = default); - - /// - /// Builds the current query builder into its - /// form and exlcudes globals from the query text and puts them in - /// . - /// - /// - /// A modifier delegate to change nodes behaviour before the finalizer is called. - /// - /// - /// A which is the current query this builder has constructed. - /// - internal BuiltQuery BuildWithGlobals(Action? preFinalizerModifier = null); - } - /// /// Represents a built query. /// From a4fcdf84445d999c0b0db617052424c638919b06 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 10 Aug 2022 01:40:52 -0300 Subject: [PATCH 56/70] initial group api --- .../Examples/QueryBuilder.cs | 18 ++++--- src/EdgeDB.Net.QueryBuilder/GroupBuilder.cs | 53 +++++++++++++++++++ .../Interfaces/IGroupable.cs | 17 ++++++ .../Interfaces/IQueryBuilder.cs | 5 +- .../Interfaces/Queries/IDeleteQuery.cs | 2 +- .../Interfaces/Queries/IGroupQuery.cs | 13 +++++ .../Interfaces/Queries/ISelectQuery.cs | 2 +- .../Interfaces/Queries/IUpdateQuery.cs | 2 +- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 20 +++++++ .../QueryNodes/Contexts/GroupContext.cs | 18 +++++++ .../QueryNodes/GroupNode.cs | 20 +++++++ 11 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/GroupBuilder.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/IGroupable.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IGroupQuery.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/GroupContext.cs create mode 100644 src/EdgeDB.Net.QueryBuilder/QueryNodes/GroupNode.cs diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index f1b54176..3898f7ba 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -62,14 +62,16 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var test = (await QueryBuilder - .Insert(new ConstraintPerson { Email = "test", Name = "test" }) - .UnlessConflict() - .Else(x => x.Update(old => new ConstraintPerson - { - Name = old == null ? "test" : old.Name + "new" - })) - .BuildAsync(client)).Prettify(); + var t = QueryBuilder + .Select() + .Group((person, builder) => + builder.Using(() => new + { + Test = "Test" + }) + .By(x => x.Test) + ); + // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.QueryBuilder/GroupBuilder.cs b/src/EdgeDB.Net.QueryBuilder/GroupBuilder.cs new file mode 100644 index 00000000..3001ad88 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/GroupBuilder.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + public abstract class BaseGroupBuilder + { + public Expression? UsingExpression { get; protected set; } + + public Expression? ByExpression { get; protected set; } + + internal BaseGroupBuilder() { } + internal BaseGroupBuilder(Expression @using) { UsingExpression = @using; } + } + public class GroupBuilder : BaseGroupBuilder + { + public GroupBuilder Using(Expression> @using) + => new(@using); + + public KeyedGroupBuilder By(Expression> keySelector) + => new(keySelector); + } + public class GroupBuilder : BaseGroupBuilder + { + public GroupBuilder(Expression @using) : base(@using) { } + + public KeyedContextGroupBuilder By(Expression> keySelector) + => new(keySelector, UsingExpression!); + + public KeyedContextGroupBuilder By(Expression> keySelector) + => new(keySelector, UsingExpression!); + } + public class KeyedGroupBuilder : BaseGroupBuilder + { + public KeyedGroupBuilder(Expression keySelector) + : base() + { + ByExpression = keySelector; + } + } + public class KeyedContextGroupBuilder : KeyedGroupBuilder + { + public KeyedContextGroupBuilder(Expression keySelector, Expression @using) + : base(keySelector) + { + UsingExpression = @using; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IGroupable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IGroupable.cs new file mode 100644 index 00000000..3bad461b --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IGroupable.cs @@ -0,0 +1,17 @@ +using EdgeDB.Interfaces.Queries; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces +{ + public interface IGroupable + { + IGroupQuery> GroupBy(Expression> propertySelector); + + IGroupQuery> Group(Expression>> groupBuilder); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs index 7dae61a5..5a2073d2 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs @@ -14,8 +14,10 @@ public interface IQueryBuilder : ISelectQuery, IUpdateQuery, IDeleteQuery, + IInsertQuery, IUnlessConflictOn, - IInsertQuery + IGroupQuery, + IGroupable { /// /// Adds a value to a WITH statement. @@ -114,7 +116,6 @@ public interface IQueryBuilder : IDeleteQuery Delete { get; } } - /// /// Represents a generic query builder with a build function. /// diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs index cc363eff..d38d579a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs @@ -11,7 +11,7 @@ namespace EdgeDB.Interfaces.Queries /// Represents a generic DELETE query used within a . /// /// The type which this DELETE query is querying against. - public interface IDeleteQuery : IMultiCardinalityExecutable + public interface IDeleteQuery : IGroupable, IMultiCardinalityExecutable { /// /// Filters the current delete query by the given predicate. diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IGroupQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IGroupQuery.cs new file mode 100644 index 00000000..f741d727 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IGroupQuery.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces.Queries +{ + public interface IGroupQuery : IMultiCardinalityExecutable + { + + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs index 6d58f7c2..c7fc38a4 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs @@ -11,7 +11,7 @@ namespace EdgeDB.Interfaces.Queries /// Represents a generic SELECT query used within a . /// /// The type which this SELECT query is querying against. - public interface ISelectQuery : IMultiCardinalityExecutable + public interface ISelectQuery : IGroupable, IMultiCardinalityExecutable { /// /// Filters the current select query by the given predicate. diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs index b81b36c6..dfdec7d9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs @@ -11,7 +11,7 @@ namespace EdgeDB.Interfaces.Queries /// Represents a generic UPDATE query used within a . /// /// The type which this UPDATE query is querying against. - public interface IUpdateQuery : IMultiCardinalityExecutable + public interface IUpdateQuery : IGroupable, IMultiCardinalityExecutable { /// /// Filters the current update query by the given predicate. diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 36cfbcbd..52e209f2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -707,6 +707,26 @@ IDeleteQuery IDeleteQuery.Limit(Expression LimitExp(limit); #endregion + #region IGroupable + IGroupQuery> IGroupable.GroupBy(Expression> propertySelector) + { + AddNode(new GroupContext(typeof(TType)) + { + PropertyExpression = propertySelector + }); + return EnterNewType>(); + } + + IGroupQuery> IGroupable.Group(Expression>> groupBuilder) + { + AddNode(new GroupContext(typeof(TType)) + { + BuilderExpression = groupBuilder + }); + return EnterNewType>(); + } + #endregion + /// /// Preforms introspection and then builds this query builder into a . /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/GroupContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/GroupContext.cs new file mode 100644 index 00000000..49ab1b5f --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/GroupContext.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class GroupContext : NodeContext + { + public Expression? PropertyExpression { get; init; } + public Expression? BuilderExpression { get; init; } + public GroupContext(Type currentType) : base(currentType) + { + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/GroupNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/GroupNode.cs new file mode 100644 index 00000000..2e93bbac --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/GroupNode.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + internal class GroupNode : QueryNode + { + public GroupNode(NodeBuilder builder) : base(builder) + { + } + + public override void Visit() + { + throw new NotImplementedException(); + } + } +} From 4e16edab31f206ef90dabfc45d29d7097ab84e88 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 14 Aug 2022 00:36:12 -0300 Subject: [PATCH 57/70] fix for iterator throwing exceptions --- dbschema/migrations/00013.edgeql | 168 +++++++++++++++ dbschema/migrations/00014.edgeql | 106 ++++++++++ .../Extensions/TypeExtensions.cs | 2 +- src/EdgeDB.Net.QueryBuilder/JsonVariable.cs | 4 +- .../QueryNodes/InsertNode.cs | 143 +------------ .../Schema/SchemaInfo.cs | 6 +- .../Utils/JsonUtils.cs | 197 ++++++++++++++++++ 7 files changed, 486 insertions(+), 140 deletions(-) create mode 100644 dbschema/migrations/00013.edgeql create mode 100644 dbschema/migrations/00014.edgeql create mode 100644 src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs diff --git a/dbschema/migrations/00013.edgeql b/dbschema/migrations/00013.edgeql new file mode 100644 index 00000000..37dcd504 --- /dev/null +++ b/dbschema/migrations/00013.edgeql @@ -0,0 +1,168 @@ +CREATE MIGRATION m1pnpccusnd6k465bvotfp64pgkmrsas3sbunubc2t2k2r3b3vp6va + ONTO m1mqksv77zeiafcinnoo4ppb4gpoxchrogef3etb6altobsj72qvca +{ + CREATE MODULE syzuna IF NOT EXISTS; + CREATE ABSTRACT TYPE syzuna::Auditable { + CREATE REQUIRED PROPERTY created_at -> std::datetime { + SET default := (std::datetime_of_statement()); + SET readonly := true; + }; + CREATE REQUIRED PROPERTY updated_at -> std::datetime { + SET default := (std::datetime_of_statement()); + }; + }; + CREATE TYPE syzuna::Conflict EXTENDING syzuna::Auditable { + CREATE REQUIRED PROPERTY conflict_type -> std::str; + CREATE REQUIRED PROPERTY faction1_name -> std::str; + CREATE REQUIRED PROPERTY faction1_stake -> std::str; + CREATE REQUIRED PROPERTY faction1_won_days -> std::int16; + CREATE REQUIRED PROPERTY faction2_name -> std::str; + CREATE REQUIRED PROPERTY faction2_stake -> std::str; + CREATE REQUIRED PROPERTY faction2_won_days -> std::int16; + CREATE REQUIRED PROPERTY status -> std::str; + }; + CREATE SCALAR TYPE syzuna::Happiness EXTENDING enum; + CREATE TYPE syzuna::BgsData EXTENDING syzuna::Auditable { + CREATE LINK conflict -> syzuna::Conflict; + CREATE REQUIRED PROPERTY active_states -> array; + CREATE REQUIRED PROPERTY happiness -> syzuna::Happiness; + CREATE REQUIRED PROPERTY influence -> std::float64; + CREATE REQUIRED PROPERTY pending_states -> array; + CREATE REQUIRED PROPERTY recovering_states -> array; + }; + CREATE TYPE syzuna::Commodity EXTENDING syzuna::Auditable { + CREATE REQUIRED PROPERTY category -> std::str; + CREATE REQUIRED PROPERTY commodity_id64 -> std::int64; + CREATE REQUIRED PROPERTY name -> std::str; + CREATE REQUIRED PROPERTY symbol -> std::str; + }; + CREATE TYPE syzuna::Faction EXTENDING syzuna::Auditable { + CREATE PROPERTY allegiance -> std::str; + CREATE REQUIRED PROPERTY eddb_id -> std::int64; + CREATE PROPERTY government -> std::str; + CREATE REQUIRED PROPERTY is_player_faction -> std::bool; + CREATE REQUIRED PROPERTY name -> std::str; + }; + CREATE TYPE syzuna::FactionPresence EXTENDING syzuna::Auditable { + CREATE REQUIRED PROPERTY is_native -> std::bool; + CREATE REQUIRED MULTI LINK bgs_data -> syzuna::BgsData { + CREATE CONSTRAINT std::exclusive; + }; + CREATE LINK faction -> syzuna::Faction; + CREATE REQUIRED PROPERTY is_active -> std::bool; + }; + CREATE TYPE syzuna::Market EXTENDING syzuna::Auditable { + CREATE PROPERTY market_id64 -> std::int64; + CREATE REQUIRED PROPERTY prohibited_commodities -> array; + }; + CREATE TYPE syzuna::MarketListing EXTENDING syzuna::Auditable { + CREATE REQUIRED LINK commodity -> syzuna::Commodity; + CREATE REQUIRED PROPERTY buy_price -> std::int32; + CREATE REQUIRED PROPERTY demand -> std::int32; + CREATE REQUIRED PROPERTY sell_price -> std::int32; + CREATE REQUIRED PROPERTY supply -> std::int32; + }; + CREATE TYPE syzuna::OutfittingModule EXTENDING syzuna::Auditable { + CREATE REQUIRED PROPERTY category -> std::str; + CREATE REQUIRED PROPERTY class -> std::str; + CREATE PROPERTY entitlement -> std::str; + CREATE PROPERTY guidance -> std::str; + CREATE PROPERTY mount -> std::str; + CREATE REQUIRED PROPERTY name -> std::str; + CREATE REQUIRED PROPERTY outfitting_module_id64 -> std::int64; + CREATE REQUIRED PROPERTY rating -> std::str; + CREATE PROPERTY ship -> std::str; + CREATE REQUIRED PROPERTY symbol -> std::str; + }; + CREATE TYPE syzuna::Outfitting EXTENDING syzuna::Auditable { + CREATE MULTI LINK modules -> syzuna::OutfittingModule; + CREATE PROPERTY market_id64 -> std::int64; + }; + CREATE TYPE syzuna::Ship EXTENDING syzuna::Auditable { + CREATE REQUIRED PROPERTY name -> std::str; + CREATE REQUIRED PROPERTY ship_id64 -> std::int64; + CREATE REQUIRED PROPERTY symbol -> std::str; + }; + CREATE TYPE syzuna::Shipyard EXTENDING syzuna::Auditable { + CREATE MULTI LINK ships -> syzuna::Ship; + CREATE PROPERTY market_id64 -> std::int64; + }; + CREATE TYPE syzuna::Station EXTENDING syzuna::Auditable { + CREATE LINK faction -> syzuna::Faction; + CREATE LINK market -> syzuna::Market { + CREATE CONSTRAINT std::exclusive; + }; + CREATE LINK outfitting -> syzuna::Outfitting { + CREATE CONSTRAINT std::exclusive; + }; + CREATE LINK shipyard -> syzuna::Shipyard { + CREATE CONSTRAINT std::exclusive; + }; + CREATE REQUIRED PROPERTY active_states -> array; + CREATE REQUIRED PROPERTY allegiance -> std::str; + CREATE REQUIRED PROPERTY distance_to_arrival -> std::float64; + CREATE REQUIRED PROPERTY government -> std::str; + CREATE PROPERTY market_id64 -> std::float64; + CREATE REQUIRED PROPERTY max_landing_pad_size -> std::str; + CREATE REQUIRED PROPERTY name -> std::str; + CREATE REQUIRED PROPERTY primary_economy -> std::str; + CREATE REQUIRED PROPERTY secondary_economy -> std::str; + CREATE REQUIRED PROPERTY services -> array; + CREATE REQUIRED PROPERTY station_type -> std::str; + }; + CREATE TYPE syzuna::StarSystem EXTENDING syzuna::Auditable { + CREATE LINK faction -> syzuna::Faction; + CREATE MULTI LINK faction_presences -> syzuna::FactionPresence; + CREATE LINK native_factions := (SELECT + .faction_presences + FILTER + (.is_native = true) + ); + CREATE MULTI LINK stations -> syzuna::Station; + CREATE PROPERTY active_states -> array; + CREATE PROPERTY allegiance -> std::str; + CREATE PROPERTY coord_x -> std::float64; + CREATE PROPERTY coord_y -> std::float64; + CREATE PROPERTY coordd_z -> std::float64; + CREATE REQUIRED PROPERTY eddb_id -> std::int64; + CREATE PROPERTY government -> std::str; + CREATE REQUIRED PROPERTY id64 -> std::int64; + CREATE REQUIRED PROPERTY name -> std::str; + CREATE PROPERTY population -> std::int64; + CREATE PROPERTY power_state -> std::str; + CREATE PROPERTY powers -> array; + CREATE PROPERTY primary_economy -> std::str; + CREATE PROPERTY secondary_economy -> std::str; + CREATE PROPERTY security -> std::str; + }; + ALTER TYPE syzuna::BgsData { + CREATE LINK faction_presence := (. syzuna::StarSystem; + }; + ALTER TYPE syzuna::FactionPresence { + CREATE LINK star_system -> syzuna::StarSystem; + }; + ALTER TYPE syzuna::Market { + CREATE MULTI LINK listings -> syzuna::MarketListing { + CREATE CONSTRAINT std::exclusive; + }; + CREATE LINK station := (.(); var name = attr?.Name ?? type.Name; - return attr != null ? $"{(attr.ModuleName != null ? $"{attr.ModuleName}::" : "")}{name}" : name; + return attr != null ? $"{(attr.ModuleName != null ? $"{attr.ModuleName}::" : "default::")}{name}" : name; } public static string GetEdgeDBPropertyName(this MemberInfo info) { diff --git a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs index 85f74743..b174a886 100644 --- a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs +++ b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs @@ -135,8 +135,10 @@ private int CalculateNodeDepth(JObject node, int depth = 0) { case JObject jObject: return CalculateNodeDepth(jObject, depth + 1); - case JArray jArray: + 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/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 05056f7f..7c388780 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -19,33 +19,6 @@ namespace EdgeDB.QueryNodes /// internal class InsertNode : QueryNode { - /// - /// Represents a node within a depth map. - /// - private readonly struct DepthNode - { - /// - /// The type of the node, this type represents the nodes value. - /// - public readonly Type Type; - - /// - /// The value of the node. - /// - public readonly JObject Node; - - /// - /// Constructs a new . - /// - /// The type of the node. - /// The node containing the value. - public DepthNode(Type type, JObject node) - { - Type = type; - Node = node; - } - } - /// /// Whether or not to autogenerate the unless conflict clause. /// @@ -62,48 +35,12 @@ public DepthNode(Type type, JObject node) /// private readonly List _subQueryMap = new(); - /// - /// The regex used to resolve json paths. - /// - private readonly Regex _pathResolverRegex = new(@"\[\d+?](?>\.(.*?)$|$)"); - /// public InsertNode(NodeBuilder builder) : base(builder) { _elseStatement = new(); } - /// - /// 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. - /// - private 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; - - // 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 = 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!; - - // return the final type. - return result; - } - /// /// Builds a json-based insert shape /// @@ -117,81 +54,13 @@ private string BuildJsonShape() var depth = jsonValue.Depth; // create a depth map that contains each nested level of types to be inserted - List[] depthMap = new List[depth + 1]; - - // iterate over the number of child types we have - for(int i = 0; i != depth; i++) - { - // get the elements at the current depth - var elements = jsonValue.GetObjectsAtDepth(i); - - var nodes = new List(); - - foreach(var element in elements) - { - // create a new json object we will use to mutate to our newer form - JObject mutableElement = new(); - - // enumerate over each property in the populated object and copy it to our mutable one - foreach(var prop in element.Properties()) - { - if (prop.Value is JObject jObject) - { - // if its a sub-object, add it to the next depth level - var mapIndex = i + 1; - - // resolve the objects link type from its path - var type = ResolveTypeFromPath(jsonValue.InnerType, prop.Path); - - if (depthMap[mapIndex] is null) - depthMap[mapIndex] = new List() - { new(type, jObject) }; - else - depthMap[mapIndex].Add(new(type, jObject)); - - // populate the mutable one with the location of the nested object - mutableElement[prop.Name] = new JObject() - { - new JProperty($"{mappingName}_depth_index", depthMap[mapIndex].Count - 1), - }; - } - 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 = i + 1; - - // resolve the objects link type from its path - var type = ResolveTypeFromPath(jsonValue.InnerType, prop.Path); - - if (depthMap[mapIndex] is null) - depthMap[mapIndex] = new List(jArray.Select(x => new DepthNode(type, (JObject)x))); - else - depthMap[mapIndex].AddRange(jArray.Select(x => new DepthNode(type, (JObject)x))); - - // populate the mutable one with the location of the nested object - mutableElement[prop.Name] = new JObject() - { - new JProperty($"{mappingName}_depth_from", (depthMap[mapIndex].Count) - jArray.Count), - new JProperty($"{mappingName}_depth_to", depthMap[mapIndex].Count) - }; - } - else - mutableElement.Add(prop); // add the property if its scalar - } - - // add the node to our node collection - nodes.Add(new(ResolveTypeFromPath(jsonValue.InnerType, element.Path), mutableElement)); - } - - // add the nodes collection to our depth map - depthMap[i] = nodes; - } - + IGrouping[] depthMap = JsonUtils.BuildDepthMap(mappingName, jsonValue).ToArray().GroupBy(x => x.Depth).ToArray(); + // generate the global maps - for(int i = depthMap.Length - 1; i != 0; i--) + for(int i = depth; i != 0; i--) { var map = depthMap[i]; - var node = map[0]; + var node = map.First(); var iterationName = QueryUtils.GenerateRandomVariableName(); var variableName = QueryUtils.GenerateRandomVariableName(); var isLast = depthMap.Length == i + 1; @@ -252,7 +121,7 @@ private string BuildJsonShape() RequiresIntrospection = true; // serialize this depths values and set the variable & global for the sub-query - var iterationJson = JsonConvert.SerializeObject(map.Select(x => x.Node)); + var iterationJson = JsonConvert.SerializeObject(map.Select(x => x.JsonNode)); SetVariable(variableName, new Json(iterationJson)); @@ -260,7 +129,7 @@ private string BuildJsonShape() } // replace the json variables content with the root depth map - SetVariable(jsonValue.VariableName, new Json(JsonConvert.SerializeObject(depthMap[0].Select(x => x.Node)))); + SetVariable(jsonValue.VariableName, new Json(JsonConvert.SerializeObject(depthMap[0].Select(x => x.JsonNode)))); // create the base insert shape var shape = jsonValue.InnerType.GetEdgeDBTargetProperties(excludeId: true).Select(x => diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs index 5145869f..edbdd99b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs @@ -40,6 +40,10 @@ public SchemaInfo(IReadOnlyCollection types) /// otherwise . /// public bool TryGetObjectInfo(Type type, [MaybeNullWhen(false)] out ObjectType info) - => (info = Types.FirstOrDefault(x => x.CleanedName == type.GetEdgeDBTypeName())) != null; + => (info = Types.FirstOrDefault(x => + { + var name = type.GetEdgeDBTypeName(); + return name == x.CleanedName || name == x.Name; + })) != null; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs new file mode 100644 index 00000000..001f57e6 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs @@ -0,0 +1,197 @@ +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 +{ + /// + /// Represents a node within a depth map. + /// + internal readonly struct DepthNode + { + /// + /// 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. + public DepthNode(Type type, JObject node, int depth) + { + Type = type; + JsonNode = node; + Depth = depth; + } + } + + internal struct NodeCollection : IEnumerable + { + private readonly Dictionary _depthIndex; + private readonly List _nodes; + + public NodeCollection() + { + _nodes = new(); + _depthIndex = new(); + } + + public void Add(DepthNode node) + { + if (_depthIndex.ContainsKey(node.Depth)) + _depthIndex[node.Depth]++; + else + _depthIndex[node.Depth] = 0; + + _nodes.Add(node); + } + + public void AddRange(IEnumerable nodes) + => _nodes.AddRange(nodes); + + public int GetNodeRelativeDepthIndex(DepthNode node) + => _depthIndex[node.Depth]; + + public int GetCurrentDepthIndex(int depth) + => _depthIndex.TryGetValue(depth, out var v) ? v : 0; + + public IEnumerator GetEnumerator() + => _nodes.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => _nodes.GetEnumerator(); + + public List ToList() + => _nodes; + } + + internal class JsonUtils + { + /// + /// The regex used to resolve json paths. + /// + private static readonly Regex _pathResolverRegex = new(@"\[\d+?](?>\.(.*?)$|$)"); + + public static List BuildDepthMap(string mappingName, IJsonVariable jsonVariable) + { + var elements = jsonVariable.GetObjectsAtDepth(0); + + var nodes = new NodeCollection(); + + foreach (var element in elements) + { + var type = ResolveTypeFromPath(jsonVariable.InnerType, element.Path); + var node = new DepthNode(type, element, 0); + nodes.Add(node); + GetNodes(mappingName, node, jsonVariable, nodes); + } + + return nodes.ToList(); + } + + private static void GetNodes(string mappingName, DepthNode node, IJsonVariable jsonValue, NodeCollection nodes) + { + var currentDepth = node.Depth; + + 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; + + // resolve the objects link type from its path + var type = ResolveTypeFromPath(jsonValue.InnerType, prop.Path); + + var childNode = new DepthNode(type, jObject, mapIndex); + nodes.Add(childNode); + + // get each sub node of the child + GetNodes(mappingName, childNode, jsonValue, nodes); + + // mutate the node + node.JsonNode[prop.Name] = new JObject() + { + new JProperty($"{mappingName}_depth_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; + + // 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(mappingName, subNode, jsonValue, nodes); + } + + // populate the mutable one with the location of the nested object + node.JsonNode[prop.Name] = new JObject() + { + new JProperty($"{mappingName}_depth_from", indx), + new JProperty($"{mappingName}_depth_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); + + // 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('.'); + + // 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(); + } + + if (EdgeDBTypeUtils.IsLink(result, out var isMultiLink, out var innerType) && isMultiLink) + return innerType!; + + // return the final type. + return result; + } + } +} From 57af0909eb463094a88cc9cc26aa21b9c040b16d Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 15 Aug 2022 02:29:30 -0300 Subject: [PATCH 58/70] fix missing param in summary --- src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs index 001f57e6..65323e1b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs @@ -35,6 +35,7 @@ internal readonly struct DepthNode /// /// 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; From 06530657a44f0795d54eb54011a5e9cc0e64c31c Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 15 Aug 2022 04:04:34 -0300 Subject: [PATCH 59/70] Fix some bugs with FOR and WITH node --- .../QueryNodes/ForNode.cs | 20 ++++++++++--------- .../QueryNodes/QueryNode.cs | 3 +++ .../QueryNodes/SelectNode.cs | 17 +++++++++++----- .../MethodCallExpressionTranslator.cs | 2 ++ 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs index 08ce247e..8eab31b1 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs @@ -58,13 +58,6 @@ _ when ReflectionUtils.IsSubTypeOfGenericType(typeof(JsonVariable<>), x.Type) // build and compile our lambda to get the query builder instance var builder = (IQueryBuilder)Context.Expression!.Compile().DynamicInvoke(parameters)!; - // copy the globals & variables to the current builder - foreach (var global in builder.Globals) - SetGlobal(global.Name, global.Value, global.Reference); - - foreach (var variable in builder.Variables) - SetVariable(variable.Key, variable.Value); - // add all nodes as sub nodes to this node SubNodes.AddRange(builder.Nodes); @@ -110,9 +103,18 @@ public override void FinalizeQuery() x.SchemaInfo = SchemaInfo; x.FinalizeQuery(); + var builtNode = x.Build(); + + foreach (var variable in builtNode.Parameters) + SetVariable(variable.Key, variable.Value); + + // copy the globals & variables to the current builder + foreach (var global in x.ReferencedGlobals) + SetGlobal(global.Name, global.Value, global.Reference); + // we don't need to copy variables or nodes here since we did that in the parse step - return x.Build().Query; - }).Aggregate((x, y) => $"{x} {y}"); + return builtNode.Query; + }).Where(x => !string.IsNullOrEmpty(x)).Aggregate((x, y) => $"{x} {y}"); // append union statement's content Query.Append($"({iterator})"); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index a4e6b70f..4122a59c 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -53,6 +53,8 @@ public bool IsAutoGenerated /// internal List SubNodes { get; } = new(); + internal List ReferencedGlobals { get; } = new(); + /// /// Gets the query string for this node. /// @@ -121,6 +123,7 @@ protected void SetGlobal(string name, object? value, object? reference) { var global = new QueryGlobal(name, value, reference); Builder.QueryGlobals.Add(global); + ReferencedGlobals.Add(global); } /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 8e18c614..166fac6d 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -29,7 +29,7 @@ public SelectNode(NodeBuilder builder) : base(builder) { } /// The type to get the shape for. /// The current depth of the shape. /// The shape of the given type. - private string GetShape(Type type, int currentDepth = 0) + private string? GetShape(Type type, int currentDepth = 0) { // get all properties that dont have the 'EdgeDBIgnore' attribute var properties = type.GetProperties().Where(x => x.GetCustomAttribute() == null); @@ -44,8 +44,11 @@ private string GetShape(Type type, int currentDepth = 0) if (EdgeDBTypeUtils.IsLink(x.PropertyType, out var isArray, out var innerType)) { var shapeType = isArray ? innerType! : x.PropertyType; - if(currentDepth < MAX_DEPTH) - return $"{name}: {GetShape(shapeType, currentDepth + 1)}"; + if (currentDepth < MAX_DEPTH) + { + var subShape = GetShape(shapeType, currentDepth + 1); + return subShape is not null ? $"{name}: {subShape}" : null; + } return null; } else // return just the name @@ -53,6 +56,10 @@ private string GetShape(Type type, int currentDepth = 0) }).Where(x => x is not null); // join our properties by commas and wrap it in braces + + if (!propertyNames.Any()) + return null; + return $"{{ {string.Join(", ", propertyNames)} }}"; } @@ -60,7 +67,7 @@ private string GetShape(Type type, int currentDepth = 0) /// Gets the default shape for the current contextual type. /// /// The default shape for the current contextual type. - private string GetDefaultShape() + private string? GetDefaultShape() => GetShape(OperatingType); /// @@ -68,7 +75,7 @@ private string GetDefaultShape() /// /// /// - private string GetShape() + private string? GetShape() { // if no user-defined shape was passed in, generate the default shape if(Context.Shape == null) diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index 22a64bba..75258bf8 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -55,6 +55,8 @@ private bool ShouldTranslate(MethodCallExpression expression, ExpressionContext { switch (expression.Method.Name) { + case nameof(QueryContext.Global): + return TranslateExpression(expression.Arguments[0], context.Enter(x => x.StringWithoutQuotes = true)); case nameof(QueryContext.Local): { // validate the type context, property should exist within the type. From bc367e1547d473516851ef64bafe589ea887fecb Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 17 Aug 2022 00:09:54 -0300 Subject: [PATCH 60/70] Implement #22 --- .../Examples/QueryBuilder.cs | 2 +- .../Interfaces/IQueryBuilder.cs | 29 +++++++- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 71 +++++++++++-------- .../QueryNodes/QueryNode.cs | 10 ++- .../QueryNodes/SelectNode.cs | 36 +++++++++- .../Methods/EnumerableMethodTranslator.cs | 2 +- .../Utils/EdgeDBTypeUtils.cs | 14 ++-- 7 files changed, 123 insertions(+), 41 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 3898f7ba..99fb1fa6 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -202,7 +202,7 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) }; var tquery = (await QueryBuilder.For(data, - x => QueryBuilder.Insert(x, false) + x => QueryBuilder.Insert(x) ).BuildAsync(client)); // Else statements (upsert demo) diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs index 5a2073d2..ea9120b6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs @@ -86,7 +86,14 @@ public interface IQueryBuilder : /// whether or not to implicitly add a select statement to return the inserted value. /// /// A . - IInsertQuery Insert(TType value, bool returnInsertedValue = true); + IInsertQuery Insert(TType value, bool returnInsertedValue); + + /// + /// Adds a INSERT statement inserting an instance of . + /// + /// The value to insert. + /// A . + IInsertQuery Insert(TType value); /// /// Adds a INSERT statement inserting an instance of . @@ -96,7 +103,14 @@ public interface IQueryBuilder : /// whether or not to implicitly add a select statement to return the inserted value. /// /// A . - IInsertQuery Insert(Expression> value, bool returnInsertedValue = true); + IInsertQuery Insert(Expression> value, bool returnInsertedValue); + + /// + /// Adds a INSERT statement inserting an instance of . + /// + /// The callback containing the value initialization to insert. + /// A . + IInsertQuery Insert(Expression> value); /// /// Adds a UPDATE statement updating an instance of . @@ -108,7 +122,16 @@ public interface IQueryBuilder : /// whether or not to implicitly add a select statement to return the inserted value. /// /// A . - IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true); + IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue); + + /// + /// Adds a UPDATE statement updating an instance of . + /// + /// + /// The callback used to update . The first parameter is the old value. + /// + /// A . + IUpdateQuery Update(Expression> updateFunc); /// /// Adds a DELETE statement deleting an instance of . diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 52e209f2..2a14f0aa 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -34,17 +34,29 @@ public static ISelectQuery Select(Expression new QueryBuilder().Select(shape); /// - public static IInsertQuery Insert(TType value, bool returnInsertedValue = true) + public static IInsertQuery Insert(TType value, bool returnInsertedValue) => new QueryBuilder().Insert(value, returnInsertedValue); + /// + public static IInsertQuery Insert(TType value) + => new QueryBuilder().Insert(value, false); + /// - public static IInsertQuery Insert(Expression> value, bool returnInsertedValue = true) + public static IInsertQuery Insert(Expression> value, bool returnInsertedValue) => new QueryBuilder().Insert(value, returnInsertedValue); - + + /// + public static IInsertQuery Insert(Expression> value) + => new QueryBuilder().Insert(value); + /// - public static IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true) + public static IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue) => new QueryBuilder().Update(updateFunc, returnUpdatedValue); + /// + public static IUpdateQuery Update(Expression> updateFunc) + => new QueryBuilder().Update(updateFunc, false); + /// public static IDeleteQuery Delete() => new QueryBuilder().Delete; @@ -154,13 +166,15 @@ private TNode AddNode(NodeContext context, bool autoGenerated = false, Qu // construct the node. var node = (TNode)Activator.CreateInstance(typeof(TNode), builder)!; + node.Parent = parent; + + parent?.SubNodes.Add(node); + // visit the node node.Visit(); _nodes.Add(node); - parent?.SubNodes.Add(node); - return node; } @@ -320,69 +334,63 @@ public ISelectQuery Select(Expression public IInsertQuery Insert(TType value, bool returnInsertedValue = true) { - var selectedGlobal = returnInsertedValue ? QueryUtils.GenerateRandomVariableName() : null; var insertNode = AddNode(new InsertContext(typeof(TType)) { Value = value, - SetAsGlobal = returnInsertedValue, - GlobalName = selectedGlobal }); if (returnInsertedValue) { - AddNode(new SelectContext(typeof(TType)) - { - SelectName = selectedGlobal, - }, true, insertNode); + AddNode(new SelectContext(typeof(TType)), true, insertNode); } return this; } + /// + public IInsertQuery Insert(TType value) + => Insert(value, false); + /// public IInsertQuery Insert(Expression> value, bool returnInsertedValue = true) { - var selectedGlobal = returnInsertedValue ? QueryUtils.GenerateRandomVariableName() : null; var insertNode = AddNode(new InsertContext(typeof(TType)) { Value = value, - SetAsGlobal = returnInsertedValue, - GlobalName = selectedGlobal }); if (returnInsertedValue) { - AddNode(new SelectContext(typeof(TType)) - { - SelectName = selectedGlobal, - }, true, insertNode); + AddNode(new SelectContext(typeof(TType)), true, insertNode); } return this; } /// - public IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue = true) + public IInsertQuery Insert(Expression> value) + => Insert(value, false); + + /// + public IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue) { - var selectedGlobal = returnUpdatedValue ? QueryUtils.GenerateRandomVariableName() : null; var updateNode = AddNode(new UpdateContext(typeof(TType)) { UpdateExpression = updateFunc, - SetAsGlobal = returnUpdatedValue, - GlobalName = selectedGlobal }); if (returnUpdatedValue) { - AddNode(new SelectContext(typeof(TType)) - { - SelectName = selectedGlobal, - }, true, updateNode); + AddNode(new SelectContext(typeof(TType)), true, updateNode); } return this; } + /// + public IUpdateQuery Update(Expression> updateFunc) + => Update(updateFunc, false); + /// public IDeleteQuery Delete { @@ -788,6 +796,13 @@ public class BuiltQuery /// Gets a collection of parameters for the query. /// public IDictionary? Parameters { get; internal init; } + + /// + /// Gets a prettified version of this query. + /// + public string Pretty + => Prettify(); + internal List? Globals { get; init; } /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs index 4122a59c..094772b1 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -53,6 +53,14 @@ public bool IsAutoGenerated /// 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(); /// @@ -75,7 +83,7 @@ internal NodeContext Context /// /// The operating type within the context of the query builder. /// - protected readonly Type OperatingType; + public readonly Type OperatingType; /// /// Constructs a new query node with the given builder. diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs index 166fac6d..0997e0ac 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -87,12 +87,46 @@ public SelectNode(NodeBuilder builder) : base(builder) { } return $"{{ {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; } + /// + /// 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); + } + /// - public override void Visit() { } + public override void Visit() + { + // is this node autogenerated and does it have a parent? + if (Parent is not null) + WrapParent(Parent); + } /// public override void FinalizeQuery() { + // if parent is defined, our select logic was generated in the + // visit step, we can just return out. + if (Parent is not null) + { + var result = Parent.Build(); + + if (string.IsNullOrEmpty(result.Query)) + throw new InvalidOperationException("Cannot wrap a global-defined query"); + + Query.Append($"select ({result.Query})"); + + // append the shape of the parents node operating type if we should include ours + if (Context.IncludeShape) + Query.Append($" {GetShape(Parent.OperatingType)}"); + return; + } + if(!Context.IncludeShape) { Query.Insert(0, $"select {Context.SelectName ?? OperatingType.GetEdgeDBTypeName()}"); diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs index 459442e7..f8b751ff 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs @@ -62,7 +62,7 @@ public string FirstOrDefault(TranslatedParameter source, TranslatedParameter fil 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; - return $"<{EdgeDBTypeUtils.GetEdgeDBScalarOrTypename(returnType)}>array_get(array_agg((select {name} := {set} filter {filterOrDefault})), 0){(defaultValue != null ? $" ?? {defaultValue}" : String.Empty)}"; + return $"<{EdgeDBTypeUtils.GetEdgeDBScalarOrTypeName(returnType)}>array_get(array_agg((select {name} := {set} filter {filterOrDefault})), 0){(defaultValue != null ? $" ?? {defaultValue}" : String.Empty)}"; } } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs index 6e8f1e7a..f81f992b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs @@ -80,7 +80,7 @@ public override string ToString() /// /// The equivalent edgedb type. /// - public static string GetEdgeDBScalarOrTypename(Type type) + public static string GetEdgeDBScalarOrTypeName(Type type) { if (TryGetScalarType(type, out var info)) return info.ToString(); @@ -103,7 +103,9 @@ public static bool TryGetScalarType(Type type, [MaybeNullWhen(false)] out EdgeDB info = null; - Type? enumerableType = type.GetInterfaces().FirstOrDefault(x => x.Name == "IEnumerable`1"); + Type? enumerableType = ReflectionUtils.IsSubTypeOfGenericType(typeof(IEnumerable<>), type) + ? type + : type.GetInterfaces().FirstOrDefault(x => ReflectionUtils.IsSubTypeOfGenericType(typeof(IEnumerable<>), x)); EdgeDBTypeInfo? child = null; var hasChild = enumerableType != null && TryGetScalarType(enumerableType.GenericTypeArguments[0], out child); @@ -134,17 +136,17 @@ public static bool IsLink(Type type, out bool isMultiLink, [MaybeNullWhen(false) innerLinkType = null; isMultiLink = false; - Type? enumerableType = null; - if (type != typeof(string) && (enumerableType = type.GetInterfaces().FirstOrDefault(x => ReflectionUtils.IsSubTypeOfGenericType(typeof(IEnumerable<>), x))) != null) + Type? enumerableType = ReflectionUtils.IsSubTypeOfGenericType(typeof(IEnumerable<>), type) ? type : null; + if (type != typeof(string) && (enumerableType is not null || (enumerableType = type.GetInterfaces().FirstOrDefault(x => ReflectionUtils.IsSubTypeOfGenericType(typeof(IEnumerable<>), x))) != null)) { innerLinkType = enumerableType.GenericTypeArguments[0]; isMultiLink = true; var result = IsLink(innerLinkType, out _, out var linkType); - innerLinkType ??= linkType; + innerLinkType = linkType ?? innerLinkType; return result; } - return TypeBuilder.IsValidObjectType(type); + return TypeBuilder.IsValidObjectType(type) && !TryGetScalarType(type, out _); } } } From 734ac580c7fa1c7b1c642fd52e7cb1c4b0c0437a Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 17 Aug 2022 01:49:35 -0300 Subject: [PATCH 61/70] contextual with statements Closes #23 --- .../Examples/QueryBuilder.cs | 5 + .../Interfaces/IQueryBuilder.cs | 46 ++-- .../Interfaces/Queries/IDeleteQuery.cs | 23 +- .../Interfaces/Queries/IInsertQuery.cs | 9 +- .../Interfaces/Queries/ISelectQuery.cs | 23 +- .../Interfaces/Queries/IUpdateQuery.cs | 5 +- .../InsertChildren/IUnlessConflictOn.cs | 9 +- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 218 +++++++++++------- src/EdgeDB.Net.QueryBuilder/QueryContext.cs | 15 +- .../Expressions/MemberExpressionTranslator.cs | 33 ++- 10 files changed, 245 insertions(+), 141 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index 99fb1fa6..e6204d12 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -62,6 +62,11 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { + var test = QueryBuilder + .With(new { Test = "test!" }) + .Select(ctx => ctx.Variables.Test) + .Build(); + var t = QueryBuilder .Select() .Group((person, builder) => diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs index ea9120b6..d16b49ce 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs @@ -9,16 +9,26 @@ namespace EdgeDB /// Represents a generic query builder for querying against . /// /// The type of which queries will be preformed with. - public interface IQueryBuilder : + /// The type of context representing the current builder. + public interface IQueryBuilder : IQueryBuilder, - ISelectQuery, - IUpdateQuery, - IDeleteQuery, - IInsertQuery, - IUnlessConflictOn, + ISelectQuery, + IUpdateQuery, + IDeleteQuery, + IInsertQuery, + IUnlessConflictOn, IGroupQuery, IGroupable { + /// + /// 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 current query. + IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator); + /// /// Adds a value to a WITH statement. /// @@ -31,7 +41,7 @@ public interface IQueryBuilder : /// /// The current query builder. /// - IQueryBuilder With(string name, object? value); + IQueryBuilder> With(TVariables variables); /// /// Adds a SELECT statement selecting the current with a autogenerated shape. @@ -39,7 +49,7 @@ public interface IQueryBuilder : /// /// A . /// - ISelectQuery Select(); + ISelectQuery Select(); /// /// Adds a SELECT statement selecting the provided expression. @@ -49,7 +59,7 @@ public interface IQueryBuilder : /// /// A . /// - ISelectQuery Select(Expression> selectFunc); + ISelectQuery Select(Expression> selectFunc); /// /// Adds a SELECT statement selecting the current with the given shape. @@ -62,7 +72,7 @@ public interface IQueryBuilder : /// /// A . /// - ISelectQuery Select(Expression> shape); + ISelectQuery Select(Expression> shape); /// /// Adds a SELECT statement selecting the type with the given shape. @@ -76,7 +86,7 @@ public interface IQueryBuilder : /// /// A . /// - ISelectQuery Select(Expression> shape); + ISelectQuery Select(Expression> shape); /// /// Adds a INSERT statement inserting an instance of . @@ -86,14 +96,14 @@ public interface IQueryBuilder : /// whether or not to implicitly add a select statement to return the inserted value. /// /// A . - IInsertQuery Insert(TType value, bool returnInsertedValue); + IInsertQuery Insert(TType value, bool returnInsertedValue); /// /// Adds a INSERT statement inserting an instance of . /// /// The value to insert. /// A . - IInsertQuery Insert(TType value); + IInsertQuery Insert(TType value); /// /// Adds a INSERT statement inserting an instance of . @@ -103,14 +113,14 @@ public interface IQueryBuilder : /// whether or not to implicitly add a select statement to return the inserted value. /// /// A . - IInsertQuery Insert(Expression> value, bool returnInsertedValue); + IInsertQuery Insert(Expression> value, bool returnInsertedValue); /// /// Adds a INSERT statement inserting an instance of . /// /// The callback containing the value initialization to insert. /// A . - IInsertQuery Insert(Expression> value); + IInsertQuery Insert(Expression> value); /// /// Adds a UPDATE statement updating an instance of . @@ -122,7 +132,7 @@ public interface IQueryBuilder : /// whether or not to implicitly add a select statement to return the inserted value. /// /// A . - IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue); + IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue); /// /// Adds a UPDATE statement updating an instance of . @@ -131,12 +141,12 @@ public interface IQueryBuilder : /// The callback used to update . The first parameter is the old value. /// /// A . - IUpdateQuery Update(Expression> updateFunc); + IUpdateQuery Update(Expression> updateFunc); /// /// Adds a DELETE statement deleting an instance of . /// - IDeleteQuery Delete { get; } + IDeleteQuery Delete { get; } } /// diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs index d38d579a..a827ed93 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs @@ -11,17 +11,18 @@ namespace EdgeDB.Interfaces.Queries /// Represents a generic DELETE query used within a . /// /// The type which this DELETE query is querying against. - public interface IDeleteQuery : IGroupable, IMultiCardinalityExecutable + /// The type of context representing the current builder. + public interface IDeleteQuery : IGroupable, IMultiCardinalityExecutable { /// /// 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); + IDeleteQuery Filter(Expression> filter); /// - IDeleteQuery Filter(Expression> filter); + IDeleteQuery Filter(Expression> filter); /// /// Orders the current s by the given property accending first. @@ -29,10 +30,10 @@ public interface IDeleteQuery : IGroupable, IMultiCardinalityExecu /// 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); + IDeleteQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); /// /// Orders the current s by the given property desending first. @@ -40,37 +41,37 @@ public interface IDeleteQuery : IGroupable, IMultiCardinalityExecu /// The property to order by. /// The order of which null values should occor. /// The current query. - IDeleteQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + IDeleteQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); /// - IDeleteQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + IDeleteQuery OrderByDesending(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); + 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); + IDeleteQuery Offset(Expression> offset); /// /// Limits the current s to the given amount. /// /// The amount to limit to. /// The current query. - IDeleteQuery Limit(long limit); + 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); + IDeleteQuery Limit(Expression> limit); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs index 5db101e7..14ae898b 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs @@ -11,7 +11,8 @@ namespace EdgeDB.Interfaces.Queries /// Represents a generic INSERT query used within a . /// /// The type which this INSERT query is querying against. - public interface IInsertQuery : ISingleCardinalityExecutable + /// The type of context representing the current builder. + public interface IInsertQuery : ISingleCardinalityExecutable { /// /// Automatically adds an UNLESS CONFLICT ON ... statement to the current insert @@ -22,7 +23,7 @@ public interface IInsertQuery : ISingleCardinalityExecutable /// when this query executes. /// /// The current query. - IUnlessConflictOn UnlessConflict(); + IUnlessConflictOn UnlessConflict(); /// /// Adds an UNLESS CONFLICT ON statement with the given property selector. @@ -31,9 +32,9 @@ public interface IInsertQuery : ISingleCardinalityExecutable /// 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); + 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 c7fc38a4..b0e683c1 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs @@ -11,17 +11,18 @@ namespace EdgeDB.Interfaces.Queries /// Represents a generic SELECT query used within a . /// /// The type which this SELECT query is querying against. - public interface ISelectQuery : IGroupable, IMultiCardinalityExecutable + /// The type of context representing the current builder. + public interface ISelectQuery : IGroupable, IMultiCardinalityExecutable { /// /// 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); + ISelectQuery Filter(Expression> filter); /// - ISelectQuery Filter(Expression> filter); + ISelectQuery Filter(Expression> filter); /// /// Orders the current s by the given property accending first. @@ -29,10 +30,10 @@ public interface ISelectQuery : IGroupable, IMultiCardinalityExecu /// The property to order by. /// The order of which null values should occor. /// The current query. - ISelectQuery OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + 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 desending first. @@ -40,37 +41,37 @@ public interface ISelectQuery : IGroupable, IMultiCardinalityExecu /// The property to order by. /// The order of which null values should occor. /// The current query. - ISelectQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + ISelectQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); /// - ISelectQuery OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement = null); + ISelectQuery OrderByDesending(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); + 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); + ISelectQuery Offset(Expression> offset); /// /// Limits the current s to the given amount. /// /// The amount to limit to. /// The current query. - ISelectQuery Limit(long limit); + 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); + 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 dfdec7d9..1c2274b6 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs @@ -11,7 +11,8 @@ namespace EdgeDB.Interfaces.Queries /// Represents a generic UPDATE query used within a . /// /// The type which this UPDATE query is querying against. - public interface IUpdateQuery : IGroupable, IMultiCardinalityExecutable + /// The type of context representing the current builder. + public interface IUpdateQuery : IGroupable, IMultiCardinalityExecutable { /// /// Filters the current update query by the given predicate. @@ -21,6 +22,6 @@ public interface IUpdateQuery : IGroupable, IMultiCardinalityExecu IMultiCardinalityExecutable Filter(Expression> filter); /// - IMultiCardinalityExecutable Filter(Expression> filter); + IMultiCardinalityExecutable Filter(Expression> filter); } } diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs index 2580b7a6..d972a731 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs @@ -10,7 +10,8 @@ namespace EdgeDB.Interfaces /// Represents a generic UNLESS CONFLICT ON query used within a . /// /// The type which this UNLESS CONFLICT ON query is querying against. - public interface IUnlessConflictOn : ISingleCardinalityExecutable + /// The type of context representing the current builder. + public interface IUnlessConflictOn : ISingleCardinalityExecutable { /// /// Adds an ELSE (SELECT ...) statment to the current query returning the conflicting object. @@ -25,7 +26,7 @@ public interface IUnlessConflictOn : ISingleCardinalityExecutable /// The callback that modifies the provided query builder to return a zero-many cardinality result. /// /// An executable query. - IMultiCardinalityExecutable Else(Func, IMultiCardinalityExecutable> elseQuery); + IMultiCardinalityExecutable Else(Func, IMultiCardinalityExecutable> elseQuery); /// /// Adds an ELSE ... statement with the else clause being the provided query builder. @@ -34,7 +35,7 @@ public interface IUnlessConflictOn : ISingleCardinalityExecutable /// The callback that modifies the provided query builder to return a zero-one cardinality result. /// /// An executable query. - ISingleCardinalityExecutable Else(Func, ISingleCardinalityExecutable> elseQuery); + ISingleCardinalityExecutable Else(Func, ISingleCardinalityExecutable> elseQuery); /// /// Adds an ELSE ... statement with the else clause being the provided query builder. @@ -42,7 +43,7 @@ public interface IUnlessConflictOn : ISingleCardinalityExecutable /// The type of the query builder /// The elses' inner clause /// A query builder representing a generic result. - IQueryBuilder Else(TQueryBuilder elseQuery) + IQueryBuilder Else(TQueryBuilder elseQuery) where TQueryBuilder : IQueryBuilder; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 2a14f0aa..3ea39a42 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -17,48 +17,53 @@ namespace EdgeDB /// public static class QueryBuilder { + /// + public static IQueryBuilder> With(TVariables variables) + => QueryBuilder.With(variables); + + /// public static IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator) => new QueryBuilder().For(collection, iterator); - /// - public static ISelectQuery Select() + /// + public static ISelectQuery Select() => new QueryBuilder().Select(); - /// - public static ISelectQuery Select(Expression> selectFunc) + /// + public static ISelectQuery Select(Expression> selectFunc) => new QueryBuilder().Select(selectFunc); - /// - public static ISelectQuery Select(Expression> shape) + /// + public static ISelectQuery Select(Expression> shape) => new QueryBuilder().Select(shape); - /// - public static IInsertQuery Insert(TType value, bool returnInsertedValue) + /// + public static IInsertQuery Insert(TType value, bool returnInsertedValue) => new QueryBuilder().Insert(value, returnInsertedValue); - /// - public static IInsertQuery Insert(TType value) + /// + public static IInsertQuery Insert(TType value) => new QueryBuilder().Insert(value, false); - /// - public static IInsertQuery Insert(Expression> value, bool returnInsertedValue) + /// + public static IInsertQuery Insert(Expression> value, bool returnInsertedValue) => new QueryBuilder().Insert(value, returnInsertedValue); - /// - public static IInsertQuery Insert(Expression> value) + /// + public static IInsertQuery Insert(Expression> value) => new QueryBuilder().Insert(value); - /// - public static IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue) + /// + public static IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue) => new QueryBuilder().Update(updateFunc, returnUpdatedValue); - /// - public static IUpdateQuery Update(Expression> updateFunc) + /// + public static IUpdateQuery Update(Expression> updateFunc) => new QueryBuilder().Update(updateFunc, false); - /// - public static IDeleteQuery Delete() + /// + public static IDeleteQuery Delete() => new QueryBuilder().Delete; } @@ -66,7 +71,24 @@ public static IDeleteQuery Delete() /// Represents a query builder used to build queries against . /// /// The type that this query builder is currently building queries for. - public sealed class QueryBuilder : IQueryBuilder + public class QueryBuilder : QueryBuilder + { + public QueryBuilder() : base() { } + + internal QueryBuilder(List nodes, List globals, Dictionary variables) + : base(nodes, globals, variables) { } + + new public static IQueryBuilder> With(TVariables variables) + => new QueryBuilder().With(variables); + } + + /// + /// 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 class QueryBuilder : IQueryBuilder { /// /// A list of query nodes that make up the current query builder. @@ -138,9 +160,20 @@ internal void AddQueryVariable(string name, object? value) /// /// The target type of the new query builder. /// - /// A new with the target type. + /// A new with the target type. + /// + private QueryBuilder EnterNewType() + => new(_nodes, _queryGlobals, _queryVariables); + + /// + /// 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. /// - private QueryBuilder EnterNewType() + private QueryBuilder EnterNewContext() => new(_nodes, _queryGlobals, _queryVariables); /// @@ -271,6 +304,22 @@ internal BuiltQuery BuildWithGlobals(Action? preFinalizerModifier = n => InternalBuild(false, preFinalizerModifier); #region Root nodes + 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"); + + // add the variables to our query variables + foreach (var variable in typeof(TVariables).GetProperties()) + _queryGlobals.Add(new QueryGlobal(variable.Name, variable.GetValue(variables))); + + return EnterNewContext>(); + } + public IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator) { AddNode(new ForContext(typeof(TType)) @@ -281,26 +330,16 @@ public IMultiCardinalityExecutable For(IEnumerable collection, Exp return this; } - - /// - public QueryBuilder With(string name, object? value) - { - if (QueryObjectManager.TryGetObjectId(value, out var id)) - _queryGlobals.Add(new QueryGlobal(name, new SubQuery($"(select {value!.GetType().GetEdgeDBTypeName()} filter .id = \"{id}\")"), value)); - else - _queryGlobals.Add(new(name, value)); - return this; - } - + /// - public ISelectQuery Select() + public ISelectQuery Select() { AddNode(new SelectContext(typeof(TType))); return this; } /// - public ISelectQuery Select(Expression> selectFunc) + public ISelectQuery Select(Expression> selectFunc) { AddNode(new SelectContext(typeof(TResult)) { @@ -311,7 +350,7 @@ public ISelectQuery Select(Expression> selectFun } /// - public ISelectQuery Select(Expression> shape) + public ISelectQuery Select(Expression> shape) { AddNode(new SelectContext(typeof(TType)) { @@ -322,7 +361,7 @@ public ISelectQuery Select(Expression> shape) } /// - public ISelectQuery Select(Expression> shape) + public ISelectQuery Select(Expression> shape) { AddNode(new SelectContext(typeof(TType)) { @@ -332,7 +371,7 @@ public ISelectQuery Select(Expression - public IInsertQuery Insert(TType value, bool returnInsertedValue = true) + public IInsertQuery Insert(TType value, bool returnInsertedValue = true) { var insertNode = AddNode(new InsertContext(typeof(TType)) { @@ -348,11 +387,11 @@ public IInsertQuery Insert(TType value, bool returnInsertedValue = true) } /// - public IInsertQuery Insert(TType value) + public IInsertQuery Insert(TType value) => Insert(value, false); /// - public IInsertQuery Insert(Expression> value, bool returnInsertedValue = true) + public IInsertQuery Insert(Expression> value, bool returnInsertedValue = true) { var insertNode = AddNode(new InsertContext(typeof(TType)) { @@ -368,11 +407,11 @@ public IInsertQuery Insert(Expression> value, b } /// - public IInsertQuery Insert(Expression> value) + public IInsertQuery Insert(Expression> value) => Insert(value, false); /// - public IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue) + public IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue) { var updateNode = AddNode(new UpdateContext(typeof(TType)) { @@ -388,11 +427,11 @@ public IUpdateQuery Update(Expression> updateFunc, boo } /// - public IUpdateQuery Update(Expression> updateFunc) + public IUpdateQuery Update(Expression> updateFunc) => Update(updateFunc, false); /// - public IDeleteQuery Delete + public IDeleteQuery Delete { get { @@ -411,7 +450,7 @@ public IDeleteQuery Delete /// /// The current node doesn't support a filter statement. /// - private QueryBuilder Filter(LambdaExpression filter) + private QueryBuilder Filter(LambdaExpression filter) { switch (CurrentUserNode) { @@ -439,7 +478,7 @@ private QueryBuilder Filter(LambdaExpression filter) /// /// The current node does not support order by statements /// - private QueryBuilder OrderBy(bool asc, LambdaExpression selector, OrderByNullPlacement? placement) + private QueryBuilder OrderBy(bool asc, LambdaExpression selector, OrderByNullPlacement? placement) { if (CurrentUserNode is not SelectNode selectNode) throw new InvalidOperationException($"Cannot order by on a {CurrentUserNode}"); @@ -457,7 +496,7 @@ private QueryBuilder OrderBy(bool asc, LambdaExpression selector, OrderBy /// /// The current node does not support offset statements. /// - private QueryBuilder Offset(long offset) + private QueryBuilder Offset(long offset) { if (CurrentUserNode is not SelectNode selectNode) throw new InvalidOperationException($"Cannot offset on a {CurrentUserNode}"); @@ -475,7 +514,7 @@ private QueryBuilder Offset(long offset) /// /// The current node does not support offset statements. /// - private QueryBuilder OffsetExp(LambdaExpression offset) + private QueryBuilder OffsetExp(LambdaExpression offset) { if (CurrentUserNode is not SelectNode selectNode) throw new InvalidOperationException($"Cannot offset on a {CurrentUserNode}"); @@ -493,7 +532,7 @@ private QueryBuilder OffsetExp(LambdaExpression offset) /// /// The current node does not support limit statements. /// - private QueryBuilder Limit(long limit) + private QueryBuilder Limit(long limit) { if (CurrentUserNode is not SelectNode selectNode) throw new InvalidOperationException($"Cannot limit on a {CurrentUserNode}"); @@ -511,7 +550,7 @@ private QueryBuilder Limit(long limit) /// /// The current node does not support limit statements. /// - private QueryBuilder LimitExp(LambdaExpression limit) + private QueryBuilder LimitExp(LambdaExpression limit) { if (CurrentUserNode is not SelectNode selectNode) throw new InvalidOperationException($"Cannot limit on a {CurrentUserNode}"); @@ -531,7 +570,7 @@ private QueryBuilder LimitExp(LambdaExpression limit) /// /// The current node does not support unless conflict on statements. /// - private QueryBuilder UnlessConflict() + private QueryBuilder UnlessConflict() { if (CurrentUserNode is not InsertNode insertNode) throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); @@ -551,7 +590,7 @@ private QueryBuilder UnlessConflict() /// /// The current node does not support unless conflict on statements. /// - private QueryBuilder UnlessConflictOn(LambdaExpression selector) + private QueryBuilder UnlessConflictOn(LambdaExpression selector) { if (CurrentUserNode is not InsertNode insertNode) throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); @@ -568,7 +607,7 @@ private QueryBuilder UnlessConflictOn(LambdaExpression selector) /// /// The current node does not support else statements. /// - private QueryBuilder ElseReturnDefault() + private QueryBuilder ElseReturnDefault() { if (CurrentUserNode is not InsertNode insertNode) throw new InvalidOperationException($"Cannot else return on a {CurrentUserNode}"); @@ -586,7 +625,7 @@ private QueryBuilder ElseReturnDefault() /// /// The current node does not support else statements /// - private IQueryBuilder ElseJoint(IQueryBuilder builder) + private IQueryBuilder ElseJoint(IQueryBuilder builder) { if (CurrentUserNode is not InsertNode insertNode) throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); @@ -606,12 +645,12 @@ private QueryBuilder ElseReturnDefault() /// /// The current node does not support else statements. /// - private QueryBuilder Else(Func, IMultiCardinalityQuery> func) + private QueryBuilder Else(Func, IMultiCardinalityQuery> func) { if (CurrentUserNode is not InsertNode insertNode) throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); - var builder = new QueryBuilder(new(), _queryGlobals, new()); + var builder = new QueryBuilder(new(), _queryGlobals, new()); func(builder); insertNode.Else(builder); @@ -628,12 +667,12 @@ private QueryBuilder Else(Func, IMultiCardinalityQue /// /// The current node does not support else statements. /// - private QueryBuilder Else(Func, ISingleCardinalityQuery> func) + private QueryBuilder Else(Func, ISingleCardinalityQuery> func) { if (CurrentUserNode is not InsertNode insertNode) throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); - var builder = new QueryBuilder(new(), _queryGlobals, new()); + var builder = new QueryBuilder(new(), _queryGlobals, new()); func(builder); insertNode.Else(builder); @@ -643,75 +682,75 @@ private QueryBuilder Else(Func, ISingleCardinalityQu #endregion #region ISelectQuery - ISelectQuery ISelectQuery.Filter(Expression> filter) + ISelectQuery ISelectQuery.Filter(Expression> filter) => Filter(filter); - ISelectQuery ISelectQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + ISelectQuery ISelectQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) => OrderBy(true, propertySelector, nullPlacement); - ISelectQuery ISelectQuery.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + ISelectQuery ISelectQuery.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) => OrderBy(false, propertySelector, nullPlacement); - ISelectQuery ISelectQuery.Offset(long offset) + ISelectQuery ISelectQuery.Offset(long offset) => Offset(offset); - ISelectQuery ISelectQuery.Limit(long limit) + ISelectQuery ISelectQuery.Limit(long limit) => Limit(limit); - ISelectQuery ISelectQuery.Filter(Expression> filter) + ISelectQuery ISelectQuery.Filter(Expression> filter) => Filter(filter); - ISelectQuery ISelectQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + ISelectQuery ISelectQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) => OrderBy(true, propertySelector, nullPlacement); - ISelectQuery ISelectQuery.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + ISelectQuery ISelectQuery.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) => OrderBy(false, propertySelector, nullPlacement); - ISelectQuery ISelectQuery.Offset(Expression> offset) + ISelectQuery ISelectQuery.Offset(Expression> offset) => OffsetExp(offset); - ISelectQuery ISelectQuery.Limit(Expression> limit) + ISelectQuery ISelectQuery.Limit(Expression> limit) => LimitExp(limit); #endregion #region IInsertQuery - IUnlessConflictOn IInsertQuery.UnlessConflict() + IUnlessConflictOn IInsertQuery.UnlessConflict() => UnlessConflict(); - IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) + IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) => UnlessConflictOn(propertySelector); - IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) + IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) => UnlessConflictOn(propertySelector); #endregion #region IUpdateQuery - IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) + IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) => Filter(filter); - IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) + IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) => Filter(filter); #endregion #region IUnlessConflictOn - ISingleCardinalityExecutable IUnlessConflictOn.ElseReturn() + ISingleCardinalityExecutable IUnlessConflictOn.ElseReturn() => ElseReturnDefault(); - IQueryBuilder IUnlessConflictOn.Else(TQueryBuilder elseQuery) + IQueryBuilder IUnlessConflictOn.Else(TQueryBuilder elseQuery) => ElseJoint(elseQuery); - IMultiCardinalityExecutable IUnlessConflictOn.Else(Func, IMultiCardinalityExecutable> elseQuery) + IMultiCardinalityExecutable IUnlessConflictOn.Else(Func, IMultiCardinalityExecutable> elseQuery) => Else(elseQuery); - ISingleCardinalityExecutable IUnlessConflictOn.Else(Func, ISingleCardinalityExecutable> elseQuery) + ISingleCardinalityExecutable IUnlessConflictOn.Else(Func, ISingleCardinalityExecutable> elseQuery) => Else(elseQuery); #endregion #region IDeleteQuery - IDeleteQuery IDeleteQuery.Filter(Expression> filter) + IDeleteQuery IDeleteQuery.Filter(Expression> filter) => Filter(filter); - IDeleteQuery IDeleteQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + IDeleteQuery IDeleteQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) => OrderBy(true, propertySelector, nullPlacement); - IDeleteQuery IDeleteQuery.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + IDeleteQuery IDeleteQuery.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) => OrderBy(false, propertySelector, nullPlacement); - IDeleteQuery IDeleteQuery.Offset(long offset) + IDeleteQuery IDeleteQuery.Offset(long offset) => Offset(offset); - IDeleteQuery IDeleteQuery.Limit(long limit) + IDeleteQuery IDeleteQuery.Limit(long limit) => Limit(limit); - IDeleteQuery IDeleteQuery.Filter(Expression> filter) + IDeleteQuery IDeleteQuery.Filter(Expression> filter) => Filter(filter); - IDeleteQuery IDeleteQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + IDeleteQuery IDeleteQuery.OrderBy(Expression> propertySelector, OrderByNullPlacement? nullPlacement) => OrderBy(true, propertySelector, nullPlacement); - IDeleteQuery IDeleteQuery.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) + IDeleteQuery IDeleteQuery.OrderByDesending(Expression> propertySelector, OrderByNullPlacement? nullPlacement) => OrderBy(false, propertySelector, nullPlacement); - IDeleteQuery IDeleteQuery.Offset(Expression> offset) + IDeleteQuery IDeleteQuery.Offset(Expression> offset) => OffsetExp(offset); - IDeleteQuery IDeleteQuery.Limit(Expression> limit) + IDeleteQuery IDeleteQuery.Limit(Expression> limit) => LimitExp(limit); #endregion @@ -776,11 +815,12 @@ private async ValueTask IntrospectAndBuildAsync(IEdgeDBQueryable edg IReadOnlyCollection IQueryBuilder.Nodes => _nodes; IReadOnlyCollection IQueryBuilder.Globals => _queryGlobals; IReadOnlyDictionary IQueryBuilder.Variables => _queryVariables; - IQueryBuilder IQueryBuilder.With(string name, object? value) => With(name, value); + IQueryBuilder> IQueryBuilder.With(TVariables variables) => With(variables); BuiltQuery IQueryBuilder.BuildWithGlobals(Action? preFinalizerModifier) => BuildWithGlobals(preFinalizerModifier); #endregion } + /// /// Represents a built query. /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs index d62d1af0..1650d2d2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -13,7 +13,7 @@ namespace EdgeDB /// /// Represents context used within query functions. /// - public sealed class QueryContext + public class QueryContext { /// /// References a defined query global given a name. @@ -223,4 +223,17 @@ public TCollection SubQuery(IMultiCardinalityQuery qu where TCollection : IEnumerable => default!; } + + /// + /// Represents context used within query functions containing a variable type. + /// + /// The type containing the variables defined in the query. + public abstract class QueryContext : QueryContext + { + /// + /// Gets a collection of variables defined in a with block. + /// + public TVariables Variables + => default!; + } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index f3f49544..3ecf4d58 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -18,10 +18,41 @@ internal class MemberExpressionTranslator : ExpressionTranslator.Variables): + // get the reference + var target = deconstructed[^3]; + + // switch the type of the target + switch (target) + { + case MemberExpression targetMember: + if (deconstructed.Length != 3) + throw new NotSupportedException("Cannot use nested values for variable access"); + + // return the name of the member + return targetMember.Member.Name; + default: + throw new NotSupportedException($"Cannot use expression type {target.NodeType} as a variable access"); + } + } + } + // if the resolute expression is a constant expression, assume // were in a set-like context and add it as a variable. - if (deconstructed.LastOrDefault() is ConstantExpression constant) + if (baseExpression is ConstantExpression constant) { // walk thru the reference tree, you can imagine this as a variac pointer resolution. object? refHolder = constant.Value; From e3865044712233ff1fe4296c5f5ce2f8d7dfcbf4 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 17 Aug 2022 01:54:33 -0300 Subject: [PATCH 62/70] fix summaries --- .../Interfaces/IQueryBuilder.cs | 34 +++++++++---------- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 4 +-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs index d16b49ce..cdb1508e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs @@ -30,16 +30,14 @@ public interface IQueryBuilder : IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator); /// - /// Adds a value to a WITH statement. + /// Adds a WITH statement whos variables are the properties defined in . /// - /// - /// This value can be used later within queries by using a lambda with the object, - /// then calling . - /// - /// The name of the value. - /// The value to add. + /// 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 builder. + /// The current query. /// IQueryBuilder> With(TVariables variables); @@ -47,7 +45,7 @@ public interface IQueryBuilder : /// Adds a SELECT statement selecting the current with a autogenerated shape. /// /// - /// A . + /// A . /// ISelectQuery Select(); @@ -57,7 +55,7 @@ public interface IQueryBuilder : /// The return result of the select expression. /// The selecting expression. /// - /// A . + /// A . /// ISelectQuery Select(Expression> selectFunc); @@ -70,7 +68,7 @@ public interface IQueryBuilder : /// /// The shape to select. /// - /// A . + /// A . /// ISelectQuery Select(Expression> shape); @@ -84,7 +82,7 @@ public interface IQueryBuilder : /// The type to select. /// The shape to select. /// - /// A . + /// A . /// ISelectQuery Select(Expression> shape); @@ -95,14 +93,14 @@ public interface IQueryBuilder : /// /// whether or not to implicitly add a select statement to return the inserted value. /// - /// A . + /// A . IInsertQuery Insert(TType value, bool returnInsertedValue); /// /// Adds a INSERT statement inserting an instance of . /// /// The value to insert. - /// A . + /// A . IInsertQuery Insert(TType value); /// @@ -112,14 +110,14 @@ public interface IQueryBuilder : /// /// whether or not to implicitly add a select statement to return the inserted value. /// - /// A . + /// A . IInsertQuery Insert(Expression> value, bool returnInsertedValue); /// /// Adds a INSERT statement inserting an instance of . /// /// The callback containing the value initialization to insert. - /// A . + /// A . IInsertQuery Insert(Expression> value); /// @@ -131,7 +129,7 @@ public interface IQueryBuilder : /// /// whether or not to implicitly add a select statement to return the inserted value. /// - /// A . + /// A . IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue); /// @@ -140,7 +138,7 @@ public interface IQueryBuilder : /// /// The callback used to update . The first parameter is the old value. /// - /// A . + /// A . IUpdateQuery Update(Expression> updateFunc); /// diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 3ea39a42..4cb52887 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -349,7 +349,7 @@ public ISelectQuery Select(Expression> return EnterNewType(); } - /// + /// public ISelectQuery Select(Expression> shape) { AddNode(new SelectContext(typeof(TType)) @@ -360,7 +360,7 @@ public ISelectQuery Select(Expression> s return this; } - /// + /// public ISelectQuery Select(Expression> shape) { AddNode(new SelectContext(typeof(TType)) From 245ac7e947854ec194142c92df2e420e9ad92b1c Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 17 Aug 2022 01:55:42 -0300 Subject: [PATCH 63/70] update example 'with' --- examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index e6204d12..a30eac0a 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -62,7 +62,7 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var test = QueryBuilder + var test = QueryBuilder .With(new { Test = "test!" }) .Select(ctx => ctx.Variables.Test) .Build(); From 81be5b046810e61ffa72130a241b587744de9f99 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Fri, 19 Aug 2022 08:00:21 -0300 Subject: [PATCH 64/70] Add introspection for value types on insert shapes --- .../Examples/QueryBuilder.cs | 6 + .../Utils/ReflectionUtils.cs | 11 + .../Interfaces/IQueryBuilder.cs | 16 + .../QueryNodes/InsertNode.cs | 365 +++++++++++++++--- .../Schema/DataTypes/Property.cs | 5 + .../Schema/SchemaIntrospector.cs | 2 +- .../Utils/ConflictUtils.cs | 2 +- 7 files changed, 342 insertions(+), 65 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index a30eac0a..f7272d8a 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -62,6 +62,12 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { + var test2 = await QueryBuilder.Insert(new LinkPerson + { + Name = "Test", + Email = "test" + }).BuildAsync(client); + var test = QueryBuilder .With(new { Test = "test!" }) .Select(ctx => ctx.Variables.Test) diff --git a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs index 2244acfe..dcb9265d 100644 --- a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs +++ b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs @@ -11,6 +11,17 @@ namespace EdgeDB { internal class ReflectionUtils { + public static object? GetValueTypeDefault(Type type) + { + if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + var valueProperty = type.GetProperty("Value")!; + type = valueProperty.PropertyType; + } + + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + public static bool IsSubTypeOfGenericType(Type genericType, Type toCheck) { Type? type = toCheck; diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs index cdb1508e..19aa930e 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs @@ -89,6 +89,10 @@ public interface IQueryBuilder : /// /// 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. @@ -99,6 +103,10 @@ public interface IQueryBuilder : /// /// 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(TType value); @@ -106,6 +114,10 @@ public interface IQueryBuilder : /// /// 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. @@ -116,6 +128,10 @@ public interface IQueryBuilder : /// /// 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); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 7c388780..7a960ed0 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -1,4 +1,5 @@ using EdgeDB.DataTypes; +using EdgeDB.Schema; using EdgeDB.Serializer; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -19,6 +20,159 @@ namespace EdgeDB.QueryNodes /// internal class InsertNode : QueryNode { + /// + /// A readonly struct representing a setter in an insert shape. + /// + private readonly struct ShapeSetter + { + /// + /// Whether or not the setter requires introspection. + /// + public readonly bool RequiresIntrospection; + + /// + /// A string-based setter. + /// + private readonly string? _setter; + + /// + /// A function-based setter which requires introspection. + /// + private readonly Func? _setterBuilder; + + /// + /// Constructs a new . + /// + /// A string-based setter. + public ShapeSetter(string setter) + { + _setter = setter; + _setterBuilder = null; + RequiresIntrospection = false; + } + + /// + /// Constructs a new . + /// + /// A function-based setter that requires introspection. + public ShapeSetter(Func builder) + { + _setterBuilder = builder; + _setter = null; + RequiresIntrospection = true; + } + + /// + /// Converts this to a string form without introspection. + /// + /// A stringified edgeql setter. + /// + /// The current setter requires introspection. + /// + public override string ToString() + { + if (_setter is null) + throw new InvalidOperationException("Cannot build insert setter, a setter requires introspection"); + return _setter; + } + + /// + /// Converts this to a string form with introspection. + /// + /// The introspected schema info. + /// A stringified edgeql setter. + public string? ToString(SchemaInfo info) + { + return RequiresIntrospection && _setterBuilder is not null + ? _setterBuilder(info) + : ToString(); + } + + public static implicit operator ShapeSetter(string s) => new(s); + public static implicit operator ShapeSetter(Func 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 string? _rawShape; + + /// + /// The setters in this shape definition. + /// + private readonly IEnumerable _shape; + + /// + /// Constructs a new with the given shape body. + /// + /// + public ShapeDefinition(string shape) + { + _rawShape = shape; + _shape = Array.Empty(); + RequiresIntrospection = false; + } + + /// + /// Constructs a new with the given shape body. + /// + /// + public ShapeDefinition(IEnumerable shape) + { + _shape = shape; + _rawShape = null; + RequiresIntrospection = shape.Any(x => x.RequiresIntrospection); + } + + /// + /// Builds this into the string form without using introspection. + /// + /// The string form of the shape definition. + /// The shape body requires introspection to build. + public string Build() + { + if (_rawShape is not null) + return _rawShape; + + if (_shape.Any(x => x.RequiresIntrospection)) + throw new InvalidOperationException("Cannot build insert shape, some properties require introspection"); + + return $"{{ {string.Join(", ", _shape)} }}"; + } + + /// + /// Builds this into a string using schema introspection. + /// + /// The schema introspection info. + /// The string form of the shape definition. + public string Build(SchemaInfo info) + { + if (_rawShape is not null) + return _rawShape; + + return RequiresIntrospection + ? $"{{ {string.Join(", ", _shape.Select(x => x.ToString(info)).Where(x => x is not null))} }}" + : Build(); + } + + public static implicit operator ShapeDefinition(string shape) => new ShapeDefinition(shape); + } + + /// + /// The insert shape definition. + /// + private ShapeDefinition _shape; + /// /// Whether or not to autogenerate the unless conflict clause. /// @@ -41,13 +195,60 @@ public InsertNode(NodeBuilder builder) : base(builder) _elseStatement = new(); } + /// + 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(); + + RequiresIntrospection = _shape.RequiresIntrospection; + } + + /// + public override void FinalizeQuery() + { + // build the shape with introspection + var shape = SchemaInfo is not null + ? _shape.Build(SchemaInfo) + : _shape.Build(); + + // prepend it to our query string + Query.Insert(0, $"insert {OperatingType.GetEdgeDBTypeName()} {shape}"); + + // if we require autogeneration of the unless conflict statement + if (_autogenerateUnlessConflict) + { + if (SchemaInfo is null) + throw new NotSupportedException("Cannot use autogenerated unless conflict on without schema interpolation"); + + if (!SchemaInfo.TryGetObjectInfo(OperatingType, out var typeInfo)) + throw new NotSupportedException($"Could not find type info for {OperatingType}"); + + Query.Append($" {ConflictUtils.GenerateExclusiveConflictStatement(typeInfo, _elseStatement.Length != 0)}"); + } + + Query.Append(_elseStatement); + + // if the query builder wants this node as a global + if (Context.SetAsGlobal && Context.GlobalName != null) + { + SetGlobal(Context.GlobalName, new SubQuery($"({Query})"), null); + Query.Clear(); + } + } + /// - /// Builds a json-based insert shape + /// Builds a json-based insert shape. /// /// /// The insert shape for a json-based value. /// - private string BuildJsonShape() + private ShapeDefinition BuildJsonShape() { var mappingName = QueryUtils.GenerateRandomVariableName(); var jsonValue = (IJsonVariable)Context.Value!; @@ -55,9 +256,9 @@ private string BuildJsonShape() // create a depth map that contains each nested level of types to be inserted IGrouping[] depthMap = JsonUtils.BuildDepthMap(mappingName, jsonValue).ToArray().GroupBy(x => x.Depth).ToArray(); - + // generate the global maps - for(int i = depth; i != 0; i--) + for (int i = depth; i != 0; i--) { var map = depthMap[i]; var node = map.First(); @@ -67,19 +268,40 @@ private string BuildJsonShape() // IMPORTANT: since we're using 'i' within the callback, we must // store a local here so we dont end up calling the last iterations 'i' value - var indexCopy = i; + var indexCopy = i; // generate a introspection-dependant sub query for the insert or select var query = new SubQuery((info) => { var allProps = QueryGenerationUtils.GetProperties(info, node.Type); var typeName = node.Type.GetEdgeDBTypeName(); + var infoCopy = info; // define the insert shape var shape = allProps.Select(x => { var edgedbName = x.GetEdgeDBPropertyName(); - + var isScalar = EdgeDBTypeUtils.TryGetScalarType(x.PropertyType, out var edgeqlType); + + // we need to add a callback for value types that are default to determine if we need to + // add the setter + if (isScalar && x.PropertyType.IsValueType && !x.PropertyType.IsEnum) + { + if (!infoCopy.TryGetObjectInfo(jsonValue.InnerType, out var info)) + throw new InvalidOperationException($"Could not find {jsonValue.InnerType.GetEdgeDBTypeName()} in schema info!"); + + // get the property defined in the schema + var edgedbProp = info.Properties!.FirstOrDefault(x => x.Name == edgedbName); + + if (edgedbProp is null) + throw new InvalidOperationException($"Could not find property '{edgedbName}' on type {jsonValue.InnerType.GetEdgeDBTypeName()}"); + + if (edgedbProp.Required && !edgedbProp.HasDefault) + return $"{edgedbName} := <{edgeqlType}>json_get({jsonValue.Name}, '{x.Name}')"; + + return null; + } + // if its a link, add a ternary statement for pulling the value out of a sub-map if (EdgeDBTypeUtils.IsLink(x.PropertyType, out var isArray, out _)) { @@ -90,18 +312,17 @@ private string BuildJsonShape() return $"{edgedbName} := {{}}"; // return a slice operator for multi links or a index operator for single links - return isArray - ? $"{edgedbName} := distinct array_unpack({mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{x.Name}', '{mappingName}_depth_from') ?? 0:json_get({iterationName}, '{x.Name}', '{mappingName}_depth_to') ?? 0])" + return isArray + ? $"{edgedbName} := distinct array_unpack({mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{x.Name}', '{mappingName}_depth_from') ?? 0:json_get({iterationName}, '{x.Name}', '{mappingName}_depth_to') ?? 0])" : $"{edgedbName} := {mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{x.Name}', '{mappingName}_depth_index')] if json_typeof(json_get({iterationName}, '{x.Name}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; } // if its a scalar type, use json_get to pull the value and cast it to our property // type - if (!EdgeDBTypeUtils.TryGetScalarType(x.PropertyType, out var edgeqlType)) + if (!isScalar) throw new NotSupportedException($"Cannot use type {x.PropertyType} as there is no serializer for it"); return $"{edgedbName} := <{edgeqlType}>json_get({iterationName}, '{x.Name}')"; - }); // generate the 'insert .. unless conflict .. else select' query @@ -135,6 +356,30 @@ private string BuildJsonShape() var shape = jsonValue.InnerType.GetEdgeDBTargetProperties(excludeId: true).Select(x => { var edgedbName = x.GetEdgeDBPropertyName(); + var isScalar = EdgeDBTypeUtils.TryGetScalarType(x.PropertyType, out var edgeqlType); + + // we need to add a callback for value types that are default to determine if we need to + // add the setter + if (isScalar && x.PropertyType.IsValueType && !x.PropertyType.IsEnum) + { + // we should include the setter based on the schema + return new ShapeSetter(s => + { + if (!s.TryGetObjectInfo(jsonValue.InnerType, out var info)) + throw new InvalidOperationException($"Could not find {jsonValue.InnerType.GetEdgeDBTypeName()} in schema info!"); + + // get the property defined in the schema + var edgedbProp = info.Properties!.FirstOrDefault(x => x.Name == edgedbName); + + if (edgedbProp is null) + throw new InvalidOperationException($"Could not find property '{edgedbName}' on type {jsonValue.InnerType.GetEdgeDBTypeName()}"); + + if (edgedbProp.Required && !edgedbProp.HasDefault) + return $"{edgedbName} := <{edgeqlType}>json_get({jsonValue.Name}, '{x.Name}')"; + + return null; + }); + } // if its a link, add a ternary statement for pulling the value out of a sub-map if (EdgeDBTypeUtils.IsLink(x.PropertyType, out var isArray, out _)) @@ -147,14 +392,14 @@ private string BuildJsonShape() // if its a scalar type, use json_get to pull the value and cast it to our property // type - if (!EdgeDBTypeUtils.TryGetScalarType(x.PropertyType, out var edgeqlType)) + if (!isScalar) throw new NotSupportedException($"Cannot use type {x.PropertyType} as there is no serializer for it"); return $"{edgedbName} := <{edgeqlType}>json_get({jsonValue.Name}, '{x.Name}')"; }); // return out our insert shape - return $"{{ {string.Join(", ", shape)} }}"; + return new ShapeDefinition(shape); } /// @@ -166,10 +411,10 @@ private string BuildJsonShape() /// /// No serialization method could be found for a property. /// - private string BuildInsertShape(Type? shapeType = null, object? shapeValue = null) + private ShapeDefinition BuildInsertShape(Type? shapeType = null, object? shapeValue = null) { - List shape = new(); - + List setters = new(); + // use the provide shape and value if they're not null, otherwise // use the ones defined in context var type = shapeType ?? OperatingType; @@ -180,24 +425,55 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul return $"{{ {ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; // get all properties that aren't marked with the EdgeDBIgnore attribute - var properties = type.GetEdgeDBTargetProperties(excludeId: true); + var properties = type.GetEdgeDBTargetProperties(); - foreach(var property in properties) + foreach (var property in properties) { // define the type and whether or not it's a link var propType = property.PropertyType; - var isLink = EdgeDBTypeUtils.IsLink(property.PropertyType, out var isArray, out var innerType); - + var propValue = property.GetValue(value); + var isScalar = EdgeDBTypeUtils.TryGetScalarType(propType, out var edgeqlType); + // get the equivalent edgedb property name var propertyName = property.GetEdgeDBPropertyName(); + // if its a default value of a struct, ignore it. + if (isScalar && propType.IsValueType && !propType.IsEnum && (propValue?.Equals(ReflectionUtils.GetValueTypeDefault(propType)) ?? false)) + { + setters.Add(new(s => + { + // get the object type from the schema + if (!s.TryGetObjectInfo(type!, out var info)) + throw new InvalidOperationException($"Could not find {type!.GetEdgeDBTypeName()} in schema info!"); + + // get the property defined in the schema + var edgedbProp = info.Properties!.FirstOrDefault(x => x.Name == propertyName); + + if (edgedbProp is null) + throw new InvalidOperationException($"Could not find property '{propertyName}' on type {type!.GetEdgeDBTypeName()}"); + + // if its required and it doesn't have a default value, set it + if (edgedbProp.Required && !edgedbProp.HasDefault) + { + var varName = QueryUtils.GenerateRandomVariableName(); + SetVariable(varName, propValue); + return $"{propertyName} := <{edgeqlType}>${varName}"; + } + + return null; + })); + continue; + } + + var isLink = EdgeDBTypeUtils.IsLink(property.PropertyType, out var isArray, out var innerType); + // if a scalar type is found for the property type - if(EdgeDBTypeUtils.TryGetScalarType(propType, out var edgeqlType)) + if (isScalar) { // set it as a variable and continue the iteration var varName = QueryUtils.GenerateRandomVariableName(); SetVariable(varName, property.GetValue(value)); - shape.Add($"{propertyName} := <{edgeqlType}>${varName}"); + setters.Add($"{propertyName} := <{edgeqlType}>${varName}"); continue; } @@ -209,7 +485,7 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul // if its null we can append an empty set if (subValue is null) - shape.Add($"{propertyName} := {{}}"); + setters.Add($"{propertyName} := {{}}"); else if (isArray) // if its a multi link { List subShape = new(); @@ -221,10 +497,10 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul } // append the sub-shape - shape.Add($"{propertyName} := {{ {string.Join(", ", subShape)} }}"); + setters.Add($"{propertyName} := {{ {string.Join(", ", subShape)} }}"); } else // generate the link resolver and append it - shape.Add($"{propertyName} := {BuildLinkResolver(propType, subValue)}"); + setters.Add($"{propertyName} := {BuildLinkResolver(propType, subValue)}"); continue; } @@ -232,7 +508,7 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul throw new InvalidOperationException($"Failed to find method to serialize the property \"{property.PropertyType.Name}\" on type {type.Name}"); } - return $"{{ {string.Join(", ", shape)} }}"; + return new ShapeDefinition(setters); } /// @@ -261,7 +537,7 @@ private string BuildLinkResolver(Type type, object? value) else { RequiresIntrospection = true; - + // add a insert select statement return InlineOrGlobal(type, new SubQuery((info) => { @@ -298,43 +574,6 @@ private string InlineOrGlobal(Type type, SubQuery value, object? reference) : value.ToString()!; } - /// - public override void Visit() - { - // add the current type to the sub query map - _subQueryMap.Add(OperatingType); - - // build the insert shape - var shape = Context.IsJsonVariable ? BuildJsonShape() : BuildInsertShape(); - - // append it to our query - Query.Append($"insert {OperatingType.GetEdgeDBTypeName()} {shape}"); - } - - /// - public override void FinalizeQuery() - { - // if we require autogeneration of the unless conflict statement - if(_autogenerateUnlessConflict) - { - if (SchemaInfo is null) - throw new NotSupportedException("Cannot use autogenerated unless conflict on without schema interpolation"); - - if (!SchemaInfo.TryGetObjectInfo(OperatingType, out var typeInfo)) - throw new NotSupportedException($"Could not find type info for {OperatingType}"); - - Query.Append($" {ConflictUtils.GenerateExclusiveConflictStatement(typeInfo, _elseStatement.Length != 0)}"); - } - - Query.Append(_elseStatement); - - // if the query builder wants this node as a global - if (Context.SetAsGlobal && Context.GlobalName != null) - { - SetGlobal(Context.GlobalName, new SubQuery($"({Query})"), null); - Query.Clear(); - } - } /// /// Adds a unless conflict on (...) statement to the insert node diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs index c4dc04ea..2c058790 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs @@ -39,6 +39,11 @@ internal class Property /// 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 this property is exclusive /// diff --git a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs index fc9e0efd..7e321631 100644 --- a/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs @@ -66,6 +66,7 @@ private static async Task IntrospectSchemaAsync(IEdgeDBQueryable edg }), Properties = ctx.IncludeMultiLink(() => new Property { + Required = ctx.Include(), Cardinality = (string)ctx.UnsafeLocal("cardinality") == "One" ? ctx.UnsafeLocal("required") ? DataTypes.Cardinality.One : DataTypes.Cardinality.AtMostOne : ctx.UnsafeLocal("required") ? DataTypes.Cardinality.AtLeastOne : DataTypes.Cardinality.Many, @@ -76,7 +77,6 @@ private static async Task IntrospectSchemaAsync(IEdgeDBQueryable edg IsComputed = EdgeQL.Length(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); diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs index 4159a557..e751dea7 100644 --- a/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs +++ b/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs @@ -32,7 +32,7 @@ public static string GenerateExclusiveConflictStatement(ObjectType type, bool ha } // does the type have a single property that is exclusive? - if(type.Properties!.Count(x => x.IsExclusive) == 1) + if(type.Properties!.Count(x => x.Name != "id" && x.IsExclusive) == 1) { return $"unless conflict on .{type.Properties!.First(x => x.IsExclusive).Name}"; } From 7d1c049e7a45f85053663a8824b2b51ae8b71b90 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Fri, 19 Aug 2022 09:31:13 -0300 Subject: [PATCH 65/70] add serialization strategies for WITH blocks --- .../Examples/QueryBuilder.cs | 21 ------------ .../Interfaces/IQueryBuilder.cs | 2 +- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 32 ++++++++++++++++--- .../Expressions/InitializationTranslator.cs | 9 +++++- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index f7272d8a..a9bb7294 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -62,27 +62,6 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var test2 = await QueryBuilder.Insert(new LinkPerson - { - Name = "Test", - Email = "test" - }).BuildAsync(client); - - var test = QueryBuilder - .With(new { Test = "test!" }) - .Select(ctx => ctx.Variables.Test) - .Build(); - - var t = QueryBuilder - .Select() - .Group((person, builder) => - builder.Using(() => new - { - Test = "Test" - }) - .By(x => x.Test) - ); - // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs index 19aa930e..ee1d41cc 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs @@ -42,7 +42,7 @@ public interface IQueryBuilder : IQueryBuilder> With(TVariables variables); /// - /// Adds a SELECT statement selecting the current with a autogenerated shape. + /// Adds a SELECT statement selecting the current with an autogenerated shape. /// /// /// A . diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 4cb52887..bae1507c 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -313,9 +313,32 @@ public QueryBuilder> With(TVariables if (!typeof(TVariables).IsAnonymousType()) throw new ArgumentException("Variables must be an anonymous type"); - // add the variables to our query variables - foreach (var variable in typeof(TVariables).GetProperties()) - _queryGlobals.Add(new QueryGlobal(variable.Name, variable.GetValue(variables))); + // 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 varName = QueryUtils.GenerateRandomVariableName(); + _queryVariables.Add(varName, value); + _queryGlobals.Add(new QueryGlobal(property.Name, new SubQuery($"<{scalarInfo}>${varName}"))); + } + else if (property.PropertyType.IsAssignableTo(typeof(IQueryBuilder))) + { + // add it as a sub-query + _queryGlobals.Add(new QueryGlobal(property.Name, value)); + } + 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 + throw new InvalidOperationException($"Cannot serialize {property.Name}: No serialization strategy found for {property.PropertyType}"); + } return EnterNewContext>(); } @@ -365,7 +388,8 @@ public ISelectQuery Select(Expression(new SelectContext(typeof(TType)) { - Shape = shape + Shape = shape, + IsFreeObject = typeof(TNewType).IsAnonymousType(), }); return EnterNewType(); } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs index 5b43fe05..bc5f2058 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs @@ -28,7 +28,7 @@ internal static class InitializationTranslator case MemberExpression when isLink: { var disassembled = ExpressionUtils.DisassembleExpression(Expression).ToArray(); - if(disassembled.Last() is ConstantExpression constant && disassembled[disassembled.Length - 2] is MemberExpression constParent) + if (disassembled.Last() is ConstantExpression constant && disassembled[disassembled.Length - 2] is MemberExpression constParent) { // get the value var memberValue = constParent.Member.GetMemberValue(constant.Value); @@ -63,6 +63,13 @@ internal static class InitializationTranslator }), null); initializations.Add($"{memberName} := {name}"); } + else if (disassembled.Last().Type.IsAssignableTo(typeof(QueryContext))) + { + var translated = ExpressionTranslator.ContextualTranslate(Expression, context); + initializations.Add($"{memberName} := {translated}"); + } + else + throw new InvalidOperationException($"Cannot translate {Expression}"); } break; case MemberInitExpression or NewExpression: From 5d664ac8b1f4c61648d9d0071b8318b59ef94aa9 Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Tue, 30 Aug 2022 17:31:48 -0700 Subject: [PATCH 66/70] different approach for array_agg --- .../Examples/QueryBuilder.cs | 1 - .../Properties/launchSettings.json | 5 ++--- src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs | 10 +++++----- .../Expressions/MethodCallExpressionTranslator.cs | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index a9bb7294..cdb0c985 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -20,7 +20,6 @@ internal class QueryBuilderExample : IExample public class LinkPerson { - public Guid Id { get; set; } public string? Name { get; set; } public string? Email { get; set; } public LinkPerson? BestFriend { get; set; } diff --git a/examples/EdgeDB.Examples.ExampleTODOApi/Properties/launchSettings.json b/examples/EdgeDB.Examples.ExampleTODOApi/Properties/launchSettings.json index 9f782db0..0999bbcb 100644 --- a/examples/EdgeDB.Examples.ExampleTODOApi/Properties/launchSettings.json +++ b/examples/EdgeDB.Examples.ExampleTODOApi/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "https://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, @@ -11,7 +11,6 @@ "profiles": { "EdgeDB.Examples.ExampleTODOApi": { "commandName": "Project", - "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "https://localhost:7155;http://localhost:5155", @@ -28,4 +27,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 7a960ed0..00949384 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -313,8 +313,8 @@ private ShapeDefinition BuildJsonShape() // return a slice operator for multi links or a index operator for single links return isArray - ? $"{edgedbName} := distinct array_unpack({mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{x.Name}', '{mappingName}_depth_from') ?? 0:json_get({iterationName}, '{x.Name}', '{mappingName}_depth_to') ?? 0])" - : $"{edgedbName} := {mappingName}_d{indexCopy + 1}[json_get({iterationName}, '{x.Name}', '{mappingName}_depth_index')] if json_typeof(json_get({iterationName}, '{x.Name}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; + ? $"{edgedbName} := (select {mappingName}_d{indexCopy + 1} offset json_get({iterationName}, '{x.Name}', '{mappingName}_depth_from') ?? 0 limit json_get({iterationName}, '{x.Name}', '{mappingName}_depth_to') ?? 0)" + : $"{edgedbName} := (select {mappingName}_d{indexCopy + 1} offset json_get({iterationName}, '{x.Name}', '{mappingName}_depth_index') limit 1) if json_typeof(json_get({iterationName}, '{x.Name}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; } // if its a scalar type, use json_get to pull the value and cast it to our property @@ -335,7 +335,7 @@ private ShapeDefinition BuildJsonShape() // add the iteration and turn it into an array so we can use the index operand // during our query stage - return $"array_agg((for {iterationName} in json_array_unpack(${variableName}) union {insertStatement}))"; + return $"(for {iterationName} in (${variableName}) union {insertStatement})"; }); // tell the builder that this query requires introspection @@ -386,8 +386,8 @@ private ShapeDefinition BuildJsonShape() { // return a slice operator for multi links or a index operator for single links return isArray - ? $"{edgedbName} := distinct array_unpack({mappingName}_d1[json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_from') ?? 0:json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_to') ?? 0])" - : $"{edgedbName} := {mappingName}_d1[json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_index')] if json_typeof(json_get({jsonValue.Name}, '{x.Name}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; + ? $"{edgedbName} := (select {mappingName}_d1 offset json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_from') ?? 0 limit json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_to') ?? 0)" + : $"{edgedbName} := (select {mappingName}_d1 offset json_get({jsonValue.Name}, '{x.Name}', '{mappingName}_depth_index') limit 1) if json_typeof(json_get({jsonValue.Name}, '{x.Name}')) != 'null' else <{x.PropertyType.GetEdgeDBTypeName()}>{{}}"; } // if its a scalar type, use json_get to pull the value and cast it to our property diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs index 75258bf8..b75ba3a9 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -38,7 +38,7 @@ private bool ShouldTranslate(MethodCallExpression expression, ExpressionContext { // if the method references context or a parameter to our current root lambda var disassembledInstance = expression.Object is null - ? Array.Empty () + ? Array.Empty() : ExpressionUtils.DisassembleExpression(expression.Object).ToArray(); var isInstanceReferenceToContext = expression.Object?.Type == typeof(QueryContext) || context.RootExpression.Parameters.Any(x => disassembledInstance.Contains(x)); From 519590fe21d093c1c7f37a0f1dab65b1949b5a81 Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Wed, 31 Aug 2022 10:02:41 -0700 Subject: [PATCH 67/70] fix for iterator to remove array_agg --- src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 00949384..a6d5a4b7 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -335,7 +335,7 @@ private ShapeDefinition BuildJsonShape() // add the iteration and turn it into an array so we can use the index operand // during our query stage - return $"(for {iterationName} in (${variableName}) union {insertStatement})"; + return $"(for {iterationName} in json_array_unpack(${variableName}) union {insertStatement})"; }); // tell the builder that this query requires introspection From e7451f8b509c26bcadbe802bde7c08cdb7057f07 Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Wed, 31 Aug 2022 12:21:30 -0700 Subject: [PATCH 68/70] add json variable pathing. Closes #27 --- .../Examples/QueryBuilder.cs | 14 +++++ .../EdgeDBContractResolver.cs | 2 +- .../Models/DataTypes/Json.cs | 10 +++ src/EdgeDB.Net.QueryBuilder/EdgeQL.cs | 3 + .../Interfaces/IQueryBuilder.cs | 2 +- src/EdgeDB.Net.QueryBuilder/JsonVariable.cs | 62 +++++++++++++++++-- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 13 +++- .../QueryNodes/Contexts/NodeContext.cs | 2 +- .../QueryNodes/ForNode.cs | 4 +- .../Expressions/MemberExpressionTranslator.cs | 23 +++++++ 10 files changed, 123 insertions(+), 12 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index cdb0c985..b0c8f068 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -61,6 +61,20 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { + var test = QueryBuilder.With(new + { + Args = EdgeQL.AsJson(new ConstraintPerson + { + Name = "Example", + Email = "example@example.com" + }) + }).Select(ctx => new + { + PassedName = ctx.Variables.Args.Value.Name, + PassedEmail = ctx.Variables.Args.Value.Email + }).Build(); + + // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); diff --git a/src/EdgeDB.Net.Driver/ContractResolvers/EdgeDBContractResolver.cs b/src/EdgeDB.Net.Driver/ContractResolvers/EdgeDBContractResolver.cs index 72c7a15c..7a52e3d9 100644 --- a/src/EdgeDB.Net.Driver/ContractResolvers/EdgeDBContractResolver.cs +++ b/src/EdgeDB.Net.Driver/ContractResolvers/EdgeDBContractResolver.cs @@ -30,7 +30,7 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ private static JsonConverter? GetConverter(JsonProperty property, PropertyInfo propInfo, Type type, int depth) { // range type - if (ReflectionUtils.IsSubclassOfRawGeneric(typeof(DataTypes.Range<>), type)) + if (ReflectionUtils.IsSubTypeOfGenericType(typeof(DataTypes.Range<>), type)) return RangeConverter.Instance; return null; diff --git a/src/EdgeDB.Net.Driver/Models/DataTypes/Json.cs b/src/EdgeDB.Net.Driver/Models/DataTypes/Json.cs index 1f775f3f..b0e86f14 100644 --- a/src/EdgeDB.Net.Driver/Models/DataTypes/Json.cs +++ b/src/EdgeDB.Net.Driver/Models/DataTypes/Json.cs @@ -47,6 +47,16 @@ public Json(string? value) ? serializer.Deserialize(new JsonTextReader(new StringReader(Value))) : EdgeDBConfig.JsonSerializer.DeserializeObject(Value); + /// + /// Serializes an to using the default + /// or if specified. + /// + /// The value to serialize. + /// The optional serializer to use when serializing. + /// The json representation of . + public static Json Serialize(object? value, JsonSerializer? serializer = null) + => (serializer ?? EdgeDBConfig.JsonSerializer).SerializeObject(value); + public static implicit operator string?(Json j) => j.Value; public static implicit operator Json(string? value) => new(value); } diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs b/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs index 3a4dead9..e16f429a 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs +++ b/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs @@ -9,6 +9,9 @@ namespace EdgeDB { public sealed partial class EdgeQL { + public static JsonReferenceVariable AsJson(T value) + => new JsonReferenceVariable(value); + [EquivalentOperator(typeof(EdgeDB.Operators.LinksAddLink))] public static TType[] AddLink(IQuery element) => default!; diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs index ee1d41cc..4ffc4ef8 100644 --- a/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs @@ -27,7 +27,7 @@ public interface IQueryBuilder : /// The collection to iterate over. /// The iterator for the UNION statement. /// The current query. - IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator); + IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator); /// /// Adds a WITH statement whos variables are the properties defined in . diff --git a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs index b174a886..00f2df77 100644 --- a/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs +++ b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs @@ -8,7 +8,7 @@ namespace EdgeDB { /// - /// Represents an abstracted form of . + /// Represents an abstracted form of . /// internal interface IJsonVariable { @@ -42,11 +42,62 @@ internal interface IJsonVariable 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 + { + /// + /// 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; + } + + /// + int IJsonVariable.Depth + => 0; + + /// + 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"); + + /// + Type IJsonVariable.InnerType + => typeof(T); + + /// + IEnumerable IJsonVariable.GetObjectsAtDepth(int targetDepth) + => Array.Empty(); + } + /// /// Represents a json value used within queries. /// /// The inner type that the json value represents. - public class JsonVariable : IJsonVariable + public class JsonCollectionVariable : IJsonVariable { /// /// Gets the name of the json variable. @@ -71,6 +122,9 @@ public T Value internal bool IsObjectArray => _array.All(x => x is JObject); + /// + /// Gets the variable name of the current json variable. + /// internal string VariableName { get; } /// @@ -79,12 +133,12 @@ internal bool IsObjectArray private readonly JArray _array; /// - /// Constructs a new . + /// 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 JsonVariable(string name, string varName, JArray array) + internal JsonCollectionVariable(string name, string varName, JArray array) { _array = array; VariableName = varName; diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index bae1507c..8bd7db0a 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -21,9 +21,9 @@ public static class QueryBuilder public static IQueryBuilder> With(TVariables variables) => QueryBuilder.With(variables); - /// + /// public static IMultiCardinalityExecutable For(IEnumerable collection, - Expression, IQueryBuilder>> iterator) + Expression, IQueryBuilder>> iterator) => new QueryBuilder().For(collection, iterator); /// @@ -336,6 +336,13 @@ public QueryBuilder> With(TVariables { _queryGlobals.Add(new QueryGlobal(property.Name, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = '{id}')"))); } + else if (property.PropertyType.IsAssignableTo(typeof(IJsonVariable))) + { + // Serialize and add as global and variable + var jsonVarName = QueryUtils.GenerateRandomVariableName(); + _queryVariables.Add(jsonVarName, DataTypes.Json.Serialize(value)); + _queryGlobals.Add(new QueryGlobal(property.Name, new SubQuery($"${jsonVarName}"), value)); + } else throw new InvalidOperationException($"Cannot serialize {property.Name}: No serialization strategy found for {property.PropertyType}"); } @@ -343,7 +350,7 @@ public QueryBuilder> With(TVariables return EnterNewContext>(); } - public IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator) + public IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator) { AddNode(new ForContext(typeof(TType)) { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs index d271e618..96275eb8 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.cs @@ -32,7 +32,7 @@ internal abstract class NodeContext /// Gets whether or not the current type is a json variable. /// public bool IsJsonVariable - => ReflectionUtils.IsSubTypeOfGenericType(typeof(JsonVariable<>), CurrentType); + => ReflectionUtils.IsSubTypeOfGenericType(typeof(JsonCollectionVariable<>), CurrentType); /// /// Gets a collection of child queries. diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs index 8eab31b1..7f2b3b2b 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs @@ -47,8 +47,8 @@ public ForNode(NodeBuilder builder) : base(builder) return x switch { _ when x.Type == typeof(QueryContext) => new QueryContext(), - _ when ReflectionUtils.IsSubTypeOfGenericType(typeof(JsonVariable<>), x.Type) - => typeof(JsonVariable<>).MakeGenericType(Context.CurrentType) + _ when ReflectionUtils.IsSubTypeOfGenericType(typeof(JsonCollectionVariable<>), x.Type) + => typeof(JsonCollectionVariable<>).MakeGenericType(Context.CurrentType) .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new Type[] { typeof(string), typeof(string), typeof(JArray)})! .Invoke(new object?[] { name, varName, jArray })!, _ => throw new ArgumentException($"Cannot use {x.Type} as a parameter to a 'FOR' expression") diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs index 3ecf4d58..43b0d43d 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -39,6 +39,29 @@ internal class MemberExpressionTranslator : ExpressionTranslator x.Name == targetMember.Member.Name); + + if (jsonGlobal is null) + throw new InvalidOperationException($"Cannot access json object \"{targetMember.Member.Name}\": No global found!"); + + // verify the global is json + if (jsonGlobal.Reference is not IJsonVariable jsonVariable) + throw new InvalidOperationException($"The global \"{jsonGlobal.Name}\" is not a json value"); + + // get the scalar type to cast to + if (!EdgeDBTypeUtils.TryGetScalarType(deconstructed[0].Type, out var scalarInfo)) + throw new InvalidOperationException($"json value access must be scalar, path: {deconstructed[0].ToString()}"); + + + return $"<{scalarInfo}>json_get({jsonGlobal.Name}, {string.Join(", ", path.Select(x => $"'{x}'"))})"; + } + if (deconstructed.Length != 3) throw new NotSupportedException("Cannot use nested values for variable access"); From 1604eee28fa6fbb281c1622370fe8633044de610 Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Wed, 31 Aug 2022 12:33:45 -0700 Subject: [PATCH 69/70] Fix serialization of wrong reference --- .../Examples/QueryBuilder.cs | 28 +++++++++---------- src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs | 5 ++-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index b0c8f068..b92d6e5f 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -61,20 +61,6 @@ public async Task ExecuteAsync(EdgeDBClient client) private static async Task QueryBuilderDemo(EdgeDBClient client) { - var test = QueryBuilder.With(new - { - Args = EdgeQL.AsJson(new ConstraintPerson - { - Name = "Example", - Email = "example@example.com" - }) - }).Select(ctx => new - { - PassedName = ctx.Variables.Args.Value.Name, - PassedEmail = ctx.Variables.Args.Value.Email - }).Build(); - - // Selecting a type with autogen shape var query = QueryBuilder.Select().Build().Prettify(); @@ -146,6 +132,20 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) }) }).Build().Prettify(); + // With object variables + query = QueryBuilder.With(new + { + Args = EdgeQL.AsJson(new ConstraintPerson + { + Name = "Example", + Email = "example@example.com" + }) + }).Select(ctx => new + { + PassedName = ctx.Variables.Args.Value.Name, + PassedEmail = ctx.Variables.Args.Value.Email + }).Build(); + // Inserting a new type var person = new LinkPerson { diff --git a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs index 8bd7db0a..c7c8eda2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -336,11 +336,12 @@ public QueryBuilder> With(TVariables { _queryGlobals.Add(new QueryGlobal(property.Name, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = '{id}')"))); } - else if (property.PropertyType.IsAssignableTo(typeof(IJsonVariable))) + else if (ReflectionUtils.IsSubTypeOfGenericType(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(value)); + _queryVariables.Add(jsonVarName, DataTypes.Json.Serialize(referenceValue)); _queryGlobals.Add(new QueryGlobal(property.Name, new SubQuery($"${jsonVarName}"), value)); } else From 3d3dff67273130d21d35ae4a5b07f500f14c2305 Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Wed, 31 Aug 2022 12:34:04 -0700 Subject: [PATCH 70/70] fix example --- examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs index b92d6e5f..64b1fb96 100644 --- a/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs +++ b/examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs @@ -144,7 +144,7 @@ private static async Task QueryBuilderDemo(EdgeDBClient client) { PassedName = ctx.Variables.Args.Value.Name, PassedEmail = ctx.Variables.Args.Value.Email - }).Build(); + }).Build().Pretty; // Inserting a new type var person = new LinkPerson