Puna me MS SQL nga Powershell në Linux

Ky artikull është thjesht praktik dhe i kushtohet historisë sime të trishtuar

Duke u përgatitur për Zero Touch PROD për RDS (MS SQL), për të cilin na gumëzhinin të gjithë veshët, bëra një prezantim (POC - Proof Of Concept) i automatizimit: një grup skriptesh powershell. Pas prezantimit, kur duartrokitjet e stuhishme, të zgjatura u shuan, duke u shndërruar në duartrokitje të pandërprera, ata më thanë - e gjithë kjo është e mirë, por vetëm për arsye ideologjike, të gjithë skllevërit tanë Jenkins punojnë në Linux!

A është e mundur kjo? Merrni një DBA kaq të ngrohtë dhe llambë nga Windows dhe ngjiteni në nxehtësinë e powershell nën Linux? A nuk është kjo mizore?

Puna me MS SQL nga Powershell në Linux
Më duhej të zhytesha në këtë kombinim të çuditshëm teknologjish. Sigurisht, të gjitha 30+ skenarët e mi nuk funksionuan. Për habinë time, arrita të rregulloja gjithçka brenda një dite pune. Unë jam duke shkruar në ndjekje të nxehtë. Pra, çfarë grackash mund të hasni kur transferoni skriptet powershell nga Windows në Linux?

sqlcmd vs Invoke-SqlCmd

Më lejoni t'ju kujtoj ndryshimin kryesor midis tyre. Shërbimi i vjetër i mirë sqlcmd Ai gjithashtu funksionon nën Linux, me funksionalitet pothuajse identik. Ne e kalojmë pyetjen për të ekzekutuar -Q, skedarin hyrës si -i dhe daljen si -o. Por emrat e skedarëve, natyrisht, janë të ndjeshëm ndaj rasteve. Nëse përdorni -i, atëherë në skedar shkruani në fund:

GO
EXIT

Nëse nuk ka EXIT në fund, atëherë sqlcmd do të vazhdojë të presë për hyrje, dhe nëse më parë EXIT nuk do GO, atëherë komanda e fundit nuk do të funksionojë. Skedari i daljes përmban të gjitha rezultatet, zgjedhjet, mesazhet, printimet, etj.

Invoke-SqlCmd prodhon rezultatin si DataSet, DataTables ose DataRows. Prandaj, nëse përpunoni rezultatin e një përzgjedhjeje të thjeshtë, mund ta përdorni sqlcmd, pasi të keni analizuar prodhimin e tij, është pothuajse e pamundur të nxirret diçka komplekse: për këtë ekziston Invoke-SqlCmd. Por kjo skuadër ka edhe batutat e veta:

  • Nëse i transferoni asaj një skedar nëpërmjet -InputFile, Pastaj EXIT nuk nevojitet, për më tepër, prodhon një gabim sintaksor
  • -OutputFile jo, komanda ju kthen rezultatin si objekt
  • Ekzistojnë dy sintaksa për të specifikuar një server: -ServerInstance -Emri i përdoruesit -Fjalëkalimi -Baza e të dhënave dhe përmes -ConnectionString. Mjaft e çuditshme, në rastin e parë nuk është e mundur të specifikohet një port tjetër përveç 1433.
  • prodhimi i tekstit, shkruani PRINT, i cili thjesht "kapet" sqlcmdpër Invoke-SqlCmd është një problem
  • Dhe më e rëndësishmja: Me shumë mundësi Linux-i juaj nuk e ka këtë cmdlet!

Dhe ky është problemi kryesor. Vetëm në mars kjo cmdlet u bë i disponueshëm për platformat jo-Windows, dhe më në fund mund të ecim përpara!

Zëvendësimi i variablave

sqlcmd ka zëvendësim të ndryshoreve duke përdorur -v, për shembull si kjo:

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

Në skriptin SQL ne përdorim zëvendësime:

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

Pra ja ku është. Në *nix Zëvendësimet e variablave nuk funksionojnë... Parametri -v injoruar. U Invoke-SqlCmd injoruar -Ndryshoret. Megjithëse parametri që specifikon vetë variablat injorohet, vetë zëvendësimet funksionojnë—mund të përdorni çdo variabël nga Shell. Sidoqoftë, unë u ofendova nga variablat dhe vendosa të mos varem fare prej tyre dhe veprova në mënyrë të vrazhdë dhe primitive, pasi skriptet SQL janë të shkurtra:

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

Ky, siç e kuptoni, është një test tashmë nga versioni Unix.

Ngarkimi i skedarëve

Në versionin e Windows, çdo operacion u shoqërua me një auditim: ne ekzekutuam sqlcmd, morëm një lloj abuzimi në skedarin e daljes, bashkëngjitëm këtë skedar në pllakën e kontrollit. Për fat të mirë, serveri SQL punoi në të njëjtin server si Jenkins, u bë diçka si kjo:

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

Kështu, ne e gëlltisim plotësisht skedarin BCP dhe e futim atë në fushën nvarchar(max) të tabelës së auditimit. Sigurisht, i gjithë ky sistem u shpërbë, sepse në vend të një serveri SQL mora RDS, dhe BULK INSERT nuk funksionon fare përmes UNC për shkak të një përpjekjeje për të marrë një bllokim ekskluziv në një skedar, dhe me RDS kjo përgjithësisht është e dënuar nga vetë fillimi. Kështu që vendosa të ndryshoj modelin e sistemit, duke ruajtur auditimin rresht pas rreshti:

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

Dhe shkruani në këtë tabelë si kjo:

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

Për të zgjedhur përmbajtjen, duhet të zgjidhni sipas ID-së, duke zgjedhur me radhë n (identitet).

Në artikullin tjetër do të hyj në më shumë detaje se si ndërvepron e gjithë kjo me Jenkins.

Burimi: www.habr.com

Shto një koment