• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loresoft / SchemaSaurus / 25437460438

06 May 2026 01:13PM UTC coverage: 87.024% (-0.09%) from 87.11%
25437460438

push

github

pwelter34
Replace SchemaQualifiedName with QualifiedName

1076 of 1424 branches covered (75.56%)

Branch coverage included in aggregate %.

71 of 95 new or added lines in 34 files covered. (74.74%)

5691 of 6352 relevant lines covered (89.59%)

223.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.44
/src/SchemaSaurus.Metadata/DatabaseModelExtensions.cs
1
using System.Text.Json;
2

3
namespace SchemaSaurus.Metadata;
4

5
/// <summary>
6
/// Extension methods for <see cref="DatabaseModel"/>.
7
/// </summary>
8
/// <remarks>
9
/// Provides three capability groups:
10
/// <list type="bullet">
11
///   <item><description>Lookup — case-insensitive find methods for every metadata object type.</description></item>
12
///   <item><description>Serialization — <see cref="ToJson"/> / <see cref="FromJson"/> round-tripping via <see cref="System.Text.Json"/>.</description></item>
13
///   <item><description>Wiring — <see cref="ResolveReferences"/> to populate back-reference and resolved-column properties after construction or deserialization.</description></item>
14
/// </list>
15
/// </remarks>
16
public static class DatabaseModelExtensions
17
{
18
    /// <summary>
19
    /// Finds a table by schema and name using <see cref="StringComparison.OrdinalIgnoreCase"/>.
20
    /// </summary>
21
    /// <param name="model">The database model to search.</param>
22
    /// <param name="schema">Schema name to match, or <see langword="null"/> to match any schema.</param>
23
    /// <param name="name">Table name to match.</param>
24
    /// <returns>The matching <see cref="Table"/>, or <see langword="null"/> if not found.</returns>
25
    public static Table? FindTable(this DatabaseModel model, string? schema, string name)
26
    {
27
        return model.Tables.FirstOrDefault(t =>
439✔
28
            (schema is null || string.Equals(t.QualifiedName.Schema, schema, StringComparison.OrdinalIgnoreCase)) &&
439✔
29
            string.Equals(t.QualifiedName.Name, name, StringComparison.OrdinalIgnoreCase));
439✔
30
    }
31

32
    /// <summary>
33
    /// Finds a table by <see cref="SchemaQualifiedName"/>.
34
    /// </summary>
35
    /// <param name="model">The database model to search.</param>
36
    /// <param name="name">Schema-qualified name to match.</param>
37
    /// <returns>The matching <see cref="Table"/>, or <see langword="null"/> if not found.</returns>
38
    public static Table? FindTable(this DatabaseModel model, SchemaQualifiedName name)
39
        => model.FindTable(name.Schema, name.Name);
437✔
40

41
    /// <summary>
42
    /// Finds a table by name only, matching any schema.
43
    /// Convenience overload for schema-less providers (e.g., SQLite) or when the
44
    /// schema is not known.
45
    /// </summary>
46
    /// <param name="model">The database model to search.</param>
47
    /// <param name="name">Table name to match.</param>
48
    /// <returns>The matching <see cref="Table"/>, or <see langword="null"/> if not found.</returns>
49
    public static Table? FindTable(this DatabaseModel model, string name)
50
        => model.FindTable(null, name);
×
51

52
    /// <summary>
53
    /// Finds a view by schema and name using <see cref="StringComparison.OrdinalIgnoreCase"/>.
54
    /// </summary>
55
    /// <param name="model">The database model to search.</param>
56
    /// <param name="schema">Schema name to match, or <see langword="null"/> to match any schema.</param>
57
    /// <param name="name">View name to match.</param>
58
    /// <returns>The matching <see cref="View"/>, or <see langword="null"/> if not found.</returns>
59
    public static View? FindView(this DatabaseModel model, string? schema, string name)
60
    {
61
        return model.Views.FirstOrDefault(v =>
1✔
62
            (schema is null || string.Equals(v.QualifiedName.Schema, schema, StringComparison.OrdinalIgnoreCase)) &&
1✔
63
            string.Equals(v.QualifiedName.Name, name, StringComparison.OrdinalIgnoreCase));
1✔
64
    }
65

66
    /// <summary>
67
    /// Finds a view by <see cref="SchemaQualifiedName"/>.
68
    /// </summary>
69
    /// <param name="model">The database model to search.</param>
70
    /// <param name="name">Schema-qualified name to match.</param>
71
    /// <returns>The matching <see cref="View"/>, or <see langword="null"/> if not found.</returns>
72
    public static View? FindView(this DatabaseModel model, SchemaQualifiedName name)
73
        => model.FindView(name.Schema, name.Name);
×
74

75
    /// <summary>
76
    /// Finds a view by name only, matching any schema.
77
    /// Convenience overload for schema-less providers (e.g., SQLite) or when the
78
    /// schema is not known.
79
    /// </summary>
80
    /// <param name="model">The database model to search.</param>
81
    /// <param name="name">View name to match.</param>
82
    /// <returns>The matching <see cref="View"/>, or <see langword="null"/> if not found.</returns>
83
    public static View? FindView(this DatabaseModel model, string name)
84
        => model.FindView(null, name);
×
85

86
    /// <summary>
87
    /// Finds a sequence by schema and name using <see cref="StringComparison.OrdinalIgnoreCase"/>.
88
    /// </summary>
89
    /// <param name="model">The database model to search.</param>
90
    /// <param name="schema">Schema name to match, or <see langword="null"/> to match any schema.</param>
91
    /// <param name="name">Sequence name to match.</param>
92
    /// <returns>The matching <see cref="Sequence"/>, or <see langword="null"/> if not found.</returns>
93
    public static Sequence? FindSequence(this DatabaseModel model, string? schema, string name)
94
    {
95
        return model.Sequences.FirstOrDefault(s =>
×
NEW
96
            (schema is null || string.Equals(s.QualifiedName.Schema, schema, StringComparison.OrdinalIgnoreCase)) &&
×
NEW
97
            string.Equals(s.QualifiedName.Name, name, StringComparison.OrdinalIgnoreCase));
×
98
    }
99

100
    /// <summary>
101
    /// Finds a sequence by <see cref="SchemaQualifiedName"/>.
102
    /// </summary>
103
    /// <param name="model">The database model to search.</param>
104
    /// <param name="name">Schema-qualified name to match.</param>
105
    /// <returns>The matching <see cref="Sequence"/>, or <see langword="null"/> if not found.</returns>
106
    public static Sequence? FindSequence(this DatabaseModel model, SchemaQualifiedName name)
107
        => model.FindSequence(name.Schema, name.Name);
×
108

109
    /// <summary>
110
    /// Finds a sequence by name only, matching any schema.
111
    /// Convenience overload for schema-less providers (e.g., SQLite) or when the
112
    /// schema is not known.
113
    /// </summary>
114
    /// <param name="model">The database model to search.</param>
115
    /// <param name="name">Sequence name to match.</param>
116
    /// <returns>The matching <see cref="Sequence"/>, or <see langword="null"/> if not found.</returns>
117
    public static Sequence? FindSequence(this DatabaseModel model, string name)
118
        => model.FindSequence(null, name);
×
119

120
    /// <summary>
121
    /// Finds a stored procedure by schema and name using <see cref="StringComparison.OrdinalIgnoreCase"/>.
122
    /// </summary>
123
    /// <param name="model">The database model to search.</param>
124
    /// <param name="schema">Schema name to match, or <see langword="null"/> to match any schema.</param>
125
    /// <param name="name">Stored procedure name to match.</param>
126
    /// <returns>The matching <see cref="StoredProcedure"/>, or <see langword="null"/> if not found.</returns>
127
    public static StoredProcedure? FindStoredProcedure(this DatabaseModel model, string? schema, string name)
128
    {
129
        return model.StoredProcedures.FirstOrDefault(sp =>
×
NEW
130
            (schema is null || string.Equals(sp.QualifiedName.Schema, schema, StringComparison.OrdinalIgnoreCase)) &&
×
NEW
131
            string.Equals(sp.QualifiedName.Name, name, StringComparison.OrdinalIgnoreCase));
×
132
    }
133

134
    /// <summary>
135
    /// Finds a stored procedure by <see cref="SchemaQualifiedName"/>.
136
    /// </summary>
137
    /// <param name="model">The database model to search.</param>
138
    /// <param name="name">Schema-qualified name to match.</param>
139
    /// <returns>The matching <see cref="StoredProcedure"/>, or <see langword="null"/> if not found.</returns>
140
    public static StoredProcedure? FindStoredProcedure(this DatabaseModel model, SchemaQualifiedName name)
141
        => model.FindStoredProcedure(name.Schema, name.Name);
×
142

143
    /// <summary>
144
    /// Finds a stored procedure by name only, matching any schema.
145
    /// Convenience overload for schema-less providers (e.g., SQLite) or when the
146
    /// schema is not known.
147
    /// </summary>
148
    /// <param name="model">The database model to search.</param>
149
    /// <param name="name">Stored procedure name to match.</param>
150
    /// <returns>The matching <see cref="StoredProcedure"/>, or <see langword="null"/> if not found.</returns>
151
    public static StoredProcedure? FindStoredProcedure(this DatabaseModel model, string name)
152
        => model.FindStoredProcedure(null, name);
×
153

154
    /// <summary>
155
    /// Finds a scalar function by schema and name using <see cref="StringComparison.OrdinalIgnoreCase"/>.
156
    /// </summary>
157
    /// <param name="model">The database model to search.</param>
158
    /// <param name="schema">Schema name to match, or <see langword="null"/> to match any schema.</param>
159
    /// <param name="name">Function name to match.</param>
160
    /// <returns>The matching <see cref="ScalarFunction"/>, or <see langword="null"/> if not found.</returns>
161
    public static ScalarFunction? FindScalarFunction(this DatabaseModel model, string? schema, string name)
162
    {
163
        return model.ScalarFunctions.FirstOrDefault(f =>
×
NEW
164
            (schema is null || string.Equals(f.QualifiedName.Schema, schema, StringComparison.OrdinalIgnoreCase)) &&
×
NEW
165
            string.Equals(f.QualifiedName.Name, name, StringComparison.OrdinalIgnoreCase));
×
166
    }
167

168
    /// <summary>
169
    /// Finds a scalar function by <see cref="SchemaQualifiedName"/>.
170
    /// </summary>
171
    /// <param name="model">The database model to search.</param>
172
    /// <param name="name">Schema-qualified name to match.</param>
173
    /// <returns>The matching <see cref="ScalarFunction"/>, or <see langword="null"/> if not found.</returns>
174
    public static ScalarFunction? FindScalarFunction(this DatabaseModel model, SchemaQualifiedName name)
175
        => model.FindScalarFunction(name.Schema, name.Name);
×
176

177
    /// <summary>
178
    /// Finds a scalar function by name only, matching any schema.
179
    /// Convenience overload for schema-less providers (e.g., SQLite) or when the
180
    /// schema is not known.
181
    /// </summary>
182
    /// <param name="model">The database model to search.</param>
183
    /// <param name="name">Function name to match.</param>
184
    /// <returns>The matching <see cref="ScalarFunction"/>, or <see langword="null"/> if not found.</returns>
185
    public static ScalarFunction? FindScalarFunction(this DatabaseModel model, string name)
186
        => model.FindScalarFunction(null, name);
×
187

188
    /// <summary>
189
    /// Finds a table-valued function by schema and name using <see cref="StringComparison.OrdinalIgnoreCase"/>.
190
    /// </summary>
191
    /// <param name="model">The database model to search.</param>
192
    /// <param name="schema">Schema name to match, or <see langword="null"/> to match any schema.</param>
193
    /// <param name="name">Function name to match.</param>
194
    /// <returns>The matching <see cref="TableValuedFunction"/>, or <see langword="null"/> if not found.</returns>
195
    public static TableValuedFunction? FindTableValuedFunction(this DatabaseModel model, string? schema, string name)
196
    {
197
        return model.TableValuedFunctions.FirstOrDefault(f =>
×
NEW
198
            (schema is null || string.Equals(f.QualifiedName.Schema, schema, StringComparison.OrdinalIgnoreCase)) &&
×
NEW
199
            string.Equals(f.QualifiedName.Name, name, StringComparison.OrdinalIgnoreCase));
×
200
    }
201

202
    /// <summary>
203
    /// Finds a table-valued function by <see cref="SchemaQualifiedName"/>.
204
    /// </summary>
205
    /// <param name="model">The database model to search.</param>
206
    /// <param name="name">Schema-qualified name to match.</param>
207
    /// <returns>The matching <see cref="TableValuedFunction"/>, or <see langword="null"/> if not found.</returns>
208
    public static TableValuedFunction? FindTableValuedFunction(this DatabaseModel model, SchemaQualifiedName name)
209
        => model.FindTableValuedFunction(name.Schema, name.Name);
×
210

211
    /// <summary>
212
    /// Finds a table-valued function by name only, matching any schema.
213
    /// Convenience overload for schema-less providers (e.g., SQLite) or when the
214
    /// schema is not known.
215
    /// </summary>
216
    /// <param name="model">The database model to search.</param>
217
    /// <param name="name">Function name to match.</param>
218
    /// <returns>The matching <see cref="TableValuedFunction"/>, or <see langword="null"/> if not found.</returns>
219
    public static TableValuedFunction? FindTableValuedFunction(this DatabaseModel model, string name)
220
        => model.FindTableValuedFunction(null, name);
×
221

222
    /// <summary>
223
    /// Finds a user-defined type by schema and name using <see cref="StringComparison.OrdinalIgnoreCase"/>.
224
    /// </summary>
225
    /// <param name="model">The database model to search.</param>
226
    /// <param name="schema">Schema name to match, or <see langword="null"/> to match any schema.</param>
227
    /// <param name="name">Type name to match.</param>
228
    /// <returns>The matching <see cref="UserDefinedType"/>, or <see langword="null"/> if not found.</returns>
229
    public static UserDefinedType? FindUserDefinedType(this DatabaseModel model, string? schema, string name)
230
    {
231
        return model.UserDefinedTypes.FirstOrDefault(t =>
×
NEW
232
            (schema is null || string.Equals(t.QualifiedName.Schema, schema, StringComparison.OrdinalIgnoreCase)) &&
×
NEW
233
            string.Equals(t.QualifiedName.Name, name, StringComparison.OrdinalIgnoreCase));
×
234
    }
235

236
    /// <summary>
237
    /// Finds a user-defined type by <see cref="SchemaQualifiedName"/>.
238
    /// </summary>
239
    /// <param name="model">The database model to search.</param>
240
    /// <param name="name">Schema-qualified name to match.</param>
241
    /// <returns>The matching <see cref="UserDefinedType"/>, or <see langword="null"/> if not found.</returns>
242
    public static UserDefinedType? FindUserDefinedType(this DatabaseModel model, SchemaQualifiedName name)
243
        => model.FindUserDefinedType(name.Schema, name.Name);
×
244

245
    /// <summary>
246
    /// Finds a user-defined type by name only, matching any schema.
247
    /// Convenience overload for schema-less providers (e.g., SQLite) or when the
248
    /// schema is not known.
249
    /// </summary>
250
    /// <param name="model">The database model to search.</param>
251
    /// <param name="name">Type name to match.</param>
252
    /// <returns>The matching <see cref="UserDefinedType"/>, or <see langword="null"/> if not found.</returns>
253
    public static UserDefinedType? FindUserDefinedType(this DatabaseModel model, string name)
254
        => model.FindUserDefinedType(null, name);
×
255

256

257
    /// <summary>
258
    /// Wires all parent back-references and resolved column references throughout the
259
    /// object graph so that navigation properties like <see cref="RelationBase.Database"/>,
260
    /// <see cref="Column.Parent"/>, and <see cref="ColumnReference.Column"/> are usable.
261
    /// </summary>
262
    /// <remarks>
263
    /// Called automatically by <see cref="Builders.DatabaseModelBuilder.Build"/> and by
264
    /// <see cref="FromJson"/> after deserialization. Safe to call multiple times; each
265
    /// invocation overwrites previously wired references.
266
    /// </remarks>
267
    /// <param name="model">The database model whose back-references should be wired.</param>
268
    public static void ResolveReferences(this DatabaseModel model)
269
    {
270
        foreach (var table in model.Tables ?? [])
1,698✔
271
        {
272
            table.Database = model;
794✔
273
            WireRelationBase(table);
794✔
274
            WireTable(model, table);
794✔
275
        }
276

277
        foreach (var view in model.Views ?? [])
204✔
278
        {
279
            view.Database = model;
47✔
280
            WireRelationBase(view);
47✔
281
        }
282
    }
55✔
283

284
    private static void WireRelationBase(RelationBase relation)
285
    {
286
        foreach (var column in relation.Columns ?? [])
14,652!
287
            column.Parent = relation;
6,485✔
288

289
        foreach (var index in relation.Indexes ?? [])
2,312✔
290
        {
291
            foreach (var col in index.Columns ?? [])
1,412!
292
                ResolveColumnRef(col, relation);
391✔
293
        }
294
    }
841✔
295

296
    private static void WireTable(DatabaseModel model, Table table)
297
    {
298
        if (table.PrimaryKey is { } pk)
794✔
299
        {
300
            foreach (var col in pk.Columns ?? [])
3,152!
301
                ResolveColumnRef(col, table);
836✔
302
        }
303

304
        foreach (var uc in table.UniqueConstraints ?? [])
1,636✔
305
        {
306
            foreach (var col in uc.Columns ?? [])
96!
307
                ResolveColumnRef(col, table);
24✔
308
        }
309

310
        foreach (var fk in table.ForeignKeys ?? [])
2,462✔
311
        {
312
            fk.DependentTable = table;
437✔
313

314
            var principalTable = model.FindTable(fk.PrincipalTableName);
437✔
315
            if (principalTable is not null)
437✔
316
            {
317
                fk.PrincipalTable = principalTable;
437✔
318
                foreach (var mapping in fk.ColumnMappings ?? [])
1,792!
319
                {
320
                    mapping.DependentColumn = table.Columns
459✔
321
                        .FirstOrDefault(c => string.Equals(
459✔
322
                            c.Name, mapping.DependentColumnName,
459✔
323
                            StringComparison.OrdinalIgnoreCase))!;
459✔
324

325
                    mapping.PrincipalColumn = principalTable.Columns
459✔
326
                        .FirstOrDefault(c => string.Equals(
459✔
327
                            c.Name, mapping.PrincipalColumnName,
459✔
328
                            StringComparison.OrdinalIgnoreCase))!;
459✔
329
                }
330
            }
331
        }
332
    }
794✔
333

334
    private static void ResolveColumnRef(ColumnReference colRef, RelationBase relation)
335
    {
336
        colRef.Column = relation.Columns
1,251✔
337
            .FirstOrDefault(c => string.Equals(
1,251✔
338
                c.Name, colRef.ColumnName,
1,251✔
339
                StringComparison.OrdinalIgnoreCase))!;
1,251✔
340
    }
1,251✔
341

342

343
    /// <summary>
344
    /// Serializes a <see cref="DatabaseModel"/> to a JSON string using the pre-configured
345
    /// <see cref="MetadataJsonContext"/> converter set.
346
    /// </summary>
347
    /// <param name="model">The database model to serialize.</param>
348
    /// <returns>A JSON string representing the full metadata snapshot.</returns>
349
    public static string ToJson(this DatabaseModel model)
350
        => JsonSerializer.Serialize(model, MetadataJsonContext.JsonSerializerOptions.Value);
19✔
351

352
    /// <summary>
353
    /// Deserializes a <see cref="DatabaseModel"/> from a JSON string produced by
354
    /// <see cref="ToJson"/>.
355
    /// </summary>
356
    /// <remarks>
357
    /// Automatically calls <see cref="ResolveReferences"/> after deserialization so
358
    /// that all navigation properties are fully populated before the model is returned.
359
    /// </remarks>
360
    /// <param name="json">The JSON string to deserialize.</param>
361
    /// <returns>A fully navigable <see cref="DatabaseModel"/> instance with all
362
    /// back-references wired.</returns>
363
    /// <exception cref="InvalidOperationException">
364
    /// Thrown when deserialization produces a <see langword="null"/> result.
365
    /// </exception>
366
    public static DatabaseModel FromJson(string json)
367
    {
368
        var model = JsonSerializer.Deserialize<DatabaseModel>(json, MetadataJsonContext.JsonSerializerOptions.Value)
4!
369
            ?? throw new InvalidOperationException(
4✔
370
                "Deserialization of DatabaseModel returned null. " +
4✔
371
                "Ensure the JSON represents a valid non-null object.");
4✔
372

373
        model.ResolveReferences();
4✔
374
        return model;
4✔
375
    }
376
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc