Annotation of sql/odbc/parser3odbc.C, revision 1.27

1.1       parser      1: /** @file
                      2:        Parser ODBC driver.
                      3: 
1.13      paf         4:        Copyright(c) 2001, 2003 ArtLebedev Group (http://www.artlebedev.com)
1.1       parser      5: 
1.5       paf         6:        Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
1.1       parser      7: */
1.27    ! misha       8: static const char *RCSId="$Id: parser3odbc.C,v 1.26 2004/09/13 14:47:43 paf Exp $"; 
1.1       parser      9: 
                     10: #ifndef _MSC_VER
                     11: #      error compile ISAPI module with MSVC [no urge for now to make it autoconf-ed (PAF)]
                     12: #endif
                     13: 
                     14: #include <string.h>
                     15: #include <stdio.h>
                     16: #include <stdlib.h>
1.2       paf        17: #include <setjmp.h>
1.1       parser     18: 
                     19: #include "pa_sql_driver.h"
                     20: 
1.16      paf        21: #define WINVER 0x0400
1.1       parser     22: #include <AFXDB.H>
                     23: 
1.10      paf        24: // defines
                     25: 
                     26: #define MAX_COLS 500
                     27: 
1.1       parser     28: #define MAX_STRING 0x400
1.7       paf        29: #define MAX_NUMBER 40
                     30: 
1.10      paf        31: // new in MSSQL2000, no MFC constants
1.7       paf        32: #ifndef SQL_NVARCHAR
                     33: #define SQL_NVARCHAR (-9)
                     34: #endif
                     35: #ifndef SQL_NTEXT 
                     36: #define SQL_NTEXT (-10)
                     37: #endif
                     38: #ifndef SQL_SMALLDATETIME
                     39: #define SQL_SMALLDATETIME 11
                     40: #endif
                     41: // create table test (id int, a smalldatetime, b ntext, c nvarchar(100))
1.1       parser     42: 
                     43: #define snprintf _snprintf
                     44: #ifndef strncasecmp
                     45: #      define strncasecmp _strnicmp
                     46: #endif
                     47: 
1.27    ! misha      48: static char *lsplit(char *string, char delim){
        !            49:        if(string){
        !            50:                if(char* v=strchr(string, delim)){
1.1       parser     51:                        *v=0;
                     52:                        return v+1;
                     53:                }
1.27    ! misha      54:        }
        !            55:        return 0;
1.1       parser     56: }
                     57: 
1.27    ! misha      58: static void toupper_str(char *out, const char *in, size_t size){
1.20      paf        59:        while(size--)
                     60:                *out++=(char)toupper(*in++);
                     61: }
                     62: 
1.18      paf        63: struct Connection {
                     64:        SQL_Driver_services* services;
                     65: 
                     66:        CDatabase* db;
1.27    ! misha      67:        const char* client_charset;
        !            68:        bool autocommit;
        !            69:        bool use_multi_row_fetch;
1.18      paf        70: };
                     71: 
1.1       parser     72: /**
                     73:        ODBC server driver
                     74: */
                     75: class ODBC_Driver : public SQL_Driver {
                     76: public:
                     77: 
                     78:        ODBC_Driver() : SQL_Driver() {
                     79:        }
                     80: 
                     81:        /// get api version
                     82:        int api_version() { return SQL_DRIVER_API_VERSION; }
1.27    ! misha      83: 
1.3       paf        84:        const char *initialize(char *dlopen_file_spec) { return 0; }
1.1       parser     85:        /**     connect
1.22      paf        86:                @param url
1.1       parser     87:                        format: @b DSN=dsn;UID=user;PWD=password (ODBC connect string)
1.27    ! misha      88:                                ;ClientCharset=charset  // transcode with parser
        !            89:                                ;MultiRowFetch=1        // 0 -- disable (slower)
        !            90:                                ;autocommit=1           // 0 -- disable auto commit
1.1       parser     91:                        WARNING: must be used only to connect, for buffer doesn't live long
                     92:        */
1.27    ! misha      93: 
1.1       parser     94:        void connect(
1.27    ! misha      95:                        char *url, 
        !            96:                        SQL_Driver_services& services, 
        !            97:                        void **connection_ref ///< output: Connection*
        !            98:        ){
        !            99:                Connection& connection=*(Connection *)services.malloc(sizeof(Connection));
1.18      paf       100:                *connection_ref=&connection;
                    101:                connection.services=&services;
1.27    ! misha     102:                connection.client_charset=0;
        !           103:                connection.use_multi_row_fetch=true;
        !           104:                connection.autocommit=true;
        !           105: 
        !           106:                size_t url_length=strlen(url);
1.20      paf       107: 
1.27    ! misha     108:                if(const char* key_start=strstr(url, "ClientCharset=")){
1.20      paf       109:                        const char* value_start=key_start+14/*strlen("ClientCharset=")*/;
                    110:                        const char* value_end=strchr(value_start, ';');
                    111:                        if(!value_end)
1.27    ! misha     112:                                value_end=url+url_length;
        !           113: 
        !           114:                        if(size_t value_length=value_end-value_start){
        !           115:                                char* client_charset=(char*)services.malloc_atomic(value_length+1);
        !           116:                                toupper_str(client_charset, value_start, value_length);
        !           117:                                client_charset[value_length]=0;
        !           118: 
        !           119:                                connection.client_charset=client_charset;
        !           120:                        }
        !           121:                }
        !           122:                if(const char* key_start=strstr(url, "MultiRowFetch=")){
        !           123:                        const char* value_start=key_start+14/*strlen("MultiRowFetch=")*/;
        !           124:                        const char* value_end=strchr(value_start, ';');
        !           125:                        if(!value_end)
        !           126:                                value_end=url+url_length;
1.20      paf       127: 
1.27    ! misha     128:                        if(size_t value_length=value_end-value_start){
        !           129:                                char* value=(char*)services.malloc_atomic(value_length+1);
        !           130:                                memcpy(value, value_start, value_length);
        !           131:                                value[value_length]=0;
        !           132:                                if(atoi(value)==0)
        !           133:                                        connection.use_multi_row_fetch=false;
        !           134:                        }
        !           135:                }
        !           136:                if(const char* key_start=strstr(url, "autocommit=")){
        !           137:                        const char* value_start=key_start+11/*strlen("autocommit=")*/;
        !           138:                        const char* value_end=strchr(value_start, ';');
        !           139:                        if(!value_end)
        !           140:                                value_end=url+url_length;
1.20      paf       141: 
1.27    ! misha     142:                        if(size_t value_length=value_end-value_start){
        !           143:                                char* value=(char*)services.malloc_atomic(value_length+1);
        !           144:                                memcpy(value, value_start, value_length);
        !           145:                                value[value_length]=0;
        !           146:                                if(atoi(value)==0)
        !           147:                                        connection.autocommit=false;
1.20      paf       148:                        }
                    149:                }
1.18      paf       150: 
1.1       parser    151:                TRY {
1.18      paf       152:                        connection.db=new CDatabase();
1.22      paf       153:                        connection.db->OpenEx(url, CDatabase::noOdbcDialog);
1.27    ! misha     154: 
        !           155:                        if(!connection.autocommit)
        !           156:                                connection.db->BeginTrans();
1.1       parser    157:                } 
                    158:                CATCH_ALL (e) {
                    159:                        _throw(services, e);
                    160:                }
                    161:                END_CATCH_ALL
                    162:        }
1.27    ! misha     163: 
        !           164:        void disconnect(void *aconnection){
1.18      paf       165:                Connection& connection=*static_cast<Connection*>(aconnection);
1.1       parser    166:                TRY
1.18      paf       167:                        delete connection.db;
                    168:                        connection.db=0;
1.1       parser    169:                CATCH_ALL (e) {
                    170:                        // nothing
                    171:                }
                    172:                END_CATCH_ALL
                    173:        }
1.27    ! misha     174: 
        !           175:        void commit(void *aconnection){
1.18      paf       176:                Connection& connection=*static_cast<Connection*>(aconnection);
1.27    ! misha     177:                if(!connection.autocommit){
        !           178:                        TRY
        !           179:                                connection.db->CommitTrans();
        !           180:                                connection.db->BeginTrans();
        !           181:                        CATCH_ALL (e) {
        !           182:                                _throw(*connection.services, e);
        !           183:                        }
        !           184:                        END_CATCH_ALL
1.1       parser    185:                }
                    186:        }
1.27    ! misha     187: 
        !           188:        void rollback(void *aconnection){
1.18      paf       189:                Connection& connection=*static_cast<Connection*>(aconnection);
1.27    ! misha     190:                if(!connection.autocommit){
        !           191:                        TRY
        !           192:                                connection.db->Rollback();
        !           193:                                connection.db->BeginTrans();
        !           194:                        CATCH_ALL (e) {
        !           195:                                _throw(*connection.services, e);
        !           196:                        }
        !           197:                        END_CATCH_ALL
1.1       parser    198:                }
                    199:        }
                    200: 
1.27    ! misha     201:        bool ping(void *connection){
1.1       parser    202:                return true;
                    203:        }
                    204: 
1.27    ! misha     205:        const char* quote(void *aconnection, const char *from, unsigned int length){
1.18      paf       206:                Connection& connection=*static_cast<Connection*>(aconnection);
                    207:                char *result=(char*)connection.services->malloc_atomic(length*2+1);
1.14      paf       208:                char *to=result;
1.27    ! misha     209:                while(length--){
1.14      paf       210:                        if(*from=='\'') { // ' -> ''
1.15      paf       211:                                *to++='\'';
1.3       paf       212:                        }
1.14      paf       213:                        *to++=*from++;
                    214:                }
                    215:                *to=0;
                    216:                return result;
1.1       parser    217:        }
1.27    ! misha     218: 
1.18      paf       219:        void query(void *aconnection, 
1.27    ! misha     220:                        const char *statement, 
        !           221:                        size_t placeholders_count,
        !           222:                        Placeholder* placeholders, 
        !           223:                        unsigned long offset,
        !           224:                        unsigned long limit,
        !           225:                        SQL_Driver_query_event_handlers& handlers
        !           226:        ){
1.18      paf       227:                Connection& connection=*static_cast<Connection*>(aconnection);
                    228:                CDatabase *db=connection.db;
                    229:                SQL_Driver_services& services=*connection.services;
1.23      paf       230: 
                    231:                if(placeholders_count>0)
                    232:                        services._throw("bind variables not supported (yet)");
1.1       parser    233: 
1.27    ! misha     234:                bool transcode_needed=_transcode_required(connection);
        !           235: 
        !           236:                // transcode query from $request:charset to ?ClientCharset
        !           237:                if(transcode_needed){
        !           238:                        size_t length=strlen(statement);
        !           239:                        services.transcode(statement, length,
        !           240:                                statement, length,
1.20      paf       241:                                services.request_charset(),
1.27    ! misha     242:                                connection.client_charset);
1.20      paf       243:                }
                    244: 
1.24      paf       245:                while(isspace((unsigned char)*statement)) 
1.1       parser    246:                        statement++;
1.27    ! misha     247: 
1.1       parser    248:                TRY {
1.8       paf       249:                        // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\adosql.chm::/adoprg02_4g33.htm
1.27    ! misha     250:                        // or http://msdn.microsoft.com/en-us/library/aa905899(SQL.80).aspx
1.8       paf       251:                        // Server cursors are created only for statements that begin with: 
                    252:                        // SELECT
                    253:                        // EXEC[ute] procedure_name
                    254:                        // call procedure_name
                    255:                        // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\odbcsql.chm::/od_6_035_5dnp.htm
                    256:                        // The ODBC CALL escape sequence for calling a procedure is:
                    257:                        // {[?=]call procedure_name[([parameter][,[parameter]]...)]}
1.27    ! misha     258:                        if(strncasecmp(statement, "SELECT", 6)==0
1.8       paf       259:                                || strncasecmp(statement, "EXEC", 4)==0
                    260:                                || strncasecmp(statement, "call", 4)==0
1.27    ! misha     261:                                || strncasecmp(statement, "{", 1)==0
        !           262:                        ){
        !           263:                                CRecordset rs(db);
        !           264:                                DWORD options=CRecordset::executeDirect|CRecordset::readOnly;
        !           265:                                //CRecordset::skipDeletedRecords
        !           266:                                //CRecordset::useMultiRowFetch
        !           267:                                //CRecordset::userAllocMultiRowBuffers
        !           268:                                //CRecordset::useExtendedFetch
        !           269:                                /*
        !           270:                                if(connection.use_multi_row_fetch){
        !           271:                                        options+=CRecordset::useMultiRowFetch+CRecordset::userAllocMultiRowBuffers;
        !           272:                                } else {
        !           273:                                        options+=CRecordset::skipDeletedRecords;
        !           274:                                }
        !           275:                                */
1.9       paf       276:                                TRY {
                    277:                                        rs.Open(
1.27    ! misha     278:                                                (connection.use_multi_row_fetch)?CRecordset::dynamic:CRecordset::forwardOnly,
1.9       paf       279:                                                statement,
1.27    ! misha     280:                                                options
        !           281:                                        );
1.9       paf       282:                                } CATCH_ALL (e) {
                    283:                                        // could not fetch a table
                    284:                                        TRY {
                    285:                                                // then try resultless query
                    286:                                                db->ExecuteSQL(statement);
                    287:                                                // OK then
                    288:                                                return;
                    289:                                        } CATCH_ALL (e2) {
                    290:                                                // still nothing good
                    291:                                                _throw(services, e); // throw ORIGINAL exception
                    292:                                        } END_CATCH_ALL
                    293:                                } END_CATCH_ALL
1.1       parser    294: 
                    295:                                int column_count=rs.GetODBCFieldCount();
                    296:                                if(!column_count)
                    297:                                        services._throw("result contains no columns");
                    298: 
1.10      paf       299:                                if(column_count>MAX_COLS)
                    300:                                        column_count=MAX_COLS;
                    301: 
1.27    ! misha     302:                                SWORD column_types[MAX_COLS];
        !           303: 
1.12      paf       304:                                SQL_Error sql_error;
                    305: #define CHECK(afailed) if(afailed) services._throw(sql_error)
                    306: 
1.1       parser    307:                                for(int i=0; i<column_count; i++){
                    308:                                        CString string;
                    309:                                        CODBCFieldInfo fieldinfo;
                    310:                                        rs.GetODBCFieldInfo(i, fieldinfo);
1.10      paf       311:                                        column_types[i]=fieldinfo.m_nSQLType;
1.20      paf       312:                                        size_t length=fieldinfo.m_strName.GetLength();
1.14      paf       313:                                        char *str=0;
1.27    ! misha     314:                                        if(length){
1.20      paf       315:                                                str=(char*)services.malloc_atomic(length+1);
1.27    ! misha     316:                                                memcpy(str, (char*)LPCTSTR(fieldinfo.m_strName), length+1);
1.20      paf       317: 
1.27    ! misha     318:                                                // transcode column name from ?ClientCharset to $request:charset
        !           319:                                                if(transcode_needed){
1.20      paf       320:                                                        services.transcode(str, length,
                    321:                                                                str, length,
1.27    ! misha     322:                                                                connection.client_charset,
1.20      paf       323:                                                                services.request_charset());
                    324:                                                }
1.1       parser    325:                                        }
1.20      paf       326:                                        CHECK(handlers.add_column(sql_error, str, length));
1.1       parser    327:                                }
                    328: 
1.12      paf       329:                                CHECK(handlers.before_rows(sql_error));
1.27    ! misha     330:                                
        !           331:                                // skip offset rows
        !           332:                                if(offset){
        !           333:                                        if(connection.use_multi_row_fetch){
        !           334:                                                rs.Move(offset);
        !           335:                                        } else {
        !           336:                                                unsigned long row=offset;
        !           337:                                                while(!rs.IsEOF() && row>0){
        !           338:                                                        rs.MoveNext();
        !           339:                                                        row--;
        !           340:                                                }
        !           341:                                        }
        !           342:                                }
1.1       parser    343: 
                    344:                                unsigned long row=0;
1.10      paf       345:                                CDBVariant v;
                    346:                                CString s;
1.27    ! misha     347:                                while(!rs.IsEOF() && (limit==SQL_NO_LIMIT || row<limit)){
        !           348:                                        CHECK(handlers.add_row(sql_error));
        !           349:                                        for(int i=0; i<column_count; i++){
        !           350:                                                size_t length;
        !           351:                                                char* str;
        !           352:                                                switch(column_types[i]){
1.7       paf       353:                                                        //case xBOOL:
1.27    ! misha     354:                                                        //case SQL_INTEGER: // serg@design.ru did that in parser2. test first!
1.14      paf       355:                                                        //case SQL_DATETIME: << default: handles that more properly (?)
                    356:                                                        case SQL_BINARY: 
1.11      paf       357:                                                        case SQL_VARBINARY:
                    358:                                                        case SQL_LONGVARBINARY:
1.7       paf       359:                                                        case SQL_SMALLDATETIME:
1.27    ! misha     360:                                                        //case SQL_NVARCHAR:    // mfc 7.1 has errors with nvarchar(length): SQLGetData in dbcore.cpp truncates last byte for unknown reason.
        !           361:                                                                                                        // could be fixed by uncommenting this and handing DBVT_WSTRING inside, but it's UNICODE
1.10      paf       362:                                                                rs.GetFieldValue(i, v);
1.20      paf       363:                                                                getFromDBVariant(services, v, str, length);
1.10      paf       364:                                                                break;
1.7       paf       365:                                                        default:
1.10      paf       366:                                                                rs.GetFieldValue(i, s);
1.20      paf       367:                                                                getFromString(services, s, str, length);
1.10      paf       368:                                                                break;
1.27    ! misha     369:                                                }
1.20      paf       370: 
1.27    ! misha     371:                                                // transcode cell value from ?ClientCharset to $request:charset
        !           372:                                                if(transcode_needed && length)
        !           373:                                                        services.transcode(str, length,
        !           374:                                                                str, length,
        !           375:                                                                connection.client_charset,
        !           376:                                                                services.request_charset());
1.20      paf       377: 
1.27    ! misha     378:                                                CHECK(handlers.add_row_cell(sql_error, str, length));
1.1       parser    379:                                        }
1.27    ! misha     380:                                        rs.MoveNext();
        !           381:                                        row++;
1.1       parser    382:                                }
                    383:                                
                    384:                                rs.Close();
                    385:                        } else {
                    386:                                db->ExecuteSQL(statement);
                    387:                        }
1.9       paf       388:                } CATCH_ALL (e) {
1.1       parser    389:                        _throw(services, e);
1.9       paf       390:                } END_CATCH_ALL
1.7       paf       391:        }
                    392: 
1.27    ! misha     393: private:
        !           394:        void getFromDBVariant(SQL_Driver_services& services, CDBVariant& v, char*& str, size_t& length){
        !           395:                switch(v.m_dwType){
1.14      paf       396:                case DBVT_BINARY: /* << would cause problems with current String implementation
                    397:                                  now falling into NULL case, effectively ignoring such columns [not failing]
                    398:                        {
1.17      paf       399:                                if(length=v.m_pbinary->m_dwDataLength) {
                    400:                                        str=services.malloc_atomic(length+1);
                    401:                                        memcpy(ptr, ::GlobalLock(v.m_pbinary->m_hData), length);
1.14      paf       402:                                        ::GlobalUnlock(v.m_pbinary->m_hData);
                    403:                                } else 
                    404:                                        str=0;
                    405:                                break;
                    406:                        }*/ 
1.7       paf       407:                case DBVT_NULL: // No union member is valid for access. 
1.14      paf       408:                        str=0;
1.17      paf       409:                        length=0;
1.7       paf       410:                        break;
                    411: /*             case DBVT_BOOL:
                    412:                        ptr=v.m_boolVal?"1":"0";
1.17      paf       413:                        length=1;
1.7       paf       414:                        break;*/
                    415: /*                                                     case DBVT_UCHAR:
1.17      paf       416:                        length=strlen(ptr=v.m_chVal);
1.7       paf       417:                        break;
                    418:                case DBVT_SHORT:
                    419:                        char buf[MAX_NUMBER];
1.17      paf       420:                        length=snprintf(HEAPIZE buf, "%d", v.m_iVal);
1.27    ! misha     421:                        break;
        !           422: */
1.7       paf       423: /*             case DBVT_LONG: 
                    424:                        {
                    425:                                char local_buf[MAX_NUMBER];
1.17      paf       426:                                length=snprintf(local_buf, MAX_NUMBER, "%ld", v.m_lVal);
                    427:                                ptr=services.malloc_atomic(length);
                    428:                                memcpy(ptr, local_buf, length);
1.7       paf       429:                                break;
1.27    ! misha     430:                        }
        !           431: */
        !           432: /*
        !           433:                case DBVT_SINGLE:
1.7       paf       434:                        m_fltVal 
                    435:                        break;
1.27    ! misha     436:                case DBVT_DOUBLE m_dblVal 
        !           437:                case DBVT_STRING m_pstring
        !           438: */ 
1.7       paf       439:                case DBVT_DATE:
                    440:                        {
                    441:                                char local_buf[MAX_STRING];
1.17      paf       442:                                length=snprintf(local_buf, MAX_STRING, 
1.7       paf       443:                                        "%04d-%02d-%02d %02d:%02d:%02d.%03d",
                    444:                                        v.m_pdate->year, 
                    445:                                        v.m_pdate->month,
                    446:                                        v.m_pdate->day,
                    447:                                        v.m_pdate->hour,
                    448:                                        v.m_pdate->minute,
                    449:                                        v.m_pdate->second,
1.25      paf       450:                                        v.m_pdate->fraction/1000000); // lexical parser of INCOMING literal choked on times like hh:mm:ss.123000000
1.17      paf       451:                                str=(char*)services.malloc_atomic(length+1);
                    452:                                memcpy(str, local_buf, length+1);
1.7       paf       453:                                break;
                    454:                        }
                    455:                default:
                    456:                        char msg[MAX_STRING];
                    457:                        snprintf(msg, MAX_STRING, "unknown column return variant type (%d)",
                    458:                                v.m_dwType);
                    459:                        services._throw(msg);
                    460:                }
                    461:        }
                    462: 
1.27    ! misha     463:        void getFromString(SQL_Driver_services& services, CString& s, char*& astr, size_t& length){
        !           464:                if(s.IsEmpty()){
1.14      paf       465:                        astr=0;
1.17      paf       466:                        length=0;
1.7       paf       467:                } else {
                    468:                        const char *cstr=LPCTSTR(s);
1.17      paf       469:                        length=strlen(cstr); //string.GetLength() works wrong with non-string types: 
                    470:                        astr=(char*)services.malloc_atomic(length+1);
                    471:                        memcpy(astr, cstr, length+1);
1.7       paf       472:                }
1.1       parser    473:        }
                    474: 
1.27    ! misha     475:        void _throw(SQL_Driver_services& services, CException *e){
        !           476:                char szCause[MAX_STRING];
        !           477:                szCause[0]=0;
1.1       parser    478:                e->GetErrorMessage(szCause, MAX_STRING);
                    479:                char msg[MAX_STRING];
                    480:                snprintf(msg, MAX_STRING, "%s: %s",
                    481:                        e->GetRuntimeClass()->m_lpszClassName,
                    482:                        *szCause?szCause:"unknown");
                    483:                services._throw(msg);
                    484:        }
                    485: 
1.27    ! misha     486:        void _throw(Connection& connection, long value){
        !           487:                char msg[MAX_STRING];
        !           488:                snprintf(msg, MAX_STRING, "%u", value);
        !           489:                connection.services->_throw(msg);
        !           490:        }
        !           491: 
        !           492:        bool _transcode_required(Connection& connection){
        !           493:                return (connection.client_charset && strcmp(connection.client_charset, connection.services->request_charset())!=0);
        !           494:        }
        !           495: 
1.1       parser    496: };
                    497: 
                    498: extern "C" SQL_Driver *SQL_DRIVER_CREATE() {
                    499:        return new ODBC_Driver();
1.26      paf       500: }

E-mail: