Работа с MS SQL от Powershell на Linux

Тази статия е чисто практическа и е посветена на моята тъжна история

Подготвяйки се за Zero Touch PROD за RDS (MS SQL), за който ни прехвърляха постоянно, направих презентация (POC — Proof Of Concept) на автоматизация: набор от скриптове на PowerShell. След презентацията, когато бурните, продължителни аплодисменти утихнаха и преминаха в неизменни овации, ми казаха — всичко това е страхотно, но по идеологически причини всички наши Jenkins slaves работят под Linux!

Възможно ли е това? Да вземеш такъв мил, уютен DBA от Windows и да го сложиш в самото огнище на PowerShell под Linux? Това не е ли жестоко?

Работа с MS SQL от Powershell на Linux
Приключих да се потопя в тази странна комбинация от технологии. Разбира се, всичките ми 30+ скрипта престанаха да работят. За мое учудване, в рамките на един работен ден успях всичко да поправя. Пиша по горещи следи. И така, какви подводни камъни могат да срещнат при прехвърлянето на скриптове на PowerShell от Windows в Linux?

sqlcmd срещу Invoke-SqlCmd

Нека напомня основната разлика между тях. Старата добра утилита sqlcmd работи и под Linux с почти идентична функционалност. Заявката, която искаме да изпълним, подаваме с -Q, входният файл като -i, а изходът -o. Единствено имената на файловете, разбира се, са чувствителни към регистъра. Ако използвате -i, то в края на файла трябва да напишете:

GO
EXIT

Ако в края няма EXIT, sqlcmd ще премине в режим на изчакване на вход, а ако преди EXIT няма да GO, то последната команда няма да се изпълни. В изходния файл попада целият изход, selects, съобщения, print и т.н.

Invoke-SqlCmd връща резултата под формата на DataSet, DataTables или DataRows. Затова, ако можете да обработите резултата от проста заявка и през sqlcmd, разглеждайки неговия изход, то изкарването на нещо по-сложно е практически невъзможно: за това има Invoke-SqlCmd. Но тази команда има и свои особености:

  • Ако й подадете файл през -InputFile, то EXIT не е необходим, освен това, тя дава синтактична грешка
  • -OutputFile не, командата ви връща резултата под формата на обект
  • За указване на сървъра има два синтаксиса: -ServerInstance -Username -Password -Database и през -ConnectionString. Както и да е, в първия случай не е възможно да се укаже порт, различен от 1433.
  • текстовият изход, тип PRINT, който е елементарен за улавяне sqlcmd, за Invoke-SqlCmd е проблем
  • И най-важното: най-вероятно в Linux нямате този cmdlet!

И това е основният проблем. Само през март този cmdlet стана достъпен за не-windows платформи, и накрая можем да продължим напред!

Замяна на променливи

В sqlcmd има замяна на променливи с помощта на -v, например, така:

# $conn содержит начало команды sqlcmd
$cmd = $conn + " -i D:appsSlaveJobsKillSpid.sql -o killspid.res 
  -v spid =`"" + $spid + "`" -v age =`"" + $age + "`""
Invoke-Expression $cmd

В скрипта на SQL използваме замени:

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

Така е. В *nix замените на променливи не работят. Параметърът -v се игнорира. При Invoke-SqlCmd се игнорира. -Променливи. Въпреки че параметърът, който задава самите променливи, се игнорира, самите замени работят — можете да използвате всякакви променливи от Shell. Но се обидих на променливите и реших да не завися от тях изобщо, и постъпих грубо и примитивно, за щастие скриптовете на sql са кратки:

# 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"

Това, както разбрахте, е тест вече с unix версия.

Качване на файлове

В Window версията всяка операция беше придружена от одит: извършили sqlcmd, получавали някаква руган в output file, прилагали този файл към таблица с одит. За щастие SQL сървърът работеше на същия сървър, на който беше Jenkins, това се правеше по следния начин:

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 = '' '',CODEPAGE = ''ACP'')'
  exec (@sql)
  select @sql=filer from #multi
  update JenkinsAudit set multiliner=@sql where ID=@id
  return

По този начин захващаме файла BCP изцяло и го вкарваме в полето nvarchar(max) на таблицата с одит. Разбира се, цялата тази система се разпадна, тъй като вместо SQL сървър получих RDS, а BULK INSERT изобщо не работи по UNC заради опитите да вземе ексклузивен лок на файла, а с RDS това от самото начало е обречено. Така че реших да променя дизайна на системата, съхранявайки одита ред по ред:

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

И да пиша в тази таблица така:

function WriteAudit([string]$Filename, [string]$ConnStr, 
     [string]$Tabname, [string]$Jobname)
{
  # get $lastid of the last execution  -- пропуснато за статията
	
  #създайте мрежа и я попълнете с данни от файла
  $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() 
  }  

За да изберете съдържанието, трябва да направите select по ID, избирайки в реда n (идентификатор).

В следващата статия ще се спра по-подробно на това как всичко това взаимодейства с Jenkins.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster