Skip to content

Commit 4c03705

Browse files
authored
Fix default crdt WS doesn't respect order (#2008)
* Fix and standardize ws ordering * Fix MoveWritingSystem_Works
1 parent 7ec05cf commit 4c03705

8 files changed

Lines changed: 115 additions & 98 deletions

File tree

backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -86,28 +86,25 @@ internal int GetWritingSystemHandle(WritingSystemId ws, WritingSystemType? type
8686

8787
public Task<WritingSystems> GetWritingSystems()
8888
{
89-
var currentVernacularWs = WritingSystemContainer
90-
.CurrentVernacularWritingSystems
91-
.Select(ws => ws.Id).ToHashSet();
92-
var currentAnalysisWs = WritingSystemContainer
93-
.CurrentAnalysisWritingSystems
94-
.Select(ws => ws.Id).ToHashSet();
9589
var writingSystems = new WritingSystems
9690
{
9791
Vernacular = WritingSystemContainer.CurrentVernacularWritingSystems.Select((definition, index) =>
98-
FromLcmWritingSystem(definition, index, WritingSystemType.Vernacular)).ToArray(),
92+
FromLcmWritingSystem(definition, WritingSystemType.Vernacular, index)).ToArray(),
9993
Analysis = WritingSystemContainer.CurrentAnalysisWritingSystems.Select((definition, index) =>
100-
FromLcmWritingSystem(definition, index, WritingSystemType.Analysis)).ToArray()
94+
FromLcmWritingSystem(definition, WritingSystemType.Analysis, index)).ToArray()
10195
};
102-
CompleteExemplars(writingSystems);
96+
// Not used and not implemented in CRDT (also not done in GetWritingSystem())
97+
// CompleteExemplars(writingSystems);
10398
return Task.FromResult(writingSystems);
10499
}
105100

106-
private WritingSystem FromLcmWritingSystem(CoreWritingSystemDefinition ws, int index, WritingSystemType type)
101+
private WritingSystem FromLcmWritingSystem(CoreWritingSystemDefinition ws, WritingSystemType type, int index = default)
107102
{
108103
return new WritingSystem
109104
{
110105
Id = Guid.Empty,
106+
// todo: Order probably shouldn't be relied on in fwdata, because it's implicit,
107+
// so it probably shouldn't be used or set at all
111108
Order = index,
112109
Type = type,
113110
//todo determine current and create a property for that.
@@ -119,15 +116,12 @@ private WritingSystem FromLcmWritingSystem(CoreWritingSystemDefinition ws, int i
119116
};
120117
}
121118

122-
public async Task<WritingSystem?> GetWritingSystem(WritingSystemId id, WritingSystemType type)
119+
public Task<WritingSystem?> GetWritingSystem(WritingSystemId id, WritingSystemType type)
123120
{
124-
var writingSystems = await GetWritingSystems();
125-
return type switch
126-
{
127-
WritingSystemType.Vernacular => writingSystems.Vernacular.FirstOrDefault(ws => ws.WsId == id),
128-
WritingSystemType.Analysis => writingSystems.Analysis.FirstOrDefault(ws => ws.WsId == id),
129-
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
130-
};
121+
var lcmWs = Cache.TryGetCoreWritingSystem(id, type);
122+
if (lcmWs is null) return Task.FromResult<WritingSystem?>(null);
123+
var ws = FromLcmWritingSystem(lcmWs, type);
124+
return Task.FromResult<WritingSystem?>(ws);
131125
}
132126

133127
internal void CompleteExemplars(WritingSystems writingSystems)
@@ -188,7 +182,7 @@ await Cache.DoUsingNewOrCurrentUOW("Create Writing System",
188182
WritingSystemType.Vernacular => WritingSystemContainer.CurrentVernacularWritingSystems.Count,
189183
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
190184
} - 1;
191-
return FromLcmWritingSystem(ws, index, type);
185+
return FromLcmWritingSystem(ws, type, index);
192186
}
193187

194188
public async Task<WritingSystem> UpdateWritingSystem(WritingSystemId id, WritingSystemType type, UpdateObjectInput<WritingSystem> update)
@@ -225,10 +219,10 @@ await Cache.DoUsingNewOrCurrentUOW("Update WritingSystem",
225219

226220
public async Task MoveWritingSystem(WritingSystemId id, WritingSystemType type, BetweenPosition<WritingSystemId?> between)
227221
{
228-
var wsToUpdate = GetLexWritingSystem(id, type);
222+
var wsToUpdate = GetNonDefaultLexWritingSystem(id, type);
229223
if (wsToUpdate is null) throw new NullReferenceException($"unable to find writing system with id {id}");
230-
var previousWs = between.Previous is null ? null : GetLexWritingSystem(between.Previous.Value, type);
231-
var nextWs = between.Next is null ? null : GetLexWritingSystem(between.Next.Value, type);
224+
var previousWs = between.Previous is null ? null : GetNonDefaultLexWritingSystem(between.Previous.Value, type);
225+
var nextWs = between.Next is null ? null : GetNonDefaultLexWritingSystem(between.Next.Value, type);
232226
if (nextWs is null && previousWs is null) throw new NullReferenceException($"unable to find writing system with id {between.Previous} or {between.Next}");
233227
await Cache.DoUsingNewOrCurrentUOW("Move WritingSystem",
234228
"Revert Move WritingSystem",
@@ -270,12 +264,10 @@ void MoveWs(CoreWritingSystemDefinition ws,
270264
});
271265
}
272266

273-
private CoreWritingSystemDefinition? GetLexWritingSystem(WritingSystemId id, WritingSystemType type)
267+
private CoreWritingSystemDefinition? GetNonDefaultLexWritingSystem(WritingSystemId id, WritingSystemType type)
274268
{
275-
var exitingWs = type == WritingSystemType.Analysis
276-
? WritingSystemContainer.AnalysisWritingSystems
277-
: WritingSystemContainer.VernacularWritingSystems;
278-
return exitingWs.FirstOrDefault(ws => ws.Id == id);
269+
if (id == default) throw new ArgumentException("Cannot use default writing system ID", nameof(id));
270+
return Cache.TryGetCoreWritingSystem(id, type);
279271
}
280272

281273
public IAsyncEnumerable<PartOfSpeech> GetPartsOfSpeech()

backend/FwLite/FwDataMiniLcmBridge/Api/LcmHelpers.cs

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -166,42 +166,44 @@ internal static WritingSystemId GetWritingSystemId(this LcmCache cache, int ws)
166166
return cache.ServiceLocator.WritingSystemManager.Get(ws).Id;
167167
}
168168

169-
internal static int GetWritingSystemHandle(this LcmCache cache, WritingSystemId ws, WritingSystemType? type = null)
169+
internal static CoreWritingSystemDefinition? TryGetCoreWritingSystem(this LcmCache cache, WritingSystemId wsId, WritingSystemType type)
170170
{
171171
var wsContainer = cache.ServiceLocator.WritingSystems;
172-
if (ws == "default")
172+
var writingSystemsOfType = type switch
173173
{
174-
return type switch
175-
{
176-
WritingSystemType.Analysis => wsContainer.DefaultAnalysisWritingSystem.Handle,
177-
WritingSystemType.Vernacular => wsContainer.DefaultVernacularWritingSystem.Handle,
178-
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
179-
};
180-
}
174+
WritingSystemType.Analysis => wsContainer.AnalysisWritingSystems,
175+
WritingSystemType.Vernacular => wsContainer.VernacularWritingSystems,
176+
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
177+
};
178+
if (wsId == default) return writingSystemsOfType.FirstOrDefault();
179+
return writingSystemsOfType.FirstOrDefault(ws => ws.Id == wsId);
180+
}
181181

182-
if (!cache.ServiceLocator.WritingSystemManager.TryGet(ws.Code, out var lcmWs))
182+
internal static CoreWritingSystemDefinition GetCoreWritingSystem(this LcmCache cache, WritingSystemId wsId, WritingSystemType? type = null)
183+
{
184+
if (type is not null)
183185
{
184-
throw new NullReferenceException($"unable to find writing system with id '{ws.Code}'");
186+
return TryGetCoreWritingSystem(cache, wsId, type.Value)
187+
?? throw new NullReferenceException($"unable to find writing system with id '{wsId.Code}' of type {type}");
185188
}
186-
if (lcmWs is not null && type is not null)
189+
190+
if (wsId == default)
187191
{
188-
var validWs = type switch
189-
{
190-
WritingSystemType.Analysis => wsContainer.AnalysisWritingSystems,
191-
WritingSystemType.Vernacular => wsContainer.VernacularWritingSystems,
192-
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
193-
};
194-
if (!validWs.Contains(lcmWs))
195-
{
196-
throw new InvalidOperationException($"Writing system {ws} is not of the requested type: {type}.");
197-
}
192+
throw new ArgumentException("Cannot look up default writing system ID when type is not specified", nameof(wsId));
198193
}
199-
if (lcmWs is null)
194+
195+
if (!cache.ServiceLocator.WritingSystemManager.TryGet(wsId.Code, out var lcmWs))
200196
{
201-
throw new NullReferenceException($"unable to find writing system with id {ws}");
197+
throw new NullReferenceException($"unable to find writing system with id '{wsId.Code}'");
202198
}
203199

204-
return lcmWs.Handle;
200+
return lcmWs ?? throw new NullReferenceException($"unable to find writing system with id {wsId}");
201+
}
202+
203+
internal static int GetWritingSystemHandle(this LcmCache cache, WritingSystemId wsId, WritingSystemType? type = null)
204+
{
205+
var ws = GetCoreWritingSystem(cache, wsId, type);
206+
return ws.Handle;
205207
}
206208

207209
internal static string PickText(this ICmObject obj, ITsMultiString multiString, string ws)

backend/FwLite/FwLiteProjectSync.Tests/WritingSystemSyncTests.cs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,32 +51,25 @@ public async Task DisposeAsync()
5151
[Fact]
5252
public async Task SyncWs_UpdatesOrder()
5353
{
54-
var en = await _fixture.FwDataApi.GetWritingSystem("en", WritingSystemType.Vernacular);
54+
var fwVernacularWSs = (await _fixture.FwDataApi.GetWritingSystems())!.Vernacular;
55+
var crdtVernacularWSs = (await _fixture.CrdtApi.GetWritingSystems())!.Vernacular;
56+
fwVernacularWSs.Should().HaveCount(2);
57+
crdtVernacularWSs.Should().HaveCount(2);
58+
fwVernacularWSs.Should().BeEquivalentTo(crdtVernacularWSs, options => options.WithStrictOrdering().Excluding(ws => ws.Order));
59+
var en = await _fixture.CrdtApi.GetWritingSystem("en", WritingSystemType.Vernacular);
60+
var fr = await _fixture.CrdtApi.GetWritingSystem("fr", WritingSystemType.Vernacular);
5561
en.Should().NotBeNull();
56-
en.Order.Should().Be(0); // 1st - fw order starts at 0
57-
var fr = await _fixture.FwDataApi.GetWritingSystem("fr", WritingSystemType.Vernacular);
5862
fr.Should().NotBeNull();
59-
fr.Order.Should().Be(1);
60-
var crdtEn = await _fixture.CrdtApi.GetWritingSystem("en", WritingSystemType.Vernacular);
61-
crdtEn.Should().NotBeNull();
62-
crdtEn.Order.Should().Be(1); // 1st - crdt order starts at 1
63-
var crdtFr = await _fixture.CrdtApi.GetWritingSystem("fr", WritingSystemType.Vernacular);
64-
crdtFr.Should().NotBeNull();
65-
crdtFr.Order.Should().Be(2);
66-
63+
fwVernacularWSs.Should().BeEquivalentTo([en, fr], options => options.WithStrictOrdering().Excluding(ws => ws.Order));
6764

6865
// act - move fr before en
6966
await _fixture.FwDataApi.MoveWritingSystem("fr", WritingSystemType.Vernacular, new(null, "en"));
70-
fr = await _fixture.FwDataApi.GetWritingSystem("fr", WritingSystemType.Vernacular);
71-
fr.Should().NotBeNull();
72-
fr.Order.Should().Be(0);
67+
var updatedFwVernacularWSs = (await _fixture.FwDataApi.GetWritingSystems())!.Vernacular;
68+
updatedFwVernacularWSs.Should().BeEquivalentTo([fr, en], options => options.WithStrictOrdering().Excluding(ws => ws.Order));
7369
await _syncService.Sync(_fixture.CrdtApi, _fixture.FwDataApi);
7470

7571
// assert
76-
var updatedCrdtEn = await _fixture.CrdtApi.GetWritingSystem("en", WritingSystemType.Vernacular);
77-
updatedCrdtEn.Should().NotBeNull();
78-
var updatedCrdtFr = await _fixture.CrdtApi.GetWritingSystem("fr", WritingSystemType.Vernacular);
79-
updatedCrdtFr.Should().NotBeNull();
80-
updatedCrdtFr.Order.Should().BeLessThan(updatedCrdtEn.Order);
72+
var updatedCrdtVernacularWSs = (await _fixture.CrdtApi.GetWritingSystems())!.Vernacular;
73+
updatedCrdtVernacularWSs.Should().BeEquivalentTo(updatedFwVernacularWSs, options => options.WithStrictOrdering().Excluding(ws => ws.Order));
8174
}
8275
}

backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,7 @@ private void AssertWritable()
6767
public async Task<WritingSystems> GetWritingSystems()
6868
{
6969
await using var repo = await repoFactory.CreateRepoAsync();
70-
var systems = await repo.WritingSystems
71-
.OrderBy(ws => ws.Order)
72-
.ThenBy(ws => ws.WsId)
73-
.ToArrayAsync();
70+
var systems = await repo.WritingSystemsOrdered.ToArrayAsync();
7471
return new WritingSystems
7572
{
7673
Analysis = [.. systems.Where(ws => ws.Type == WritingSystemType.Analysis)],

backend/FwLite/LcmCrdt/Data/MiniLcmRepository.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
namespace LcmCrdt.Data;
1515

1616
public class MiniLcmRepositoryFactory(
17-
Microsoft.EntityFrameworkCore.IDbContextFactory<LcmCrdtDbContext> dbContextFactory,
17+
IDbContextFactory<LcmCrdtDbContext> dbContextFactory,
1818
IServiceProvider serviceProvider,
1919
EntrySearchServiceFactory? entrySearchServiceFactory = null)
2020
{
@@ -67,6 +67,7 @@ public void Dispose()
6767
public IQueryable<Sense> Senses => dbContext.Senses;
6868
public IQueryable<ExampleSentence> ExampleSentences => dbContext.ExampleSentences;
6969
public IQueryable<WritingSystem> WritingSystems => dbContext.WritingSystems;
70+
public IQueryable<WritingSystem> WritingSystemsOrdered => dbContext.WritingSystemsOrdered;
7071
public IQueryable<SemanticDomain> SemanticDomains => dbContext.SemanticDomains;
7172
public IQueryable<PartOfSpeech> PartsOfSpeech => dbContext.PartsOfSpeech;
7273
public IQueryable<Publication> Publications => dbContext.Publications;
@@ -81,14 +82,14 @@ public void Dispose()
8182
return type switch
8283
{
8384
WritingSystemType.Analysis => _defaultAnalysisWs ??=
84-
await AsyncExtensions.FirstOrDefaultAsync(dbContext.WritingSystems, ws => ws.Type == type),
85+
await AsyncExtensions.FirstOrDefaultAsync(WritingSystemsOrdered, ws => ws.Type == type),
8586
WritingSystemType.Vernacular => _defaultVernacularWs ??=
86-
await AsyncExtensions.FirstOrDefaultAsync(dbContext.WritingSystems, ws => ws.Type == type),
87+
await AsyncExtensions.FirstOrDefaultAsync(WritingSystemsOrdered, ws => ws.Type == type),
8788
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
8889
} ?? throw new NullReferenceException($"Unable to find a default writing system of type {type}");
8990
}
9091

91-
return await AsyncExtensions.FirstOrDefaultAsync(dbContext.WritingSystems, ws => ws.WsId == id && ws.Type == type);
92+
return await AsyncExtensions.FirstOrDefaultAsync(WritingSystemsOrdered, ws => ws.WsId == id && ws.Type == type);
9293
}
9394

9495
public async Task<AddEntryComponentChange> CreateComplexFormComponentChange(

backend/FwLite/LcmCrdt/FullTextSearch/EntrySearchService.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System.Globalization;
2-
using System.Linq.Expressions;
31
using System.Text;
42
using LcmCrdt.Data;
53
using LinqToDB;
@@ -139,7 +137,7 @@ public async Task UpdateEntrySearchTable(Guid entryId)
139137

140138
public async Task UpdateEntrySearchTable(Entry entry)
141139
{
142-
var writingSystems = await dbContext.WritingSystems.OrderBy(ws => ws.Order).ToArrayAsync();
140+
var writingSystems = await dbContext.WritingSystemsOrdered.ToArrayAsync();
143141
var record = ToEntrySearchRecord(entry, writingSystems);
144142
await InsertOrUpdateEntrySearchRecord(record, EntrySearchRecordsTable);
145143
}
@@ -181,7 +179,12 @@ public static async Task UpdateEntrySearchTable(IEnumerable<Entry> entries,
181179
..dbContext.WritingSystems,
182180
..newWritingSystems
183181
];
184-
Array.Sort(writingSystems, (ws1, ws2) => ws1.Order.CompareTo(ws2.Order));
182+
Array.Sort(writingSystems, (ws1, ws2) =>
183+
{
184+
var orderComparison = ws1.Order.CompareTo(ws2.Order);
185+
if (orderComparison != 0) return orderComparison;
186+
return ws1.Id.CompareTo(ws2.Id);
187+
});
185188
var entrySearchRecordsTable = dbContext.GetTable<EntrySearchRecord>();
186189
var searchRecords = entries.Select(entry => ToEntrySearchRecord(entry, writingSystems));
187190
foreach (var entrySearchRecord in searchRecords)
@@ -200,7 +203,7 @@ public async Task RegenerateEntrySearchTable()
200203
await using var transaction = await dbContext.Database.BeginTransactionAsync();
201204
await EntrySearchRecordsTable.TruncateAsync();
202205

203-
var writingSystems = await dbContext.WritingSystems.OrderBy(ws => ws.Order).ToArrayAsync();
206+
var writingSystems = await dbContext.WritingSystemsOrdered.ToArrayAsync();
204207
await EntrySearchRecordsTable
205208
.BulkCopyAsync(dbContext.Set<Entry>()
206209
.LoadWith(e => e.Senses)

backend/FwLite/LcmCrdt/LcmCrdtDbContext.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Text.Json;
2-
using LcmCrdt.Data;
32
using LcmCrdt.FullTextSearch;
43
using SIL.Harmony;
54
using SIL.Harmony.Db;
@@ -17,6 +16,8 @@ IOptions<CrdtConfig> options
1716
{
1817
public DbSet<ProjectData> ProjectData => Set<ProjectData>();
1918
public IQueryable<WritingSystem> WritingSystems => Set<WritingSystem>().AsNoTracking();
19+
public IQueryable<WritingSystem> WritingSystemsOrdered => Set<WritingSystem>().AsNoTracking()
20+
.OrderBy(ws => ws.Order).ThenBy(ws => ws.Id);
2021
public IQueryable<Entry> Entries => Set<Entry>().AsNoTracking();
2122
public IQueryable<ComplexFormComponent> ComplexFormComponents => Set<ComplexFormComponent>().AsNoTracking();
2223
public IQueryable<ComplexFormType> ComplexFormTypes => Set<ComplexFormType>().AsNoTracking();

0 commit comments

Comments
 (0)