[ भाग 1 | भाग 2 | भाग 3 ]
अपनी पिछली पोस्ट में, मैंने दिखाया था कि TSqlParser
. का उपयोग कैसे किया जाता है और TSqlFragmentVisitor
संग्रहीत कार्यविधि परिभाषाओं वाली T-SQL स्क्रिप्ट से महत्वपूर्ण जानकारी निकालने के लिए। उस स्क्रिप्ट के साथ, मैंने कुछ चीजें छोड़ दीं, जैसे कि OUTPUT
. को कैसे पार्स करना है और READONLY
पैरामीटर के लिए कीवर्ड, और एकाधिक ऑब्जेक्ट्स को एक साथ पार्स कैसे करें। आज, मैं एक स्क्रिप्ट प्रदान करना चाहता था जो उन चीजों को संभालती है, भविष्य के कुछ अन्य संवर्द्धन का उल्लेख करती है, और इस काम के लिए मेरे द्वारा बनाए गए GitHub रिपॉजिटरी को साझा करती है।
पहले, मैंने इस तरह के एक साधारण उदाहरण का उपयोग किया था:
CREATE PROCEDURE dbo.procedure1 @param1 AS int = /* comment */ -64 AS PRINT 1; GO
और मेरे द्वारा प्रदान किए गए विज़िटर कोड के साथ, कंसोल का आउटपुट था:
========================प्रक्रिया संदर्भ
=========================
dbo.procedure1
=========================
प्रक्रिया पैरामीटर
==========================
परम नाम:@ param1
परम प्रकार:int
डिफ़ॉल्ट:-64
अब, क्या होगा यदि स्क्रिप्ट पास हुई और इस तरह दिखे? यह पहले से जानबूझकर भयानक प्रक्रिया परिभाषा को कुछ अन्य तत्वों के साथ जोड़ती है जिनसे आप समस्या पैदा करने की उम्मीद कर सकते हैं, जैसे उपयोगकर्ता-परिभाषित प्रकार के नाम, OUT
के दो अलग-अलग रूप /OUTPUT
कीवर्ड, पैरामीटर मानों में यूनिकोड (और पैरामीटर नामों में!), कीवर्ड स्थिरांक के रूप में, और ODBC शाब्दिक से बच जाते हैं।
/* AS BEGIN , @a int = 7, comments can appear anywhere */ CREATE PROCEDURE dbo.some_procedure -- AS BEGIN, @a int = 7 'blat' AS = /* AS BEGIN, @a int = 7 'blat' AS = -- */ @a AS /* comment here because -- chaos */ int = 5, @b AS varchar(64) = 'AS = /* BEGIN @a, int = 7 */ ''blat''', @c AS int = -- 12 6 AS -- @d int = 72, DECLARE @e int = 5; SET @e = 6; GO CREATE PROCEDURE [dbo].another_procedure ( @p1 AS [int] = /* 1 */ 1, @p2 datetime = getdate OUTPUT,-- comment, @p3 date = {ts '2020-02-01 13:12:49'}, @p4 dbo.tabletype READONLY, @p5 geography OUT, @p6 sysname = N'学中' ) AS SELECT 5
पिछली स्क्रिप्ट कई वस्तुओं को सही ढंग से संभाल नहीं पाती है, और हमें OUTPUT
के लिए खाते में कुछ तार्किक तत्वों को जोड़ने की आवश्यकता है और READONLY
. विशेष रूप से, OUTPUT
और ReadOnly
टोकन प्रकार नहीं हैं, बल्कि उन्हें Identifier
. के रूप में पहचाना जाता है . इसलिए हमें किसी भी ProcedureParameter
. के भीतर उन स्पष्ट नामों वाले पहचानकर्ताओं को खोजने के लिए कुछ अतिरिक्त तर्क की आवश्यकता है टुकड़ा। आप कुछ अन्य छोटे परिवर्तन देख सकते हैं:
Add-Type -Path "Microsoft.SqlServer.TransactSql.ScriptDom.dll"; $parser = [Microsoft.SqlServer.TransactSql.ScriptDom.TSql150Parser]($true)::New(); $errors = [System.Collections.Generic.List[Microsoft.SqlServer.TransactSql.ScriptDom.ParseError]]::New(); $procedure = @" /* AS BEGIN , @a int = 7, comments can appear anywhere */ CREATE PROCEDURE dbo.some_procedure -- AS BEGIN, @a int = 7 'blat' AS = /* AS BEGIN, @a int = 7 'blat' AS = -- */ @a AS /* comment here because -- chaos */ int = 5, @b AS varchar(64) = 'AS = /* BEGIN @a, int = 7 */ ''blat''', @c AS int = -- 12 6 AS -- @d int = 72, DECLARE @e int = 5; SET @e = 6; GO CREATE PROCEDURE [dbo].another_procedure ( @p1 AS [int] = /* 1 */ 1, @p2 datetime = getdate OUTPUT,-- comment, @p3 date = {ts '2020-02-01 13:12:49'}, @p4 dbo.tabletype READONLY, @p5 geography OUT, @p6 sysname = N'学中' ) AS SELECT 5 "@ $fragment = $parser.Parse([System.IO.StringReader]::New($procedure), [ref]$errors); $visitor = [Visitor]::New(); $fragment.Accept($visitor); class Visitor: Microsoft.SqlServer.TransactSql.ScriptDom.TSqlFragmentVisitor { [void]Visit ([Microsoft.SqlServer.TransactSql.ScriptDom.TSqlFragment] $fragment) { $fragmentType = $fragment.GetType().Name; if ($fragmentType -in ("ProcedureParameter", "ProcedureReference")) { if ($fragmentType -eq "ProcedureReference") { Write-Host "`n=========================="; Write-Host " $($fragmentType)"; Write-Host "=========================="; } $output = ""; $param = ""; $type = ""; $default = ""; $extra = ""; $isReadOnly = $false; $isOutput = $false; $seenEquals = $false; for ($i = $fragment.FirstTokenIndex; $i -le $fragment.LastTokenIndex; $i++) { $token = $fragment.ScriptTokenStream[$i]; if ($token.TokenType -notin ("MultiLineComment", "SingleLineComment", "As")) { if ($fragmentType -eq "ProcedureParameter") { if ($token.TokenType -eq "Identifier" -and ($token.Text.ToUpper -in ("OUT", "OUTPUT", "READONLY")) { $extra = $token.Text.ToUpper(); if ($extra -eq "READONLY") { $isReadOnly = $true; } else { $isOutput = $true; } } if (!$seenEquals) { if ($token.TokenType -eq "EqualsSign") { $seenEquals = $true; } else { if ($token.TokenType -eq "Variable") { $param += $token.Text; } else { if (!$isOutput -and !$isReadOnly) { $type += $token.Text; } } } } else { if ($token.TokenType -ne "EqualsSign" -and !$isOutput -and !$isReadOnly) { $default += $token.Text; } } } else { $output += $token.Text.Trim(); } } } if ($param.Length -gt 0) { $output = "`nParam name: " + $param.Trim(); } if ($type.Length -gt 0) { $type = "`nParam type: " + $type.Trim(); } if ($default.Length -gt 0) { $default = "`nDefault: " + $default.TrimStart(); } if ($isReadOnly) { $extra = "`nRead Only: yes"; } if ($isOutput) { $extra = "`nOutput: yes"; } Write-Host $output $type $default $extra; } } }
यह कोड केवल प्रदर्शन उद्देश्यों के लिए है, और शून्य संभावना है कि यह सबसे वर्तमान है। कृपया अधिक नवीनतम संस्करण डाउनलोड करने के बारे में विवरण नीचे देखें।
इस मामले में आउटपुट:
========================प्रक्रिया संदर्भ
=========================
dbo.some_procedure
परम नाम:@a
परम प्रकार:int
डिफ़ॉल्ट:5
परम नाम:@b
परम प्रकार:varchar(64)
डिफ़ॉल्ट:'AS =/* BEGIN @a, int =7 */ "blat"'
परम नाम:@c
परम प्रकार:int
डिफ़ॉल्ट:6
========================
प्रक्रिया संदर्भ
==========================
[डीबीओ]। अन्य_प्रक्रिया
परम नाम:@p1
परम प्रकार:[int]
डिफ़ॉल्ट:1
परम नाम:@p2
परम प्रकार:डेटाटाइम
डिफ़ॉल्ट:getdate
आउटपुट:हाँ
परम नाम:@p3
परम प्रकार:दिनांक
डिफ़ॉल्ट:{ts '2020-02-01 13:12:49'}
परम नाम:@p4
परम प्रकार:dbo.tabletype
केवल पढ़ने के लिए:हाँ
परम नाम:@p5
परम प्रकार:भूगोल
आउटपुट:हाँ
परम नाम:@p6
परम प्रकार:sysname
डिफ़ॉल्ट:N'学中'
यह कुछ बहुत शक्तिशाली पार्सिंग है, भले ही कुछ थकाऊ किनारे के मामले और बहुत सारे सशर्त तर्क हैं। मुझे TSqlFragmentVisitor
देखना अच्छा लगेगा विस्तारित इसलिए इसके कुछ टोकन प्रकारों में अतिरिक्त गुण हैं (जैसे SchemaObjectName.IsFirstAppearance
और ProcedureParameter.DefaultValue
), और जोड़े गए नए टोकन प्रकार देखें (जैसे FunctionReference
) लेकिन अब भी, यह आपके द्वारा किसी भी . में लिखने वाले क्रूर बल पार्सर से परे प्रकाश वर्ष है भाषा, कोई बात नहीं टी-एसक्यूएल।
अभी भी कुछ सीमाएँ हैं जिन्हें मैंने अभी तक संबोधित नहीं किया है, हालाँकि:
- यह केवल संग्रहीत कार्यविधियों को संबोधित करता है। सभी तीन प्रकार के उपयोगकर्ता-परिभाषित कार्यों को संभालने के लिए कोड समान है , लेकिन कोई आसान
FunctionReference
नहीं है खंड प्रकार, इसलिए इसके बजाय आपको पहलेSchemaObjectName
. की पहचान करने की आवश्यकता है टुकड़ा (याIdentifier
. का पहला सेट औरDot
टोकन) और किसी भी बाद के उदाहरणों को अनदेखा करें। वर्तमान में इस पोस्ट में कोड करेगा पैरामीटर . के बारे में सारी जानकारी लौटाएं एक समारोह के लिए, लेकिन यह नहीं फ़ंक्शन का नाम . लौटाएं . इसे केवल संग्रहित प्रक्रियाओं वाले सिंगलटन या बैच के लिए उपयोग करने के लिए स्वतंत्र महसूस करें, लेकिन आपको कई मिश्रित ऑब्जेक्ट प्रकारों के लिए भ्रमित करने वाला आउटपुट मिल सकता है। नीचे दिए गए भंडार में नवीनतम संस्करण पूरी तरह से ठीक कार्यों को संभालता है। - यह कोड राज्य को नहीं बचाता है। प्रत्येक विज़िट के भीतर कंसोल पर आउटपुट करना आसान है, लेकिन कई विज़िट से डेटा एकत्र करना, फिर कहीं और पाइपलाइन करना, थोड़ा अधिक जटिल है, मुख्यतः विज़िटर पैटर्न के काम करने के तरीके के कारण।
- उपरोक्त कोड सीधे इनपुट स्वीकार नहीं कर सकता। यहां प्रदर्शन को आसान बनाने के लिए, यह सिर्फ एक कच्ची स्क्रिप्ट है जहां आप अपने टी-एसक्यूएल ब्लॉक को स्थिरांक के रूप में चिपकाते हैं। अंतिम लक्ष्य एक फ़ाइल से इनपुट का समर्थन करना, फाइलों की एक सरणी, एक फ़ोल्डर, फ़ोल्डरों की एक सरणी, या डेटाबेस से मॉड्यूल परिभाषाओं को खींचना है। और आउटपुट कहीं भी हो सकता है:कंसोल में, फ़ाइल में, डेटाबेस में ... तो आकाश की सीमा है। उस दौरान कुछ काम हुआ है, लेकिन उसमें से कोई भी उस साधारण संस्करण में नहीं लिखा गया है जिसे आप ऊपर देख रहे हैं।
- कोई त्रुटि प्रबंधन नहीं है। फिर से, संक्षिप्तता और उपभोग में आसानी के लिए, यहां कोड अपरिहार्य अपवादों को संभालने के बारे में चिंता नहीं करता है, हालांकि सबसे विनाशकारी चीज जो इसके वर्तमान स्वरूप में हो सकती है, वह यह है कि यदि यह ठीक से नहीं हो सकता है तो एक बैच आउटपुट में दिखाई नहीं देगा। पार्स किया गया (जैसे
CREATE STUPID PROCEDURE dbo.whatever
) जब हम डेटाबेस और/या फाइल सिस्टम का उपयोग करना शुरू करते हैं, तो उचित त्रुटि प्रबंधन और भी महत्वपूर्ण हो जाएगा।
आप सोच रहे होंगे कि मैं इस पर चल रहे काम को कहां रखूंगा और इन सभी चीजों को ठीक करूंगा? खैर, मैंने इसे गिटहब पर रखा है, अस्थायी रूप से प्रोजेक्ट को ParamParser . कहा है , और पहले से ही सुधार में मदद करने वाले योगदानकर्ता हैं। कोड का वर्तमान संस्करण पहले से ही ऊपर के नमूने से काफी अलग दिखता है, और जब तक आप इसे पढ़ते हैं, तब तक यहां बताई गई कुछ सीमाओं को पहले ही संबोधित किया जा सकता है। मैं केवल एक ही स्थान पर कोड बनाए रखना चाहता हूं; यह टिप एक न्यूनतम नमूना दिखाने के बारे में है कि यह कैसे काम कर सकता है, और इस बात पर प्रकाश डाला गया है कि इस कार्य को सरल बनाने के लिए समर्पित एक परियोजना है।
अगले खंड में, मैं इस बारे में और बात करूंगा कि मेरे मित्र और सहयोगी, विल व्हाइट ने आपको ऊपर दिखाई देने वाली स्टैंडअलोन स्क्रिप्ट से गिटहब पर मिलने वाले अधिक शक्तिशाली मॉड्यूल तक पहुंचने में कैसे मदद की।
यदि आपको इस बीच पैरामीटर से डिफ़ॉल्ट मानों को पार्स करने की आवश्यकता है, तो बेझिझक कोड डाउनलोड करें और इसे आज़माएं। और जैसा कि मैंने पहले सुझाव दिया था, अपने दम पर प्रयोग करें, क्योंकि इन कक्षाओं और विज़िटर पैटर्न के साथ आप बहुत सी अन्य शक्तिशाली चीजें कर सकते हैं।
[ भाग 1 | भाग 2 | भाग 3 ]