Functions/Split-SqlBatchQuery.ps1


function Split-SqlBatchQuery
{
    <#
    .SYNOPSIS
    Splits a SQL batch query into individual queries.
 
    .DESCRIPTION
    `Split-SqlBatchQuery` takes a batch query and splits it by the `GO` statements it contains. `GO` statements inside comments and strings are ignored. It does not use regular expressions.
 
    If the query has no `GO` statements, you'll get your original query back.
 
    You can pipe SQL batch queries into this function and you'll get runnable queries out the other side.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
        [string]
        $Query
    )

    begin
    {
    }

    process
    {
        Set-StrictMode -Version 'Latest'

        $currentQuery = New-Object 'Text.StringBuilder'
        $inSingleLineComment = $false
        $inMultiLineComment = $false
        $inString = $false
        $stringCouldBeEnding = $false
        $prevChar = $null
        $currentChar = $null
        $commentDepth = 0
        $currentLine = New-Object 'Text.StringBuilder'

        function Complete-Line
        {
            Write-Debug -Message ("inMultiLineComment: {0}; inSingleLineComment: {1}; inString {2}; {3}" -f $inMultiLineComment,$inSingleLineComment,$inString,$currentLine.ToString())
            $trimmedLine = $currentLine.ToString().Trim() 
            if( $trimmedLine -notmatch "^GO\b" )
            {
                [void]$currentQuery.Append( $currentLine )
            }

            $currentLine.Length = 0
                
            if( $trimmedLine -match "^GO\b" -or $atLastChar )
            {
                $currentQuery.ToString()
                $currentQuery.Length = 0
            }
        }

        $chars = $Query.ToCharArray()
        for( $idx = 0; $idx -lt $chars.Count; ++$idx )
        {
            $prevChar = $null
            if( $idx -gt 1 )
            {
                $prevChar = $chars[$idx - 1]
            }

            $currentChar = $chars[$idx]

            $nextChar = $null
            if( $idx + 1 -lt $chars.Count )
            {
                $nextChar = $chars[$idx + 1]
            }

            $atLastChar = $idx -eq $chars.Count - 1
            if( $atLastChar )
            {
                [void]$currentLine.Append( $currentChar )
                Complete-Line
                continue
            }

            if( $inMultiLineComment )
            {
                [void] $currentLine.Append( $currentChar )
                if( $prevChar -eq '/' -and $currentChar -eq '*' )
                {
                    Write-Debug -Message ('Entering nested multi-line comment.')
                    $commentDepth++
                    continue
                }
                elseif( $prevChar -eq '*' -and $currentChar -eq '/' )
                {
                    Write-Debug -Message ('Leaving multi-line comment.')
                    $commentDepth--
                    $inMultiLineComment = ($commentDepth -gt 0)
                }

                if( -not $inMultiLineComment )
                {
                    Write-Debug -Message ('Multi-line comment closed.')
                }
                continue
            }

            if( $inSingleLineComment )
            {
                if( $currentChar -eq "`n" )
                {
                    Write-Debug -Message ('Leaving single-line comment.')
                    $inSingleLineComment = $false
                }
                else
                {
                    [void] $currentLine.Append( $currentChar )
                    continue
                }
            }
            
            if( $inString )
            {
                if( $stringCouldBeEnding )
                {
                    $stringCouldBeEnding = $false
                    if( $currentChar -eq "'" )
                    {
                        [void] $currentLine.Append( $currentChar )
                        Write-Debug -Message ('Found escaped quote.')
                        continue
                    }
                    else
                    {
                        Write-Debug -Message ('Leaving string.')
                        $inString = $false
                    }
                }
                elseif( $currentChar -eq "'" )
                {
                    [void] $currentLine.Append( $currentChar )
                    $stringCouldBeEnding = $true
                    continue
                }
                else
                {
                    [void]$currentLine.Append( $currentChar )
                    continue
                }
            }

            if( $prevChar -eq "/" -and $currentChar -eq "*" )
            {
                Write-Debug -Message ('Entering multi-line comment.')
                $inMultiLineComment = $true
                $commentDepth++
            }
            elseif( $prevChar -eq '-' -and $currentChar -eq '-' )
            {
                Write-Debug -Message ('Entering single-line comment.')
                $inSingleLineComment = $true
            }
            elseif( $currentChar -eq "'" )
            {
                Write-Debug -Message ('Entering string.')
                $inString = $true
            }

            [void] $currentLine.Append( $currentChar )

            if( $currentChar -eq "`n" -or $atLastChar )
            {
                Complete-Line
            }
        }
    }
    end
    {
    }
}