Arbejder med MS SQL fra Powershell på Linux

Denne artikel er rent praktisk og dedikeret til min triste historie

Forberedelse til Zero Touch PROD for RDS (MS SQL), om hvilket vi har hørt meget om, lavede jeg en præsentation (POC – Proof Of Concept) af automatiseringen: en samling af powershell-scripts. Efter præsentationen, da de stormende, langvarige applaus blev til uophørlige ovationer, blev jeg fortalt – alt dette er godt, men af ideologiske grunde kører alle vores Jenkins-slaver på Linux!

Er det overhovedet muligt? At tage en så varm, hyggelig DBA fra Windows og smide ham ind i helvedet af powershell under Linux? Er det ikke grusomt?

Arbejder med MS SQL fra Powershell på Linux
Jeg måtte dykke ned i denne mærkelige kombination af teknologier. Selvfølgelig stoppede alle mine 30+ scripts med at fungere. Til min overraskelse lykkedes det mig at rette det hele på en arbejdsdag. Jeg skriver i øjeblikket. Så hvilke faldgruber kan du støde på, når du overfører powershell-scripts fra Windows til Linux?

sqlcmd vs Invoke-SqlCmd

Lad mig minde om den vigtigste forskel imellem dem. Den gamle, gode værktøj sqlcmd fungerer også under Linux, med næsten identisk funktionalitet. Vi sender forespørgslen til udførelse med -Q, indgangsfilen som -i, og output -o. Bare husk, at filnavne, selvfølgelig, er case-sensitive. Hvis du bruger -i, så skriv i filen til sidst:

GO
EXIT

Hvis der ikke er EXIT til sidst, vil sqlcmd vente på input, og hvis før EXIT vil ikke GO, så vil den sidste kommando ikke udføres. Alt output, selects, meddelelser, print osv. går til outputfilen.

Invoke-SqlCmd returnerer resultatet som DataSet, DataTables eller DataRows. Så hvis du kan behandle resultatet af et simpelt select via sqlcmd, og analysere dens output, er det næsten umuligt at udlede noget komplekst: til det formål er der Invoke-SqlCmd. Men denne kommando har også sine sjove elementer:

  • Hvis du sender en fil til den via -InputFile, så EXIT er ikke påkrævet, desuden giver det en syntaktisk fejl
  • -OutputFile nej, kommandoen returnerer dig resultatet som et objekt
  • For at angive serveren er der to syntakser: -ServerInstance -Username -Password -Database og gennem -ConnectionString. Underligt nok, i det første tilfælde kan du ikke angive en port, der er forskellig fra 1433.
  • den tekstlige output, såsom PRINT, der simpelthen kan "fanges" sqlcmd, for Invoke-SqlCmd er et problem
  • Og det vigtigste: der er sandsynligvis ikke denne cmdlet i din Linux!

Og det er det primære problem. Først i marts blev denne cmdlet tilgængelig for ikke-Windows platforme, og endelig kan vi komme videre!

Variabeludskiftning

I sqlcmd er der variabel substitution ved hjælp af -v, for eksempel sådan her:

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

I SQL-scriptet bruger vi substitutioner:

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

Så det. I *nix virtuelle variabler fungerer ikke. Parameter -v ignoreres. I Invoke-SqlCmd ignoreres -Variabler. Selvom parameteren, der definerer selve variablerne, ignoreres, fungerer selve substitutioner — du kan bruge enhver variabel fra Shell. Men jeg blev irriteret på variablerne og besluttede helt at være uafhængig af dem, og jeg gik groft og primitivt til værks, heldigvis er SQL-scripts korte:

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

Det, som du har forstået, er allerede en test fra Unix-versionen.

Filupload

I Windows-versionen blev enhver operation ledsaget af revision: udførte sqlcmd, modtog noget klager i output-filen, vedhæftede denne fil til revisionsregistret. Heldigvis kørte SQL serveren på den samme server som Jenkins, det blev gjort cirka sådan her:

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

På den måde indlæser vi hele BCP-filen og gemmer den i feltet nvarchar(max) i revisionsregistret. Selvfølgelig kollapsede hele dette system, da jeg i stedet for en SQL-server fik RDS, og BULK INSERT fungerer overhovedet ikke via UNC på grund af forsøget på at opnå eksklusiv lås på filen, og med RDS er det allerede dømt fra starten af. Så jeg besluttede at ændre systemdesignet og gemme revisionen liniært:

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

Og skrive til denne tabel sådan her:

function WriteAudit([string]$Filename, [string]$ConnStr, 
     [string]$Tabname, [string]$Jobname)
{
  # få $lastid af den sidste eksekvering  -- springet over for artiklen
	
  #opret gitter og fyld det med data fra filen
  $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)   
    } 

  # skriv det til tabellen
  $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() 
  }  

For at vælge indholdet skal man lave select efter ID, vælge i rækkefølge n (identity).

I den næste artikel vil jeg uddybe, hvordan det hele interagerer med Jenkins.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster