Skip to content

Commit

Permalink
debug views
Browse files Browse the repository at this point in the history
  • Loading branch information
quinchs committed Mar 9, 2024
1 parent 975f2c6 commit 8cc1ebb
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 74 deletions.
39 changes: 33 additions & 6 deletions examples/EdgeDB.Examples.CSharp/Examples/QueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,35 @@ public async Task ExecuteAsync(EdgeDBClient client)
{
try
{
var tests = await QueryBuilder
.Select<ObjectType>(shape =>
{
shape.IncludeMultiLink(x => x.Constraints);
shape.IncludeMultiLink(x => x.Properties, shape =>
shape.Computeds((ctx, prop) => new
{
Cardinality = (string)ctx.UnsafeLocal<object>("cardinality") == "One"
? ctx.UnsafeLocal<bool>("required")
? Cardinality.One
: Cardinality.AtMostOne
: ctx.UnsafeLocal<bool>("required")
? Cardinality.AtLeastOne
: Cardinality.Many,
TargetId = ctx.UnsafeLocal<Guid>("target.id"),
IsLink = ctx.Raw<object?>("[IS schema::Link]") != null,
IsExclusive =
ctx.Raw<bool>("exists (select .constraints filter .name = 'std::exclusive')"),
IsComputed = EdgeQL.Len(ctx.UnsafeLocal<object[]>("computed_fields")) != 0,
IsReadonly = ctx.UnsafeLocal<bool>("readonly"),
HasDefault =
ctx.Raw<bool>(
"EXISTS .default or (\"std::sequence\" in .target[IS schema::ScalarType].ancestors.name)")
})
);
})
.Filter((x, ctx) => !ctx.UnsafeLocal<bool>("builtin"))
.CompileAsync(client, true);

await QueryBuilderDemo(client);
}
catch (Exception x)
Expand All @@ -46,14 +75,13 @@ private static async Task QueryBuilderDemo(EdgeDBClient client)
var query = QueryBuilder.Select<Person>().Compile().Prettify();

// Adding a filter, orderby, offset, and limit
query = QueryBuilder
var queryTest = QueryBuilder
.Select<Person>()
.Filter(x => EdgeQL.ILike(x.Name, "e%"))
.OrderByDesending(x => x.Name)
.Offset(2)
.Limit(10)
.Compile()
.Prettify();
.Compile(true);

// Specifying a shape
query = QueryBuilder.Select<Person>(shape =>
Expand Down Expand Up @@ -117,12 +145,11 @@ private static async Task QueryBuilderDemo(EdgeDBClient client)
.Prettify();

// Autogenerating unless conflict with introspection
query = (await QueryBuilder
queryTest = (await QueryBuilder
.Insert(person)
.UnlessConflict()
.ElseReturn()
.CompileAsync(client))
.Prettify();
.CompileAsync(client, true));

// Bulk inserts
var data = new Person[]
Expand Down
154 changes: 151 additions & 3 deletions src/EdgeDB.Net.QueryBuilder/Compiled/DebugCompiledQuery.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,160 @@
namespace EdgeDB.Compiled;
using System.Diagnostics;
using System.Text;

namespace EdgeDB.Compiled;

[DebuggerDisplay("{DebugView}")]
public sealed class DebugCompiledQuery : CompiledQuery
{
public string DebugView { get; }

internal DebugCompiledQuery(string query, Dictionary<string, object?> variables, LinkedList<QuerySpan> markers)
: base(query, variables)
{
DebugView = CreateDebugText(query, variables, markers);
}

internal DebugCompiledQuery(string query, Dictionary<string, object?> variablesInternal, QueryWriter writer)
: base(query, variablesInternal)
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<string, object?> variables,
LinkedList<QuerySpan> markers)
{
var sb = new StringBuilder();

sb.AppendLine(query);

if (markers.Count > 0)
{
var view = CreateMarkerView(markers);
var markerTexts = new Dictionary<string, string>();
var rows = new List<StringBuilder>();

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;

// 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
rowText.Insert(column.Range.Start.Value, barText);

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');
}

// desc
var desc = $"{column.Marker.Type}: {column.Name}";
string descriptionText;

if (desc.Length > size)
{
var icon = NumberCircle(markerTexts.Count + 1);
markerTexts.Add(icon, desc);
descriptionText = icon;
}
else
{
descriptionText = desc;
}

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

rowText.Remove(position, descriptionText.Length);
rowText.Insert(position, descriptionText);
}

rows.Add(rowText);

}

foreach (var row in rows)
{
sb.AppendLine(row.ToString());
}

if (markerTexts.Count > 0)
{
sb.AppendLine("Markers:");

foreach (var (name, value) in markerTexts)
{
sb.AppendLine($" - {name}: {value}");
}
}
}
else
{
sb.AppendLine();
}

if (variables.Count > 0)
{
sb.AppendLine("Variables: ");

foreach (var (name, value) in variables)
{
sb.AppendLine($" - {name} := {value}");
}
}

return sb.ToString();
}

private static List<List<QuerySpan>> CreateMarkerView(LinkedList<QuerySpan> spans)
{
var ordered = new Queue<QuerySpan>(spans.OrderBy(x => x.Range.End.Value - x.Range.Start.Value)); // order by 'size'
var result = new List<List<QuerySpan>>();
var row = new List<QuerySpan>();

while (ordered.TryDequeue(out var span))
{
var head = row.LastOrDefault();
if (head is null)
{
row.Add(span);
continue;
}

if (head.Range.End.Value >= span.Range.Start.Value)
{
// overlap
result.Add(row);
row = [span];
continue;
}

row.Add(span);
}

result.Add(row);
return result;
}
}
6 changes: 4 additions & 2 deletions src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,18 +305,20 @@ public interface IQueryBuilder
/// If the query requires introspection please use
/// <see cref="CompileAsync"/>.
/// </remarks>
/// <param name="debug">Whether or not to compile the query in a debug fashion, returning a <see cref="DebugCompiledQuery"/>.</param>
/// <returns>
/// A <see cref="CompiledQuery"/>.
/// </returns>
CompiledQuery Compile();
CompiledQuery Compile(bool debug = false);

/// <summary>
/// Compiles the current query asynchronously, allowing database introspection.
/// </summary>
/// <param name="edgedb">The client to preform introspection with.</param>
/// <param name="debug">Whether or not to compile the query in a debug fashion, returning a <see cref="DebugCompiledQuery"/>.</param>
/// <param name="token">A cancellation token to cancel the asynchronous operation.</param>
/// <returns>A <see cref="CompiledQuery"/>.</returns>
ValueTask<CompiledQuery> CompileAsync(IEdgeDBQueryable edgedb, CancellationToken token = default);
ValueTask<CompiledQuery> CompileAsync(IEdgeDBQueryable edgedb, bool debug = false, CancellationToken token = default);

internal void CompileInternal(QueryWriter writer, CompileContext? context = null);
}
Expand Down
40 changes: 0 additions & 40 deletions src/EdgeDB.Net.QueryBuilder/Lexical/Observers/LastNodeObserver.cs

This file was deleted.

52 changes: 52 additions & 0 deletions src/EdgeDB.Net.QueryBuilder/Lexical/Observers/RangeNodeObserver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace EdgeDB;

internal sealed class RangeNodeObserver : INodeObserver, IDisposable
{
[MemberNotNullWhen(true, nameof(First))]
[MemberNotNullWhen(true, nameof(Last))]
public bool HasValue
=> First is not null;

public LooseLinkedList<Value>.Node? First { get; private set; }

public LooseLinkedList<Value>.Node? Last { get; private set; }


private readonly QueryWriter _writer;

public RangeNodeObserver(QueryWriter writer)
{
_writer = writer;
_writer.AddObserver(this);
}

public void OnAdd(LooseLinkedList<Value>.Node node)
{
Last = node;

if (First is not null) return;

First = node;
}

public void OnRemove(LooseLinkedList<Value>.Node node)
{
if (First == node)
{
First = null;
}

if (Last == node)
{
Last = null;
}
}

public void Dispose()
{
_writer.RemoveObserver(this);
}
}
11 changes: 11 additions & 0 deletions src/EdgeDB.Net.QueryBuilder/Lexical/QuerySpan.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace EdgeDB;

internal sealed class QuerySpan(Range range, string content, Marker marker, string name)
{
public Range Range { get; } = range;

public string Content { get; } = content;

public Marker Marker { get; } = marker;
public string Name { get; } = name;
}
Loading

0 comments on commit 8cc1ebb

Please sign in to comment.