العمل مع MS SQL من خلال PowerShell على Linux

هذه المقالة عملية بحتة وهي مخصصة لقصتي الحزينة

يجهز الى منتج زيرو تاتش для RDS (MS SQL), про который нам прожужжали все уши, я сделал презентацию (POC — Proof Of Concept) автоматизации: набора powershell скриптов. После презентации, когда стихли бурные, продолжительные аплодисменты, переходящие в несмолкаемые овации, мне сказали — все это хорошо, но вот только по идеологическим причинам у нас все Jenkins slaves работают под Linux!

Разве так можно? Взять такого теплого, лампового DBA из под Windows и сунуть его в самое пекло powershell под Linux? Разве это не жестоко?

العمل مع MS SQL من خلال PowerShell على Linux
Пришлось погрузиться в эту странную комбинацию технологий. Разумеется, все мои 30+ скриптов перестали работать. К моему удивлению, за один рабочий день мне все удалось исправить. Пишу по горячим следам. Итак, какие подводные камни могут встретиться вам при переносе powershell скриптов из Windows تحت Linux?

sqlcmd مقابل Invoc-SqlCmd

دعني أذكرك بالفرق الرئيسي بينهما. فائدة قديمة جيدة المساعدة Sqlcmd كما أنه يعمل أيضًا ضمن نظام التشغيل Linux، مع وظائف متطابقة تقريبًا. نقوم بتمرير الاستعلام لتنفيذ -Q، وملف الإدخال كـ -i، والإخراج كـ -o. لكن أسماء الملفات، بالطبع، أصبحت حساسة لحالة الأحرف. إذا كنت تستخدم -i، فاكتب في الملف في النهاية:

GO
EXIT

إذا لم يكن هناك خروج في النهاية، فسيستمر sqlcmd في انتظار الإدخال، وإذا كان ذلك قبل ذلك EXIT لن GO، فإن الأمر الأخير لن يعمل. يحتوي ملف الإخراج على جميع المخرجات والاختيارات والرسائل والطباعة وما إلى ذلك.

يُنتج Invoc-SqlCmd النتيجة على هيئة DataSet أو DataTables أو DataRows. لذلك، إذا قمت بمعالجة نتيجة تحديد بسيط، يمكنك استخدامها المساعدة Sqlcmd، بعد تحليل مخرجاته، يكاد يكون من المستحيل استخلاص شيء معقد: لهذا يوجد استدعاء-SqlCmd. لكن هذا الفريق لديه أيضًا نكاته الخاصة:

  • إذا قمت بنقل ملف لها عبر -ملف الإدخالثم EXIT ليست هناك حاجة إليها، وعلاوة على ذلك، فإنه ينتج خطأ في بناء الجملة
  • -ملف إلاخراج لا، يقوم الأمر بإرجاع النتيجة ككائن
  • هناك صيغتان لتحديد الخادم: -ServerInstance -اسم المستخدم -كلمة المرور -قاعدة البيانات ومن خلال -سلسلة الاتصال. ومن الغريب أنه في الحالة الأولى لا يمكن تحديد منفذ آخر غير 1433.
  • إخراج النص، اكتب PRINT، وهو ببساطة "تم التقاطه" المساعدة Sqlcmdإلى استدعاء-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)

حتى هنا هو عليه. في * لا شىء البدائل المتغيرة لا تعمل. معامل -v تم تجاهله. ش استدعاء-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"

هذا، كما تفهم، هو اختبار بالفعل من إصدار يونكس.

تحميل الملفات

في إصدار 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() 
  }  

لتحديد المحتوى، تحتاج إلى التحديد حسب المعرف، واختيار الترتيب n (الهوية).

في المقالة التالية سأتناول المزيد من التفاصيل حول كيفية تفاعل كل هذا مع جينكينز.

المصدر: www.habr.com

شراء استضافة موثوقة للمواقع مع حماية DDoS وخوادم VPS VDS 🔥 اشترِ استضافة مواقع ويب موثوقة مع حماية من هجمات DDoS، وخوادم VPS وVDS | ProHoster