Trabalhando com MS SQL da Powershell no Linux

Este artigo é puramente prático e é dedicado à minha triste história.


Preparando-me para o Zero Touch PROD para RDS (MS SQL), sobre o qual todos os nossos ouvidos estavam zumbindo, fiz uma apresentação (POC - Proof Of Concept) de automação: um conjunto de scripts do PowerShell. Após a apresentação, quando os aplausos altos e prolongados cessaram, transformando-se em uma ovação incessante, me disseram que tudo isso é bom, mas apenas por razões ideológicas, todos nós temos escravos Jenkins rodando no Linux!

Isso é possível? Tirar um DBA de tubo tão quente do Windows e colocá-lo no calor do PowerShell no Linux? Isso não é cruel?


Eu tive que mergulhar nessa estranha combinação de tecnologia. Obviamente, todos os meus mais de 30 scripts pararam de funcionar. Para minha surpresa, em um dia útil consegui consertar tudo. Estou escrevendo em perseguição. Então, que armadilhas você pode encontrar ao transportar scripts do PowerShell do Windows para o Linux?

sqlcmd vs Invoke-SqlCmd


Deixe-me lembrá-lo da principal diferença entre eles. O bom e velho utilitário sqlcmd também funciona no Linux, com funcionalidade quase idêntica. Para executar a consulta, passamos -Q, o arquivo de entrada como -i e a saída -o. Aqui estão apenas os nomes dos arquivos, é claro, diferenciam maiúsculas de minúsculas. Se você usar -i, escreva no arquivo no final:

GO EXIT 

Se não houver EXIT no final, o sqlcmd aguardará a entrada e, se não houver GO antes de EXIT , o último comando não funcionará. Toda saída, seleção, mensagem, impressão, etc., chega ao arquivo de saída.

Invoke-SqlCmd retorna o resultado como um DataSet, DataTables ou DataRows. Portanto, se você puder processar o resultado de uma seleção simples por meio do sqlcmd , analisando sua saída, produzir algo complicado é quase impossível: existe Invoke-SqlCmd para isso. Mas essa equipe tem suas próprias piadas:

  • Se você passar o arquivo para ele via -InputFile , EXIT não é necessário, além disso, isso gera um erro de sintaxe
  • -OutputFile não, o comando retorna o resultado como um objeto
  • Existem duas sintaxes para especificar um servidor: -ServerInstance -Username -Password -Database e através de -ConnectionString . Curiosamente, no primeiro caso, você não pode especificar uma porta diferente de 1433.
  • A saída de texto, do tipo PRINT, que é "capturada" pelo sqlcmd de maneira elementar, é um problema do Invoke-SqlCmd
  • E o mais importante: provavelmente esse cmdlet não está no seu Linux!

E este é o principal problema. Somente em março, esse cmdlet ficou disponível para plataformas não Windows e, finalmente, podemos seguir em frente!

Substituição variável


Sqlcmd tem substituição variável com -v, assim:

 # $conn    sqlcmd $cmd = $conn + " -i D:\apps\SlaveJobs\KillSpid.sql -o killspid.res -v spid =`"" + $spid + "`" -v age =`"" + $age + "`"" Invoke-Expression $cmd 

No script SQL, usamos substituições:

 set @spid=$(spid) set @age=$(age) 

Então aqui. No * nix , as substituições de variáveis ​​não funcionam . A opção -v é ignorada. Invoke-SqlCmd ignora -Variables . Embora o parâmetro que define as variáveis ​​em si seja ignorado, as próprias substituições funcionam - você pode usar quaisquer variáveis ​​do Shell. No entanto, fiquei ofendido com as variáveis ​​e decidi não depender delas, e agi de maneira grosseira e primitiva, pois os scripts para sql são curtos:

 # prepend the parameters "declare @age int, @spid int" | Add-Content "q.sql" "set @spid=" + $spid | Add-Content "q.sql" "set @age=" + $age | Add-Content "q.sql" foreach ($line in Get-Content "Sqlserver/Automation/KillSpid.sql") { $line | Add-Content "q.sql" } $cmd = "/opt/mssql-tools/bin/" + $conn + " -i q.sql -o res.log" 

Este, como você entende, é um teste da versão Unix.

Carregar arquivos


Na versão do Windows, qualquer operação que eu realizava era acompanhada de uma auditoria: eles executavam o sqlcmd, obtinha algum tipo de abuso no arquivo de saída, anexava esse arquivo à placa de auditoria. Felizmente, o servidor SQL trabalhava no mesmo servidor que o Jenkins, era feito algo como isto:

 CREATE procedure AuditUpload @id int, @filename varchar(256) as set nocount on declare @sql varchar(max) CREATE TABLE #multi (filer NVARCHAR(MAX)) set @sql='BULK INSERT #multi FROM '''+@filename +''' WITH (ROWTERMINATOR = ''\0'',CODEPAGE = ''ACP'')' exec (@sql) select @sql=filer from #multi update JenkinsAudit set multiliner=@sql where ID=@id return 

Assim, engolimos o arquivo BCP inteiro e colocamos a tabela de auditoria no campo nvarchar (max). Obviamente, todo esse sistema desmoronou, porque, em vez do SQL Server, eu obtive o RDS, e o BULK INSERT não funciona no \\ UNC por tentar bloquear um arquivo exclusivamente, e com o RDS isso geralmente está condenado desde o início. Então, decidi mudar o design do sistema, armazenando a linha de auditoria por linha:

 CREATE TABLE AuditOut ( ID int NULL, TextLine nvarchar(max) NULL, n int IDENTITY(1,1) PRIMARY KEY ) 

E escreva nesta tabela assim:

 function WriteAudit([string]$Filename, [string]$ConnStr, [string]$Tabname, [string]$Jobname) { # get $lastid of the last execution --    #create grid and populate it with data from file $audit = Get-Content $Filename $DT = new-object Data.DataTable $COL1 = new-object Data.DataColumn; $COL1.ColumnName = "ID"; $COL1.DataType = [System.Type]::GetType("System.Int32") $COL2 = new-object Data.DataColumn; $COL2.ColumnName = "TextLine"; $COL2.DataType = [System.Type]::GetType("System.String") $DT.Columns.Add($COL1) $DT.Columns.Add($COL2) foreach ($line in $audit) { $DR = $dt.NewRow() $DR.Item("ID") = $lastid $DR.Item("TextLine") = $line $DT.Rows.Add($DR) } # write it to table $conn=new-object System.Data.SqlClient.SQLConnection $conn.ConnectionString = $ConnStr $conn.Open() $bulkCopy = new-object ("Data.SqlClient.SqlBulkCopy") $ConnStr $bulkCopy.DestinationTableName = $Tabname $bulkCopy.BatchSize = 50000 $bulkCopy.BulkCopyTimeout = 0 $bulkCopy.WriteToServer($DT) $conn.Close() } 

Para selecionar o conteúdo, selecione por ID, escolhendo n (identidade) em ordem.

No próximo artigo, abordarei mais detalhadamente como tudo isso interage com Jenkins.

Source: https://habr.com/ru/post/pt447100/


All Articles