From e035d58cdfee69fabc25a2cbf503bda618e49885 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sat, 9 Mar 2024 02:41:49 -0400 Subject: [PATCH] debug view flame-graph --- .../Builders/ShapeBuilder.cs | 11 +- .../Compiled/DebugCompiledQuery.cs | 118 ++++++++++++------ src/EdgeDB.Net.QueryBuilder/Grammar/Terms.cs | 54 +++++--- src/EdgeDB.Net.QueryBuilder/Lexical/Marker.cs | 4 +- .../Lexical/MarkerType.cs | 1 + .../Lexical/QueryWriter.cs | 13 +- .../QueryNodes/ForNode.cs | 8 +- .../QueryNodes/InsertNode.cs | 47 +++---- .../Expressions/ExpressionTranslator.cs | 7 +- .../Utils/Deferrable.cs | 42 +++++++ 10 files changed, 208 insertions(+), 97 deletions(-) create mode 100644 src/EdgeDB.Net.QueryBuilder/Utils/Deferrable.cs diff --git a/src/EdgeDB.Net.QueryBuilder/Builders/ShapeBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Builders/ShapeBuilder.cs index 8787c328..eebd74dd 100644 --- a/src/EdgeDB.Net.QueryBuilder/Builders/ShapeBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/Builders/ShapeBuilder.cs @@ -25,14 +25,17 @@ internal class SelectShape { private readonly SelectedProperty[] _shape; - public SelectShape(IEnumerable shape) + private readonly Type _type; + + public SelectShape(IEnumerable shape, Type type) { _shape = shape.ToArray(); + _type = type; } public void Compile(QueryWriter writer, SelectShapeExpressionTranslatorCallback translator) { - writer.Shape($"shape_{_shape.GetHashCode()}", _shape, (writer, x) => + writer.Shape($"{_type.GetEdgeDBTypeName()}_shape", _shape, (writer, x) => { x.Compile(writer, translator); }); @@ -163,7 +166,7 @@ internal static MemberInfo GetSelectedProperty(LambdaExpression expression) internal SelectShape GetShape() { - return new(SelectedProperties.Select(x => x.Value)); + return new(SelectedProperties.Select(x => x.Value), SelectedType); } SelectShape IShapeBuilder.GetShape() => GetShape(); @@ -266,7 +269,7 @@ private SelectedProperty ParseShape(MemberInfo info, ShapeElementExpression elem var flattened = FlattenNewExpression(info.GetMemberType(), newExpression, element.Root) .Select(x => ParseShape(x.Key, x.Value)); - return new SelectedProperty(info, new SelectShape(flattened)); + return new SelectedProperty(info, new SelectShape(flattened, info.GetMemberType())); } // computed diff --git a/src/EdgeDB.Net.QueryBuilder/Compiled/DebugCompiledQuery.cs b/src/EdgeDB.Net.QueryBuilder/Compiled/DebugCompiledQuery.cs index 67543be6..d72260ff 100644 --- a/src/EdgeDB.Net.QueryBuilder/Compiled/DebugCompiledQuery.cs +++ b/src/EdgeDB.Net.QueryBuilder/Compiled/DebugCompiledQuery.cs @@ -14,41 +14,44 @@ internal DebugCompiledQuery(string query, Dictionary variables, DebugView = CreateDebugText(query, variables, markers); } - private static string NumberCircle(int i) - { - if (i <= 50) - { - return i == 0 ? "\u24ea" : ((char)('\u2460' + i - 1)).ToString(); - } - - return i.ToString(); - } - private static string CreateDebugText(string query, Dictionary variables, LinkedList markers) { var sb = new StringBuilder(); - sb.AppendLine(query); - if (markers.Count > 0) { + StringBuilder? topRow = null; + var view = CreateMarkerView(markers); - var markerTexts = new Dictionary(); + var markerTexts = new Dictionary(); var rows = new List(); foreach (var row in view) { var rowText = new StringBuilder($"{"".PadLeft(query.Length)}\n{"".PadLeft(query.Length)}"); - foreach (var column in row) { var size = column.Range.End.Value - column.Range.Start.Value; + if (size <= 2) + { + topRow ??= new StringBuilder("".PadLeft(query.Length)); + + var indicator = (markerTexts.Count + 1).ToString(); + + topRow.Remove(column.Range.Start.Value, indicator.Length); + topRow.Insert(column.Range.Start.Value, indicator); + + markerTexts.Add(indicator, column); + continue; + } + // bar rowText.Remove(column.Range.Start.Value, size); var barText = new StringBuilder($"\u2550".PadLeft(size - 3, '\u2550')); + barText.Insert(barText.Length / 2, "\u2566"); // T barText.Insert(0, "\u255a"); // corner UR barText.Insert(size - 1, "\u255d"); // corner UL @@ -56,45 +59,84 @@ private static string CreateDebugText(string query, Dictionary foreach (var prevRowText in rows) { - prevRowText.Remove(column.Range.Start.Value, 1); - prevRowText.Insert(column.Range.Start.Value, '\u2551'); - prevRowText.Remove(query.Length + 1 + column.Range.Start.Value, 1); - prevRowText.Insert(query.Length + 1 + column.Range.Start.Value, '\u2551'); - - prevRowText.Remove(column.Range.End.Value - 1, 1); - prevRowText.Insert(column.Range.End.Value - 1, '\u2551'); - prevRowText.Remove(query.Length + column.Range.End.Value, 1); - prevRowText.Insert(query.Length + column.Range.End.Value, '\u2551'); + var prevStart = prevRowText[column.Range.Start.Value]; + + if (prevStart is ' ' or '\u255a') + { + prevRowText.Remove(column.Range.Start.Value, 1); + prevRowText.Insert(column.Range.Start.Value, prevStart == '\u255a' ? '\u2560' : '\u2551'); + } + + prevStart = prevRowText[query.Length + 1 + column.Range.Start.Value]; + + if (prevStart is ' ' or '\u255a') + { + prevRowText.Remove(query.Length + 1 + column.Range.Start.Value, 1); + prevRowText.Insert(query.Length + 1 + column.Range.Start.Value, prevStart == '\u255a' ? '\u2560' : '\u2551'); + } + + var prevEnd = prevRowText[column.Range.End.Value - 1]; + + if (prevEnd is ' ' or '\u255d') + { + prevRowText.Remove(column.Range.End.Value - 1, 1); + prevRowText.Insert(column.Range.End.Value - 1, prevEnd == '\u255d' ? '\u2563' : '\u2551'); + } + + prevEnd = prevRowText[query.Length + column.Range.End.Value]; + + if (prevEnd is ' ' or '\u255d') + { + prevRowText.Remove(query.Length + column.Range.End.Value, 1); + prevRowText.Insert(query.Length + column.Range.End.Value, prevEnd == '\u255d' ? '\u2563' : '\u2551'); + } } // desc - var desc = $"{column.Marker.Type}: {column.Name}"; - string descriptionText; + var icon = (markerTexts.Count + 1).ToString(); + var desc = $"{icon} [{column.Marker.Type}] {column.Name}"; + + if (column.Marker.DebugText is not null) + desc += $": {column.Marker.DebugText.Get()}"; + - if (desc.Length > size) + if (desc.Length - 3 > size) { - var icon = NumberCircle(markerTexts.Count + 1); - markerTexts.Add(icon, desc); - descriptionText = icon; + desc = $"{icon} [{column.Marker.Type}] {column.Name}"; } - else + + if (desc.Length - 3 > size) + { + desc = $"{icon} {column.Name}"; + } + + if (desc.Length - 3 >= size) { - descriptionText = desc; + desc = icon; } + markerTexts.Add(icon, column); + var position = query.Length + 1 // line 2 + column.Range.Start.Value // start of the slice + size / 2 // half of the slices' length : center of the slice - - (descriptionText.Length == 1 ? 1 : descriptionText.Length / 2); // half of the contents length : centers it + - (desc.Length == 1 + ? size % 2 == 0 ? 1 : 0 // don't ask + : desc.Length / 2); // half of the contents length : centers it - rowText.Remove(position, descriptionText.Length); - rowText.Insert(position, descriptionText); + rowText.Remove(position, desc.Length); + rowText.Insert(position, desc); } rows.Add(rowText); } + if (topRow is not null) + sb.AppendLine(topRow.ToString()); + + sb.AppendLine(query); + foreach (var row in rows) { sb.AppendLine(row.ToString()); @@ -104,14 +146,20 @@ private static string CreateDebugText(string query, Dictionary { sb.AppendLine("Markers:"); + var markerTypePadding = Enum.GetValues().Max(x => Enum.GetName(x)!.Length) + 2; foreach (var (name, value) in markerTexts) { - sb.AppendLine($" - {name}: {value}"); + var desc = $"{$"[{value.Marker.Type}]".PadRight(markerTypePadding)} {value.Name}"; + if (value.Marker.DebugText is not null) + desc += $": {value.Marker.DebugText.Get()}"; + + sb.AppendLine($" - {name.PadRight(markerTexts.Count.ToString().Length)} {$"({value.Range})".PadRight(query.Length.ToString().Length*2+4)} {desc}"); } } } else { + sb.AppendLine(query); sb.AppendLine(); } diff --git a/src/EdgeDB.Net.QueryBuilder/Grammar/Terms.cs b/src/EdgeDB.Net.QueryBuilder/Grammar/Terms.cs index e092566f..729f592a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Grammar/Terms.cs +++ b/src/EdgeDB.Net.QueryBuilder/Grammar/Terms.cs @@ -3,10 +3,10 @@ internal static class Terms { #region Markers - public static QueryWriter LabelVariable(this QueryWriter writer, string name, params Value[] values) - => writer.Marker(MarkerType.Variable, name, values); - public static QueryWriter LabelVerbose(this QueryWriter writer, string name, params Value[] values) - => writer.Marker(MarkerType.Verbose, name, values); + public static QueryWriter LabelVariable(this QueryWriter writer, string name, Deferrable? debug = null, params Value[] values) + => writer.Marker(MarkerType.Variable, name, debug, values); + public static QueryWriter LabelVerbose(this QueryWriter writer, string name, Deferrable? debug = null, params Value[] values) + => writer.Marker(MarkerType.Verbose, name, debug, values); #endregion public static QueryWriter Wrapped(this QueryWriter writer, Value value, string separator = "()") @@ -20,7 +20,7 @@ public static QueryWriter Wrapped(this QueryWriter writer, Value value, string s public static QueryWriter Wrapped(this QueryWriter writer, WriterProxy value, string separator = "()") { if (separator.Length != 2) - throw new ArgumentOutOfRangeException(nameof(separator)); + throw new ArgumentOutOfRangeException(nameof(separator)); return writer.Append(separator[0], value, separator[1]); } @@ -38,7 +38,7 @@ public static QueryWriter WrappedValues(this QueryWriter writer, string separato return writer.Append(value); } - public static QueryWriter Shape(this QueryWriter writer, string name, params Value[] values) + public static QueryWriter Shape(this QueryWriter writer, string name, Deferrable? debug, params Value[] values) { var value = new Value[values.Length + 2]; value[0] = "{ "; @@ -46,11 +46,11 @@ public static QueryWriter Shape(this QueryWriter writer, string name, params Val values.CopyTo(value[1..^1].AsSpan()); - return writer.Marker(MarkerType.Shape, name, value); + return writer.Marker(MarkerType.Shape, name, debug, value); } public static QueryWriter Shape(this QueryWriter writer, string name, T[] elements, - Action func, string parentheses = "{}") + Action func, string parentheses = "{}", Deferrable? debug = null) { if (parentheses.Length != 2) throw new ArgumentException("Parentheses must contain 2 characters", nameof(parentheses)); @@ -69,7 +69,8 @@ public static QueryWriter Shape(this QueryWriter writer, string name, T[] ele } writer.Append(' ', parentheses[1]); - }) + }), + debug ); } @@ -119,7 +120,7 @@ public FunctionArg(Value value, string? named = null) public static QueryWriter Function(this QueryWriter writer, string name, params FunctionArg[] args) { - return writer.Marker(MarkerType.Function, $"func_{name}_{args.GetHashCode()}", Value.Of( + return writer.Marker(MarkerType.Function, $"func_{name}", Value.Of( writer => { writer.Append(name, '('); @@ -127,14 +128,31 @@ public static QueryWriter Function(this QueryWriter writer, string name, params for (var i = 0; i < args.Length;) { var arg = args[i++]; - - if(writer.AppendIsEmpty(arg.Value, out _, out var node)) + bool isEmpty = false; + + writer.Marker( + MarkerType.FunctionArg, + $"func_{name}_arg_{i}", + null, + Value.Of( + writer => + { + if (writer.AppendIsEmpty(arg.Value, out _, out var node)) + { + isEmpty = true; + return; + } + + // append the named part if its specified + if (arg.Named is not null) + writer.Prepend(node, Value.Of(writer => writer.Append(arg.Named, " := "))); + } + ) + ); + + if(isEmpty) continue; - // append the named part if its specified - if (arg.Named is not null) - writer.Prepend(node, Value.Of(writer => writer.Append(arg.Named, " := "))); - if (i != args.Length) writer.Append(", "); } @@ -147,8 +165,8 @@ public static QueryWriter Function(this QueryWriter writer, string name, params public static QueryWriter SingleQuoted(this QueryWriter writer, Value value) => writer.Append('\'', value, '\''); - public static QueryWriter QueryArgument(this QueryWriter writer, Value type, string name) - => writer.Marker(MarkerType.Variable, name, '<', type, ">$", name); + public static QueryWriter QueryArgument(this QueryWriter writer, Value type, string name, Deferrable? debug = null) + => writer.Marker(MarkerType.Variable, name, debug, '<', type, ">$", name); public static Value[] Span(this QueryWriter writer, WriterProxy proxy) { diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/Marker.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/Marker.cs index 9f91a98b..33921b92 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/Marker.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/Marker.cs @@ -5,6 +5,7 @@ internal sealed class Marker public MarkerType Type { get; } public int Position { get; private set; } public int Size { get; } + public Deferrable? DebugText { get; } public LooseLinkedList.Node Start { get; set; } @@ -51,13 +52,14 @@ public IOrderedEnumerable Parents private readonly QueryWriter _writer; - internal Marker(MarkerType type, QueryWriter writer, int size, int position, LooseLinkedList.Node start) + internal Marker(MarkerType type, QueryWriter writer, int size, int position, LooseLinkedList.Node start, Deferrable? debugText) { Type = type; _writer = writer; Size = size; Position = position; Start = start; + DebugText = debugText; } internal void Update(int delta) diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerType.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerType.cs index 379a3930..f1c8d4ef 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerType.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/MarkerType.cs @@ -4,6 +4,7 @@ public enum MarkerType { Global, Function, + FunctionArg, Shape, Variable, Verbose diff --git a/src/EdgeDB.Net.QueryBuilder/Lexical/QueryWriter.cs b/src/EdgeDB.Net.QueryBuilder/Lexical/QueryWriter.cs index 81b2a4ef..2468a731 100644 --- a/src/EdgeDB.Net.QueryBuilder/Lexical/QueryWriter.cs +++ b/src/EdgeDB.Net.QueryBuilder/Lexical/QueryWriter.cs @@ -200,7 +200,7 @@ private void UpdateMarkers(int position, int delta) public bool TryGetMarker(string name, [MaybeNullWhen(false)] out LinkedList markers) => Markers.TryGetValue(name, out markers); - public QueryWriter Marker(MarkerType type, string name, in Value value) + public QueryWriter Marker(MarkerType type, string name, in Value value, Deferrable? debug = null) { if (!Markers.TryGetValue(name, out var markers)) Markers[name] = markers = new(); @@ -209,16 +209,16 @@ public QueryWriter Marker(MarkerType type, string name, in Value value) Append(in value, out var head); var size = TailIndex - currentIndex; - var marker = new Marker(type, this, size, currentIndex + 1, head); + var marker = new Marker(type, this, size, currentIndex + 1, head, debug); _markersRef.Add(marker); markers.AddLast(marker); return this; } - public QueryWriter Marker(MarkerType type, string name) - => Marker(type, name, name); + public QueryWriter Marker(MarkerType type, string name, Deferrable? debug = null) + => Marker(type, name, debug, name); - public QueryWriter Marker(MarkerType type, string name, params Value[] values) + public QueryWriter Marker(MarkerType type, string name, Deferrable? debug = null, params Value[] values) { if (values.Length == 0) return this; @@ -237,7 +237,8 @@ public QueryWriter Marker(MarkerType type, string name, params Value[] values) this, count, currentIndex + 1, - head + head, + debug ); _markersRef.Add(marker); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs index 83685497..dd3819ee 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs @@ -71,7 +71,13 @@ _ when ReflectionUtils.IsSubclassOfRawGeneric(typeof(JsonCollectionVariable<>), foreach (var node in SubNodes) { node.SchemaInfo = SchemaInfo; - node.FinalizeQuery(writer); + + writer.Marker( + MarkerType.Verbose, + $"FOR_inner_{GetHashCode()}", + Defer.This(() => $"FOR iteration inner node {node.GetType().Name}"), + Value.Of(writer => node.FinalizeQuery(writer)) + ); foreach (var variable in node.Builder.QueryVariables) SetVariable(variable.Key, variable.Value); diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs index 48dbd426..07e7bdb2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -708,8 +708,11 @@ private ShapeDefinition BuildInsertShape(Type? shapeType = null, object? shapeVa setters.Add(new ShapeSetter(writer => writer .Append(property.EdgeDBName) .Append(" := ") - .Shape($"shape_{type.GetEdgeDBTypeName()}_prop_{property.EdgeDBName}", enumerable.Cast().ToArray(), (w, x) => - BuildLinkResolver(w, innerType!, x) + .Shape( + $"shape_{type.GetEdgeDBTypeName()}_prop_{property.EdgeDBName}", + enumerable.Cast().ToArray(), + (w, x) => BuildLinkResolver(w, innerType!, x), + debug: Defer.This(() => $"Shape of property {property.PropertyName} of type {property.Type}") ))); RequiresIntrospection = true; @@ -773,7 +776,10 @@ private void BuildLinkResolver(QueryWriter writer, Type type, object? value) // add a insert select statement InlineOrGlobal(writer, type, new SubQuery((info, writer) => { - writer.Wrapped(writer => + writer.LabelVerbose( + $"link_resolver_{type.GetEdgeDBTypeName()}_{value.GetHashCode()}", + Defer.This(() => $"Built link resolver for {type}"), + Value.Of(writer => writer.Wrapped(writer => { var name = type.GetEdgeDBTypeName(); var exclusiveProps = QueryGenerationUtils.GetProperties(info, type, true).ToArray(); @@ -796,7 +802,8 @@ private void BuildLinkResolver(QueryWriter writer, Type type, object? value) "()" ); writer.Append(" else (select ", name, ")"); - }); + })) + ); }), value); } @@ -816,7 +823,11 @@ private void InlineOrGlobal(QueryWriter writer, Type type, SubQuery value, objec // if were in a query with the type or the query requires introspection add it as a global if (_subQueryMap.Contains(type) || value.RequiresIntrospection) { - writer.Marker(MarkerType.Global, GetOrAddGlobal(reference, value)); + writer.Marker( + MarkerType.Global, + GetOrAddGlobal(reference, value), + Defer.This(() => $"Global of type {type}") + ); return; } @@ -879,32 +890,6 @@ public void Else(IQueryBuilder builder) IncludeAutogeneratedNodes = false }) ); - - // // remove addon & autogen nodes. - // var userNodes = builder.Nodes - // .Where(x => !builder.Nodes.Any(y => y.SubNodes.Contains(x)) || !x.IsAutoGenerated) - // .ToArray(); - // - // // 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) - // { - // Builder.QueryVariables[variable.Key] = variable.Value; - // } - // - // var newBuilder = new QueryBuilder( - // userNodes.ToList(), builder.Globals.ToList(), - // builder.Variables.ToDictionary(x => x.Key, x => x.Value) - // ); - // - // var result = newBuilder.BuildWithGlobals(); - // - // _elseStatement = writer => writer - // .Append(" else ") - // .Wrapped(result.Query); } } } diff --git a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs index c0c8c15c..6fca4d1a 100644 --- a/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -67,6 +67,7 @@ public static WriterProxy Proxy(Expression expression, ExpressionContext express return writer => writer.LabelVerbose( label, + Defer.This(() => $"Translation of {expression}"), Value.Of(writer => ContextualTranslate(expression, expressionContext, writer)) ); } @@ -157,7 +158,11 @@ protected static void TranslateExpression( // if we can find a translator for the expression type, use it. if (_translators.TryGetValue(expType, out var translator)) { - translator.Translate(expression, context, writer); + writer.LabelVerbose( + expType.Name, + Defer.This(() => $"Translated form of '{expression}'"), + Value.Of(writer => translator.Translate(expression, context, writer)) + ); return; } diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/Deferrable.cs b/src/EdgeDB.Net.QueryBuilder/Utils/Deferrable.cs new file mode 100644 index 00000000..9619732c --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils/Deferrable.cs @@ -0,0 +1,42 @@ +namespace EdgeDB; + +internal static class Defer +{ + public static Deferrable This(Func value) => new Deferrable(value); +} + +internal sealed class Deferrable +{ + private T _value; + private readonly Func? _getValue; + + private bool _isDeferred; + + public Deferrable(T value) + { + _value = value; + _isDeferred = false; + _getValue = null; + } + + public Deferrable(Func value) + { + _value = default!; + _getValue = value; + _isDeferred = true; + } + + public T Get() + { + lock (this) + { + if (!_isDeferred) return _value; + _isDeferred = false; + return _value = _getValue!(); + + } + } + + public static implicit operator Deferrable(T value) => new(value); + public static implicit operator Deferrable(Func value) => new(value); +}