Εργασία με MS SQL από το Powershell σε Linux

Αυτό το άρθρο είναι καθαρά πρακτικό και είναι αφιερωμένο στη θλιβερή μου ιστορία

Προετοιμασία για Zero Touch PROD για το RDS (MS SQL), για το οποίο βούιζαν όλα τα αυτιά μας, έκανα μια παρουσίαση (POC - Proof Of Concept) αυτοματισμού: ένα σύνολο σεναρίων powershell. Μετά την παρουσίαση, όταν το θυελλώδες, παρατεταμένο χειροκρότημα κόπηκε, μετατράπηκε σε αδιάκοπο χειροκρότημα, μου είπαν - όλα αυτά είναι καλά, αλλά μόνο για ιδεολογικούς λόγους, όλοι οι δούλοι μας Jenkins δουλεύουν στο Linux!

Είναι δυνατόν; Πάρτε ένα τόσο ζεστό, λαμπτήρα DBA από κάτω από τα Windows και κολλήστε το στη ζέστη του powershell κάτω από το Linux; Δεν είναι σκληρό αυτό;

Εργασία με MS SQL από το Powershell σε Linux
Έπρεπε να βυθιστώ σε αυτόν τον περίεργο συνδυασμό τεχνολογιών. Φυσικά, όλα τα 30+ σενάρια μου σταμάτησαν να λειτουργούν. Προς έκπληξή μου, κατάφερα να φτιάξω τα πάντα σε μια εργάσιμη ημέρα. Γράφω σε καταδίωξη. Λοιπόν, ποιες παγίδες μπορείτε να συναντήσετε κατά τη μεταφορά σεναρίων powershell από τα Windows στο Linux;

sqlcmd vs Invoke-SqlCmd

Επιτρέψτε μου να σας υπενθυμίσω την κύρια διαφορά μεταξύ τους. Παλιό καλό βοηθητικό πρόγραμμα sqlcmd Λειτουργεί επίσης υπό Linux, με σχεδόν ίδια λειτουργικότητα. Περνάμε το ερώτημα για να εκτελεστεί -Q, το αρχείο εισόδου ως -i και η έξοδος ως -o. Αλλά τα ονόματα των αρχείων, φυσικά, γίνονται διάκριση πεζών-κεφαλαίων. Εάν χρησιμοποιείτε -i, τότε στο αρχείο γράψτε στο τέλος:

GO
EXIT

Εάν δεν υπάρχει EXIT στο τέλος, τότε το sqlcmd θα προχωρήσει στην αναμονή για είσοδο και αν πριν EXIT δεν θα GO, τότε η τελευταία εντολή δεν θα λειτουργήσει. Το αρχείο εξόδου περιέχει όλα τα αποτελέσματα, τις επιλογές, τα μηνύματα, την εκτύπωση κ.λπ.

Το Invoke-SqlCmd παράγει το αποτέλεσμα ως DataSet, DataTables ή DataRows. Επομένως, εάν επεξεργαστείτε το αποτέλεσμα μιας απλής επιλογής, μπορείτε να χρησιμοποιήσετε sqlcmd, έχοντας αναλύσει την παραγωγή του, είναι σχεδόν αδύνατο να εξαχθεί κάτι πολύπλοκο: γι' αυτό υπάρχει Invoke-SqlCmd. Αλλά αυτή η ομάδα έχει και τα δικά της αστεία:

  • Εάν της μεταφέρετε ένα αρχείο μέσω -InputFile, Στη συνέχεια EXIT δεν χρειάζεται, επιπλέον, παράγει ένα συντακτικό σφάλμα
  • -OutputFile όχι, η εντολή σας επιστρέφει το αποτέλεσμα ως αντικείμενο
  • Υπάρχουν δύο συντάξεις για τον καθορισμό ενός διακομιστή: -ServerInstance -Όνομα χρήστη -Κωδικός πρόσβασης -Βάση δεδομένων και μέσω -Σύμβολο σύνδεσης. Παραδόξως, στην πρώτη περίπτωση δεν είναι δυνατό να καθοριστεί μια θύρα διαφορετική από το 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 αγνόησε. U 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.

Μεταφόρτωση αρχείων

Στην έκδοση των Windows, οποιαδήποτε λειτουργία συνοδεύτηκε από έλεγχο: εκτελέσαμε το sqlcmd, λάβαμε κάποιου είδους κατάχρηση στο αρχείο εξόδου, επισυνάψαμε αυτό το αρχείο στην πινακίδα ελέγχου. Ευτυχώς, ο διακομιστής 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  -- проскипано для статьи
	
  #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() 
  }  

Για να επιλέξετε περιεχόμενο, πρέπει να επιλέξετε με ID, επιλέγοντας με τη σειρά n (ταυτότητα).

Στο επόμενο άρθρο θα μπω σε περισσότερες λεπτομέρειες σχετικά με το πώς όλα αυτά αλληλεπιδρούν με τον Jenkins.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο