diff --git a/.github/workflows/deploy-prerelease.yml b/.github/workflows/deploy-prerelease.yml index 6bc7d2a8..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 }}" @@ -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 diff --git a/EdgeDB.Net.sln b/EdgeDB.Net.sln index 5e490f2b..379b5f04 100644 --- a/EdgeDB.Net.sln +++ b/EdgeDB.Net.sln @@ -35,6 +35,10 @@ 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("{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 Debug|Any CPU = Debug|Any CPU @@ -85,6 +89,14 @@ 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 + {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 @@ -101,6 +113,8 @@ 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} + {48C73144-9A14-442D-BFCE-C010CC017972} = {67ED9EF0-7828-44C0-8CB0-DEBD69EC94CA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4E90C94F-D693-4411-82F3-2051DE1BE052} diff --git a/dbschema/default.esdl b/dbschema/default.esdl index e440bc13..69bf5962 100644 --- a/dbschema/default.esdl +++ b/dbschema/default.esdl @@ -15,6 +15,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; @@ -39,4 +46,36 @@ 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; + } + + type MultiLinkPerson { + required property name -> str; + required property email -> str { + constraint exclusive; + } + 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/00001.edgeql b/dbschema/migrations/00001.edgeql index 5865ffc4..0ab4501c 100644 --- a/dbschema/migrations/00001.edgeql +++ b/dbschema/migrations/00001.edgeql @@ -5,4 +5,4 @@ CREATE MIGRATION m1gajj2cjvikxjfhwnczhcfrcdlycg5pbg7vvehuaohazd2ltajlnq 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 2f42c09a..b2128644 100644 --- a/dbschema/migrations/00002.edgeql +++ b/dbschema/migrations/00002.edgeql @@ -6,4 +6,4 @@ CREATE MIGRATION m1vqxxs4ie4so3x35nk2wyu3jy3hmolscpyxu6bccucaofjtnrtj3a CREATE CONSTRAINT std::exclusive; }; }; -}; +}; \ No newline at end of file diff --git a/dbschema/migrations/00003.edgeql b/dbschema/migrations/00003.edgeql index 0d296276..ad33efcd 100644 --- a/dbschema/migrations/00003.edgeql +++ b/dbschema/migrations/00003.edgeql @@ -17,4 +17,4 @@ CREATE MIGRATION m1tpouzupcrd2nkhl45qsdykw3dzjbk4ecndr73tmatxskaxkixu3q SET REQUIRED USING ('e'); }; }; -}; +}; \ No newline at end of file diff --git a/dbschema/migrations/00004.edgeql b/dbschema/migrations/00004.edgeql index 043ec31a..d335f7fe 100644 --- a/dbschema/migrations/00004.edgeql +++ b/dbschema/migrations/00004.edgeql @@ -6,4 +6,4 @@ CREATE MIGRATION m1xp6ukrooc4chjfnhmaky4ywsoip5wvbz4ffm36tsqosspiyqjy6q CREATE CONSTRAINT std::exclusive; }; }; -}; +}; \ No newline at end of file diff --git a/dbschema/migrations/00005.edgeql b/dbschema/migrations/00005.edgeql index ee5b2c04..a1d75de9 100644 --- a/dbschema/migrations/00005.edgeql +++ b/dbschema/migrations/00005.edgeql @@ -8,4 +8,4 @@ CREATE MIGRATION m1rgfzvgm77nvwkjt5i4hw3ruxtxoed4osu2hv7uj6huagohxxuu2q 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 index 8296f5b3..f63b397b 100644 --- a/dbschema/migrations/00006.edgeql +++ b/dbschema/migrations/00006.edgeql @@ -6,4 +6,4 @@ CREATE MIGRATION m1bthirxb7a7lq7mjtrdjsxdcjcy4or3tlprzh74azy44h7n3re6zq SET default := (std::datetime_current()); }; }; -}; +}; \ No newline at end of file diff --git a/dbschema/migrations/00007.edgeql b/dbschema/migrations/00007.edgeql index d75eb2bf..04883369 100644 --- a/dbschema/migrations/00007.edgeql +++ b/dbschema/migrations/00007.edgeql @@ -10,4 +10,4 @@ CREATE MIGRATION m1dntdma2rrziv35tbt7mfl7qeg3wpzaqk73edwaw5i4kkz5fg6z6a 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 index 92be5cd4..833a6a82 100644 --- a/dbschema/migrations/00008.edgeql +++ b/dbschema/migrations/00008.edgeql @@ -6,4 +6,4 @@ CREATE MIGRATION m1isiclyxqa32luj6hdazr4mft2mvvq4tmmuoygl7m7k2iimxm5y3a 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/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/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 := (.? 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); + await QueryableCollectionDemo(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%")) + .OrderByDesending(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(); + + // 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 + { + 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(); + + // 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().Pretty; + + // 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(); + + // 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", + } + } + }; + + var tquery = (await QueryBuilder.For(data, + x => QueryBuilder.Insert(x) + ).BuildAsync(client)); + + // Else statements (upsert demo) + query = (await QueryBuilder + .Insert(person) + .UnlessConflict() + .Else(q => + q.Update(old => new LinkPerson + { + Name = old!.Name!.ToLower() + }) + ) + .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(); + + // 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.Driver/ClientPacketDuplexer.cs b/src/EdgeDB.Net.Driver/ClientPacketDuplexer.cs index e946ae0a..9a504f8f 100644 --- a/src/EdgeDB.Net.Driver/ClientPacketDuplexer.cs +++ b/src/EdgeDB.Net.Driver/ClientPacketDuplexer.cs @@ -276,7 +276,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/Clients/EdgeDBBinaryClient.cs b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs index 08526489..f93b8e29 100644 --- a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs @@ -228,12 +228,12 @@ bool parseHandlerPredicate(IReceiveable? packet) { Capabilities = capabilities, Query = query, + ImplicitLimit = _config.ImplicitLimit, Format = format, ExpectedCardinality = cardinality ?? Cardinality.Many, 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, token: token).ConfigureAwait(false); @@ -249,6 +249,7 @@ bool parseHandlerPredicate(IReceiveable? packet) throw new MissingCodecException($"Cannot encode arguments, {inCodecInfo.Codec} is not a registered argument codec"); List receivedData = new(); + CommandDataDescription d = default!; bool handler(IReceiveable msg) { @@ -257,6 +258,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: @@ -270,6 +274,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, @@ -279,7 +284,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/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.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/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/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/Attributes/EdgeDBTypeAttribute.cs b/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs index 6767cc0d..e4db7811 100644 --- a/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs +++ b/src/EdgeDB.Net.Driver/Serializer/Attributes/EdgeDBTypeAttribute.cs @@ -3,9 +3,13 @@ /// /// 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 { + /// + /// 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/Serializer/PacketWriter.cs b/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs index 28d19070..d4d6302c 100644 --- a/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs +++ b/src/EdgeDB.Net.Driver/Serializer/PacketWriter.cs @@ -38,29 +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) - { - Write(header.Code); - WriteArray(header.Value); - } - } - } - - public void Write(IEnumerable? headers) - { - // write length - Write((ushort)(headers?.Count() ?? 0)); - - if (headers is not null) - { - foreach (var header in headers) + foreach (var header in annotations) { Write(header.Name); Write(header.Value); 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.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/ObjectBuilder.cs index dd0dc913..af821d38 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) { @@ -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) { @@ -119,17 +126,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 f17345a6..446f68f8 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs @@ -6,6 +6,7 @@ namespace EdgeDB.Serializer { + internal delegate void OnObjectCreated(object obj, Guid id); /// /// Represents the class used to build types from edgedb query results. /// @@ -21,10 +22,12 @@ 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(); @@ -126,15 +129,15 @@ internal static bool IsValidObjectType(Type type) type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, 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.IsInterface || type.GetCustomAttribute() != null || (type.IsClass || type.IsValueType) && !type.IsSealed && validConstructor; } internal static bool TryGetCollectionParser(Type type, out Func? builder) { builder = null; - if (ReflectionUtils.IsSubclassOfRawGeneric(typeof(List<>), type)) + if (ReflectionUtils.IsSubTypeOfGenericType(typeof(List<>), type)) builder = CreateDynamicList; return builder != null; @@ -182,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) @@ -256,6 +259,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); @@ -288,8 +296,8 @@ public TypeDeserializeInfo(Type type, TypeDeserializerFactory factory) } public void AddOrUpdateChildren(TypeDeserializeInfo child) - => Children[child._type] = child; - + => Children[child._type] = child; + public void AddOrUpdateChildren(IEnumerable children) { foreach (var child in children) @@ -425,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; @@ -434,7 +453,12 @@ private TypeDeserializerFactory CreateDefaultFactory() } public object Deserialize(IDictionary args) - => _factory(args); + { + var result = _factory(args); + if (args.TryGetValue("id", out var rawGuid)) + TypeBuilder.DispatchObjectCreate(result, (Guid)rawGuid!); + return result; + } public static implicit operator TypeDeserializerFactory(TypeDeserializeInfo info) => info._factory; } diff --git a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs index 708500aa..dcb9265d 100644 --- a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs +++ b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs @@ -11,16 +11,28 @@ namespace EdgeDB { internal class ReflectionUtils { - public static bool IsSubclassOfRawGeneric(Type generic, Type? toCheck) + public static object? GetValueTypeDefault(Type type) { - while (toCheck is not null && toCheck != typeof(object)) + if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { - var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; - if (generic == cur) + 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; + while (type != null) + { + if (type.IsGenericType && + type.GetGenericTypeDefinition() == genericType) { return true; } - toCheck = toCheck.BaseType; + type = type.BaseType; } return false; } diff --git a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs index a8ed8186..d39321a3 100644 --- a/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs +++ b/src/EdgeDB.Net.QueryBuilder/AssemblyInfo.cs @@ -1,3 +1,11 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("EdgeDB.Driver")] +[assembly: InternalsVisibleTo("EdgeDB.Examples.ExampleApp")] +[assembly: InternalsVisibleTo("EdgeDB.QueryBuilder.StandardLibGenerator")] +[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/EdgeDB.Net.QueryBuilder.csproj b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj index 5a2fb385..bd5db833 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj +++ b/src/EdgeDB.Net.QueryBuilder/EdgeDB.Net.QueryBuilder.csproj @@ -1,38 +1,25 @@  - + - 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 - \ - + - - + - diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs b/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs new file mode 100644 index 00000000..371ecd18 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/EdgeDBObject.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + /// + /// 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) + { + Id = (Guid)data["id"]!; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs b/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs index 74e7be15..e16f429a 100644 --- a/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs +++ b/src/EdgeDB.Net.QueryBuilder/EdgeQL.cs @@ -9,13 +9,43 @@ 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(); - } + public static JsonReferenceVariable AsJson(T value) + => new JsonReferenceVariable(value); + + [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 385fda3e..9799f566 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() @@ -1689,29 +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 - - #region variables - - #endregion variables internal static Dictionary FunctionOperators = new() { 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/EdgeDBExtensions.cs b/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs new file mode 100644 index 00000000..a62605ed --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/EdgeDBExtensions.cs @@ -0,0 +1,21 @@ +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); + } + + 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/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/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 new file mode 100644 index 00000000..17abb40c --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Extensions/TypeExtensions.cs @@ -0,0 +1,60 @@ +using EdgeDB.Serializer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +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 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) + { + var attr = type.GetCustomAttribute(); + var name = attr?.Name ?? type.Name; + return attr != null ? $"{(attr.ModuleName != null ? $"{attr.ModuleName}::" : "default::")}{name}" : name; + } + public static string GetEdgeDBPropertyName(this MemberInfo info) + { + var att = info.GetCustomAttribute(); + + return $"{((att?.IsLinkProperty ?? false) ? "@" : "")}{att?.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/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/IMultiCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs new file mode 100644 index 00000000..f399e9fb --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityExecutable.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces +{ + /// + /// Represents an executable query with one or more returning objects. + /// + /// The object the query will return. + public interface IMultiCardinalityExecutable : IQueryBuilder, IMultiCardinalityQuery + { + /// + /// 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, Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs new file mode 100644 index 00000000..e15c468c --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IMultiCardinalityQuery.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces +{ + /// + /// 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 new file mode 100644 index 00000000..d7437308 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQuery.cs @@ -0,0 +1,14 @@ +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/IQueryBuilder.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs new file mode 100644 index 00000000..4ffc4ef8 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/IQueryBuilder.cs @@ -0,0 +1,219 @@ +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. + /// The type of context representing the current builder. + public interface IQueryBuilder : + IQueryBuilder, + 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 WITH statement whos variables are the properties defined in . + /// + /// 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. + /// + IQueryBuilder> With(TVariables variables); + + /// + /// Adds a SELECT statement selecting the current with an 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 . + /// + /// + /// 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. + /// + /// A . + IInsertQuery Insert(TType value, bool returnInsertedValue); + + /// + /// 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); + + /// + /// 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. + /// + /// A . + IInsertQuery Insert(Expression> value, bool returnInsertedValue); + + /// + /// 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); + + /// + /// 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); + + /// + /// 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 . + /// + 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/Interfaces/ISingleCardinalityExecutable.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs new file mode 100644 index 00000000..8aff28a1 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityExecutable.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces +{ + /// + /// Represents an executable query with at most one returning objects. + /// + /// The object the query will return. + public interface ISingleCardinalityExecutable : IQueryBuilder, ISingleCardinalityQuery + { + /// + /// 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, Capabilities? capabilities = Capabilities.Modifications, CancellationToken token = default); + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs new file mode 100644 index 00000000..4959487d --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/ISingleCardinalityQuery.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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/Interfaces/Queries/IDeleteQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs new file mode 100644 index 00000000..a827ed93 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IDeleteQuery.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces.Queries +{ + /// + /// Represents a generic DELETE query used within a . + /// + /// The type which this DELETE query is querying against. + /// 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); + + /// + /// 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/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/IInsertQuery.cs b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.cs new file mode 100644 index 00000000..14ae898b --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IInsertQuery.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.Interfaces.Queries +{ + /// + /// Represents a generic INSERT query used within a . + /// + /// The type which this INSERT query is querying against. + /// The type of context representing the current builder. + 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 new file mode 100644 index 00000000..b0e683c1 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/ISelectQuery.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces.Queries +{ + /// + /// Represents a generic SELECT query used within a . + /// + /// The type which this SELECT query is querying against. + /// 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); + + /// + /// 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 new file mode 100644 index 00000000..1c2274b6 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/IUpdateQuery.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces.Queries +{ + /// + /// Represents a generic UPDATE query used within a . + /// + /// The type which this UPDATE query is querying against. + /// The type of context representing the current builder. + public interface IUpdateQuery : IGroupable, 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 new file mode 100644 index 00000000..d972a731 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Interfaces/Queries/InsertChildren/IUnlessConflictOn.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Interfaces +{ + /// + /// Represents a generic UNLESS CONFLICT ON query used within a . + /// + /// The type which this UNLESS CONFLICT ON query is querying against. + /// 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. + /// + /// 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/JsonVariable.cs b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs new file mode 100644 index 00000000..00f2df77 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/JsonVariable.cs @@ -0,0 +1,208 @@ +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 variable name representing this json value. + /// + string VariableName { 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); + } + + /// + /// 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 JsonCollectionVariable : 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); + + /// + /// Gets the variable name of the current json variable. + /// + internal string VariableName { get; } + + /// + /// The root containing all the json objects. + /// + private readonly JArray _array; + + /// + /// 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 JsonCollectionVariable(string name, string varName, JArray array) + { + _array = array; + VariableName = varName; + 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 => + { + switch(x.Value) + { + case JObject jObject: + return CalculateNodeDepth(jObject, depth + 1); + 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; + } + }); + } + + string IJsonVariable.Name => Name; + 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/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..9dcd7343 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 string EdgeQLOperator => "+= {1}"; + public ExpressionType? Expression => null; + 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 1c81b64f..2528aa49 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 string EdgeQLOperator => "-= {1}"; + public ExpressionType? Expression => null; + public string EdgeQLOperator => "-= {0}"; } } 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/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 0512bf04..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? ExpressionType => 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/OrderByNullPlacement.cs b/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs new file mode 100644 index 00000000..e2a752c0 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/OrderByNullPlacement.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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, + + /// + /// Places values at the end of the ordered set. + /// + Last + } +} 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..c7c8eda2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryBuilder.cs @@ -1,724 +1,942 @@ -using EdgeDB.DataTypes; +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 { - public partial class QueryBuilder + /// + /// A static class providing methods for building queries. + /// + public static class QueryBuilder { - internal virtual Type QuerySelectorType => typeof(object); - - 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); - } + /// + 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() + => 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) + => new QueryBuilder().Insert(value, returnInsertedValue); + + /// + public static IInsertQuery Insert(TType value) + => new QueryBuilder().Insert(value, false); + + /// + 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) + => new QueryBuilder().Update(updateFunc, returnUpdatedValue); + + /// + public static IUpdateQuery Update(Expression> updateFunc) + => new QueryBuilder().Update(updateFunc, false); + + /// + public static IDeleteQuery Delete() + => new QueryBuilder().Delete; + } - return lastNode; - } - } + /// + /// Represents a query builder used to build queries against . + /// + /// The type that this query builder is currently building queries for. + public class QueryBuilder : QueryBuilder + { + public QueryBuilder() : base() { } - public List> Arguments { get; set; } = new(); + internal QueryBuilder(List nodes, List globals, Dictionary variables) + : base(nodes, globals, variables) { } - #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); + new public static IQueryBuilder> With(TVariables variables) + => new QueryBuilder().With(variables); + } - 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); + /// + /// 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. + /// + private readonly List _nodes; - 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); + /// + /// The current user defined query node. + /// + private QueryNode? CurrentUserNode => _nodes.LastOrDefault(x => !x.IsAutoGenerated); - public static QueryBuilder For(Expression, QueryBuilder>> iterator) => new QueryBuilder().For(iterator); + /// + /// A list of query globals used by this query builder. + /// + private readonly List _queryGlobals; - internal static QueryBuilder StaticLiteral(string query, QueryExpressionType type) => new QueryBuilder().Literal(query, type); + /// + /// The current schema introspection info if it has been fetched. + /// + private SchemaInfo? _schemaInfo; - internal static QueryBuilder StaticLiteral(string query, QueryExpressionType type) => new QueryBuilder().Literal(query, type); + /// + /// A dictionary of query variables used by the . + /// + private readonly Dictionary _queryVariables; - internal QueryBuilder Literal(string query, QueryExpressionType type) + /// + /// Initializes the . + /// + static QueryBuilder() { - QueryNodes.Add(new QueryNode - { - Query = query, - Type = type, - }); + QueryObjectManager.Initialize(); + } - return this; + /// + /// Constructs an empty query builder. + /// + public QueryBuilder() + { + _nodes = new(); + _queryGlobals = new(); + _queryVariables = new(); } - #endregion + /// + /// 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; + _queryGlobals = globals; + _queryVariables = variables; + } /// - /// Turns this query builder into a edgeql representation. + /// Adds a query variable to the current query builder. /// - /// A edgeql query. - public override string? ToString() => Build().QueryText; + /// The name of the variable. + /// The value of the variable. + internal void AddQueryVariable(string name, object? value) + => _queryVariables[name] = value; /// - /// Turns this query builder into a edgeql representation where each - /// statement is seperated by newlines. + /// Copies this query builders nodes, globals, and variables + /// to a new query builder with a given generic type. /// - /// A prettified version of the current query. - public string ToPrettyString() => Build().Prettify(); + /// The target type of the new query builder. + /// + /// A new with the target type. + /// + private QueryBuilder EnterNewType() + => new(_nodes, _queryGlobals, _queryVariables); - public BuiltQuery Build() => Build(new()); + /// + /// 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 EnterNewContext() + => new(_nodes, _queryGlobals, _queryVariables); - 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()) + /// + /// 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 a new builder for the node. + var builder = new NodeBuilder(context, _queryGlobals, _nodes, _queryVariables) + { + IsAutoGenerated = autoGenerated }; - } - } - - public class QueryBuilder : QueryBuilder - { - internal override Type QuerySelectorType => typeof(TType); + + // 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); - public QueryBuilder() : this(null) { } - internal QueryBuilder(List? query = null) - { - QueryNodes = query ?? new List(); + return node; } - internal QueryBuilder ConvertTo() - => typeof(TTarget) == typeof(TType) ? (this as QueryBuilder)! : new QueryBuilder(QueryNodes); + /// + /// Builds the current query builder into its form. + /// + /// + /// 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. + /// + internal BuiltQuery InternalBuild(bool includeGlobalsInQuery = true, Action? preFinalizerModifier = null) + { + List query = new(); + List> parameters = new(); + + var nodes = _nodes; + + // reference the introspection and finalize all nodes. + foreach (var node in nodes) + { + node.SchemaInfo ??= _schemaInfo; + if (preFinalizerModifier is not null) + preFinalizerModifier(node); + node.FinalizeQuery(); + } - public new QueryBuilder Select(object shape) - { - return SelectInternal(context => + // create a with block if we have any globals + if (includeGlobalsInQuery && _queryGlobals.Any()) { - return context.DontSelectProperties ? null : (ParseShapeDefinition(shape, typeof(TTarget), false), null); - }); - } + var builder = new NodeBuilder(new WithContext(typeof(TType)) + { + Values = _queryGlobals, + }, _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(); + } - public new QueryBuilder Select(QueryBuilder value, params Expression>[] shape) - { - EnterRootNode(QueryExpressionType.Select, (QueryNode node, ref QueryBuilderContext context) => + // build each node starting at the last node. + for (int i = nodes.Count - 1; i >= 0; i--) { - var innerQuery = value.Build(context); - List parsedShape; + var node = nodes[i]; - 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); - } + var result = node.Build(); - node.Query = $"select ({innerQuery.QueryText}){(parsedShape != null && parsedShape.Count != 0 ? $" {{ {string.Join(", ", parsedShape)} }}" : "")}"; - node.AddArguments(innerQuery.Parameters); - }); + // 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); + } - return ConvertTo(); - } + // reverse our query string since we built our nodes in reverse. + query.Reverse(); - 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); - }); + // flatten our parameters into a single collection and make it distinct. + var variables = parameters + .SelectMany(x => x) + .DistinctBy(x => x.Key); - return typeof(TTarget) == typeof(TType) ? (this as QueryBuilder)! : ConvertTo(); - } + // 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))); - public new QueryBuilder Select(params Expression>[] shape) - { - return SelectInternal(context => + // construct a built query with our query text, variables, and globals. + return new BuiltQuery(string.Join(' ', query)) { - return context.DontSelectProperties ? null : (ParseShapeDefinition(context, shape: shape), null); - }); + Parameters = variables + .ToDictionary(x => x.Key, x => x.Value), + + Globals = !includeGlobalsInQuery ? _queryGlobals : null + }; } - public QueryBuilder Select() => Select(); + /// + public BuiltQuery Build() + => InternalBuild(); + + /// + public ValueTask BuildAsync(IEdgeDBQueryable edgedb, CancellationToken token = default) + => IntrospectAndBuildAsync(edgedb, token); + + /// + internal BuiltQuery BuildWithGlobals(Action? preFinalizerModifier = null) + => InternalBuild(false, preFinalizerModifier); - public new QueryBuilder Select() + #region Root nodes + public QueryBuilder> With(TVariables variables) { - return SelectInternal(context => + 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 properties to our query variables & globals + foreach (var property in typeof(TVariables).GetProperties()) { - if (context.DontSelectProperties) + var value = property.GetValue(variables); + // if its scalar, just add it as a query variable + if (EdgeDBTypeUtils.TryGetScalarType(property.PropertyType, out var scalarInfo)) { - return null; + var varName = QueryUtils.GenerateRandomVariableName(); + _queryVariables.Add(varName, value); + _queryGlobals.Add(new QueryGlobal(property.Name, new SubQuery($"<{scalarInfo}>${varName}"))); } - var result = GetTypePropertyNames(typeof(TTarget), context); - - if (context.IsVariable) + else if (property.PropertyType.IsAssignableTo(typeof(IQueryBuilder))) { - result.Properties = new(); + // 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 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(referenceValue)); + _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}"); + } - 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(); + return EnterNewContext>(); } - - public QueryBuilder Filter(Expression> filter) - => Filter(filter); - public QueryBuilder Filter(Expression> filter) + public IMultiCardinalityExecutable For(IEnumerable collection, Expression, IQueryBuilder>> iterator) { - EnterNode(QueryExpressionType.Filter, (ref QueryBuilderContext builderContext) => + AddNode(new ForContext(typeof(TType)) { - var context = new QueryContext(filter) { BuilderContext = builderContext }; - var builtFilter = ConvertExpression(filter.Body, context); - - return new BuiltQuery - { - Parameters = builtFilter.Arguments, - QueryText = $"filter {builtFilter.Filter}" - }; + Expression = iterator, + Set = collection }); - 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) + + /// + public ISelectQuery Select() { - AssertValid(QueryExpressionType.Offset); - EnterNode(QueryExpressionType.Offset, (ref QueryBuilderContext context) => - { - return new BuiltQuery - { - QueryText = $"offset {count}" - }; - }); return this; + AddNode(new SelectContext(typeof(TType))); + return this; } - public QueryBuilder Limit(ulong count) + /// + public ISelectQuery Select(Expression> selectFunc) { - AssertValid(QueryExpressionType.Limit); - EnterNode(QueryExpressionType.Limit, (ref QueryBuilderContext context) => - { - return new BuiltQuery - { - QueryText = $"limit {count}" - }; + AddNode(new SelectContext(typeof(TResult)) + { + Shape = selectFunc, + IsFreeObject = typeof(TResult).IsAnonymousType() }); - return this; + return EnterNewType(); } - public QueryBuilder For(Expression, QueryBuilder>> iterator) + /// + public ISelectQuery Select(Expression> shape) { - EnterRootNode(QueryExpressionType.For, (QueryNode node, ref QueryBuilderContext context) => + AddNode(new SelectContext(typeof(TType)) { - 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; - }); + Shape = shape, + IsFreeObject = typeof(TType).IsAnonymousType(), }); return this; } - public new QueryBuilder Insert(TTarget value) + /// + public ISelectQuery Select(Expression> shape) { - EnterRootNode(QueryExpressionType.Insert, (QueryNode node, ref QueryBuilderContext context) => + AddNode(new SelectContext(typeof(TType)) { - 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); + Shape = shape, + IsFreeObject = typeof(TNewType).IsAnonymousType(), }); - - return ConvertTo(); + return EnterNewType(); } - public QueryBuilder UnlessConflictOn(params Expression>[] selectors) + /// + public IInsertQuery Insert(TType value, bool returnInsertedValue = true) { - EnterNode(QueryExpressionType.UnlessConflictOn, (ref QueryBuilderContext innerContext) => + var insertNode = AddNode(new InsertContext(typeof(TType)) { - var props = ParseShapeDefinition(innerContext, true, selectors); - - return new BuiltQuery - { - QueryText = props.Count > 1 - ? $"unless conflict on ({string.Join(", ", props)})" - : $"unless conflict on {props[0]}" - }; + Value = value, }); + if (returnInsertedValue) + { + AddNode(new SelectContext(typeof(TType)), true, insertNode); + } + return this; } - public new QueryBuilder Update(TTarget obj) + /// + public IInsertQuery Insert(TType value) + => Insert(value, false); + + /// + public IInsertQuery Insert(Expression> value, bool returnInsertedValue = true) { - EnterRootNode(QueryExpressionType.Update, (QueryNode node, ref QueryBuilderContext context) => + var insertNode = AddNode(new InsertContext(typeof(TType)) { - 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, - }; - }); + Value = value, }); - return ConvertTo(); - } - public new QueryBuilder Update(TTarget? reference, Expression> builder) - { - EnterRootNode(QueryExpressionType.Update, (QueryNode node, ref QueryBuilderContext context) => + if (returnInsertedValue) { - 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 - }; - }); - }); + AddNode(new SelectContext(typeof(TType)), true, insertNode); + } - return ConvertTo(); + return this; } - public new QueryBuilder Update(Expression> builder) + /// + public IInsertQuery Insert(Expression> value) + => Insert(value, false); + + /// + public IUpdateQuery Update(Expression> updateFunc, bool returnUpdatedValue) { - EnterRootNode(QueryExpressionType.Update, (QueryNode node, ref QueryBuilderContext context) => + var updateNode = AddNode(new UpdateContext(typeof(TType)) { - 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 - }; - }); - + UpdateExpression = updateFunc, }); - return ConvertTo(); + if (returnUpdatedValue) + { + AddNode(new SelectContext(typeof(TType)), true, updateNode); + } + + return this; } - public QueryBuilder Delete() + /// + public IUpdateQuery Update(Expression> updateFunc) + => Update(updateFunc, false); + + /// + public IDeleteQuery Delete { - EnterRootNode(QueryExpressionType.Delete, (QueryNode node, ref QueryBuilderContext context) => + get { - node.Query = $"delete {GetTypeName(typeof(TType))}"; - }); + AddNode(new DeleteContext(typeof(TType))); + return this; + } + } + #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) + { + 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; } - public QueryBuilder With(string moduleName) - { - EnterRootNode(QueryExpressionType.With, (QueryNode node, ref QueryBuilderContext context) => - { - node.Query = $"with module {moduleName}"; - }); + /// + /// 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) + throw new InvalidOperationException($"Cannot order by on a {CurrentUserNode}"); + + selectNode.OrderBy(asc, selector, placement); + return this; } - public new QueryBuilder With(params (string Name, object? Value)[] variables) + /// + /// 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) { - EnterRootNode(QueryExpressionType.With, (QueryNode node, ref QueryBuilderContext context) => - { - List statements = new(); + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot offset on a {CurrentUserNode}"); - 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)}"; - }); + selectNode.Offset(offset); return this; } - public new QueryBuilder With(string name, TTarget value) + /// + /// 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 (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}"; - }); - } + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot offset on a {CurrentUserNode}"); + + selectNode.OffsetExpression(offset); - return ConvertTo(); + return this; } - public QueryBuilder Else() + /// + /// 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) { - EnterRootNode(QueryExpressionType.Else, (QueryNode node, ref QueryBuilderContext context) => - { - node.Query = "else"; - }); + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot limit on a {CurrentUserNode}"); + + selectNode.Limit(limit); return this; } - public QueryBuilder Else() + /// + /// 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) { - EnterRootNode(QueryExpressionType.Else, (QueryNode node, ref QueryBuilderContext context) => - { - node.Query = $"else {GetTypeName(typeof(TTarget))}"; - }); + if (CurrentUserNode is not SelectNode selectNode) + throw new InvalidOperationException($"Cannot limit on a {CurrentUserNode}"); - return ConvertTo(); - } + selectNode.LimitExpression(limit); - public QueryBuilder Else(QueryBuilder builder) - { - Else(builder as QueryBuilder); - return ConvertTo(); + return this; } - public QueryBuilder Else(QueryBuilder builder) + /// + /// 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() { - EnterRootNode(QueryExpressionType.Else, (QueryNode node, ref QueryBuilderContext context) => - { - node.Query = "else"; + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); - foreach (var childNode in builder.QueryNodes) - { - node.AddChild(node.Type, (ref QueryBuilderContext innerContext) => - { - return childNode.Build(innerContext); - }); - } - }); + insertNode.UnlessConflict(); return this; } - internal QueryBuilder Literal(string query, QueryExpressionType type) + /// + /// 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) { - QueryNodes.Add(new QueryNode - { - Query = query, - Type = type + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot unless conflict on a {CurrentUserNode}"); - }); + insertNode.UnlessConflictOn(selector); - return ConvertTo(); + return this; } - private QueryNode EnterRootNode(QueryExpressionType type, RootNodeBuilder builder) + /// + /// Adds a 'ELSE (SELECT )' statement to the current node. + /// + /// The current builder. + /// + /// The current node does not support else statements. + /// + private QueryBuilder ElseReturnDefault() { - AssertValid(type); - var node = new QueryNode(type, builder); - QueryNodes.Add(node); - return node; - } + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot else return on a {CurrentUserNode}"); - private void EnterNode(QueryExpressionType type, ChildNodeBuilder builder) => CurrentRootNode.AddChild(type, builder); + insertNode.ElseDefault(); - private void AssertValid(QueryExpressionType currentExpression) - { - if (_validExpressions.TryGetValue(currentExpression, out var exp) && !exp.Contains(CurrentRootNode.Type)) - { - throw new InvalidQueryOperationException(currentExpression, _validExpressions[currentExpression]); - } + return this; } - private readonly Dictionary _validExpressions = new() + /// + /// 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) { - { 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 } } - }; + if (CurrentUserNode is not InsertNode insertNode) + throw new InvalidOperationException($"Cannot else on a {CurrentUserNode}"); - //public static implicit operator Set(QueryBuilder v) => new Set(v); - public static implicit operator ComputedValue(QueryBuilder v) - { - return new ComputedValue(default, v); + insertNode.Else(builder); + + return EnterNewType(); } - public static implicit operator TType(QueryBuilder v) - { - return v.SubQuery(); + /// + /// 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}"); + + var builder = new QueryBuilder(new(), _queryGlobals, new()); + func(builder); + insertNode.Else(builder); + + return this; } - //public Set SubQuerySet() - // => (Set)this; + /// + /// 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}"); + + var builder = new QueryBuilder(new(), _queryGlobals, new()); + func(builder); + insertNode.Else(builder); - public TType SubQuery() - { - var obj = (ISubQueryType)Activator.CreateInstance(CreateMockedType(typeof(TType)))!; - obj.Builder = this; - return (TType)obj; + return this; } - } - - internal delegate BuiltQuery ChildNodeBuilder(ref QueryBuilderContext context); - internal delegate void RootNodeBuilder(QueryNode node, ref QueryBuilderContext context); + #endregion - 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(); + #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); + 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 - private readonly object _lock = new(); + #region IInsertQuery + IUnlessConflictOn IInsertQuery.UnlessConflict() + => UnlessConflict(); + IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) + => UnlessConflictOn(propertySelector); + IUnlessConflictOn IInsertQuery.UnlessConflictOn(Expression> propertySelector) + => UnlessConflictOn(propertySelector); + #endregion - private readonly RootNodeBuilder? _builder; + #region IUpdateQuery + IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) + => Filter(filter); + IMultiCardinalityExecutable IUpdateQuery.Filter(Expression> filter) + => Filter(filter); + #endregion - public QueryNode() { } + #region IUnlessConflictOn + ISingleCardinalityExecutable IUnlessConflictOn.ElseReturn() + => 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 - public QueryNode(QueryExpressionType type, RootNodeBuilder builder) - { - _builder = builder; - Type = type; - } + #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); + 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 - public void AddChild(QueryExpressionType type, ChildNodeBuilder builder) - => Children.Add((builder, type)); - public void SetChild(int index, QueryExpressionType type, ChildNodeBuilder builder) + #region IGroupable + IGroupQuery> IGroupable.GroupBy(Expression> propertySelector) { - if (Children.Count > index) - Children[index] = (builder, type); - else Children.Insert(index, (builder, type)); + AddNode(new GroupContext(typeof(TType)) + { + PropertyExpression = propertySelector + }); + return EnterNewType>(); } - public void AddArguments(IEnumerable> args) + IGroupQuery> IGroupable.Group(Expression>> groupBuilder) { - lock (_lock) + AddNode(new GroupContext(typeof(TType)) { - Arguments = Arguments.Concat(args); - } + BuilderExpression = groupBuilder + }); + return EnterNewType>(); } + #endregion - public BuiltQuery Build(QueryBuilderContext config) + /// + /// 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) { - // remove current arguments incase of building twice - Arguments = new Dictionary(); + if(_nodes.Any(x => x.RequiresIntrospection) || _queryGlobals.Any(x => x.Value is SubQuery subQuery && subQuery.RequiresIntrospection)) + _schemaInfo ??= await SchemaIntrospector.GetOrCreateSchemaIntrospectionAsync(edgedb, token).ConfigureAwait(false); - if (_builder != null) - _builder.Invoke(this, ref config); + var result = Build(); + _nodes.Clear(); + _queryGlobals.Clear(); - var result = $"{Query}"; + return result; + } - 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)); - } + /// + 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, capabilities, token).ConfigureAwait(false); + } - return new BuiltQuery - { - Parameters = Arguments, - QueryText = result - }; + /// + 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, capabilities, token).ConfigureAwait(false); } - } - public enum QueryExpressionType - { - Start, - Select, - Insert, - Update, - Delete, - With, - For, - Filter, - OrderBy, - Offset, - Limit, - Set, - Transaction, - Union, - UnlessConflictOn, - Rollback, - Commit, - Else, - - // internal - Variable, + #region IQueryBuilder + IReadOnlyCollection IQueryBuilder.Nodes => _nodes; + IReadOnlyCollection IQueryBuilder.Globals => _queryGlobals; + IReadOnlyDictionary IQueryBuilder.Variables => _queryVariables; + IQueryBuilder> IQueryBuilder.With(TVariables variables) => With(variables); + BuiltQuery IQueryBuilder.BuildWithGlobals(Action? preFinalizerModifier) => BuildWithGlobals(preFinalizerModifier); + #endregion } - public enum IsolationMode + + /// + /// Represents a built query. + /// + [System.Diagnostics.DebuggerDisplay(@"{Query,nq}")] + public class BuiltQuery { /// - /// 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. + /// Gets the query text. /// - Serializable, + public string Query { get; internal init; } + /// - /// All statements of the current transaction can only see data committed before the first query or data-modification - /// statement was executed in this transaction. + /// Gets a collection of parameters for the query. /// - /// - /// This is the default isolation mode. - /// - RepeatableRead - } + public IDictionary? Parameters { get; internal init; } - public enum AccessMode - { /// - /// Sets the transaction access mode to read/write. + /// Gets a prettified version of this query. /// - /// - /// This is the default transaction access mode. - /// - ReadWrite, + public string Pretty + => Prettify(); + + internal List? Globals { get; init; } /// - /// 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. + /// Creates a new built query. /// - ReadOnly - } + /// The query text. + internal BuiltQuery(string query) + { + Query = query; + } - public enum NullPlacement - { - First, - Last, + /// + /// 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 + 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/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..1650d2d2 100644 --- a/src/EdgeDB.Net.QueryBuilder/QueryContext.cs +++ b/src/EdgeDB.Net.QueryBuilder/QueryContext.cs @@ -1,93 +1,239 @@ -using System.Linq.Expressions; -using System.Reflection; +using EdgeDB.Interfaces; +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; namespace EdgeDB { - internal class QueryContext + /// + /// Represents context used within query functions. + /// + public 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 bool IsVariableReference - => Parent?.Body is MethodCallExpression mc && mc.Method.GetCustomAttribute()?.Operator?.GetType() == typeof(Operators.VariablesReference); - - 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; - } - } + /// + /// 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!; - 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; - } + /// + /// 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!; + + /// + /// 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!; + + /// + /// 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!; + + /// + /// 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!; + + /// + /// 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!; } - internal class QueryContext : QueryContext + /// + /// Represents context used within query functions containing a variable type. + /// + /// The type containing the variables defined in the query. + public abstract class QueryContext : QueryContext { - public QueryContext() { } - public QueryContext(Expression> func) : base() - { - Body = func.Body; - } - - public QueryContext Enter(Expression x, int? paramIndex = null) - { - return new QueryContext() - { - Body = x, - ParameterName = ParameterName, - ParameterType = ParameterType, - IsCharContext = IsCharContext, - ParameterIndex = paramIndex, - Parent = this - }; - } + /// + /// Gets a collection of variables defined in a with block. + /// + public TVariables Variables + => default!; } } diff --git a/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs b/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs new file mode 100644 index 00000000..f0d134f3 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryGlobal.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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; + Value = value; + Reference = reference; + } + } +} 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..56925020 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/DeleteContext.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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/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/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/Contexts/InsertContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs new file mode 100644 index 00000000..f423fbfe --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/InsertContext.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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 new file mode 100644 index 00000000..96275eb8 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/NodeContext.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.QueryNodes +{ + /// + /// 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; } + + /// + /// Gets whether or not the current type is a json variable. + /// + public bool IsJsonVariable + => ReflectionUtils.IsSubTypeOfGenericType(typeof(JsonCollectionVariable<>), CurrentType); + + /// + /// Gets a collection of child queries. + /// + internal Dictionary ChildQueries { get; } = new(); + + /// + /// 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 new file mode 100644 index 00000000..499f9076 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/SelectContext.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +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; } + + /// + /// Gets whether or not the select statement is selecting a free object. + /// + 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/Contexts/UpdateContext.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs new file mode 100644 index 00000000..b60c11c2 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/UpdateContext.cs @@ -0,0 +1,25 @@ +using System; +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 UpdateContext : NodeContext + { + /// + /// Gets the update factory used within the 'SET' statement. + /// + 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 new file mode 100644 index 00000000..c342b692 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/Contexts/WithContext.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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 new file mode 100644 index 00000000..0faeb00d --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/DeleteNode.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + /// + /// Represents a 'DELETE' node + /// + internal class DeleteNode : SelectNode + { + /// + public DeleteNode(NodeBuilder builder) : base(builder) + { + } + + /// + /// + /// Overrides the default method and does nothing. + /// + public override void FinalizeQuery() { } + + /// + public override void Visit() + { + Query.Append($"delete {Context.SelectName ?? OperatingType.GetEdgeDBTypeName()}"); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs new file mode 100644 index 00000000..7f2b3b2b --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/ForNode.cs @@ -0,0 +1,123 @@ +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; + +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 name of the query variable containing the json value. + /// 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 varName, 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.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") + }; + }).ToArray(); + + // build and compile our lambda to get the query builder instance + var builder = (IQueryBuilder)Context.Expression!.Compile().DynamicInvoke(parameters)!; + + // 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, new Json(setJson)); + + // append the 'FOR' statement + Query.Append($"for {name} in json_array_unpack(${jsonName}) union "); + + // parse the iterator expression + var parsed = ParseExpression(name, jsonName, 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(); + + 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 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/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(); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs new file mode 100644 index 00000000..a6d5a4b7 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/InsertNode.cs @@ -0,0 +1,632 @@ +using EdgeDB.DataTypes; +using EdgeDB.Schema; +using EdgeDB.Serializer; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + /// + /// Represents a 'INSERT' node. + /// + 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. + /// + private bool _autogenerateUnlessConflict; + + /// + /// 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) + { + _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. + /// + /// + /// The insert shape for a json-based value. + /// + private ShapeDefinition 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 + IGrouping[] depthMap = JsonUtils.BuildDepthMap(mappingName, jsonValue).ToArray().GroupBy(x => x.Depth).ToArray(); + + // generate the global maps + for (int i = depth; i != 0; i--) + { + var map = depthMap[i]; + var node = map.First(); + 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 = 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 _)) + { + // 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 $"{edgedbName} := {{}}"; + + // return a slice operator for multi links or a index operator for single links + return isArray + ? $"{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 + // type + 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 + 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; + + 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 $"(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.JsonNode)); + + 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.JsonNode)))); + + // create the base insert shape + 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 _)) + { + // return a slice operator for multi links or a index operator for single links + return isArray + ? $"{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 + // type + 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 new ShapeDefinition(shape); + } + + /// + /// 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 ShapeDefinition BuildInsertShape(Type? shapeType = null, object? shapeValue = null) + { + 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; + var value = shapeValue ?? Context.Value; + + // if the value is an expression we can just directly translate it + if (value is LambdaExpression expression) + return $"{{ {ExpressionTranslator.Translate(expression, Builder.QueryVariables, Context, Builder.QueryGlobals)} }}"; + + // get all properties that aren't marked with the EdgeDBIgnore attribute + var properties = type.GetEdgeDBTargetProperties(); + + foreach (var property in properties) + { + // define the type and whether or not it's a link + var propType = property.PropertyType; + 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 (isScalar) + { + // set it as a variable and continue the iteration + var varName = QueryUtils.GenerateRandomVariableName(); + SetVariable(varName, property.GetValue(value)); + setters.Add($"{propertyName} := <{edgeqlType}>${varName}"); + continue; + } + + // 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) + setters.Add($"{propertyName} := {{}}"); + 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 + setters.Add($"{propertyName} := {{ {string.Join(", ", subShape)} }}"); + } + else // generate the link resolver and append it + setters.Add($"{propertyName} := {BuildLinkResolver(propType, subValue)}"); + + continue; + } + + throw new InvalidOperationException($"Failed to find method to serialize the property \"{property.PropertyType.Name}\" on type {type.Name}"); + } + + return new ShapeDefinition(setters); + } + + /// + /// 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}\")"), + value); + } + else + { + RequiresIntrospection = true; + + // add a insert select statement + return InlineOrGlobal(type, new SubQuery((info) => + { + var name = type.GetEdgeDBTypeName(); + 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; + return $"(insert {name} {BuildInsertShape(type, value)}{exclusiveCondition})"; + }), value); + } + } + + /// + /// 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()!; + } + + + /// + /// 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() + { + _elseStatement.Append($" else (select {OperatingType.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. + 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) + { + 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.Append($" else ({result.Query})"); + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs new file mode 100644 index 00000000..ed3e52de --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/NodeBuilder.cs @@ -0,0 +1,61 @@ +using EdgeDB.QueryNodes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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(); + 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 new file mode 100644 index 00000000..094772b1 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/QueryNode.cs @@ -0,0 +1,199 @@ +using EdgeDB.Schema; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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 child nodes. + /// + 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(); + + /// + /// 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; + + /// + /// The operating type within the context of the query builder. + /// + public readonly Type OperatingType; + + /// + /// Constructs a new query node with the given builder. + /// + /// the builder used to build this node. + public QueryNode(NodeBuilder builder) + { + Builder = builder; + OperatingType = GetOperatingType(); + } + + /// + /// 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) + { + 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); + Builder.QueryGlobals.Add(global); + ReferencedGlobals.Add(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); + if (global != null) + return global.Name; + var name = QueryUtils.GenerateRandomVariableName(); + SetGlobal(name, value, reference); + 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] + : Context.CurrentType; + + /// + /// 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; + Parameters = parameters; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs new file mode 100644 index 00000000..0997e0ac --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/SelectNode.cs @@ -0,0 +1,214 @@ +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; + +namespace EdgeDB.QueryNodes +{ + /// + /// Represents a 'SELECT' node + /// + internal class SelectNode : QueryNode + { + /// + /// The max recursion depth for generating default shapes. + /// + public const int MAX_DEPTH = 2; + + /// + 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 => + { + // 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 (EdgeDBTypeUtils.IsLink(x.PropertyType, out var isArray, out var innerType)) + { + var shapeType = isArray ? innerType! : x.PropertyType; + 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 + return name; + }).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)} }}"; + } + + /// + /// Gets the default shape for the current contextual type. + /// + /// The default shape for the current contextual type. + private string? GetDefaultShape() + => GetShape(OperatingType); + + /// + /// 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(); + } + + // generate the shape based on the contexts' expression. + 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() + { + // 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()}"); + return; + } + + // if our shape is 'new {...}' or null then parse the shape + if (Context.Shape?.Body is NewExpression or MemberInitExpression || Context.Shape is null) + { + var shape = GetShape(); + + if (Context.IsFreeObject) + Query.Insert(0, $"select {shape}"); + else + Query.Insert(0, $"select {Context.SelectName ?? OperatingType.GetEdgeDBTypeName()} {shape}"); + } + else + { + // else we can just translate the shape and append it. + Query.Insert(0, $"select {ExpressionTranslator.Translate(Context.Shape, Builder.QueryVariables, Context, Builder.QueryGlobals)}"); + } + } + + /// + /// Adds a filter to the select node. + /// + /// The filter predicate to add. + public void Filter(LambdaExpression expression) + { + 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) + { + 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 new file mode 100644 index 00000000..551f3579 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/UpdateNode.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + /// + /// Represents a 'UPDATE' node. + /// + internal class UpdateNode : QueryNode + { + /// + /// The translated update factory. + /// + private string? _translatedExpression; + + /// + public UpdateNode(NodeBuilder builder) : base(builder) { } + + /// + public override void Visit() + { + // append 'update type' + Query.Append($"update {OperatingType.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) + { + // 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); + Query.Clear(); + } + } + + /// + /// Adds a filter to the update node. + /// + /// The filter predicate to add. + public void Filter(LambdaExpression filter) + { + // 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 new file mode 100644 index 00000000..4c95bf1e --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryNodes/WithNode.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.QueryNodes +{ + /// + /// Represents a 'WITH' node. + /// + internal class WithNode : QueryNode + { + /// + public WithNode(NodeBuilder builder) : base(builder) { } + + /// + public override void Visit() + { + // 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(); + value = new SubQuery($"({query.Query})"); + + if(query.Parameters is not null) + foreach (var variable in query.Parameters) + SetVariable(variable.Key, variable.Value); + + if (query.Globals is not null) + foreach (var queryGlobal in query.Globals) + 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) + throw new InvalidOperationException("Cannot build without introspection! A node requires query introspection."); + 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 new file mode 100644 index 00000000..c2b2b169 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryObjectManager.cs @@ -0,0 +1,87 @@ +using EdgeDB.Serializer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + /// + /// Represents a class that contains safe references to objects returned in queries. + /// + internal class QueryObjectManager + { + /// + /// 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 += 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; + if (obj == null) + return false; + + var reference = _references.FirstOrDefault(x => x.Reference.IsAlive && x.Reference.Target == obj); + id = reference?.ObjectId ?? default; + return reference != null; + } + + /// + /// 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; + Reference = reference; + } + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs new file mode 100644 index 00000000..7cbf2852 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/QueryableCollection.cs @@ -0,0 +1,241 @@ +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.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + /// + /// A wrapper for the which expose basic CRUD methods. + /// + /// + public sealed class QueryableCollection + { + /// + /// Gets a query builder for . + /// + public QueryBuilder QueryBuilder + => new(); + + /// + /// The client used for introspection and execution. + /// + private readonly IEdgeDBQueryable _edgedb; + + /// + /// Constructs a new . + /// + /// The client to introspect and execute with. + 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: 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: 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 updateFactory = await QueryGenerationUtils.GenerateUpdateFactoryAsync(_edgedb, item, token).ConfigureAwait(false); + + return await QueryBuilder + .Insert(item) + .UnlessConflict() + .Else(q => + (ISingleCardinalityExecutable)q.Update(updateFactory!, false) + ).ExecuteAsync(_edgedb, token: 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: token); + return result != null; + } + + /// + /// 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: 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: token).ConfigureAwait(false) + ).Any(); + + // try to get exclusive property set on the instance + 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."); + + // 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: token).ConfigureAwait(false)).Any(); + } + + /// + /// 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) + => ((ISingleCardinalityExecutable)QueryBuilder.Select(() => EdgeQL.Count(QueryBuilder.Delete.Filter(filter)))).ExecuteAsync(_edgedb, token: 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: token); + + /// + /// 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 QueryGenerationUtils.GenerateUpdateFilterAsync(_edgedb, value, token)) + .ExecuteAsync(_edgedb, token: token).ConfigureAwait(false)).FirstOrDefault(); + + /// + /// 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 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/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 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 new file mode 100644 index 00000000..b4a834e2 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/ObjectType.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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 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/DataTypes/Property.cs b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs new file mode 100644 index 00000000..2c058790 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Schema/DataTypes/Property.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Schema.DataTypes +{ + /// + /// Represents the cardinality of a . + /// + internal enum Cardinality + { + One, + AtMostOne, + AtLeastOne, + Many, + } + 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 the property is required. + /// + public bool Required { 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 new file mode 100644 index 00000000..edbdd99b --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaInfo.cs @@ -0,0 +1,49 @@ +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 +{ + /// + /// 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 => + { + var name = type.GetEdgeDBTypeName(); + return name == x.CleanedName || name == x.Name; + })) != 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..7e321631 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Schema/SchemaIntrospector.cs @@ -0,0 +1,87 @@ +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 +{ + /// + /// 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)) + return ValueTask.FromResult(info); + 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(), + IsAbstract = ctx.Include(), + Name = ctx.Include(), + Constraints = ctx.IncludeMultiLink(() => new Constraint + { + SubjectExpression = ctx.Include(), + Name = ctx.Include(), + }), + 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, + 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: 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 new file mode 100644 index 00000000..8735fe14 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/SubQuery.cs @@ -0,0 +1,64 @@ +using EdgeDB.Schema; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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 new file mode 100644 index 00000000..1ab15784 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/BinaryExpressionTranslator.cs @@ -0,0 +1,38 @@ +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 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); + + // 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)}"; + } + + // Try to get a IEdgeQLOperator for the given binary operator + if (!TryGetExpressionOperator(expression.NodeType, out var op)) + throw new NotSupportedException($"Failed to find operator for node type {expression.NodeType}"); + + // build the operator + return op.Build(left, right); + } + } +} 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..703a98e7 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConditionalExpressionTranslator.cs @@ -0,0 +1,29 @@ +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 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 new file mode 100644 index 00000000..fefb2e8c --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ConstantExpressionTranslator.cs @@ -0,0 +1,25 @@ +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 an expression with a constant value. + /// + internal class ConstantExpressionTranslator : ExpressionTranslator + { + /// + public override string? Translate(ConstantExpression expression, ExpressionContext context) + { + // 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 new file mode 100644 index 00000000..27959691 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionContext.cs @@ -0,0 +1,176 @@ +using EdgeDB.QueryNodes; +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 +{ + /// + /// Represents context used by an . + /// + internal class ExpressionContext + { + /// + /// Gets the calling nodes context. + /// + public NodeContext NodeContext { get; } + + /// + /// Gets the root lambda function that is currently being translated. + /// + public LambdaExpression RootExpression { get; set; } + + /// + /// Gets a collection of method parameters within the . + /// + public Dictionary Parameters { get; } + + /// + /// 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. + /// + internal readonly IDictionary QueryArguments; + + /// + /// The collection of query globals. + /// + internal 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) + { + RootExpression = rootExpression; + 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(); + 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) + => QueryArguments[name] = value; + + /// + /// 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 == 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 && 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); + } + + public void AddChildQuery(SubQuery query) + { + var name = QueryUtils.GenerateRandomVariableName(); + NodeContext.ChildQueries.Add(name, query); + } + + /// + /// 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); + return exp; + } + } +} 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..d374be63 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/ExpressionTranslator.cs @@ -0,0 +1,170 @@ +using EdgeDB.Operators; +using EdgeDB.QueryNodes; +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 +{ + /// + /// 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"); + + 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); + } + + /// + /// 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) + { + // 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!; + + // if we can find a translator for the expression type, use it. + if (_translators.TryGetValue(expType, out var translator)) + { + return translator.Translate(expression, context)!; + } + + 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/InitializationTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs new file mode 100644 index 00000000..bc5f2058 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/InitializationTranslator.cs @@ -0,0 +1,123 @@ +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 typeName = memberType.GetEdgeDBTypeName(); + var isLink = EdgeDBTypeUtils.IsLink(memberType, out var isMultiLink, out var innerType); + + switch (Expression) + { + case MemberExpression when isLink: + { + var disassembled = ExpressionUtils.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(QueryGenerationUtils.GenerateInsertShapeExpression(memberValue, memberType), context); + + if (!info.TryGetObjectInfo(memberType, out var objInfo)) + throw new InvalidOperationException($"No schema type found for {memberType}"); + + var exclusiveCondition = $"{ConflictUtils.GenerateExclusiveConflictStatement(objInfo, true)} else (select {typeName})"; + return $"(insert {memberType.GetEdgeDBTypeName()} {{ {insertShape} }} {exclusiveCondition})"; + }), 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: + { + var name = QueryUtils.GenerateRandomVariableName(); + var expression = Expression; + context.SetGlobal(name, new SubQuery((info) => + { + // generate an insert shape + var insertShape = ExpressionTranslator.ContextualTranslate(expression, context); + + if (!info.TryGetObjectInfo(memberType, out var objInfo)) + throw new InvalidOperationException($"No schema type found for {memberType}"); + + var exclusiveCondition = $"{ConflictUtils.GenerateExclusiveConflictStatement(objInfo, true)} else (select {typeName})"; + + 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 or UpdateContext || + 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/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 new file mode 100644 index 00000000..43b0d43d --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberExpressionTranslator.cs @@ -0,0 +1,131 @@ +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 +{ + /// + /// Represents a translator for translating an expression accessing a field or property. + /// + internal class MemberExpressionTranslator : ExpressionTranslator + { + /// + public override string? Translate(MemberExpression expression, ExpressionContext context) + { + // deconstruct the member access tree. + var deconstructed = ExpressionUtils.DisassembleExpression(expression).ToArray(); + + var baseExpression = deconstructed.LastOrDefault(); + + // if the base class is context + if (baseExpression is not null && baseExpression.Type.IsAssignableTo(typeof(QueryContext))) + { + // switch the name of the accessed member + var accessExpression = deconstructed[^2]; + if(accessExpression is not MemberExpression memberExpression) + throw new NotSupportedException($"Cannot use expression type {accessExpression.NodeType} for a contextual member access"); + + switch (memberExpression.Member.Name) + { + case nameof(QueryContext.Variables): + // get the reference + var target = deconstructed[^3]; + + // switch the type of the target + switch (target) + { + case MemberExpression targetMember: + if (targetMember.Type.IsAssignableTo(typeof(IJsonVariable))) + { + // pull the paths coming off of target member + var path = deconstructed[0].ToString()[(targetMember.ToString().Length + 6)..].Split('.', options: StringSplitOptions.RemoveEmptyEntries); + + // get the name of the json value + var jsonGlobal = context.Globals.FirstOrDefault(x => 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"); + + // 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 (baseExpression is ConstantExpression constant) + { + // 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); + } + + // 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 (!EdgeDBTypeUtils.TryGetScalarType(expression.Type, out var type)) + throw new NotSupportedException($"Cannot use {expression.Type} as no edgeql equivalent can be found"); + + return $"<{type}>${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() + { + expression.Member.GetEdgeDBPropertyName() + }; + + if (expression.Expression is MemberExpression innerExp) + tree.Add(ParseMemberExpression(innerExp)); + if (expression.Expression is ParameterExpression param) + if(includeSelfReference) + tree.Add(includeParameter ? param.Name : string.Empty); + + tree.Reverse(); + return string.Join('.', tree); + } + } +} 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..f3559c65 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MemberInitExpressionTranslator.cs @@ -0,0 +1,34 @@ +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; + +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<(MemberInfo, Expression)> expressions = new(); + + 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])); + + foreach (var binding in expression.Bindings.Where(x => x is MemberAssignment).Cast()) + expressions.Add((binding.Member, binding.Expression)); + + 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 new file mode 100644 index 00000000..b75ba3a9 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/MethodCallExpressionTranslator.cs @@ -0,0 +1,225 @@ +using EdgeDB.Operators; +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 +{ + /// + /// 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) + { + // 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, context.RootExpression.Parameters).Compile().DynamicInvoke(); + + // attempt to get the scalar type of the result of the method. + if (!EdgeDBTypeUtils.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() + : 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 => 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; + } + + private string? TranslateToEdgeQL(MethodCallExpression expression, ExpressionContext context) + { + // if our method is within the query context class + if (expression.Method.DeclaringType == typeof(QueryContext)) + { + 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. + 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]) ?? + 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): + { + // same thing as local except we dont validate anything here + return $".{TranslateExpression(expression.Arguments[0], context.Enter(x => x.StringWithoutQuotes = true))}"; + } + case nameof(QueryContext.Include): + { + // return nothing for scalar includes + 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 the raw string as a constant expression and serialize it without quotes + return TranslateExpression(expression.Arguments[0], context.Enter(x => 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) + : context.Enter(x => x.IncludeSelfReference = false)); + + var backlink = $".<{property}"; + + // if its a lambda, add the corresponding generic type as a [is x] statement + if (!isRawPropertyName) + backlink += $"[is {expression.Method.GetGenericArguments()[0].GetEdgeDBTypeName()}]"; + + // if it has a shape, translate the shape and add it to the backlink + if (hasShape) + backlink += $"{{ {TranslateExpression(expression.Arguments[1], context)} }}"; + + return backlink; + } + case nameof(QueryContext.SubQuery): + { + // pull the builder parameter and add it to a new lambda + // and execute it to get an instanc of the builder + var builder = (IQueryBuilder)Expression.Lambda(expression.Arguments[0]).Compile().DynamicInvoke()!; + + // build it and copy its globals & parameters to our builder + var result = builder.BuildWithGlobals(); + + if (result.Parameters is not null) + foreach (var parameter in result.Parameters) + context.SetVariable(parameter.Key, parameter.Value); + + if (result.Globals is not null) + foreach (var global in result.Globals) + context.SetGlobal(global.Name, global.Value, global.Reference); + + // add the result as a sub query + return $"({result.Query})"; + } + 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 our method translators can translate it + if (MethodTranslator.TryTranslateMethod(expression, context, out var translatedMethod)) + return translatedMethod; + + // 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]; + var parameters = expression.Method.GetParameters(); + for (int i = 0; i != argsArray.Length; i++) + { + var arg = expression.Arguments[i]; + + // check if the argument is a query builder + if (parameters[i].ParameterType.IsAssignableTo(typeof(IQueryBuilder))) + { + // compile and execute the lambda to get an instance of the builder + 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(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"); + + if (result.Parameters is not null) + foreach (var parameter in result.Parameters) + context.SetVariable(parameter.Key, parameter.Value); + + argsArray[i] = context.GetOrAddGlobal(null, new SubQuery($"({result.Query})")); + } + else + argsArray[i] = TranslateExpression(arg, context); + } + + // check if the edgeql operator has an initialization operator + context.HasInitializationOperator = edgeqlOperator switch + { + LinksAddLink or LinksRemoveLink => true, + _ => false + }; + + // build the operator + return edgeqlOperator.Build(argsArray); + } + + // check if its a known method + if (EdgeQL.FunctionOperators.TryGetValue($"{expression.Method.DeclaringType?.Name}.{expression.Method.Name}", out edgeqlOperator)) + { + 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/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs new file mode 100644 index 00000000..7a3a25cc --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewArrayExpressionTranslator.cs @@ -0,0 +1,30 @@ +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 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 + 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(EdgeDBTypeUtils.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 new file mode 100644 index 00000000..a0a0f61b --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/NewExpressionTranslator.cs @@ -0,0 +1,23 @@ +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 +{ + /// + /// Represents a translator for translating an expression with a constructor call. + /// + internal class NewExpressionTranslator : ExpressionTranslator + { + /// + public override string? Translate(NewExpression expression, ExpressionContext context) + => 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/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 new file mode 100644 index 00000000..8d01b1f4 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Translators/Expressions/UnaryExpressionTranslator.cs @@ -0,0 +1,66 @@ +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 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); + + 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) + { + // no need to cast in edgedb, return the value + return value; + } + + var type = EdgeDBTypeUtils.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. + 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/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs b/src/EdgeDB.Net.QueryBuilder/Translators/Methods/EnumerableMethodTranslator.cs new file mode 100644 index 00000000..f8b751ff --- /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 $"<{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/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/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..d189c708 --- /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..7c70b995 --- /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 + => EdgeDBTypeUtils.TryGetScalarType(ParameterType, out var info) && info.IsArray; + + /// + /// Gets whether or not the parameter is a scalar type. + /// + public bool IsScalarType + => EdgeDBTypeUtils.TryGetScalarType(ParameterType, out _); + + /// + /// Gets whether or not the parameter is a valid link type. + /// + public bool IsLinkType + => EdgeDBTypeUtils.IsLink(ParameterType, out _, out _); + + /// + /// Gets whether or not the parameter is a valid multi-link type. + /// + public bool IsMutliLinkType + => EdgeDBTypeUtils.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/Utils/ConflictUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/ConflictUtils.cs new file mode 100644 index 00000000..e751dea7 --- /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.Name != "id" && 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..f81f992b --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils/EdgeDBTypeUtils.cs @@ -0,0 +1,152 @@ +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 = 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); + 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 = 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; + return result; + } + + return TypeBuilder.IsValidObjectType(type) && !TryGetScalarType(type, out _); + } + } +} 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/JsonUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs new file mode 100644 index 00000000..65323e1b --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils/JsonUtils.cs @@ -0,0 +1,198 @@ +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. + /// The depth of the node. + 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; + } + } +} diff --git a/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs b/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs new file mode 100644 index 00000000..af9f33e6 --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils/QueryGenerationUtils.cs @@ -0,0 +1,168 @@ +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. + /// + /// Whether or not to include the 'id' property. + /// 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 new file mode 100644 index 00000000..d4682dad --- /dev/null +++ b/src/EdgeDB.Net.QueryBuilder/Utils/QueryUtils.cs @@ -0,0 +1,65 @@ +using EdgeDB.Interfaces; +using EdgeDB.Interfaces.Queries; +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; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB +{ + /// + /// A class containing useful utilities for building queries. + /// + internal static class QueryUtils + { + private const string VARIABLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static readonly Random _rng = new(); + + /// + /// 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) + 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() ?? "{}", + _ => "{}" + } : $"\"{obj}\""; + } + + return obj switch + { + SubQuery query when !query.RequiresIntrospection => query.Query!, + string str => $"\"{str}\"", + char chr => $"\"{chr}\"", + Type type => EdgeDBTypeUtils.TryGetScalarType(type, out var info) ? info.ToString() : type.GetEdgeDBTypeName(), + _ => obj.ToString()! + }; + } + + /// + /// Generates a random valid variable name for use in queries. + /// + /// 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()); + } +} 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/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/tests/EdgeDB.Tests.QueryBuilder.Unit/Consts.cs b/tests/EdgeDB.Tests.QueryBuilder.Unit/Consts.cs new file mode 100644 index 00000000..9bfd22f6 --- /dev/null +++ b/tests/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/tests/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj b/tests/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj new file mode 100644 index 00000000..b876d78d --- /dev/null +++ b/tests/EdgeDB.Tests.QueryBuilder.Unit/EdgeDB.Tests.QueryBuilder.Unit.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/tests/EdgeDB.Tests.QueryBuilder.Unit/Selects.cs b/tests/EdgeDB.Tests.QueryBuilder.Unit/Selects.cs new file mode 100644 index 00000000..814c30d7 --- /dev/null +++ b/tests/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/tests/EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs b/tests/EdgeDB.Tests.QueryBuilder.Unit/TestBase.cs new file mode 100644 index 00000000..42c8b89d --- /dev/null +++ b/tests/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/tests/EdgeDB.Tests.QueryBuilder.Unit/Types.cs b/tests/EdgeDB.Tests.QueryBuilder.Unit/Types.cs new file mode 100644 index 00000000..e96b0b5a --- /dev/null +++ b/tests/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/tests/EdgeDB.Tests.QueryBuilder.Unit/Usings.cs b/tests/EdgeDB.Tests.QueryBuilder.Unit/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/tests/EdgeDB.Tests.QueryBuilder.Unit/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file 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; 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}\";"); } } diff --git a/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml b/tools/EdgeDB.QueryBuilder.OperatorGenerator/operators.yml index 668cd2b0..a5cf0af9 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> @@ -1453,29 +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?" - -variables: - - operator: "{0}" - name: Reference - - enums: - name: DurationTruncateUnit serialize_method: Lower diff --git a/tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs new file mode 100644 index 00000000..9cdfdb7a --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/CodeWriter.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EdgeDB.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..ca0ce1f2 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/FunctionGenerator.cs @@ -0,0 +1,355 @@ +using EdgeDB.DataTypes; +using EdgeDB.StandardLibGenerator.Models; +using System.Collections; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; + +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 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) + { + _client = client; + _edgeqlClassWriter = eqlWriter; + if (!Directory.Exists(OUTPUT_PATH)) + Directory.CreateDirectory(OUTPUT_PATH); + + if (!Directory.Exists(STDLIB_PATH)) + Directory.CreateDirectory(STDLIB_PATH); + + try + { + 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 class ParsedParameter + { + public string? Name { get; init; } + 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) + { + 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, 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), 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) + { + 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 = await ParseParameter("result", returnTypeInfo, func.ReturnType, func.ReturnTypeModifier); + + 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"); + + ParsedParameter[] parsedParameters = new ParsedParameter[parameters.Count()]; + + for(int i = 0; i != parsedParameters.Length; i++) + { + var x = parameters.ElementAt(i); + var name = x.Value.Parameter.Name; + parsedParameters[i] = await ParseParameter(name, x.Value.Node, x.Value.Parameter.Type!, x.Value.Parameter.TypeModifier, i); + if (!string.IsNullOrEmpty(x.Value.Parameter.Default) && x.Value.Parameter.Default != "{}") + parsedParameters[i].DefaultValue = await ParseDefaultAsync(x.Value.Parameter.Default); + } + + var parameterGenerics = parsedParameters.Where(x => x.Generics.Any()).SelectMany(x => x.Generics); + + var strongMappedParameters = string.Join(", ", parsedParameters.Select((x, i) => + { + 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 + }; + } + + return t; + })); + + 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(); + + var formattedGenerics = string.Join(", ", dotnetReturnType.Generics.Concat(parsedParameters.Where(x => x.Generics!.Any()).SelectMany(x => x.Generics!)).Distinct()); + + 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).Concat(dotnetReturnType.GenericConditions).Distinct()) + { + _edgeqlClassWriter.AppendLine($" {c}"); + } + _edgeqlClassWriter.AppendLine(" => default!;"); + _generatedPublicFuncs.Add(genKey); + } + } + 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 async ValueTask ParseParameter(string? name, TypeNode node, Models.Type type, TypeModifier? modifier, int index = 0, int subIndex = 0) + { + if (node.IsGeneric) + { + 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: + tModified += "?"; + break; + case TypeModifier.SetOfType: + tModified = $"IEnumerable<{tname}>"; + break; + default: + break; + } + } + return new ParsedParameter() { Name = name, Generics = new string[] { tname }, Type = tModified }; + } + + var typeName = node.DotnetTypeName ?? await GenerateType(node); + List generics = new(); + List subTypes = new(); + List constraints = 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.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()) + 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 { 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) + { + 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 + { + TypeModifier.OptionalType => $"{name}?", + TypeModifier.SingletonType => name, + TypeModifier.SetOfType => $"IEnumerable<{name}>", + _ => name + }; + } + + private static async ValueTask GenerateType(TypeNode node) + { + 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 = ""; + + using(_ = writer.BeginScope("namespace EdgeDB")) + { + switch (meta.Type) + { + case MetaInfoType.Object: + { + + } + 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}"); + } + } + + File.WriteAllText(Path.Combine(STDLIB_PATH, $"{typeName}.g.cs"), writer.ToString()); + TypeUtils.GeneratedTypes.Add(edgedbType.Name, typeName); + return typeName; + } + + + private static readonly Regex _typeCastOne = new(@"(<[^<]*?>)"); + private static async Task ParseDefaultAsync(string @default) + { + 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/Models/Annotation.cs b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Annotation.cs new file mode 100644 index 00000000..d06d900f --- /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.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..c8016468 --- /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.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..34a8794c --- /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.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..cce3c1d5 --- /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.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..c1ed5458 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Models/Type.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.StandardLibGenerator.Models +{ + [EdgeDBType(ModuleName = "schema")] + internal class Type + { + public string Name { get; set; } + public bool IsAbstract { 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, target: {name, is_abstract}}"), + EnumValues = ctx.Raw("[is schema::ScalarType].enum_values") + }).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; } + public string[]? EnumValues { get; set; } + + [EdgeDBIgnore] + public MetaInfoType Type + => Pointers?.Any() ?? false + ? MetaInfoType.Object + : EnumValues?.Any() ?? false + ? MetaInfoType.Enum + : MetaInfoType.Unknown; + } + + public enum MetaInfoType + { + Enum, + Object, + Unknown + } + + 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 new file mode 100644 index 00000000..67f4eb49 --- /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.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..106ca804 --- /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.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..b83a37a5 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/OperatorGenerator.cs @@ -0,0 +1,18 @@ +using EdgeDB.StandardLibGenerator.Models; +using System.Linq.Expressions; + +namespace EdgeDB.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..9299c3e0 --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/Program.cs @@ -0,0 +1,30 @@ +using EdgeDB; +using EdgeDB.StandardLibGenerator; +using EdgeDB.StandardLibGenerator.Models; + +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(); + +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 EdgeQLTest")) + { + 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 new file mode 100644 index 00000000..8d0cb7ce --- /dev/null +++ b/tools/EdgeDB.QueryBuilder.StandardLibGenerator/TypeUtils.cs @@ -0,0 +1,177 @@ +using EdgeDB.DataTypes; +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.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; + public readonly TypeNode[] Children; + + public readonly string? TupleElementName; + 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) + { + EdgeDBName = name; + DotnetType = dotnetType; + IsGeneric = isGeneric; + Children = children; + IsChildOfNamedTuple = false; + TupleElementName = null; + RequiresGeneration = false; + _dotnetName = null; + WasGenerated = 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; + _dotnetName = null; + WasGenerated = 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; + _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() + { + if (Children is null || !Children.Any()) + return DotnetTypeName; + + return $"{DotnetTypeName.Replace("`1", "")}<{string.Join(", ", Children)}>"; + } + } + + 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 + { + "std::set" => typeof(IEnumerable), + "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), + "cal::date_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), + "schema::ScalarType" => typeof(Type), + _ => null + }; + + if (dotnetType is not null) + type = new(t, dotnetType, false); + else if (t.StartsWith("any") || t.StartsWith("std::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(ValueTuple<>), + "array" => typeof(IEnumerable<>), + "set" => typeof(IEnumerable<>), + "range" => typeof(Range<>), + _ => 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"); + + type = new(t, wrapperType, false, innerTypes.Select(x => x!.Value).ToArray()); + } + + return true; + } + } +}