הבלוג של גרי רשף

13/01/2011

הודעות שגיאה: השימוש ב-RaisError וב-Throw

Filed under: Uncategorized — תגיות: , , , , , — גרי רשף @ 20:41

הקדמה:

לקבל הודעות שגיאה לא מסובך, והמערכת "מפנקת" אותנו בכאלו על ימין ועל שמאל: בעיות בחומרה, בעיות בתוכנה, באגים של מיקרוסופט, באגים שלנו, שימוש שגוי על ידי המשתמשים.. השימוש ב-RaisError והחל מהגרסה הבאה – גם ב-Throw – מאפשר למתכנת ה-SQL ליזום הודעות שגיאה בעצמו. במה דברים אמורים?

נניח שבנינו טבלה, הגדרנו לה מפתח ראשי, וכעת אם מישהו מנסה להכניס בכל דרך שהיא מפתח קיים לטבלה- הוא מקבל הודעת שגיאה. באופן דומה ניתן להגדיר Rules ו-Constraints מסוגים שונים לשמירה על איכות הנתונים ברמת הטבלה.

לעיתים נרצה להגן על הנתונים באופן פרואקטיבי ולבדוק אותם לפני הכנסתם לטבלה, כדי למנוע מהמערכת להקפיץ הודעות שגיאה שאינן בשליטתנו, ואת זה נעשה במסגרת הקוד המטפל בקליטת הנתונים. כאשר הקוד הוא ב-TSQL נבדוק את תקינות הנתונים, ואם יש בעייה – נקפיץ הודעת שגיאה מתוך הקוד תוך שאנו שולטים בנוסח ההודעה ובקוד השגיאה המוחזר למשתמש, בחומרת השגיאה שאנחנו יוצרים, ובהתנהגות המערכת בעקבות כך.

השימוש ב-RaisError

נפתח בדוגמה לשימוש באופרטור זה-

RaisError('נסיון 1',1,1);

Go


RaisError('נסיון 2',16,1);

Go

clip_image002

כפי שאפשר להבחין- הפקודה הראשונה יצרה שגיאה Level 1 (=Severity) בצבע שחור,

והפקודה שניה יצרה שגיאה Level 16 (=Severity) בצבע אדום.

מה ההבדל חוץ מאשר צבע ההודעה? כאשר ה-Severity הוא בתחום 0-10 זו הודעה אינפורמטיבית שאינה מפריעה לריצת הקוד,

וכאשר ה-Severity הוא בתחום 11-18 נוצרת שגיאה.

נוכל לראות זאת כשנשתמש במנגנון Try & Catch:

Begin Try

Print 'התחלה';

RaisError('נסיון',1,1);

Print 'סיום תקין';

End Try


Begin Catch

Print IsNull(Object_Name(@@ProcID),'פרוצדורה לא ידועה')+': Error_Procedure()='+IsNull(Error_Procedure(),'פרוצדורה לא ידועה')+', Error_Line()='+Cast(Error_Line() As Varchar)+', Error_Message()='+Cast(Error_Message() As Varchar)+', Error_Severity()='+Cast(Error_Severity() As Varchar);

Print 'סיום שגוי'

End Catch

clip_image004

Begin Try

Print 'התחלה';

RaisError('נסיון',16,1);

Print 'סיום תקין';

End Try


Begin Catch

Print IsNull(Object_Name(@@ProcID),'פרוצדורה לא ידועה')+': Error_Procedure()='+IsNull(Error_Procedure(),'פרוצדורה לא ידועה')+', Error_Line()='+Cast(Error_Line() As Varchar)+', Error_Message()='+Cast(Error_Message() As Varchar)+', Error_Severity()='+Cast(Error_Severity() As Varchar);

Print 'סיום שגוי'

End Catch

clip_image006

במקרה הראשון הופיעה ההודעה והריצה המשיכה והסתיימה באופן תקין,

ובמקרה השני ה-RaisError יצר שגיאה שהעבירה את המשך הריצה לבלוק ה-Catch, שם הודפסה הודעה על תוכן ואופי השגיאה (זו פקודה שאני משתמש בה לצרכי דיבוג וכשהיא מופעלת מתוך פרוצדורה אני יכול לדעת איזו פרוצדורה הדפיסה את ההודעה, איזו פרוצדורה יצרה את השגיאה, באיזו שורה הבעייה ועוד).

כאשר ה-Severity הוא בתחום 19-25 זו שגיאה חמורה, ה-Connection מתנתק, ויש צורך להשתמש באופרטור With Log כדי שהשגיאה החמורה תיכתב ללוג השגיאות:

Print 'התחלה';

RaisError('נסיון',24,1) With Log;

Print 'סיום';

clip_image008

clip_image010

הריצה נעצרה כי ה-Connection התנתק ולכן לא הופיעה ההודעה 'סיום',

ונכתבה הודעת שגיאה ללוג (לצד הודעות שגיאה נוספות מנסיונות שערכתי במהלך כתיבת הפוסט..).

לו הייתי משתמש כאן במנגנון Try & Catch – ה-Connection לא היה מתנתק והריצה הייתה ממשיכה ב-Catch.

עד כאן הודעות השגיאה נכתבו Hard coded בפקודת ה-RaisError עצמה. ניתן לנהל באופן עצמאי טבלת הודעות שגיאה (למשל- כדי להקפיד על נוסח מוסכם או לאפשר שינוי נוסח ההודעות מבלי להיכנס לקוד), ואפשר להשתמש בטבלת הודעות השגיאה של המערכת:

Select *

From sys.messages

Order By message_id;

Go

clip_image012

כפי שאפשר לראות- כל הודעה נשמרת בשפות שונות וכך הן מתפרסמות בהתאם לשפת ה-Session.

ההודעות שמספרן עד 13,000 משמשות את המערכת, ומשם ואילך (למעט 50,000) הן לרשות המשתמשים, למשל כך:

RaisError(19129,1,1);

Go

clip_image014

או כך:

RaisError(14524,1,1,'Me','You');

Go

clip_image016

במקרה השני החלפנו בהודעת המערכת מס 14524 " Supply either %s or %s." את %s הראשון ב-Me ואת %s השני ב-You.

את טבלת ההודעות ניתן לעדכן בהודעות חדשות:

EXEC sp_AddMessage 50001,1,'פריט %s אינו קיים';

Go


RaisError(50001,1,1,'אבקת חשמל');

Go

clip_image018

וכמובן למחוק הודעות מיותרות:

sp_DropMessage 50001;

Go

כדאי לציין שהפרמטר השלישי בהודעות RaisError (State) הוא לנוחות המתכנת והוא יכול להציב שם ערכים שונים בתחום 1-255 ולתת להם משמעות לפי צרכיו.

מה קורה אם משתמשים בקוד שגיאה שאינו קיים? תקלה אופיינית להעברה לא מסודרת בין סביבות כשאת קוד ה-SQL מעבירים אבל לא מעדכנים כנדרש את טבלת sys.messages: תקלה זו עצמה תיצור שגיאה – ולא זו שהתכוונו ליצור בעצמנו באופן מבוקר, אלא אם כן זה קרה בתוך בלוק Try ואז המערכת תתנהג כאילו קוד השגיאה קיים:

Print '10';

RaisError(13005,1,1);

Print '20';


Begin Try

Print '110';

RaisError(13005,1,1);

Print '120';

RaisError(13005,16,1);

Print '130';

End Try


Begin Catch

Print IsNull(Object_Name(@@ProcID),'פרוצדורה לא ידועה')+': Error_Procedure()='+IsNull(Error_Procedure(),'פרוצדורה לא ידועה')+', Error_Line()='+Cast(Error_Line() As Varchar)+', Error_Message()='+Cast(Error_Message() As Varchar)+', Error_Severity()='+Cast(Error_Severity() As Varchar);

Print '999'

End Catch

clip_image020

קוד 13005 אינו קיים במערכת (הקוד הורץ בגרסת 2008, בגרסת Denali שתוזכר בהמשך יש קוד כזה ויש להחליפו בדוגמה ב-13007). ההפעלה הראשונה שלו – בין 10 ל-20 – יצרה שגיאה 18054.

לעומת זאת ההפעלה שלו ב-Severity=1 בתוך בלוק ה-Try לא יצרה שגיאה או חיווי כלשהו,

וההפעלה שלו ב-Severity=16 בתוך בלוק ה-Try העבירה את המשך הריצה לבלוק ה-Catch עם מספר שגיאה 13005;

כלומר- בתוך בלוק ה-Try המערכת מתעלמת מהעובדה שהקוד אינו קיים ומטפלת בו בהתאם ל-Severity שלו.

השימוש ב-Throw

החל מגרסת SQL Server הבאה (שם קוד Denali לגרסת ה-CTP) מתווספת אפשרות נוספת- האופרטור Throw שמתנהג בצורה שונה מ-RaisError. אופרטור זה אינו נעזר בטבלת Sys.messages (הוא חסין מפני תקלות בהעברה בין סביבות) ולכן יש לציין קוד שגיאה (מ-50,000 ומעלה), טקסט שגיאה, ו-State.

ה-Severity תמיד יהיה 16, והשימוש באופרטור רק במסגרת Try & Catch:

Begin Try

Print '110';

Throw 50000,'שגיאה',1;

Print '120';

End Try


Begin Catch

Print IsNull(Object_Name(@@ProcID),'פרוצדורה לא ידועה')+': Error_Procedure()='+IsNull(Error_Procedure(),'פרוצדורה לא ידועה')+', Error_Line()='+Cast(Error_Line() As Varchar)+', Error_Message()='+Cast(Error_Message() As Varchar)+', Error_Severity()='+Cast(Error_Severity() As Varchar);

Print '999';

End Catch

clip_image022

הופיעה הודעת שגיאה שאינה קיימת בטבלת הודעות השגיאה (ניתן להציג באופן דומה גם את את קוד השגיאה – 50000).

יכולת נוספת של האופרטור Throw- מתוך בלוק ה-Catch ניתן לשחזר את הודעת השגיאה המקורית, כלומר- אם המערכת הפעילה שגיאה – ניתן לטפל בה בקוד ה-Catch ולאחר מכן להציג אותה עבור המשתמש.

בדוגמה שלהלן אני יוזם שגיאה ומנסה להפוך תו X למספר:

Begin Try

Print '110';

Select Cast('X' As Int);

Print '120';

End Try


Begin Catch

Print IsNull(Object_Name(@@ProcID),'פרוצדורה לא ידועה')+': Error_Procedure()='+IsNull(Error_Procedure(),'פרוצדורה לא ידועה')+', Error_Line()='+Cast(Error_Line() As Varchar)+', Error_Message()='+Cast(Error_Message() As Varchar)+', Error_Severity()='+Cast(Error_Severity() As Varchar);

Print '999';

Throw;

End Catch

clip_image024

כשהשגיאה נוצרה – המשך הריצה עבר לבלוק ה-Catch, ולאחר שבוצעה מניפולציה כלשהי – משתמשים ב-Throw להצגת הודעת השגיאה והחזרת קוד השגיאה המקוריים.

מודעות פרסומת

2 תגובות »

  1. גרי, השאלה האם המתכנת, מצד הקוד לדוגמא -NET, ייקבל את השגיאה הזו!
    מבדיקות שעשיתי (עד SQL2008 כולל), כשאתה מבצע RAISERROR יזום על ידך, אתה לא מקבל את השגיאה הזו חזרה אליך לקוד כמתכנת – משמע אתה לא מודע שהיתה שגיאה והוצאת RAISEROR, וזאת לעומת שגיאה שה SQL זורק ואתה כן רואה אותה בצד הקוד.
    הבעיה לכך נבעה ככל הנראה, בגלל שאין מיפוי ברמת ה DLL לשגיאות "שהמצאת לבד" מעל 50000. ידוע לך האם ה THROW ייאפשר זאת בדרך כלשהי (למרות שזה נראה לי בעייתי אם אין לך מיפוי כזה ברמת ה DLL שמבצע את הקישור בין NET לדוגמא ל SQLSERVER).

    תגובה של פלג — 15/01/2011 @ 10:26

    • שמע- אין לי מושג.. אני מפתח ב-TSQL ולא בשפת תכנות.
      הייתי בטוח עד כה שהודעת השגיאה של ה-RaisError היא מה שהמשתמש רואה..
      אנסה לבדוק את זה מתוך סקרנות.

      תגובה של גרי רשף — 15/01/2011 @ 19:38


RSS feed for comments on this post. TrackBack URI

להשאיר תגובה

הזינו את פרטיכם בטופס, או לחצו על אחד מהאייקונים כדי להשתמש בחשבון קיים:

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s

יצירה של אתר חינמי או בלוג ב־WordPress.com.

%d בלוגרים אהבו את זה: