lib/functions/Private/Get-SqlDiagnostic.ps1

function Get-SqlDiagnostic {
    <#
    .SYNOPSIS
        Runs the client-side validation passes over a script and returns everything they found, on
        one diagnostics channel.
 
    .DESCRIPTION
        The single entry point issue #61 section 3 asks for. The script is parsed ONCE and the tree is
        handed to each enabled pass:
 
            T-SQL syntax ScriptDom parse errors, Error, exactly SQL Server's wording
            SQL schema identifiers resolved against the already-cached schema, Warning
            Omada compatibility the rule catalogue, severities from the rule table and config
 
        Each pass is switched on independently (acceptance criterion 7 and A8), and with ScriptDom
        unavailable all three are off and the application behaves exactly as it did before the feature
        existed (acceptance criterion 6 and A8).
 
        Nothing here contacts Omada. The schema pass reads the cache the editor's IntelliSense already
        filled, and makes no request of its own (acceptance criterion 5).
 
        The schema pass is skipped when the script did not parse cleanly. A partial tree from a
        recovered parse names identifiers the user has not finished typing, and resolving those
        produces a warning under the cursor on every keystroke - the exact noise that gets a pass
        switched off. The syntax error is the useful thing to say about that script anyway.
 
    .PARAMETER SqlText
        The script to check.
 
    .PARAMETER Setting
        The resolved settings from Get-SqlValidationSetting. Omit to resolve them here.
 
    .PARAMETER SchemaModel
        The indexed schema to resolve against. Omit to take the active tab's, which is what every
        caller in the application wants; tests pass one explicitly.
 
    .OUTPUTS
        [PSCustomObject] with
 
            Status Ok the script was checked
                           Unavailable nothing was parsed, so nothing was checked
                           Disabled every pass is switched off
            Diagnostic the markers from every enabled pass, ordered by position
            ParserVersion the parser used, or $null
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, Position = 0)]
        [AllowNull()]
        [AllowEmptyString()]
        [string]$SqlText,
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        $Setting,
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        $SchemaModel,
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        $DatabaseSchemaModel
    )

    # No tracer preamble: $SqlText is the user's query (issue #61 section 5).

    if ($null -eq $Setting) {
        $Setting = Get-SqlValidationSetting
    }

    if (-not $Setting.Enabled -and -not $Setting.SchemaEnabled -and -not $Setting.OmadaEnabled) {
        return [PSCustomObject]@{
            Status        = "Disabled"
            Diagnostic    = @()
            ParserVersion = $null
        }
    }

    $Parsed = Get-SqlScriptFragment -SqlText $SqlText -ParserVersion $Setting.ParserVersion

    if ($Parsed.Status -ne "Ok") {
        return [PSCustomObject]@{
            Status        = "Unavailable"
            Diagnostic    = @()
            ParserVersion = $null
        }
    }

    $Diagnostic = [System.Collections.Generic.List[PSCustomObject]]::new()

    if ($Setting.Enabled) {
        foreach ($Item in @(ConvertTo-SqlSyntaxDiagnostic -ParseError $Parsed.ParseError -SqlText $SqlText)) {
            $Diagnostic.Add($Item)
        }
    }

    $ParsedCleanly = (@($Parsed.ParseError).Count -eq 0)

    if ($Setting.SchemaEnabled -and $ParsedCleanly) {
        if (-not $PSBoundParameters.ContainsKey("SchemaModel")) {
            $SchemaModel = Get-ActiveSqlSchemaModel
        }

        # The schemas of any OTHER databases that happen to be cached (issue #158). Read here rather
        # than inside the pass, so the pass is structurally incapable of fetching one: it only ever
        # sees what this hands it. Empty is the normal case and means "behave as before".
        if (-not $PSBoundParameters.ContainsKey("DatabaseSchemaModel")) {
            $DatabaseSchemaModel = Get-CachedSqlSchemaModelByDatabase
        }

        # Each pass walks the tree dozens of times; inside Use-SqlFragmentWalkCache each node is walked
        # once (issue #185).
        foreach ($Item in @(Use-SqlFragmentWalkCache { Get-SqlSchemaDiagnostic -Fragment $Parsed.Fragment -SchemaModel $SchemaModel -DatabaseSchemaModel $DatabaseSchemaModel })) {
            $Diagnostic.Add($Item)
        }
    }

    if ($Setting.OmadaEnabled -and $ParsedCleanly) {
        foreach ($Item in @(Use-SqlFragmentWalkCache { Get-OmadaCompatibilityDiagnostic -Fragment $Parsed.Fragment -RuleSeverity $Setting.RuleSeverity })) {
            $Diagnostic.Add($Item)
        }
    }

    return [PSCustomObject]@{
        Status        = "Ok"
        Diagnostic    = @($Diagnostic | Sort-Object -Property @{ Expression = { [int]$_.Line } }, @{ Expression = { [int]$_.Column } })
        ParserVersion = $Parsed.ParserVersion
    }
}

function Test-SqlDiagnosticBlocksExecution {
    <#
    .SYNOPSIS
        Decides whether a diagnostic takes part in the "execute anyway?" confirmation.
 
    .DESCRIPTION
        Issue #61 puts the three passes in three different places on purpose, and this is where that
        decision is written down:
 
          * A SYNTAX error is a batch SQL Server will reject before it touches a table. It asks.
          * An OMADA COMPATIBILITY diagnostic at Error or Warning is a deterministic wrong outcome -
            a statement that cannot run here, or a result set that will arrive empty. It asks
            (section 3.5). At Info it is an observation about naming, and it does not.
          * A SCHEMA warning is a guess against a cache that may be stale and a schema whose coverage
            is not guaranteed. It never asks, and never gates execution (acceptance criterion 4).
 
        Nothing here ever blocks. The answer only decides whether the user is asked once.
 
        The Source labels are matched as literals, and they are the same literals each pass declares
        as its own Source default. They are part of the diagnostics contract in issue #61 section 3 -
        the editor shows them in the marker hover - so they are written out here rather than hidden
        behind a shared constant that a test could leave undefined.
 
    .PARAMETER Diagnostic
        The diagnostic to classify.
 
    .OUTPUTS
        [bool]
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $true)]
        [AllowNull()]
        $Diagnostic
    )

    process {
        if ($null -eq $Diagnostic) {
            return $false
        }

        switch ([string]$Diagnostic.Source) {
            "T-SQL syntax" { return $true }
            "Omada compatibility" { return ([string]$Diagnostic.Severity -in @("Error", "Warning")) }
            default { return $false }
        }
    }
}