Skip to content

Commit

Permalink
Implemented bulk replace for trait relations
Browse files Browse the repository at this point in the history
  • Loading branch information
maximiliancsuk committed May 7, 2024
1 parent 8967bc7 commit bb08044
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 0 deletions.
29 changes: 29 additions & 0 deletions backend/Omnikeeper.Base/Model/TraitBased/TraitEntityModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,22 @@ public async Task<bool> BulkReplaceAttributesOnly(ICIIDSelection relevantCIs, IE
return await WriteAttributes(attributeFragments, relevantCIs, NamedAttributesSelection.Build(relevantAttributesForTrait), layerSet, writeLayer, changesetProxy, trans, maskHandlingForRemoval);
}


/*
* NOTE: unlike the regular update, this does not do any checks if the updated entities actually fulfill the trait requirements
* and will be considered as this trait's entities going forward
*/
// NOTE: the cis MUST exist already
public async Task<bool> BulkReplaceRelationsOnly(IEnumerable<BulkRelationFullFragment> fragments, IReadOnlySet<Guid> relevantCIIDs,
LayerSet layerSet, string writeLayer, IChangesetProxy changesetProxy, IModelContext trans, IMaskHandlingForRemoval maskHandlingForRemoval)
{
if (relevantCIIDs.IsEmpty())
return false;

var scope = new BulkRelationDataCIScope(writeLayer, fragments, relevantCIIDs);
return await WriteRelations(scope, layerSet, writeLayer, changesetProxy, trans, maskHandlingForRemoval);
}

// NOTE: the ci MUST exist already
public async Task<(EffectiveTrait et, bool changed)> InsertOrUpdate(Guid ciid, IEnumerable<BulkCIAttributeDataCIAndAttributeNameScope.Fragment> attributeFragments,
IList<(Guid thisCIID, string predicateID, Guid[] otherCIIDs)> outgoingRelations, IList<(Guid thisCIID, string predicateID, Guid[] otherCIIDs)> incomingRelations,
Expand Down Expand Up @@ -302,5 +318,18 @@ await relationModel.BulkReplaceRelations(new BulkRelationDataCIAndPredicateScope
throw new Exception("Cannot remove relations from trait entity: trait entity does not conform to trait requirements");
return (dc, changed);
}

//public async Task<(EffectiveTrait et, bool changed)> BulkReplaceRelations(TraitRelation tr, Guid thisCIID, Guid[] relatedCIIDs, LayerSet layerSet, string writeLayerID, IChangesetProxy changesetProxy, IModelContext trans, MaskHandlingForRemovalApplyNoMask maskHandlingForRemoval)
//{
// var relevantRelations = new HashSet<(Guid thisCIID, string predicateID)>() { (thisCIID, tr.RelationTemplate.PredicateID) };
// var relations = new List<(Guid thisCIID, string predicateID, Guid[] otherCIIDs)>() { (thisCIID, predicateID: tr.RelationTemplate.PredicateID, relatedCIIDs) };
// var scope = new BulkRelationDataCIAndPredicateScope(writeLayerID, relations, relevantRelations, tr.RelationTemplate.DirectionForward);
// var changed = await WriteRelations(scope, layerSet, writeLayerID, changesetProxy, trans, maskHandlingForRemoval);

// var dc = await GetSingleByCIID(thisCIID, layerSet, trans, changesetProxy.TimeThreshold);
// if (dc == null)
// throw new Exception("Cannot set relations of trait entity: trait entity does not conform to trait requirements");
// return (dc, changed);
//}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ public static string SanitizeMutationName(string unsanitizedMutationName)
public static string GenerateTraitRelationFilterWrapperInputGraphTypeName(ITrait trait) => SanitizeTypeName("TR_filter_Input_" + trait.ID);
public static string GenerateUpsertTraitEntityInputGraphTypeName(ITrait trait) => SanitizeTypeName("TE_Upsert_Input_" + trait.ID);
public static string GenerateUpsertAttributesOnlyTraitEntityInputGraphTypeName(ITrait trait) => SanitizeTypeName("TE_Upsert_Attributes_Only_Input_" + trait.ID);
public static string GenerateUpsertRelationsOnlyTraitEntityInputGraphTypeName() => SanitizeTypeName("TE_Upsert_Relations_Only_Input");
public static string GenerateCIIDAndUpsertAttributesOnlyInputGraphTypeName(ITrait trait) => SanitizeTypeName("TE_CIID_And_Upsert_Attributes_Only_Input_" + trait.ID);
public static string GenerateUpdateTraitEntityInputGraphTypeName(ITrait trait) => SanitizeTypeName("TE_Update_Input_" + trait.ID);
public static string GenerateSetRelationsByCIIDMutationName(string traitID, TraitRelation tr) => "setRelationsByCIID_" + SanitizeMutationName(traitID) + "_" + SanitizeMutationName(tr.Identifier);
public static string GenerateAddRelationsByCIIDMutationName(string traitID, TraitRelation tr) => "addRelationsByCIID_" + SanitizeMutationName(traitID) + "_" + SanitizeMutationName(tr.Identifier);
public static string GenerateRemoveRelationsByCIIDMutationName(string traitID, TraitRelation tr) => "removeRelationsByCIID_" + SanitizeMutationName(traitID) + "_" + SanitizeMutationName(tr.Identifier);
public static string GenerateBulkReplaceRelationsMutationName(string traitID, TraitRelation tr) => "bulkReplaceRelations_" + SanitizeMutationName(traitID) + "_" + SanitizeMutationName(tr.Identifier);
public static string GenerateInsertNewMutationName(string traitID) => "insertNew_" + SanitizeMutationName(traitID);
public static string GenerateInsertChangesetDataAsTraitEntityMutationName(string traitID) => "insertChangesetData_" + SanitizeMutationName(traitID);
public static string GenerateUpdateByCIIDMutationName(string traitID) => "updateByCIID_" + SanitizeMutationName(traitID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,48 @@ public void Init(GraphQLMutation tet, TypeContainer typeContainer)
userContext.CommitAndStartNewTransactionIfLastMutationAndNoErrors(context, mc => mc.BuildImmediate());
return t.et;
});


tet.Field<BulkReplaceTraitEntityReturnType>(TraitEntityTypesNameGenerator.GenerateBulkReplaceRelationsMutationName(traitID, tr))
.Arguments(
new QueryArgument<NonNullGraphType<ListGraphType<StringGraphType>>> { Name = "layers" },
new QueryArgument<NonNullGraphType<StringGraphType>> { Name = "writeLayer" },
new QueryArgument<NonNullGraphType<ListGraphType<CIIDAndUpsertRelationsOnlyInputType>>> { Name = "input" }
)
.ResolveAsync(async context =>
{
var layerStrings = context.GetArgument<string[]>("layers")!;
var writeLayerID = context.GetArgument<string>("writeLayer")!;
var input = context.GetArgument<CIIDAndUpsertRelationsOnlyInput[]>("input")!;
var userContext = await context.GetUserContext()
.WithLayersetAsync(async trans => await layerModel.BuildLayerSet(layerStrings, trans), context.Path);
var layerset = userContext.GetLayerSet(context.Path);
var timeThreshold = userContext.GetTimeThreshold(context.Path);
var trans = userContext.Transaction;
// TODO: this could be done faster, because we only need the CIIDs that are TEs, not the TEs themselves at this point
var relevantETs = await elementTypeContainer.TraitEntityModel.GetByCIID(AllCIIDsSelection.Instance, layerset, trans, timeThreshold);
var relevantCIIDs = relevantETs.Keys.ToHashSet();
foreach (var relevantCIID in relevantCIIDs)
if (await authzFilterManager.ApplyPreFilterForMutation(new PreUpsertContextForTraitEntities(relevantCIID, elementTypeContainer.Trait), writeLayerID, userContext, context.Path) is AuthzFilterResultDeny d)
throw new ExecutionError(d.Reason);
var fragments = input.SelectMany(i => i.RelatedCIIDs.Select(rc => new BulkRelationFullFragment(i.BaseCIID, rc, tr.RelationTemplate.PredicateID, false)));
var changed = await elementTypeContainer.TraitEntityModel.BulkReplaceRelationsOnly(fragments, relevantCIIDs, layerset, writeLayerID, userContext.ChangesetProxy, trans, MaskHandlingForRemovalApplyNoMask.Instance);
var changeset = userContext.ChangesetProxy.GetActiveChangeset(writeLayerID);
foreach (var relevantCIID in relevantCIIDs)
if (await authzFilterManager.ApplyPostFilterForMutation(new PostUpsertContextForTraitEntities(relevantCIID, elementTypeContainer.Trait), writeLayerID, userContext, context.Path) is AuthzFilterResultDeny dPost)
throw new ExecutionError(dPost.Reason);
userContext.CommitAndStartNewTransactionIfLastMutationAndNoErrors(context, mc => mc.BuildImmediate());
return new BulkReplaceTraitEntityReturn(changeset, true, !changed);
});

}
}
}
Expand Down
22 changes: 22 additions & 0 deletions backend/Omnikeeper/GraphQL/TraitEntities/TraitEntityInputTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,28 @@ public CIIDAndUpsertAttributesOnlyInputType(UpsertAttributesOnlyInputType attrib
}
}

public class CIIDAndUpsertRelationsOnlyInput
{
public readonly Guid BaseCIID;
public readonly Guid[] RelatedCIIDs;

public CIIDAndUpsertRelationsOnlyInput(Guid baseCIID, Guid[] relatedCIIDs)
{
BaseCIID = baseCIID;
RelatedCIIDs = relatedCIIDs;
}
}
public class CIIDAndUpsertRelationsOnlyInputType : InputObjectGraphType<CIIDAndUpsertRelationsOnlyInput>
{
public CIIDAndUpsertRelationsOnlyInputType()
{
Name = TraitEntityTypesNameGenerator.GenerateUpsertRelationsOnlyTraitEntityInputGraphTypeName();

Field("baseCIID", x => x.BaseCIID);
Field("relatedCIIDs", x => x.RelatedCIIDs);
}
}

public class UpsertInput
{
public readonly (TraitAttribute traitAttribute, IAttributeValue? value)[] AttributeValues;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
using NUnit.Framework;
using Omnikeeper.Base.Entity;
using Omnikeeper.Base.Model;
using Omnikeeper.Base.Utils.ModelContext;
using System.Threading.Tasks;
using Tests.Integration.GraphQL.Base;

namespace Tests.Integration.GraphQL
{
class TraitEntityBulkReplaceRelationsTest : QueryTestBase
{
[Test]
public async Task TestBasics()
{
var userInDatabase = await SetupDefaultUser();
var (layerOkConfig, _) = await GetService<ILayerModel>().CreateLayerIfNotExists("__okconfig", ModelContextBuilder.BuildImmediate());
var (layer1, _) = await GetService<ILayerModel>().CreateLayerIfNotExists("layer_1", ModelContextBuilder.BuildImmediate());
var user = new AuthenticatedInternalUser(userInDatabase);

// force rebuild graphql schema
await ReinitSchema();

string mutationCreateTrait = @"
mutation {
manage_upsertRecursiveTrait(
trait: {
id: ""test_trait_a""
requiredAttributes: [
{
identifier: ""id""
template: {
name: ""test_trait_a.id""
type: INTEGER
isID: false
isArray: false
valueConstraints: []
}
}
{
identifier: ""name""
template: {
name: ""test_trait_a.name""
type: TEXT
isID: false
isArray: false
valueConstraints: []
}
}
]
optionalAttributes: [
{
identifier: ""optional""
template: {
name: ""test_trait_a.optional""
type: INTEGER
isID: false
isArray: false
valueConstraints: []
}
}
]
optionalRelations: [
{
identifier: ""assignments""
template: {
predicateID: ""is_assigned_to""
directionForward: true
traitHints: [""test_trait_a""]
}
}
],
requiredTraits: []
}
) {
id
}
}
";
var expected1 = @"
{
""manage_upsertRecursiveTrait"":
{
""id"": ""test_trait_a""
}
}";
AssertQuerySuccess(mutationCreateTrait, expected1, user);

// force rebuild graphql schema
await ReinitSchema();


// insert base trait entities, without relations
var initialBulkInsert = @"
mutation {
bulkReplace_test_trait_a(
layers: [""layer_1""]
writeLayer: ""layer_1""
input: [{ciid: ""e4125f12-0257-4835-aa25-b8f83a64a38c"", attributes: {id: 1, name: ""testname_a""}},
{ciid: ""eb3772f6-6d3e-426b-86f3-1ff8ba165d0c"", attributes: {id: 2, name: ""testname_b""}},
{ciid: ""fb3772f6-6d3e-426b-86f3-1ff8ba165d0c"", attributes: {id: 3, name: ""testname_c""}}]
) {
isNoOp
}
}
";
AssertQuerySuccess(initialBulkInsert, @"{ ""bulkReplace_test_trait_a"": { ""isNoOp"": false } }", user);


var expectedNoop = @"{ ""bulkReplaceRelations_test_trait_a_assignments"": { ""isNoOp"": true } }";
var expectedOp = @"{ ""bulkReplaceRelations_test_trait_a_assignments"": { ""isNoOp"": false } }";

// first bulk
var mutationBulkReplaceRelations1 = @"
mutation {
bulkReplaceRelations_test_trait_a_assignments(
layers: [""layer_1""]
writeLayer: ""layer_1""
input: [
{baseCIID: ""e4125f12-0257-4835-aa25-b8f83a64a38c"", relatedCIIDs: [""eb3772f6-6d3e-426b-86f3-1ff8ba165d0c""]},
{baseCIID: ""eb3772f6-6d3e-426b-86f3-1ff8ba165d0c"", relatedCIIDs: [""e4125f12-0257-4835-aa25-b8f83a64a38c"", ""fb3772f6-6d3e-426b-86f3-1ff8ba165d0c""]}]
) {
isNoOp
}
}
";
AssertQuerySuccess(mutationBulkReplaceRelations1, expectedOp, user);

// do it again, should return false
AssertQuerySuccess(mutationBulkReplaceRelations1, expectedNoop, user);

// second bulk, with different relations
var mutationBulkReplaceRelations2 = @"
mutation {
bulkReplaceRelations_test_trait_a_assignments(
layers: [""layer_1""]
writeLayer: ""layer_1""
input: [
{baseCIID: ""eb3772f6-6d3e-426b-86f3-1ff8ba165d0c"", relatedCIIDs: [""e4125f12-0257-4835-aa25-b8f83a64a38c""]},
{baseCIID: ""fb3772f6-6d3e-426b-86f3-1ff8ba165d0c"", relatedCIIDs: [""eb3772f6-6d3e-426b-86f3-1ff8ba165d0c"", ""e4125f12-0257-4835-aa25-b8f83a64a38c""]}]
) {
isNoOp
}
}
";
AssertQuerySuccess(mutationBulkReplaceRelations2, expectedOp, user);

// do it again, should return false
AssertQuerySuccess(mutationBulkReplaceRelations2, expectedNoop, user);

// fetch final data
var query = @"
{
traitEntities(layers: [""layer_1""]) {
test_trait_a {
all {
entity {
id
name
optional
assignments { relatedCIID }
}
}
}
}
}
";
var expectedQuery1 = @"
{
""traitEntities"": {
""test_trait_a"": {
""all"": [
{ ""entity"": { ""id"": 1, ""name"": ""testname_a"", ""optional"": null, ""assignments"": [] } },
{ ""entity"": { ""id"": 2, ""name"": ""testname_b"", ""optional"": null, ""assignments"": [{""relatedCIID"": ""e4125f12-0257-4835-aa25-b8f83a64a38c""}] } },
{ ""entity"": { ""id"": 3, ""name"": ""testname_c"", ""optional"": null, ""assignments"": [{""relatedCIID"": ""eb3772f6-6d3e-426b-86f3-1ff8ba165d0c""}, {""relatedCIID"": ""e4125f12-0257-4835-aa25-b8f83a64a38c""}] } }
]
}
}
}
";
AssertQuerySuccess(query, expectedQuery1, user);
}
}
}

0 comments on commit bb08044

Please sign in to comment.