Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ec51d8c
Add provider metadata discovery
PrzemyslawKlys Jun 22, 2026
fc58e2b
Fix SQLite metadata discovery cleanup
PrzemyslawKlys Jun 22, 2026
1cf319d
Fix metadata discovery review gaps
PrzemyslawKlys Jun 22, 2026
c55ccc7
Add foreign key and routine metadata
PrzemyslawKlys Jun 22, 2026
4b0ce89
Fix metadata fidelity review gaps
PrzemyslawKlys Jun 22, 2026
91ecc37
Merge master into metadata discovery branch
PrzemyslawKlys Jun 22, 2026
90bc39a
Fix latest metadata review comments
PrzemyslawKlys Jun 22, 2026
b903a59
Fix metadata fidelity review comments
PrzemyslawKlys Jun 22, 2026
d213320
Add SQL Server management metadata foundation
PrzemyslawKlys Jun 23, 2026
fca37d0
Add SQL Server management tier builders
PrzemyslawKlys Jun 23, 2026
f705f85
Address SQL Server management PR review
PrzemyslawKlys Jun 23, 2026
ff6ec3f
Tighten SQL Server management scripting fidelity
PrzemyslawKlys Jun 23, 2026
bf88c06
Improve SQL Server script fidelity
PrzemyslawKlys Jun 23, 2026
a097ab9
Preserve advanced SQL Server table metadata
PrzemyslawKlys Jun 23, 2026
32c3f42
Improve provider metadata fidelity
PrzemyslawKlys Jun 23, 2026
2df8aa7
Merge remote-tracking branch 'origin/feature/metadata-discovery-no-sm…
PrzemyslawKlys Jun 23, 2026
ed34822
Preserve SQL Server management details
PrzemyslawKlys Jun 23, 2026
3cef7e6
Improve advanced metadata fidelity
PrzemyslawKlys Jun 23, 2026
be534d0
Merge remote-tracking branch 'origin/feature/metadata-discovery-no-sm…
PrzemyslawKlys Jun 23, 2026
5cc59d5
Harden SQL Server management scripting
PrzemyslawKlys Jun 23, 2026
22e8709
Preserve SQL Server table scripting state
PrzemyslawKlys Jun 23, 2026
6249327
Defer SQL Server table constraint scripting
PrzemyslawKlys Jun 23, 2026
9590f35
Preserve provider metadata edge cases
PrzemyslawKlys Jun 23, 2026
697080d
Preserve advanced provider metadata
PrzemyslawKlys Jun 23, 2026
5331402
Preserve SQL Server table script state
PrzemyslawKlys Jun 23, 2026
6b0020b
Preserve advanced SQL Server management metadata
PrzemyslawKlys Jun 23, 2026
bd1956b
Preserve provider metadata review cases
PrzemyslawKlys Jun 23, 2026
5fd7aa0
Preserve SQL Server advanced script edge cases
PrzemyslawKlys Jun 23, 2026
dbcde21
Preserve advanced SQL Server script metadata
PrzemyslawKlys Jun 23, 2026
0c7a863
Gate advanced SQL Server catalogs
PrzemyslawKlys Jun 23, 2026
a0573e8
Preserve advanced SQL Server management metadata
PrzemyslawKlys Jun 23, 2026
6846387
Refine SQL Server management edge scripting
PrzemyslawKlys Jun 23, 2026
ca0ffad
Preserve advanced SQL Server script metadata
PrzemyslawKlys Jun 23, 2026
54eaeee
Handle additional SQL Server management edge cases
PrzemyslawKlys Jun 23, 2026
392ebf3
Preserve SQL Server trigger and permission metadata
PrzemyslawKlys Jun 23, 2026
efac3a6
Preserve SQL Server ledger and CLR metadata
PrzemyslawKlys Jun 23, 2026
65c222b
Gate SQL Server management catalog branches
PrzemyslawKlys Jun 23, 2026
9ca3ebd
Fix SQL Server Azure metadata edge cases
PrzemyslawKlys Jun 23, 2026
7fc4eb0
Preserve SQL Server graph and CLR edge metadata
PrzemyslawKlys Jun 23, 2026
0c2d320
Preserve SQL Server trigger metadata edge cases
PrzemyslawKlys Jun 23, 2026
63ca13e
Preserve SQL Server advanced script details
PrzemyslawKlys Jun 23, 2026
fa81868
Merge pull request #188 from EvotecIT/feature/sqlserver-management-tiers
PrzemyslawKlys Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test-powershell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ jobs:
shell: pwsh
env:
RefreshPSD1Only: 'false'
SignModule: 'false'
run: ./Module/Build/Build-Module.ps1

- name: Run packaged ALC smoke test
Expand Down
56 changes: 56 additions & 0 deletions DbaClientX.Core/DatabaseClientBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,62 @@ protected static bool TryGetDictionaryValue<TValue>(IDictionary<string, TValue>?
});
}

/// <summary>
/// Executes a query and maps each row with a caller-provided mapper.
/// </summary>
/// <typeparam name="T">The row result type produced by <paramref name="map"/>.</typeparam>
/// <param name="connection">The open database connection.</param>
/// <param name="transaction">The transaction to enlist in, if any.</param>
/// <param name="query">The query to execute.</param>
/// <param name="map">A mapper that converts the current data record into a result value.</param>
/// <param name="initialize">Optional callback invoked once after the reader opens and before the first row is read.</param>
/// <param name="parameters">Optional parameter values.</param>
/// <param name="parameterTypes">Optional parameter types.</param>
/// <param name="parameterDirections">Optional parameter directions.</param>
/// <returns>The mapped result rows.</returns>
protected virtual IReadOnlyList<T> ExecuteMappedQuery<T>(
DbConnection connection,
DbTransaction? transaction,
string query,
Func<IDataRecord, T> map,
Action<IDataRecord>? initialize = null,
IDictionary<string, object?>? parameters = null,
IDictionary<string, DbType>? parameterTypes = null,
IDictionary<string, ParameterDirection>? parameterDirections = null)
{
ValidateCommandText(query);
if (map == null)
{
throw new ArgumentNullException(nameof(map));
}

return ExecuteWithRetry<IReadOnlyList<T>>(() =>
{
using var command = connection.CreateCommand();
command.CommandText = query;
command.Transaction = transaction;
AddParameters(command, parameters, parameterTypes, parameterDirections);
var commandTimeout = CommandTimeout;
if (commandTimeout > 0)
{
command.CommandTimeout = commandTimeout;
}

var rows = new List<T>();
using (var reader = command.ExecuteReader(CommandBehavior.Default))
{
initialize?.Invoke(reader);
while (reader.Read())
{
rows.Add(map(reader));
}
}

UpdateOutputParameters(command, parameters);
return rows;
});
}

/// <summary>
/// Executes a non-query command (INSERT/UPDATE/DELETE) with retry support.
/// </summary>
Expand Down
25 changes: 25 additions & 0 deletions DbaClientX.Core/Metadata/DbaColumnInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace DBAClientX.Metadata;

/// <summary>
/// Describes a column on a provider-neutral table or view result.
/// </summary>
public sealed record DbaColumnInfo(string Schema, string Table, string Name, string DataType)
{
/// <summary>One-based ordinal position in the table or view.</summary>
public int Ordinal { get; init; }

/// <summary>Indicates whether the provider reports the column as nullable.</summary>
public bool? IsNullable { get; init; }

/// <summary>Maximum character or binary length when available.</summary>
public long? MaxLength { get; init; }

/// <summary>Numeric precision when available.</summary>
public int? Precision { get; init; }

/// <summary>Numeric scale when available.</summary>
public int? Scale { get; init; }

/// <summary>Provider-specific default expression when available.</summary>
public string? DefaultExpression { get; init; }
Comment thread
PrzemyslawKlys marked this conversation as resolved.
}
16 changes: 16 additions & 0 deletions DbaClientX.Core/Metadata/DbaDatabaseInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace DBAClientX.Metadata;

/// <summary>
/// Describes a database or catalog visible to a provider connection.
/// </summary>
public sealed record DbaDatabaseInfo(string Name)
{
/// <summary>Provider-specific owner, schema owner, or current user when available.</summary>
public string? Owner { get; init; }

/// <summary>Provider-specific collation or encoding when available.</summary>
public string? Collation { get; init; }

/// <summary>Indicates whether the database is provider/system-owned when the provider exposes that information.</summary>
public bool? IsSystem { get; init; }
}
29 changes: 29 additions & 0 deletions DbaClientX.Core/Metadata/DbaForeignKeyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace DBAClientX.Metadata;

/// <summary>
/// Describes one column mapping in a foreign key relationship.
/// </summary>
public sealed record DbaForeignKeyInfo(
string Schema,
string Table,
string Name,
string Column,
string ReferencedSchema,
string ReferencedTable,
string ReferencedColumn)
{
/// <summary>One-based ordinal position of the column inside the foreign key.</summary>
public int Ordinal { get; init; }

/// <summary>Provider-specific update action when available.</summary>
public string? UpdateRule { get; init; }

/// <summary>Provider-specific delete action when available.</summary>
public string? DeleteRule { get; init; }

/// <summary>Indicates whether the provider reports the constraint as enabled.</summary>
public bool? IsEnabled { get; init; }

/// <summary>Indicates whether the provider reports the constraint as validated or trusted.</summary>
public bool? IsTrusted { get; init; }
}
25 changes: 25 additions & 0 deletions DbaClientX.Core/Metadata/DbaIndexInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace DBAClientX.Metadata;

/// <summary>
/// Describes an index column. Multi-column indexes return one row per indexed column.
/// </summary>
public sealed record DbaIndexInfo(string Schema, string Table, string Name)
{
/// <summary>Provider-specific index type or access method when available.</summary>
public string? IndexType { get; init; }

/// <summary>Indicates whether the index is unique.</summary>
public bool IsUnique { get; init; }

/// <summary>Indicates whether the index backs a primary key.</summary>
public bool IsPrimaryKey { get; init; }

/// <summary>Indexed column name when the provider exposes a direct column mapping.</summary>
public string? Column { get; init; }

/// <summary>One-based ordinal position of the column inside the index.</summary>
public int Ordinal { get; init; }

/// <summary>Indicates descending sort order when the provider exposes it.</summary>
public bool? IsDescending { get; init; }
}
97 changes: 97 additions & 0 deletions DbaClientX.Core/Metadata/DbaMetadataReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Data;
using System.Globalization;

namespace DBAClientX.Metadata;

/// <summary>
/// Provides small conversion helpers for provider metadata readers.
/// </summary>
public static class DbaMetadataReader
{
/// <summary>Reads a required string field.</summary>
public static string GetString(IDataRecord record, string name)
=> Convert.ToString(GetValue(record, name), CultureInfo.InvariantCulture) ?? string.Empty;

/// <summary>Reads an optional string field.</summary>
public static string? GetNullableString(IDataRecord record, string name)
{
var value = GetValue(record, name);
return value == null || value == DBNull.Value
? null
: Convert.ToString(value, CultureInfo.InvariantCulture);
}

/// <summary>Reads a required integer field.</summary>
public static int GetInt32(IDataRecord record, string name)
=> Convert.ToInt32(GetValue(record, name), CultureInfo.InvariantCulture);

/// <summary>Reads an optional integer field.</summary>
public static int? GetNullableInt32(IDataRecord record, string name)
{
var value = GetValue(record, name);
return value == null || value == DBNull.Value
? null
: Convert.ToInt32(value, CultureInfo.InvariantCulture);
}

/// <summary>Reads an optional long field.</summary>
public static long? GetNullableInt64(IDataRecord record, string name)
{
var value = GetValue(record, name);
return value == null || value == DBNull.Value
? null
: Convert.ToInt64(value, CultureInfo.InvariantCulture);
}

/// <summary>Reads a required Boolean field.</summary>
public static bool GetBoolean(IDataRecord record, string name)
=> ConvertToBoolean(GetValue(record, name)) ?? false;

/// <summary>Reads an optional Boolean field.</summary>
public static bool? GetNullableBoolean(IDataRecord record, string name)
=> ConvertToBoolean(GetValue(record, name));

private static object? GetValue(IDataRecord record, string name)
{
var ordinal = record.GetOrdinal(name);
return record.IsDBNull(ordinal) ? null : record.GetValue(ordinal);
}

private static bool? ConvertToBoolean(object? value)
{
if (value == null || value == DBNull.Value)
{
return null;
}

if (value is bool boolean)
{
return boolean;
}

if (value is string text)
{
if (bool.TryParse(text, out var parsed))
{
return parsed;
}

if (string.Equals(text, "YES", StringComparison.OrdinalIgnoreCase) ||
string.Equals(text, "Y", StringComparison.OrdinalIgnoreCase) ||
string.Equals(text, "1", StringComparison.OrdinalIgnoreCase))
{
return true;
}

if (string.Equals(text, "NO", StringComparison.OrdinalIgnoreCase) ||
string.Equals(text, "N", StringComparison.OrdinalIgnoreCase) ||
string.Equals(text, "0", StringComparison.OrdinalIgnoreCase))
{
return false;
}
}

return Convert.ToInt32(value, CultureInfo.InvariantCulture) != 0;
}
}
16 changes: 16 additions & 0 deletions DbaClientX.Core/Metadata/DbaRoutineInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace DBAClientX.Metadata;

/// <summary>
/// Describes a provider-neutral stored routine such as a procedure or function.
/// </summary>
public sealed record DbaRoutineInfo(string Schema, string Name, DbaRoutineKind Kind)
{
/// <summary>Provider-specific result data type when available.</summary>
public string? DataType { get; init; }

/// <summary>Routine definition text when the provider exposes it to the current principal.</summary>
public string? Definition { get; init; }

/// <summary>Indicates whether the provider marks the routine as system-owned.</summary>
public bool? IsSystem { get; init; }
}
19 changes: 19 additions & 0 deletions DbaClientX.Core/Metadata/DbaRoutineKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace DBAClientX.Metadata;

/// <summary>
/// Classifies a stored routine.
/// </summary>
public enum DbaRoutineKind
{
/// <summary>Routine type is not known or does not fit the provider-neutral categories.</summary>
Unknown,

/// <summary>Stored procedure routine.</summary>
Procedure,

/// <summary>Function routine.</summary>
Function,

/// <summary>Oracle package or another package-style routine container.</summary>
Package
}
10 changes: 10 additions & 0 deletions DbaClientX.Core/Metadata/DbaTableInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace DBAClientX.Metadata;

/// <summary>
/// Identifies a tabular database object such as a table or view.
/// </summary>
public sealed record DbaTableInfo(string Schema, string Name, DbaTableKind Kind)
{
/// <summary>Provider-specific object owner when different from <see cref="Schema"/>.</summary>
public string? Owner { get; init; }
}
13 changes: 13 additions & 0 deletions DbaClientX.Core/Metadata/DbaTableKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace DBAClientX.Metadata;

/// <summary>
/// Classifies provider-neutral tabular objects returned by metadata discovery.
/// </summary>
public enum DbaTableKind
{
/// <summary>A regular table.</summary>
Table,

/// <summary>A view or virtual table-like object.</summary>
View
}
Loading
Loading