Skip to content

Commit

Permalink
groups!
Browse files Browse the repository at this point in the history
  • Loading branch information
quinchs committed Mar 11, 2024
1 parent 165a03b commit c1eb806
Show file tree
Hide file tree
Showing 41 changed files with 706 additions and 404 deletions.
97 changes: 97 additions & 0 deletions examples/EdgeDB.Examples.CSharp/Examples/QueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,103 @@ private static async Task QueryBuilderDemo(EdgeDBClient client)
.Delete<Person>()
.Filter(x => EdgeQL.ILike(x.Name, "e%"))
.Compile(true);

// grouping
query = QueryBuilder
.Group<Person>()
.By(x => x.Name)
.Compile(true);

// grouping by expressions
query = QueryBuilder
.Group<Person>()
.Using(person => new {StartsWithVowel = Regex.Matches(person.Name!, "(?i)^[aeiou]")})
.By(ctx => ctx.Using.StartsWithVowel)
.Compile(true);

// grouping by scalars
query = QueryBuilder
.With(ctx => new {People = ctx.SubQuerySingle(QueryBuilder.Select<Person>())})
.Group(ctx => ctx.Variables.People.Name)
.Using(name => new {StartsWithVowel = Regex.IsMatch(name!, "(?i)^[aeiou]")})
.By(group => group.Using.StartsWithVowel)
.Compile(true);

// grouping result as select
query = QueryBuilder
.With(ctx => new
{
People = ctx.SubQuerySingle(QueryBuilder.Select<Person>()),
Groups = ctx.SubQuerySingle(
QueryBuilder
.Group(ctx => ctx.Local<Person>("People"))
.Using(person => new {Vowel = Regex.IsMatch(person.Name!, "(?i)^[aeiou]")})
.By(ctx => ctx.Using.Vowel)
)
})
.SelectExpression(ctx => ctx.Variables.Groups, shape => shape
.Computeds((ctx, group) => new
{
StartsWithVowel = group.Key,
Count = EdgeQL.Count(group.Elements),
NameLength = EdgeQL.Len(ctx.Ref(group.Elements).Name!)
})
)
.Compile(true);

// grouping by more than 1 parameter
query = QueryBuilder
.With(ctx => new
{
People = ctx.SubQuerySingle(QueryBuilder.Select<Person>()),
Groups = ctx.SubQuerySingle(
QueryBuilder
.Group(ctx => ctx.Local<Person>("People"))
.Using(person => new
{
Vowel = Regex.IsMatch(person.Name!, "(?i)^[aeiou]"),
NameLength = person.Name!.Length
})
.By(ctx => Tuple.Create(ctx.Using.Vowel, ctx.Using.NameLength))
)
})
.SelectExpression(ctx => ctx.Variables.Groups, shape => shape
.Computeds((ctx, group) => new
{
StartsWithVowel = group.Key,
Count = EdgeQL.Count(group.Elements),
NameLength = EdgeQL.Len(ctx.Ref(group.Elements).Name!)
})
)
.Compile(true);

// grouping by grouping sets
query = QueryBuilder
.With(ctx => new
{
People = ctx.SubQuerySingle(QueryBuilder.Select<Person>()),
Groups = ctx.SubQuerySingle(
QueryBuilder
.Group(ctx => ctx.Local<Person>("People"))
.Using(person => new
{
Vowel = Regex.IsMatch(person.Name!, "(?i)^[aeiou]"),
NameLength = person.Name!.Length
})
.By(ctx => EdgeQL.Cube(new { ctx.Using.Vowel, ctx.Using.NameLength }))
)
})
.SelectExpression(ctx => ctx.Variables.Groups, shape => shape
.Computeds((ctx, group) => new
{
group.Key,
group.Grouping,
Count = EdgeQL.Count(group.Elements),
NameLength = EdgeQL.Len(ctx.Ref(group.Elements).Name!)
})
)
.OrderBy(x => EdgeQL.ArrayAgg(x.Grouping))
.Compile(true);
}
}
}
73 changes: 40 additions & 33 deletions src/EdgeDB.Net.Driver/Models/DataTypes/Group.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using EdgeDB.Binary;
using EdgeDB.DataTypes;
using System.Collections;
using System.Collections.Immutable;
Expand All @@ -12,6 +13,30 @@ namespace EdgeDB;
/// <typeparam name="TElement">The type of the elements.</typeparam>
public sealed class Group<TKey, TElement> : IGrouping<TKey, TElement>
{
static Group()
{
// precache the deserializer, in case theres issues with it, this will throw before the query is run.
if(TypeBuilder.IsValidObjectType(typeof(TElement)))
TypeBuilder.GetDeserializationFactory(typeof(TElement));
}
/// <summary>
/// Gets the key used to group the set of <see cref="Elements" />.
/// </summary>
[EdgeDBIgnore]
public TKey Key { get; }

/// <summary>
/// Gets a collection of all the names of the parameters used as the key for this particular subset.
/// </summary>
[EdgeDBProperty("grouping")]
public IReadOnlyCollection<string> Grouping { get; }

/// <summary>
/// Gets a collection of elements that have the same key as <see cref="Key" />.
/// </summary>
[EdgeDBProperty("elements")]
public IReadOnlyCollection<TElement> Elements { get; }

/// <summary>
/// Constructs a new grouping.
/// </summary>
Expand All @@ -34,25 +59,11 @@ internal Group(IDictionary<string, object?> raw)

Grouping = ((string[])groupingValue!).ToImmutableArray();
Key = BuildKey((IDictionary<string, object?>)keyValue!);
throw new NotImplementedException("TODO");
//Elements = ((IDictionary<string, object?>[])elementsValue!).Select(x => (TElement)TypeBuilder.BuildObject(typeof(TElement), x)!).ToImmutableArray();
Elements = elementsValue is null
? Array.Empty<TElement>()
: ((object?[])elementsValue).Cast<TElement>().ToImmutableArray();
}

/// <summary>
/// Gets the name of the property that was grouped by.
/// </summary>
public IReadOnlyCollection<string> Grouping { get; }

/// <summary>
/// Gets a collection of elements that have the same key as <see cref="Key" />.
/// </summary>
public IReadOnlyCollection<TElement> Elements { get; }

/// <summary>
/// Gets the key used to group the set of <see cref="Elements" />.
/// </summary>
public TKey Key { get; }

/// <inheritdoc />
public IEnumerator<TElement> GetEnumerator()
=> Elements.GetEnumerator();
Expand All @@ -61,26 +72,22 @@ public IEnumerator<TElement> GetEnumerator()
IEnumerator IEnumerable.GetEnumerator()
=> Elements.GetEnumerator();

private static TKey BuildKey(IDictionary<string, object?> value)
private static TKey BuildKey(object? value)
{
if (typeof(TKey).IsAssignableTo(typeof(ITuple)))
switch (value)
{
var types = typeof(TKey).GenericTypeArguments;
var transientTuple = new TransientTuple(types, value.Values.ToArray());

switch (typeof(TKey).Name.Split('`')[0])
case TKey key:
return key;
case IDictionary<string, object?> {Count: 2} dict when dict.ContainsKey("id"):
{
case "ValueTuple":
return (TKey)transientTuple.ToValueTuple();
case "Tuple":
return (TKey)transientTuple.ToReferenceTuple();
default:
throw new InvalidOperationException($"Cannot build tuple with the type of {typeof(TKey).Name}");
var keyRaw = dict.FirstOrDefault(x => x.Key != "id");
if (keyRaw.Value is TKey keyValue)
return keyValue;

throw new InvalidOperationException($"Key of grouping cannot be extracted from {keyRaw.Value?.GetType()}");
}
default:
throw new InvalidOperationException($"Cannot build key with the type of {typeof(TKey).Name}");
}

if (value.Count == 1)
return (TKey)value.First().Value!;
throw new InvalidOperationException($"Cannot build key with the type of {typeof(TKey).Name}");
}
}
62 changes: 0 additions & 62 deletions src/EdgeDB.Net.QueryBuilder/Builders/GroupBuilder.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/EdgeDB.Net.QueryBuilder/Builders/ShapeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public ShapeBuilder<T> Computeds<TAnon>(Expression<Func<T, TAnon>> computedsSele
return this;
}

public ShapeBuilder<T> Computeds<TAnon>(Expression<Func<QueryContext<T>, T, TAnon>> computedsSelector)
public ShapeBuilder<T> Computeds<TAnon>(Expression<Func<QueryContextSelf<T>, T, TAnon>> computedsSelector)
{
var computeds = FlattenAnonymousExpression(SelectedType, computedsSelector);

Expand Down
9 changes: 4 additions & 5 deletions src/EdgeDB.Net.QueryBuilder/Compiled/DebugCompiledQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,17 @@ private static string CreateDebugText(string query, Dictionary<string, object?>
desc += $": {column.Marker.DebugText.Get()}";


if (desc.Length - 3 > size)
if (desc.Length > size)
{
desc = $"{icon} [{column.Marker.Type}] {column.Name}";
}

if (desc.Length - 3 > size)
if (desc.Length > size)
{
desc = $"{icon} {column.Name}";
}

if (desc.Length - 3 >= size)
if (desc.Length >= size)
{
desc = icon;
}
Expand Down Expand Up @@ -178,8 +178,7 @@ private static string CreateDebugText(string query, Dictionary<string, object?>

private static List<List<QuerySpan>> CreateMarkerView(LinkedList<QuerySpan> spans)
{
var orderedTemp = spans.OrderBy(x => x.Range.End.Value - x.Range.Start.Value).ToList();
var ordered = new Queue<QuerySpan>(orderedTemp); // order by 'size'
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>();

Expand Down
6 changes: 6 additions & 0 deletions src/EdgeDB.Net.QueryBuilder/EdgeQL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ namespace EdgeDB
{
public sealed partial class EdgeQL
{
public static T Rollup<T>(T value)
=> default!;

public static T Cube<T>(T value)
=> default!;

public static JsonReferenceVariable<T> AsJson<T>(T value) => new(value);

public static long Count<TType>(IMultiCardinalityExecutable<TType> a) { return default!; }
Expand Down
26 changes: 26 additions & 0 deletions src/EdgeDB.Net.QueryBuilder/Extensions/GroupingExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using EdgeDB.Interfaces.Queries;
using System.Linq.Expressions;

namespace EdgeDB;

public static class QueryBuilderGroupingExtensions
{
// public static IGroupUsingQuery<TType, QueryContextSelfUsing<TType, TUsing>> Using<TType, TUsing>(
// this IGroupQuery<TType, QueryContextSelf<TType>> query, Expression<Func<TType, TUsing>> expression)
// {
// return query.UsingInternal<TUsing, QueryContextSelfUsing<TType, TUsing>>(expression);
// }
//
// public static IGroupUsingQuery<TType, GroupContext<TType, TUsing, TVars>> Using<TType, TUsing, TVars>(
// this IGroupQuery<TType, QueryContext<TType, TVars>> query, Expression<Func<TType, TUsing>> expression)
// {
// return query.UsingInternal<TUsing, GroupContext<TType, TUsing, TVars>>(expression);
// }

// public static IGroupUsingQuery<TType, GroupContext<TType, TUsing, TVars>> Using<TType, TUsing, TVars, TOldContext>(
// this IGroupQuery<TType, TOldContext> query, Expression<Func<TType, TUsing>> expression
// ) where TOldContext : QueryContext<TType, TVars>
// {
//
// }
}
6 changes: 4 additions & 2 deletions src/EdgeDB.Net.QueryBuilder/Grammar/Terms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ public static QueryWriter LabelVerbose(this QueryWriter writer, string name, Def
=> writer.Marker(MarkerType.Verbose, name, debug, values);
#endregion

public static QueryWriter Wrapped(this QueryWriter writer, Value value, string separator = "()")
public static QueryWriter Wrapped(this QueryWriter writer, Value value, string separator = "()", bool spaced = false)
{
if (separator.Length != 2)
throw new ArgumentOutOfRangeException(nameof(separator));

return writer.Append(separator[0], value, separator[1]);
return spaced ?
writer.Append(separator[0], ' ', value, ' ', separator[1]) :
writer.Append(separator[0], value, separator[1]);
}

public static QueryWriter Wrapped(this QueryWriter writer, WriterProxy value, string separator = "()")
Expand Down
7 changes: 7 additions & 0 deletions src/EdgeDB.Net.QueryBuilder/GroupContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace EdgeDB;

public abstract class GroupContext<TUsing, TContext> : IQueryContextUsing<TUsing> where TContext : IQueryContext
{
public abstract TUsing Using { get; }
public abstract TContext Context { get; }
}
18 changes: 0 additions & 18 deletions src/EdgeDB.Net.QueryBuilder/Interfaces/IGroupable.cs

This file was deleted.

Loading

0 comments on commit c1eb806

Please sign in to comment.