Werk met MS SQL vanaf Powershell op Linux

Hierdie artikel is bloot prakties en word opgedra aan my hartseer verhaal

Maak gereed vir Zero Touch PROD vir RDS (MS SQL), waaroor ons almal se ore gegons het, het ek 'n aanbieding (POC - Proof Of Concept) van outomatisering gemaak: 'n stel powershell-skrifte. Na die aanbieding, toe die stormagtige, langdurige applous bedaar en in onophoudelike applous verander het, het hulle vir my gesê - dit alles is goed, maar net om ideologiese redes werk al ons Jenkins-slawe op Linux!

Is dit moontlik? Neem so 'n warm, lamp DBA van onder Windows en plak dit in die hitte van powershell onder Linux? Is dit nie wreed nie?

Werk met MS SQL vanaf Powershell op Linux
Ek moes myself in hierdie vreemde kombinasie van tegnologieë verdiep. Natuurlik het al my 30+ skrifte opgehou werk. Tot my verbasing het ek daarin geslaag om alles in een werksdag reg te maak. Ek skryf op heterdaad. So, watter slaggate kan jy teëkom wanneer jy powershell-skrifte van Windows na Linux oordra?

sqlcmd vs Invoke-SqlCmd

Laat ek jou herinner aan die belangrikste verskil tussen hulle. Goeie ou nut sqlcmd Dit werk ook onder Linux, met byna identiese funksionaliteit. Ons gee die navraag deur om -Q uit te voer, die invoerlêer as -i, en die uitvoer as -o. Maar die lêername word natuurlik hooflettergevoelig gemaak. As jy -i gebruik, skryf dan in die lêer aan die einde:

GO
EXIT

As daar geen UITGANG aan die einde is nie, sal sqlcmd voortgaan om te wag vir invoer, en as voor EXIT sal nie GO, dan sal die laaste opdrag nie werk nie. Die uitvoerlêer bevat al die afvoer, keuses, boodskappe, druk, ens.

Invoke-SqlCmd produseer die resultaat as 'n DataSet, DataTables of DataRows. Daarom, as jy die resultaat van 'n eenvoudige seleksie verwerk, kan jy dit gebruik sqlcmd, nadat die uitset daarvan ontleed is, is dit byna onmoontlik om iets kompleks af te lei: hiervoor is daar Roep-SqlCmd. Maar hierdie span het ook sy eie grappies:

  • As jy 'n lêer na haar oordra via - Invoerlêer, Dan EXIT nie nodig nie, bowendien produseer dit 'n sintaksfout
  • -Uitvoerlêer nee, die opdrag gee vir jou die resultaat as 'n voorwerp terug
  • Daar is twee sintakse om 'n bediener te spesifiseer: -ServerInstance -Gebruikernaam -Wagwoord -Databasis en deur -ConnectionString. Vreemd genoeg is dit in die eerste geval nie moontlik om 'n ander poort as 1433 te spesifiseer nie.
  • teksuitvoer, tik PRINT, wat eenvoudig "gevang" word sqlcmdvir Roep-SqlCmd is 'n probleem
  • En die belangrikste: Heel waarskynlik het jou Linux nie hierdie cmdlet nie!

En dit is die hoofprobleem. Eers in Maart hierdie cmdlet beskikbaar geword het vir nie-Windows-platforms, en uiteindelik kan ons vorentoe beweeg!

Veranderlike vervanging

sqlcmd het veranderlike vervanging deur -v te gebruik, byvoorbeeld soos volg:

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

In die SQL script gebruik ons ​​vervangings:

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

So hier is dit. In *nix veranderlike vervangings werk nie... Parameter -v geïgnoreer. U Roep-SqlCmd geïgnoreer -Veranderlikes. Alhoewel die parameter wat die veranderlikes self spesifiseer geïgnoreer word, werk die vervangings self - jy kan enige veranderlikes van Shell gebruik. Ek was egter beledig deur die veranderlikes en het besluit om glad nie daarvan afhanklik te wees nie, en het onbeskof en primitief opgetree, aangesien die SQL-skrifte kort is:

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

Dit, soos u verstaan, is 'n toets wat reeds van die Unix-weergawe af is.

Laai lêers op

In die Windows-weergawe is enige bewerking vergesel van 'n oudit: ons het sqlcmd gehardloop, 'n soort misbruik in die uitvoerlêer ontvang, hierdie lêer aan die ouditplaat geheg. Gelukkig het SQL-bediener op dieselfde bediener as Jenkins gewerk, dit is so iets gedoen:

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

Dus, ons sluk die BCP-lêer heeltemal in en skuif dit in die nvarchar(max)-veld van die oudittabel. Natuurlik het hierdie hele stelsel uitmekaar geval, want in plaas van 'n SQL-bediener het ek RDS gekry, en BULK INSERT werk glad nie via UNC nie as gevolg van 'n poging om 'n eksklusiewe slot op 'n lêer te neem, en met RDS is dit oor die algemeen gedoem van die heel begin. Ek het dus besluit om die stelselontwerp te verander en die oudit reël vir reël te stoor:

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

En skryf so in hierdie tabel:

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() 
  }  

Om inhoud te kies, moet jy volgens ID kies, kies in volgorde n (identiteit).

In die volgende artikel gaan ek in meer besonderhede in oor hoe dit alles met Jenkins in wisselwerking tree.

Bron: will.com

Voeg 'n opmerking