Skip to content

Commit 4ca0467

Browse files
committed
Refactor metadata providers for table and user-defined types
1 parent 0b57852 commit 4ca0467

7 files changed

Lines changed: 194 additions & 43 deletions

src/SpocRVNext/SnapshotBuilder/CHECKLIST.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@
8080
- Sollten wir die DataTypes bereits auflösen oder einfach nur referenzieren und im Output erst auflösen (Vor/Nachteile ...)? _Entscheidung: Snapshots referenzieren Schemanamen/Typnamen, die eigentliche Auflösung passiert erst in den Konsumenten (auch für JSON/No-JSON Procs, Functions, Views, Tables)._
8181
- Legacy Output Anbindung. _Entscheidung: Output-Bridge bereitstellen, die neue Artefakte auf das bisherige Layout mappt, bis alle Downstream-Abhängigkeiten auf vNext migriert sind._
8282

83-
## Austeshende Optimierungen
83+
## Ausstehende Optimierungen
8484

85-
- [~] debug\.spocr\schema redundant minimieren
85+
- [~] debug\.spocr\schema redundanz minimieren
8686
- [x] MaxLength/Precision/Scale bei TypeRef → UDTT oder konstanten sys-Typen unterdrücken.
8787
- [x] `IsNullable`-Spiegelung zum zugrunde liegenden TypeRef auflösen (nur Fälle mit abweichender Semantik beibehalten).
8888
- [x] Weitere sys-Typen mit festen Längen prüfen und ggf. streichen.
@@ -97,8 +97,7 @@
9797
- [x] C:\Projekte\GitHub\spocr\src\Services\SchemaSnapshotService.cs:ResolveLegacySchemaDir(): Kommentar ergänzt – Pfad bleibt für deterministische Artefakte/Legacy-Fallback erhalten.
9898
- [x] debug\.spocr\schema\procedures\workflow-state.TransitionFindAsJson.json `SqlTypeName` scheint redundant zu sein, können wir die Property komplett entfernen, da alles über `TypeRef` ableitbar? [x] leeres `"Json": {},` vermeiden.
9999
- [x] debug\.spocr\schema\procedures\workflow.NodeListAsJson.json: "FunctionRef": "identity.RecordAsJson" dürfte kein Array sein, da die referenzierte Funktion kein Array liefert. Siehe debug\[workflow]_[NodeListAsJson].sql und debug\[identity]_[RecordAsJson].sql (benötigen wir hier die `Json` Property überhaupt, wenn wir `FunctionRef` haben? Oder kann diese Eigenschaft in anderen Fällen abweichen?)
100-
- [ ] debug\.spocr\schema\procedures\workflow.NodeFindAsJson.json: `"FunctionRef": "workflow.StatusFindAsJson"` existiert noch nicht in debug\.spocr\schema\functions
101-
- [ ] src\SpocRVNext\SnapshotBuilder\Metadata hier wurden bereits einige Queries hin verschoben. Es fehlen aber m.E. noch die Queries der Types und StoreProcedures (weitere?) oder ist bereits alles im neuen SpocRVNext vorhanden?
100+
- [x] src\SpocRVNext\SnapshotBuilder\Metadata: TableType- und UDT-Queries als Provider ausgekoppelt (`DatabaseTableTypeMetadataProvider`, `DatabaseUserDefinedTypeMetadataProvider`); StoredProcedures laufen weiterhin über Collector/Analyzer.
102101

103102
## Artefakte
104103

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using SpocR.DataContext;
7+
using SpocR.DataContext.Models;
8+
using SpocR.DataContext.Queries;
9+
using SpocR.Services;
10+
11+
namespace SpocR.SpocRVNext.SnapshotBuilder.Metadata;
12+
13+
internal sealed class DatabaseTableTypeMetadataProvider : ITableTypeMetadataProvider
14+
{
15+
private readonly DbContext _dbContext;
16+
private readonly IConsoleService _console;
17+
18+
public DatabaseTableTypeMetadataProvider(DbContext dbContext, IConsoleService console)
19+
{
20+
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
21+
_console = console ?? throw new ArgumentNullException(nameof(console));
22+
}
23+
24+
public async Task<IReadOnlyList<TableTypeMetadata>> GetTableTypesAsync(ISet<string> schemas, CancellationToken cancellationToken)
25+
{
26+
if (schemas == null || schemas.Count == 0)
27+
{
28+
return Array.Empty<TableTypeMetadata>();
29+
}
30+
31+
var escapedSchemas = schemas
32+
.Where(static s => !string.IsNullOrWhiteSpace(s))
33+
.Select(static s => $"'{s.Replace("'", "''")}'")
34+
.ToArray();
35+
36+
if (escapedSchemas.Length == 0)
37+
{
38+
return Array.Empty<TableTypeMetadata>();
39+
}
40+
41+
var schemaListString = string.Join(',', escapedSchemas);
42+
List<TableType> tableTypes;
43+
try
44+
{
45+
var list = await _dbContext.TableTypeListAsync(schemaListString, cancellationToken).ConfigureAwait(false);
46+
tableTypes = list ?? new List<TableType>();
47+
}
48+
catch (Exception ex)
49+
{
50+
_console.Verbose($"[snapshot-tabletype] failed to enumerate table types: {ex.Message}");
51+
return Array.Empty<TableTypeMetadata>();
52+
}
53+
54+
if (tableTypes.Count == 0)
55+
{
56+
return Array.Empty<TableTypeMetadata>();
57+
}
58+
59+
var results = new List<TableTypeMetadata>(tableTypes.Count);
60+
foreach (var tableType in tableTypes)
61+
{
62+
cancellationToken.ThrowIfCancellationRequested();
63+
if (tableType == null || string.IsNullOrWhiteSpace(tableType.SchemaName) || string.IsNullOrWhiteSpace(tableType.Name))
64+
{
65+
continue;
66+
}
67+
68+
List<Column> columns = new();
69+
if (tableType.UserTypeId.HasValue)
70+
{
71+
try
72+
{
73+
var list = await _dbContext.TableTypeColumnListAsync(tableType.UserTypeId.Value, cancellationToken).ConfigureAwait(false);
74+
if (list != null)
75+
{
76+
columns = list;
77+
}
78+
}
79+
catch (Exception ex)
80+
{
81+
_console.Verbose($"[snapshot-tabletype] failed to load columns for {tableType.SchemaName}.{tableType.Name}: {ex.Message}");
82+
}
83+
}
84+
85+
results.Add(new TableTypeMetadata(tableType, columns));
86+
}
87+
88+
return results;
89+
}
90+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using SpocR.DataContext;
7+
using SpocR.DataContext.Queries;
8+
using SpocR.Services;
9+
10+
namespace SpocR.SpocRVNext.SnapshotBuilder.Metadata;
11+
12+
internal sealed class DatabaseUserDefinedTypeMetadataProvider : IUserDefinedTypeMetadataProvider
13+
{
14+
private readonly DbContext _dbContext;
15+
private readonly IConsoleService _console;
16+
17+
public DatabaseUserDefinedTypeMetadataProvider(DbContext dbContext, IConsoleService console)
18+
{
19+
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
20+
_console = console ?? throw new ArgumentNullException(nameof(console));
21+
}
22+
23+
public async Task<IReadOnlyList<UserDefinedTypeRow>> GetUserDefinedTypesAsync(ISet<string> schemas, CancellationToken cancellationToken)
24+
{
25+
try
26+
{
27+
var list = await _dbContext.UserDefinedScalarTypesAsync(cancellationToken).ConfigureAwait(false);
28+
if (list == null || list.Count == 0)
29+
{
30+
return Array.Empty<UserDefinedTypeRow>();
31+
}
32+
33+
if (schemas == null || schemas.Count == 0)
34+
{
35+
return list;
36+
}
37+
38+
return list
39+
.Where(row => row != null && !string.IsNullOrWhiteSpace(row.schema_name) && schemas.Contains(row.schema_name))
40+
.ToList();
41+
}
42+
catch (Exception ex)
43+
{
44+
_console.Verbose($"[snapshot-udt] failed to enumerate user-defined types: {ex.Message}");
45+
return Array.Empty<UserDefinedTypeRow>();
46+
}
47+
}
48+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Collections.Generic;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using SpocR.DataContext.Models;
5+
6+
namespace SpocR.SpocRVNext.SnapshotBuilder.Metadata;
7+
8+
internal interface ITableTypeMetadataProvider
9+
{
10+
Task<IReadOnlyList<TableTypeMetadata>> GetTableTypesAsync(ISet<string> schemas, CancellationToken cancellationToken);
11+
}
12+
13+
internal sealed record TableTypeMetadata(TableType TableType, IReadOnlyList<Column> Columns);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using SpocR.DataContext.Queries;
5+
6+
namespace SpocR.SpocRVNext.SnapshotBuilder.Metadata;
7+
8+
internal interface IUserDefinedTypeMetadataProvider
9+
{
10+
Task<IReadOnlyList<UserDefinedTypeRow>> GetUserDefinedTypesAsync(ISet<string> schemas, CancellationToken cancellationToken);
11+
}

src/SpocRVNext/SnapshotBuilder/SnapshotBuilderServiceCollectionExtensions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@ public static IServiceCollection AddSnapshotBuilder(this IServiceCollection serv
1616
{
1717
services.AddSingleton<IDependencyMetadataProvider, DatabaseDependencyMetadataProvider>();
1818
services.AddSingleton<IFunctionJsonMetadataProvider, DatabaseFunctionJsonMetadataProvider>();
19+
services.AddSingleton<ITableTypeMetadataProvider, DatabaseTableTypeMetadataProvider>();
20+
services.AddSingleton<IUserDefinedTypeMetadataProvider, DatabaseUserDefinedTypeMetadataProvider>();
1921
services.AddSingleton<IProcedureCollector, DatabaseProcedureCollector>();
2022
services.AddSingleton<IProcedureAnalyzer, DatabaseProcedureAnalyzer>();
2123
services.AddSingleton<ISnapshotWriter>(provider =>
2224
{
2325
var console = provider.GetRequiredService<IConsoleService>();
2426
var dbContext = provider.GetRequiredService<DbContext>();
2527
var legacySnapshotService = provider.GetService<ISchemaSnapshotService>();
26-
return new ExpandedSnapshotWriter(console, dbContext, legacySnapshotService);
28+
var tableTypeMetadataProvider = provider.GetRequiredService<ITableTypeMetadataProvider>();
29+
var userDefinedTypeMetadataProvider = provider.GetRequiredService<IUserDefinedTypeMetadataProvider>();
30+
return new ExpandedSnapshotWriter(console, dbContext, legacySnapshotService, tableTypeMetadataProvider, userDefinedTypeMetadataProvider);
2731
});
2832
services.AddSingleton<ISnapshotCache, FileSnapshotCache>();
2933
services.AddSingleton<ISnapshotDiagnostics, ConsoleSnapshotDiagnostics>();

src/SpocRVNext/SnapshotBuilder/Writers/ExpandedSnapshotWriter.cs

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using SpocR.DataContext.Queries;
1515
using SpocR.Models;
1616
using SpocR.Services;
17+
using SpocR.SpocRVNext.SnapshotBuilder.Metadata;
1718
using SpocR.SpocRVNext.SnapshotBuilder.Models;
1819
using SpocR.SpocRVNext.Utils;
1920

@@ -27,16 +28,25 @@ internal sealed class ExpandedSnapshotWriter : ISnapshotWriter
2728
private readonly IConsoleService _console;
2829
private readonly DbContext _dbContext;
2930
private readonly ISchemaSnapshotService? _legacySnapshotService;
31+
private readonly ITableTypeMetadataProvider _tableTypeMetadataProvider;
32+
private readonly IUserDefinedTypeMetadataProvider _userDefinedTypeMetadataProvider;
3033
private static readonly JsonSerializerOptions IndexSerializerOptions = new()
3134
{
3235
WriteIndented = true
3336
};
3437

35-
public ExpandedSnapshotWriter(IConsoleService console, DbContext dbContext, ISchemaSnapshotService? legacySnapshotService)
38+
public ExpandedSnapshotWriter(
39+
IConsoleService console,
40+
DbContext dbContext,
41+
ISchemaSnapshotService? legacySnapshotService,
42+
ITableTypeMetadataProvider tableTypeMetadataProvider,
43+
IUserDefinedTypeMetadataProvider userDefinedTypeMetadataProvider)
3644
{
3745
_console = console ?? throw new ArgumentNullException(nameof(console));
3846
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
3947
_legacySnapshotService = legacySnapshotService;
48+
_tableTypeMetadataProvider = tableTypeMetadataProvider ?? throw new ArgumentNullException(nameof(tableTypeMetadataProvider));
49+
_userDefinedTypeMetadataProvider = userDefinedTypeMetadataProvider ?? throw new ArgumentNullException(nameof(userDefinedTypeMetadataProvider));
4050
}
4151

4252
public async Task<SnapshotWriteResult> WriteAsync(IReadOnlyList<ProcedureAnalysisResult> analyzedProcedures, SnapshotBuildOptions options, CancellationToken cancellationToken)
@@ -340,54 +350,36 @@ private async Task<SchemaArtifactSummary> WriteSchemaArtifactsAsync(string schem
340350
summary.Functions.AddRange(functionSummary.Functions);
341351
}
342352

343-
var escapedSchemas = schemaSet
344-
.Select(s => $"'{s.Replace("'", "''")}'")
345-
.ToArray();
346-
var schemaListString = string.Join(',', escapedSchemas);
347-
348-
List<TableType> tableTypes = new();
353+
IReadOnlyList<TableTypeMetadata> tableTypes = Array.Empty<TableTypeMetadata>();
349354
try
350355
{
351-
var list = await _dbContext.TableTypeListAsync(schemaListString, cancellationToken).ConfigureAwait(false);
352-
if (list != null)
353-
{
354-
tableTypes = list;
355-
}
356+
tableTypes = await _tableTypeMetadataProvider.GetTableTypesAsync(schemaSet, cancellationToken).ConfigureAwait(false);
356357
}
357358
catch (Exception ex)
358359
{
359-
_console.Verbose($"[snapshot-tabletype] failed to enumerate table types: {ex.Message}");
360+
_console.Verbose($"[snapshot-tabletype] metadata provider failed: {ex.Message}");
360361
}
361362

362363
var tableTypeRoot = Path.Combine(schemaRoot, "tabletypes");
363364
Directory.CreateDirectory(tableTypeRoot);
364365
var validTableTypeFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
365366

366-
foreach (var tableType in tableTypes)
367+
foreach (var tableTypeMetadata in tableTypes)
367368
{
368369
cancellationToken.ThrowIfCancellationRequested();
369-
if (tableType == null || string.IsNullOrWhiteSpace(tableType.SchemaName) || string.IsNullOrWhiteSpace(tableType.Name))
370+
if (tableTypeMetadata == null)
370371
{
371372
continue;
372373
}
373374

374-
List<Column> columns = new();
375-
if (tableType.UserTypeId.HasValue)
375+
var tableType = tableTypeMetadata.TableType;
376+
if (tableType == null || string.IsNullOrWhiteSpace(tableType.SchemaName) || string.IsNullOrWhiteSpace(tableType.Name))
376377
{
377-
try
378-
{
379-
var columnList = await _dbContext.TableTypeColumnListAsync(tableType.UserTypeId.Value, cancellationToken).ConfigureAwait(false);
380-
if (columnList != null)
381-
{
382-
columns = columnList;
383-
}
384-
}
385-
catch (Exception ex)
386-
{
387-
_console.Verbose($"[snapshot-tabletype] failed to load columns for {tableType.SchemaName}.{tableType.Name}: {ex.Message}");
388-
}
378+
continue;
389379
}
390380

381+
var columns = tableTypeMetadata.Columns ?? Array.Empty<Column>();
382+
391383
var jsonBytes = BuildTableTypeJson(tableType, columns, requiredTypeRefs);
392384
var fileName = BuildArtifactFileName(tableType.SchemaName, tableType.Name);
393385
var filePath = Path.Combine(tableTypeRoot, fileName);
@@ -413,20 +405,14 @@ private async Task<SchemaArtifactSummary> WriteSchemaArtifactsAsync(string schem
413405

414406
PruneExtraneousFiles(tableTypeRoot, validTableTypeFiles);
415407

416-
List<UserDefinedTypeRow> scalarTypes = new();
408+
IReadOnlyList<UserDefinedTypeRow> scalarTypes = Array.Empty<UserDefinedTypeRow>();
417409
try
418410
{
419-
var list = await _dbContext.UserDefinedScalarTypesAsync(cancellationToken).ConfigureAwait(false);
420-
if (list != null)
421-
{
422-
scalarTypes = list
423-
.Where(t => t != null && !string.IsNullOrWhiteSpace(t.schema_name) && schemaSet.Contains(t.schema_name))
424-
.ToList();
425-
}
411+
scalarTypes = await _userDefinedTypeMetadataProvider.GetUserDefinedTypesAsync(schemaSet, cancellationToken).ConfigureAwait(false);
426412
}
427413
catch (Exception ex)
428414
{
429-
_console.Verbose($"[snapshot-udt] failed to enumerate user-defined types: {ex.Message}");
415+
_console.Verbose($"[snapshot-udt] metadata provider failed: {ex.Message}");
430416
}
431417

432418
var scalarRoot = Path.Combine(schemaRoot, "types");

0 commit comments

Comments
 (0)