Rivet.psm1


$Connection = New-Object Data.SqlClient.SqlConnection

$RivetSchemaName = 'rivet'
$RivetMigrationsTableName = 'Migrations'
$RivetMigrationsTableFullName = "[$($RivetSchemaName)].[$($RivetMigrationsTableName)]"
$RivetActivityTableName = 'Activity'
$rivetModuleRoot = $PSScriptRoot

$timer = New-Object 'Diagnostics.Stopwatch'
$timerForWrites = New-Object 'Diagnostics.Stopwatch'
$timingLevel = 0

$plugins = @()

function Write-Timing
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]
        $Message,

        [Switch]
        $Indent,

        [Switch]
        $Outdent
    )

    Set-StrictMode -Version 'Latest'

    if( -not $timer.IsRunning )
    {
        $timer.Start()
    }

    if( -not $timerForWrites.IsRunning )
    {
        $timerForWrites.Start()
    }

    if( $Outdent )
    {
        $script:timingLevel -= 1
    }

    $prefix = ' ' * ($timingLevel * 2)

    function ConvertTo-DurationString
    {
        param(
            [Parameter(Mandatory,ValueFromPipeline)]
            [TimeSpan]$TimeSpan
        )

        process 
        {
            Set-StrictMode -Version 'Latest'

            $hours = ''
            if( $TimeSpan.Hours )
            {
                $hours = "$($TimeSpan.Hours.ToString())h "
            }

            $minutes = ''
            if( $TimeSpan.Minutes )
            {
                $minutes = "$($TimeSpan.Minutes.ToString('00'))m "
            }

            $seconds = ''
            if( $TimeSpan.Seconds )
            {
                $seconds = "$($TimeSpan.Seconds.ToString('00'))s "
            }

            return "$($hours)$($minutes)$($seconds)$($TimeSpan.Milliseconds.ToString('000'))ms"
        }
    }
    
    # $DebugPreference = 'Continue'

    if( $DebugPreference -eq 'Continue' )
    {
        Write-Debug -Message ('{0,17} {1,17} {2}{3}' -f ($timer.Elapsed | ConvertTo-DurationString),($timerForWrites.Elapsed | ConvertTo-DurationString),$prefix,$Message)
    }

    $timerForWrites.Restart()

    if( $Indent )
    {
        $script:timingLevel += 1
    }

    if( $timingLevel -lt 0 )
    {
        $timingLevel = 0
    }
}

function Test-RivetTypeDataMember
{
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory=$true)]
        [string]
        # The type name to check.
        $TypeName,

        [Parameter(Mandatory=$true)]
        [string]
        # The name of the member to check.
        $MemberName
    )

    Set-StrictMode -Version 'Latest'

    $typeData = Get-TypeData -TypeName $TypeName
    if( -not $typeData )
    {
        # The type isn't defined or there is no extended type data on it.
        return $false
    }

    return $typeData.Members.ContainsKey( $MemberName )
}

$oldVersionLoadedMsg = 'You have an old version of Rivet loaded. Please restart your PowerShell session.'
function New-RivetObject
{
    param(
        [Parameter(Mandatory)]
        [String]$TypeName,

        [Object[]]$ArgumentList
    )

    try
    {
        return (New-Object -TypeName $TypeName -ArgumentList $ArgumentList -ErrorAction Ignore)
    }
    catch
    {
        Write-Error -Message ('Unable to find type "{0}". {1}' -f $TypeName,$oldVersionLoadedMsg) -ErrorAction Stop
    }
}

if( -not (Test-RivetTypeDataMember -TypeName 'Rivet.OperationResult' -MemberName 'MigrationID') )
{
    Update-TypeData -TypeName 'Rivet.OperationResult' -MemberType ScriptProperty -MemberName 'MigrationID' -Value { $this.Migration.ID }
}

# Added in Rivet 0.10.0
Test-RivetTypeDataMember -TypeName 'Rivet.Scale' -MemberName 'Value'

# Import functions on developer computers.
& {
    Join-Path -Path $rivetModuleRoot -ChildPath 'Functions'
    Join-Path -Path $rivetModuleRoot -ChildPath 'Functions\Columns'
    Join-Path -Path $rivetModuleRoot -ChildPath 'Functions\Operations'
} |
    Where-Object { Test-Path -Path $_ -PathType Container } |
    Get-ChildItem -Filter '*-*.ps1' |
    ForEach-Object { . $_.FullName }



function Connect-Database
{
    param(
        [Parameter(Mandatory=$true)]
        [string]
        # The SQL Server instance to connect to.
        $SqlServerName,
        
        [Parameter(Mandatory=$true)]
        [string]
        # The database to connect to.
        $Database,
        
        [UInt32]
        # The time (in seconds) to wait for a connection to open. The default is 10 seconds.
        $ConnectionTimeout = 10
    )
    
    Set-StrictMode -Version 'Latest'

    $startedAt = Get-Date

    if( -not $Connection -or $Connection.DataSource -ne $SqlServerName -or $Connection.State -eq [Data.ConnectionState]::Closed)
    {
        Disconnect-Database

        $connString = 'Server={0};Database=master;Integrated Security=True;Connection Timeout={1}' -f $SqlServerName,$ConnectionTimeout
        Set-Variable -Name 'Connection' -Scope 1 -Value (New-Object 'Data.SqlClient.SqlConnection' ($connString)) -Confirm:$False -WhatIf:$false
        try
        {
            $Connection.Open()
        }
        catch
        {
            $ex = $_.Exception
            while( $ex.InnerException )
            {
                $ex = $ex.InnerException
            }

            Write-Error ('Failed to connect to SQL Server "{0}" (connection string: {1}). Does this database server exist? ({2})' -f $SqlServerName,$connString,$ex.Message)
            return $false
        }
    }

    if( -not ($Connection | Get-Member -Name 'Transaction' ) )
    {
        $Connection |
            Add-Member -MemberType NoteProperty -Name 'Transaction' -Value $null
    }

    if( $Connection.Database -ne 'master' )
    {
        $Connection.ChangeDatabase( 'master' )
    }

    $query = 'select 1 from sys.databases where name=''{0}''' -f $Database
    $dbExists = Invoke-Query -Query $query -AsScalar
    if( -not $dbExists )
    {
        Write-Debug -Message ('Creating database {0}.{1}.' -f $SqlServerName,$Database)
        $query = 'create database [{0}]' -f $Database
        Invoke-Query -Query $query -NonQuery
    }

    $Connection.ChangeDatabase( $Database )

    Write-Debug -Message ('{0,8} (ms) Connect-Database' -f ([int]((Get-Date) - $startedAt).TotalMilliseconds))
    return $true
}


function Convert-FileInfoToMigration
{
    <#
    .SYNOPSIS
    Converts a `System.IO.FileInfo` object containing a migration into a `Rivet.Operations.Operation` object.
    #>

    [CmdletBinding()]
    [OutputType([Rivet.Migration])]
    param(
        [Parameter(Mandatory)]
        # The Rivet configuration to use.
        [Rivet.Configuration.Configuration]$Configuration,

        [Parameter(Mandatory,ValueFromPipeline)]
        # The database whose migrations to get.
        [IO.FileInfo]$InputObject
    )

    begin
    {
        Set-StrictMode -Version 'Latest'

        Write-Timing -Message 'Convert-FileInfoToMigration BEGIN' -Indent
        function Clear-Migration
        {
            ('function:Push-Migration','function:Pop-Migration') |
                Where-Object { Test-Path -Path $_ } |
                Remove-Item -WhatIf:$false -Confirm:$false
        }

        Clear-Migration
    }

    process
    {
        filter Add-Operation
        {
            param(
                [Parameter(Mandatory,ValueFromPipeline)]
                # The migration object to invoke.
                [Object]$Operation,

                [Parameter(ParameterSetName='Push',Mandatory)]
                [AllowEmptyCollection()]
                [Collections.Generic.List[Rivet.Operations.Operation]]$OperationsList,

                [Parameter(ParameterSetName='Pop',Mandatory)]
                [switch]$Pop
            )

            Set-StrictMode -Version 'Latest'

            foreach( $operationItem in $Operation )
            {
                if( $operationItem -isnot [Rivet.Operations.Operation] )
                {
                    continue
                }

                $pluginParameter = @{ Migration = $m ; Operation = $_ }

                [Rivet.Operations.Operation[]]$operations = & {
                        Invoke-RivetPlugin -Event ([Rivet.Events]::BeforeOperationLoad) -Parameter $pluginParameter
                        $operationItem
                        Invoke-RivetPlugin -Event ([Rivet.Events]::AfterOperationLoad) -Parameter $pluginParameter
                    } | 
                    Where-Object { $_ -is [Rivet.Operations.Operation] } |
                    Repair-Operation

                $OperationsList.AddRange($operations)
            }
        }

        foreach( $fileInfo in $InputObject )
        {
            $dbName = Split-Path -Parent -Path $fileInfo.FullName
            $dbName = Split-Path -Parent -Path $dbName
            $dbName = Split-Path -Leaf -Path $dbName

            $m = New-Object 'Rivet.Migration' $fileInfo.MigrationID, $fileInfo.MigrationName, $fileInfo.FullName, $dbName
            Write-Timing -Message ('Convert-FileInfoToMigration {0}' -f $m.FullName)

            # Do not remove. It's a variable expected in some migrations.
            $DBMigrationsRoot = Split-Path -Parent -Path $fileInfo.FullName

            . $fileInfo.FullName | Out-Null

            try
            {
                if( -not (Test-Path -Path 'function:Push-Migration') )
                {
                    throw (@'
Push-Migration function not found. All migrations are required to have a Push-Migration function that contains at least one operation. Here's some sample code to get you started:
 
    function Push-Migration
    {
        Add-Table 'LetsCreateATable' {
            int 'ID' -NotNull
        }
    }
'@
)
                }

                Push-Migration | Add-Operation -OperationsList $m.PushOperations
                if( $m.PushOperations.Count -eq 0 )
                {
                    throw (@'
Push-Migration function is empty and contains no operations. Maybe you''d like to create a table? Here's some sample code to get you started:
 
    function Push-Migration
    {
        Add-Table 'LetsCreateATable' {
            int 'ID' -NotNull
        }
    }
'@
)
                }
            
                if( -not (Test-Path -Path 'function:Pop-Migration') )
                {
                    throw (@'
Pop-Migration function not found. All migrations are required to have a Pop-Migration function that contains at least one operation. Here's some sample code to get you started:
 
    function Pop-Migration
    {
        Remove-Table 'LetsCreateATable'
    }
'@
)
                    return
                }

                Pop-Migration | Add-Operation -OperationsList $m.PopOperations
                if( $m.PopOperations.Count -eq 0 )
                {
                    throw (@'
Pop-Migration function is empty and contains no operations. Maybe you''d like to drop a table? Here's some sample code to get you started:
 
    function Pop-Migration
    {
        Remove-Table 'LetsCreateATable'
    }
'@
)
                }
                $m | Write-Output
            }
            catch
            {
                Write-RivetError -Message ('Loading migration "{0}" failed' -f $m.Path) `
                                 -CategoryInfo $_.CategoryInfo.Category `
                                 -ErrorID $_.FullyQualifiedErrorID `
                                 -Exception $_.Exception `
                                 -CallStack ($_.ScriptStackTrace) 
            }
            finally
            {
                Clear-Migration
            }
        }
    }

    end
    {
        Write-Timing -Message 'Convert-FileInfoToMigration END' -Outdent
    }
}


function Disconnect-Database
{
    param(
    )
    
    if( $Connection -and $Connection.State -ne [Data.ConnectionState]::Closed )
    {
        $Connection.Close()
    }
}


function Export-Migration
{
    <#
    .SYNOPSIS
    Exports objects from a database as Rivet migrations.
 
    .DESCRIPTION
    The `Export-Migration` function exports database objects, schemas, and data types as a Rivet migration. By default, it exports *all* non-system, non-Rivet objects, data types, and schemas. You can filter specific objects by passing their full name to the `Include` parameter. Wildcards are supported. Objects are matched on their schema *and* name.
 
    Extended properties are *not* exported, except table and column descriptions.
 
    .EXAMPLE
    Export-Migration -SqlServerName 'some\instance' -Database 'database'
 
    Demonstrates how to export an entire database.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]
        # The connection string for the database to connect to.
        $SqlServerName,

        [Parameter(Mandatory)]
        [string]
        # The database to connect to.
        $Database,

        [string[]]
        # The names of the objects to export. Must include the schema if exporting a specific object. Wildcards supported.
        #
        # The default behavior is to export all non-system objects.
        $Include,

        [string[]]
        # The names of any objects *not* to export. Matches the object name *and* its schema name, i.e. `schema.name`. Wildcards supported.
        $Exclude,

        [string[]]
        [ValidateSet('CheckConstraint','DataType','DefaultConstraint','ForeignKey','Function','Index','PrimaryKey','Schema','StoredProcedure','Synonym','Table','Trigger','UniqueKey','View','XmlSchema')]
        # Any object types to exclude.
        $ExcludeType,

        [Switch]
        $NoProgress
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $pops = New-Object 'Collections.Generic.Stack[string]'
    $popsHash = @{}
    $exportedObjects = @{ }
    $exportedSchemas = @{ 
                            'dbo' = $true;
                            'guest' = $true;
                            'sys' = $true;
                            'INFORMATION_SCHEMA' = $true;
                        }
    $exportedTypes = @{ }
    $exportedIndexes = @{ }
    $exportedXmlSchemas = @{ }
    $rivetColumnTypes = Get-Alias | 
                            Where-Object { $_.Source -eq 'Rivet' } | 
                            Where-Object { $_.ReferencedCommand -like 'New-*Column' } | 
                            Select-Object -ExpandProperty 'Name'

    $dependencies = @{ }
    $externalDependencies = @{ }
    $indentLevel = 0

    $timer = New-Object 'Timers.Timer' 100

    $checkConstraints = @()
    $checkConstraintsByID = @{}
    $columns = @()
    $columnsByTable = @{}
    $dataTypes = @()
    $defaultConstraints = @()
    $defaultConstraintsByID = @{}
    $foreignKeys = @()
    $foreignKeysByID = @{}
    $foreignKeyColumns = @()
    $foreignKeyColumnsByObjectID = @{}
    $indexes = @()
    $indexesByObjectID = @{}
    $indexColumns = @()
    $indexColumnsByObjectID = @{}
    $objects = @()
    $objectsByID = @{}
    $objectsByParentID = @{}
    $primaryKeys = @()
    $primaryKeysByID = @{}
    $primaryKeyColumns = @()
    $primaryKeyColumnsByObjectID = @{}
    $schemas = @()
    $schemasByName = @{}
    $modules = @()
    $modulesByID = @{}
    $storedProcedures = @()
    $storedProceduresByID = @{}
    $synonyms = @()
    $synonymsByID = @{}
    $triggers = @()
    $triggersByID = @{}
    $triggersByTable = @{}
    $uniqueKeys = @()
    $uniqueKeysByID = @{}
    $uniqueKeysByTable = @{}
    $uniqueKeyColumnsByObjectID = @()
    $uniqueKeyColumnsByObjectID = @{}
    $functions = @()
    $functionsByID = @{}
    $views = @()
    $viewByID = @{}
    $xmlSchemaDependencies = @{ }
    $xmlSchemasByID = @{ }

    $exclusionTypeMap = @{ 
        'CheckConstraint' = 'CHECK_CONSTRAINT';
        'DefaultConstraint' = 'DEFAULT_CONSTRAINT';
        'ForeignKey' = 'FOREIGN_KEY_CONSTRAINT';
        'Function' = @('SQL_INLINE_TABLE_VALUED_FUNCTION','SQL_SCALAR_FUNCTION','SQL_TABLE_VALUED_FUNCTION');
        'PrimaryKey' = 'PRIMARY_KEY_CONSTRAINT';
        'StoredProcedure' = 'SQL_STORED_PROCEDURE';
        'Synonym' = 'SYNONYM';
        'Table' = 'USER_TABLE';
        'Trigger' = 'SQL_TRIGGER';
        'UniqueKey' = 'UNIQUE_CONSTRAINT';
        'View' = 'VIEW';
    }


    function ConvertTo-SchemaParameter
    {
        param(
            [Parameter(Mandatory)]
            [AllowNull()]
            [AllowEmptyString()]
            [string]
            $SchemaName,

            [string]
            $ParameterName = 'SchemaName'
        )

        $parameter = ''
        if( $SchemaName -and $SchemaName -ne 'dbo' )
        {
            $parameter = ' -{0} ''{1}''' -f $ParameterName,$SchemaName
        }
        return $parameter
    }

    function Get-ChildObject
    {
        param(
            [Parameter(Mandatory)]
            [int]
            $TableID,

            [Parameter(Mandatory)]
            [string]
            $Type
        )

        if( $objectsByParentID.ContainsKey($TableID) )
        {
            $objectsByParentID[$TableID] | Where-Object { $_.type -eq $Type }
        }
    }

    $checkConstraintsQuery = '
-- CHECK CONSTRAINTS
select
    sys.check_constraints.object_id,
    schema_name(sys.tables.schema_id) as schema_name,
    sys.tables.name as table_name,
    sys.check_constraints.name as name,
    sys.check_constraints.is_not_trusted,
    sys.check_constraints.is_not_for_replication,
    sys.check_constraints.is_disabled,
    sys.check_constraints.definition
from
    sys.check_constraints
        join
    sys.tables
            on sys.check_constraints.parent_object_id = sys.tables.object_id
--where
-- sys.check_constraints.object_id = @object_id'

    function Export-CheckConstraint
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ByObject')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ByTableID')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $TableID )
        {
            $objects = Get-ChildObject -TableID $TableID -Type 'C'
            foreach( $object in $objects )
            {
                Export-CheckConstraint -Object $object -ForTable:$ForTable
            }
            return
        }

        $constraint = $checkConstraintsByID[$Object.object_id]
        if( -not $ForTable )
        {
            Export-Object -ObjectID $Object.parent_object_id
        }

        if( $exportedObjects.ContainsKey($Object.object_id) )
        {
            continue
        }
            
        Export-DependentObject -ObjectID $constraint.object_id

        Write-ExportingMessage -Schema $constraint.schema_name -Name $constraint.name -Type CheckConstraint

        $notChecked = ''
        if( $constraint.is_not_trusted )
        {
            $notChecked = ' -NoCheck'
        }

        $notForReplication = ''
        if( $constraint.is_not_for_replication )
        {
            $notForReplication = ' -NotForReplication'
        }

        $schema = ConvertTo-SchemaParameter -SchemaName $constraint.schema_name
        ' Add-CheckConstraint{0} -TableName ''{1}'' -Name ''{2}'' -Expression ''{3}''{4}{5}' -f $schema,$constraint.table_name,$constraint.name,($constraint.definition -replace '''',''''''),$notForReplication,$notChecked
        if( $constraint.is_disabled )
        {
            ' Disable-Constraint{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$constraint.table_name,$constraint.name
        }
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-CheckConstraint{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$constraint.table_name,$constraint.name)
        }
        $exportedObjects[$constraint.object_id] = $true
    }

    $columnsQuery = '
-- COLUMNS
select
    sys.columns.object_id,
    sys.columns.is_nullable,
    sys.types.name as type_name,
    sys.columns.name as column_name,
    sys.types.collation_name as type_collation_name,
    sys.columns.max_length as max_length,
    sys.extended_properties.value as description,
    sys.columns.is_identity,
    sys.identity_columns.increment_value,
    sys.identity_columns.seed_value,
    sys.columns.precision,
    sys.columns.scale,
    sys.types.precision as default_precision,
    sys.types.scale as default_scale,
    sys.columns.is_sparse,
    sys.columns.collation_name,
    serverproperty(''collation'') as default_collation_name,
    sys.columns.is_rowguidcol,
    sys.types.system_type_id,
    sys.types.user_type_id,
    isnull(sys.identity_columns.is_not_for_replication, 0) as is_not_for_replication,
    sys.columns.column_id,
    sys.columns.is_xml_document,
    sys.columns.xml_collection_id,
    sys.xml_schema_collections.name as xml_schema_name,
    sys.types.max_length as default_max_length
from
    sys.columns
        inner join
    sys.types
            on columns.user_type_id = sys.types.user_type_id
        left join
    sys.extended_properties
            on sys.columns.object_id = sys.extended_properties.major_id
            and sys.columns.column_id = sys.extended_properties.minor_id
            and sys.extended_properties.name = ''MS_Description''
        left join
    sys.identity_columns
            on sys.columns.object_id = sys.identity_columns.object_id
            and sys.columns.column_id = sys.identity_columns.column_id
        left join
    sys.xml_schema_collections
            on sys.columns.xml_collection_id=sys.xml_schema_collections.xml_collection_id
'

    function Export-Column
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ForTable')]
            [int]
            $TableID
        )

        foreach( $column in ($columnsByTable[$TableID] | Sort-Object -Property 'column_id') )
        {
            $notNull = ''
            $parameters = & {
                $isBinaryVarColumn = $column.type_name -in @( 'varbinary', 'binary' )
                if( $column.type_collation_name -or $isBinaryVarColumn )
                {
                    $isSizable = $column.type_name -in @( 'binary', 'char', 'nchar', 'nvarchar', 'varbinary', 'varchar' )
                    if( $isSizable )
                    {
                        $maxLength = $column.max_length
                        if( $maxLength -eq -1 )
                        {
                            '-Max'
                        }
                        else
                        {
                            if( $column.type_name -like 'n*' )
                            {
                                $maxLength = $maxLength / 2
                            }
                            '-Size {0}' -f $maxLength
                        }
                    }

                    if( $column.collation_name -ne $column.default_collation_name -and -not $isBinaryVarColumn )
                    {
                        '-Collation'
                        '''{0}''' -f $column.collation_name
                    }
                }

                if( $column.type_name -eq 'xml' )
                {
                    if( $column.xml_schema_name )
                    {
                        if( $column.is_xml_document )
                        {
                            '-Document'
                        }
                        '-XmlSchemaCollection'
                        '''{0}''' -f $column.xml_schema_name
                    }
                }

                if( $column.is_rowguidcol )
                {
                    '-RowGuidCol'
                }

                $scaleOnlyTypes = @( 'time','datetime2', 'datetimeoffset' )
                if( $column.precision -ne $column.default_precision -and $column.type_name -notin $scaleOnlyTypes )
                {
                    '-Precision'
                    $column.precision
                }
                if( $column.scale -ne $column.default_scale )
                {
                    '-Scale'
                    $column.scale
                }

                if( $column.is_identity )
                {
                    '-Identity'
                    if( $column.seed_value -ne 1 -or $column.increment_value -ne 1 )
                    {
                        '-Seed'
                        $column.seed_value
                        '-Increment'
                        $column.increment_value
                    }
                }
                if( $column.is_not_for_replication )
                {
                    '-NotForReplication'
                }
                if( -not $column.is_nullable )
                {
                    if( -not $column.is_identity )
                    {
                        '-NotNull'
                    }
                }
                if( $column.is_sparse )
                {
                    '-Sparse'
                }
                if( $column.description )
                {
                    '-Description ''{0}''' -f ($column.description -replace '''','''''')
                }
            }

            if( $parameters )
            {
                $parameters = $parameters -join ' '
                $parameters = ' {0}' -f $parameters
            }

            if( $rivetColumnTypes -contains $column.type_name )
            {
                ' {0} ''{1}''{2}' -f $column.type_name,$column.column_name,$parameters
            }
            else
            {
                ' New-Column -DataType ''{0}'' -Name ''{1}''{2}' -f $column.type_name,$column.column_name,$parameters
            }
        }
    }

    $dataTypesQuery = '
-- DATA TYPES
select
    schema_name(sys.types.schema_id) as schema_name,
    sys.types.name,
    sys.types.max_length,
    sys.types.precision,
    sys.types.scale,
    sys.types.collation_name,
    sys.types.is_nullable,
    systype.name as from_name,
    systype.max_length as from_max_length,
    systype.precision as from_precision,
    systype.scale as from_scale,
    systype.collation_name as from_collation_name,
    sys.types.is_table_type,
    sys.table_types.type_table_object_id
from
    sys.types
        left join
    sys.types systype
            on sys.types.system_type_id = systype.system_type_id
            and sys.types.system_type_id = systype.user_type_id
        left join
    sys.table_types
            on sys.types.user_type_id = sys.table_types.user_type_id
where
    sys.types.is_user_defined = 1'

    function Export-DataType
    {
        [CmdletBinding(DefaultParameterSetName='All')]
        param(
            [Parameter(Mandatory,ParameterSetName='ByDataType')]
            [object]
            $Object
        )

        if( $ExcludeType -contains 'DataType' )
        {
            return
        }

        if( $PSCmdlet.ParameterSetName -eq 'All' )
        {
            foreach( $object in $dataTypes )
            {
                if( (Test-SkipObject -SchemaName $object.schema_name -Name $object.name) )
                {
                    continue
                }
                Export-DataType -Object $object
            }
            return
        }

        if( $exportedTypes.ContainsKey($Object.name) )
        {
            Write-Debug ('Skipping ALREADY EXPORTED {0}' -f $Object.name)
            continue
        }
        
        Export-Schema -Name $Object.schema_name

        Write-ExportingMessage -SchemaName $Object.schema_name -Name $Object.name -Type DataType

        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        if( $Object.is_table_type )
        {
            ' Add-DataType{0} -Name ''{1}'' -AsTable {{' -f $schema,$Object.name
            Export-Column -TableID $Object.type_table_object_id
            ' }'
        }
        else
        {
            $typeDef = $object.from_name
            if( $object.from_collation_name )
            {
                if( $object.max_length -ne $object.from_max_length )
                {
                    $maxLength = $object.max_length
                    if( $maxLength -eq -1 )
                    {
                        $maxLength = 'max'
                    }
                    $typeDef = '{0}({1})' -f $typeDef,$maxLength
                }
            }
            else
            {
                if( ($object.precision -ne $object.from_precision) -or ($object.scale -ne $object.from_scale) )
                {
                    $typeDef = '{0}({1},{2})' -f $typeDef,$object.precision,$object.scale
                }
            }

            if( -not $object.is_nullable )
            {
                $typeDef = '{0} not null' -f $typeDef
            }

            ' Add-DataType{0} -Name ''{1}'' -From ''{2}''' -F $schema,$Object.name,$typeDef
        }
        Push-PopOperation ('Remove-DataType{0} -Name ''{1}''' -f $schema,$Object.name)
        $exportedtypes[$object.name] = $true
    }

    $defaultConstraintsQuery = '
-- DEFAULT CONSTRAINTS
select
    schema_name(sys.tables.schema_id) as schema_name,
    sys.tables.name as table_name,
    sys.default_constraints.name as name,
    sys.columns.name as column_name,
    definition,
    sys.default_constraints.object_id,
    sys.default_constraints.parent_object_id
from
    sys.objects
        join
    sys.default_constraints
            on sys.default_constraints.object_id = sys.objects.object_id
        join
    sys.columns
            on sys.columns.object_id = sys.default_constraints.parent_object_id
            and sys.columns.column_id = sys.default_constraints.parent_column_id
        left join
    sys.tables
            on sys.objects.parent_object_id = sys.tables.object_id
        left join
    sys.schemas
            on sys.schemas.schema_id = sys.tables.schema_id
-- where
-- sys.default_constraints.object_id = @object_id'

    function Export-DefaultConstraint
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ByObject')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ByTableID')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $TableID )
        {
            $objects = Get-ChildObject -TableID $TableID -Type 'D'
            foreach( $item in $objects )
            {
                Export-DefaultConstraint -Object $item -ForTable:$ForTable
            }
            return
        }

        $constraint = $defaultConstraintsByID[$Object.object_id]
        if( -not $constraint )
        {
            Write-Warning -Message ('Unable to export default constraint [{0}].[{1}] ({2}): its metadata is missing from the databse.' -f $Object.schema_name,$Object.name,$Object.object_id)
            $exportedObjects[$Object.object_id] = $true
            return
        }

        # Default constraint isn't on a table
        if( $constraint.table_name -eq $null )
        {
            $exportedObjects[$Object.object_id] = $true
            return
        }

        if( -not $ForTable )
        {
            Export-Object -ObjectID $constraint.parent_object_id
        }

        if( $exportedObjects.ContainsKey($constraint.object_id) )
        {
            continue
        }

        Export-DependentObject -ObjectID $constraint.object_id

        Write-ExportingMessage -Schema $Object.schema_name -Name $constraint.name -Type DefaultConstraint
        $schema = ConvertTo-SchemaParameter -SchemaName $constraint.schema_name
        ' Add-DefaultConstraint{0} -TableName ''{1}'' -ColumnName ''{2}'' -Name ''{3}'' -Expression ''{4}''' -f $schema,$Object.parent_object_name,$constraint.column_name,$constraint.name,($constraint.definition -replace '''','''''')
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-DefaultConstraint{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.parent_object_name,$constraint.name)
        }
        $exportedObjects[$constraint.object_id] = $true
    }

    function Export-DependentObject
    {
        param(
            [Parameter(Mandatory)]
            [int]
            $ObjectID
        )

        $indentLevel += 1
        try
        {
            if( $dependencies.ContainsKey($ObjectID) )
            {
                foreach( $dependencyID in $dependencies[$ObjectID].Keys )
                {
                    Export-Object -ObjectID $dependencyID
                }
            }

            if( $xmlSchemaDependencies.ContainsKey($ObjectID) )
            {
                foreach( $xmlSchemaID in $xmlSchemaDependencies[$ObjectID] )
                {
                    Export-XmlSchema -ID $xmlSchemaID
                }
            }
        }
        finally
        {
            $indentLevel -= 1
        }
    }

    $foreignKeysQuery = '
-- FOREIGN KEYS
select
    sys.foreign_keys.object_id,
    is_not_trusted,
    is_not_for_replication,
    delete_referential_action_desc,
    update_referential_action_desc,
    schema_name(sys.objects.schema_id) as references_schema_name,
    sys.objects.name as references_table_name,
    sys.foreign_keys.referenced_object_id,
    is_disabled
from
    sys.foreign_keys
        join
    sys.objects
        on sys.foreign_keys.referenced_object_id = sys.objects.object_id
'

    $foreignKeyColumnsQuery = '
-- FOREIGN KEY COLUMNS
select
    sys.foreign_key_columns.constraint_object_id,
    sys.columns.name as name,
    referenced_columns.name as referenced_name,
    sys.foreign_key_columns.constraint_column_id
from
    sys.foreign_key_columns
        join
    sys.columns
            on sys.foreign_key_columns.parent_object_id = sys.columns.object_id
            and sys.foreign_key_columns.parent_column_id = sys.columns.column_id
        join
    sys.columns as referenced_columns
            on sys.foreign_key_columns.referenced_object_id = referenced_columns.object_id
            and sys.foreign_key_columns.referenced_column_id = referenced_columns.column_id
'

    function Export-ForeignKey
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )

        # Make sure the key's table is exported.
        Export-Object -ObjectID $Object.parent_object_id

        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        $foreignKey = $foreignKeysByID[$Object.object_id]

        # Make sure the key's referenced table is exported.
        Export-Object -ObjectID $foreignKey.referenced_object_id

        $referencesSchema = ConvertTo-SchemaParameter -SchemaName $foreignKey.references_schema_name -ParameterName 'ReferencesSchema'
        $referencesTableName = $foreignKey.references_table_name

        $columns = $foreignKeyColumnsByObjectID[$Object.object_id] | Sort-Object -Property 'constraint_column_id'

        $columnNames = $columns | Select-Object -ExpandProperty 'name'
        $referencesColumnNames = $columns | Select-Object -ExpandProperty 'referenced_name'

        $onDelete = ''
        if( $foreignKey.delete_referential_action_desc -ne 'NO_ACTION' )
        {
            $onDelete = ' -OnDelete ''{0}''' -f $foreignKey.delete_referential_action_desc
        }

        $onUpdate = ''
        if( $foreignKey.update_referential_action_desc -ne 'NO_ACTION' )
        {
            $onUpdate = ' -OnUpdate ''{0}''' -f $foreignKey.update_referential_action_desc
        }

        $notForReplication = ''
        if( $foreignKey.is_not_for_replication )
        {
            $notForReplication = ' -NotForReplication'
        }

        $noCheck = ''
        if( $foreignKey.is_not_trusted )
        {
            $noCheck = ' -NoCheck'
        }

        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type ForeignKey

        ' Add-ForeignKey{0} -TableName ''{1}'' -ColumnName ''{2}''{3} -References ''{4}'' -ReferencedColumn ''{5}'' -Name ''{6}''{7}{8}{9}{10}' -f $schema,$Object.parent_object_name,($columnNames -join ''','''),$referencesSchema,$referencesTableName,($referencesColumnNames -join ''','''),$Object.name,$onDelete,$onUpdate,$notForReplication,$noCheck
        if( $foreignKey.is_disabled )
        {
            ' Disable-Constraint{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.parent_object_name,$Object.name
        }
        Push-PopOperation ('Remove-ForeignKey{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.parent_object_name,$Object.name)
        $exportedObjects[$Object.object_id] = $true
    }

    $indexesQuery = '
-- INDEXES
select
    sys.indexes.object_id,
    schema_name(sys.tables.schema_id) as schema_name,
    sys.indexes.name,
    sys.tables.name as table_name,
    sys.indexes.is_unique,
    sys.indexes.type_desc,
    sys.indexes.has_filter,
    sys.indexes.filter_definition,
    sys.indexes.index_id
from
    sys.indexes
        join
    sys.tables
            on sys.indexes.object_id = sys.tables.object_id
where
    is_primary_key = 0 and
    sys.indexes.type != 0 and
    sys.indexes.is_unique_constraint != 1 and
    sys.tables.is_ms_shipped = 0'


    $indexesColumnsQuery = '
-- INDEX COLUMNS
select
    sys.indexes.object_id,
    sys.indexes.index_id,
    sys.columns.name,
    sys.index_columns.key_ordinal,
    sys.index_columns.is_included_column,
    sys.index_columns.is_descending_key
from
    sys.indexes
        join
    sys.index_columns
            on sys.indexes.object_id = sys.index_columns.object_id
            and sys.indexes.index_id = sys.index_columns.index_id
        join
    sys.columns
            on sys.indexes.object_id = sys.columns.object_id
            and sys.index_columns.column_id = sys.columns.column_id
-- where
-- sys.indexes.object_id = @object_id and
-- sys.indexes.index_id = @index_id
'

    function Export-Index
    {
        [CmdletBinding(DefaultParameterSetName='All')]
        param(
            [Parameter(Mandatory,ParameterSetName='ByIndex')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ByTable')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $PSCmdlet.ParameterSetName -eq 'All' )
        {
            foreach( $object in $indexes )
            {
                if( (Test-SkipObject -SchemaName $Object.schema_name -Name $Object.name) -or $ExcludeType -contains 'Index' )
                {
                    continue
                }

                Export-Index -Object $object -ForTable:$ForTable
            }
            return
        }
        elseif( $PSCmdlet.ParameterSetName -eq 'ByTable' )
        {
            foreach( $object in $indexesByObjectID[$TableID] )
            {
                Export-Index -Object $object -ForTable:$ForTable
            }
            return
        }

        if( -not $ForTable )
        {
            Export-Object -ObjectID $Object.object_id
        }

        $indexKey = '{0}_{1}' -f $Object.object_id,$Object.index_id
        if( $exportedIndexes.ContainsKey($indexKey) )
        {
            return
        }

        Export-DependentObject -ObjectID $Object.object_id

        $unique = ''
        if( $Object.is_unique )
        {
            $unique = ' -Unique'
        }
        $clustered = ''
        if( $Object.type_desc -eq 'CLUSTERED' )
        {
            $clustered = ' -Clustered'
        }
        $where = ''
        if( $Object.has_filter )
        {
            $where = ' -Where ''{0}''' -f $Object.filter_definition
        }

        $allColumns = $indexColumnsByObjectID[$Object.object_id] | Where-Object { $_.index_id -eq $Object.index_id }

        $includedColumns = $allColumns | Where-Object { $_.is_included_column } | Sort-Object -Property 'name' # I don't think order matters so order them discretely.
        $include = ''
        if( $includedColumns )
        {
            $include = ' -Include ''{0}''' -f (($includedColumns | Select-Object -ExpandProperty 'name') -join ''',''')
        }

        $columns = $allColumns | Where-Object { -not $_.is_included_column } | Sort-Object -Property 'key_ordinal'

        $descending = ''
        if( $columns | Where-Object { $_.is_descending_key } )
        {
            $descending = $columns | Select-Object -ExpandProperty 'is_descending_key' | ForEach-Object { if( $_ ) { '$true' } else { '$false' } }
            $descending = ' -Descending {0}' -f ($descending -join ',')
        }

        $columnNames = $columns | Select-Object -ExpandProperty 'name'
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type Index
        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        ' Add-Index{0} -TableName ''{1}'' -ColumnName ''{2}'' -Name ''{3}''{4}{5}{6}{7}{8}' -f $schema,$Object.table_name,($columnNames -join ''','''),$Object.name,$clustered,$unique,$include,$descending,$where
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-Index{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.table_name,$Object.name)
        }
        $exportedIndexes[$indexKey] = $true
    }

    function Get-ModuleDefinition
    {
        param(
            [Parameter(Mandatory)]
            [int]
            $ObjectID
        )

        $modulesByID[$ObjectID].definition
    }

    $objectsQuery = '
-- OBJECTS
select
    sys.schemas.name as schema_name,
    sys.objects.name as object_name,
    sys.objects.name as name,
    sys.schemas.name + ''.'' + sys.objects.name as full_name,
    sys.extended_properties.value as description,
    parent_objects.name as parent_object_name,
    sys.objects.object_id as object_id,
    RTRIM(sys.objects.type) as type,
    sys.objects.type_desc,
    sys.objects.parent_object_id
from
    sys.objects
        join
    sys.schemas
            on sys.objects.schema_id = sys.schemas.schema_id
        left join
    sys.extended_properties
            on sys.objects.object_id = sys.extended_properties.major_id
            and sys.extended_properties.minor_id = 0
            and sys.extended_properties.name = ''MS_Description''
        left join
    sys.objects parent_objects
        on sys.objects.parent_object_id = parent_objects.object_id
where
    sys.objects.is_ms_shipped = 0 and
    (parent_objects.is_ms_shipped is null or parent_objects.is_ms_shipped = 0) and
    sys.schemas.name != ''rivet'''

    function Export-Object
    {
        [CmdletBinding(DefaultParameterSetName='All')]
        param(
            [Parameter(Mandatory,ParameterSetName='ByObjectID')]
            [int[]]
            $ObjectID = @()
        )

        $filteredObjects = $objects
        if( $PSCmdlet.ParameterSetName -eq 'ByObjectID' )
        {
            $filteredObjects = $ObjectID | ForEach-Object { $objectsByID[$_] }
        }

        foreach( $object in $filteredObjects )
        {
            if( $exportedObjects.ContainsKey($object.object_id) )
            {
                Write-Debug ('Skipping ALREADY EXPORTED {0}' -f $object.full_name)
                continue
            }

            if( (Test-SkipObject -SchemaName $object.schema_name -Name $object.object_name -Type $object.type_desc) )
            {
                continue
            }

            if( $object.schema_name -eq 'rivet' )
            {
                continue
            }

            Export-Schema -Name $object.schema_name

            Export-DependentObject -ObjectID $object.object_id

            if( $exportedObjects.ContainsKey($object.object_id) )
            {
                continue
            }

            if( $externalDependencies.ContainsKey($object.object_id) )
            {
                Write-Warning -Message ('Unable to export {0} {1}: it depends on external object {2}.' -f $object.type_desc,$object.full_name,$externalDependencies[$object.object_id])
                $exportedObjects[$object.object_id] = $true
                continue
            }

            switch ($object.type_desc)
            {
                'CHECK_CONSTRAINT'
                {
                    Export-CheckConstraint -Object $object
                    break
                }
                'DEFAULT_CONSTRAINT'
                {
                    Export-DefaultConstraint -Object $object
                    break
                }
                'FOREIGN_KEY_CONSTRAINT'
                {
                    Export-ForeignKey -Object $object
                    break
                }
                'PRIMARY_KEY_CONSTRAINT'
                {
                    Export-PrimaryKey -Object $object
                    break
                }
                'SQL_INLINE_TABLE_VALUED_FUNCTION'
                {
                    Export-UserDefinedFunction -Object $object
                    break
                }
                'SQL_SCALAR_FUNCTION'
                {
                    Export-UserDefinedFunction -Object $object
                    break
                }
                'SQL_STORED_PROCEDURE'
                {
                    Export-StoredProcedure -Object $object
                    break
                }
                'SQL_TABLE_VALUED_FUNCTION'
                {
                    Export-UserDefinedFunction -Object $object
                    break
                }
                'SQL_TRIGGER'
                {
                    Export-Trigger -Object $object
                    break
                }
                'SYNONYM'
                {
                    Export-Synonym -Object $object
                    break
                }
                'UNIQUE_CONSTRAINT'
                {
                    Export-UniqueKey -Object $object
                    break
                }
                'USER_TABLE'
                {
                    Export-Table -Object $object
                    break
                }
                'VIEW'
                {
                    Export-View -Object $object
                    break
                }

                default
                {
                    Write-Error -Message ('Unable to export object "{0}": unsupported object type "{1}".' -f $object.full_name,$object.type_desc)
                }
            }
            $exportedObjects[$object.object_id] = $true
        }
    }

    # PRIMARY KEYS
    $primaryKeysQuery = '
-- PRIMARY KEYS
select
    sys.key_constraints.object_id,
    sys.indexes.type_desc
from
    sys.key_constraints
        join
    sys.indexes
            on sys.key_constraints.parent_object_id = sys.indexes.object_id
            and sys.key_constraints.unique_index_id = sys.indexes.index_id
where
    sys.key_constraints.type = ''PK''
    and sys.key_constraints.is_ms_shipped = 0'


    # PRIMARY KEY COLUMNS
    $primaryKeyColumnsQuery = '
-- PRIMARY KEY COLUMNS
select
    sys.objects.object_id,
    sys.schemas.name as schema_name,
    sys.tables.name as table_name,
    sys.columns.name as column_name,
    sys.indexes.type_desc,
    sys.index_columns.key_ordinal
from
    sys.objects
    join sys.tables
        on sys.objects.parent_object_id = sys.tables.object_id
    join sys.schemas
        on sys.schemas.schema_id = sys.tables.schema_id
    join sys.indexes
        on sys.indexes.object_id = sys.tables.object_id
    join sys.index_columns
        on sys.indexes.object_id = sys.index_columns.object_id
        and sys.indexes.index_id = sys.index_columns.index_id
    join sys.columns
        on sys.indexes.object_id = sys.columns.object_id
        and sys.columns.column_id = sys.index_columns.column_id
where
-- sys.objects.object_id = @object_id and
    sys.objects.type = ''PK'' and
    sys.indexes.is_primary_key = 1'


    function Export-PrimaryKey
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ByObject')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ByTableID')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $TableID )
        {
            $Object = Get-ChildObject -TableID $TableID -Type 'PK'
            if( -not $Object )
            {
                return
            }
        }

        if( -not $ForTable )
        {
            Export-Object -ObjectID $Object.parent_object_id
        }

        if( $exportedObjects.ContainsKey($Object.object_id) )
        {
            return
        }

        Export-DependentObject -ObjectID $Object.object_id

        $primaryKey = $primaryKeysByID[$Object.object_id]
        $columns = $primaryKeyColumnsByObjectID[$Object.object_id]
        if( -not $columns )
        {
            # PK on a table-valued function.
            $exportedObjects[$Object.object_id] = $true
            return        
        }

        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        $columnNames = $columns | Sort-Object -Property 'key_ordinal' | Select-Object -ExpandProperty 'column_name'
        $nonClustered = ''
        if( $primaryKey.type_desc -eq 'NONCLUSTERED' )
        {
            $nonClustered = ' -NonClustered'
        }
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type PrimaryKey
        ' Add-PrimaryKey{0} -TableName ''{1}'' -ColumnName ''{2}'' -Name ''{3}''{4}' -f $schema,$Object.parent_object_name,($columnNames -join ''','''),$object.object_name,$nonClustered
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-PrimaryKey{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.parent_object_name,$object.object_name)
        }
        $exportedObjects[$Object.object_id] = $true
    }

    $schemasQuery = '
-- SCHEMAS
select
    sys.schemas.name,
    sys.sysusers.name as owner
from
    sys.schemas
        join
    sys.sysusers
        on sys.schemas.principal_id = sys.sysusers.uid'

    function Export-Schema
    {
        param(
            [Parameter(Mandatory)]
            [string]
            $Name
        )

        if( $exportedSchemas.ContainsKey($Name) )
        {
            return
        }

        $schema = $schemasByName[$Name]
        if( -not $schema )
        {
            return
        }

        Write-ExportingMessage -Schema $Object.schema_name -Type Schema
        ' Add-Schema -Name ''{0}'' -Owner ''{1}''' -f $schema.name,$schema.owner
        $exportedSchemas[$schema.name] = $true
        Push-PopOperation ('Remove-Schema -Name ''{0}''' -f $schema.name)
    }

    function Export-StoredProcedure
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )

        Export-DependentObject -ObjectID $Object.object_id

        $query = 'select definition from sys.sql_modules where object_id = @object_id'
        $definition = Get-ModuleDefinition -ObjectID $Object.object_id

        try
        {
            if( -not $definition )
            {
                Write-Warning -Message ('Unable to export stored procedure [{0}].[{1}]: definition not readable.' -f $Object.schema_name,$Object.name)
                return
            }

            $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
            $createPreambleRegex = '^CREATE\s+procedure\s+\[{0}\]\.\[{1}\]\s+' -f [regex]::Escape($Object.schema_name),[regex]::Escape($Object.object_name)
            Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type StoredProcedure
            if( $definition -match $createPreambleRegex )
            {
                $definition = $definition -replace $createPreambleRegex,''
                ' Add-StoredProcedure{0} -Name ''{1}'' -Definition @''{2}{3}{2}''@' -f $schema,$Object.object_name,[Environment]::NewLine,$definition
            }
            else
            {
                ' Invoke-Ddl -Query @''{0}{1}{0}''@' -f [Environment]::NewLine,$definition
            }
            Push-PopOperation ('Remove-StoredProcedure{0} -Name ''{1}''' -f $schema,$Object.object_name)
        }
        finally
        {
            $exportedObjects[$Object.object_id] = $true
        }
    }

    $synonymsQuery = '
-- SYNONYMS
select
    sys.synonyms.object_id,
    parsename(base_object_name,3) as database_name,
    parsename(base_object_name,2) as schema_name,
    parsename(base_object_name,1) as object_name,
    sys.objects.object_id as target_object_id
from
    sys.synonyms
        left join
    sys.objects
            on parsename(sys.synonyms.base_object_name,2) = schema_name(sys.objects.schema_id)
            and parsename(sys.synonyms.base_object_name,1) = sys.objects.name
'

    function Export-Synonym
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )
        
        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        $synonym = $synonymsByID[$Object.object_id]

        if( $synonym.target_object_id -and $synonym.target_object_id -ne $synonym.object_id )
        {
            Export-Object -ObjectID $synonym.target_object_id
        }

        if( $synonym.database_name -and $synonym.database_name -ne $Database )
        {
            Write-Warning -Message ('Unable to export SYNONYM {0}.{1}: it depends on external object [{2}].[{3}].[{4}].' -f $Object.schema_name,$Object.name,$synonym.database_name,$synonym.schema_name,$synonym.object_name)
            $exportedObjects[$Object.object_id] = $true
            return
        }

        $targetDBName = ''
        if( $synonym.database_name )
        {
            $targetDBName = ' -TargetDatabaseName ''{0}''' -f $synonym.database_name
        }

        $targetSchemaName = ''
        if( $synonym.schema_name )
        {
            $targetSchemaName = ' -TargetSchemaName ''{0}''' -f $synonym.schema_name
        }

        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type Synonym
        ' Add-Synonym{0} -Name ''{1}''{2}{3} -TargetObjectName ''{4}''' -f $schema,$Object.name,$targetDBName,$targetSchemaName,$synonym.object_name
        Push-PopOperation ('Remove-Synonym{0} -Name ''{1}''' -f $schema,$Object.name)
        $exportedObjects[$Object.object_id] = $true
    }

    function Export-Table
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )

        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name

        $description = $Object.description
        if( $description )
        {
            $description = ' -Description ''{0}''' -f ($description -replace '''','''''')
        }

        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type Table
        ' Add-Table{0} -Name ''{1}''{2} -Column {{' -f $schema,$object.object_name,$description
        Export-Column -TableID $object.object_id
        ' }'

        $exportedObjects[$object.object_id] = $true

        Export-PrimaryKey -TableID $Object.object_id -ForTable
        Export-DefaultConstraint -TableID $Object.object_id -ForTable
        Export-CheckConstraint -TableID $Object.object_id -ForTable
        Export-Index -TableID $Object.object_id -ForTable
        Export-UniqueKey -TableID $Object.object_id -ForTable
        Export-Trigger -TableID $Object.object_id -ForTable

        # Do this last because table objects can reference other objects and those would need to get removed before the table
        Push-PopOperation ('Remove-Table{0} -Name ''{1}''' -f $schema,$object.object_name)
    }

    $triggersQuery = '
-- TRIGGERS
select
    sys.triggers.name,
    schema_name(sys.objects.schema_id) as schema_name,
    sys.triggers.object_id,
    sys.triggers.parent_id
from
    sys.triggers
        join
    sys.objects
        on sys.triggers.object_id = sys.objects.object_id'

    function Export-Trigger
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ByTrigger')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ByTable')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $PSCmdlet.ParameterSetName -eq 'ByTable' )
        {
            foreach( $object in $triggersByTable[$TableID] )
            {
                Export-Trigger -Object $object -ForTable:$ForTable
            }
            return
        }

        if( -not $ForTable )
        {
            Export-Object -ObjectID $Object.parent_object_id
        }

        if( $exportedObjects.ContainsKey($Object.object_id) )
        {
            return
        }

        Export-DependentObject -ObjectID $Object.object_id

        $schema = ConvertTo-SchemaParameter -SchemaName $object.schema_name
        $trigger = Get-ModuleDefinition -ObjectID $Object.object_id
        $createPreambleRegex = '^create\s+trigger\s+\[{0}\]\.\[{1}\]\s+' -f [regex]::Escape($Object.schema_name),[regex]::Escape($Object.name)
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type Trigger
        if( $trigger -match $createPreambleRegex )
        {
            $trigger = $trigger -replace $createPreambleRegex,''
            ' Add-Trigger{0} -Name ''{1}'' -Definition @''{2}{3}{2}''@' -f $schema,$Object.name,[Environment]::NewLine,$trigger
        }
        else
        {
            ' Invoke-Ddl -Query @''{0}{1}{0}''@' -f [Environment]::NewLine,$trigger
        }
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-Trigger{0} -Name ''{1}''' -f $schema,$Object.name)
        }
        $exportedObjects[$Object.object_id] = $true
    }

    $uniqueKeysQuery = '
-- UNIQUE KEYS
select
    sys.key_constraints.name,
    schema_name(sys.key_constraints.schema_id) as schema_name,
    sys.key_constraints.object_id,
    sys.tables.name as parent_object_name,
    sys.key_constraints.parent_object_id,
    sys.indexes.type_desc
from
    sys.key_constraints
        join
    sys.tables
            on sys.key_constraints.parent_object_id = sys.tables.object_id
        join
    sys.indexes
            on sys.indexes.object_id = sys.tables.object_id
            and sys.key_constraints.unique_index_id = sys.indexes.index_id
where
    sys.key_constraints.type = ''UQ'''


    $uniqueKeysColumnsQuery = '
-- UNIQUE KEY COLUMNS
select
    sys.key_constraints.object_id,
    sys.columns.name
from
    sys.key_constraints
        join
    sys.indexes
            on sys.key_constraints.parent_object_id = sys.indexes.object_id
            and sys.key_constraints.unique_index_id = sys.indexes.index_id
        join
    sys.index_columns
            on sys.indexes.object_id = sys.index_columns.object_id
            and sys.indexes.index_id = sys.index_columns.index_id
        join
    sys.columns
            on sys.indexes.object_id = sys.columns.object_id
            and sys.index_columns.column_id = sys.columns.column_id
where
    sys.key_constraints.type = ''UQ'''

    function Export-UniqueKey
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ByKey')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ForTable')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $PSCmdlet.ParameterSetName -eq 'ForTable' )
        {
            foreach( $object in $uniqueKeysByTable[$TableID] )
            {
                Export-UniqueKey -Object $object -ForTable:$ForTable
            }
            return
        }

        if( -not $ForTable )
        {
            Export-Object -ObjectID $Object.parent_object_id
        }

        if( $exportedObjects.ContainsKey($Object.object_id) )
        {
            return
        }

        Export-DependentObject -ObjectID $Object.object_id

        $uniqueKey = $uniqueKeysByID[$Object.object_id]

        $columns = $uniqueKeyColumnsByObjectID[$Object.object_id]
        $columnNames = $columns | Select-Object -ExpandProperty 'name'
        $clustered = ''
        if( $uniqueKey.type_desc -eq 'CLUSTERED' )
        {
            $clustered = ' -Clustered'
        }
        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type UniqueKey
        ' Add-UniqueKey{0} -TableName ''{1}'' -ColumnName ''{2}''{3} -Name ''{4}''' -f $schema,$Object.parent_object_name,($columnNames -join ''','''),$clustered,$Object.name
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-UniqueKey{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.parent_object_name,$Object.name)
        }
        $exportedObjects[$Object.object_id] = $true
    }

    function Export-UserDefinedFunction
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )

        Export-DependentObject -ObjectID $Object.object_id

        $schema = ConvertTo-SchemaParameter -SchemaName $object.schema_name
        $function = Get-ModuleDefinition -ObjectID $Object.object_id
        $createPreambleRegex = '^create\s+function\s+\[{0}\]\.\[{1}\]\s+' -f [regex]::Escape($Object.schema_name),[regex]::Escape($Object.name)
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type Function
        if( $function -match $createPreambleRegex )
        {
            $function = $function -replace $createPreambleRegex,''
            ' Add-UserDefinedFunction{0} -Name ''{1}'' -Definition @''{2}{3}{2}''@' -f $schema,$Object.name,[Environment]::NewLine,$function
        }
        else
        {
            ' Invoke-Ddl -Query @''{0}{1}{0}''@' -f [Environment]::NewLine,$function
        }
        Push-PopOperation ('Remove-UserDefinedFunction{0} -Name ''{1}''' -f $schema,$Object.name)
        $exportedObjects[$Object.object_id] = $true
    }

    function Export-View
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )

        Export-DependentObject -ObjectID $Object.object_id

        $schema = ConvertTo-SchemaParameter -SchemaName $object.schema_name
        $query = 'select definition from sys.sql_modules where object_id = @view_id'
        $view = Get-ModuleDefinition -ObjectID $Object.object_id
        $createPreambleRegex = '^CREATE\s+view\s+\[{0}\]\.\[{1}\]\s+' -f [regex]::Escape($Object.schema_name),[regex]::Escape($Object.name)
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type View
        if( $view -match $createPreambleRegex )
        {
            $view = $view -replace $createPreambleRegex,''
            ' Add-View{0} -Name ''{1}'' -Definition @''{2}{3}{2}''@' -f $schema,$Object.name,[Environment]::NewLine,$view
        }
        else
        {
            ' Invoke-Ddl -Query @''{0}{1}{0}''@' -f [Environment]::NewLine,$view
        }
        Push-PopOperation ('Remove-View{0} -Name ''{1}''' -f $schema,$Object.name)
        $exportedObjects[$Object.object_id] = $true
    }

    $xmlSchemaQuery = '
select
    schema_name(schema_id) as schema_name,
    name,
    xml_collection_id,
    XML_SCHEMA_NAMESPACE(schema_name(schema_id),sys.xml_schema_collections.name) as xml_schema
from
    sys.xml_schema_collections
where
    sys.xml_schema_collections.name != ''sys'''

    function Export-XmlSchema
    {
        param(
            [Parameter(Mandatory)]
            [int]
            $ID
        )

        if( $exportedXmlSchemas.ContainsKey($ID) )
        {
            return
        }

        if( $ExcludeType -contains 'XmlSchema' )
        {
            return
        }

        $xmlSchema = $xmlSchemasByID[$ID]

        Write-ExportingMessage -SchemaName $xmlSchema.schema_name -Name $xmlSchema.name -Type XmlSchema
        ' Invoke-Ddl @'''
        'create xml schema collection [{0}].[{1}] as' -f $xmlSchema.schema_name,$xmlSchema.name
        'N'''
        $xmlschema.xml_schema
        ''''
        '''@'
        Push-PopOperation ('Invoke-Ddl ''drop xml schema collection [{0}].[{1}]''' -f $xmlSchema.schema_name,$xmlSchema.name)
        $exportedXmlSchemas[$ID] = $true
    }

    function Push-PopOperation
    {
        param(
            [Parameter(Mandatory)]
            $InputObject
        )

        if( -not ($popsHash.ContainsKey($InputObject)) )
        {
            $pops.Push($InputObject)
            $popsHash[$InputObject] = $true
        }
    }

    $objectTypesToExclude = @()
    if( $ExcludeType )
    {
        $objectTypesToExclude = $ExcludeType | ForEach-Object { $exclusionTypeMap[$_] }
    }
    function Test-SkipObject
    {
        param(
            [Parameter(Mandatory)]
            [string]
            $SchemaName,

            [Parameter(Mandatory)]
            [string]
            $Name,

            [string]
            $Type
        )

        if( -not $Include -and -not $ExcludeType -and -not $Exclude )
        {
            return $false
        }

        $fullName = '{0}.{1}' -f $SchemaName,$Name

        if( $Type )
        {
            if( $objectTypesToExclude -contains $Type )
            {
                Write-Debug ('Skipping EXCLUDED TYPE {0} {1}' -f $fullName,$Type)
                return $true
            }
        }

        if( $Include )
        {
            $skip = $true
            foreach( $filter in $Include )
            {
                if( $fullName -like $filter )
                {
                    $skip = $false
                    break
                }
            }
            if( $skip )
            {
                return $true
            }
        }

        if( $Exclude )
        {
            foreach( $filter in $Exclude )
            {
                if( $fullName -like $filter )
                {
                    return $true
                }
            }
        }

        return $false
    }

    function Write-ExportingMessage
    {
        [CmdletBinding(DefaultParameterSetName='Schema')]
        param(
            [Parameter(Mandatory)]
            [string]
            $SchemaName,

            [Parameter(Mandatory,ParameterSetName='NotSchema')]
            [string]
            $Name,

            [Parameter(Mandatory)]
            [ValidateSet('Table','View','DefaultConstraint','StoredProcedure','Synonym','ForeignKey','CheckConstraint','PrimaryKey','Trigger','Function','Index','DataType','Schema','UniqueKey','XmlSchema')]
            [string]
            $Type
        )

        $objectName = $SchemaName
        if( $Name )
        {
            $objectName = '{0}.{1}' -f $objectName,$Name
        }

        $message = '{0,-17} {1}{2}' -f $Type,(' ' * $indentLevel),$objectName
        $timer.CurrentOperation = $message
        $timer.ExportCount += 1
        Write-Verbose -Message $message
    }

    $activity = 'Exporting migrations from {0}.{1}' -f $SqlServerName,$Database
    $writeProgress = [Environment]::UserInteractive
    if( $NoProgress )
    {
        $writeProgress = $false
    }
    $event = $null

    Connect-Database -SqlServerName $SqlServerName -Database $Database -ErrorAction Stop | Out-Null
    try
    {
        #region QUERIES
        # OBJECTS
        $objects = Invoke-Query -Query $objectsQuery
        $objects | ForEach-Object { $objectsByID[$_.object_id] = $_ }
        $objects | Group-Object -Property 'parent_object_id' | ForEach-Object { $objectsByParentID[[int]$_.Name] = $_.Group }
        $objectTypes = $objects | Select-Object -ExpandProperty 'type_desc' | Select-Object -Unique

        # CHECK CONSTRAINTS
        if( $objectTypes -contains 'CHECK_CONSTRAINT' )
        {
            $checkConstraints = Invoke-Query -Query $checkConstraintsQuery
            $checkConstraints | ForEach-Object { $checkConstraintsByID[$_.object_id] = $_ }
        }

        # DATA TYPES
        $dataTypes = Invoke-Query -Query $dataTypesQuery

        # COLUMNS
        if( $objectTypes -contains 'USER_TABLE' -or $dataTypes )
        {
            $columns = Invoke-Query -Query $columnsQuery
            $columns | Group-Object -Property 'object_id' | ForEach-Object { $columnsByTable[[int]$_.Name] = $_.Group }
        }

        # DEFAULT CONSTRAINTS
        if( $objectTypes -contains 'DEFAULT_CONSTRAINT' )
        {
            $defaultConstraints = Invoke-Query -Query $defaultConstraintsQuery #-Parameter @{ '@object_id' = $constraintObject.object_id }
            $defaultConstraints | ForEach-Object { $defaultConstraintsByID[$_.object_id] = $_ }
        }

        # FOREIGN KEYS
        if( $objectTypes -contains 'FOREIGN_KEY_CONSTRAINT' )
        {
            $foreignKeys = Invoke-Query -Query $foreignKeysQuery
            $foreignKeys | ForEach-Object { $foreignKeysByID[$_.object_id] = $_ }

            # FOREIGN KEY COLUMNS
            $foreignKeyColumns = Invoke-Query -Query $foreignKeyColumnsQuery
            $foreignKeyColumns | Group-Object -Property 'constraint_object_id' | ForEach-Object { $foreignKeyColumnsByObjectID[[int]$_.Name] = $_.Group }
        }

        # INDEXES
        if( $objectTypes -contains 'USER_TABLE' )
        {
            $indexes = Invoke-Query -Query $indexesQuery
            $indexes | Group-Object -Property 'object_id' | ForEach-Object { $indexesByObjectID[[int]$_.Name] = $_.Group }

            # INDEX COLUMNS
            $indexColumns = Invoke-Query -Query $indexesColumnsQuery
            $indexColumns | Group-Object -Property 'object_id' | ForEach-Object { $indexColumnsByObjectID[[int]$_.Name] = $_.Group }
        }

        if( $objectTypes -contains 'PRIMARY_KEY_CONSTRAINT' )
        {
            $primaryKeys = Invoke-Query -Query $primaryKeysQuery
            $primaryKeys | ForEach-Object { $primaryKeysByID[$_.object_id] = $_ }

            $primaryKeyColumns = Invoke-Query -Query $primaryKeyColumnsQuery
            $primaryKeyColumns | Group-Object -Property 'object_id' | ForEach-Object { $primaryKeyColumnsByObjectID[[int]$_.Name] = $_.Group }
        }

        # SCHEMAS
        if( ($objects | Where-Object { $_.schema_name -ne 'dbo' }) -or ($dataTypes | Where-Object { $_.schema_name -ne 'dbo' }) )
        {
            $schemas = Invoke-Query -Query $schemasQuery
            $schemas | ForEach-Object { $schemasByName[$_.name] = $_ }
        }

        # MODULES/PROGRAMMABILITY
        if( $objectTypes -contains 'SQL_INLINE_TABLE_VALUED_FUNCTION' -or $objectTypes -contains 'SQL_SCALAR_FUNCTION' -or $objectTypes -contains 'SQL_STORED_PROCEDURE' -or $objectTypes -contains 'SQL_TABLE_VALUED_FUNCTION' -or $objectTypes -contains 'SQL_TRIGGER' -or $objectTypes -contains 'VIEW' )
        {
            $query = 'select object_id, definition from sys.sql_modules'
            $modules = Invoke-Query -Query $query
            $modules | ForEach-Object { $modulesByID[$_.object_id] = $_ }
        }

        # SYNONYMS
        if( $objectTypes -contains 'SYNONYM' )
        {
            $synonyms = Invoke-Query -Query $synonymsQuery
            $synonyms | ForEach-Object { $synonymsByID[$_.object_id] = $_ }
        }

        # TRIGGERS
        if( $objectTypes -contains 'SQL_TRIGGER' )
        {
            $triggers = Invoke-Query -Query $triggersQuery
            $triggers | ForEach-Object { $triggersByID[$_.object_id] = $_ }        
            $triggers | Group-Object -Property 'parent_id' | ForEach-Object { $triggersByTable[[int]$_.Name] = $_.Group }
        }

        if( $objectTypes -contains 'UNIQUE_CONSTRAINT' )
        {
            # UNIQUE KEYS
            $uniqueKeys =  Invoke-Query -Query $uniqueKeysQuery
            $uniqueKeys | ForEach-Object { $uniqueKeysByID[$_.object_id] = $_ }
            $uniqueKeys | Group-Object -Property 'parent_object_id' | ForEach-Object { $uniqueKeysByTable[[int]$_.Name] = $_.Group }
        
            # UNIQUE KEY COLUMNS
            $uniqueKeyColumns = Invoke-Query -Query $uniqueKeysColumnsQuery
            $uniqueKeyColumns | Group-Object -Property 'object_id' | ForEach-Object { $uniqueKeyColumnsByObjectID[[int]$_.Name] = $_.Group }
        }

        if( $columns | Where-Object { $_.xml_collection_id } )
        {
            $query = '
select
    sys.columns.object_id,
    sys.columns.xml_collection_id
from
    sys.columns
        join
    sys.types
            on sys.columns.user_type_id=sys.types.user_type_id
            and sys.columns.system_type_id=sys.types.system_type_id
where
    sys.types.name = ''xml'' and
    sys.columns.xml_collection_id != 0
'

            $objectsWithXmlSchemas = Invoke-Query -Query $query
            $objectsWithXmlSchemas | Group-Object -Property 'object_id' | ForEach-Object { $xmlSchemaDependencies[[int]$_.Name] = $_.Group | Select-Object -ExpandProperty 'xml_collection_id' | Select-Object -Unique }

            $xmlSchemas = Invoke-Query -Query $xmlSchemaQuery
            $xmlSchemas | ForEach-Object { $xmlSchemasByID[$_.xml_collection_id] = $_ }
        }
        #endregion

        $sysDatabases = @( 'master', 'model', 'msdb', 'tempdb' )
        $query = 'select * from sys.sql_expression_dependencies'
        foreach( $row in (Invoke-Query -Query $query) )
        {
            $externalName = '[{0}]' -f $row.referenced_entity_name
            if( $row.referenced_schema_name )
            {
                $externalName = '[{0}].{1}' -f $row.referenced_schema_name,$externalName
            }
            if( $row.referenced_database_name )
            {
                # Allow references to system databases.
                if( $row.referenced_database_name -in $sysDatabases )
                {
                    continue
                }

                $externalName = '[{0}].{1}' -f $row.referenced_database_name,$externalName
            }
            if( $row.referenced_server_name )
            {
                $externalName = '[{0}].{1}' -f $row.referenced_server_name,$externalName
            }

            if( $row.referenced_server_name -or ($row.referenced_database_name -ne $null -and $row.referenced_database_name -ne $Database) )
            {
                $externalDependencies[$row.referencing_id] = $externalName
            }
            else
            {
                if( -not $dependencies.ContainsKey($row.referencing_id) )
                {
                    $dependencies[$row.referencing_id] = @{}
                }
                if( $row.referenced_id -ne $null -and $row.referenced_id -ne $row.referencing_id )
                {
                    $dependencies[$row.referencing_id][$row.referenced_id] = $externalName
                }
            }
        }

        $totalOperationCount = & {
                                    $objects
                                    $schemas
                                    $indexes
                                    $dataTypes
                                } | 
            Measure-Object | 
            Select-Object -ExpandProperty 'Count'

        if( $writeProgress )
        {
            Write-Progress -Activity $activity 
        }

        $timer | 
            Add-Member -Name 'ExportCount' -Value 0 -MemberType NoteProperty -PassThru |
            Add-Member -MemberType NoteProperty -Name 'Activity' -Value $activity -PassThru |
            Add-Member -MemberType NoteProperty -Name 'CurrentOperation' -Value '' -PassThru |
            Add-Member -MemberType NoteProperty -Name 'TotalCount' -Value $totalOperationCount
        
        if( $writeProgress )
        {
            # Write-Progress is *expensive*. Only do it if the user is interactive and only every 1/10th of a second.
            $event = Register-ObjectEvent -InputObject $timer -EventName 'Elapsed' -Action {
                param(
                    $Timer,
                    $EventArgs
                )
                Write-Progress -Activity $Timer.Activity -CurrentOperation $Timer.CurrentOperation -PercentComplete (($Timer.ExportCount/$Timer.TotalCount) * 100)
            }
            $timer.Enabled = $true
            $timer.Start()
        }

        'function Push-Migration'
        '{'
            Export-DataType
            Export-Object
            Export-Index
        '}'
        ''
        'function Pop-Migration'
        '{'
            $pops | ForEach-Object { ' {0}' -f $_ }
        '}'
    }
    finally
    {
        if( $writeProgress )
        {
            $timer.Stop()
            if( $event )
            {
                Unregister-Event -SourceIdentifier $event.Name
            }
            Write-Progress -Activity $activity -PercentComplete 99
            Write-Progress -Activity $activity -Completed
        }
        Disconnect-Database
    }
}



function Export-Row 
{
    <#
    .SYNOPSIS
    Export rows from a database as a migration where those rows get added using the `Add-Row` operation.
 
    .DESCRIPTION
    When getting your database working with Rivet, you may want to get some data exported into an initial migration. This script does that.
 
    .EXAMPLE
    Export-Row -SqlServerName .\Rivet -DatabaseName 'Rivet' -SchemaName 'rivet' -TableName 'Migrations' -Column 'MigrationID','RunAtUtc'
 
    Demonstrates how to export the `MigrationID` and `RunAtUtc` columns of the `rivet.Migrations` table from the `.\Rivet.Rivet` database
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]
        # The SQL Server to connect to.
        $SqlServerName,

        [Parameter(Mandatory=$true)]
        [string]
        # The name of the database.
        $DatabaseName,

        [string]
        # The schema of the table.
        $SchemaName = 'dbo',

        [Parameter(Mandatory=$true)]
        [string]
        # The name of the table.
        $TableName,

        [Parameter()]
        [string[]]
        # The columns to export.
        $Column,

        [string]
        # An orderBy clause to use to order the results.
        $OrderBy
    )

    #Require -Version 3
    Set-StrictMode -Version Latest

    $connectionString = 'Server={0};Database={1};Integrated Security=True;' -f $SqlServerName,$DatabaseName
        
    $connection = New-Object Data.SqlClient.SqlConnection $connectionString
    $columnClause = $Column -join ', '
    $query = 'select {0} from {1}.{2}' -f $columnClause,$SchemaName,$TableName
    if( $OrderBy )
    {
        $query += ' order by {0}' -f $OrderBy
    }
    $cmd = New-Object Data.SqlClient.SqlCommand ($query,$connection)
        
    $connection.Open()
    try
    {
        ' Add-Row -SchemaName ''{0}'' -TableName ''{1}'' -Column @('
        $cmdReader = $cmd.ExecuteReader()
        try
        {
            if( -not $cmdReader.HasRows )
            {
                return
            }
                
            while( $cmdReader.Read() )
            {
                ' @{'
                for ($i= 0; $i -lt $cmdReader.FieldCount; $i++) 
                { 
                    if( $cmdReader.IsDbNull( $i ) )
                    {
                        continue
                    }
                    $name = $cmdReader.GetName( $i )
                    $value = $cmdReader.GetValue($i)
                    if( $value -is [Boolean] )
                    {
                        $value = if( $cmdReader.GetBoolean($i) ) { '1' } else { '0' }
                    }
                    elseif( $value -is [string] )
                    {
                        $value = "'{0}'" -f $value.ToString().Replace("'","''")
                    }
                    elseif( $value -is [DAteTime] -or $value -is [Guid] )
                    {
                        $value = "'{0}'" -f $value
                    }
                    else
                    {
                        $value = $value.ToString()
                    }
                    
                    ' {0} = {1};' -f $name,$value
                }
                ' },'
            }
        }
        finally
        {
            ' )'
            $cmdReader.Close()
        }
    }
    finally
    {
        $cmd.Dispose()
        $connection.Close()
    }
}



function Get-Migration
{
    <#
    .SYNOPSIS
    Gets the migrations for all or specific databases.
 
    .DESCRIPTION
    The `Get-Migration` function returns `Rivet.Migration` objects for all the migrations in all or specific databases. With no parameters, looks in the current directory for a `rivet.json` file and returns all the migrations for all the databases based on that configuration. Use the `ConfigFilePath` to load and use a specific `rivet.json` file.
 
    You can return migrations from specific databases by passing those database names as values to the `Database` parameter.
 
    The `Environment` parameter is used to load the correct environment-specific settings from the `rivet.json` file.
 
    You can filter what migrations are returned using the `Include` or `Exclude` parameters, which support wildcards, and will match any part of the migration's filename, including the ID.
 
    Use the `Before` and `After` parameters to return migrations whose timestamps/IDs come before and after the given dates.
 
    .OUTPUTS
    Rivet.Migration.
 
    .EXAMPLE
    Get-Migration
 
    Returns `Rivet.Migration` objects for each migration in each database.
 
    .EXAMPLE
    Get-Migration -Database StarWars
 
    Returns `Rivet.Migration` objects for each migration in the `StarWars` database.
 
    .EXAMPLE
    Get-Migration -Include 'CreateDeathStarTable','20150101000648','20150101150448_CreateRebelBaseTable','*Hoth*','20150707*'
 
    Demonstrates how to get use the `Include` parameter to find migrations by name, ID, or file name. In this case, the following migrations will be returned:
     
     * The migration whose name is `CreateDeathStarTable`.
     * The migration whose ID is `20150101000648`.
     * The migration whose full name is `20150101150448_CreateRebelBaseTable`.
     * Any migration whose contains `Hoth`.
     * Any migration created on July 7th, 2015.
 
    .EXAMPLE
    Get-Migration -Exclude 'CreateDeathStarTable','20150101000648','20150101150448_CreateRebelBaseTable','*Hoth*','20150707*'
 
    Demonstrates how to get use the `Exclude` parameter to skip/not return certain migrations by name, ID, or file name. In this case, the following migrations will be *not* be returned:
     
     * The migration whose name is `CreateDeathStarTable`.
     * The migration whose ID is `20150101000648`.
     * The migration whose full name is `20150101150448_CreateRebelBaseTable`.
     * Any migration whose contains `Hoth`.
     * Any migration created on July 7th, 2015.
    #>

    [CmdletBinding(DefaultParameterSetName='External')]
    [OutputType([Rivet.Migration])]
    param(
        [Parameter(ParameterSetName='External')]
        [string[]]
        # The database whose migrations to get.np
        $Database,

        [Parameter(ParameterSetName='External')]
        [string]
        # The environment settings to use.
        $Environment,

        [Parameter(ParameterSetName='External')]
        [string]
        # The path to the rivet.json file to use. Defaults to `rivet.json` in the current directory.
        $ConfigFilePath,

        [string[]]
        # A list of migrations to include. Matches against the migration's ID or Name or the migration's file name (without extension). Wildcards permitted.
        $Include,

        [string[]]
        # A list of migrations to exclude. Matches against the migration's ID or Name or the migration's file name (without extension). Wildcards permitted.
        $Exclude,

        [DateTime]
        # Only get migrations before this date. Default is all.
        $Before,

        [DateTime]
        # Only get migrations after this date. Default is all.
        $After
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    Write-Timing -Message 'Get-Migration BEGIN' -Indent
    
    function Clear-Migration
    {
        ('function:Push-Migration','function:Pop-Migration') |
            Where-Object { Test-Path -Path $_ } |
            Remove-Item -WhatIf:$false -Confirm:$false
    }

    Clear-Migration
    Write-Timing -Message 'Get-Migration Clear-Migration'

    $getRivetConfigParams = @{ }
    if( $Database )
    {
        $getRivetConfigParams['Database'] = $Database
    }

    if( $ConfigFilePath )
    {
        $getRivetConfigParams['Path'] = $ConfigFilePath
    }

    if( $Environment )
    {
        $getRivetConfigParams['Environment'] = $Environment
    }

    $Configuration = Get-RivetConfig @getRivetConfigParams
    if( -not $Configuration )
    {
        return
    }

    Import-RivetPlugin -Path $Configuration.PluginPaths -ModuleName $Configuration.PluginModules
    Write-Timing -Message 'Get-Migration Import-RivetPlugin'

    $getMigrationFileParams = @{}
    @( 'Include', 'Exclude' ) | ForEach-Object {
                                                    if( $PSBoundParameters.ContainsKey($_) )
                                                    {
                                                        $getMigrationFileParams[$_] = $PSBoundParameters[$_]
                                                    }
                                                }

    Get-MigrationFile -Configuration $Configuration @getMigrationFileParams |
        Where-Object {
            if( $PSBoundParameters.ContainsKey( 'Before' ) )
            {
                $beforeTimestamp = [uint64]$Before.ToString('yyyyMMddHHmmss')
                if( $_.MigrationID -gt $beforeTimestamp )
                {
                    return $false
                }
            }

            if( $PSBoundParameters.ContainsKey( 'After' ) )
            {
                $afterTimestamp = [uint64]$After.ToString('yyyyMMddHHmmss')
                if( $_.MigrationID -lt $afterTimestamp )
                {
                    return $false
                }
            }
            return $true
        } |
        Convert-FileInfoToMigration -Configuration $Configuration

    Write-Timing -Message 'Get-Migration END' -Outdent
}


function Get-MigrationFile
{
    <#
    .SYNOPSIS
    Gets the migration script files.
    #>

    [CmdletBinding(DefaultParameterSetName='External')]
    [OutputType([IO.FileInfo])]
    param(
        [Parameter(Mandatory)]
        # The configuration to use.
        [Rivet.Configuration.Configuration]$Configuration,

        [Parameter(Mandatory,ParameterSetName='ByPath')]
        # The path to a migrations directory to get.
        [String[]]$Path,

        # A list of migrations to include. Matches against the migration's ID or Name or the migration's file name (without extension). Wildcards permitted.
        [String[]]$Include,

        # A list of migrations to exclude. Matches against the migration's ID or Name or the migration's file name (without extension). Wildcards permitted.
        [String[]]$Exclude
    )

    Set-StrictMode -Version Latest
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    
    Write-Timing -Message 'Get-MigrationFile BEGIN' -Indent

    $requiredMatches = @{ }
    if( $PSBoundParameters.ContainsKey('Include') )
    {
        foreach( $includeItem in $Include )
        {
            if( -not [Management.Automation.WildcardPattern]::ContainsWildcardCharacters($includeItem) )
            {
                $requiredMatches[$includeItem] = $true
            }
        }
    }

    $foundMatches = @{ }
    
    Invoke-Command -ScriptBlock {
            if( $PSCmdlet.ParameterSetName -eq 'ByPath' )
            {
                $Path
            }
            else
            {
                $Configuration.Databases | Select-Object -ExpandProperty 'MigrationsRoot'
            }
        } |
        ForEach-Object {
            Write-Debug -Message $_ 
            if( (Test-Path -Path $_ -PathType Container) )
            {
                Get-ChildItem -Path $_ -Filter '*_*.ps1'
            }
            elseif( (Test-Path -Path $_ -PathType Leaf) )
            {
                Get-Item -Path $_
            }
        } | 
        ForEach-Object {
            if( $_.BaseName -notmatch '^(\d{14})_(.+)' )
            {
                Write-Error ('Migration {0} has invalid name. Must be of the form `YYYYmmddhhMMss_MigrationName.ps1' -f $_.FullName)
                return
            }
        
            $id = [int64]$matches[1]
            $name = $matches[2]
        
            $_ | 
                Add-Member -MemberType NoteProperty -Name 'MigrationID' -Value $id -PassThru |
                Add-Member -MemberType NoteProperty -Name 'MigrationName' -Value $name -PassThru
        } |
        Where-Object {
            if( -not ($PSBoundParameters.ContainsKey( 'Include' )) )
            {
                return $true
            }

            $migration = $_
            foreach( $includeItem in $Include )
            {
                $foundMatch = $migration.MigrationID -like $includeItem -or `
                              $migration.MigrationName -like $includeItem -or `
                              $migration.BaseName -like $includeItem
                if( $foundMatch )
                {
                    $foundMatches[$includeItem] = $true
                    return $true
                } 
            }

            return $false
        } |
        Where-Object { 

            if( -not ($PSBoundParameters.ContainsKey( 'Exclude' )) )
            {
                return $true
            }

            $migration = $_
            foreach( $pattern in $Exclude )
            {
                $foundMatch = $migration.MigrationID -like $pattern -or `
                              $migration.MigrationName -like $pattern -or `
                              $migration.BaseName -like $pattern
                if( $foundMatch )
                {
                    return $false
                }
            }

            return $true
        } 

    foreach( $requiredMatch in $requiredMatches.Keys )
    {
        if( -not $foundMatches.ContainsKey( $requiredMatch ) )
        {
            Write-Error ('Migration "{0}" not found.' -f $requiredMatch)
        }
    }

    Write-Timing -Message 'Get-MigrationFile BEGIN' -Outdent
}



function Get-RivetConfig
{
    <#
    .SYNOPSIS
    Gets the configuration to use when running Rivet.
 
    .DESCRIPTION
    Rivet will look in the current directory for a `rivet.json` file.
 
    .LINK
    about_Rivet_Configuration
 
    .EXAMPLE
    Get-RivetConfig
 
    Looks in the current directory for a `rivet.json` file, loads it, and returns an object representing its configuration.
 
    .EXAMPLE
    Get-RivetConfig -Path F:\etc\rivet
 
    Demonstrates how to load a custom Rivet configuration file.
    #>

    [CmdletBinding()]
    [OutputType([Rivet.Configuration.Configuration])]
    param(
        # The list of specific database names being operated on.
        [String[]]$Database,

        # The name of the environment whose settings to return. If not provided, uses the default settings.
        [String]$Environment,

        # The path to the Rivet configuration file to load. Defaults to `rivet.json` in the current directory.
        [String]$Path
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    function Resolve-RivetConfigPath
    {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory,ValueFromPipeline)]
            # The path from the rivet config file to resolve.
            [String]$ConfigPath,

            # The path *must* exist, so resolve it.
            [switch]$Resolve
        )

        process
        {
            $originalPath = $ConfigPath
            if( -not [IO.Path]::IsPathRooted( $ConfigPath ) )
            {
                $ConfigPath = Join-Path -Path $configRoot -ChildPath $ConfigPath
            }

            if( $Resolve )
            {
                $resolvedPath = Resolve-Path -Path $ConfigPath | Select-Object -ExpandProperty 'Path'
                if( ($resolvedPath | Measure-Object).Count -gt 1 )
                {
                    Write-ValidationError -Message ('path "{0}" resolves to multiple items: "{1}". Please update the path so that it resolves to only one item, or remove items so that only one remains.' -f $originalPath,($resolvedPath -join '", "'))
                    return
                }
                return $resolvedPath
            }
            else
            {
                return [IO.Path]::GetFullPath( $ConfigPath )
            }
        }
    }

    $currentPropertyName = $null

    filter Get-ConfigProperty
    {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory)]
            # The name of the property to get.
            [String]$Name,

            # The configuration value is required.
            [switch]$Required,

            [Parameter(Mandatory,ParameterSetName='AsInt')]
            # Set the configuration value as an integer.
            [switch]$AsInt,

            [Parameter(Mandatory,ParameterSetName='AsArray')]
            # Set the configuration value as a list of strings.
            [switch]$AsArray,

            [Parameter(Mandatory,ParameterSetName='AsPath')]
            # Set the configuration value as a path.
            [switch]$AsPath,

            [Parameter(ParameterSetName='AsPath')]
            # Resolves the path to an actual path.
            [switch]$Resolve,

            [Parameter(Mandatory,ParameterSetName='AsString')]
            # Set the configuration value as a string.
            [switch]$AsString,

            [Parameter(Mandatory,ParameterSetName='AsHashtable')]
            # Set the configuration value as a hashtable.
            [switch]$AsHashtable
        )
        
        $value = $null

        if( $rawConfig | Get-Member -Name $Name )
        {
            $value = $rawConfig.$Name
        }

        $env = Get-Environment
        if( $env -and ($env | Get-Member -Name $Name))
        {
            $value = $env.$Name
        }

        if( -not $value )
        {
            if( $Required )
            {
                Write-ValidationError ('is required.')
            }
            return
        }

        switch ($PSCmdlet.ParameterSetName )
        {
            'AsInt'
            {
                if( -not ($value -is [int] -or $value -is [int64]) )
                {
                    Write-ValidationError -Message ('is invalid. It should be an integer but we found a "{0}".' -f $value.GetType().FullName)
                    return
                }
                return $value
            }
            'AsArray'
            {
                return [String[]]$value
            }
            'AsPath'
            {
                $configPath = $value | Resolve-RivetConfigPath -Resolve:$Resolve
                if( -not $configPath )
                {
                    return
                }
                if( -not (Test-Path -Path $configPath) )
                {
                    Write-ValidationError ('path "{0}" not found.' -f $configPath)
                    return
                }
                return $configPath
            }
            'AsString'
            {
                return $value
            }
            'AsHashtable'
            {
                $hashtable = @{ }
                Get-Member -InputObject $value -MemberType NoteProperty |
                    ForEach-Object { $hashtable[$_.Name] = $value.($_.Name) }
                return ,$hashtable
            }
        }
    }

    function Write-ValidationError
    {
        param(
            [Parameter(Mandatory,Position=1)]
            # The error message to write.
            [String]$Message
        )
        $envMsg = ''
        if( $Environment )
        {
            $envMsg = 'environment "{0}": ' -f $Environment
        }
        $nameMsg = ''
        if( $currentPropertyName )
        {
            $nameMsg = 'property "{0}": ' -f $currentPropertyName
        }
        Write-Error -Message ('Invalid Rivet configuration file "{0}": {1}{2}{3} See about_Rivet_Configuration for more information.' -f $Path,$envMsg,$nameMsg,$Message)
    }


    function Get-Environment
    {
        if( $Environment )
        {
            if( ($rawConfig | Get-Member -Name 'Environments') -and 
                ($rawConfig.Environments | Get-Member -Name $Environment) )
            {
                $rawConfig.Environments.$Environment
            }
        }
    }

    ## If there is no $Path defined set $Path to current directory
    if( -not $Path )
    {
        $Path = Get-Location | Select-Object -ExpandProperty 'ProviderPath'
        $Path = Join-Path -Path $Path -ChildPath 'rivet.json'
    }

    if( -not [IO.Path]::IsPathRooted( $Path ) )
    {
        $Path = Join-Path -Path (Get-Location) -ChildPath $Path
    }

    $Path = [IO.Path]::GetFullPath( $Path )

    ## Check for existence of rivet.json
    if( -not (Test-Path -Path $Path -PathType Leaf) )
    {
        Write-Error ('Rivet configuration file "{0}" not found.' -f $Path)
        return
    }

    $configRoot = Split-Path -Parent -Path $Path

    $rawConfig = Get-Content -Raw -Path $Path | ConvertFrom-Json
    if( -not $rawConfig )
    {
        Write-Error -Message ('Rivet configuration file "{0}" contains invalid JSON.' -f $Path)
        return
    }

    if( $Environment -and -not (Get-Environment) )
    {
        Write-Error ('Environment "{0}" not found in "{1}".' -f $Environment,$Path)
        return
    }

    $errorCount = $Global:Error.Count

    $sqlServerName = Get-ConfigProperty -Name 'SqlServerName' -Required -AsString
    $dbsRoot = Get-ConfigProperty -Name 'DatabasesRoot' -Required -AsPath
    $connectionTimeout = Get-ConfigProperty -Name 'ConnectionTimeout' -AsInt
    if( $null -eq $connectionTimeout )
    {
        $connectionTimeout = 15
    }

    $commandTimeout = Get-ConfigProperty -Name 'CommandTimeout' -AsInt
    if( $null -eq $commandTimeout )
    {
        $commandTimeout = 30
    }
    $pluginPaths = Get-ConfigProperty -Name 'PluginPaths' -AsPath -Resolve

    $ignoredDatabases = Get-ConfigProperty -Name 'IgnoreDatabases' -AsArray
    $targetDatabases = Get-ConfigProperty -Name 'TargetDatabases' -AsHashtable
    if( $null -eq $targetDatabases )
    {
        $targetDatabases = @{ }
    }

    $order = Get-ConfigProperty -Name 'DatabaseOrder' -AsArray
    $pluginModules = Get-ConfigProperty -Name 'PluginModules' -AsArray

    [Rivet.Configuration.Configuration]$configuration = 
        [Rivet.Configuration.Configuration]::New($Path, $Environment, $sqlServerName, $dbsRoot, $connectionTimeout, $commandTimeout, $pluginPaths, $pluginModules)

    if( $Global:Error.Count -ne $errorCount )
    {
        return
    }

    $databaseInfos = Invoke-Command {
            # Get user-specified databases first
            if( $Database )
            {
                $Database | 
                    Add-Member -MemberType ScriptProperty -Name Name -Value { $this } -PassThru |
                    Add-Member -MemberType ScriptProperty -Name FullName -Value { Join-Path -Path $configuration.DatabasesRoot -ChildPath $this.Name } -PassThru
            }
            else
            {                                    
                # Then get all of them in the order requested
                if( $order )
                {
                    foreach( $dbName in $order )
                    {
                        Get-ChildItem -Path $configuration.DatabasesRoot -Filter $dbName -Directory
                    }
                }

                Get-ChildItem -Path $configuration.DatabasesRoot -Exclude $order -Directory
            }
        } |
        Select-Object -Property Name,FullName -Unique |
        Where-Object { 
            if( -not $ignoredDatabases )
            {
                return $true
            }

            $dbName = $_.Name                                        
            $ignore = $ignoredDatabases | Where-Object { $dbName -like $_ }
            return -not $ignore
        }

    foreach( $databaseInfo in $databaseInfos )
    {
        $dbName = $databaseInfo.Name
        [Rivet.Configuration.Database[]]$rivetDatabases = & {
            if( $targetDatabases.ContainsKey( $dbName ) )
            {
                foreach( $targetDBName in $targetDatabases[$dbName] )
                {
                    [Rivet.Configuration.Database]::New($targetDBName, $databaseInfo.FullName) | Write-Output
                }
            }
            else
            {
                [Rivet.Configuration.Database]::New($dbName, $databaseInfo.FullName) | Write-Output
            }
        }

        [void]$configuration.Databases.AddRange( $rivetDatabases )
    } 

    return $configuration
}



function Import-RivetPlugin
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [String[]]$Path,

        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [String[]]$ModuleName
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    
    Write-Timing -Message 'Import-RivetPlugin BEGIN' -Indent

    $moduleNames = & {
        foreach( $pluginPath in $Path )
        {
            if( [IO.Path]::GetExtension($pluginPath) -eq '.ps1' )
            {
                Write-Error -Message ('Unable to import Rivet plugin "{0}": invalid plugin file extension. A Rivet plugin must be a PowerShell module. The path to your plugin must be to a directory that is importable by the `Import-Module` command, or to a .psd1 or .psm1 file.' -f $pluginPath) -ErrorAction Stop
                continue
            }

            Write-Timing -Message " Import BEGIN $($pluginPath)"
            Import-Module -Name $pluginPath -Global -Force -PassThru |
                Select-Object -ExpandProperty 'Name' |
                Write-Output
            Write-Timing -Message " Import END $($pluginPath)"
        }

        $ModuleName | Write-Output
    }

    $commands = & {
        foreach( $moduleName in $moduleNames )
        {
            Write-Timing -Message " Get Commands BEGIN $($moduleName)"
            if( -not (Get-Module -Name $moduleName) )
            {
                $msg = ("Unable to load plugins from module ""$($moduleName)"": the module is not loaded. Please " +
                        'call "Import-Module" to load this module before running Rivet. If you want Rivet to load the ' +
                        'module for you, use the "PluginPaths" setting and set it to a list of paths to modules ' +
                        'that Rivet should import.')
                Write-Error -Message $msg -ErrorAction Stop
                continue
            }

            Get-Command -Module $moduleName
            Write-Timing -Message " Get Commands END $($moduleName)"
        }

        # Get any global functions that may be plugins.
        Write-Timing -Message (' Get Functions BEGIN')
        Get-Command -CommandType Function | Where-Object { -not $_.Module }
        Write-Timing -Message (' Get Functions End')
    }
 
    $script:plugins = & {
        foreach( $command in $commands ) 
        {
            if( -not ($command | Get-Member -Name 'ScriptBlock') )
            {
                continue
            }

            if( $command.ScriptBlock.Attributes | Where-Object { $_ -is [Rivet.PluginAttribute] } )
            {
                $command | Write-Output
            }
        }

        foreach( $command in $commands )
        {
            if( -not ($command | Get-Member -Name 'ImplementingType') )
            {
                continue
            }

            f( $command.ImplementingType.Attributes | Where-Object { $_ -is [Rivet.PluginAttribute] } )
            {
                $command | Write-Output
            }
        }
    }

    Write-Timing -Message (' Discovered {0} plugins.' -f ($plugins | Measure-Object).Count)

    Write-Timing -Message 'Import-RivetPlugin END' -Outdent
}


function Initialize-Database
{
    <#
    .SYNOPSIS
    Intializes the database so that it can be migrated by Rivet.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [Rivet.Configuration.Configuration]
        $Configuration
    )

    Set-StrictMode -Version 'Latest'

    $who = ('{0}\{1}' -f $env:USERDOMAIN,$env:USERNAME);
    $migrationsPath = Join-Path -Path $rivetModuleRoot -ChildPath 'Migrations'
    Write-Debug -Message ('# {0}.{1}' -f $Connection.DataSource,$Connection.Database)
    Update-Database -Path $migrationsPath -RivetSchema -Configuration $Configuration
}



function Invoke-MigrationOperation
{
    <#
    .SYNOPSIS
    Runs the SQL created by a `Rivet.Migration` object.
 
    .DESCRIPTION
    All Rivet migrations are described by instances of `Rivet.Migration` objects. These objects eventually make their way here, at which point they are converted to SQL, and executed.
 
    .EXAMPLE
    Invoke-Migration -Operation $operation
 
    This example demonstrates how to call `Invoke-Migration` with a migration object.
    #>

    [CmdletBinding(DefaultParameterSetName='AsReader')]
    param(
        [Parameter(Mandatory)]
        # The migration this operation is from.
        [Rivet.Migration]$Migration,

        [Parameter(Mandatory,ValueFromPipeline)]
        # The migration object to invoke.
        [Rivet.Operations.Operation]$Operation
    )

    begin
    {
    }

    process
    {
        Set-StrictMode -Version 'Latest'

        $optionalParams = @{ }
        $nonQuery = $false
        $asScalar = $false
        if( $Operation.QueryType -eq [Rivet.OperationQueryType]::NonQuery -or $Operation.QueryType -eq [Rivet.OperationQueryType]::Ddl )
        {
            $optionalParams['NonQuery'] = $true
            $nonQuery = $true
        }
        elseif( $Operation.QueryType -eq [Rivet.OperationQueryType]::Scalar )
        {
            $optionalParams['AsScalar'] = $true
            $asScalar = $true
        }

        $Operation.ToQuery() |
            Split-SqlBatchQuery -Verbose:$false |
            Where-Object { $_ } |
            ForEach-Object {

                $batchQuery = $_
                $result = $null
                $rowsAffected = -1
                $rowCount = $null

                try
                {
                    if( $Operation -is [Rivet.Operations.RemoveRowOperation] -and $Operation.Truncate )
                    {
                        $rowCount = Invoke-Query -Query ('select count(*) from [{0}].[{1}]' -f $Operation.SchemaName,$Operation.TableName) -AsScalar
                    }

                    $result = Invoke-Query -Query $batchQuery -Parameter $Operation.Parameters -CommandTimeout $Operation.CommandTimeout @optionalParams
                }
                catch
                {
                    Write-RivetError -Message ('Migration {0} failed' -f $migrationInfo.FullName) `
                                        -CategoryInfo $_.CategoryInfo.Category `
                                        -ErrorID $_.FullyQualifiedErrorID `
                                        -Exception $_.Exception `
                                        -CallStack ($_.ScriptStackTrace) `
                                        -Query $batchQuery
                    throw (New-Object ApplicationException 'Migration failed.',$_.Exception)
                }

                if( $nonQuery )
                {
                    if( $rowCount -eq $null )
                    {
                        $rowsAffected = $result
                    }
                }
                elseif( $asScalar )
                {
                    if( $result -ne 0 )
                    {
                        if( $Operation -is [Rivet.Operations.UpdateCodeObjectMetadataOperation] )
                        {
                            $exMsg = "Failed to refresh {0}.{1}" -f $Operation.SchemaName,$Operation.Name
                        }
                        elseif( $Operation -is [Rivet.Operations.RenameColumnOperation] )
                        {
                            $exMsg = "Failed to rename column {0}.{1}.{2} to {0}.{1}.{3}" -f $Operation.SchemaName,$Operation.TableName,$Operation.Name,$Operation.NewName
                        }
                        elseif( $Operation -is [Rivet.Operations.RenameOperation] )
                        {
                            $exMsg = "Failed to rename object {0}.{1} to {0}.{2}" -f $Operation.SchemaName,$Operation.Name,$Operation.NewName
                        }
                        throw ('{0}: error code {1}' -f $exMsg,$result)
                    }
                }
                        
                return New-Object 'Rivet.OperationResult' $Migration,$Operation,$batchQuery,$rowsAffected              
            }
    }

    end
    {
    }
}



filter Invoke-Query
{
    <#
    .SYNOPSIS
    Executes a SQL query against the database.
     
    .DESCRIPTION
    The `Invoke-Query` function runs arbitrary queries aginst the database. Queries are split on `GO` statements, and each query is sent individually to the database.
     
    By default, rows are returned as anonymous PsObjects, with properties for each named column returned. Unnamed columns are given arbitrary `ColumnIdx` names, where `Idx` is a number the increments by one for each anonymous column, beginning with 0.
 
    You can return the results as a scalar using the AsScalar parameter.
 
    use the `NonQuery` switch to run non-queryies (e.g. `update`, `insert`, etc.). In this case, the number of rows affected by the query is returned.
 
    Do not use this method to migrate/transform your database, or issue DDL queries! The queries issued by this function happen before the DDL applied by a migration's operations. Use the `Invoke-Ddl` function instead. If you need to dynamically migrate your database based on its state, use this function to query the state of the database, and the other Rivet operations to perform the migration.
 
    You can pipe queries to this method, too!
 
    .LINK
    Invoke-Ddl
     
    .EXAMPLE
    Invoke-Query -Query 'create table rivet.Migrations( )'
     
    Executes the create table syntax above against the database.
     
    .EXAMPLE
    Invoke-Query -Query 'select count(*) from MyTable' -Database MyOtherDatabase
     
    Executes a query against the non-current database. Returns the rows as objects.
     
    .EXAMPLE
    'select count(*) from sys.tables' | Invoke-Query -AsScalar
     
    Demonstrates how queries can be piped into `Invoke-Query`. Also shows how a result can be returned as a scalar.
    #>

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

        [Parameter(Mandatory=$true,ParameterSetName='ExecuteScalar')]
        [Switch]
        $AsScalar,
        
        [Parameter(Mandatory=$true,ParameterSetName='ExecuteNonQuery')]
        [Switch]
        $NonQuery,
        
        [UInt32]
        # The time in seconds to wait for the command to execute. The default is 30 seconds.
        $CommandTimeout = 30
    )

    Set-StrictMode -Version 'Latest'

    $Query |
        Split-SqlBatchQuery -Verbose:$false |
        Where-Object { $_ } |
        ForEach-Object {
                
                $queryBatch = $_
                $cmd = New-Object 'Data.SqlClient.SqlCommand' ($queryBatch,$Connection,$Connection.Transaction)

                $cmdStartedAt = [DateTime]::UtcNow
                try
                {
                    $cmd.CommandTimeout = $CommandTimeout

                    if( $Parameter )
                    {
                        $Parameter.Keys | ForEach-Object { 
                            $name = $_
                            $value = $Parameter[$name]
                            if( -not $name.StartsWith( '@' ) )
                            {
                                $name = '@{0}' -f $name
                            }
                            [void] $cmd.Parameters.AddWithValue( $name, $value )
                       }
                    }

                    if( $PSCmdlet.ParameterSetName -eq 'ExecuteNonQuery' )
                    {
                        $cmd.ExecuteNonQuery()
                    }
                    elseif( $PSCmdlet.ParameterSetName -eq 'ExecuteScalar' )
                    {
                        $cmd.ExecuteScalar()
                    }
                    else
                    {
                        $cmdReader = $cmd.ExecuteReader()
                        try
                        {
                            if( $cmdReader.HasRows )
                            {                
                                while( $cmdReader.Read() )
                                {
                                    $row = @{ }
                                    for ($i= 0; $i -lt $cmdReader.FieldCount; $i++) 
                                    { 
                                        $name = $cmdReader.GetName( $i )
                                        if( -not $name )
                                        {
                                            $name = 'Column{0}' -f $i
                                        }
                                        $value = $cmdReader.GetValue($i)
                                        if( $cmdReader.IsDBNull($i) )
                                        {
                                            $value = $null
                                        }
                                        $row[$name] = $value
                                    }
                                    New-Object PsObject -Property $row
                                }
                            }
                        }
                        finally
                        {
                            $cmdReader.Close()
                        }
                    }
                }
                finally
                {
                    $cmd.Dispose()
                    $queryLines = $queryBatch -split ([TExt.RegularExpressions.Regex]::Escape([Environment]::NewLine))
                    Write-Verbose -Message ('{0,8} (ms) {1}' -f ([int]([DateTime]::UtcNow - $cmdStartedAt).TotalMilliseconds),($queryLines | Select-Object -First 1))
                    $queryLines | Select-Object -Skip 1 | ForEach-Object {  Write-Verbose -Message ('{0} {1}' -f (' ' * 13),$_) } 
                }
        }

}



function Invoke-Rivet
{
    [CmdletBinding(SupportsShouldProcess=$True)]
    param(
        [Parameter(Mandatory=$true,ParameterSetName='New')]
        [Switch]
        # Creates a new migration.
        $New,
    
        [Parameter(Mandatory=$true,ParameterSetName='Push')]
        [Switch]
        # Applies migrations.
        $Push,
    
        [Parameter(Mandatory=$true,ParameterSetName='Pop')]
        [Parameter(Mandatory=$true,ParameterSetName='PopByCount')]
        [Parameter(Mandatory=$true,ParameterSetName='PopByName')]
        [Parameter(Mandatory=$true,ParameterSetName='PopAll')]
        [Switch]
        # Reverts migrations.
        $Pop,
    
        [Parameter(Mandatory=$true,ParameterSetName='Redo')]
        [Switch]
        # Reverts a migration, then re-applies it.
        $Redo,

        [Parameter(Mandatory=$true,ParameterSetName='New',Position=1)]
        [Parameter(ParameterSetName='Push',Position=1)]
        [Parameter(Mandatory=$true,ParameterSetName='PopByName',Position=1)]
        [ValidateLength(1,241)]
        [string[]]
        # The name of the migrations to create, push, or pop. Matches against the migration's ID, Name, or file name (without extension). Wildcards permitted.
        $Name,
    
        [Parameter(Mandatory=$true,ParameterSetName='PopByCount',Position=1)]
        [UInt32]
        # The number of migrations to pop. Default is 1.
        $Count = 1,

        [Parameter(Mandatory=$true,ParameterSetName='PopAll')]
        [Switch]
        # Pop all migrations
        $All,

        [Parameter(ParameterSetName='Pop')]
        [Parameter(ParameterSetName='PopByCount')]
        [Parameter(ParameterSetName='PopByName')]
        [Parameter(ParameterSetName='PopAll')]
        [Parameter(ParameterSetName='DropDatabase')]
        [Switch]
        # Force popping a migration you didn't apply or that is old.
        $Force,

        [Parameter(ParameterSetName='New',Position=2)]
        [Parameter(ParameterSetName='Push')]
        [Parameter(ParameterSetName='Pop')]
        [Parameter(ParameterSetName='PopByCount')]
        [Parameter(ParameterSetName='PopByName')]
        [Parameter(ParameterSetName='PopAll')]
        [Parameter(ParameterSetName='Redo')]
        [Parameter(ParameterSetName='DropDatabase')]
        [string[]]
        # The database(s) to migrate. Optional. Will operate on all databases otherwise.
        $Database,

        [Parameter(ParameterSetName='New')]
        [Parameter(ParameterSetName='Push')]
        [Parameter(ParameterSetName='Pop')]
        [Parameter(ParameterSetName='PopByCount')]
        [Parameter(ParameterSetName='PopByName')]
        [Parameter(ParameterSetName='PopAll')]
        [Parameter(ParameterSetName='Redo')]
        [Parameter(ParameterSetName='DropDatabase')]
        [string]
        # The environment you're working in. Controls which settings Rivet loads from the `rivet.json` configuration file.
        $Environment,

        [Parameter(ParameterSetName='New')]
        [Parameter(ParameterSetName='Push')]
        [Parameter(ParameterSetName='Pop')]
        [Parameter(ParameterSetName='PopByCount')]
        [Parameter(ParameterSetName='PopByName')]
        [Parameter(ParameterSetName='PopAll')]
        [Parameter(ParameterSetName='Redo')]
        [Parameter(ParameterSetName='DropDatabase')]
        [string]
        # The path to the Rivet configuration file. Default behavior is to look in the current directory for a `rivet.json` file. See `about_Rivet_Configuration` for more information.
        $ConfigFilePath,

        [Parameter(ParameterSetName='DropDatabase')]
        [Switch]
        # Drops the database(s) for the current environment when given. User will be prompted for confirmation when used.
        $DropDatabase
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    [Rivet.Configuration.Configuration]$settings = Get-RivetConfig -Database $Database -Path $ConfigFilePath -Environment $Environment

    if( -not $settings.Databases )
    {
        Write-Error (@'
Found no databases to migrate. This can be a few things:
  
 * There are no database directories in ''{0}''. Please create a database directory there or supply an explicit database name with the `Database` parameter.
 * You supplied an explicit database name, but that database is on the ignore list. Remove it from the ignore list in ''{1}'' or enter a database name that isn't ignored.
 * You supplied an explicit database name, but no directory for migrations exist on the file system (under {0}). Create a migrations directory or enter the name of a database that exists.
 
'@
 -f $settings.DatabasesRoot,$settings.Path)
        return
    }

    Import-RivetPlugin -Path $settings.PluginPaths -ModuleName $settings.PluginModules

    try
    {
        if( $PSCmdlet.ParameterSetName -eq 'New' )
        {
            $settings.Databases | 
                Select-Object -ExpandProperty 'MigrationsRoot' -Unique |
                ForEach-Object { New-Migration -Name $Name -Path $_ }
            return
        }

        if( $DropDatabase )
        {
            # Connect to master as we cannot drop a database if we're connected to it
            Connect-Database -SqlServerName $settings.SqlServerName `
                                    -Database 'Master' `
                                    -ConnectionTimeout $settings.ConnectionTimeout

            $databaseString = ($settings.Databases | Select-Object -ExpandProperty 'Name') -join "', '"
            $query = "select name from sys.databases where name in ('$($databaseString)')"
            $databaseList = Invoke-Query -Query $query

            if( $databaseList )
            {
                $confirmDropDatabase = $false
                if( -not $Force)
                {
                    $confirmQuery = 'Using the `DropDatabase` switch will drop the database(s) for the current ' +
                                    'environment. Do you want to proceed?'
                    
                    $confirmCaption = 'Drop the following database(s)? ' +
                                      (($databaseList | Select-Object -ExpandProperty 'Name') -join ', ')
                    
                    $confirmDropDatabase = $PSCmdlet.ShouldContinue( $confirmQuery, $confirmCaption )
                }

                if( $confirmDropDatabase -or $Force )
                {
                    foreach( $databaseItem in $databaseList )
                    {
                        $query = "DROP DATABASE {0}" -f $databaseItem.Name
                        Invoke-Query -Query $query
                    }
                }
            }
            return
        }

        foreach( $databaseItem in $settings.Databases )
        {
            $databaseName = $databaseItem.Name
            $dbMigrationsPath = $databaseItem.MigrationsRoot
        
            $result = Connect-Database -SqlServerName $settings.SqlServerName `
                                       -Database $databaseName `
                                       -ConnectionTimeout $settings.ConnectionTimeout
            if( -not $result )
            {
                continue
            }
        
            try
            {
                Initialize-Database -Configuration $settings

                $updateParams = @{
                                    Path = $dbMigrationsPath;
                                    Configuration = $settings;
                                }

                if( -not (Test-Path -Path $dbMigrationsPath -PathType Container) )
                {
                    Write-Warning ('{0} database migrations directory ({1}) not found.' -f $databaseName,$dbMigrationsPath)
                    continue
                }
            
                if( $PSBoundParameters.ContainsKey('Name') )
                {
                    $updateParams.Name = $Name    # Join-Path $dbMigrationsPath ("*_{0}.ps1" -f $Name)
                }

                Write-Debug -Message ('# {0}.{1}' -f $Connection.DataSource,$Connection.Database)
            
                if( $pscmdlet.ParameterSetName -eq 'Push' )
                {
                    Update-Database @updateParams
                }
                elseif( $pscmdlet.ParameterSetName -eq 'Pop' )
                {
                    Update-Database -Pop -Count 1 -Force:$Force @updateParams
                }
                elseif( $pscmdlet.ParameterSetName -eq 'PopByName' )
                {
                    Update-Database -Pop -Force:$Force @updateParams
                }
                elseif( $pscmdlet.ParameterSetName -eq 'PopByCount' )
                {
                    Update-Database -Pop -Count $Count -Force:$Force @updateParams
                }
                elseif ( $pscmdlet.ParameterSetName -eq 'PopAll' )
                {
                    Update-Database -Pop -All -Force:$Force @updateParams
                }
                elseif( $pscmdlet.ParameterSetName -eq 'Redo' )
                {
                    Update-Database -Pop -Count 1 @updateParams
                    Update-Database @updateParams
                }
            }
            catch
            {
                $firstException = $_.Exception
                while( $firstException.InnerException )
                {
                    $firstException = $firstException.InnerException
                }
            
                Write-Error ('{0} database migration failed: {1}.' -f $databaseName,$firstException.Message)
            }
        }
    }
    finally
    {
        Disconnect-Database
    }
}

Set-Alias -Name rivet -Value Invoke-Rivet



function Invoke-RivetPlugin
{
    param(
        [Parameter(Mandatory)]
        [Rivet.Events]
        $Event,

        [hashtable]
        $Parameter
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    Write-Timing -Message 'Invoke-RivetPlugin BEGIN' -Indent

    $responders = $plugins |
                        Where-Object {
                            $_.ScriptBlock.Attributes | Where-Object { $_ -is [Rivet.PluginAttribute] -and $_.RespondsTo -eq $Event }
                        }
    try
    {
        if( -not $responders )
        {
            return
        }

        foreach( $plugin in $responders )
        {
            foreach( $parameterName in $Parameter.Keys )
            {
                if( -not $plugin.Parameters.ContainsKey($parameterName) )
                {
                    Write-Error -Message ('The function "{0}" that responds to Rivet''s "{1}" event must have a named "{2}" parameter. Please update this function''s definition.' -f $plugin.Name,$Event,$parameterName) -ErrorAction Stop
                }
            }

            & $plugin.Name @Parameter
            Write-Timing -Message (' {0}' -f $plugin.Name)
        }
    }
    finally
    {
        Write-Timing -Message 'Invoke-RivetPlugin END' -Outdent
    }
}

function Merge-Migration
{
    <#
    .SYNOPSIS
    Creates a cumulative set of operations from migration scripts.
 
    .DESCRIPTION
    The `Merge-Migration` functions creates a cumulative set of migrations from migration scripts. If there are multiple operations across one or more migration scripts that touch the same database object, those changes are combined into one operation. For example, if you create a table in one migration, add a column in another migrations, then remove a column in a third migration, this function will output an operation that represents the final state for the object: a create table operation that includes the added column and doesn't include the removed column. In environments where tables are replicated, it is more efficient to modify objects once and have that change replicated once, than to have the same object modified multiple times and replicated multiple times.
 
    This function returns `Rivet.Migration` objects. Each object will have zero or more operations in its `PushOperations` property. If there are zero operations, it means the original operation was consolidated into another migration. Each operation has `Source` member on it, which is a list of all the migrations that contributed to that operation.
 
    .OUTPUTS
    Rivet.Migration
 
    .EXAMPLE
    Get-Migration | Merge-Migration
 
    Demonstrates how to run `Merge-Migration`. It is always used in conjunction with `Get-Migration`.
    #>

    [CmdletBinding()]
    [OutputType([Rivet.Migration])]
    param(
        [Parameter(ValueFromPipeline)]
        # The path to the rivet.json file to use. By default, it will look in the current directory.
        [Rivet.Migration[]]$Migration
    )

    begin
    {
        Set-StrictMode -Version 'Latest'

        # Collect all the migrations. We can't merge anything until we get to the end.
        [Collections.ArrayList]$migrations = [Collections.ArrayList]::New()
        [Collections.Generic.List[Rivet.Operations.Operation]]$allOperations = [Collections.Generic.List[Rivet.Operations.Operation]]::New()
    }

    process
    {
        foreach( $migrationItem in $Migration )
        {
            [void]$migrations.Add($migrationItem)

            foreach( $op in $migrationItem.PushOperations )
            {
                for( $idx = $allOperations.Count - 1; $idx -ge 0; --$idx)
                {
                    $allOperations[$idx].Merge($op)
                }

                [void]$allOperations.Add($op)
            }
        }
    }

    end
    {
        foreach( $migrationItem in $migrations )
        {
            for( $idx = $migrationItem.PushOperations.Count - 1; $idx -ge 0 ; --$idx )
            {
                $operation = $migrationItem.PushOperations[$idx]
                if( $operation.Disabled )
                {
                    $migrationItem.PushOperations.RemoveAt($idx)    
                }
            }
            $migrationItem | Write-Output
        }
    }
}


function New-ConstraintName
{
    <#
    .SYNOPSIS
    Creates a default constraint name for a column in a table.
    #>

    [CmdletBinding(DefaultParameterSetName='DF')]
    param(
        [Parameter(Mandatory,ParameterSetName='DF')]
        # Creates a default constraint name.
        [switch]$Default,

        [Parameter(Mandatory,ParameterSetName='PK')]
        # Creates a primary key name.
        [switch]$PrimaryKey,

        [Parameter(Mandatory,ParameterSetName='IX')]
        # Creates an index name.
        [switch]$Index,

        [Parameter(ParameterSetName='IX')]
        # For a unique index.
        [switch]$Unique,

        [Parameter(Mandatory,ParameterSetName='AK')]
        # Creates an unique key/alternate key constraint name.
        [switch]$UniqueKey,

        [Parameter(Mandatory,ParameterSetName='FK')]
        # Creates a foreign key constraint name.
        [switch]$ForeignKey,

        # The table's schema. Default is `dbo`.
        [String]$SchemaName = 'dbo',

        [Parameter(Mandatory,Position=0)]
        # The table name.
        [String]$TableName,

        [Parameter(Mandatory,ParameterSetName='DF',Position=1)]
        [Parameter(Mandatory,ParameterSetName='IX',Position=1)]
        [Parameter(Mandatory,ParameterSetName='AK',Position=1)]
        [Parameter(Mandatory,ParameterSetName='UIX',Position=1)]
        # The column name.
        [String[]]$ColumnName,

        [Parameter(ParameterSetName='FK')]
        [String]$ReferencesSchemaName = 'dbo',

        [Parameter(Mandatory,ParameterSetName='FK',Position=1)]
        [String]$ReferencesTableName
    )

    Set-StrictMode -Version 'Latest'

    $op = switch( $PSCmdlet.ParameterSetName )
    {
        'DF'
        {
            [Rivet.ConstraintName]::new($SchemaName, $TableName, $ColumnName, [Rivet.ConstraintType]::Default)
        }
        'FK'
        {
            [Rivet.ForeignKeyConstraintName]::new($SchemaName, $TableName, $ReferencesSchemaName, $ReferencesTableName)
        }
        'PK'
        {
            [Rivet.ConstraintName]::new($SchemaName, $TableName, $null, [Rivet.ConstraintType]::PrimaryKey)
        }
        'IX'
        {
            [Rivet.IndexName]::new($SchemaName, $TableName, $ColumnName, $Unique)
        }
        'AK'
        {
            [Rivet.ConstraintName]::new($SchemaName, $TableName, $ColumnName, [Rivet.ConstraintType]::UniqueKey)
        }
    }

    return $op.Name
}



function New-Migration
{
    <#
    .SYNOPSIS
    Creates a new migration script.
     
    .DESCRIPTION
    Creates a migration script with a given name. The script is prefixed with the current timestamp (e.g. yyyyMMddHHmmss). The script is created in `$Path\$Database\Migrations`.
    #>

    param(
        [Parameter(Mandatory=$true)]
        [string[]]
        # The name of the migration to create.
        $Name,
        
        [Parameter(Mandatory=$true)]
        [string]
        # The path to the directory where the migration should be saved.
        $Path
    )

    foreach( $nameItem in $Name )
    {
        $id = $null
        $id = [int64](Get-Date).ToString('yyyyMMddHHmmss')
        while( (Test-Path -Path $Path -PathType Container) -and `
               (Get-ChildItem -Path $Path -Filter ('{0}_*' -f $id) ) )
        {
            $id++
        }

        $filename = '{0}_{1}.ps1' -f $id,$nameItem

        $importRivetPath = Join-Path -Path $rivetModuleRoot -ChildPath 'Import-Rivet.ps1' -Resolve

        $migrationPath = Join-Path -Path $Path -ChildPath $filename
        $migrationPath = [IO.Path]::GetFullPath( $migrationPath )
        New-Item -Path $migrationPath -Force -ItemType File

        $template = @"
<#
Your migration is ready to go! For the best development experience, please
write your migration in the PowerShell 3 ISE. Run the following at a
PowerShell prompt:
 
    PS> ise "{0}"
     
or right-click the migration in Windows Explorer and choose "Edit".
 
The PowerShell ISE gives you intellisense, auto-complete, and other features
you may be used to from the Visual Studio IDE. Use this command in the ISE to
import Rivet and get intellisense/auto-complete:
 
    PSISE> {1}
 
The ISE has a "Show Command" add-on which will let you build your migration
with a GUI. Once you've got Rivet imported, choose View > Show Command Add-on.
When the Show Command Add-on appears, choose 'Rivet' from the module. Click on
a migration operation to build it with the Show Command GUI.
#>
function Push-Migration
{{
}}
 
function Pop-Migration
{{
}}
"@
 -f $migrationPath,$importRivetPath 

        $template | Set-Content -Path $migrationPath
    }
}


function New-MigrationObject
{
    <#
    .SYNOPSIS
    Creates a new `Rivet.Migration` object, suitable for passing to `Invoke-Migration` function.
 
    .DESCRIPTION
    All migrations in Rivet should be represented as an object. Each object should inherit from `Rivet.Migration`. This method returns an empty `Rivet.Migration` object, which is typically used to create migration-specific properties/methods.
 
    .EXAMPLE
    $migration = New-MigrationObject
 
    Returns a `Rivet.Migration` object.
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [hashtable]
        # The properties on the object.
        $Property,

        [Parameter(Mandatory=$true)]
        [ScriptBlock]
        # The script block to execute as the ToQuery method.
        $ToQueryMethod
    )

    $o = New-Object 'Rivet.Migration' '','','',''
    $Property.Keys | 
        ForEach-Object { $o | Add-Member -MemberType NoteProperty -Name $_ -Value $Property.$_ }

    $o |
        Add-Member -MemberType ScriptMethod -Name 'ToQuery' -Value $ToQueryMethod -PassThru
}


function Repair-Operation
{
    [CmdletBinding()]
    [OutputType([Rivet.Operations.Operation])]
    param(
        [Parameter(Mandatory,ValueFromPipeline)]
        [Rivet.Operations.Operation]$Operation
    )

    begin
    {
        Set-StrictMode -Version 'Latest'

        function Repair-DefaultConstraintName
        {
            param(
                [Parameter(Mandatory,ValueFromPipeline)]
                [Rivet.Column]$Column
            )
            
            begin
            {
                Set-StrictMode -Version 'Latest'

                $operationName = 'Add-Table'
                if( $Operation -is [Rivet.Operations.UpdateTableOperation] )
                {
                    $operationName = 'Update-Table'
                }
            }

            process
            {
                if( -not $Column.DefaultExpression -or ($Column.DefaultExpression -and $Column.DefaultConstraintName) )
                {
                    return
                }

                $column.DefaultConstraintName = New-ConstraintName -Default `
                                                                   -SchemaName $schemaName `
                                                                   -TableName $name `
                                                                   -ColumnName $column.Name
                Write-Warning -Message ('Column default constraint names will be required in a future version of ' +
                                        "Rivet. Add a ""DefaultConstraintName"" parameter to the [$($Column.Name)] " +
                                        "column on the $($operationName) operation for the " +
                                        "[$($schemaName)].[$($name)] table.")
            }
        }
    }

    process
    {
        $name = $Operation | Select-Object -ExpandProperty 'Name' -ErrorAction Ignore
        # If a constraint operation already has a name, don't do anything.
        if( $name -and $Operation -isnot [Rivet.Operations.AddTableOperation] -and $Operation -isnot [Rivet.Operations.UpdateTableOperation] )
        {
            return $Operation
        }

        $schemaName = $Operation | Select-Object -ExpandProperty 'SchemaName' -ErrorAction Ignore
        $tableName = $Operation | Select-Object -ExpandProperty 'TableName' -ErrorAction Ignore
        $columnName = $Operation | Select-Object -ExpandProperty 'ColumnName' -ErrorAction Ignore
        $columnDesc = $columnName -join '", "'
        $pluralSuffix = ''
        if( ($columnName | Measure-Object).Count -gt 1 )
        {
            $pluralSuffix = 's'
        }

        $tableDesc = "[$($schemaName)].[$($tableName)]"

        $warningMsg = ''

        switch( $Operation.GetType().Name )
        {
            'AddDefaultConstraintOperation'
            {
                $Operation.Name = New-ConstraintName -Default -SchemaName $schemaName -TableName $tableName -ColumnName $columnName
                $warningMsg = "Default constraint names will be required in a future version of Rivet. Add a " +
                              """Name"" parameter (with a value of ""$($Operation.Name)"") to the Add-DefaultConstraint " +
                              "operation for the $($tableDesc) table's ""$($columnDesc)"" column."
            }
            'AddForeignKeyOperation'
            {
                $Operation.Name = New-ConstraintName -ForeignKey `
                                                     -SchemaName $schemaName `
                                                     -TableName $tableName `
                                                     -ReferencesSchemaName $Operation.ReferencesSchemaName `
                                                     -ReferencesTableName $Operation.ReferencesTableName
                $warningMsg = "Foreign key constraint names will be required in a future version of Rivet. " +
                              "Add a ""Name"" parameter (with a value of ""$($Operation.Name)"") to the Add-ForeignKey " +
                              "operation for the $($tableDesc) table's $($columnDesc) column$($pluralSuffix)."
            }
            'AddIndexOperation'
            {
                $Operation.Name = New-ConstraintName -Index -SchemaName $schemaName -TableName $tableName -ColumnName $columnName -Unique:$Operation.Unique
                $warningMsg = "Index names will be required in a future version of Rivet. Add a ""Name"" " +
                              "parameter (with a value of ""$($Operation.Name)"") to the Add-Index operation for the " +
                              "$($tableDesc) table's ""$($columnDesc)"" column$($pluralSuffix)."
            }
            'AddPrimaryKeyOperation'
            {
                $Operation.Name = New-ConstraintName -PrimaryKey -SchemaName $schemaName -TableName $tableName
                $warningMsg = "Primary key constraint names will be required in a future version of Rivet. " +
                              "Add a ""Name"" parameter (with a value of ""$($Operation.Name)"") to the Add-PrimaryKey " +
                              "operation for the $($tableDesc) table's $($columnDesc) column."
            }
            'AddTableOperation'
            {
                $Operation.Columns | Repair-DefaultConstraintName
            }
            'AddUniqueKeyOperation'
            {
                $Operation.Name = New-ConstraintName -UniqueKey -SchemaName $schemaName -TableName $tableName -ColumnName $columnName
                $warningMsg = "Unique key constraint names will be required in a future version of Rivet. Add " +
                              "a ""Name"" parameter (with a value of ""$($Operation.Name)"") to the Add-UniqueKey " +
                              "operation on the $($tableDesc) table's $($columnDesc) column$($pluralSuffix)."
            }
            'RemoveDefaultConstraint'
            {
                $Operation.Name = New-ConstraintName -Default -SchemaName $schemaName -TableName $tableName -ColumnName $columnName
                $warningMsg = "Default constraint names will be required in a future version of Rivet. Add a " +
                              """Name"" parameter (with a value of ""$($Operation.Name)"") to the Remove-DefaultConstraint " +
                              "operation for the $($tableDesc) table's ""$($columnDesc)"" column."
            }
            'RemoveForeignKeyOperation'
            {
                $Operation.Name = New-ConstraintName -ForeignKey 
                                                     -SchemaName $schemaName `
                                                     -TableName $tableName `
                                                     -ReferencesSchema $Operation.ReferencesSchema `
                                                     -ReferencesTableName $Operation.ReferencesTableName
                $warningMsg = "Foreign key constraint names will be required in a future version of Rivet. " +
                              "Add a ""Name"" parameter (with a value of ""$($Operation.Name)"") to the Remove-ForeignKey " +
                              "operation for the $($tableDesc) table that references the " +
                              "[$($Operation.ReferencesSchemaName)].[$($Operation.ReferencesTableName)] table."
            }
            'RemoveIndexOperation'
            {
                $Operation.Name = New-ConstraintName -Index -SchemaName $schemaName -TableName $tableName -ColumnName $columnName -Unique:$Operation.Unique
                $warningMsg = "Index names will be required in a future version of Rivet. Add a ""Name"" " +
                              "parameter (with a value of ""$($Operation.Name)"") to the Remove-Index operation for the " +
                              "$($tableDesc) table's ""$($columnDesc)"" column$($pluralSuffix)."
            }
            'RemovePrimaryKeyOperation'
            {
                $Operation.Name = New-ConstraintName -PrimaryKey -SchemaName $schemaName -TableName $tableName
                $warningMsg = "Primay key constraint names will be required in a future version of Rivet. " +
                              "Add a ""Name"" parameter (with a value of ""$($Operation.Name)"") to the Remove-PrimaryKey " +
                              "operation for the $($tableDesc) table."
            }
            'RemoveUniqueKeyOperation'
            {
                $Operation.Name = New-ConstraintName -UniqueKey -SchemaName $schemaName -TableName $tableName -ColumnName $columnName
                $warningMsg = "Unique key constraint names will be required in a future version of Rivet. " +
                              "Remove the ""ColumnName"" parameter and add a ""Name"" parameter (with a value of " +
                              """$($Operation.Name)"") to the Remove-UniqueKey operation for the " +
                              "$($tableDesc) table's ""$($columnDesc)"" column$($pluralSuffix)."
            }
            'UpdateTableOperation'
            {
                $Operation.AddColumns | Repair-DefaultConstraintName
            }
        }

        if( $warningMsg )
        {
            Write-Warning -Message $warningMsg
        }

        return $Operation
    }

    end
    {

    }
}


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
    {
    }
}


function Test-Migration
{
    <#
    .SYNOPSIS
    Tests if a migration was applied to the database.
     
    .DESCRIPTION
    Returns `true` if a migration with the given ID has already been applied. `False` otherwise.
     
    .EXAMPLE
    Test-Migration -ID 20120211235838
     
    Returns `True` if a migration with ID `20120211235838` already exists or `False` if it doesn't.
    #>

    param(
        [Parameter(Mandatory=$true)]
        [Int64]
        $ID,

        [Switch]
        # Returns the migration info.
        $PassThru
    )
    
    $query = 'select ID, Name, Who, AtUtc from {0} where ID=@ID' -f $RivetMigrationsTableFullName,$ID
    $info = Invoke-Query -Query $query -Parameter @{ ID = $ID } -Verbose:$false
    if( $info )
    {
        Write-Debug -Message ('{0} {1,-35} {2,14:00000000000000}_{3}' -f $info.AtUtc.ToLocalTime().ToString('yyyy-mm-dd HH:mm'),$info.Who,$info.ID,$info.Name)
        if( $PassThru )
        {
            return $info
        }
        return $true
    }
    return $false
}



function Update-Database
{
    <#
    .SYNOPSIS
    Applies a set of migrations to the database.
     
    .DESCRIPTION
    By default, applies all unapplied migrations to the database. You can reverse all migrations with the `Down` switch.
     
    .EXAMPLE
    Update-Database -Path C:\Projects\Rivet\Databases\Rivet\Migrations
     
    Applies all un-applied migrations from the `C:\Projects\Rivet\Databases\Rivet\Migrations` directory.
     
    .EXAMPLE
    Update-Database -Path C:\Projects\Rivet\Databases\Rivet\Migrations -Pop
     
    Reverses all migrations in the `C:\Projects\Rivet\Databases\Rivet\Migrations` directory
    #>

    [CmdletBinding(DefaultParameterSetName='Push', SupportsShouldProcess=$True)]
    param(
        [Parameter(Mandatory=$true)]
        [Rivet.Configuration.Configuration]
        $Configuration,

        [Parameter(Mandatory=$true)]
        [string[]]
        # The path to the migration.
        $Path,

        [Parameter(Mandatory=$true,ParameterSetName='Pop')]
        [Parameter(Mandatory=$true,ParameterSetName='PopByName')]
        [Parameter(Mandatory=$true,ParameterSetName='PopByCount')]
        [Parameter(Mandatory=$true,ParameterSetName='PopAll')]
        [Switch]
        # Reverse the given migration(s).
        $Pop,

        [Parameter(ParameterSetName='Push')]
        [Parameter(Mandatory=$true,ParameterSetName='PopByName')]
        [string[]]
        $Name,

        [Parameter(Mandatory=$true,ParameterSetName='PopByCount')]
        [UInt32]
        # Reverse the given migration(s).
        $Count,

        [Parameter(Mandatory=$true,ParameterSetName='PopAll')]
        [Switch]
        # Reverse the given migration(s).
        $All,

        [Switch]
        # Running internal Rivet migrations. This is for internal use only. If you use this flag, Rivet will break when you upgrade. You've been warned!
        $RivetSchema,

        [Parameter(ParameterSetName='Pop')]
        [Parameter(ParameterSetName='PopByCount')]
        [Parameter(ParameterSetName='PopByName')]
        [Parameter(ParameterSetName='PopAll')]
        [Switch]
        # Force popping a migration you didn't apply or that is old.
        $Force
    )

    Set-StrictMode -Version 'Latest'

    function ConvertTo-RelativeTime
    {
        param(
            [Parameter(Mandatory=$true)]
            [DateTime]
            # The date time to convert to a relative time string.
            $DateTime
        )

        [TimeSpan]$howLongAgo = (Get-Date) - $DateTime
        $howLongAgoMsg = New-Object 'Text.StringBuilder'
        if( $howLongAgo.Days )
        {
            [void] $howLongAgoMsg.AppendFormat('{0} day', $howLongAgo.Days)
            if( $howLongAgo.Days -ne 1 )
            {
                [void] $howLongAgoMsg.Append('s')
            }
            [void] $howLongAgoMsg.Append(', ')
        }

        if( $howLongAgo.Days -or $howLongAgo.Hours )
        {
            [void] $howLongAgoMsg.AppendFormat('{0} hour', $howLongAgo.Hours)
            if( $howLongAgo.Hours -ne 1 )
            {
                [void] $howLongAgoMsg.Append('s')
            }
            [void] $howLongAgoMsg.Append(', ')
        }

        if( $howLongAgo.Days -or $howLongAgo.Hours -or $howLongAgo.Minutes )
        {
            [void] $howLongAgoMsg.AppendFormat('{0} minute', $howLongAgo.Minutes)
            if( $howLongAgo.Minutes -ne 1 )
            {
                [void] $howLongAgoMsg.Append('s')
            }
            [void] $howLongAgoMsg.Append(', ')
        }

        [void] $howLongAgoMsg.AppendFormat('{0} second', $howLongAgo.Seconds)
        if( $howLongAgo.Minutes -ne 1 )
        {
            [void] $howLongAgoMsg.Append('s')
        }

        [void] $howLongAgoMsg.Append( ' ago' )

        return $howLongAgoMsg.ToString()
    }

    $popping = ($PSCmdlet.ParameterSetName -like 'Pop*')
    $numPopped = 0

    $who = ('{0}\{1}' -f $env:USERDOMAIN,$env:USERNAME);

    #$matchedNames = @{ }
    $byName = @{ }
    if( $PSBoundParameters.ContainsKey('Name') )
    {
        $byName['Include'] = $Name
    }

    $query = 'if (object_id(''{0}'', ''U'') is not null) select ID, Name, Who, AtUtc from {0}' -f $RivetMigrationsTableFullName
    $appliedMigrations = @{}
    foreach( $migration in (Invoke-Query -Query $query) )
    {
        $appliedMigrations[$migration.ID] = $migration
    }

    $migrations = 
        Get-MigrationFile -Path $Path -Configuration $Configuration @byName -ErrorAction Stop |
        Sort-Object -Property 'MigrationID' -Descending:$popping |
        Where-Object {
            if( $RivetSchema )
            {
                return $true
            }

            if( [int64]$_.MigrationID -lt 1000000000000 )
            {
                Write-Error ('Migration "{0}" has an invalid ID. IDs lower than 01000000000000 are reserved for internal use.' -f $_.FullName) -ErrorAction Stop
                return $false
            }
            return $true
        } |
        Where-Object { 
            $migration = $appliedMigrations[$_.MigrationID]

            if( $popping )
            {
                if( $PSCmdlet.ParameterSetName -eq 'PopByCount' -and $numPopped -ge $Count )
                {
                    return $false
                }
                $numPopped++

                # Don't need to pop if migration hasn't been applied.
                if( -not $migration )
                {
                    return $false
                }

                $youngerThan = ((Get-Date).ToUniversalTime()) - (New-TimeSpan -Minutes 20)
                if( $migration.Who -ne $who -or $migration.AtUtc -lt $youngerThan )
                {
                    $howLongAgo = ConvertTo-RelativeTime -DateTime ($migration.AtUtc.ToLocalTime())
                    $confirmQuery = "Are you sure you want to pop migration {0} from database {1} on {2} applied by {3} {4}?" -f $_.FullName,$Connection.Database,$Connection.DataSource,$migration.Who,$howLongAgo
                    $confirmCaption = "Pop Migration {0}?" -f $_.FullName
                    if( -not $Force -and -not $PSCmdlet.ShouldContinue( $confirmQuery, $confirmCaption ) )
                    {
                        return $false
                    }
                }
                return $true
            }

            # Only need to parse/push if migration hasn't already been pushed.
            if( $migration )
            {
                return $false
            }
            return $true
        } |
        Convert-FileInfoToMigration -Configuration $Configuration 
        
    foreach( $migrationInfo in $migrations )
    {
        $migrationInfo.DataSource = $Connection.DataSource

        try
        {
            $Connection.Transaction = $Connection.BeginTransaction()

            if( $Pop )
            {
                $operations = $migrationInfo.PopOperations
                $action = 'Pop'
                $sprocName = 'RemoveMigration'
            }
            else
            {
                $operations = $migrationInfo.PushOperations
                $action = 'Push'
                $sprocName = 'InsertMigration'
            }

            if( -not $operations.Count )
            {
                Write-Error ('{0} migration''s {1}-Migration function is empty.' -f $migrationInfo.FullName,$action)
                return
            }

            $operations | Invoke-MigrationOperation -Migration $migrationInfo

            $query = 'exec [rivet].[{0}] @ID = @ID, @Name = @Name, @Who = @Who, @ComputerName = @ComputerName' -f $sprocName
            $parameters = @{
                                ID = [int64]$migrationInfo.ID; 
                                Name = $migrationInfo.Name;
                                Who = $who;
                                ComputerName = $env:COMPUTERNAME;
                            }
            Invoke-Query -Query $query -NonQuery -Parameter $parameters  | Out-Null

            $target = '{0}.{1}' -f $Connection.DataSource,$Connection.Database
            $operation = '{0} migration {1} {2}' -f $PSCmdlet.ParameterSetName,$migrationInfo.ID,$migrationInfo.Name
            if ($PSCmdlet.ShouldProcess($target, $operation))
            {
                $Connection.Transaction.Commit()
            }
            else 
            {
                $Connection.Transaction.Rollback()
                break
            }
        }
        catch
        {
            $Connection.Transaction.Rollback()
        
            # TODO: Create custom exception for migration query errors so that we can report here when unknown things happen.
            if( $_.Exception -isnot [ApplicationException] )
            {
                Write-RivetError -Message ('Migration {0} failed' -f $migrationInfo.Path) -CategoryInfo $_.CategoryInfo.Category -ErrorID $_.FullyQualifiedErrorID -Exception $_.Exception -CallStack ($_.ScriptStackTrace)
            }
            break
        }
        finally
        {
            $Connection.Transaction = $null
        }
    }
}


# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function Use-CallerPreference
{
    <#
    .SYNOPSIS
    Sets the PowerShell preference variables in a module's function based on the callers preferences.
 
    .DESCRIPTION
    Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module.
 
    When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller:
 
     * ErrorAction
     * Debug
     * Confirm
     * InformationAction
     * Verbose
     * WarningAction
     * WhatIf
     
    This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function.
 
    This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d).
 
    There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every function/cmdlet call in your function. Please vote up this issue so it can get fixed.
 
    .LINK
    about_Preference_Variables
 
    .LINK
    about_CommonParameters
 
    .LINK
    https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d
 
    .LINK
    http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/
 
    .EXAMPLE
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
 
    Demonstrates how to set the caller's common parameter preference variables in a module function.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        #[Management.Automation.PSScriptCmdlet]
        # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` attribute.
        $Cmdlet,

        [Parameter(Mandatory = $true)]
        [Management.Automation.SessionState]
        # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the `[CmdletBinding()]` attribute.
        #
        # Used to set variables in its callers' scope, even if that caller is in a different script module.
        $SessionState
    )

    Set-StrictMode -Version 'Latest'

    # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken from about_CommonParameters).
    $commonPreferences = @{
                              'ErrorActionPreference' = 'ErrorAction';
                              'DebugPreference' = 'Debug';
                              'ConfirmPreference' = 'Confirm';
                              'InformationPreference' = 'InformationAction';
                              'VerbosePreference' = 'Verbose';
                              'WarningPreference' = 'WarningAction';
                              'WhatIfPreference' = 'WhatIf';
                          }

    foreach( $prefName in $commonPreferences.Keys )
    {
        $parameterName = $commonPreferences[$prefName]

        # Don't do anything if the parameter was passed in.
        if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) )
        {
            continue
        }

        $variable = $Cmdlet.SessionState.PSVariable.Get($prefName)
        # Don't do anything if caller didn't use a common parameter.
        if( -not $variable )
        {
            continue
        }

        if( $SessionState -eq $ExecutionContext.SessionState )
        {
            Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false
        }
        else
        {
            $SessionState.PSVariable.Set($variable.Name, $variable.Value)
        }
    }

}



function Write-RivetError
{
    param(
        [Parameter(Mandatory=$true)]
        [string]
        # The error message to display.
        $Message,
        
        [Parameter(Mandatory=$true)]
        [Exception]
        # The exception being reported.
        $Exception,
        
        [Parameter(Mandatory=$true)]
        [string]
        # The call stack to report.
        $CallStack,

        [Parameter(Mandatory=$true)]
        [string]
        # The Category Info
        $CategoryInfo,

        [Parameter(Mandatory=$true)]
        [string]
        # The Fully Qualified Error ID
        $ErrorID,

        [Parameter()]
        [string]
        # Query, if any
        $Query
    )
    
    $firstException = $_.Exception
    while( $firstException.InnerException )
    {
        $firstException = $firstException.InnerException
    }
    
    if (-not $Query)
    {
        $Query = "None"
    }
        
    Write-Error (@"
[{0}].[{1}] {2}: {3}
  {4}
  
QUERY
=====
{5}
  
"@
 -f $Connection.DataSource,$Connection.Database,$Message,$firstException.Message,($CallStack -replace "`n","`n "), $Query) -ErrorID $ErrorID -Category $CategoryInfo 

}

function New-BigIntColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an BigInt datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Migrations' {
            BigInt 'MigrationID'
        }
 
    ## ALIASES
 
     * BigInt
 
    .EXAMPLE
    Add-Table 'Migrations' { BigInt 'MigrationID' }
 
    Demonstrates how to create an optional `bigint` column called `MigrationID`.
 
    .EXAMPLE
    Add-Table 'Migrations' { BigInt 'ID' -Identity 1 1 }
 
    Demonstrates how to create a required `bigint` column called `ID`, which is used as the table's identity. The identity values will start at 1, and increment by 1.
 
    .EXAMPLE
    Add-Table 'Migrations' { BigInt 'MigrationID' -NotNull }
 
    Demonstrates how to create a required `bigint` column called `MigrationID`.
 
    .EXAMPLE
    Add-Table 'Migrations' { BigInt 'MigrationID' -Sparse }
 
    Demonstrates how to create a sparse, optional `bigint` column called `MigrationID`.
 
    .EXAMPLE
    Add-Table 'Migrations' { BigInt 'MigrationID' -NotNull -Default '0' }
 
    Demonstrates how to create a required `bigint` column called `MigrationID` with a default value of `0`.
 
    .EXAMPLE
    Add-Table 'Migrations' { BigInt 'MigrationID' -NotNull -Description 'The number of items currently on hand.' }
 
    Demonstrates how to create a required `bigint` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='Identity')]
        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed')]
        # The column should be an identity.
        [switch]$Identity,

        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=1)]
        # The starting value for the identity.
        [int]$Seed,

        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=2)]
        # The increment between auto-generated identity values.
        [int]$Increment,

        [Parameter(ParameterSetName='Identity')]
        [Parameter(ParameterSetName='IdentityWithSeed')]
        # Stops the identity from being replicated.
        [switch]$NotForReplication,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value. The DefaultConstraintName parameter is required if this parameter is used.
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,

        # A description of the column.
        [String]$Description
    )

    Set-StrictMode -Version 'Latest'

    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::BigInt($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::BigInt($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }

        'Identity'
        {
            $i = New-Object 'Rivet.Identity' $NotForReplication
            [Rivet.Column]::BigInt( $Name, $i, $Description )
        }

        'IdentityWithSeed'
        {
            $i = New-Object 'Rivet.Identity' $Seed, $Increment, $NotForReplication
            [Rivet.Column]::BigInt( $Name, $i, $Description )
        }
    }
}

Set-Alias -Name 'BigInt' -Value 'New-BigIntColumn'

function New-BinaryColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an Binary datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Images' {
            Binary 'Bits' 256
        }
 
    ## ALIASES
 
     * Binary
 
    .EXAMPLE
    Add-Table 'Images' { Binary 'Bytes' 256 }
 
    Demonstrates how to create an optional `binary` column with a maximum length of 256 bytes.
 
    .EXAMPLE
    Add-Table 'Images' { Binary 'Bytes' 256 -NotNull }
 
    Demonstrates how to create a required `binary` column with maximum length of 256 bytes.
 
    .EXAMPLE
    Add-Table 'Images' { Binary 'Bytes' -Max }
 
    Demonstrates how to create an optional `binary` column with the maximum length (2^31 -1 bytes).
 
    .EXAMPLE
    Add-Table 'Images' { Binary 'Bytes' -Max -FileStream }
 
    Demonstrates now to create an optional `binary` column with the maximum length, and stores the data in a filestream data container.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,Position=1)]
        # The number of bytes the column will hold.
        [int]$Size,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $sizetype = New-Object Rivet.CharacterLength $Size

    $nullable = 'Null'
    if( $PSCmdlet.ParameterSetName -eq 'NotNull' )
    {
        $nullable = 'NotNull'
    }
    elseif( $Sparse )
    {
        $nullable = 'Sparse'
    }

    [Rivet.Column]::Binary($Name, $sizetype, $nullable, $Default, $DefaultConstraintName, $Description)
}
    
Set-Alias -Name 'Binary' -Value 'New-BinaryColumn'

function New-BitColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an Bit datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Items' {
            Bit 'IsAvailable'
        }
 
    ## ALIASES
 
     * Bit
 
    .EXAMPLE
    Add-Table 'Items' { Bit 'IsAvailable' }
 
    Demonstrates how to create an optional `bit` column called `IsAvailable`.
 
    .EXAMPLE
    Add-Table 'Items' { Bit 'IsAvailable' -NotNull }
 
    Demonstrates how to create a required `bit` column called `IsAvailable`.
 
    .EXAMPLE
    Add-Table 'Items' { Bit 'IsAvailable' -Sparse }
 
    Demonstrates how to create a sparse, optional `bit` column called `IsAvailable`.
 
    .EXAMPLE
    Add-Table 'Items' { Bit 'IsAvailable' -NotNull -Default '1' }
 
    Demonstrates how to create a required `bit` column called `IsAvailable` with a default value of `1`.
 
    .EXAMPLE
    Add-Table 'Items' { Bit 'IsAvailable' -NotNull -Description 'The price of the item.' }
 
    Demonstrates how to create a required `bit` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::Bit($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::Bit($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }
    }
}
    
Set-Alias -Name 'Bit' -Value 'New-BitColumn'

function New-CharColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an Char datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table -State 'Addresses' -Column {
            Char 'State' 2
        }
 
    ## ALIASES
 
     * Char
 
    .EXAMPLE
    Add-Table 'Addresses' { Char 'State' 2 }
 
    Demonstrates how to create an optional `char` column with a length of 2 bytes.
 
    .EXAMPLE
    Add-Table 'Addresses' { Char 'State' 2 -NotNull }
 
    Demonstrates how to create a required `char` column with length of 2 bytes.
 
    .EXAMPLE
    Add-Table 'Addresses' { Char 'State' 2 -Collation 'Latin1_General_BIN' }
 
    Demonstrates now to create an optional `char` column with a custom `Latin1_General_BIN` collation.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,Position=1)]
        [Alias('Length')]
        # The length of the column, i.e. the number of characters.
        [int]$Size,

        # Controls the code page that is used to store the data
        [String]$Collation,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $Sizetype = $null

    $Sizetype = New-Object Rivet.CharacterLength $Size

    $nullable = 'Null'
    if( $PSCmdlet.ParameterSetName -eq 'NotNull' )
    {
        $nullable = 'NotNull'
    }
    elseif( $Sparse )
    {
        $nullable = 'Sparse'
    }

    [Rivet.Column]::Char($Name, $Sizetype, $Collation, $nullable, $Default, $DefaultConstraintName, $Description)
}
    
Set-Alias -Name 'Char' -Value 'New-CharColumn'

function New-Column
{
    <#
    .SYNOPSIS
    Creates a column object of an explicit datatype which can be used with the `Add-Table` or `Update-Table` migrations.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Members' {
            New-Column 'Birthday' 'datetime'
        }
     
    This column is useful for creating columns of custom types or types for which Rivet doesn't have a specific function.
 
    Returns an object that can be used when adding columns or creating tables to get the SQL needed to create that column.
     
    .LINK
    New-BigIntColumn
 
    .LINK
    New-BinaryColumn
 
    .LINK
    New-BitColumn
 
    .LINK
    New-CharColumn
 
    .LINK
    New-DateColumn
 
    .LINK
    New-DateTime2Column
 
    .LINK
    New-DateTimeOffsetColumn
 
    .LINK
    New-DecimalColumn
 
    .LINK
    New-FloatColumn
 
    .LINK
    New-HierarchyIDColumn
 
    .LINK
    New-IntColumn
 
    .LINK
    New-MoneyColumn
 
    .LINK
    New-NCharColumn
 
    .LINK
    New-NVarCharColumn
 
    .LINK
    New-RealColumn
 
    .LINK
    New-RowVersionColumn
 
    .LINK
    New-SmallDateTimeColumn
 
    .LINK
    New-SmallIntColumn
 
    .LINK
    New-SmallMoneyColumn
 
    .LINK
    New-SqlVariantColumn
 
    .LINK
    New-TimeColumn
 
    .LINK
    New-TinyIntColumn
 
    .LINK
    New-UniqueIdentifierColumn
 
    .LINK
    New-VarBinaryColumn
 
    .LINK
    New-VarCharColumn
 
    .LINK
    New-XmlColumn
 
    .EXAMPLE
    Add-Table 'Members' { New-Column 'Birthday' 'datetime' -NotNull }
 
    Demonstrates how to create a required `datetime` column.
 
    .EXAMPLE
    Add-Table 'Members' { New-Column 'Birthday' 'float(7)' -NotNull }
 
    Demonstrates that the value of the `DataType` parameter should also include any precision/scale/size specifiers.
 
    .EXAMPLE
    Add-Table 'Members' { New-Column 'Birthday' 'datetime' -Sparse }
 
    Demonstrate show to create a nullable, sparse `datetime` column when adding a new table.
 
    .EXAMPLE
    Add-Table 'Members' { New-Column 'Birthday' 'datetime' -NotNull -Default 'getdate()' }
     
    Demonstrates how to create a date column with a default value, in this case the current date. (You alwyas use UTC dates, right?) Probably not a great example, setting someone's birthday to the current date. Reasons are left as an exercise for the reader.
 
    .EXAMPLE
    Add-Table 'Members' { New-Column 'Birthday' 'datetime' -Description 'The members birthday.' }
 
    Demonstrates how to create an optional date column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(

        [Parameter(Mandatory,Position=0)]
        # The Name of the new column.
        [String]$Name,

        [Parameter(Mandatory,Position=1)]
        # The datatype of the new column. Scale/size/precision clause is optional.
        [String]$DataType,

        [Parameter(ParameterSetName='Nullable')]
        [Parameter(ParameterSetName='NotNull')]
        # Allow the column to be its maximum size. Sets the columnn's size clause to `(max)`. Only use this with columns whose underlying type supports it. If you supply this argument, the `Size`, `Precision`, and `Scale` parameters are ignored.
        [switch]$Max,

        [Parameter(ParameterSetName='Nullable')]
        [Parameter(ParameterSetName='NotNull')]
        # The size/length of the column. Sets the column's size clause to `($Size)`. Ignored if `Max` parameter is used. If provided, the `Precision` and `Scale` parameters are ignored.
        [int]$Size,

        [Parameter(ParameterSetName='Nullable')]
        [Parameter(ParameterSetName='NotNull')]
        # The precision of the column. Set's the columns size clause to `($Precision)`. If `Scale` is also given, the size clause is set to `($Precision,$Scale)`. Ignored if the `Max` or `Size` parameters are used.
        [int]$Precision,

        [Parameter(ParameterSetName='Nullable')]
        [Parameter(ParameterSetName='NotNull')]
        # The scale of the column. Set's the column's size clause to `($Scale)`. If `Precision` is also given, the size clause is set to `($Precision,$Scale)`. Ignored if the `Max` or `Size` parameters are used.
        [int]$Scale,

        [Parameter(Mandatory,ParameterSetName='Identity')]
        # Make the column an identity.
        [switch]$Identity,

        [Parameter(ParameterSetName='Identity')]
        # The starting value for the identity column.
        [int]$Seed,

        [Parameter(ParameterSetName='Identity')]
        # The increment between new identity values.
        [int]$Increment,

        [Parameter(ParameterSetName='Identity')]
        # Don't replicate the identity column value.
        [switch]$NotForReplication,

        [Parameter(ParameterSetName='Nullable')]
        # Optimizes the column storage for null values. Cannot be used with the `NotNull` switch.
        [switch]$Sparse,

        [Parameter(ParameterSetName='NotNull')]
        # Makes the column not nullable. Cannot be used with the `Sparse` switch.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        [Parameter(ParameterSetName='NotNull')]
        # The collation of the column.
        [String]$Collation,

        # Whether or not to make the column a `rowguidcol`.
        [switch]$RowGuidCol,

        [Parameter(ParameterSetName='Nullable')]
        [Parameter(ParameterSetName='NotNull')]
        # A SQL Server expression for the column's default value.
        [Object]$Default,

        [Parameter(ParameterSetName='Nullable')]
        [Parameter(ParameterSetName='NotNull')]
        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,

        # A description of the column.
        [String]$Description,
        
        # Whether or not the column is a filestream.
        [switch]$FileStream       
    )

    [Rivet.ColumnSize]$sizeParam = $null
    if( $Max )
    {
        $sizeParam = [Rivet.CharacterLength]::new()
    }
    elseif( $PSBoundParameters.ContainsKey('Size') )
    {
        $sizeParam = [Rivet.CharacterLength]::new($Size)
    }
    elseif( $PSBoundParameters.ContainsKey('Precision') -and $PSBoundParameters.ContainsKey('Scale') )
    {
        $sizeParam = [Rivet.PrecisionScale]::new($Precision,$Scale)
    }
    elseif( $PSBoundParameters.ContainsKey('Precision') )
    {
        $sizeParam = [Rivet.PrecisionScale]::new($Precision)
    }
    elseif( $PSBoundParameters.ContainsKey('Scale') )
    {
        $sizeParam = [Rivet.Scale]::new($Scale)
    }

    if( $PSCmdlet.ParameterSetName -eq 'Identity' )
    {
        [Rivet.Identity]$identityParam = [Rivet.Identity]::new($NotForReplication)
        if( $Seed -or $Increment )
        {
            $identityParam = [Rivet.Identity]::new($Seed, $Increment, $NotForReplication)
        }
        [Rivet.Column]::new($Name, $DataType, $sizeParam, $identityParam, $RowGuidCol, $Description, $FileStream)
    }
    else
    {
        $nullable = 'Null'
        if( $PSCmdlet.ParameterSetName -eq 'NotNull' )
        {
            $nullable = 'NotNull'
        }
        elseif( $Sparse )
        {
            $nullable = 'Sparse'
        }

        [Rivet.Column]::new($Name, $DataType, $sizeParam, $nullable, $Collation, $RowGuidCol, $Default, $DefaultConstraintName, $Description, $FileStream)
    }
}

function New-DateColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an Date datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Members' {
            Date 'Birthday'
        }
 
    ## ALIASES
 
     * Date
 
    .EXAMPLE
    Add-Table 'Members' { New-DateColumn 'Birthday' -NotNull }
 
    Demonstrates how to create a required `date` column.
 
    .EXAMPLE
    Add-Table 'Members' { Date 'Birthday' -Sparse }
 
    Demonstrate show to create a nullable, sparse `date` column when adding a new table.
 
    .EXAMPLE
    Add-Table 'Members' { Date 'Birthday' -NotNull -Default 'get`date`()' }
     
    Demonstrates how to create a `date` column with a default value, in this case the current `date`. (You alwyas use UTC `date`s, right?) Probably not a great example, setting someone's birthday to the current `date`. Reasons are left as an exercise for the reader.
 
    .EXAMPLE
    Add-Table 'Members' { Date 'Birthday' -Description 'The members birthday.' }
 
    Demonstrates how to create an optional `date` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::Date($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::Date($Name, [Rivet.Nullable]::NotNull, $Default, $DefaultConstraintName, $Description)
        }
    }
}
    
Set-Alias -Name 'Date' -Value 'New-DateColumn'

function New-DateTime2Column
{
    <#
    .SYNOPSIS
    Creates a column object representing an DateTime2 datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Orders' {
            DateTime2 'OrderedAt'
        }
 
    ## ALIASES
 
     * DateTime2
 
    .EXAMPLE
    Add-Table 'Orers' { DateTime2 'OrderedAt' }
 
    Demonstrates how to create an optional `datetime2` column.
 
    .EXAMPLE
    Add-Table 'Orders' { DateTime2 'OrderedAt' 5 -NotNull }
 
    Demonstrates how to create a required `datetime2` column with 5 digits of fractional seconds precision.
 
    .EXAMPLE
    Add-Table 'Orders' { DateTime2 'OrderedAt' -Sparse }
 
    Demonstrate show to create a nullable, sparse `datetime2` column when adding a new table.
 
    .EXAMPLE
    Add-Table 'Orders' { DateTime2 'OrderedAt' -NotNull -Default 'getutcdate()' }
 
    Demonstrates how to create a `datetime2` column with a default value. You only use UTC dates, right?
 
    .EXAMPLE
    Add-Table 'Orders' { DateTime2 'OrderedAt' -NotNull -Description 'The time the record was created.' }
 
    Demonstrates how to create a `datetime2` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Null')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Position=1)]
        [Alias('Precision')]
        # The number of decimal digits for the fractional seconds. SQL Server's default is `7`, or 100 nanoseconds.
        [int]$Scale,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Null')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $dataSize = $null
    if( $PSBoundParameters.ContainsKey( 'Scale' ) )
    {
        $dataSize = New-Object Rivet.Scale $Scale
    }
     
    $nullable = $PSCmdlet.ParameterSetName
    if( $nullable -eq 'Null' -and $Sparse )
    {
        $nullable = 'Sparse'
    }

    [Rivet.Column]::DateTime2($Name, $dataSize, $nullable, $Default, $DefaultConstraintName, $Description)
}
    
Set-Alias -Name 'DateTime2' -Value 'New-DateTime2Column'

function New-DateTimeColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an DateTime datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Orders' {
            DateTime 'OrderedAt'
        }
 
    ## ALIASES
 
     * DateTime
 
    .EXAMPLE
    Add-Table 'Orers' { DateTime 'OrderedAt' }
 
    Demonstrates how to create an optional `datetime` column.
 
    .EXAMPLE
    Add-Table 'Orders' { DateTime 'OrderedAt' 5 -NotNull }
 
    Demonstrates how to create a required `datetime` column with 5 digits of fractional seconds precision.
 
    .EXAMPLE
    Add-Table 'Orders' { DateTime 'OrderedAt' -Sparse }
 
    Demonstrate show to create a nullable, sparse `datetime` column when adding a new table.
 
    .EXAMPLE
    Add-Table 'Orders' { DateTime 'OrderedAt' -NotNull -Default 'getutcdate()' }
 
    Demonstrates how to create a `datetime` column with a default value. You only use UTC dates, right?
 
    .EXAMPLE
    Add-Table 'Orders' { DateTime 'OrderedAt' -NotNull -Description 'The time the record was created.' }
 
    Demonstrates how to create a `datetime` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,

        # A description of the column.
        [String]$Description
    )

    if ($PsCmdlet.ParameterSetName -eq 'Nullable')
    {
        if ($Sparse)
        {
            New-Column -Name $Name -DataType 'datetime' -Sparse -Default $Default -DefaultConstraintName $DefaultConstraintName -Description $Description
        }
        else {
            New-Column -Name $Name -DataType 'datetime' -Default $Default -DefaultConstraintName $DefaultConstraintName -Description $Description
        }
    }
    elseif ($PsCmdlet.ParameterSetName -eq 'NotNull')
    {
        New-Column -Name $Name -DataType 'datetime' -NotNull -Default $Default -DefaultConstraintName $DefaultConstraintName -Description $Description
    }
}
    
Set-Alias -Name 'DateTime' -Value 'New-DateTimeColumn'

function New-DateTimeOffsetColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an DateTimeOffset datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Orders' {
            DateTimeOffset 'OrderedAt'
        }
 
    ## ALIASES
 
     * DateTimeOffset
 
    .EXAMPLE
    Add-Table 'Orers' { DateTimeOffset 'OrderedAt' }
 
    Demonstrates how to create an optional `datetimeoffset` column.
 
    .EXAMPLE
    Add-Table 'Orders' { DateTimeOffset 'OrderedAt' 5 -NotNull }
 
    Demonstrates how to create a required `datetimeoffset` column with a digits of fractional seconds precision.
 
    .EXAMPLE
    Add-Table 'Orders' { DateTimeOffset 'OrderedAt' -Sparse }
 
    Demonstrate show to create a nullable, sparse `datetimeoffset` column when adding a new table.
 
    .EXAMPLE
    Add-Table 'Orders' { DateTimeOffset 'OrderedAt' -NotNull -Default 'getutcdate()' }
 
    Demonstrates how to create a `datetimeoffset` column with a default value. You only use UTC dates, right?
 
    .EXAMPLE
    Add-Table 'Orders' { DateTimeOffset 'OrderedAt' -NotNull -Description 'The time the record was created.' }
 
    Demonstrates how to create a `datetimeoffset` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Null')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Position=1)]
        [Alias('Precision')]
        # The number of decimal digits for the fractional seconds. SQL Server's default is `7`, or 100 nanoseconds.
        [int]$Scale,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Null')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $dataSize = $null
    if( $PSBoundParameters.ContainsKey('Scale') )
    {
        $dataSize = New-Object Rivet.Scale $Scale
    }
     
    $nullable = $PSCmdlet.ParameterSetName
    if( $nullable -eq 'Null' -and $Sparse )
    {
        $nullable = 'Sparse'
    }

    [Rivet.Column]::DateTimeOffset($Name, $dataSize, $nullable, $Default, $DefaultConstraintName, $Description)
}
    
Set-Alias -Name 'DateTimeOffset' -Value 'New-DateTimeOffsetColumn'

function New-DecimalColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing a `decimal` data type.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Items' {
            Decimal 'Price'
        }
 
    ## ALIASES
 
     * Decimal
     * Numeric
     * New-NumericColumn
 
    .EXAMPLE
    Add-Table 'Items' { Decimal 'Price' 5 2 }
 
    Demonstrates how to create an optional `decimal` column called `Price`, with a five-digit precision (prices less than $999.99) and a scale of 2 (2 digits after the `decimal`).
 
    .EXAMPLE
    Add-Table 'Items' { Decimal 'Price' -Identity -Seed 1 -Increment 1 }
 
    Demonstrates how to create a required `decimal` column called `Price`, which is used as the table's identity. The identity values will start at 1, and increment by 1. Uses SQL Server's default precision/scale.
 
    .EXAMPLE
    Add-Table 'Items' { Decimal 'Price' -NotNull }
 
    Demonstrates how to create a required `decimal` column called `Price`. Uses SQL Server's default precision/scale.
 
    .EXAMPLE
    Add-Table 'Items' { Decimal 'Price' -Sparse }
 
    Demonstrates how to create a sparse, optional `decimal` column called `Price`. Uses SQL Server's default precision/scale.
 
    .EXAMPLE
    Add-Table 'Items' { Decimal 'Price' -NotNull -Default '0' }
 
    Demonstrates how to create a required `decimal` column called `Price` with a default value of `0`. Uses SQL Server's default precision/scale.
 
    .EXAMPLE
    Add-Table 'Items' { Decimal 'Price' -NotNull -Description 'The price of the item.' }
 
    Demonstrates how to create a required `decimal` column with a description. Uses SQL Server's default precision/scale.
    #>

    [CmdletBinding(DefaultParameterSetName='Null')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Position=1)]
        # Maximum total number of decimal digits that will be stored.
        [int]$Precision,

        [Parameter(Position=2)]
        # The number of decimal digits that will be stored to the right of the decimal point.
        [int]$Scale,

        [Parameter(Mandatory,ParameterSetName='Identity')]
        # The column should be an identity.
        [switch]$Identity,

        [Parameter(ParameterSetName='Identity')]
        # The starting value for the identity.
        [int]$Seed,

        [Parameter(ParameterSetName='Identity')]
        # The increment between auto-generated identity values.
        [int]$Increment,

        [Parameter(ParameterSetName='Identity')]
        # Stops the identity from being replicated.
        [switch]$NotForReplication,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Null')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    $dataSize = $null
    if( $PSBoundParameters.ContainsKey( 'Precision' ) -and $PSBoundParameters.ContainsKey( 'Scale' ) )
    {
        $dataSize = New-Object Rivet.PrecisionScale $Precision, $Scale
    }
    elseif( $PSBoundParameters.ContainsKey( 'Precision' ) )
    {
        $dataSize = New-Object Rivet.PrecisionScale $Precision    
    }
    elseif( $PSBoundParameters.ContainsKey( 'Scale' ) )
    {
        throw ('New-DecimalColumn: a scale for column {0} is given, but no precision. Please remove the `-Scale` parameter, or add a `-Precision` parameter with a value.' -f $Name)
    }

    switch ($PSCmdlet.ParameterSetName)
    {
        'Null'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::Decimal($Name, $dataSize, $nullable, $Default, $DefaultConstraintName, $Description)
            break
        }
            
        'NotNull'
        {
            [Rivet.Column]::Decimal($Name, $dataSize, 'NotNull', $Default, $DefaultConstraintName, $Description)
            break
        }

        'Identity'
        {
            if( $PSBoundParameters.ContainsKey('Seed') -and $PSBoundParameters.ContainsKey('Increment') )
            {
                $i = New-Object 'Rivet.Identity' $Seed,$Increment,$NotForReplication
            }
            elseif( $PSBoundParameters.ContainsKey('Seed') )
            {
                $i = New-Object 'Rivet.Identity' $Seed,1,$NotForReplication
            }
            elseif( $PSBoundParameters.ContainsKey('Increment') )
            {
                $i = New-Object 'Rivet.Identity' 1,$Increment,$NotForReplication
            }
            else
            {
                $i = New-Object 'Rivet.Identity' $NotForReplication
            }
            [Rivet.Column]::Decimal( $Name, $dataSize, $i, $Description )
            break
        }

    }
}
    
Set-Alias -Name 'Decimal' -Value 'New-DecimalColumn'
Set-Alias -Name 'Numeric' -Value 'New-DecimalColumn'
Set-Alias -Name 'New-NumericColumn' -Value 'New-DecimalColumn'



function New-FloatColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing a `float` datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Items' {
            Float 'Price'
        }
 
    ## ALIASES
 
     * Float
 
    .EXAMPLE
    Add-Table 'Items' { Float 'Price' -Precision 5 }
 
    Demonstrates how to create an optional `float` column called `Price`, with a precision of 5.
 
    .EXAMPLE
    Add-Table 'Items' { Float 'Price' -NotNull }
 
    Demonstrates how to create a required `float` column called `Price`. Uses SQL Server's default precision.
 
    .EXAMPLE
    Add-Table 'Items' { Float 'Price' -Sparse }
 
    Demonstrates how to create a sparse, optional `float` column called `Price`. Uses SQL Server's default precision.
 
    .EXAMPLE
    Add-Table 'Items' { Float 'Price' -NotNull -Default '0.0' }
 
    Demonstrates how to create a required `float` column called `Price` with a default value of `0`. Uses SQL Server's default precision.
 
    .EXAMPLE
    Add-Table 'Items' { Float 'Price' -NotNull -Description 'The price of the item.' }
 
    Demonstrates how to create a required `float` column with a description. Uses SQL Server's default precision.
    #>

    [CmdletBinding(DefaultParameterSetName='Null')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Position=1)]
        # Maximum total number of Numeric digits that will be stored
        [int]$Precision,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Null')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $dataSize = $null

    if ($Precision -gt 0)
    {
        $dataSize = New-Object Rivet.PrecisionScale $Precision
    }
    
    $nullable = $PSCmdlet.ParameterSetName
    if( $nullable -eq 'Null' -and $Sparse )
    {
        $nullable = 'Sparse'
    }
    [Rivet.Column]::Float($Name, $dataSize, $nullable, $Default, $DefaultConstraintName, $Description)
}
    
Set-Alias -Name 'Float' -Value 'New-FloatColumn'

function New-HierarchyIDColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an HierarchyID datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'FamilyTree' {
            HierarchyID 'Father'
        }
 
    ## ALIASES
 
     * HierarchyID
 
    .EXAMPLE
    Add-Table 'FamilyTree' { HierarchyID 'Father' }
 
    Demonstrates how to create an optional `hierarchyid` column called `Father`.
 
    .EXAMPLE
    Add-Table 'FamilyTree' { HierarchyID 'Father' -NotNull }
 
    Demonstrates how to create a required `hierarchyid` column called `Father`.
 
    .EXAMPLE
    Add-Table 'FamilyTree' { HierarchyID 'Father' -Sparse }
 
    Demonstrates how to create a sparse, optional `hierarchyid` column called `Father`.
 
    .EXAMPLE
    Add-Table 'FamilyTree' { HierarchyID 'Father' -NotNull -Description "The hierarchy ID of this person's father." }
 
    Demonstrates how to create a required `hierarchyid` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::HierarchyID($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::HierarchyID($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }
    }
}
    
Set-Alias -Name 'HierarchyID' -Value 'New-HierarchyIDColumn'

function New-IntColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an Int datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Items' {
            Int 'Quantity'
        }
 
    ## ALIASES
 
     * Int
 
    .EXAMPLE
    Add-Table 'Items' { Int 'Quantity' }
 
    Demonstrates how to create an optional `int` column called `Quantity`.
 
    .EXAMPLE
    Add-Table 'Items' { Int 'Quantity' -Identity 1 1 }
 
    Demonstrates how to create a required `int` column called `Quantity`, which is used as the table's identity. The identity values will start at 1, and increment by 1.
 
    .EXAMPLE
    Add-Table 'Items' { Int 'Quantity' -NotNull }
 
    Demonstrates how to create a required `int` column called `Quantity`.
 
    .EXAMPLE
    Add-Table 'Items' { Int 'Quantity' -Sparse }
 
    Demonstrates how to create a sparse, optional `int` column called `Quantity`.
 
    .EXAMPLE
    Add-Table 'Items' { Int 'Quantity' -NotNull -Default '0' }
 
    Demonstrates how to create a required `int` column called `Quantity` with a default value of `0`.
 
    .EXAMPLE
    Add-Table 'Items' { Int 'Quantity' -NotNull -Description 'The number of items currently on hand.' }
 
    Demonstrates how to create a required `int` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='Identity')]
        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed')]
        # The column should be an identity.
        [switch]$Identity,

        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=1)]
        # The starting value for the identity.
        [int]$Seed,

        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=2)]
        # The increment between auto-generated identity values.
        [int]$Increment,

        [Parameter(ParameterSetName='Identity')]
        [Parameter(ParameterSetName='IdentityWithSeed')]
        # Stops the identity from being replicated.
        [switch]$NotForReplication,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::Int($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::Int($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }

        'Identity'
        {
            $i = New-Object 'Rivet.Identity' $NotForReplication
            [Rivet.Column]::Int( $Name, $i, $Description )
        }

        'IdentityWithSeed'
        {
            $i = New-Object 'Rivet.Identity' $Seed, $Increment, $NotForReplication
            [Rivet.Column]::Int( $Name, $i, $Description )
        }

            
    }
}
    
Set-Alias -Name 'Int' -Value 'New-IntColumn'

function New-MoneyColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an Money datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Items' {
            Money 'Price'
        }
 
    ## ALIASES
 
     * Money
 
    .EXAMPLE
    Add-Table 'Items' { Money 'Price' }
 
    Demonstrates how to create an optional `money` column called `Price`.
 
    .EXAMPLE
    Add-Table 'Items' { Money 'Price' -NotNull }
 
    Demonstrates how to create a required `money` column called `Price`.
 
    .EXAMPLE
    Add-Table 'Items' { Money 'Price' -Sparse }
 
    Demonstrates how to create a sparse, optional `money` column called `Price`.
 
    .EXAMPLE
    Add-Table 'Items' { Money 'Price' -NotNull -Default '0.00' }
 
    Demonstrates how to create a required `money` column called `Price` with a default value of `$0.00`.
 
    .EXAMPLE
    Add-Table 'Items' { Money 'Price' -NotNull -Description 'The price of the item.' }
 
    Demonstrates how to create a required `money` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::Money($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::Money($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }
    }
}
    
Set-Alias -Name 'Money' -Value 'New-MoneyColumn'

function New-NCharColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an NChar datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table -State 'Addresses' -Column {
            NChar 'State' 2
        }
 
    ## ALIASES
 
     * NChar
 
    .EXAMPLE
    Add-Table 'Addresses' { NChar 'State' 2 }
 
    Demonstrates how to create an optional `nchar` column with a length of 2 bytes.
 
    .EXAMPLE
    Add-Table 'Addresses' { NChar 'State' 2 -NotNull }
 
    Demonstrates how to create a required `nchar` column with length of 2 bytes.
 
    .EXAMPLE
    Add-Table 'Addresses' { NChar 'State' 2 -Collation 'Latin1_General_BIN' }
 
    Demonstrates now to create an optional `nchar` column with a custom `Latin1_General_BIN` collation.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,Position=1)]
        # Defines the string Size of the fixed-Size string data. Default is 30
        [int]$Size,

        # Controls the code page that is used to store the data
        [String]$Collation,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $Sizetype = $null

    $Sizetype = New-Object Rivet.CharacterLength $Size

    $nullable = 'Null'

    if( $PSCmdlet.ParameterSetName -eq 'NotNull' )
    {
        $nullable = 'NotNull'
    }
    elseif( $Sparse )
    {
        $nullable = 'Sparse'
    }
    [Rivet.Column]::NChar($Name, $Sizetype, $Collation, $nullable, $Default, $DefaultConstraintName, $Description)
}
    
Set-Alias -Name 'NChar' -Value 'New-NCharColumn'

function New-NVarCharColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an NVarChar datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table -Name 'Albums' -Column {
            NVarChar 'Name' 50
        }
 
    ## ALIASES
 
     * NVarChar
 
    .EXAMPLE
    Add-Table 'Albums' { NVarChar 'Name' 100 }
 
    Demonstrates how to create an optional `nvarchar` column with a maximum length of 100 bytes.
 
    .EXAMPLE
    Add-Table 'Albums' { NVarChar 'Name' 100 -NotNull }
 
    Demonstrates how to create a required `nvarchar` column with maximum length of 100 bytes.
 
    .EXAMPLE
    Add-Table 'Albums' { NVarChar 'Name' -Max }
 
    Demonstrates how to create an optional `nvarchar` column with the maximum length (about 2GB).
 
    .EXAMPLE
    Add-Table 'Albums' { NVarChar 'Name' 100 -Collation 'Latin1_General_BIN' }
 
    Demonstrates now to create an optional `nvarchar` column with a custom `Latin1_General_BIN` collation.
    #>

    [CmdletBinding(DefaultParameterSetName='NullSize')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,Position=1,ParameterSetName='NullSize')]
        [Parameter(Mandatory,Position=1,ParameterSetName='NotNullSize')]
        [Alias('Length')]
        # The maximum length of the column, i.e. the number of unicode characters.
        [int]$Size,

        [Parameter(Mandatory,ParameterSetName='NullMax')]
        [Parameter(Mandatory,ParameterSetName='NotNullMax')]
        # Create an `nvarchar(max)` column.
        [switch]$Max,

        # Controls the code page that is used to store the data
        [String]$Collation,

        [Parameter(Mandatory,ParameterSetName='NotNullSize')]
        [Parameter(Mandatory,ParameterSetName='NotNullMax')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='NullSize')]
        [Parameter(ParameterSetName='NullMax')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $sizeType = $null
    if( $PSCmdlet.ParameterSetName -like '*Size' )
    {
        $sizeType = New-Object Rivet.CharacterLength $Size
    }
    else 
    {
        $sizeType = New-Object Rivet.CharacterLength @()   
    }

    $nullable = 'Null'
    if( $PSCmdlet.ParameterSetName -like 'NotNull*' )
    {
        $nullable = 'NotNull'
    }
    elseif( $Sparse )
    {
        $nullable = 'Sparse'
    }

    [Rivet.Column]::NVarChar($Name, $sizeType, $Collation, $nullable, $Default, $DefaultConstraintName, $Description)
}
    
Set-Alias -Name 'NVarChar' -Value 'New-NVarCharColumn'

function New-RealColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an Real datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Items' {
            Real 'Price'
        }
 
    ## ALIASES
 
     * Real
 
    .EXAMPLE
    Add-Table 'Items' { Real 'Price' }
 
    Demonstrates how to create an optional `real` column called `Price`.
 
    .EXAMPLE
    Add-Table 'Items' { Real 'Price' -NotNull }
 
    Demonstrates how to create a required `real` column called `Price`.
 
    .EXAMPLE
    Add-Table 'Items' { Real 'Price' -Sparse }
 
    Demonstrates how to create a sparse, optional `real` column called `Price`.
 
    .EXAMPLE
    Add-Table 'Items' { Real 'Price' -NotNull -Default '0.00' }
 
    Demonstrates how to create a required `real` column called `Price` with a default value of `$0.00`.
 
    .EXAMPLE
    Add-Table 'Items' { Real 'Price' -NotNull -Description 'The price of the item.' }
 
    Demonstrates how to create a required `real` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::Real($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::Real($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }
    }
}
    
Set-Alias -Name 'Real' -Value 'New-RealColumn'

function New-RowVersionColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an RowVersion datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'WithUUID' {
            RowVersion 'ColumnName'
        }
 
    ## ALIASES
 
     * RowVersion
 
    .EXAMPLE
    Add-Table Changes { RowVersion 'Version' }
 
    Demonstrates how to create a table with an optional `rowversion` column.
 
    .EXAMPLE
    Add-Table Locations { RowVersion 'LocationID' -RowGuidCol }
 
    Demonstrates how to create a table with an optional `rowversion`, which is used as the RowGuid identifier for SQL Server replication.
 
    .EXAMPLE
    Add-Table Locations { RowVersion 'LocationID' -NotNull }
 
    Demonstrates how to create a table with an required `rowversion` column.
 
    .EXAMPLE
    Add-Table Locations { RowVersion 'LocationID' -Default 'newid()' }
 
    Demonstrates how to create a table with an optional `rowversion` column with a default value.
 
    .EXAMPLE
    Add-Table Locations { RowVersion 'LocationID' -Description 'The unique identifier for this location.' }
 
    Demonstrates how to create a table with an optional `rowversion` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::RowVersion($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::RowVersion($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }
    }
}
    
Set-Alias -Name 'RowVersion' -Value 'New-RowVersionColumn'

function New-SmallDateTimeColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an SmallDateTime datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Orders' {
            SmallDateTime 'OrderedAt'
        }
 
    ## ALIASES
 
     * SmallDateTime
 
    .EXAMPLE
    Add-Table 'Orders' { New-SmallDateTimeColumn 'OrderedAt' -NotNull }
 
    Demonstrates how to create a required `smalldatetime` colum when adding a new table.
 
    .EXAMPLE
    Add-Table 'Orders' { SmallDateTime 'OrderedAt' -Sparse }
 
    Demonstrate show to create a nullable, sparse `smalldatetime` column when adding a new table.
 
    .EXAMPLE
    Add-Table 'Orders' { SmallDateTime 'OrderedAt' -NotNull -Default 'getutcdate()' }
 
    Demonstrates how to create a `smalldatetime` column with a default value. You only use UTC dates, right?
 
    .EXAMPLE
    Add-Table 'Orders' { SmallDateTime 'OrderedAt' -NotNull -Description 'The time the record was created.' }
 
    Demonstrates how to create a `smalldatetime` column a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
 
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::SmallDateTime($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::SmallDateTime($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }
    }
}
    
Set-Alias -Name 'SmallDateTime' -Value 'New-SmallDateTimeColumn'

function New-SmallIntColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an SmallInt datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Items' {
            SmallInt 'Quantity'
        }
 
    ## ALIASES
 
     * SmallInt
 
    .EXAMPLE
    Add-Table 'Items' { SmallInt 'Quantity' }
 
    Demonstrates how to create an optional `smallint` column called `Quantity`.
 
    .EXAMPLE
    Add-Table 'Items' { SmallInt 'Quantity' -Identity 1 1 }
 
    Demonstrates how to create a required `smallint` column called `Quantity`, which is used as the table's identity. The identity values will start at 1, and increment by 1.
 
    .EXAMPLE
    Add-Table 'Items' { SmallInt 'Quantity' -NotNull }
 
    Demonstrates how to create a required `smallint` column called `Quantity`.
 
    .EXAMPLE
    Add-Table 'Items' { SmallInt 'Quantity' -Sparse }
 
    Demonstrates how to create a sparse, optional `smallint` column called `Quantity`.
 
    .EXAMPLE
    Add-Table 'Items' { SmallInt 'Quantity' -NotNull -Default '0' }
 
    Demonstrates how to create a required `smallint` column called `Quantity` with a default value of `0`.
 
    .EXAMPLE
    Add-Table 'Items' { SmallInt 'Quantity' -NotNull -Description 'The number of items currently on hand.' }
 
    Demonstrates how to create a required `smallint` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='Identity')]
        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed')]
        # The column should be an identity.
        [switch]$Identity,

        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=1)]
        # The starting value for the identity.
        [int]$Seed,

        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=2)]
        # The increment between auto-generated identity values.
        [int]$Increment,

        [Parameter(ParameterSetName='Identity')]
        [Parameter(ParameterSetName='IdentityWithSeed')]
        # Stops the identity from being replicated.
        [switch]$NotForReplication,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::SmallInt($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::SmallInt($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }

        'Identity'
        {
            $i = New-Object 'Rivet.Identity' $NotForReplication
            [Rivet.Column]::SmallInt( $Name, $i, $Description )
        }

        'IdentityWithSeed'
        {
            $i = New-Object 'Rivet.Identity' $Seed, $Increment, $NotForReplication
            [Rivet.Column]::SmallInt( $Name, $i, $Description )
        }

            
    }
}
    
Set-Alias -Name 'SmallInt' -Value 'New-SmallIntColumn'

function New-SmallMoneyColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an SmallMoney datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Items' {
            SmallMoney 'Price'
        }
 
    ## ALIASES
 
     * SmallMoney
 
    .EXAMPLE
    Add-Table 'Items' { SmallMoney 'Price' }
 
    Demonstrates how to create an optional `smallmoney` column called `Price`.
 
    .EXAMPLE
    Add-Table 'Items' { SmallMoney 'Price' -NotNull }
 
    Demonstrates how to create a required `smallmoney` column called `Price`.
 
    .EXAMPLE
    Add-Table 'Items' { SmallMoney 'Price' -Sparse }
 
    Demonstrates how to create a sparse, optional `smallmoney` column called `Price`.
 
    .EXAMPLE
    Add-Table 'Items' { SmallMoney 'Price' -NotNull -Default '0.00' }
 
    Demonstrates how to create a required `smallmoney` column called `Price` with a default value of `$0.00`.
 
    .EXAMPLE
    Add-Table 'Items' { SmallMoney 'Price' -NotNull -Description 'The price of the item.' }
 
    Demonstrates how to create a required `smallmoney` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::SmallMoney($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::SmallMoney($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }
    }
}
    
Set-Alias -Name 'SmallMoney' -Value 'New-SmallMoneyColumn'

function New-SqlVariantColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an SqlVariant datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'WithSqlVariant' {
            SqlVariant 'ColumnName'
        }
 
    ## ALIASES
 
     * SqlVariant
 
    .EXAMPLE
    Add-Table 'WithSqlVar' { SqlVariant 'WhoKnows' }
 
    Demonstrates how to create an optional `sql_variant` column called `WhoKnows`.
 
    .EXAMPLE
    Add-Table 'WithSqlVar' { SqlVariant 'WhoKnows' -NotNull }
 
    Demonstrates how to create a required `sql_variant` column called `WhoKnows`.
 
    .EXAMPLE
    Add-Table 'WithSqlVar' { SqlVariant 'WhoKnows' -Sparse }
 
    Demonstrates how to create a sparse, optional `sql_variant` column called `WhoKnows`.
 
    .EXAMPLE
    Add-Table 'WithSqlVar' { SqlVariant 'WhoKnows' -NotNull -Default '1' }
 
    Demonstrates how to create a required `sql_variant` column called `WhoKnows` with a default value of `1`.
 
    .EXAMPLE
    Add-Table 'WithSqlVar' { SqlVariant 'WhoKnows' -NotNull -Description 'The contents of this column are left as an exercise for the reader.' }
 
    Demonstrates how to create a required `sql_variant` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::SqlVariant($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::SqlVariant($Name,'NotNull', $Default, $DefaultConstraintName, $Description)
        }
    }
}
    
Set-Alias -Name 'SqlVariant' -Value 'New-SqlVariantColumn'

function New-TimeColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an Time datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'WithTime' {
            Time 'ColumnName'
        }
 
    ## ALIASES
 
     * Time
 
    .EXAMPLE
    Add-Table 'WithTime' { New-TimeColumn 'CreatedAt' 5 -NotNull }
 
    Demonstrates how to create a required `time` column with a given scale when adding a new table.
 
    .EXAMPLE
    Add-Table 'WithTime' { Time 'CreatedAt' -Sparse }
 
    Demonstrate show to create a nullable, sparse `time` column when adding a new table.
 
    .EXAMPLE
    Add-Table 'WithTime' { Time 'CreatedAt' -NotNull -Default 'convert(`time`, getutcdate())' }
     
    Demonstrates how to create a `time` column with a default value, in this case the current time. You alwyas use UTC, right?
 
    .EXAMPLE
    Add-Table 'WithTime' { Time 'CreatedAt' -NotNull -Description 'The `time` the record was created.' }
 
    Demonstrates how to create a `time` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Null')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Position=1)]
        [Alias('Precision')]
        # The number of decimal digits for the fractional seconds. SQL Server's default is `7`, or 100 nanoseconds..
        [int]$Scale,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Null')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $dataSize = $null
    if( $PSBoundParameters.ContainsKey('Scale') )
    {
        $dataSize = New-Object Rivet.Scale $Scale
    }
    
    $nullable = $PSCmdlet.ParameterSetName
    if( $nullable -eq 'Null' -and $Sparse )
    {
        $nullable = 'Sparse'
    }

    [Rivet.Column]::Time($Name, $dataSize, $nullable, $Default, $DefaultConstraintName, $Description)
}
    
Set-Alias -Name 'Time' -Value 'New-TimeColumn'

function New-TinyIntColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an TinyInt datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'WithTintyInt' {
            TinyInt 'ColumnName'
        }
 
    ## ALIASES
 
     * TinyInt
 
    .EXAMPLE
    Add-Table 'Items' { TinyInt 'Quantity' }
 
    Demonstrates how to create an optional `tinyint` column called `Quantity`.
 
    .EXAMPLE
    Add-Table 'Items' { TinyInt 'Quantity' -Identity 1 1 }
 
    Demonstrates how to create a required `tinyint` column called `Quantity`, which is used as the table's identity. The identity values will start at 1, and increment by 1.
 
    .EXAMPLE
    Add-Table 'Items' { TinyInt 'Quantity' -NotNull }
 
    Demonstrates how to create a required `tinyint` column called `Quantity`.
 
    .EXAMPLE
    Add-Table 'Items' { TinyInt 'Quantity' -Sparse }
 
    Demonstrates how to create a sparse, optional `tinyint` column called `Quantity`.
 
    .EXAMPLE
    Add-Table 'Items' { TinyInt 'Quantity' -NotNull -Default '0' }
 
    Demonstrates how to create a required `tinyint` column called `Quantity` with a default value of `0`.
 
    .EXAMPLE
    Add-Table 'Items' { TinyInt 'Quantity' -NotNull -Description 'The number of items currently on hand.' }
 
    Demonstrates how to create a required `tinyint` column with a description.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,ParameterSetName='Identity')]
        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed')]
        # The column should be an identity.
        [switch]$Identity,

        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=1)]
        # The starting value for the identity.
        [int]$Seed,

        [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=2)]
        # The increment between auto-generated identity values.
        [int]$Increment,

        [Parameter(ParameterSetName='Identity')]
        [Parameter(ParameterSetName='IdentityWithSeed')]
        # Stops the identity from being replicated.
        [switch]$NotForReplication,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::TinyInt($Name, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::TinyInt($Name, [Rivet.Nullable]::NotNull, $Default, $DefaultConstraintName, $Description)
        }

        'Identity'
        {
            $i = New-Object 'Rivet.Identity' $NotForReplication
            [Rivet.Column]::TinyInt($Name, $i, $Description)
        }

        'IdentityWithSeed'
        {
            $i = New-Object 'Rivet.Identity' $Seed, $Increment, $NotForReplication
            [Rivet.Column]::TinyInt($Name, $i, $Description)
        }

            
    }
}
    
Set-Alias -Name 'TinyInt' -Value 'New-TinyIntColumn'

function New-UniqueIdentifierColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an UniqueIdentifier datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'WithUUID' {
            UniqueIdentifier 'ColumnName'
        }
 
    ## ALIASES
 
     * UniqueIdentifier
 
    .EXAMPLE
    Add-Table Locations { UniqueIdentifier 'LocationID' }
 
    Demonstrates how to create a table with an optional `uniqueidentifier` column.
 
    .EXAMPLE
    Add-Table Locations { UniqueIdentifier 'LocationID' -RowGuidCol }
 
    Demonstrates how to create a table with an optional `uniqueidentifier`, which is used as the RowGuid identifier for SQL Server replication.
 
    .EXAMPLE
    Add-Table Locations { UniqueIdentifier 'LocationID' -NotNull }
 
    Demonstrates how to create a table with an required `uniqueidentifier` column.
 
    .EXAMPLE
    Add-Table Locations { UniqueIdentifier 'LocationID' -Default 'newid()' }
 
    Demonstrates how to create a table with an optional `uniqueidentifier` column with a default value.
 
    .EXAMPLE
    Add-Table Locations { UniqueIdentifier 'LocationID' -Description 'The unique identifier for this location.' }
 
    Demonstrates how to create a table with an optional `uniqueidentifier` column with a default value.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        # Sets RowGuidCol
        [switch]$RowGuidCol,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )
        
    switch ($PSCmdlet.ParameterSetName)
    {
        'Nullable'
        {
            $nullable = 'Null'
            if( $Sparse )
            {
                $nullable = 'Sparse'
            }
            [Rivet.Column]::UniqueIdentifier($Name, $RowGuidCol, $nullable, $Default, $DefaultConstraintName, $Description)
        }
            
        'NotNull'
        {
            [Rivet.Column]::UniqueIdentifier($Name, $RowGuidCol, [Rivet.Nullable]::NotNull, $Default, $DefaultConstraintName, $Description)
        }
    }
}
    
Set-Alias -Name 'UniqueIdentifier' -Value 'New-UniqueIdentifierColumn'

function New-VarBinaryColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an VarBinary datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table 'Images' {
            VarBinary 'Bits' 8000
        }
 
    ## ALIASES
 
     * VarBinary
 
    .EXAMPLE
    Add-Table 'Images' { VarBinary 'Bytes' 8000 }
 
    Demonstrates how to create an optional `varbinary` column with a maximum length of 8000 bytes.
 
    .EXAMPLE
    Add-Table 'Images' { VarBinary 'Bytes' 8000 -NotNull }
 
    Demonstrates how to create a required `varbinary` column with maximum length of 8000 bytes.
 
    .EXAMPLE
    Add-Table 'Images' { VarBinary 'Bytes' -Max }
 
    Demonstrates how to create an optional `varbinary` column with the maximum length (2^31 -1 bytes).
 
    .EXAMPLE
    Add-Table 'Images' { VarBinary 'Bytes' -Max -FileStream }
 
    Demonstrates now to create an optional `varbinary` column with the maximum length, and stores the data in a filestream data container.
    #>

    [CmdletBinding(DefaultParameterSetName='NullSize')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,Position=1,ParameterSetName='NullSize')]
        [Parameter(Mandatory,Position=1,ParameterSetName='NotNullSize')]
        # The maximum number of bytes the column will hold.
        [int]$Size,

        [Parameter(Mandatory,ParameterSetName='NullMax')]
        [Parameter(Mandatory,ParameterSetName='NotNullMax')]
        # Creates a `varbinary(max)` column.
        [switch]$Max,

        [Parameter(ParameterSetName='NullMax')]
        [Parameter(ParameterSetName='NotNullMax')]
        # Stores the varbinary(max) data in a filestream data container on the file system. Requires VarBinary(max).
        [switch]$FileStream,

        [Parameter(Mandatory,ParameterSetName='NotNullSize')]
        [Parameter(Mandatory,ParameterSetName='NotNullMax')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='NullSize')]
        [Parameter(ParameterSetName='NullMax')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $sizeType = $null

    if( $PSCmdlet.ParameterSetName -like '*Size' )
    {
        $sizeType = New-Object Rivet.CharacterLength $Size
    }
    else 
    {
        $sizeType = New-Object Rivet.CharacterLength @()   
    }

    $nullable = 'Null'
    if( $PSCmdlet.ParameterSetName -like 'NotNull*' )
    {
        $nullable = 'NotNull'
    }
    elseif( $Sparse )
    {
        $nullable = 'Sparse'
    }

    [Rivet.Column]::VarBinary($Name, $sizeType, $FileStream, $nullable, $Default, $DefaultConstraintName, $Description)
}
    
Set-Alias -Name 'VarBinary' -Value 'New-VarBinaryColumn'

function New-VarCharColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an VarChar datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table -Name 'WithVarCharColumn' -Column {
            VarChar 'ColumnName' 50
        }
 
    ## ALIASES
 
     * VarChar
 
    .EXAMPLE
    Add-Table 'Albums' { VarChar 'Name' 100 }
 
    Demonstrates how to create an optional `varchar` column with a maximum length of 100 bytes.
 
    .EXAMPLE
    Add-Table 'Albums' { VarChar 'Name' 100 -NotNull }
 
    Demonstrates how to create a required `varchar` column with maximum length of 100 bytes.
 
    .EXAMPLE
    Add-Table 'Albums' { VarChar 'Name' -Max }
 
    Demonstrates how to create an optional `varchar` column with the maximum length (about 2GB).
 
    .EXAMPLE
    Add-Table 'Albums' { VarChar 'Name' 100 -Collation 'Latin1_General_BIN' }
 
    Demonstrates now to create an optional `varchar` column with a custom `Latin1_General_BIN` collation.
    #>

    [CmdletBinding(DefaultParameterSetName='NullSize')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Mandatory,Position=1,ParameterSetName='NullSize')]
        [Parameter(Mandatory,Position=1,ParameterSetName='NotNullSize')]
        # The maximum length of the column, i.e. the number of characters.
        [int]$Size,

        [Parameter(Mandatory,ParameterSetName='NullMax')]
        [Parameter(Mandatory,ParameterSetName='NotNullMax')]
        # Create a `varchar(max)` column.
        [switch]$Max,

        # Controls the code page that is used to store the data
        [String]$Collation,

        [Parameter(Mandatory,ParameterSetName='NotNullSize')]
        [Parameter(Mandatory,ParameterSetName='NotNullMax')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='NullSize')]
        [Parameter(ParameterSetName='NullMax')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $sizeType = $null

    if( $PSCmdlet.ParameterSetName -like '*Size' )
    {
        $sizeType = New-Object Rivet.CharacterLength $Size
    }
    else 
    {
        $sizeType = New-Object Rivet.CharacterLength @()   
    }

    $nullable = 'Null'
    if( $PSCmdlet.ParameterSetName -like 'NotNull*' )
    {
        $nullable = 'NotNull'
    }
    elseif( $Sparse )
    {
        $nullable = 'Sparse'
    }

    [Rivet.Column]::VarChar($Name, $sizeType, $Collation, $nullable, $Default, $DefaultConstraintName, $Description)
}
    
Set-Alias -Name 'VarChar' -Value 'New-VarCharColumn'

function New-XmlColumn
{
    <#
    .SYNOPSIS
    Creates a column object representing an Xml datatype.
 
    .DESCRIPTION
    Use this function in the `Column` script block for `Add-Table`:
 
        Add-Table -Name 'WebConfigs' -Column {
            Xml 'WebConfig' -XmlSchemaCollection 'webconfigschema'
        }
 
    Remember you have to have already created the XML schema before creating a column that uses it.
 
    ## ALIASES
 
     * Xml
 
    .EXAMPLE
    Add-Table 'WebConfigs' { Xml 'WebConfig' -XmlSchemaCollection 'webconfigschema' }
 
    Demonstrates how to create an optional `xml` column which uses the `webconfigschema` schema collection.
 
    .EXAMPLE
    Add-Table 'WebConfigs' { Xml 'WebConfig' -XmlSchemaCollection 'webconfigschema' -NotNull }
 
    Demonstrates how to create a required `xml` column.
 
    .EXAMPLE
    Add-Table 'WebConfigs' { Xml 'WebConfig' -XmlSchemaCollection 'webconfigschema'' -Document }
 
    Demonstrates how to create an `xml` column that holds an entire XML document.
    #>

    [CmdletBinding(DefaultParameterSetName='Nullable')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The column's name.
        [String]$Name,

        [Parameter(Position=1)]
        # Name of an XML schema collection
        [String]$XmlSchemaCollection,

        # Specifies that this is a well-formed XML document instead of an XML fragment.
        [switch]$Document,

        [Parameter(Mandatory,ParameterSetName='NotNull')]
        # Don't allow `NULL` values in this column.
        [switch]$NotNull,

        [Parameter(ParameterSetName='Nullable')]
        # Store nulls as Sparse.
        [switch]$Sparse,

        # A SQL Server expression for the column's default value
        [String]$Default,

        # The name of the default constraint for the column's default expression. Required if the Default parameter is given.
        [String]$DefaultConstraintName,
            
        # A description of the column.
        [String]$Description
    )

    $nullable = [Rivet.Nullable]::Null
    if( $PSCmdlet.ParameterSetName -eq 'NotNull' )
    {
        $nullable = [Rivet.Nullable]::NotNull
    }
    else
    {
        if( $Sparse )
        {
            $nullable = [Rivet.Nullable]::Sparse
        }
    }

    if( $XmlSchemaCollection )
    {
        [Rivet.Column]::Xml($Name, $Document, $XmlSchemaCollection, $nullable, $Default, $DefaultConstraintName, $Description)
    }
    else
    {
        [Rivet.Column]::Xml($Name, $nullable, $Default, $DefaultConstraintName, $Description)
    }
}

    
Set-Alias -Name 'Xml' -Value 'New-XmlColumn'

function Add-CheckConstraint
{
    <#
    .SYNOPSIS
    Add a check constraint to a table.
     
    .DESCRIPTION
    Check constraints add validation for data in columns.
     
    .EXAMPLE
    Add-CheckConstraint 'Migrations' 'CK_Migrations_MigrationID' 'MigrationID > 0'
     
    Demonstrates how to add a check constraint to a column that requires the value to be greater than 0.
 
    .EXAMPLE
    Add-CheckConstraint 'Migrations' 'CK_Migrations_MigrationID' 'MigrationID > 0' -NoCheck
 
    Demonstrates how to add a check constraint to a column without validating the current contents of the table against this check.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the check constraint's table.
        $TableName,
        
        [Parameter()]
        [string]
        # The schema of the table. Default is `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The name of the check constraint.
        $Name,
        
        [Parameter(Mandatory=$true,Position=2)]
        [string]
        # The expression to use for the constraint.
        $Expression,
        
        [Switch]
        # Don't use the check constraint when inserting, updating, or deleting rows during replication.
        $NotForReplication,

        [Switch]
        # Specifies that the data in the table is not validated against a newly added CHECK constraint. If not specified, WITH CHECK is assumed for new constraints.
        $NoCheck
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.AddCheckConstraintOperation' $SchemaName, $TableName, $Name, $Expression, $NotForReplication, $NoCheck
}

function Add-DataType
{
    <#
    .SYNOPSIS
    Creates an alias or user-defined type.
     
    .DESCRIPTION
    There are three different user-defined data types. The first is an alias, from a name you choose to a system datatype. The second is an assembly type, which uses a type stored in a .NET assembly. The third is a table data type, which create a type for a table.
     
    .LINK
    Remove-DataType
 
    .LINK
    http://technet.microsoft.com/en-us/library/ms175007.aspx
     
    .EXAMPLE
    Add-DataType 'GUID' 'uniqueidentifier'
     
    Demonstrates how to create a new alias data type called `GUID` which aliases the system `uniqueidentifier`.
     
    .EXAMPLE
    Add-DataType 'Names' -AsTable { varchar 'Name' 50 } -TableConstraint 'primary key'
     
    Demonstrates how to create a new table-based data type.
     
    .EXAMPLE
    Add-DataType 'RivetDateTime' -AssemblyName 'Rivet' -ClassName 'Rivet.RivetDateTime'
     
    Demonstrates how to create a `RivetDateTime` type that references the `Rivet.RivetDateTime` class. The `Rivet` assembly must first be registered using `create assembly`.
    #>


    [CmdletBinding(DefaultParameterSetName='From')]
    param(
        [Parameter()]
        [string]
        # The schema for the type. Default is `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the type.
        $Name,
        
        [Parameter(Mandatory=$true,Position=1,ParameterSetName='From')]
        [string]
        # The system type to alias.
        $From,
        
        [Parameter(Mandatory=$true,ParameterSetName='Assembly')]
        [string]
        # The name of the assembly for the type's implementation.
        $AssemblyName,
        
        [Parameter(Mandatory=$true,ParameterSetName='Assembly')]
        [string]
        # The name of the type's class implementation.
        $ClassName,
        
        [Parameter(Mandatory=$true,ParameterSetName='AsTable')]
        [ScriptBlock]
        # A `ScriptBlock` which returns columns for the table.
        $AsTable,
        
        [Parameter(ParameterSetName='AsTable')]
        [string[]]
        # A list of table constraints for a table-based data type.
        $TableConstraint
    )

    Set-StrictMode -Version 'Latest'

    if ($PsCmdlet.ParameterSetName -eq 'From')
    {
        $op = New-Object 'Rivet.Operations.AddDataTypeOperation' $SchemaName, $Name, $From
    }

    if ($PsCmdlet.ParameterSetName -eq 'Assembly')
    {
        $op = New-Object 'Rivet.Operations.AddDataTypeOperation' $SchemaName, $Name, $AssemblyName, $ClassName
    }

    if ($PsCmdlet.ParameterSetName -eq 'AsTable')
    {
        # Process Column Scriptblock -> Rivet.Column[]
        [Rivet.Column[]]$columns = & $AsTable
        $op = New-Object 'Rivet.Operations.AddDataTypeOperation' $SchemaName, $Name, $columns, ([string[]]$TableConstraint)
    }

    return $op
}


function Add-DefaultConstraint
{
    <#
    .SYNOPSIS
    Creates a Default constraint to an existing column
 
    .DESCRIPTION
    The DEFAULT constraint is used to insert a default value into a column. The default value will be added to all new records, if no other value is specified.
     
    .LINK
    Add-DefaultConstraint
 
    .EXAMPLE
    Add-DefaultConstraint -TableName Cars -ColumnName Year -Expression '2015'
 
    Adds an Default constraint on column 'Year' in the table 'Cars'
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        # The name of the target table.
        [String]$TableName,

        # The schema name of the target table. Defaults to `dbo`.
        [String]$SchemaName = 'dbo',

        [Parameter(Mandatory,Position=1)]
        # The column on which to add the default constraint
        [String]$ColumnName,

        # The name for the default constraint.
        [String]$Name,

        [Parameter(Mandatory,Position=2)]
        #The default expression
        [String]$Expression,

        # WithValues
        [switch]$WithValues
    )

    Set-StrictMode -Version 'Latest'

    [Rivet.Operations.AddDefaultConstraintOperation]::new($SchemaName, $TableName, $Name, $ColumnName, $Expression, $WithValues)
}



function Add-Description
{
    <#
    .SYNOPSIS
    Adds the `MS_Description` extended property to a table or column.
 
    .DESCRIPTION
    The `sys.sp_addextendedproperty` stored procedure is used to set a table/column's description (i.e. the `MS_Description` extended property), but the syntax is weird. This function hides that weirdness from you. You're welcome.
 
    .EXAMPLE
    Add-Description -Description 'Whoseit's whatsits table.' -TableName WhoseitsWhatsits
 
    Adds a description (i.e. the `MS_Description` extended property) on the `WhoseitsWhatsits` table.
 
    .EXAMPLE
    Add-Description -Description 'Is it a snarfblat?' -TableName WhoseitsWhatsits -ColumnName IsSnarfblat
 
    Adds a description (i.e. the `MS_Description` extended property) on the `WhoseitsWhatsits` table's `IsSnarfblat` column.
     
    .EXAMPLE
    Add-Description -Description 'Whoseit's whatsits table.' -TableName WhoseitsWhatsits -ForTable
     
    PowerShell v2.0 doesn't parse the parameters correctly when setting a table name, so you have to explicitly tell it what to do. Upgrade to PowerShell 3!
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The value for the MS_Description extended property.
        $Description,

        [Alias('Schema')]
        [string]
        # The schema. Defaults to `dbo`.
        $SchemaName = 'dbo',

        [Parameter(Mandatory=$true)]
        [Alias('Table')]
        [string]
        # The name of the table where the extended property is getting set.
        $TableName,

        [Parameter(ParameterSetName='ForColumn')]
        [Alias('Column')]
        [string]
        # The name of the column where the extended property is getting set.
        $ColumnName
    )

    $optionalArgs = @{ }
    if( $ColumnName )
    {
        $optionalArgs.ColumnName = $ColumnName
    }

    Add-ExtendedProperty -Name ([Rivet.Operations.ExtendedPropertyOperation]::DescriptionPropertyName) `
                         -Value $Description `
                         -SchemaName $SchemaName `
                         -TableName $TableName `
                         @optionalArgs
}

function Add-ExtendedProperty
{
    <#
    .SYNOPSIS
    Adds an extended property for a schema, table, view or column.
     
    .DESCRIPTION
    SQL Server has a special stored procedure for adding extended property metatdata about an object. Unfortunately, it has a really clunky interface. This function is an attempt to wrap `sp_addextendedproperty` with a saner interface.
     
    Currently, this function only supports adding properties for schemas, tables, and columns. Submit a patch!
     
    .LINK
    Add-Description
     
    .LINK
    Remove-Description
     
    .LINK
    Remove-ExtendedProperty
     
    .LINK
    Update-Description
     
    .LINK
    Update-ExtendedProperty
     
    .EXAMPLE
    Add-ExtendedProperty -Name 'Deploy' -Value 'TRUE' -SchemaName 'spike'
     
    Adds custom `Deploy` metadata for the `spike` schema.
     
    .EXAMPLE
    Add-ExtendedProperty -Name 'Deploy' -Value 'TRUE' -TableName 'Food'
     
    Adds custom `Deploy` metadata on the `Food` table in the `dbo` schema.
     
    .EXAMPLE
    Add-ExtendedProperty -Name 'IsEncrypted' -Value 'FALSE' -TableName 'User' -ColumnName 'Password'
     
    Adds custom `IsEncrypted` metadata on the `User` table's `Password` column.
 
    .EXAMPLE
    Add-ExtendedProperty -Name 'ContainsPII' -Value 'FALSE' -View 'LoggedInUsers'
     
    Demonstrates how to add custom metadata on the `LoggedInUsers` view
 
    .EXAMPLE
    Add-ExtendedProperty -Name 'IsEncrypted' -Value 'FALSE' -View 'LoggedInUsers' -ColumnName 'Password'
 
    Demonstrates how to add custom metadata for a view's column
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the extended property to add.
        $Name,
        
        [Parameter(Mandatory=$true,Position=1)]
        [AllowNull()]
        # The value of the extended property.
        $Value,
        
        [Parameter(ParameterSetName='SCHEMA')]
        [Parameter(ParameterSetName='TABLE')]
        [Parameter(ParameterSetName='TABLE-COLUMN')]
        [Parameter(ParameterSetName='VIEW')]
        [Parameter(ParameterSetName='VIEW-COLUMN')]
        [string]
        # The schema of the object.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,ParameterSetName='TABLE')]
        [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')]
        [Alias('Table')]
        [string]
        # The table name.
        $TableName,
        
        [Parameter(Mandatory=$true,ParameterSetName='VIEW')]
        [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')]
        [Alias('View')]
        [string]
        # The table name.
        $ViewName,
        
        [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')]
        [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')]
        [Alias('Column')]
        [string]
        # The column name.
        $ColumnName
    )

    Set-StrictMode -Version 'Latest'
    
    if ($PsCmdlet.ParameterSetName -eq "SCHEMA")
    {
        $op = New-Object 'Rivet.Operations.AddExtendedPropertyOperation' $SchemaName, $Name, $Value
    }

    if ($PsCmdlet.ParameterSetName -eq "TABLE")
    {
        $op = New-Object 'Rivet.Operations.AddExtendedPropertyOperation' $SchemaName, $TableName, $Name, $Value, $false
    }

    if ($PsCmdlet.ParameterSetName -eq "VIEW")
    {
        $op = New-Object 'Rivet.Operations.AddExtendedPropertyOperation' $SchemaName, $ViewName, $Name, $Value, $true
    }

    if ($PsCmdlet.ParameterSetName -eq "TABLE-COLUMN")
    {
        $op = New-Object 'Rivet.Operations.AddExtendedPropertyOperation' $SchemaName, $TableName, $ColumnName, $Name, $Value, $false
    }

    if ($PsCmdlet.ParameterSetName -eq "VIEW-COLUMN")
    {
        $op = New-Object 'Rivet.Operations.AddExtendedPropertyOperation' $SchemaName, $ViewName, $ColumnName, $Name, $Value, $true
    }

    return $op
}

function Add-ForeignKey
{
    <#
    .SYNOPSIS
    Adds a foreign key to an existing table that doesn't have a foreign key constraint.
 
    .DESCRIPTION
    Adds a foreign key to a table. The table/column that the foreign key references must have a primary key. If the table already has a foreign key, make sure to remove it with `Remove-ForeignKey`.
 
    .LINK
    Add-ForeignKey
 
    .EXAMPLE
    Add-ForeignKey -TableName Cars -ColumnName DealerID -References Dealer -ReferencedColumn DealerID
 
    Adds a foreign key to the 'Cars' table on the 'DealerID' column that references the 'DealerID' column on the 'Dealer' table.
 
    .EXAMPLE
    Add-ForeignKey -TableName 'Cars' -ColumnName 'DealerID' -References 'Dealer' -ReferencedColumn 'DealerID' -OnDelete 'CASCADE' -OnUpdate 'CASCADE' -NotForReplication
 
    Adds a foreign key to the 'Cars' table on the 'DealerID' column that references the 'DealerID' column on the 'Dealer' table with the options to cascade on delete and update, and also set notforreplication
     
    .EXAMPLE
    Add-ForeignKey -TableName Cars -ColumnName DealerID -References Dealer -ReferencedColumn DealerID -NoCheck
 
    Adds a foreign key to the 'Cars' table on the 'DealerID' column that references the 'DealerID' column on the 'Dealer' table without validating the current contents of the table against this key.
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        # The name of the table to alter.
        [String]$TableName,

        # The name for the foreign key.
        [String]$Name,

        # The schema name of the table. Defaults to `dbo`.
        [String]$SchemaName = 'dbo',

        [Parameter(Mandatory,Position=1)]
        # The column(s) that should be part of the foreign key.
        [String[]]$ColumnName,

        [Parameter(Mandatory,Position=2)]
        # The table that the foreign key references
        [String]$References,

        # The schema name of the reference table. Defaults to `dbo`.
        [String]$ReferencesSchema = 'dbo',

        [Parameter(Mandatory,Position=3)]
        # The column(s) that the foreign key references
        [String[]]$ReferencedColumn,

        # Specifies what action happens to rows in the table that is altered, if those rows have a referential relationship and the referenced row is deleted from the parent table. The default is NO ACTION.
        [String]$OnDelete,

        # Specifies what action happens to rows in the table altered when those rows have a referential relationship and the referenced row is updated in the parent table. The default is NO ACTION.
        [String]$OnUpdate,

        # Can be specified for FOREIGN KEY constraints and CHECK constraints. If this clause is specified for a constraint, the constraint is not enforced when replication agents perform insert, update, or delete operations.
        [switch]$NotForReplication,

        # Specifies that the data in the table is not validated against a newly added FOREIGN KEY constraint. If not specified, WITH CHECK is assumed for new constraints.
        [switch]$NoCheck
    )

    Set-StrictMode -Version Latest
    
    [Rivet.Operations.AddForeignKeyOperation]::new($SchemaName, $TableName, $Name, $ColumnName, $ReferencesSchema, $references, $ReferencedColumn, $OnDelete, $OnUpdate, $NotForReplication, $NoCheck)
}



function Add-PrimaryKey
{
    <#
    .SYNOPSIS
    Adds a primary key to an existing table that doesn't have a primary key.
 
    .DESCRIPTION
    Adds a primary key to a table. If the table already has a primary key, make sure to remove it with `Remove-PrimaryKey`.
 
    .LINK
    Remove-PrimaryKey
 
    .EXAMPLE
    Add-PrimaryKey -TableName Cars -ColumnName Year,Make,Model
 
    Adds a primary key to the `Cars` table on the `Year`, `Make`, and `Model` columns.
 
    .EXAMPLE
    Add-PrimaryKey -TableName Cars -ColumnName Year,Make,Model -NonClustered -Option 'IGNORE_DUP_KEY = ON','DROP_EXISTING=ON'
 
    Demonstrates how to create a non-clustered primary key, with some index options.
    #>

    [CmdletBinding()]
    param(
        # The schema name of the table. Defaults to `dbo`.
        [String]$SchemaName = 'dbo',

        # The name for the primary key constraint.
        [String]$Name,

        [Parameter(Mandatory,Position=0)]
        # The name of the table.
        [String]$TableName,

        [Parameter(Mandatory,Position=1)]
        # The column(s) that should be part of the primary key.
        [String[]]$ColumnName,

        # Create a non-clustered primary key.
        [switch]$NonClustered,

        # An array of primary key options.
        [String[]]$Option
    )

    Set-StrictMode -Version 'Latest'

    [Rivet.Operations.AddPrimaryKeyOperation]::New($SchemaName, $TableName, $Name, $ColumnName, $NonClustered, $Option)
}


function Add-Row
{
    <#
    .SYNOPSIS
    Inserts a row of data in a table.
     
    .DESCRIPTION
    To specify which columns to insert into the new row, pass a hashtable as a value to the `Column` parameter. This hashtable should have keys that map to column names, and the value of each key will be used as the value for that column in the row.
     
    .EXAMPLE
    Add-Row -SchemaName 'rivet' 'Migrations' @{ ID = 2013093131104 ; Name = 'AMadeUpMigrationDoNotDoThis' ; Who = 'abadbadman' ; ComputerName 'abadbadcomputer' }
     
    Demonstrates how to insert a row into the `rivet.Migrations` table. This is for illustrative purposes only. If you do this yourself, a butterfly loses its wings.
     
    .EXAMPLE
    Add-Row 'Cars' @( @{ Make = 'Toyota' ; Model = 'Celica' }, @{ Make = 'Toyota' ; Model = 'Camry' } )
     
    Demonstrates how to insert multiple rows into a table by passing an array of hashtables.
     
    .EXAMPLE
    @( @{ Make = 'Toyota' ; Model = 'Celica' }, @{ Make = 'Toyota' ; Model = 'Camry' } ) | New-Row 'Cars'
     
    Demonstrates how to pipe data into `New-Row` to insert a bunch of rows into the database.
    #>

    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the table.
        $TableName,
        
        [Parameter()]
        [string]
        # The schema name of the table. Default is `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1,ValueFromPipeline=$true)]
        [Hashtable[]]
        # A hashtable of name/value pairs that map to column names/values that will inserted.
        $Column,

        [Switch]
        # Allow inserting identies.
        $IdentityInsert
    )

    process
    {
        Set-StrictMode -Version 'Latest'

        New-Object 'Rivet.Operations.AddRowOperation' $SchemaName, $TableName, $Column, $IdentityInsert
    }
}


function Add-RowGuidCol
{
    <#
    .SYNOPSIS
    Adds the `rowguidcol` property to a column in a table.
 
    .DESCRIPTION
    The `Add-RowGuidCol` operation adds the `rowguidcol` property to a `uniqueidentifier` column in a table. A table can only have one `rowguidcol` column. If a table has an existing `rowguidcol` column, use `Remove-RowGuidCol` to remove it before adding a new one.
 
    The `Add-RowGuidCol` operation was added in Rivet 0.7.
 
    .LINK
    https://msdn.microsoft.com/en-us/library/ms190273.aspx
 
    .LINK
    Remove-RowGuidCol
 
    .EXAMPLE
    Add-RowGuidCol -TableName 'MyTable' -ColumnName 'MyUniqueIdentifier'
 
    Demonstrates how to add the `rowguidcol` property to a column in a table. In this example, the `dbo.MyTable` table's `MyUniqueIdentifier` column will get the propery.
 
    .EXAMPLE
    Add-RowGuidCol -SchemaName 'cstm' -TableName 'MyTable' -ColumnName 'MyUniqueIdentifier'
 
    Demonstrates how to add the `rowguidcol` property to a column in a table whose schema isn't `dbo`, in this case the `cstm.MyTable` table's `MyUniqueIdentifier` column will get the property.
    #>

    [CmdletBinding()]
    param(
        [string]
        # The table's schema. Default is `dbo`.
        $SchemaName = 'dbo',

        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The table's name.
        $TableName,

        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The name of the column that should get the `rowguidcol` property.
        $ColumnName
    )

    Set-StrictMode -Version 'Latest'

    New-Object -TypeName 'Rivet.Operations.AddRowGuidColOperation' -ArgumentList $SchemaName,$TableName,$ColumnName
}


function Add-Schema
{
    <#
    .SYNOPSIS
    Creates a new schema.
 
    .DESCRIPTION
    The `Add-Schema` operation creates a new schema in a database. It does so in an idempotent way, i.e. it only creates the schema if it doesn't exist.
 
    .EXAMPLE
    Add-Schema -Name 'rivetexample'
 
    Creates the `rivetexample` schema.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [Alias('SchemaName')]
        [string]
        # The name of the schema.
        $Name,

        [Alias('Authorization')]
        [string]
        # The owner of the schema.
        $Owner
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.AddSchemaOperation' $Name, $Owner
}

function Add-StoredProcedure
{
    <#
    .SYNOPSIS
    Creates a new stored procedure.
 
    .DESCRIPTION
    Creates a new stored procedure.
 
    .EXAMPLE
    Add-StoredProcedure -SchemaName 'rivet' 'ReadMigrations' 'AS select * from rivet.Migrations'
 
    Creates a stored procedure to read the migrations from Rivet's Migrations table. Note that in real life, you probably should leave my table alone.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the stored procedure.
        $Name,
            
        [Parameter()]
        [string]
        # The schema name of the stored procedure. Defaults to `dbo`.
        $SchemaName = 'dbo',
            
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The store procedure's definition, which is everything after the `create procedure [schema].[name]` clause.
        $Definition
    )
    
    Set-StrictMode -Version 'Latest'
        
    New-Object 'Rivet.Operations.AddStoredProcedureOperation' $SchemaName, $Name, $Definition
}

 function Add-Synonym
{
    <#
    .SYNOPSIS
    Creates a synonym.
 
    .DESCRIPTION
    SQL Server lets you create synonyms so you can reference an object with a different name, or reference an object in another database with a local name.
     
    .LINK
    http://technet.microsoft.com/en-us/library/ms177544.aspx
     
    .EXAMPLE
    Add-Synonym -Name 'Buzz' -TargetObjectName 'Fizz'
     
    Creates a synonym called `Buzz` to the object `Fizz`.
     
    .EXAMPLE
    Add-Synonym -SchemaName 'fiz' -Name 'Buzz' -TargetSchemaName 'baz' -TargetObjectName 'Buzz'
     
    Demonstrates how to create a synonym in a different schema. Creates a synonym to the `baz.Buzz` object so that it can referenced as `fiz.Buzz`.
     
    .EXAMPLE
    Add-Synonym -Name 'Buzz' -TargetDatabaseName 'Fizzy' -TargetObjectName 'Buzz'
     
    Demonstrates how to create a synonym to an object in a different database.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The name of the synonym.
        $Name,
        
        [Parameter()]
        [string]
        # The name of the schema where the synonym should be created.
        $SchemaName = 'dbo',
        
        [Parameter()]
        [string]
        # The database where the target object is located. Defaults to the current database.
        $TargetDatabaseName,
        
        [Parameter()]
        [string]
        # The scheme of the target object. Defaults to `dbo`.
        $TargetSchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=2)]
        [string]
        # The target object's name the synonym will refer to.
        $TargetObjectName
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.AddSynonymOperation' $SchemaName, $Name, $TargetSchemaName, $TargetDatabaseName, $TargetObjectName
}



function Add-Table
{
    <#
    .SYNOPSIS
    Creates a new table in the database.
 
    .DESCRIPTION
    The column's for the table should be created and returned in a script block, which is passed as the value of the `Column` parameter. For example,
 
        Add-Table 'Suits' {
            Int 'id' -Identity
            TinyInt 'pieces -NotNull
            VarChar 'color' -NotNull
        }
 
    .LINK
    bigint
 
    .LINK
    binary
 
    .LINK
    bit
 
    .LINK
    char
 
    .LINK
    date
 
    .LINK
    datetime
 
    .LINK
    datetime2
 
    .LINK
    datetimeoffset
 
    .LINK
    decimal
 
    .LINK
    float
 
    .LINK
    hierarchyid
 
    .LINK
    int
 
    .LINK
    money
 
    .LINK
    nchar
 
    .LINK
    numeric
 
    .LINK
    nvarchar
 
    .LINK
    real
 
    .LINK
    rowversion
 
    .LINK
    smalldatetime
 
    .LINK
    smallint
 
    .LINK
    smallmoney
 
    .LINK
    sqlvariant
 
    .LINK
    time
 
    .LINK
    tinyint
 
    .LINK
    uniqueidentifier
 
    .LINK
    varbinary
 
    .LINK
    varchar
 
    .LINK
    xml
 
    .EXAMPLE
    Add-Table -Name 'Ties' -Column { VarChar 'color' -NotNull }
 
    Creates a `Ties` table with a single column for each tie's color. Pretty!
    #>

    [CmdletBinding(DefaultParameterSetName='AsNormalTable')]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the table.
        $Name,

        [string]
        # The table's schema. Defaults to 'dbo'.
        $SchemaName = 'dbo',

        [Parameter(Mandatory=$true,Position=1,ParameterSetName='AsNormalTable')]
        [ScriptBlock]
        # A script block that returns the table's columns.
        $Column,

        [Parameter(Mandatory=$true,ParameterSetName='AsFileTable')]
        [Switch]
        # Creates a [FileTable](http://msdn.microsoft.com/en-us/library/ff929144.aspx) table.
        $FileTable,

        [string]
        # Specifies the partition scheme or filegroup on which the table is stored, e.g. `ON $FileGroup`
        $FileGroup,

        [string]
        # The filegroup where text, ntext, image, xml, varchar(max), nvarchar(max), and varbinary(max) columns are stored. The table has to have one of those columns. For example, `TEXTIMAGE_ON $TextImageFileGroup`.
        $TextImageFileGroup,

        [string]
        # Specifies the filegroup for FILESTREAM data, e.g. `FILESTREAM_ON $FileStreamFileGroup`.
        $FileStreamFileGroup,

        [string[]]
        # Specifies one or more table options.
        $Option,

        [string]
        # A description of the table.
        $Description
    )

    Set-StrictMode -Version 'Latest'

    $columns = & $Column

    $tableOp = New-Object 'Rivet.Operations.AddTableOperation' $SchemaName, $Name, $columns, $FileTable, $FileGroup, $TextImageFileGroup, $FileStreamFileGroup, $Option

    $addDescriptionArgs = @{
                                SchemaName = $SchemaName;
                                TableName = $Name;
                            }

    if( $Description )
    {
        $tableDescriptionOp = Add-Description -Description $Description @addDescriptionArgs
        $tableOp.ChildOperations.Add($tableDescriptionOp)
    }

    $tableOp | Write-Output
    $tableOp.ChildOperations | Write-Output

    foreach( $columnItem in $columns )
    {
        if( $columnItem.Description )
        {
            Add-Description -Description $columnItem.Description -ColumnName $columnItem.Name @addDescriptionArgs | Write-Output
        }
    }
}


function Add-Trigger
{
    <#
    .SYNOPSIS
    Creates a new trigger.
     
    .DESCRIPTION
    Creates a new trigger. If updating an existing trigger, use `Remove-Trigger` to remove it first, then `New-Trigger` to re-create it.
     
    .LINK
    Remove-Trigger.
     
    .EXAMPLE
    Add-Trigger 'PrintMessage' 'ON rivet.Migrations for insert as print ''Migration applied!'''
     
    Creates a trigger that prints a method when a row gets inserted into the `rivet.Migrations` table.
    #>

    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the trigger.
        $Name,
        
        [Parameter()]
        [string]
        # The schema of the trigger.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The body of the trigger. Everything after and including the `ON` clause.
        $Definition
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.AddTriggerOperation' $SchemaName, $Name, $Definition
        
}

function Add-UniqueKey
{
    <#
    .SYNOPSIS
    Creates a UNIQUE constraint on the specified column and table.
 
    .DESCRIPTION
    Creates a UNIQUE constraint on the specified column and table.
    You can use UNIQUE constraints to make sure that no duplicate values are entered in specific columns that do not participate in a primary key. Although both a UNIQUE constraint and a PRIMARY KEY constraint enforce uniqueness, use a UNIQUE constraint instead of a PRIMARY KEY constraint when you want to enforce the uniqueness of a column, or combination of columns, that is not the primary key.
 
    .EXAMPLE
    Add-UniqueKey -TableName Cars -ColumnName Year
 
    Adds an unique constraint on column 'Year' in the table 'Cars'
 
    .EXAMPLE
    Add-UniqueKey -TableName 'Cars' -ColumnName 'Year' -Option @('IGNORE_DUP_KEY = ON','ALLOW_ROW_LOCKS = OFF')
 
    Adds an unique constraint on column 'Year' in the table 'Cars' with specified options
    #>

    [CmdletBinding()]
    param(
        # The schema name of the target table. Defaults to `dbo`.
        [String]$SchemaName = 'dbo',

        [Parameter(Mandatory,Position=0)]
        # The name of the target table.
        [String]$TableName,

        # The name for the <object type>. If not given, a sensible name will be created.
        [String]$Name,

        [Parameter(Mandatory,Position=1)]
        # The column(s) on which the index is based
        [String[]]$ColumnName,

        # Creates a clustered index, otherwise non-clustered
        [switch]$Clustered,

        # FillFactor as Integer
        [int]$FillFactor,

        # An array of index options.
        [String[]]$Option,

        # The value of the `ON` clause, which controls the filegroup/partition to use for the index.
        [String]$On
    )

    Set-StrictMode -Version Latest

    [Rivet.Operations.AddUniqueKeyOperation]::new($SchemaName, $TableName, $Name, $ColumnName, $Clustered, $FillFactor, $Option, $On)
}


 function Add-UserDefinedFunction
{
    <#
    .SYNOPSIS
    Creates a new user-defined function.
 
    .DESCRIPTION
    Creates a new user-defined function.
 
    .EXAMPLE
    Add-UserDefinedFunction -SchemaName 'rivet' 'One' 'returns tinyint begin return 1 end'
 
    Creates a user-defined function that returns the number 1.
    #>
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the stored procedure.
        $Name,
        
        [Parameter()]
        [string]
        # The schema name of the stored procedure. Defaults to `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The store procedure's definition. Everything after the `create function [schema].[name]` clause.
        $Definition
    )
    
    Set-StrictMode -Version 'Latest'

    New-Object Rivet.Operations.AddUserDefinedFunctionOperation $SchemaName,$Name,$Definition
}

 function Add-View
 {
    <#
    .SYNOPSIS
    Creates a new view.
 
    .DESCRIPTION
    Creates a new view.
 
    .EXAMPLE
    Add-View -SchemaName 'rivet' 'ReadMigrations' 'AS select * from rivet.Migrations'
 
    Creates a view to read all the migrations from Rivet's Migrations table. Don't do this in real life.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the view.
        $Name,
        
        [Parameter()]
        [string]
        # The schema name of the view. Defaults to `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The definition of the view. Everything after the `create view [schema].[name]` clause.
        $Definition
    )
    
    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.AddViewOperation' $SchemaName,$Name,$Definition
}


function Disable-Constraint
{
    <#
    .SYNOPSIS
    Disable a check of foreign key constraint on a table.
     
    .DESCRIPTION
    The `Disable-Constraint` operation disables a check or foreign key constraint on a table. Only check and foreign key constraints can be enabled/disabled.
     
    .LINK
    Enable-Constraint
 
    .EXAMPLE
    Disable-CheckConstraint 'Migrations' 'CK_Migrations_MigrationID'
     
    Demonstrates how to disable a constraint on a table. In this case, the `CK_Migrations_MigrationID` constraint on the `Migrations` table is disabled. Is it a check constraint? Foreign key constraint? It doesn't matter!
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the constraint's table.
        $TableName,
        
        [Parameter()]
        [string]
        # The schema of the table. Default is `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The name of the constraint.
        $Name
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.DisableConstraintOperation' $SchemaName, $TableName, $Name
}

Set-Alias -Name 'Disable-CheckConstraint' -Value 'Disable-Constraint'


function Disable-ForeignKey
{
    <#
    .SYNOPSIS
    OBSOLETE. Use `Disable-Constraint` instead.
     
    .DESCRIPTION
    OBSOLETE. Use `Disable-Constraint` instead.
     
    .EXAMPLE
    Disable-Constraint 'SourceTable' 'FK_SourceID_ReferenceTable'
     
    Demonstrates that `Disable-ForeignKey` is obsolete by showing that you should use `Disable-Constraint` instead.
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        # The name of the table to alter.
        [String]$TableName,

        # The schema name of the table. Defaults to `dbo`.
        [String]$SchemaName = 'dbo',

        [Parameter(Mandatory,Position=1)]
        # The column(s) that should be part of the foreign key.
        [String[]]$ColumnName,

        [Parameter(Mandatory,Position=2)]
        # The table that the foreign key references
        [String]$References,

        [Parameter()]
        # The schema name of the reference table. Defaults to `dbo`.
        [String]$ReferencesSchema = 'dbo',

        # The name for the <object type>. If not given, a sensible name will be created.
        [String]$Name
    )

    Set-StrictMode -Version 'Latest'

    Write-Warning ('The "Disable-ForeignKey" operation is obsolete and will removed in a future version of Rivet. Please use "Disable-Constraint" instead.')

    if( -not $PSBoundParameters.ContainsKey('Name') )
    {
        $Name = New-ConstraintName -ForeignKey `
                                   -SchemaName $SchemaName `
                                   -TableName $TableName `
                                   -ReferencesSchemaName $ReferencesSchema `
                                   -ReferencesTableName $References 
    }

    Disable-Constraint -SchemaName $SchemaName -TableName $TableName -Name $Name
}


function Enable-Constraint
{
    <#
    .SYNOPSIS
    Enable a check or foreign key constraint.
     
    .DESCRIPTION
    The `Enable-Constraint` operation enables a check or foreign key constraint on a table. Only check and foreign key constraints can be enabled/disabled.
     
    .LINK
    Disable-Constraint
 
    .EXAMPLE
    Enable-Constraint 'Migrations' 'FK_Migrations_MigrationID'
     
    Demonstrates how to disable a constraint on a table. In this case, the `FK_Migrations_MigrationID` constraint on the `Migrations` table is disabled. Is it a check constraint? Foreign key constraint? It doesn't matter!
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        # The name of the constraint's table.
        [String]$TableName,
        
        [Parameter()]
        # The schema of the table. Default is `dbo`.
        [String]$SchemaName = 'dbo',
        
        [Parameter(Mandatory,Position=1)]
        # The name of the constraint.
        [String]$Name
    )

    Set-StrictMode -Version 'Latest'

    [Rivet.Operations.EnableConstraintOperation]::New($SchemaName, $TableName, $Name, $false)
}

Set-Alias -Name 'Enable-CheckConstraint' -Value 'Enable-Constraint'


function Enable-ForeignKey
{
    <#
    .SYNOPSIS
    OBSOLETE. Use `Enable-Constraint` instead.
     
    .DESCRIPTION
    OBSOLETE. Use `Enable-Constraint` instead.
     
    .EXAMPLE
    Enable-Constraint 'TAbleName', 'FK_ForeignKeyName'
     
    Demonstrates that `Enable-ForeignKey` is obsolete and you should use `Enable-Constraint` instead.
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        # The name of the table to alter.
        [String]$TableName,

        # The schema name of the table. Defaults to `dbo`.
        [String]$SchemaName = 'dbo',

        [Parameter(Mandatory,Position=1)]
        # The column(s) that should be part of the foreign key.
        [String[]]$ColumnName,

        [Parameter(Mandatory,Position=2)]
        # The table that the foreign key references
        [String]$References,

        # The schema name of the reference table. Defaults to `dbo`.
        [String]$ReferencesSchema = 'dbo',

        # The name for the <object type>. If not given, a sensible name will be created.
        [String]$Name
    )

    Set-StrictMode -Version 'Latest'

    Write-Warning ('The "Enable-ForeignKey" operation is obsolete and will removed in a future version of Rivet. Please use "Enable-Constraint" instead.')

    if( -not $PSBoundParameters.ContainsKey('Name') )
    {
        $Name = New-ConstraintName -ForeignKey `
                                   -SchemaName $SchemaName `
                                   -TableName $TableName `
                                   -ReferencesSchemaName $ReferencesSchema `
                                   -ReferencesTableName $References 
    }

    Enable-Constraint -SchemaName $SchemaName -TableName $TableName -Name $Name
}


function Invoke-Ddl
{
    <#
    .SYNOPSIS
    Executes a DDL statement against the database.
     
    .DESCRIPTION
    The `Invoke-Ddl` function is used to update the structure of a database when none of Rivet's other operations will work.
     
    .EXAMPLE
    Invoke-Ddl -Query 'create table rivet.Migrations ( id int not null )'
     
    Executes the create table syntax above against the database.
    #>

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

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RawDdlOperation' $Query
}



function Invoke-SqlScript
{
    <#
    .SYNOPSIS
    Runs a SQL script file as part of a migration.
     
    .DESCRIPTION
    The SQL script is split on GO statements, which must be by themselves on a line, e.g.
 
        select * from sys.tables
        GO
 
        select * from sys.views
        GO
 
    #>

    [CmdletBinding(DefaultParameterSetName='AsReader')]
    param(
        [Parameter(Mandatory=$true)]
        [string]
        # The path to the SQL script to execute.
        $Path,

        [Parameter(Mandatory=$true,ParameterSetName='AsScalar')]
        [Switch]
        $AsScalar,
        
        [Parameter(Mandatory=$true,ParameterSetName='AsNonQuery')]
        [Switch]
        $NonQuery,
        
        [UInt32]
        # The time in seconds to wait for the command to execute. The default is 30 seconds.
        $CommandTimeout = 30
    )

    Set-StrictMode -Version 'Latest'

    $invokeMigrationParams = @{
                                    CommandTimeout = $CommandTimeout; 
                              }

    if( $pscmdlet.ParameterSetName -eq 'AsScalar' )
    {
        $invokeMigrationParams.AsScalar = $true
    }
    elseif( $pscmdlet.ParameterSetName -eq 'AsNonQuery' )
    {
        $invokeMigrationParams.NonQuery = $true
    }
    
    if( -not ([IO.Path]::IsPathRooted( $Path )) )
    {
        $Path = Join-Path $DBMigrationsRoot $Path
    }

    if( -not (Test-Path -Path $Path -PathType Leaf) )
    {
        throw ('SQL script ''{0}'' not found.' -f $Path)
        return
    }

    $Path = Resolve-Path -Path $Path | Select-Object -ExpandProperty 'ProviderPath'
    
    $sql = Get-Content -Path $Path -Raw
    New-Object 'Rivet.Operations.ScriptFileOperation' $Path,$sql
}

function Remove-CheckConstraint
{
    <#
    .SYNOPSIS
    Removes a check constraint from a table.
     
    .DESCRIPTION
    The `Remove-CheckConstraint` operation removes a check constraint from a table. Check constraints add validation for data in columns.
     
    .EXAMPLE
    Remove-CheckConstraint 'Migrations' 'CK_Migrations_MigrationID'
     
    Demonstrates how to remove a check constraint from a table. In this case, the `CK_Migrations_MigrationID` constraint will be removed from the `Migrations` table.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the check constraint's table.
        $TableName,
        
        [Parameter()]
        [string]
        # The schema of the table. Default is `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The name of the check constraint to remove.
        $Name
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RemoveCheckConstraintOperation' $SchemaName, $TableName, $Name
}

function Remove-DataType
{
    <#
    .SYNOPSIS
    Drops a user-defined datatype.
     
    .DESCRIPTION
    Handles all three datatypes: alias, CLR, and table. If the datatype is in use, you'll get an error. Make sure to remove/alter any objects that reference the type first.
     
    .LINK
    Add-DataType
     
    .LINK
    http://technet.microsoft.com/en-us/library/ms174407.aspx
     
    .EXAMPLE
    Remove-DataType 'GUID'
     
    Demonstrates how to remove the `GUID` user-defined data type.
     
    .EXAMPLE
    Remove-DataType -SchemaName 'rivet' 'GUID'
     
    Demonstrates how to remove a datatype in a schema other than `dbo`.
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        # The name of the type's schema. Default is `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the datatype to drop.
        $Name
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RemoveDataTypeOperation' $SchemaName, $Name
}


function Remove-DefaultConstraint
{
    <#
    .SYNOPSIS
    Removes a default constraint from a table.
 
    .DESCRIPTION
    The `Remove-DefaultConstraint` operation removes a default constraint from a table.
 
    .EXAMPLE
    Remove-DefaultConstraint 'Cars' -ColumnName 'Year' -Name 'Cars_Year_DefaultConstraint'
 
    Demonstrates how to remove a default constraint. In this case, the `Cars_Year_DefaultConstraint` constraint will be removed from the `Cars` table.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        # The name of the target table.
        [String]$TableName,

        # The schema name of the target table. Defaults to `dbo`.
        [String]$SchemaName = 'dbo',

        [Parameter(Position=1)]
        # The column name.
        [String]$ColumnName,

        # The name of the default constraint to remove.
        [String]$Name
    )

    Set-StrictMode -Version 'Latest'

    if( -not $Name )
    {
        if( -not $ColumnName )
        {
            Write-Error -Message ('The Name parameter is mandatory. Please pass the name of the default constraint to the Name parameter.') -ErrorAction Stop
            return
        }
    }

    if( -not $ColumnName )
    {
        $nameMsg = ''
        if( $Name )
        {
            $nameMsg = "'s $($Name) constraint"
        }
        $msg = ('The ColumnName parameter will be required in a future version of Rivet. Add a "ColumnName" ' +
                "parameter to the Remove-DefaulConstraint operation for the [$($SchemaName)].[$($TableName)] " +
                "table$($nameMsg).")
        Write-Warning -Message $msg
    }

    [Rivet.Operations.RemoveDefaultConstraintOperation]::New($SchemaName, $TableName, $ColumnName, $Name)
}



function Remove-Description
{
    <#
    .SYNOPSIS
    Removes the `MS_Description` extended property for a table or column.
 
    .DESCRIPTION
    The `sys.sp_dropextendedproperty` stored procedure is used to remove a table/column's description (i.e. the `MS_Description` extended property), but the syntax is weird. This function hides that weirdness from you. You're welcome.
 
    .EXAMPLE
    Remove-Description -TableName WhoseitsWhatsits
 
    Removes the description (i.e. the `MS_Description` extended property) for the `WhoseitsWhatsits` table.
 
    .EXAMPLE
    Remove-Description -TableName WhoseitsWhatsits -ColumnName IsSnarfblat
 
    Removes the description (i.e. the `MS_Description` extended property) for the `WhoseitsWhatsits` table's `IsSnarfblat` column.
    #>

    [CmdletBinding()]
    param(
        [Alias('Schema')]
        [string]
        # The schema. Defaults to `dbo`.
        $SchemaName = 'dbo',

        [Parameter(Mandatory=$true)]
        [Alias('Table')]
        [string]
        # The name of the table where the extended property is getting set.
        $TableName,

        [Parameter(ParameterSetName='ForColumn')]
        [Alias('Column')]
        [string]
        # The name of the column where the extended property is getting set.
        $ColumnName
    )

    Set-StrictMode -Version 'Latest'

    $optionalArgs = @{ }
    if( $ColumnName )
    {
        $optionalArgs.ColumnName = $ColumnName
    }
    
    Remove-ExtendedProperty -Name 'MS_Description' `
                            -SchemaName $SchemaName `
                            -TableName $TableName `
                            @optionalArgs
}

function Remove-ExtendedProperty
{
    <#
    .SYNOPSIS
    Drops an extended property for a schema, table, or column.
     
    .DESCRIPTION
    SQL Server has a special stored procedure for removing extended property metatdata about an object. Unfortunately, it has a really clunky interface. This function is an attempt to wrap `sp_dropextendedproperty` with a saner interface.
     
    Currently, this function only supports dropping properties for schemas, tables, and columns. Submit a patch!
     
    .LINK
    Add-Description
     
    .LINK
    Add-ExtendedProperty
     
    .LINK
    Remove-Description
     
    .LINK
    Update-Description
     
    .LINK
    Update-ExtendedProperty
     
    .EXAMPLE
    Remove-ExtendedProperty -Name 'Deploy' -SchemaName 'spike'
     
    Drops the custom `Deploy` metadata for the `spike` schema.
     
    .EXAMPLE
    Remove-ExtendedProperty -Name 'Deploy' -TableName 'Food'
     
    Drops the custom `Deploy` metadata on the `Food` table in the `dbo` schema.
     
    .EXAMPLE
    Remove-ExtendedProperty -Name 'IsEncrypted' -TableName 'User' -ColumnName 'Password'
     
    Drops the custom `IsEncrypted` metadata on the `User` table's `Password` column.
 
    .EXAMPLE
    Remove-ExtendedProperty -Name 'ContainsPII' -View 'LoggedInUsers'
     
    Demonstrates how to remove custom metadata on the `LoggedInUsers` view
 
    .EXAMPLE
    Remove-ExtendedProperty -Name 'IsEncrypted' -View 'LoggedInUsers' -ColumnName 'Password'
     
    Demonstrates how to remove custom metadata for a view's column
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the extended property to add.
        $Name,
        
        [Parameter(ParameterSetName='SCHEMA')]
        [Parameter(ParameterSetName='TABLE')]
        [Parameter(ParameterSetName='TABLE-COLUMN')]
        [Parameter(ParameterSetName='VIEW')]
        [Parameter(ParameterSetName='VIEW-COLUMN')]
        [string]
        # The schema of the object.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,ParameterSetName='TABLE')]
        [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')]
        [Alias('Table')]
        [string]
        # The table name.
        $TableName,
        
        [Parameter(Mandatory=$true,ParameterSetName='VIEW')]
        [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')]
        [Alias('View')]
        [string]
        # The table name.
        $ViewName,        
        
        [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')]
        [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')]
        [Alias('Column')]
        [string]
        # The column name.
        $ColumnName
    )

    Set-StrictMode -Version 'Latest'

    if ($PsCmdlet.ParameterSetName -eq "SCHEMA")
    {
        $op = New-Object 'Rivet.Operations.RemoveExtendedPropertyOperation' $SchemaName, $Name
    }

    if ($PsCmdlet.ParameterSetName -eq "TABLE")
    {
        $op = New-Object 'Rivet.Operations.RemoveExtendedPropertyOperation' $SchemaName, $TableName, $Name, $false
    }

    if ($PsCmdlet.ParameterSetName -eq "VIEW")
    {
        $op = New-Object 'Rivet.Operations.RemoveExtendedPropertyOperation' $SchemaName, $ViewName, $Name, $true
    }

    if ($PsCmdlet.ParameterSetName -eq "TABLE-COLUMN")
    {
        $op = New-Object 'Rivet.Operations.RemoveExtendedPropertyOperation' $SchemaName, $TableName, $ColumnName, $Name, $false
    }

    if ($PsCmdlet.ParameterSetName -eq "VIEW-COLUMN")
    {
        $op = New-Object 'Rivet.Operations.RemoveExtendedPropertyOperation' $SchemaName, $ViewName, $ColumnName, $Name, $true
    }

    return $op
}

function Remove-ForeignKey
{
    <#
    .SYNOPSIS
    Removes a foreign key from an existing table that has a foreign key.
 
    .DESCRIPTION
    Removes a foreign key to a table.
 
    .EXAMPLE
    Remove-ForeignKey 'Cars' -Name 'FK_Cars_Year'
 
    Demonstrates how to remove a foreign key that has a name different than Rivet's derived name.
    #>

    [CmdletBinding(DefaultParameterSetName='ByDefaultName')]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the table.
        $TableName,

        [Parameter()]
        [string]
        # The schema name of the table. Defaults to `dbo`.
        $SchemaName = 'dbo',

        [Parameter(Mandatory=$true,Position=1,ParameterSetName='ByDefaultName')]
        [string]
        # OBSOLETE. Use the `Name` parameter to specify the foreign key to remove.
        $References,

        [Parameter(ParameterSetName='ByDefaultName')]
        [string]
        # OBSOLETE. Use the `Name` parameter to specify the foreign key to remove.
        $ReferencesSchema = 'dbo',

        [Parameter(Mandatory=$true,ParameterSetName='ByCustomName')]
        [string]
        # The name of the foreign key to remove.
        $Name
    )

    Set-StrictMode -Version 'Latest'

    [Rivet.Operations.RemoveForeignKeyOperation]::New($SchemaName, $TableName, $Name)
}



function Remove-Index
{
    <#
    .SYNOPSIS
    Removes an index from a table.
 
    .DESCRIPTION
    The `Remove-Index` operation removes an index from a table.
 
    .EXAMPLE
    Remove-Index 'Cars' -Name 'YearIX'
 
    Demonstrates how to drop an index
    #>

    [CmdletBinding(DefaultParameterSetName='ByDefaultName')]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the target table.
        $TableName,

        [Parameter()]
        [string]
        # The schema name of the target table. Defaults to `dbo`.
        $SchemaName = 'dbo',

        [Parameter(Mandatory=$true,Position=1,ParameterSetName='ByDefaultName')]
        [string[]]
        # OBSOLETE. Use the `Name` parameter to remove an index.
        $ColumnName,

        [Parameter(ParameterSetName='ByDefaultName')]
        [Switch]
        # OBSOLETE. Use the `Name` parameter to remove an index.
        $Unique,

        [Parameter(Mandatory=$true,ParameterSetName='ByExplicitName')]
        [string]
        # The name of the index to remove.
        $Name
    )

    Set-StrictMode -Version 'Latest'

    # TODO: once generating constraint names is out, remove $columnName and $unique parameters.
    [Rivet.Operations.RemoveIndexOperation]::New($SchemaName, $TableName, $Name, $ColumnName, $Unique)
}



function Remove-PrimaryKey
{
    <#
    .SYNOPSIS
    Removes a primary key from a table.
 
    .DESCRIPTION
    The `Remove-PrimaryKey` operation removes a primary key from a table.
 
    .EXAMPLE
    Remove-PrimaryKey 'Cars' -Name 'Car_PK'
 
    Demonstrates how to remove a primary key whose name is different than the derived name Rivet creates for primary keys.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        # The name of the table.
        [String]$TableName,

        # The schema name of the table. Defaults to `dbo`.
        [String]$SchemaName = 'dbo',

        [Parameter(Position=1)]
        # The name of the primary key to remove.
        [String]$Name
    )

    Set-StrictMode -Version 'Latest'

    [Rivet.Operations.RemovePrimaryKeyOperation]::New($SchemaName, $TableName, $Name)
}


 function Remove-Row
{
    <#
    .SYNOPSIS
    Removes a row from a table.
         
    .DESCRIPTION
    To specify which columns to insert into the new row, pass a hashtable as a value to the `Column` parameter. This hashtable should have keys that map to column names, and the value of each key will be used as the value for that column in the row.
         
    .EXAMPLE
    Remove-Row -SchemaName 'rivet' 'Migrations' 'MigrationID=20130913132411'
         
    Demonstrates how to delete a specific set of rows from a table.
         
    .EXAMPLE
    Remove-Row 'Cars' -All
         
    Demonstrates how to remove all rows in a table.
    #>

    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the table.
        $TableName,
            
        [Parameter()]
        [string]
        # The schema name of the table. Default is `dbo`.
        $SchemaName = 'dbo',
            
        [Parameter(Mandatory=$true,Position=1,ParameterSetName='DropSpecificRows')]
        [string]
        # The condition to use for choosing which rows to remove. This parameter is required, unless you *really* want to
        $Where,
            
        [Parameter(Mandatory=$true,ParameterSetName='AllRows')]
        [Switch]
        # Drop all the rows in the table.
        $All,
            
        [Parameter(ParameterSetName='AllRows')]
        [Switch]
        # Truncate the table instead to delete all the rows. This is faster than using a `delete` statement.
        $Truncate
    )

    Set-StrictMode -Version 'Latest'

    if ($PSCmdlet.ParameterSetName -eq 'DropSpecificRows')
    {
        New-Object 'Rivet.Operations.RemoveRowOperation' $SchemaName, $TableName, $Where        
    }
    elseif ($PSCmdlet.ParameterSetName -eq 'AllRows')
    {
        if ($Truncate)
        {
            New-Object 'Rivet.Operations.RemoveRowOperation' $SchemaName, $TableName, $true
        }
        else
        {
            New-Object 'Rivet.Operations.RemoveRowOperation' $SchemaName, $TableName, $false
        }
    }
}


function Remove-RowGuidCol
{
    <#
    .SYNOPSIS
    Remove the `rowguidcol` property from a column in a table.
 
    .DESCRIPTION
    The `Remove-RowGuidCol` operation removes the `rowguidcol` property from a `uniqueidentifier` column in a table.
 
    The `Remove-RowGuidCol` operation was added in Rivet 0.7.
 
    .LINK
    https://msdn.microsoft.com/en-us/library/ms190273.aspx
 
    .LINK
    Add-RowGuidCol
 
    .EXAMPLE
    Remove-RowGuidCol -TableName 'MyTable' -ColumnName 'MyUniqueIdentifier'
 
    Demonstrates how to remove the `rowguidcol` property from a column in a table. In this example, the `dbo.MyTable` table's `MyUniqueIdentifier` column will lose the propery.
 
    .EXAMPLE
    Remove-RowGuidCol -SchemaName 'cstm' -TableName 'MyTable' -ColumnName 'MyUniqueIdentifier'
 
    Demonstrates how to remove the `rowguidcol` property from a column in a table whose schema isn't `dbo`, in this case the `cstm.MyTable` table's `MyUniqueIdentifier` column will lose the property.
    #>

    [CmdletBinding()]
    param(
        [string]
        # The table's schema. Default is `dbo`.
        $SchemaName = 'dbo',

        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The table's name.
        $TableName,

        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The name of the column that should get the `rowguidcol` property.
        $ColumnName
    )

    Set-StrictMode -Version 'Latest'

    New-Object -TypeName 'Rivet.Operations.RemoveRowGuidColOperation' -ArgumentList $SchemaName,$TableName,$ColumnName
}


function Remove-Schema
{
    <#
    .SYNOPSIS
    Removes a schema.
 
    .EXAMPLE
    Remove-Schema -Name 'rivetexample'
 
    Drops/removes the `rivetexample` schema.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [Alias('SchemaName')]
        [string]
        # The name of the schema.
        $Name
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RemoveSchemaOperation' $Name
}


function Remove-StoredProcedure
{
    <#
    .SYNOPSIS
    Removes a stored procedure.
     
    .DESCRIPTION
    Removes a stored procedure. Will throw an exception and rollback the migration if the stored procedure doesn't exist.
     
    By default, the stored procedure is assumed to be in the `dbo` schema. Use the `Schema` parameter to specify a different schema.
     
    You can conditionally delete a stored procedure only if it exists using the `IfExists` switch.
      
    .EXAMPLE
    Remove-StoredProcedure -Name MySproc
     
    Removes the `dbo.MySproc` stored procedure.
     
    .EXAMPLE
    Remove-StoredProcedure -Name MySproc -SchemaName rivet
     
    Removes the `rivet.MySproc` stored procedure.
     
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]
        # The name of the stored procedure to remove/delete.
        $Name,
        
        [Parameter()]
        [string]
        # The schema of the stored procedure. Default is `dbo`.
        $SchemaName = 'dbo'
        
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RemoveStoredProcedureOperation' $SchemaName, $Name
}

function Remove-Synonym
{
    <#
    .SYNOPSIS
    Drops a synonym.
 
    .DESCRIPTION
    Drops an existing synonym. If the synonym doesn't exist, you'll get an error.
     
    .LINK
    http://technet.microsoft.com/en-us/library/ms174996.aspx
     
    .EXAMPLE
    Remove-Synonym -Name 'Buzz'
     
    Removes the `Buzz` synonym.
     
    .EXAMPLE
    Remove-Synonym -SchemaName 'fiz' -Name 'Buzz'
     
    Demonstrates how to remove a synonym in a schema other than `dbo`.
     
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The name of the synonym to drop.
        $Name,
        
        [Parameter()]
        [string]
        # The name of the synonym's schema. Default to `dbo`.
        $SchemaName = 'dbo'
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RemoveSynonymOperation' $SchemaName, $Name
}


function Remove-Table
{
    <#
    .SYNOPSIS
    Removes a table from a database.
 
    .DESCRIPTION
    You can't get any of the data back, so be careful.
 
    .EXAMPLE
    Remove-Table -Name 'Coffee'
 
    Removes the `Coffee` table from the database.
    #>

    param(
        # The name of the table where the column should be removed.
        [Parameter(Mandatory=$true)]
        [string]
        $Name,

        [string]
        # The schema of the table where the column should be added. Default is `dbo`.
        $SchemaName = 'dbo'
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RemoveTableOperation' $SchemaName, $Name
}


function Remove-Trigger
{
    <#
    .SYNOPSIS
    Deletes a new trigger.
     
    .DESCRIPTION
    Deletes an existing trigger.
     
    .LINK
    New-Trigger.
     
    .EXAMPLE
    Remove-Trigger 'PrintMessage'
     
    Removes the `PrintMessage` trigger.
    #>


    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the trigger.
        $Name,
        
        [Parameter()]
        [string]
        # The schema of the trigger.
        $SchemaName = "dbo"
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RemoveTriggerOperation' $SchemaName, $Name
}


function Remove-UniqueKey
{
    <#
    .SYNOPSIS
    Removes the Unique Constraint from the database
 
    .DESCRIPTION
    Removes the Unique Constraint from the database.
 
    .EXAMPLE
    Remove-UniqueKey 'Cars' -Name 'YearUK'
 
    Demonstrates how to remove a unique key whose name is different than the name Rivet derives for unique keys.
    #>


    [CmdletBinding(DefaultParameterSetName='ByDefaultName')]
    param(
        [Parameter(Mandatory,Position=0)]
        # The name of the target table.
        [String]$TableName,

        [Parameter()]
        [String]
        # The schema name of the target table. Defaults to `dbo`.
        $SchemaName = 'dbo',

        [Parameter(Mandatory,Position=1,ParameterSetName='ByDefaultName')]
        # OBSOLETE. Use the `Name` parameter to specify the name of the unique key to remove.
        [String[]]$ColumnName,

        [Parameter(Mandatory,Position=1,ParameterSetName='ByExplicitName')]
        # The name of the unique key to remove.
        [String]$Name
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RemoveUniqueKeyOperation' $SchemaName, $TableName, $Name, $ColumnName
}



function Remove-UserDefinedFunction
{
    <#
    .SYNOPSIS
    Removes a user-defined function.
     
    .DESCRIPTION
    Removes a user-defined function. Will throw an exception and rollback the migration if the user-defined function doesn't exist.
     
    By default, the user-defined function is assumed to be in the `dbo` schema. Use the `Schema` parameter to specify a different schema.
     
    You can conditionally delete a user-defined function only if it exists using the `IfExists` switch.
      
    .EXAMPLE
    Remove-UserDefinedFunction -Name MyFunc
     
    Removes the `dbo.MyFunc` user-defined function.
     
    .EXAMPLE
    Remove-UserDefinedFunction -Name MyFunc -SchemaName rivet
     
    Removes the `rivet.MyFunc` user-defined function.
     
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]
        # The name of the user-defined function to remove/delete.
        $Name,
        
        [Parameter()]
        [string]
        # The schema of the user-defined function. Default is `dbo`.
        $SchemaName = 'dbo'

    )
    
    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RemoveUserDefinedFunctionOperation' $SchemaName, $Name
}


function Remove-View
{
    <#
    .SYNOPSIS
    Removes a view.
     
    .DESCRIPTION
    Removes a view. Will throw an exception and rollback the migration if the view doesn't exist.
     
    By default, the view is assumed to be in the `dbo` schema. Use the `Schema` parameter to specify a different schema.
     
    You can conditionally delete a view only if it exists using the `IfExists` switch.
      
    .EXAMPLE
    Remove-View -Name MyView
     
    Removes the `dbo.MyView` view.
     
    .EXAMPLE
    Remove-View -Name MyView -SchemaName rivet
     
    Removes the `rivet.MyView` view.
     
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]
        # The name of the view to remove/delete.
        $Name,
        
        [Parameter()]
        [string]
        # The schema of the view. Default is `dbo`.
        $SchemaName = 'dbo'
    )
    
    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RemoveViewOperation' $SchemaName, $Name
}

function Rename-Column
{
    <#
    .SYNOPSIS
    Renames a column.
     
    .DESCRIPTION
    SQL Server ships with a stored procedure which is used to rename certain objects. This operation wraps that stored procedure.
     
    Use `Rename-DataType` to rename a data type. Use `Rename-Index` to rename an index. Use `Rename-Object` to rename an object.
 
    .LINK
    http://technet.microsoft.com/en-us/library/ms188351.aspx
 
    .LINK
    Rename-DataType
 
    .LINK
    Rename-Index
     
    .LINK
    Rename-Object
     
    .EXAMPLE
    Rename-Column -TableName 'FooBar' -Name 'Fizz' -NewName 'Buzz'
     
    Changes the name of the `Fizz` column in the `FooBar` table to `Buzz`.
     
    .EXAMPLE
    Rename-Column -SchemaName 'fizz' -TableName 'FooBar' -Name 'Buzz' -NewName 'Baz'
     
    Demonstrates how to rename a column in a table that is in a schema other than `dbo`.
     
    .EXAMPLE
    Rename-Column 'FooBar' 'Fizz' 'Buzz'
     
    Demonstrates how to use the short form to rename `Fizz` column in the `FooBar` table to `Buzz`: table name is first, then existing column name, then new column name.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the table of the column to rename.
        $TableName,
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The current name of the column.
        $Name,
        
        [Parameter(Mandatory=$true,Position=2)]
        [string]
        # The new name of the column.
        $NewName,
        
        [Parameter()]
        [string]
        # The schema of the table. Default is `dbo`.
        $SchemaName = 'dbo'
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RenameColumnOperation' $SchemaName, $TableName, $Name, $NewName
}


function Rename-DataType
{
    <#
    .SYNOPSIS
    Renames data types.
     
    .DESCRIPTION
    This function wraps the `sp_rename` stored procedure, and can be used to rename `USERDATATYPE` types.
 
    Use `Rename-Index` to rename an index. Use `Rename-Column` to rename a column. Use `Rename-Object` to rename an object.
 
    .LINK
    http://technet.microsoft.com/en-us/library/ms188351.aspx
 
    .LINK
    Rename-Column
     
    .LINK
    Rename-Index
 
    .LINK
    Rename-Object
 
    .EXAMPLE
    Rename-DataType -Name 'FooBar' -NewName 'BarFoo'
     
    Changes the name of the `FooBar` type to `BarFoo`.
     
    .EXAMPLE
    Rename-DataType -SchemaName 'fizz' -Name 'Buzz' -NewName 'Baz'
     
    Demonstrates how to rename a data type that is in a schema other than `dbo`.
    #>


    [CmdletBinding()]
    param(
        [Parameter()]
        # The schema of the table. Default is `dbo`.
        [String]$SchemaName = "dbo",

        [Parameter(Mandatory,Position=0)]
        # The current name of the table.
        [String]$Name,
        
        [Parameter(Mandatory,Position=1)]
        # The new name of the table.
        [String]$NewName
    )

    Set-StrictMode -Version 'Latest'

    [Rivet.Operations.RenameDataTypeOperation]::New($SchemaName, $Name, $NewName)

}


 function Rename-Index
{
    <#
    .SYNOPSIS
    Renames an index.
     
    .DESCRIPTION
    SQL Server ships with a stored procedure which is used to rename certain objects. This operation wraps that stored procedure.
     
    Use `Rename-Column` to rename a column. Use `Rename-DataType` to rename a data type. Use `Rename-Object` to rename an object.
 
    .LINK
    http://technet.microsoft.com/en-us/library/ms188351.aspx
 
    .LINK
    Rename-Column
     
    .LINK
    Rename-DataType
 
    .LINK
    Rename-Object
     
    .EXAMPLE
    Rename-Index -TableName 'FooBar' -Name 'IX_Fizz' -NewName 'Buzz'
     
    Changes the name of the `Fizz` index on the `FooBar` table to `Buzz`.
     
    .EXAMPLE
    Rename-Index -SchemaName 'fizz' -TableName 'FooBar' -Name 'IX_Buzz' -NewName 'Fizz'
     
    Demonstrates how to rename an index on a table that is in a schema other than `dbo`.
     
    .EXAMPLE
    Rename-Index 'FooBar' 'IX_Fizz' 'Buzz'
     
    Demonstrates how to use the short form to rename the `Fizz` index on the `FooBar` table to `Buzz`: table name is first, then existing index name, then new index name.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the table of the index to rename.
        $TableName,
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The current name of the index.
        $Name,
        
        [Parameter(Mandatory=$true,Position=2)]
        [string]
        # The new name of the index.
        $NewName,
        
        [Parameter()]
        [string]
        # The schema of the table. Default is `dbo`.
        $SchemaName = 'dbo'
    )
    
    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.RenameIndexOperation' $SchemaName, $TableName, $Name, $NewName
}


function Rename-Object
{
    <#
    .SYNOPSIS
    Renames objects (e.g. tables, constraints, keys).
     
    .DESCRIPTION
    This function wraps the `sp_rename` stored procedure, and can be used to rename objects tracked in `sys.objects`:
 
     * Tables
     * Functions
     * Synonyms
     * Constraints/keys
     * Views
     * Stored procedures
     * Triggers
 
    Use `Rename-Index` to rename an index. Use `Rename-Column` to rename a column. Use `Rename-DataType` to rename a data type.
 
    .LINK
    http://technet.microsoft.com/en-us/library/ms188351.aspx
 
    .LINK
    Rename-Column
     
    .LINK
    Rename-DataType
     
    .LINK
    Rename-Index
 
    .EXAMPLE
    Rename-Object -Name 'FooBar' -NewName 'BarFoo'
     
    Changes the name of the `FooBar` table to `BarFoo`.
     
    .EXAMPLE
    Rename-Object -SchemaName 'fizz' -Name 'Buzz' -NewName 'Baz'
     
    Demonstrates how to rename a table that is in a schema other than `dbo`.
     
    .EXAMPLE
    Rename-Object 'FK_Foo_Bar' 'FK_Bar_Foo'
     
    Demonstrates how to use `Rename-Object` without explicit parameters, and how to rename a foreign key.
    #>


    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        # The schema of the table. Default is `dbo`.
        $SchemaName = "dbo",

        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The current name of the table.
        $Name,
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The new name of the table.
        $NewName
    )

    Set-StrictMode -Version 'Latest'

    [Rivet.Operations.RenameObjectOperation]::New($SchemaName, $Name, $NewName)
}



function Stop-Migration
{
    <#
    .SYNOPSIS
    Stops a migration from getting poppped.
 
    .DESCRIPTION
    The `Stop-Migration` operation stops a migration from getting popped. When put in your migration's `Pop-Migration` function, the migration will fail when someone attempts to pop it. Use this operation to mark a migration as irreversible.
 
    `Stop-Migration` was added in Rivet 0.6.
 
    .EXAMPLE
    Stop-Migration
 
    Demonstrates how to use use `Stop-Migration`.
 
    .EXAMPLE
    Stop-Migration -Message 'The datatabase's flibbers have been upgraed to flobbers. This operation can't be undone. Sorry.'
 
    Demonstrates how to display a message explaining why the migration isn't reversible.
    #>

    [CmdletBinding()]
    param(
        [string]
        # A message to show that explains why the migrations isn't reversible. Default message is `This migration is irreversible and can't be popped.`.
        $Message = 'This migration is irreversible and can''t be popped.'
    )

    Set-StrictMode -Version 'Latest'

    New-Object -TypeName 'Rivet.Operations.IrreversibleOperation' -ArgumentList $Message
}


function Update-CodeObjectMetadata
{
    <#
    .SYNOPSIS
    Updates the metadata for a stored procedure, user-defined function, view, trigger, etc.
 
    .DESCRIPTION
    SQL Server has a stored procedure, `sys.sp_refreshsqlmodule`, which will refresh/update a the objects used by a code object (stored procedure, user-defined function, view, etc.) if that object has changed since the code object was created.
 
    .LINK
    http://technet.microsoft.com/en-us/library/bb326754.aspx
 
    .EXAMPLE
    Update-CodeObjectMetadata 'GetUsers'
 
    Demonstrates how to update the `GetUsers` code object.
 
    .EXAMPLE
    Update-CodeObjectMetadata -SchemaName 'example' 'GetUsers'
 
    Demonstrates how to update a code object in a custom schema, in this case the `example` schema.
    #>

    [CmdletBinding(DefaultParameterSetName='CodeObject')]
    param(
        [Parameter()]
        [string]
        # The code object's schema name. Default is `dbo`.
        $SchemaName = 'dbo',

        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the code object.
        $Name,

        [Parameter(Mandatory=$true,ParameterSetName='DATABASE_DDL_TRIGGER')]
        [Switch]
        # The object is a database DDL trigger.
        $DatabaseDdlTrigger,

        [Parameter(Mandatory=$true,ParameterSetName='SERVER_DDL_TRIGGER')]
        [Switch]
        # The object is a server DDL trigger.
        $ServerDdlTrigger
    )

    Set-StrictMode -Version 'Latest'

    $namespace = $null
    if( $PSCmdlet.ParameterSetName -like '*_DDL_TRIGGER' )
    {
        $namespace = $PSCmdlet.ParameterSetName
    }

    New-Object 'Rivet.Operations.UpdateCodeObjectMetadataOperation' $SchemaName,$Name,$namespace
}


function Update-Description
{
    <#
    .SYNOPSIS
    Updates the `MS_Description` extended property of a table or column.
 
    .DESCRIPTION
    The `sys.sp_updateextendedproperty` stored procedure is used to update a table/column's description (i.e. the `MS_Description` extended property), but the syntax is weird. This function hides that weirdness from you. You're welcome.
 
    .EXAMPLE
    Update-Description -Description 'Whoseit's whatsits table.' -TableName WhoseitsWhatsits
 
    Updates the description (i.e. the `MS_Description` extended property) on the `WhoseitsWhatsits` table.
 
    .EXAMPLE
    Update-Description -Description 'Is it a snarfblat?' -TableName WhoseitsWhatsits -ColumnName IsSnarfblat
 
    Updates the description (i.e. the `MS_Description` extended property) on the `WhoseitsWhatsits` table's `IsSnarfblat` column.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The value for the MS_Description extended property.
        $Description,

        [Alias('Schema')]
        [string]
        # The schema. Defaults to `dbo`.
        $SchemaName = 'dbo',

        [Parameter(Mandatory=$true)]
        [Alias('Table')]
        [string]
        # The name of the table where the extended property is getting updated.
        $TableName,

        [Parameter(ParameterSetName='ForColumn')]
        [Alias('Column')]
        [string]
        # The name of the column where the extended property is getting updated.
        $ColumnName
    )

    Set-StrictMode -Version 'Latest'

    $optionalArgs = @{ }
    if( $ColumnName )
    {
        $optionalArgs.ColumnName = $ColumnName
    }

    Update-ExtendedProperty -Name ([Rivet.Operations.ExtendedPropertyOperation]::DescriptionPropertyName) `
                            -Value $Description `
                            -SchemaName $SchemaName `
                            -TableName $TableName `
                            @optionalArgs

}

function Update-ExtendedProperty
{
    <#
    .SYNOPSIS
    Updates an object's extended property.
     
    .DESCRIPTION
    SQL Server has a special stored procedure for updating extended property metatdata about an object. Unfortunately, it has a really clunky interface. This function is an attempt to wrap `sp_updateextendedproperty` with a saner interface.
     
    Currently, this function only supports updating properties for schemas, tables, and columns. Submit a patch!
     
    .LINK
    Add-Description
     
    .LINK
    Add-ExtendedProperty
     
    .LINK
    Remove-Description
     
    .LINK
    Remove-ExtendedProperty
     
    .LINK
    Update-Description
     
    .LINK
    Update-ExtendedProperty
     
    .EXAMPLE
    Update-ExtendedProperty -Name 'Deploy' -Value 'FALSE' -SchemaName 'spike'
     
    Sets the custom `Deploy` metadata to be `FALSE`.
     
    .EXAMPLE
    Update-ExtendedProperty -Name 'Deploy' -Value 'FALSE' -TableName 'Food'
     
    Sets the custom `Deploy` metadata to be `FALSE` on the `Food` table in the `dbo` schema.
     
    .EXAMPLE
    Update-ExtendedProperty -Name 'IsEncrypted' -Value 'TRUE' -TableName 'User' -ColumnName 'Password'
     
    Sets the custom `IsEncrypted` metadata to be `TRUE` on the `User` table's `Password` column.
 
    .EXAMPLE
    Update-ExtendedProperty -Name 'ContainsPII' -Value 'FALSE' -View 'LoggedInUsers'
     
    Demonstrates how to update custom metadata on the `LoggedInUsers` view
 
    .EXAMPLE
    Update-ExtendedProperty -Name 'IsEncrypted' -Value 'FALSE' -View 'LoggedInUsers' -ColumnName 'Password'
     
    Demonstrates how to update custom metadata for a view's column
 
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the extended property to update.
        $Name,
        
        [Parameter(Mandatory=$true,Position=1)]
        [AllowNull()]
        # The value of the extended property.
        $Value,
        
        [Parameter(ParameterSetName='SCHEMA')]
        [Parameter(ParameterSetName='TABLE')]
        [Parameter(ParameterSetName='TABLE-COLUMN')]
        [Parameter(ParameterSetName='VIEW')]
        [Parameter(ParameterSetName='VIEW-COLUMN')]
        [string]
        # The schema of the object.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,ParameterSetName='TABLE')]
        [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')]
        [Alias('Table')]
        [string]
        # The table name.
        $TableName,
        
        [Parameter(Mandatory=$true,ParameterSetName='VIEW')]
        [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')]
        [Alias('View')]
        [string]
        # The table name.
        $ViewName,        
        
        [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')]
        [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')]
        [Alias('Column')]
        [string]
        # The column name.
        $ColumnName
    )

    Set-StrictMode -Version 'Latest'

    $objectName = ''
    if ($PsCmdlet.ParameterSetName -eq "SCHEMA")
    {
        $op = New-Object 'Rivet.Operations.UpdateExtendedPropertyOperation' $SchemaName, $Name, $Value
        $objectName = $SchemaName
    }

    if ($PsCmdlet.ParameterSetName -eq "TABLE")
    {
        $op = New-Object 'Rivet.Operations.UpdateExtendedPropertyOperation' $SchemaName, $TableName, $Name, $Value, $false
        $objectName = '{0}.{1}' -f $SchemaName,$TableName
    }

    if ($PsCmdlet.ParameterSetName -eq "VIEW")
    {
        $op = New-Object 'Rivet.Operations.UpdateExtendedPropertyOperation' $SchemaName, $ViewName, $Name, $Value, $true
        $objectName = '{0}.{1}' -f $SchemaName,$ViewName
    }

    if ($PsCmdlet.ParameterSetName -eq "TABLE-COLUMN")
    {
        $op = New-Object 'Rivet.Operations.UpdateExtendedPropertyOperation' $SchemaName, $TableName, $ColumnName, $Name, $Value, $false
        $objectName = '{0}.{1}.{2}' -f $SchemaName,$TableName,$ColumnName
    }

    if ($PsCmdlet.ParameterSetName -eq "VIEW-COLUMN")
    {
        $op = New-Object 'Rivet.Operations.UpdateExtendedPropertyOperation' $SchemaName, $ViewName, $ColumnName, $Name, $Value, $true
        $objectName = '{0}.{1}.{2}' -f $SchemaName,$ViewName,$ColumnName
    }

    return $op
}

function Update-Row
{
    <#
    .SYNOPSIS
    Updates a row of data in a table.
     
    .DESCRIPTION
    To specify which columns in a row to update, pass a hashtable as a value to the `Column` parameter. This hashtable should have keys that map to column names, and the value of each key will be used to update row(s) in the table.
     
    You are required to use a `Where` clause so that you don't inadvertently/accidentally update a column in every row in a table to the same value. If you *do* want to update the value in every row of the database, omit the `Where` parameter and add the `Force` switch.
     
    .EXAMPLE
    Update-Row -SchemaName 'rivet' 'Migrations' @{ LastUpdated = (Get-Date -Utc) } -Where 'MigrationID=20130913131104'
     
    Demonstrates how to update the `LastUpdated` date in the `rivet.Migrations` table for the migration with ID `20130913131104`. Don't do this in real life.
     
    .EXAMPLE
    Update-Row -SchemaName 'rivet' 'Migrations' @{ LastUpdated = (Get-Date -Utc) } -Force
     
    Demonstrates how to update the `LastUpdated` date *for all rows* in the `rivet.Migrations` table. You *really, really* don't want to do this in real life.
 
    .EXAMPLE
    Update-Row 'Migrations' @{ MigrationID = 'MigrationID + 1' } -Raw -Where 'MigrationID=20130101010101'
 
    Demonstrates how to pass a SQL expression as the value for the column to update: use the `-RawColumnValue` switch.
    #>

    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the table.
        $TableName,
        
        [Parameter()]
        [string]
        # The schema name of the table. Default is `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1)]
        [Hashtable]
        # A hashtable of name/value pairs that map to column names/values that will be updated.
        $Column,

        [Switch]
        # Don't escape/quote the column value(s).
        $RawColumnValue,
        
        [Parameter(Mandatory=$true,Position=2,ParameterSetName='SpecificRows')]
        [string]
        # A condition to use so that only certain rows are updated. Without a value, you will need to use the `Force` parameter so you don't accidentally update the contents of an entire table.
        $Where,
        
        [Parameter(Mandatory=$true,ParameterSetName='AllRows')]
        [Switch]
        # Updates all the rows in the table.
        $All
    )

    Set-StrictMode -Version 'Latest'

    if ($PSCmdlet.ParameterSetName -eq 'SpecificRows')
    {
        $op = New-Object 'Rivet.Operations.UpdateRowOperation' $SchemaName, $TableName, $Column, $Where, $RawColumnValue       
    }
    elseif ($PSCmdlet.ParameterSetName -eq 'AllRows')
    {
        $op = New-Object 'Rivet.Operations.UpdateRowOperation' $SchemaName, $TableName, $Column, $RawColumnValue
    }

    return $op
}

function Update-StoredProcedure
{
    <#
    .SYNOPSIS
    Updates an existing stored procedure.
 
    .DESCRIPTION
    Updates an existing stored procedure.
 
    .LINK
    https://msdn.microsoft.com/en-us/library/ms189762.aspx
 
    .EXAMPLE
    Update-StoredProcedure -SchemaName 'rivet' 'ReadMigrations' 'AS select * from rivet.Migrations'
 
    Updates a stored procedure to read the migrations from Rivet's Migrations table. Note that in real life, you probably should leave my table alone.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the stored procedure.
        $Name,
            
        [Parameter()]
        [string]
        # The schema name of the stored procedure. Defaults to `dbo`.
        $SchemaName = 'dbo',
            
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The store procedure's definition, which is everything after the `alter procedure [schema].[name]` clause.
        $Definition
    )
    
    Set-StrictMode -Version 'Latest'
        
    New-Object 'Rivet.Operations.UpdateStoredProcedureOperation' $SchemaName, $Name, $Definition
}

function Update-Table
{
    <#
    .SYNOPSIS
    Adds new columns or alters existing columns on an existing table.
 
    .DESCRIPTION
    The `Update-Table` operation adds, updates, and removes columns from a table. Columns are added, then updated, then removed.
     
    The new columns for the table should be created and returned in a script block, which is passed as the value of the `AddColumn` parameter. For example,
 
        Update-Table 'Suits' -AddColumn {
            Bit 'HasVest' -NotNull -Default 0
        }
         
    The new definitions for existing columns should be created and returned in a script block, which is passed as the value of the `UpdateColumn` parameter. For example,
     
        Update-Table 'Suits' -UpdateColumn {
            VarChar 'Color' 256 -NotNull
        }
 
    .LINK
    bigint
 
    .LINK
    binary
 
    .LINK
    bit
 
    .LINK
    char
 
    .LINK
    date
 
    .LINK
    datetime
 
    .LINK
    datetime2
 
    .LINK
    datetimeoffset
 
    .LINK
    decimal
 
    .LINK
    float
 
    .LINK
    hierarchyid
 
    .LINK
    int
 
    .LINK
    money
 
    .LINK
    nchar
 
    .LINK
    numeric
 
    .LINK
    nvarchar
 
    .LINK
    real
 
    .LINK
    rowversion
 
    .LINK
    smalldatetime
 
    .LINK
    smallint
 
    .LINK
    smallmoney
 
    .LINK
    sqlvariant
 
    .LINK
    time
 
    .LINK
    tinyint
 
    .LINK
    uniqueidentifier
 
    .LINK
    varbinary
 
    .LINK
    varchar
 
    .LINK
    xml
 
    .EXAMPLE
    Update-Table -Name 'Ties' -AddColumn { VarChar 'Color' 50 -NotNull }
 
    Adds a new `Color` column to the `Ties` table. Pretty!
     
    .EXAMPLE
    Update-Table -Name 'Ties' -UpdateColumn { VarChar 'Color' 100 -NotNull }
     
    Demonstrates how to change the definition of an existing column.
 
    .EXAMPLE
    Update-Table -Name 'Ties' -RemoveColumn 'Pattern','Manufacturer'
 
    Demonstrates how to remove columns from a table.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        # The name of the table.
        [String]$Name,

        # The table's schema. Defaults to `dbo`.
        [String]$SchemaName = 'dbo',

        [Alias('Add')]
        # A script block that returns the new columns to add to a table.
        [scriptblock]$AddColumn,
        
        [Alias('Update')]
        [Alias('Alter')]
        # A script block that returns new column definitions for existing columns
        [scriptblock]$UpdateColumn,

        [Alias('Remove')]
        # Columns to remove.
        [String[]]$RemoveColumn
    )

    Set-StrictMode -Version 'Latest'

    [Object[]]$newColumns = @()
    if( $AddColumn )
    {
        $newColumns = & $AddColumn
    }

    [Object[]]$updatedColumns = @()
    if ($UpdateColumn)
    {
        $updatedColumns = & $UpdateColumn
        foreach( $column in $updatedColumns )
        {
            if( $column.DefaultExpression -or $column.DefaultConstraintName )
            {
                Write-Error -Message ("You're attempting to add a default constraint to existing column [$($column.Name)] on table [$($SchemaName)].[$($Name)]. SQL Server doesn't support adding default constraints on existing columns. Remove the -Default and -DefaultConstraintName parameters on this column and use the Add-DefaultConstraint operation to add a default constraint to this column.") -ErrorAction Stop
                return
            }

            if( $column.Identity )
            {
                Write-Error -Message ("You're attempting to add identity to existing column [$($Column.Name)] on table [$($SchemaName)].[$($Name)]. This is not supported by SQL Server. You'll need to drop and re-create the column.") -ErrorAction Stop
                return
            }
        }

    }

    New-Object 'Rivet.Operations.UpdateTableOperation' $SchemaName,$Name,$newColumns,$updatedColumns,$RemoveColumn

    foreach ($i in $newColumns)
    {
        if ($i.Description)
        {
            Add-Description -Description $i.Description -SchemaName $SchemaName -TableName $Name -ColumnName $i.Name
        }
    }

    foreach ($i in $updatedColumns)
    {
        if ($i.Description)
        {
            Update-Description -Description $i.Description -SchemaName $SchemaName -TableName $Name -ColumnName $i.Name
        }
    }
}


function Update-Trigger
{
    <#
    .SYNOPSIS
    Updates an existing trigger.
     
    .DESCRIPTION
    Updates an existing trigger.
 
    .LINK
    https://msdn.microsoft.com/en-us/library/ms176072.aspx
     
    .LINK
    Add-Trigger
    Remove-Trigger
     
    .EXAMPLE
    Update-Trigger 'PrintMessage' 'ON rivet.Migrations for insert as print ''Migration applied!'''
     
    Updates a trigger to prints a method when a row gets inserted into the `rivet.Migrations` table.
    #>

    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the trigger.
        $Name,
        
        [Parameter()]
        [string]
        # The schema of the trigger.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The body of the trigger. Everything after and including the `ON` clause.
        $Definition
    )

    Set-StrictMode -Version 'Latest'

    New-Object 'Rivet.Operations.UpdateTriggerOperation' $SchemaName, $Name, $Definition
}


 function Update-UserDefinedFunction
{
    <#
    .SYNOPSIS
    Updates an existing user-defined function.
 
    .DESCRIPTION
    Updates an existing user-defined function.
 
    .LINK
    https://msdn.microsoft.com/en-us/library/ms186967.aspx
 
    .EXAMPLE
    Update-UserDefinedFunction -SchemaName 'rivet' 'One' 'returns tinyint begin return 1 end'
 
    Updates a user-defined function to return the number 1.
    #>
    

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the stored procedure.
        $Name,
        
        [Parameter()]
        [string]
        # The schema name of the stored procedure. Defaults to `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The store procedure's definition. Everything after the `alter function [schema].[name]` clause.
        $Definition
    )
    
    New-Object Rivet.Operations.UpdateUserDefinedFunctionOperation $SchemaName,$Name,$Definition
}

 function Update-View
 {
    <#
    .SYNOPSIS
    Updates an existing view.
 
    .DESCRIPTION
    Updates an existing view.
 
    .LINK
    https://msdn.microsoft.com/en-us/library/ms173846.aspx
 
    .EXAMPLE
    Update-View -SchemaName 'rivet' 'ReadMigrations' 'AS select * from rivet.Migrations'
 
    Updates a view to read all the migrations from Rivet's Migrations table. Don't do this in real life.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The name of the view.
        $Name,
        
        [Parameter()]
        [string]
        # The schema name of the view. Defaults to `dbo`.
        $SchemaName = 'dbo',
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The definition of the view. Everything after the `alter view [schema].[name]` clause.
        $Definition
    )
    
    Set-StrictMode -Version 'Latest'

    New-Object Rivet.Operations.UpdateViewOperation $SchemaName,$Name,$Definition
}