Skip to content

Commit 461a998

Browse files
committed
Various and sundry performance fixes
1 parent 6ae4ca6 commit 461a998

5 files changed

Lines changed: 81 additions & 33 deletions

File tree

src/Figment.Common/Schema.cs

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ You should have received a copy of the GNU Affero General Public License
1616
along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19+
using System.Collections.Frozen;
1920
using System.Runtime.CompilerServices;
2021
using Figment.Common.Data;
2122

@@ -74,12 +75,13 @@ public Schema(string guid, string name)
7475
public string? Description { get; set; }
7576

7677
/// <summary>
77-
/// Gets the list of fields, keyed by name, defined for this schema.
78+
/// Gets or sets the list of fields, keyed by name, defined for this schema.
7879
/// </summary>
7980
/// <remarks>
80-
/// Do not use this outside of this class. Left public for serialization only.
81+
/// After loading, this is a <see cref="FrozenDictionary{TKey, TValue}"/> for optimized lookups.
82+
/// Use <see cref="SetProperty"/> and <see cref="RemoveProperty"/> for mutations.
8183
/// </remarks>
82-
public Dictionary<string, SchemaFieldBase> Properties { get; init; } = [];
84+
public IReadOnlyDictionary<string, SchemaFieldBase> Properties { get; set; } = new Dictionary<string, SchemaFieldBase>();
8385

8486
/// <summary>
8587
/// Gets or sets the versioning plan for this schema, if the schema is versioned.
@@ -201,7 +203,7 @@ public SchemaTextField AddTextField(string name, ushort? minLength = null, ushor
201203
MaxLength = maxLength,
202204
Pattern = pattern,
203205
};
204-
Properties.Add(name, stf);
206+
SetProperty(name, stf);
205207
return stf;
206208
}
207209

@@ -223,7 +225,7 @@ public SchemaDateField AddDateField(string name)
223225
}
224226

225227
var sdf = new SchemaDateField(name);
226-
Properties.Add(name, sdf);
228+
SetProperty(name, sdf);
227229
return sdf;
228230
}
229231

@@ -244,9 +246,9 @@ public SchemaIncrementField AddIncrementField(string name)
244246
throw new ArgumentException($"A field named '{name}' already exists on this schema", nameof(name));
245247
}
246248

247-
var sdf = new SchemaIncrementField(name);
248-
Properties.Add(name, sdf);
249-
return sdf;
249+
var sif = new SchemaIncrementField(name);
250+
SetProperty(name, sif);
251+
return sif;
250252
}
251253

252254
/// <summary>
@@ -267,10 +269,48 @@ public SchemaMonthDayField AddMonthDayField(string name)
267269
}
268270

269271
var smdf = new SchemaMonthDayField(name);
270-
Properties.Add(name, smdf);
272+
SetProperty(name, smdf);
271273
return smdf;
272274
}
273275

276+
/// <summary>
277+
/// Adds or replaces a property field on this schema.
278+
/// </summary>
279+
/// <param name="name">The name of the property.</param>
280+
/// <param name="field">The schema field definition.</param>
281+
public void SetProperty(string name, SchemaFieldBase field)
282+
{
283+
var mutable = Properties as Dictionary<string, SchemaFieldBase>
284+
?? new Dictionary<string, SchemaFieldBase>(Properties, StringComparer.Ordinal);
285+
mutable[name] = field;
286+
Properties = mutable;
287+
}
288+
289+
/// <summary>
290+
/// Removes a property field from this schema.
291+
/// </summary>
292+
/// <param name="name">The name of the property to remove.</param>
293+
/// <returns>True if the property was found and removed; otherwise, false.</returns>
294+
public bool RemoveProperty(string name)
295+
{
296+
var mutable = Properties as Dictionary<string, SchemaFieldBase>
297+
?? new Dictionary<string, SchemaFieldBase>(Properties, StringComparer.Ordinal);
298+
var removed = mutable.Remove(name);
299+
Properties = mutable;
300+
return removed;
301+
}
302+
303+
/// <summary>
304+
/// Freezes the <see cref="Properties"/> dictionary for optimized read performance.
305+
/// </summary>
306+
public void FreezeProperties()
307+
{
308+
if (Properties is not FrozenDictionary<string, SchemaFieldBase>)
309+
{
310+
Properties = Properties.ToFrozenDictionary(StringComparer.Ordinal);
311+
}
312+
}
313+
274314
/// <summary>
275315
/// Attempts to find a schema by <paramref name="guidOrNamePart"/>.
276316
/// </summary>

src/Figment.Data.Local/JsonSchemaDefinition.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ You should have received a copy of the GNU Affero General Public License
1616
along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19+
using System.Collections.Frozen;
1920
using System.Text.Json.Serialization;
2021
using Figment.Common;
2122

@@ -165,12 +166,14 @@ public Schema ToSchema(DateTime? createdOn = null, DateTime? lastModified = null
165166
LastAccessed = lastAccessed ?? DateTime.UnixEpoch,
166167
};
167168

169+
var mutableProps = new Dictionary<string, SchemaFieldBase>(StringComparer.Ordinal);
168170
foreach (var prop in Properties)
169171
{
170172
prop.Value.Required = RequiredProperties?.Any(sdr => string.Equals(sdr, prop.Key, StringComparison.Ordinal)) == true;
171-
schema.Properties.Add(prop.Key, prop.Value);
173+
mutableProps.Add(prop.Key, prop.Value);
172174
}
173175

176+
schema.Properties = mutableProps.ToFrozenDictionary(StringComparer.Ordinal);
174177
schema.ImportMaps.AddRange(ImportMaps);
175178

176179
return schema;

src/Figment.Data.Local/LocalDirectoryThingStorageProvider.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ You should have received a copy of the GNU Affero General Public License
1616
along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19+
using System.Collections.Frozen;
1920
using System.Runtime.CompilerServices;
2021
using System.Text.Json;
2122
using Figment.Common;
@@ -30,6 +31,13 @@ namespace Figment.Data.Local;
3031
/// <param name="ThingDirectoryPath">The path to the <see cref="Thing"/> subdirectory under the root of the file system database.</param>
3132
public class LocalDirectoryThingStorageProvider(string ThingDirectoryPath) : ThingStorageProviderBase, IThingStorageProvider
3233
{
34+
private static readonly FrozenSet<string> BuiltInPropertyNames = FrozenSet.ToFrozenSet(
35+
[
36+
nameof(Thing.Name),
37+
nameof(Thing.Guid),
38+
nameof(Thing.SchemaGuids),
39+
], StringComparer.Ordinal);
40+
3341
private const string NameIndexFileName = $"_thing.names.csv";
3442

3543
/// <inheritdoc/>
@@ -214,10 +222,10 @@ public override Task<bool> GuidExists(string thingGuid, CancellationToken _)
214222

215223
var fileName = $"{thingGuid}.thing.json";
216224
var filePath = Path.Combine(ThingDirectoryPath, fileName);
217-
if (!File.Exists(filePath))
225+
var fileInfo = new FileInfo(filePath);
226+
if (!fileInfo.Exists)
218227
return Task.FromResult(false);
219228

220-
var fileInfo = new FileInfo(filePath);
221229
if (fileInfo.Length == 0)
222230
{
223231
try
@@ -336,10 +344,7 @@ public override Task<bool> GuidExists(string thingGuid, CancellationToken _)
336344
if (cancellationToken.IsCancellationRequested)
337345
return null;
338346

339-
if (
340-
string.Equals(prop.Name, nameof(Thing.Name), StringComparison.Ordinal)
341-
|| string.Equals(prop.Name, nameof(Thing.Guid), StringComparison.Ordinal)
342-
|| string.Equals(prop.Name, nameof(Thing.SchemaGuids), StringComparison.Ordinal))
347+
if (BuiltInPropertyNames.Contains(prop.Name))
343348
{
344349
// Ignore built-ins, as they're defined on root, not Properties
345350
continue;

src/jot/Commands/Schemas/SetSchemaPropertyFormulaCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public override async Task<int> ExecuteAsync(CommandContext context, SetSchemaPr
7474
}
7575

7676
scf.Formula = settings.Formula;
77-
schema.Properties[propName] = scf;
77+
schema.SetProperty(propName, scf);
7878

7979
var (saved, saveMessage) = await schema.SaveAsync(cancellationToken);
8080
if (!saved)

src/jot/Commands/Schemas/SetSchemaPropertyTypeCommand.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public override async Task<int> ExecuteAsync(CommandContext context, SetSchemaPr
6161
return (int)Globals.GLOBAL_ERROR_CODES.NOT_FOUND;
6262
}
6363

64-
schema.Properties.Remove(propToDelete.Key);
64+
schema.RemoveProperty(propToDelete.Key);
6565
AmbientErrorContext.Provider.LogWarning($"Deleted property name '{propName}'.");
6666
}
6767
else if (string.Equals(settings.FieldType, SchemaArrayField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
@@ -74,81 +74,81 @@ public override async Task<int> ExecuteAsync(CommandContext context, SetSchemaPr
7474
Type = "string",
7575
},
7676
};
77-
schema!.Properties[propName] = saf;
77+
schema!.SetProperty(propName, saf);
7878
}
7979
else if (string.Equals(settings.FieldType, SchemaBooleanField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
8080
{
8181
// Boolean
8282
var sbf = new SchemaBooleanField(propName);
83-
schema!.Properties[propName] = sbf;
83+
schema!.SetProperty(propName, sbf);
8484
}
8585
else if (string.Equals(settings.FieldType, SchemaCalculatedField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
8686
{
8787
// Calculated
8888
var scf = new SchemaCalculatedField(propName);
89-
schema!.Properties[propName] = scf;
89+
schema!.SetProperty(propName, scf);
9090

9191
// Formula is null at this point.
9292
}
9393
else if (string.Equals(settings.FieldType, SchemaDateField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
9494
{
9595
// Date
9696
var sdf = new SchemaDateField(propName);
97-
schema!.Properties[propName] = sdf;
97+
schema!.SetProperty(propName, sdf);
9898
}
9999
else if (string.Equals(settings.FieldType, SchemaEmailField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
100100
{
101101
// Email
102102
var sef = new SchemaEmailField(propName);
103-
schema!.Properties[propName] = sef;
103+
schema!.SetProperty(propName, sef);
104104
}
105105
else if (string.Equals(settings.FieldType, SchemaIncrementField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
106106
{
107107
// Auto-incrementing id (increment)
108108
var sif = new SchemaIncrementField(propName);
109-
schema!.Properties[propName] = sif;
109+
schema!.SetProperty(propName, sif);
110110
}
111111
else if (string.Equals(settings.FieldType, SchemaIntegerField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
112112
{
113113
// Number (integer)
114114
var sif = new SchemaIntegerField(propName);
115-
schema!.Properties[propName] = sif;
115+
schema!.SetProperty(propName, sif);
116116
}
117117
else if (string.Equals(settings.FieldType, SchemaMonthDayField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
118118
{
119119
// Month+day
120120
var ssf = new SchemaMonthDayField(propName);
121-
schema!.Properties[propName] = ssf;
121+
schema!.SetProperty(propName, ssf);
122122
}
123123
else if (string.Equals(settings.FieldType, SchemaNumberField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
124124
{
125125
// Number (double)
126126
var snf = new SchemaNumberField(propName);
127-
schema!.Properties[propName] = snf;
127+
schema!.SetProperty(propName, snf);
128128
}
129129
else if (string.Equals(settings.FieldType, SchemaPhoneField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
130130
{
131131
// Phone
132132
var spf = new SchemaPhoneField(propName);
133-
schema!.Properties[propName] = spf;
133+
schema!.SetProperty(propName, spf);
134134
}
135135
else if (string.Equals(settings.FieldType, SchemaSchemaField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
136136
{
137137
// Schema
138138
var ssf = new SchemaSchemaField(propName);
139-
schema!.Properties[propName] = ssf;
139+
schema!.SetProperty(propName, ssf);
140140
}
141141
else if (string.Equals(settings.FieldType, "text", StringComparison.Ordinal))
142142
{
143143
// Text
144144
var stf = new SchemaTextField(propName);
145-
schema!.Properties[propName] = stf;
145+
schema!.SetProperty(propName, stf);
146146
}
147147
else if (string.Equals(settings.FieldType, SchemaUriField.SCHEMA_FIELD_TYPE, StringComparison.Ordinal))
148148
{
149149
// Uri
150150
var suf = new SchemaUriField(propName);
151-
schema!.Properties[propName] = suf;
151+
schema!.SetProperty(propName, suf);
152152
}
153153
else if (settings.FieldType != null
154154
&& settings.FieldType.StartsWith('[')
@@ -159,7 +159,7 @@ public override async Task<int> ExecuteAsync(CommandContext context, SetSchemaPr
159159
// Enum
160160
var enumValues = settings.FieldType[1..^1].Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
161161
var sef = new SchemaEnumField(propName, enumValues);
162-
schema!.Properties[propName] = sef;
162+
schema!.SetProperty(propName, sef);
163163
}
164164
else
165165
{
@@ -172,7 +172,7 @@ public override async Task<int> ExecuteAsync(CommandContext context, SetSchemaPr
172172
}
173173

174174
var srf = new SchemaRefField(propName, refSchema.Guid);
175-
schema!.Properties[propName] = srf;
175+
schema!.SetProperty(propName, srf);
176176
}
177177

178178
var (saved, saveMessage) = await schema!.SaveAsync(cancellationToken);

0 commit comments

Comments
 (0)