Skip to content

Commit

Permalink
Fix for #564 - make sure parallel corpora exist before updating
Browse files Browse the repository at this point in the history
  • Loading branch information
johnml1135 committed Dec 11, 2024
1 parent 779cb72 commit f8e6250
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 16 deletions.
3 changes: 2 additions & 1 deletion src/DataAccess/src/SIL.DataAccess/IUpdateBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ IUpdateBuilder<T> SetAll<TItem, TField>(
Expression<Func<T, IEnumerable<TItem>?>> collectionField,
Expression<Func<TItem, TField>> itemField,
TField value,
Expression<Func<TItem, bool>>? predicate = null
Expression<Func<TItem, bool>>? predicate = null,
Expression<Func<T, object>>? setIfFieldExists = null
);
}
4 changes: 3 additions & 1 deletion src/DataAccess/src/SIL.DataAccess/MemoryUpdateBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,11 @@ public IUpdateBuilder<T> SetAll<TItem, TField>(
Expression<Func<T, IEnumerable<TItem>?>> collectionField,
Expression<Func<TItem, TField>> itemField,
TField value,
Expression<Func<TItem, bool>>? predicate = null
Expression<Func<TItem, bool>>? predicate = null,
Expression<Func<T, object>>? setIfFieldExists = null
)
{
// TODO check for setIfFieldExists
(IEnumerable<object> owners, PropertyInfo? prop, object? index) = GetFieldOwners(
_entity,
_filter,
Expand Down
20 changes: 14 additions & 6 deletions src/DataAccess/src/SIL.DataAccess/MongoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ await _collection
var updateBuilder = new MongoUpdateBuilder<T>();
update(updateBuilder);
updateBuilder.Inc(e => e.Revision, 1);
(UpdateDefinition<T> updateDef, IReadOnlyList<ArrayFilterDefinition> arrayFilters) = updateBuilder.Build();
(
FilterDefinition<T> filterDef,
UpdateDefinition<T> updateDef,
IReadOnlyList<ArrayFilterDefinition> arrayFilters
) = updateBuilder.Build(filter);
var options = new FindOneAndUpdateOptions<T>
{
IsUpsert = upsert,
Expand All @@ -136,13 +140,13 @@ await _collection
if (_context.Session is not null)
{
entity = await _collection
.FindOneAndUpdateAsync(_context.Session, filter, updateDef, options, cancellationToken)
.FindOneAndUpdateAsync(_context.Session, filterDef, updateDef, options, cancellationToken)
.ConfigureAwait(false);
}
else
{
entity = await _collection
.FindOneAndUpdateAsync(filter, updateDef, options, cancellationToken)
.FindOneAndUpdateAsync(filterDef, updateDef, options, cancellationToken)
.ConfigureAwait(false);
}
}
Expand All @@ -162,7 +166,11 @@ public async Task<int> UpdateAllAsync(
var updateBuilder = new MongoUpdateBuilder<T>();
update(updateBuilder);
updateBuilder.Inc(e => e.Revision, 1);
(UpdateDefinition<T> updateDef, IReadOnlyList<ArrayFilterDefinition> arrayFilters) = updateBuilder.Build();
(
FilterDefinition<T> filterDef,
UpdateDefinition<T> updateDef,
IReadOnlyList<ArrayFilterDefinition> arrayFilters
) = updateBuilder.Build(filter);
UpdateOptions? updateOptions = null;
if (arrayFilters.Count > 0)
updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
Expand All @@ -172,13 +180,13 @@ public async Task<int> UpdateAllAsync(
if (_context.Session is not null)
{
result = await _collection
.UpdateManyAsync(_context.Session, filter, updateDef, updateOptions, cancellationToken)
.UpdateManyAsync(_context.Session, filterDef, updateDef, updateOptions, cancellationToken)
.ConfigureAwait(false);
}
else
{
result = await _collection
.UpdateManyAsync(filter, updateDef, updateOptions, cancellationToken)
.UpdateManyAsync(filterDef, updateDef, updateOptions, cancellationToken)
.ConfigureAwait(false);
}
}
Expand Down
25 changes: 21 additions & 4 deletions src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ public class MongoUpdateBuilder<T> : IUpdateBuilder<T>
where T : IEntity
{
private readonly UpdateDefinitionBuilder<T> _builder;
private readonly FilterDefinitionBuilder<T> _filterBuilder;
private readonly List<UpdateDefinition<T>> _defs;
private FilterDefinition<T>? _existsFilter = null;
private readonly Dictionary<BsonDocument, (string Id, ArrayFilterDefinition<BsonValue> FilterDef)> _arrayFilters;

public MongoUpdateBuilder()
{
_builder = Builders<T>.Update;
_filterBuilder = Builders<T>.Filter;
_defs = new List<UpdateDefinition<T>>();
_arrayFilters = new Dictionary<BsonDocument, (string, ArrayFilterDefinition<BsonValue>)>();
}
Expand Down Expand Up @@ -63,13 +66,19 @@ public IUpdateBuilder<T> SetAll<TItem, TField>(
Expression<Func<T, IEnumerable<TItem>?>> collectionField,
Expression<Func<TItem, TField>> itemField,
TField value,
Expression<Func<TItem, bool>>? predicate = null
Expression<Func<TItem, bool>>? predicate = null,
Expression<Func<T, object>>? setIfFieldExists = null
)
{
Expression<Func<T, TItem>> itemExpr = ExpressionHelper.Concatenate(
collectionField,
(collection) => ((IReadOnlyList<TItem>?)collection)![ArrayPosition.ArrayFilter]
);
FieldDefinition<T, TItem> itemFieldDef = ToFieldDefinition(itemExpr);
if (setIfFieldExists == null)
{
_existsFilter = _filterBuilder.Exists(setIfFieldExists, true);

Check failure on line 80 in src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs

View workflow job for this annotation

GitHub Actions / NUnit Tests

Serval.ApiServer.TranslationEngineTests ► DataFileUpdate_Propagated

Failed test found in: src/Serval/test/Serval.ApiServer.IntegrationTests/TestResults/test-results.trx Error: System.ArgumentNullException : Value cannot be null. (Parameter 'expression')
Raw output
System.ArgumentNullException : Value cannot be null. (Parameter 'expression')
   at MongoDB.Driver.Core.Misc.Ensure.IsNotNull[T](T value, String paramName)
   at MongoDB.Driver.ExpressionFieldDefinition`1..ctor(LambdaExpression expression)
   at MongoDB.Driver.FilterDefinitionBuilder`1.Exists(Expression`1 field, Boolean exists)
   at SIL.DataAccess.MongoUpdateBuilder`1.SetAll[TItem,TField](Expression`1 collectionField, Expression`1 itemField, TField value, Expression`1 predicate, Expression`1 setIfFieldExists) in /home/runner/work/serval/serval/src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs:line 80
   at Serval.Translation.Services.EngineService.<>c__DisplayClass28_0.<UpdateDataFileFilenameFilesAsync>b__1(IUpdateBuilder`1 u) in /home/runner/work/serval/serval/src/Serval/src/Serval.Translation/Services/EngineService.cs:line 603
   at SIL.DataAccess.MongoRepository`1.UpdateAllAsync(Expression`1 filter, Action`1 update, CancellationToken cancellationToken) in /home/runner/work/serval/serval/src/DataAccess/src/SIL.DataAccess/MongoRepository.cs:line 167
   at Serval.Translation.Consumers.DataFileUpdatedConsumer.Consume(ConsumeContext`1 context) in /home/runner/work/serval/serval/src/Serval/src/Serval.Translation/Consumers/DataFileUpdatedConsumer.cs:line 9
   at MassTransit.DependencyInjection.ScopeConsumerFactory`1.Send[TMessage](ConsumeContext`1 context, IPipe`1 next) in /_/src/MassTransit/DependencyInjection/DependencyInjection/ScopeConsumerFactory.cs:line 22
   at MassTransit.DependencyInjection.ScopeConsumerFactory`1.Send[TMessage](ConsumeContext`1 context, IPipe`1 next) in /_/src/MassTransit/DependencyInjection/DependencyInjection/ScopeConsumerFactory.cs:line 22
   at MassTransit.Middleware.ConsumerMessageFilter`2.MassTransit.IFilter<MassTransit.ConsumeContext<TMessage>>.Send(ConsumeContext`1 context, IPipe`1 next) in /_/src/MassTransit/Middleware/ConsumerMessageFilter.cs:line 48
   at MassTransit.Middleware.ConsumerMessageFilter`2.MassTransit.IFilter<MassTransit.ConsumeContext<TMessage>>.Send(ConsumeContext`1 context, IPipe`1 next) in /_/src/MassTransit/Middleware/ConsumerMessageFilter.cs:line 73
   at MassTransit.Middleware.TeeFilter`1.<>c__DisplayClass5_0.<<Send>g__SendAsync|1>d.MoveNext() in /_/src/MassTransit/Middleware/TeeFilter.cs:line 40
--- End of stack trace from previous location ---
   at MassTransit.Middleware.ConsumeContextOutputMessageTypeFilter`1.SendToOutput(IPipe`1 next, ConsumeContext`1 pipeContext) in /_/src/MassTransit/Middleware/ConsumeContextOutputMessageTypeFilter.cs:line 76
   at MassTransit.Middleware.ConsumeContextOutputMessageTypeFilter`1.SendToOutput(IPipe`1 next, ConsumeContext`1 pipeContext) in /_/src/MassTransit/Middleware/ConsumeContextOutputMessageTypeFilter.cs:line 108
   at MassTransit.Middleware.ConsumeContextMessageTypeFilter.<>c__DisplayClass8_0.<<Send>g__SendAsync|0>d.MoveNext() in /_/src/MassTransit/Middleware/MessageTypeFilter.cs:line 76
--- End of stack trace from previous location ---
   at MassTransit.Middleware.DeserializeFilter.Send(ReceiveContext context, IPipe`1 next) in /_/src/MassTransit/Middleware/DeserializeFilter.cs:line 40
   at MassTransit.Middleware.RescueFilter`2.MassTransit.IFilter<TContext>.Send(TContext context, IPipe`1 next) in /_/src/MassTransit/Middleware/RescueFilter.cs:line 43
   at MassTransit.Internals.ExceptionExtensions.Rethrow(Exception exception) in /_/src/MassTransit.Abstractions/Internals/Extensions/ExceptionExtensions.cs:line 15
   at MassTransit.Middleware.RethrowErrorTransportFilter.Send(ExceptionReceiveContext context, IPipe`1 next) in /_/src/MassTransit/Middleware/RethrowErrorTransportFilter.cs:line 14
   at MassTransit.Middleware.RescueFilter`2.MassTransit.IFilter<TContext>.Send(TContext context, IPipe`1 next) in /_/src/MassTransit/Middleware/RescueFilter.cs:line 61
   at MassTransit.Middleware.DeadLetterFilter.MassTransit.IFilter<MassTransit.ReceiveContext>.Send(ReceiveContext context, IPipe`1 next) in /_/src/MassTransit/Middleware/DeadLetterFilter.cs:line 32
   at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock) in /_/src/MassTransit/Transports/ReceivePipeDispatcher.cs:line 65
   at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock) in /_/src/MassTransit/Transports/ReceivePipeDispatcher.cs:line 108
   at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock) in /_/src/MassTransit/Transports/ReceivePipeDispatcher.cs:line 115
   at MassTransit.Mediator.Contexts.MediatorSendEndpoint.SendMessage[T](T message, IPipe`1 pipe, CancellationToken cancellationToken) in /_/src/MassTransit/Mediator/Contexts/MediatorSendEndpoint.cs:line 222
   at MassTransit.Mediator.Contexts.MediatorSendEndpoint.SendMessage[T](T message, IPipe`1 pipe, CancellationToken cancellationToken) in /_/src/MassTransit/Mediator/Contexts/MediatorSendEndpoint.cs:line 232
   at Serval.DataFiles.Services.DataFileService.<>c__DisplayClass9_0.<<UpdateAsync>b__0>d.MoveNext() in /home/runner/work/serval/serval/src/Serval/src/Serval.DataFiles/Services/DataFileService.cs:line 92
--- End of stack trace from previous location ---
   at SIL.DataAccess.MongoDataAccessContext.<>c__DisplayClass9_0.<<WithTransactionAsync>b__0>d.MoveNext() in /home/runner/work/serval/serval/src/DataAccess/src/SIL.DataAccess/MongoDataAccessContext.cs:line 33
--- End of stack trace from previous location ---
   at SIL.DataAccess.MongoDataAccessContext.<>c__DisplayClass8_0`1.<<WithTransactionAsync>b__0>d.MoveNext() in /home/runner/work/serval/serval/src/DataAccess/src/SIL.DataAccess/MongoDataAccessContext.cs:line 19
--- End of stack trace from previous location ---
   at MongoDB.Driver.TransactionExecutor.ExecuteCallbackAsync[TResult](IClientSessionHandle clientSession, Func`3 callbackAsync, DateTime startTime, IClock clock, CancellationToken cancellationToken)
   at MongoDB.Driver.TransactionExecutor.ExecuteCallbackAsync[TResult](IClientSessionHandle clientSession, Func`3 callbackAsync, DateTime startTime, IClock clock, CancellationToken cancellationToken)
   at MongoDB.Driver.TransactionExecutor.ExecuteWithRetriesAsync[TResult](IClientSessionHandle clientSession, Func`3 callbackAsync, TransactionOptions transactionOptions, IClock clock, CancellationToken cancellationToken)
   at SIL.DataAccess.MongoDataAccessContext.WithTransactionAsync[TResult](Func`2 callbackAsync, CancellationToken cancellationToken) in /home/runner/work/serval/serval/src/DataAccess/src/SIL.DataAccess/MongoDataAccessContext.cs:line 17
   at Serval.DataFiles.Services.DataFileService.UpdateAsync(String id, Stream stream, CancellationToken cancellationToken) in /home/runner/work/serval/serval/src/Serval/src/Serval.DataFiles/Services/DataFileService.cs:line 72
   at Serval.DataFiles.Controllers.DataFilesController.UpdateAsync(String id, IFormFile file, CancellationToken cancellationToken) in /home/runner/work/serval/serval/src/Serval/src/Serval.DataFiles/Controllers/DataFilesController.cs:line 187
   at lambda_method1064(Closure, Object)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ExceptionContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Http.Timeouts.RequestTimeoutsMiddleware.<>c__DisplayClass5_0.<<Invoke>g__SetTimeoutAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at SIL.ServiceToolkit.Services.BugsnagMiddleware.Invoke(HttpContext context, IClient client) in /home/runner/work/serval/serval/src/ServiceToolkit/src/SIL.ServiceToolkit/Services/BugsnagMiddleware.cs:line 35
   at Microsoft.AspNetCore.TestHost.HttpContextBuilder.<>c__DisplayClass23_0.<<SendAsync>g__RunRequestAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.ClientHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Testing.Handlers.CookieContainerHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Testing.Handlers.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Serval.Client.DataFilesClient.UpdateAsync(String id, FileParameter file, CancellationToken cancellationToken) in /home/runner/work/serval/serval/src/Serval/src/Serval.Client/Client.g.cs:line 1733
   at Serval.ApiServer.TranslationEngineTests.DataFileUpdate_Propagated() in /home/runner/work/serval/serval/src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs:line 2051
   at NUnit.Framework.Internal.AsyncToSyncAdapter.Await[TResult](Func`1 invoke)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod(TestExecutionContext context)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)
   at NUnit.Framework.Internal.Commands.DelegatingTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)
}
Expression<Func<T, TField>> fieldExpr = ExpressionHelper.Concatenate(itemExpr, itemField);
if (predicate != null)
{
Expand Down Expand Up @@ -107,12 +116,20 @@ public IUpdateBuilder<T> SetAll<TItem, TField>(
return this;
}

public (UpdateDefinition<T>, IReadOnlyList<ArrayFilterDefinition>) Build()
public (FilterDefinition<T>, UpdateDefinition<T>, IReadOnlyList<ArrayFilterDefinition>) Build(
Expression<Func<T, bool>> filter
)
{
ArrayFilterDefinition[] arrayFilters = _arrayFilters.Values.Select(f => f.FilterDef).ToArray();
FilterDefinition<T> outFilter;
if (_existsFilter != null)
outFilter = _filterBuilder.And(_existsFilter, filter);
else
outFilter = filter;

if (_defs.Count == 1)
return (_defs.Single(), arrayFilters);
return (_builder.Combine(_defs), arrayFilters);
return (outFilter, _defs.Single(), arrayFilters);
return (outFilter, _builder.Combine(_defs), arrayFilters);
}

private static FieldDefinition<T, TField> ToFieldDefinition<TField>(
Expand Down
12 changes: 8 additions & 4 deletions src/Serval/src/Serval.Translation/Services/EngineService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -616,13 +616,15 @@ public Task UpdateDataFileFilenameFilesAsync(
e => e.ParallelCorpora[ArrayPosition.All].SourceCorpora[ArrayPosition.All].Files,
f => f.Filename,
filename,
f => f.Id == dataFileId
f => f.Id == dataFileId,
f => f.ParallelCorpora
);
u.SetAll(
e => e.ParallelCorpora[ArrayPosition.All].TargetCorpora[ArrayPosition.All].Files,
f => f.Filename,
filename,
f => f.Id == dataFileId
f => f.Id == dataFileId,
f => f.ParallelCorpora
);
},
cancellationToken: cancellationToken
Expand All @@ -646,13 +648,15 @@ public Task UpdateCorpusFilesAsync(
e => e.ParallelCorpora[ArrayPosition.All].SourceCorpora,
mc => mc.Files,
files,
mc => mc.Id == corpusId
f => f.Id == corpusId,
f => f.ParallelCorpora
);
u.SetAll(
e => e.ParallelCorpora[ArrayPosition.All].TargetCorpora,
mc => mc.Files,
files,
mc => mc.Id == corpusId
f => f.Id == corpusId,
f => f.ParallelCorpora
);
},
cancellationToken: cancellationToken
Expand Down

0 comments on commit f8e6250

Please sign in to comment.