Skip to content
This repository was archived by the owner on Oct 6, 2022. It is now read-only.

Commit 7f63666

Browse files
committed
FOR operator work
1 parent 815ad50 commit 7f63666

8 files changed

Lines changed: 257 additions & 44 deletions

File tree

examples/EdgeDB.Examples.ExampleApp/Examples/QueryBuilder.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,30 @@ public async Task ExecuteAsync(EdgeDBClient client)
3131

3232
private static async Task QueryBuilderDemo(EdgeDBClient client)
3333
{
34+
var set = new List<LinkPerson>()
35+
{
36+
new LinkPerson
37+
{
38+
Email = "email1@example.com",
39+
Name = "test1",
40+
},
41+
new LinkPerson
42+
{
43+
Email = "email2@example.com",
44+
Name = "test2",
45+
},
46+
new LinkPerson
47+
{
48+
Email = "email3@example.com",
49+
Name = "test3",
50+
},
51+
};
52+
53+
var test = new QueryBuilder<LinkPerson>()
54+
.For(set, x =>
55+
QueryBuilder.Insert(x, false)
56+
).Build().Prettify();
57+
3458
// Selecting a type with autogen shape
3559
var query = QueryBuilder.Select<LinkPerson>().Build().Prettify();
3660

src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
</None>
3232
</ItemGroup>
3333

34+
<ItemGroup>
35+
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
36+
</ItemGroup>
37+
3438
<ItemGroup>
3539
<ProjectReference Include="..\EdgeDB.Net.Driver\EdgeDB.Net.Driver.csproj" />
3640
</ItemGroup>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace EdgeDB
8+
{
9+
internal static class QueryBuilderExtensions
10+
{
11+
public static BuiltQuery BuildWithoutAutogeneratedNodes(this IQueryBuilder builder, NodeBuilder nodeBuilder)
12+
{
13+
// remove addon & autogen nodes.
14+
var userNodes = builder.Nodes.Where(x => !builder.Nodes.Any(y => y.SubNodes.Contains(x)) || !x.IsAutoGenerated);
15+
16+
// TODO: better checks for this, future should add a callback to add the
17+
// node with its context so any parent builder can change contexts for nodes
18+
foreach (var node in userNodes)
19+
node.Context.SetAsGlobal = false;
20+
21+
foreach (var variable in builder.Variables)
22+
{
23+
nodeBuilder.QueryVariables[variable.Key] = variable.Value;
24+
}
25+
26+
var newBuilder = new QueryBuilder<object?>(userNodes.ToList(), builder.Globals.ToList(), builder.Variables.ToDictionary(x => x.Key, x => x.Value));
27+
28+
return newBuilder.BuildWithGlobals();
29+
}
30+
}
31+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Newtonsoft.Json.Linq;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace EdgeDB
9+
{
10+
internal interface IJsonVariable
11+
{
12+
string Name { get; }
13+
14+
Type InnerType { get; }
15+
16+
bool HasProperty(string name);
17+
}
18+
19+
public class JsonVariable<T> : IJsonVariable
20+
{
21+
public string Name { get; }
22+
23+
public T Value
24+
=> throw new InvalidOperationException("Value cannot be accessed outside of an expression.");
25+
26+
internal bool IsObject
27+
=> _jToken is JObject;
28+
29+
private readonly JToken _jToken;
30+
31+
public JsonVariable(string name, JArray arr)
32+
{
33+
Name = name;
34+
// get first element or empty jobject
35+
if (arr.Count > 0)
36+
_jToken = arr[0];
37+
else
38+
_jToken = new JObject();
39+
}
40+
41+
internal bool HasProperty(string name)
42+
{
43+
if (_jToken is not JObject obj)
44+
return false;
45+
46+
return obj.Property(name) is not null;
47+
}
48+
49+
string IJsonVariable.Name => Name;
50+
Type IJsonVariable.InnerType => typeof(T);
51+
bool IJsonVariable.HasProperty(string name) => HasProperty(name);
52+
}
53+
}

src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,29 @@ internal BuiltQuery BuildWithGlobals()
178178
=> InternalBuild(false);
179179

180180
#region Root nodes
181+
public QueryBuilder<TType> For(IEnumerable<TType> set, Expression<Func<JsonVariable<TType>, object?>> iterator)
182+
{
183+
AddNode<ForNode>(new ForContext(typeof(TType))
184+
{
185+
Expression = iterator,
186+
Set = set
187+
});
188+
189+
return this;
190+
}
191+
192+
// for the simplified expression return type check
193+
public QueryBuilder<TType> For(IEnumerable<TType> set, Expression<Func<JsonVariable<TType>, IQueryBuilder>> iterator)
194+
{
195+
AddNode<ForNode>(new ForContext(typeof(TType))
196+
{
197+
Expression = iterator,
198+
Set = set
199+
});
200+
201+
return this;
202+
}
203+
181204
/// <inheritdoc cref="IQueryBuilder{TType}.With(string, object?)"/>
182205
public QueryBuilder<TType> With(string name, object? value)
183206
{
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Linq.Expressions;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace EdgeDB.QueryNodes
10+
{
11+
internal class ForContext : NodeContext
12+
{
13+
public LambdaExpression? Expression { get; init; }
14+
public IEnumerable? Set { get; init; }
15+
16+
public ForContext(Type currentType) : base(currentType)
17+
{
18+
}
19+
}
20+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace EdgeDB.QueryNodes
10+
{
11+
internal class ForNode : QueryNode<ForContext>
12+
{
13+
public ForNode(NodeBuilder builder) : base(builder)
14+
{
15+
}
16+
17+
private string ParseExpression(string name, string json)
18+
{
19+
// check if we're returning a query builder
20+
if (Context.Expression!.ReturnType == typeof(IQueryBuilder))
21+
{
22+
// construct the lambda and call it
23+
var jArray = JArray.Parse(json);
24+
25+
var parameters = Context.Expression.Parameters.Select(x =>
26+
{
27+
return x switch
28+
{
29+
_ when x.Type == typeof(QueryContext) => new QueryContext(),
30+
_ when ReflectionUtils.IsInstanceOfGenericType(typeof(JsonVariable<>), x.Type)
31+
=> Activator.CreateInstance(typeof(JsonVariable<>).MakeGenericType(Context.CurrentType), name, jArray),
32+
_ => throw new ArgumentException($"Cannot use {x.Type} as a parameter to a for expression")
33+
};
34+
}).ToArray();
35+
36+
var builder = (IQueryBuilder)Context.Expression!.Compile().DynamicInvoke(parameters)!;
37+
38+
return builder.BuildWithoutAutogeneratedNodes(Builder).Query;
39+
}
40+
else
41+
return ExpressionTranslator.Translate(Context.Expression!, Builder.QueryVariables, Context, Builder.QueryGlobals);
42+
}
43+
44+
public override void Visit()
45+
{
46+
var name = Context.Expression!.Parameters.First(x => x.Type != typeof(QueryContext)).Name!;
47+
48+
var setJson = JsonConvert.SerializeObject(Context.Set);
49+
var jsonName = QueryUtils.GenerateRandomVariableName();
50+
51+
SetVariable(jsonName, setJson);
52+
53+
Query.Append($"for {name} in <json>${jsonName} union ({ParseExpression(name, setJson)})");
54+
}
55+
}
56+
}

src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using EdgeDB.Serializer;
22
using System;
33
using System.Collections.Generic;
4+
using System.Diagnostics.CodeAnalysis;
45
using System.Linq;
56
using System.Linq.Expressions;
67
using System.Reflection;
@@ -22,13 +23,24 @@ private string BuildInsertLambdaShape(LambdaExpression expression)
2223
{
2324
return $"{{ {ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}";
2425
}
25-
26-
private string BuildInsertShape(Type? shapeType = null, object? shapeValue = null)
26+
27+
private string GetPropertyValue(object? inst, string? path, string type, PropertyInfo info)
28+
{
29+
if (inst is IJsonVariable json && json.HasProperty(info.Name))
30+
return $"<{type}>{json.Name}{path}['{info.Name}']";
31+
32+
var varName = QueryUtils.GenerateRandomVariableName();
33+
SetVariable(varName, info.GetValue(inst));
34+
return $"<{type}>${varName}";
35+
}
36+
37+
private List<Type> _subInserts = new();
38+
private string BuildInsertShape(Type? shapeType = null, object? shapeValue = null, string? path = null)
2739
{
2840
List<string> shape = new();
2941

30-
var type = shapeType ?? Context.CurrentType;
3142
var value = shapeValue ?? Context.Value;
43+
var type = shapeType ?? (value is IJsonVariable var ? var.InnerType : Context.CurrentType);
3244

3345
if (value is LambdaExpression expression)
3446
return BuildInsertLambdaShape(expression);
@@ -43,45 +55,50 @@ private string BuildInsertShape(Type? shapeType = null, object? shapeValue = nul
4355

4456
if(edgeqlType != null)
4557
{
46-
var varName = QueryUtils.GenerateRandomVariableName();
47-
SetVariable(varName, property.GetValue(Context.Value));
48-
shape.Add($"{propertyName} := <{edgeqlType}>${varName}");
58+
shape.Add(GetPropertyValue(value, path, edgeqlType, property));
4959
continue;
5060
}
5161

52-
// TODO: sub queries!
5362
// might be a link?
5463
if (TypeBuilder.IsValidObjectType(property.PropertyType))
5564
{
56-
// is it a object we've seen before?
57-
var subValue = property.GetValue(value);
58-
if(QueryObjectManager.TryGetObjectId(subValue, out var id))
65+
if(value is IJsonVariable jsonVariable)
5966
{
60-
// insert a sub query
61-
var globalName = GetOrAddGlobal(subValue, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = <uuid>\"{id}\")"));
62-
shape.Add($"{propertyName} := {globalName}");
63-
continue;
67+
if (_subInserts.Any(x => jsonVariable.InnerType == x))
68+
throw new InvalidOperationException("cannot preform sub type inserts of the same type within a json imported union");
6469
}
6570
else
6671
{
67-
if (subValue is null)
68-
shape.Add($"{propertyName} := {{}}");
72+
// is it a object we've seen before?
73+
var subValue = property.GetValue(value);
74+
if (QueryObjectManager.TryGetObjectId(subValue, out var id))
75+
{
76+
// insert a sub query
77+
var globalName = GetOrAddGlobal(subValue, new SubQuery($"(select {property.PropertyType.GetEdgeDBTypeName()} filter .id = <uuid>\"{id}\")"));
78+
shape.Add($"{propertyName} := {globalName}");
79+
continue;
80+
}
6981
else
7082
{
71-
RequiresIntrospection = true;
72-
var globalName = GetOrAddGlobal(subValue, new SubQuery((info) =>
83+
if (subValue is null)
84+
shape.Add($"{propertyName} := {{}}");
85+
else
7386
{
74-
var name = property.PropertyType.GetEdgeDBTypeName();
75-
var exclusiveProps = QueryUtils.GetProperties(info, property.PropertyType, true);
76-
var exclusiveCondition = exclusiveProps.Any() ?
77-
$" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {name})"
78-
: string.Empty;
79-
return $"(insert {name} {BuildInsertShape(property.PropertyType, subValue)}{exclusiveCondition})";
80-
}));
81-
shape.Add($"{propertyName} := {globalName}");
87+
RequiresIntrospection = true;
88+
var globalName = GetOrAddGlobal(subValue, new SubQuery((info) =>
89+
{
90+
var name = property.PropertyType.GetEdgeDBTypeName();
91+
var exclusiveProps = QueryUtils.GetProperties(info, property.PropertyType, true);
92+
var exclusiveCondition = exclusiveProps.Any() ?
93+
$" unless conflict on {(exclusiveProps.Count() > 1 ? $"({string.Join(", ", exclusiveProps.Select(x => $".{x.GetEdgeDBPropertyName()}"))})" : $".{exclusiveProps.First().GetEdgeDBPropertyName()}")} else (select {name})"
94+
: string.Empty;
95+
return $"(insert {name} {BuildInsertShape(property.PropertyType, subValue, path is null ? property.Name : path += $".{property.Name}")}{exclusiveCondition})";
96+
}));
97+
shape.Add($"{propertyName} := {globalName}");
98+
}
99+
100+
continue;
82101
}
83-
84-
continue;
85102
}
86103
}
87104

@@ -146,22 +163,7 @@ public void ElseDefault()
146163

147164
public void Else(IQueryBuilder builder)
148165
{
149-
// remove addon & autogen nodes.
150-
var userNodes = builder.Nodes.Where(x => !builder.Nodes.Any(y => y.SubNodes.Contains(x)) || !x.IsAutoGenerated);
151-
152-
// TODO: better checks for this, future should add a callback to add the
153-
// node with its context so any parent builder can change contexts for nodes
154-
foreach (var node in userNodes)
155-
node.Context.SetAsGlobal = false;
156-
157-
foreach(var variable in builder.Variables)
158-
{
159-
Builder.QueryVariables[variable.Key] = variable.Value;
160-
}
161-
162-
var newBuilder = new QueryBuilder<object?>(userNodes.ToList(), builder.Globals.ToList(), builder.Variables.ToDictionary(x => x.Key, x=> x.Value));
163-
164-
var result = newBuilder.BuildWithGlobals();
166+
var result = builder.BuildWithoutAutogeneratedNodes(Builder);
165167
_children.Append($" else ({result.Query})");
166168
}
167169
}

0 commit comments

Comments
 (0)