internal/functions/providers/schema/Get-SldgCheckConstraintRow.ps1

function Get-SldgCheckConstraintRow {
    <#
    .SYNOPSIS
        Reads CHECK constraints per column - the one piece of the catalogue the EF import does not carry.
    .DESCRIPTION
        Generators clamp values to CHECK clauses (ranges, IN lists), so the clauses are read
        straight from the engine, keyed by the dialect's catalogue: sys.check_constraints on SQL
        Server (already attributed to a column), the CREATE TABLE text in sqlite_master on SQLite,
        and INFORMATION_SCHEMA.CHECK_CONSTRAINTS everywhere else. Table-level clauses are
        attributed to every column of the table whose name appears in them. Engines without a
        readable catalogue (or without the standard view) yield no rows and a verbose note.
    .PARAMETER ConnectionInfo
        The active SqlLabDataGenerator.Connection.
    .PARAMETER ColumnsByTable
        Column names per 'schema.table' key, used to attribute table-level clauses.
    .OUTPUTS
        System.Data.DataTable with SchemaName, TableName, ConstraintName, ConstraintDefinition, ColumnName.
    #>

    [OutputType([System.Data.DataTable])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [SqlLabDataGenerator.Connection]$ConnectionInfo,

        [Parameter(Mandatory)]
        [hashtable]$ColumnsByTable
    )

    $rows = [System.Data.DataTable]::new()
    foreach ($name in 'SchemaName', 'TableName', 'ConstraintName', 'ConstraintDefinition', 'ColumnName') { [void]$rows.Columns.Add($name, [string]) }

    $dialect = $ConnectionInfo.GetDialect()
    $columnIndex = $ColumnsByTable
    $regexTimeout = [timespan]::FromSeconds(2)

    # Attributes one clause to every column of the table it mentions.
    $attribute = {
        param([string]$SchemaName, [string]$TableName, [string]$ConstraintName, [string]$Clause)
        $key = "$SchemaName.$TableName"
        if (-not $Clause -or -not $columnIndex.ContainsKey($key)) { return }
        foreach ($columnName in $columnIndex[$key]) {
            try {
                if ([regex]::IsMatch($Clause, "(?i)\b$([regex]::Escape($columnName))\b", 'None', $regexTimeout)) {
                    [void]$rows.Rows.Add($SchemaName, $TableName, $ConstraintName, $Clause, $columnName)
                }
            }
            catch [System.Text.RegularExpressions.RegexMatchTimeoutException] {
                Write-PSFMessage -Level Warning -Message ($script:strings.'Schema.SQLite.CheckParseTimeout' -f $TableName)
            }
        }
    }

    switch ($dialect.SchemaSource) {
        'SqlServer' {
            $raw = Invoke-SldgDbQuery -ConnectionInfo $ConnectionInfo -Query @"
SELECT
    OBJECT_SCHEMA_NAME(cc.parent_object_id) AS SchemaName,
    OBJECT_NAME(cc.parent_object_id) AS TableName,
    cc.name AS ConstraintName,
    cc.definition AS ConstraintDefinition,
    COL_NAME(cc.parent_object_id, cc.parent_column_id) AS ColumnName
FROM sys.check_constraints cc
"@

            foreach ($row in $raw.Rows) {
                if ($row.ColumnName -is [DBNull] -or -not [string]$row.ColumnName) {
                    & $attribute ([string]$row.SchemaName) ([string]$row.TableName) ([string]$row.ConstraintName) ([string]$row.ConstraintDefinition)
                }
                else {
                    [void]$rows.Rows.Add([string]$row.SchemaName, [string]$row.TableName, [string]$row.ConstraintName, [string]$row.ConstraintDefinition, [string]$row.ColumnName)
                }
            }
        }
        'Sqlite' {
            $raw = Invoke-SldgDbQuery -ConnectionInfo $ConnectionInfo -Query "SELECT name, sql FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'"
            foreach ($row in $raw.Rows) {
                $createSql = [string]$row.sql
                if (-not $createSql) { continue }
                try {
                    # Walk each CHECK( ... ) to its balancing parenthesis so clauses with nested
                    # parentheses - IN ('a', 'b'), length(x) > 0 - come out whole.
                    $checkRegex = [regex]::new('CHECK\s*\(', 'IgnoreCase', $regexTimeout)
                    $index = 0
                    foreach ($m in $checkRegex.Matches($createSql)) {
                        $depth = 1
                        $start = $m.Index + $m.Length
                        $pos = $start
                        while ($pos -lt $createSql.Length -and $depth -gt 0) {
                            switch ($createSql[$pos]) { '(' { $depth++ } ')' { $depth-- } }
                            $pos++
                        }
                        if ($depth -ne 0) { continue }
                        $index++
                        & $attribute $dialect.DefaultSchema ([string]$row.name) "CK_$($row.name)_$index" $createSql.Substring($start, $pos - 1 - $start).Trim()
                    }
                }
                catch [System.Text.RegularExpressions.RegexMatchTimeoutException] {
                    Write-PSFMessage -Level Warning -Message ($script:strings.'Schema.SQLite.CheckParseTimeout' -f [string]$row.name)
                }
            }
        }
        default {
            try {
                $raw = Invoke-SldgDbQuery -ConnectionInfo $ConnectionInfo -Query @"
SELECT tc.TABLE_SCHEMA AS SchemaName, tc.TABLE_NAME AS TableName, cc.CONSTRAINT_NAME AS ConstraintName, cc.CHECK_CLAUSE AS ConstraintDefinition
FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
    ON tc.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
   AND tc.CONSTRAINT_SCHEMA = cc.CONSTRAINT_SCHEMA
WHERE tc.CONSTRAINT_TYPE = 'CHECK'
"@

                foreach ($row in $raw.Rows) {
                    & $attribute ([string]$row.SchemaName) ([string]$row.TableName) ([string]$row.ConstraintName) ([string]$row.ConstraintDefinition)
                }
            }
            catch {
                Write-PSFMessage -Level Verbose -String 'Schema.CheckConstraintsUnavailable' -StringValues $ConnectionInfo.Provider, $_.Exception.Message
            }
        }
    }

    , $rows
}