یکی از نیازمندیهای من در برنامه ام این بود که برخی کوئری های 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 تعریف شده اند نیز مورد استفاده قرار گرفته اند.
کد PHP:
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(pDbhstmt);
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(pDbhstmt);
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(pDbhstmt);
}
#endif
// Fall through
case SQL_SUCCESS:
case 
SQL_NO_DATA_FOUND:
case 
SQL_NEED_DATA:
return 
TRUE;
}
return 
FALSE;

استفاده از این کد بسیار ساده است. تنها کافی است که متد CSQLSyntaxValidator::VerifySQL را فراخوانی کنیم. همه آنچه بدان نیاز است ارسال اشاره گر پایگاه داده و عبارت SQL است که سینتکس آن قرار است بررسی شود و همچنین یک متغیر خطا برای تشخیص خطا.

این تابع یک مقدار TRUE یا FALSE برمیگرداند که نشاندهنده صحیح یا غلط بودن کوئری داده شده هستند.
کد PHP:
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