یکی از نیازمندیهای من در برنامه ام این بود که برخی کوئری های SQL را درون سیستم ذخیره کنم. این کوئری ها توسط یک کاربر که در زمینه SQL مهرات داشت ایجاد میشد و سیستم ما بعدا این کوئری ها را برای انجام برخی امور اجرا میکرد.
بنابراین یک مساله بسیار مهم این بود که بتوانیم سینتکس این کوئری ها را اعتبار سنجی کنیم و در صورت داشتم خطای نحوی، آن را ه کاربر مذکور اطلاع دهیم. شاید تصور شود که میتوان به راحتی این کوئری ها را اجرا کنیم و در صورتی که هرگونه Exception رخ داد، به مساله رسیدگی کنیم اما این راه حل بسیار خطرناک است چرا که ممکن است برخی کوئری ها آسیبهای جبران ناپذیری به سیستم وارد کنند.
از سوی دیگر این سیستم نباید کوئری هایی مشکل دار را در مرحله اول بپذیرد و ذخیره کند.
راه دیگر برای حل این مشکل استفاده از ترانس اکشن ها و BeginTrans و RollBack به نظر میرسد. اما از طرفی این کار نیز باعث مصرف شدن برخی منابع نرم افزاری و سخت افزاری میشود و از سوی دیگر میبایست بر روی پایگاه داده ای اجرا گردد که از ترانس اکشن ها پشتیبانی کند.
پس از این که مدتی با Cdatabase و CRecordset در MFC کار کردم یک API به نام SQLPrepare مشاهده کردم که باعث میشد یک عبارت SQL تنها تحلیل نحوی ( Parse ) شده و کامپایل شود اما اجرا نگردد. لذا از این متد میتوان برای بررسی سینتاکس دستورات استفاده کرد. بنابراین تصمیم گرفتم این API را در کلاس CSQLSyntaxValidator کپسوله سازی کنم.
در کد مختصر زیر، یک HSTMT با استفاده از API به نام ::SQLAllocStmt اختصاص داده شده است. سپس مقدار برگشتی API بررسی شده تا خطای رخ داده تشخیص داده شود و در مقدار برگشتی szError ذخیره گردد.
تابع بررسی کننده درست مثل تابع مشابه اش در DBCore.Cpp است و همچنین AFX_SQL_SYNC و AFX_ODBC_CALL که در AFXDB.H تعریف شده اند نیز مورد استفاده قرار گرفته اند.
BOOL CSQLSyntaxValidator::VerifySQL(CDatabase *pDb,CString szSQL,CString &szError)
{
USES_CONVERSION;
szSQL.TrimLeft();
szSQL.TrimRight();
if(szSQL.IsEmpty())
return TRUE;
HSTMT hstmt = SQL_NULL_HSTMT;
ASSERT(pDb->IsOpen());
RETCODE nRetCode;
AFX_SQL_SYNC(::SQLAllocStmt(pDb->m_hdbc, &hstmt));
if (!Check(pDb,hstmt,nRetCode))
{
CDBException e(nRetCode);
e.BuildErrorString(pDb, hstmt);
szError =
e.m_strError;
#ifdef _DEBUG
if (afxTraceFlags & traceDatabase)
TRACE0(e.m_strError);
#endif
}
pDb->OnSetOptions(hstmt);
AFX_ODBC_CALL(::SQLPrepare(hstmt,
(UCHAR*)T2A(szSQL.GetBuffer(szSQL.GetLength())), SQL_NTS));
szSQL.ReleaseBuffer();
if (!Check(pDb,hstmt,nRetCode))
{
CDBException e(nRetCode);
e.BuildErrorString(pDb, hstmt);
szError = e.m_strError;
#ifdef _DEBUG
if (afxTraceFlags & traceDatabase)
TRACE0(e.m_strError);
#endif
return FALSE;
}
return TRUE;
}
BOOL CSQLSyntaxValidator::Check(CDatabase *pDb,HSTMT &hstmt,RETCODE nRetCode)
{
switch (nRetCode)
{
case SQL_SUCCESS_WITH_INFO:
#ifdef _DEBUG
if (afxTraceFlags & traceDatabase)
{
CDBException e(nRetCode);
TRACE0("Warning: ODBC Success With Info, ");
e.BuildErrorString(pDb, hstmt);
}
#endif
// Fall through
case SQL_SUCCESS:
case SQL_NO_DATA_FOUND:
case SQL_NEED_DATA:
return TRUE;
}
return FALSE;
}
استفاده از این کد بسیار ساده است. تنها کافی است که متد CSQLSyntaxValidator::VerifySQL را فراخوانی کنیم. همه آنچه بدان نیاز است ارسال اشاره گر پایگاه داده و عبارت SQL است که سینتکس آن قرار است بررسی شود و همچنین یک متغیر خطا برای تشخیص خطا.
این تابع یک مقدار TRUE یا FALSE برمیگرداند که نشاندهنده صحیح یا غلط بودن کوئری داده شده هستند.
try
{
CDatabase db;
if(db.OpenEx(""))
{
CString szSQL,szError;
szSQL = _T("Select x from y");
if(!CSQLSyntaxValidator::VerifySQL(&db,szSQL,szError))
{
//Give Error Message
AfxMessageBox("Failed");
AfxMessageBox("szError");
}
else
{
AfxMessageBox("Success");
}
}
else
AfxMessageBox("DB Not Opened");
}
catch(CDBException *dbe)
{
dbe->ReportError();
dbe->Delete();
}
منبع: codeproject.com
مترجم : علیرضا شیرازی