/*************************************************************************** * Copyright (C) 1995-2002 MySQL AB, www.mysql.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software Foundation * * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ****************************************************************************/ /*************************************************************************** * RESULTS.C * * * * @description: For handling resultset and resultset information * * * * @author : MySQL AB(monty@mysql.com, venu@mysql.com) * * @date : 2001-Aug-15 * * @product : myodbc3 * * * ****************************************************************************/ /*************************************************************************** * The following ODBC APIs are implemented in this file: * * * * SQLRowCount (ISO 92) * * SQLNumResultCols (ISO 92) * * SQLDescribeCol (ISO 92) * * SQLColAttribute (ISO 92) * * SQLColAttributes (ODBC, Deprecated) * * SQLBindCol (ISO 92) * * SQLFetch (ISO 92) * * SQLFetchScroll (ISO 92) * * SQLGetData (ISO 92) * * SQLExtendedFetch (ODBC, Deprecated) * * SQLMoreResults (ODBC) * * * ****************************************************************************/ #include "myodbc3.h" #include #include #define digit(A) ((int) (A - '0')) SQLRETURN SQL_API sql_get_data(STMT *stm,SQLSMALLINT fCType,MYSQL_FIELD *field, SQLPOINTER rgbValue, SQLINTEGER cbValueMax, SQLINTEGER FAR *pcbValue, char *value,uint length); /* @type : myodbc3 internal @purpose : execute the query if it is only prepared. This is needed because the ODBC standard allows calling some functions before SQLExecute(). (Stupid!) */ static SQLRETURN check_result(STMT FAR *stmt) { SQLRETURN error; switch (stmt->state) { case ST_UNKNOWN: error=set_stmt_error(stmt,"24000","Invalid cursor state",0); break; case ST_PREPARED: if ((error=my_SQLExecute(stmt)) == SQL_SUCCESS) stmt->state=ST_PRE_EXECUTED; /* mark for execute */ break; case ST_PRE_EXECUTED: case ST_EXECUTED: error=SQL_SUCCESS; } return(error); } /* @type : myodbc3 internal @purpose : does the any open param binding */ SQLRETURN do_dummy_parambind(SQLHSTMT hstmt) { STMT FAR *stmt = (STMT FAR *)hstmt; uint nparam; for (nparam = 0; nparam < stmt->param_count; nparam++) { PARAM_BIND *param= dynamic_element(&stmt->params,nparam,PARAM_BIND*); if (param->real_param_done != TRUE && param->used != 1) { /* do the dummy bind temporarily to get the result set and once everything is done, remove it */ param->used = 1; param->SqlType = SQL_VARCHAR; param->CType = SQL_C_CHAR; param->buffer = "NULL"; if (set_dynamic(&stmt->params,(gptr)param,nparam)) { return set_stmt_error(stmt,"S1001","Not enough memory",4001); } } } stmt->dummy_state = ST_DUMMY_EXECUTED; return(SQL_SUCCESS); } /* @type : ODBC 1.0 API @purpose : returns the number of columns in a result set */ SQLRETURN SQL_API SQLNumResultCols(SQLHSTMT hstmt, SQLSMALLINT FAR *pccol) { SQLRETURN error; STMT FAR *stmt=(STMT FAR*) hstmt; DBUG_ENTER("SQLNumResultCols"); if (stmt->param_count > 0 && stmt->dummy_state == ST_DUMMY_UNKNOWN && (stmt->state != ST_PRE_EXECUTED || stmt->state != ST_EXECUTED)) { if (do_dummy_parambind(hstmt) != SQL_SUCCESS) DBUG_RETURN(SQL_ERROR); } if ((error=check_result(stmt)) != SQL_SUCCESS) DBUG_RETURN(error); if (!stmt->result) *pccol=0; /* Not a select */ else *pccol=stmt->result->field_count; DBUG_PRINT("exit",("columns: %d",*pccol)); DBUG_RETURN(SQL_SUCCESS); } /* @type : ODBC 1.0 API @purpose : returns the result column name, type, column size, decimal digits, and nullabilityfor one column in the result set */ SQLRETURN SQL_API SQLDescribeCol(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLCHAR FAR *szColName, SQLSMALLINT cbColNameMax, SQLSMALLINT FAR *pcbColName, SQLSMALLINT FAR *pfSqlType, SQLUINTEGER FAR *pcbColDef, SQLSMALLINT FAR *pibScale, SQLSMALLINT FAR *pfNullable) { SQLRETURN error; MYSQL_FIELD *field; STMT FAR *stmt=(STMT FAR*) hstmt; ulong transfer_length,precision,display_size; int type; DBUG_ENTER("SQLDescribeCol"); if ((error=check_result(stmt)) != SQL_SUCCESS) DBUG_RETURN(error); if (! stmt->result) DBUG_RETURN(set_stmt_error(stmt,"07005","No result set",0)); mysql_field_seek(stmt->result,icol-1); if (!(field=mysql_fetch_field(stmt->result))) DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,stmt,MYERR_S1002, "Invalid column number",0)); type=unireg_to_sql_datatype(stmt,field,0,&transfer_length,&precision, &display_size); if (pfSqlType) *pfSqlType=type; if (pcbColDef) *pcbColDef=precision; if (pibScale) *pibScale=field->decimals; if (pfNullable) *pfNullable= (((field->flags & (NOT_NULL_FLAG | AUTO_INCREMENT_FLAG)) == NOT_NULL_FLAG) ? SQL_NO_NULLS : SQL_NULLABLE); DBUG_PRINT("info",("col: %d type: %d precision: %ld decimals: %d", icol,type,precision,field->decimals)); if (stmt->dbc->flag & FLAG_FULL_COLUMN_NAMES && field->table) { char *tmp=my_malloc(strlen(field->name)+strlen(field->table)+2, MYF(MY_WME)); SQLRETURN error; if (!tmp) { DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,stmt,MYERR_S1001,NULL,4001)); } strxmov(tmp,field->table,".",field->name,NullS); error=copy_str_data(SQL_HANDLE_STMT, stmt, szColName, cbColNameMax, pcbColName, tmp); my_free((gptr) tmp,MYF(0)); DBUG_RETURN(error); } DBUG_RETURN(copy_str_data(SQL_HANDLE_STMT, stmt, szColName, cbColNameMax, pcbColName, field->name)); } /* @type : myodbc3 internal @purpose : rerunrs column atribute values */ SQLRETURN SQL_API get_col_attr(SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLUSMALLINT FieldIdentifier, SQLPOINTER CharacterAttributePtr, SQLSMALLINT BufferLength, SQLSMALLINT *StringLengthPtr, SQLPOINTER NumericAttributePtr) { MYSQL_FIELD *field; STMT FAR *stmt=(STMT FAR*) StatementHandle; SQLSMALLINT str_length; SQLPOINTER strparam=0; SQLPOINTER nparam=0; ulong transfer_length,precision,display_size; SQLRETURN error; DBUG_ENTER("get_col_atrr"); DBUG_PRINT("enter",("column: %d type: %d",ColumnNumber, FieldIdentifier)); if (check_result(stmt) != SQL_SUCCESS) DBUG_RETURN(SQL_ERROR); if (!stmt->result) DBUG_RETURN(set_stmt_error(stmt,"07005","No result set",0)); if (ColumnNumber > stmt->result->field_count) DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,StatementHandle, MYERR_07009, NULL,0)); if (!StringLengthPtr) StringLengthPtr= &str_length; if (!CharacterAttributePtr) CharacterAttributePtr= nparam; if (!NumericAttributePtr) NumericAttributePtr= strparam; if ((error=check_result(stmt)) != SQL_SUCCESS) DBUG_RETURN(error); if (FieldIdentifier == SQL_DESC_COUNT || FieldIdentifier == SQL_COLUMN_COUNT ) { *(SQLINTEGER *)NumericAttributePtr= stmt->result->field_count; *StringLengthPtr=sizeof(SQLINTEGER); DBUG_RETURN(SQL_SUCCESS); } if (FieldIdentifier == SQL_DESC_TYPE && ColumnNumber == 0) { *(SQLINTEGER *) NumericAttributePtr=SQL_INTEGER; *StringLengthPtr=sizeof(SQLINTEGER); DBUG_RETURN(SQL_SUCCESS); } mysql_field_seek(stmt->result,ColumnNumber-1); if (!(field=mysql_fetch_field(stmt->result))) DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,stmt,MYERR_S1002, "Invalid column number",0)); switch (FieldIdentifier) { case SQL_DESC_AUTO_UNIQUE_VALUE: *(SQLINTEGER *)NumericAttributePtr = (field->flags & AUTO_INCREMENT_FLAG ? SQL_TRUE : SQL_FALSE); *StringLengthPtr=sizeof(SQLINTEGER); break; /* We need support from server, when aliasing is there */ case SQL_DESC_BASE_COLUMN_NAME: case SQL_DESC_LABEL: case SQL_DESC_NAME: case SQL_COLUMN_NAME: DBUG_RETURN(copy_str_data(SQL_HANDLE_STMT, stmt, CharacterAttributePtr, BufferLength,StringLengthPtr, field->name)); case SQL_DESC_BASE_TABLE_NAME: case SQL_DESC_TABLE_NAME: DBUG_RETURN(copy_str_data(SQL_HANDLE_STMT, stmt,CharacterAttributePtr, BufferLength,StringLengthPtr, field->table ? field->table : "")); case SQL_DESC_CASE_SENSITIVE: *(SQLINTEGER *)NumericAttributePtr = (field->flags & BINARY_FLAG ? SQL_FALSE : SQL_TRUE); *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_DESC_CATALOG_NAME: DBUG_RETURN(copy_str_data(SQL_HANDLE_STMT, stmt,CharacterAttributePtr, BufferLength, StringLengthPtr, stmt->dbc->database)); case SQL_DESC_DISPLAY_SIZE: (void) unireg_to_sql_datatype(stmt,field,0,&transfer_length,&precision, &display_size); *(SQLINTEGER *)NumericAttributePtr = display_size; *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_DESC_FIXED_PREC_SCALE: /* need to verify later */ { if( field->type == FIELD_TYPE_DECIMAL ) *(SQLINTEGER *)NumericAttributePtr = SQL_TRUE; else *(SQLINTEGER *)NumericAttributePtr = SQL_FALSE; *StringLengthPtr=sizeof(SQLINTEGER); break; } case SQL_COLUMN_LENGTH: case SQL_DESC_LENGTH: case SQL_DESC_OCTET_LENGTH: /* need to validate again for octet length..*/ (void) unireg_to_sql_datatype(stmt,field,0,&transfer_length,&precision, &display_size); *(SQLINTEGER *)NumericAttributePtr = transfer_length; *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_DESC_LITERAL_PREFIX: case SQL_DESC_LITERAL_SUFFIX: switch (field->type) { case FIELD_TYPE_LONG_BLOB: case FIELD_TYPE_TINY_BLOB: case FIELD_TYPE_MEDIUM_BLOB: case FIELD_TYPE_BLOB: DBUG_RETURN(copy_str_data(SQL_HANDLE_STMT, stmt,CharacterAttributePtr, BufferLength,StringLengthPtr,"0x")); case FIELD_TYPE_DATE: case FIELD_TYPE_DATETIME: case FIELD_TYPE_NEWDATE: case FIELD_TYPE_VAR_STRING: case FIELD_TYPE_STRING: case FIELD_TYPE_TIMESTAMP: case FIELD_TYPE_TIME: case FIELD_TYPE_YEAR: DBUG_RETURN(copy_str_data(SQL_HANDLE_STMT, stmt,CharacterAttributePtr, BufferLength,StringLengthPtr,"'")); default: DBUG_RETURN(copy_str_data(SQL_HANDLE_STMT, stmt,CharacterAttributePtr, BufferLength,StringLengthPtr,"")); } break; case SQL_DESC_NULLABLE: case SQL_COLUMN_NULLABLE: *(SQLINTEGER *)NumericAttributePtr = (((field->flags & (NOT_NULL_FLAG | AUTO_INCREMENT_FLAG)) == NOT_NULL_FLAG) ? SQL_NO_NULLS : SQL_NULLABLE); *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_DESC_NUM_PREC_RADIX: switch(field->type) { case FIELD_TYPE_SHORT: case FIELD_TYPE_LONG: case FIELD_TYPE_LONGLONG: case FIELD_TYPE_INT24: case FIELD_TYPE_TINY: case FIELD_TYPE_DECIMAL: *(SQLINTEGER *)NumericAttributePtr = 10; break; case FIELD_TYPE_FLOAT: case FIELD_TYPE_DOUBLE: *(SQLINTEGER *) NumericAttributePtr = 2; break; default: *(SQLINTEGER *)NumericAttributePtr = 0; break; } *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_COLUMN_PRECISION: case SQL_DESC_PRECISION: (void )unireg_to_sql_datatype(stmt,field,0,&transfer_length,&precision, &display_size); *(SQLINTEGER *)NumericAttributePtr = precision; *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_COLUMN_SCALE: case SQL_DESC_SCALE: *(SQLINTEGER *)NumericAttributePtr = field->decimals; *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_DESC_SCHEMA_NAME: DBUG_RETURN(copy_str_data(SQL_HANDLE_STMT, stmt,CharacterAttributePtr, BufferLength,StringLengthPtr, "")); case SQL_DESC_SEARCHABLE: *(SQLINTEGER *)NumericAttributePtr = SQL_SEARCHABLE; *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_DESC_TYPE: case SQL_DESC_CONCISE_TYPE: *(SQLINTEGER *)NumericAttributePtr = unireg_to_sql_datatype(stmt, field, 0, &transfer_length, &precision, &display_size); *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_DESC_TYPE_NAME: { char buff[40]; (void)unireg_to_sql_datatype(stmt,field,(char *)buff,&transfer_length, &precision, &display_size); DBUG_RETURN(copy_str_data(SQL_HANDLE_STMT, stmt,CharacterAttributePtr, BufferLength,StringLengthPtr, buff)); } case SQL_DESC_UNNAMED: *(SQLINTEGER *)NumericAttributePtr=SQL_NAMED; *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_DESC_UNSIGNED: *(SQLINTEGER *) NumericAttributePtr= (field->flags & UNSIGNED_FLAG ? SQL_TRUE : SQL_FALSE); *StringLengthPtr=sizeof(SQLINTEGER); break; case SQL_DESC_UPDATABLE: *(SQLINTEGER *)NumericAttributePtr= (field->table && field->table[0] ? SQL_ATTR_WRITE : SQL_ATTR_READONLY); *StringLengthPtr=sizeof(SQLINTEGER); break; default: DBUG_PRINT("warning",("Type: %d is not supported by MyODBC3", FieldIdentifier)); } DBUG_RETURN(SQL_SUCCESS); } /* @type : ODBC 3.0 API @purpose : rerunrs column atribute values */ SQLRETURN SQL_API SQLColAttribute(SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLUSMALLINT FieldIdentifier, SQLPOINTER CharacterAttributePtr, SQLSMALLINT BufferLength, SQLSMALLINT *StringLengthPtr, SQLPOINTER NumericAttributePtr) { return get_col_attr(StatementHandle, ColumnNumber, FieldIdentifier, CharacterAttributePtr, BufferLength, StringLengthPtr, NumericAttributePtr); } /* @type : ODBC 1.0 API @purpose : rerunrs column atribute values */ SQLRETURN SQL_API SQLColAttributes(SQLHSTMT hstmt,SQLUSMALLINT icol, SQLUSMALLINT fDescType, SQLPOINTER rgbDesc, SQLSMALLINT cbDescMax, SQLSMALLINT FAR *pcbDesc, SQLINTEGER FAR *pfDesc) { SQLRETURN result=SQL_SUCCESS; DBUG_ENTER("SQLColAttributes"); result = get_col_attr(hstmt, icol, fDescType, rgbDesc, cbDescMax, pcbDesc, pfDesc); DBUG_RETURN(result); } /* @type : ODBC 1.0 API @purpose : binds application data buffers to columns in the result set */ SQLRETURN SQL_API SQLBindCol(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLSMALLINT fCType, SQLPOINTER rgbValue, SQLINTEGER cbValueMax, SQLINTEGER FAR *pcbValue) { BIND *bind; STMT FAR *stmt=(STMT FAR*) hstmt; SQLRETURN error; DBUG_ENTER("SQLBindCol"); DBUG_PRINT("enter", ("icol: %d Type: %d ValueMax: %ld Valueptr: %lx Value: %ld", icol,fCType,(long) cbValueMax, pcbValue, (long) (pcbValue ? *pcbValue : 0L))); icol--; /* The next case if because of VB 5.0 that binds columns before preparing a statement */ if (stmt->state == ST_UNKNOWN) { DBUG_PRINT("info",("Binding columns without a statement; Hope you know what you are doing")); if (icol >= stmt->bound_columns) { if (!(stmt->bind=(BIND*) my_realloc((char*) stmt->bind, (icol+1)*sizeof(BIND), MYF(MY_ALLOW_ZERO_PTR | MY_FREE_ON_ERROR)))) { stmt->bound_columns=0; DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,stmt,MYERR_S1001,NULL, 4001)); } bzero((gptr) (stmt->bind+stmt->bound_columns), (icol+1-stmt->bound_columns)*sizeof(BIND)); stmt->bound_columns=icol+1; } } else { /* Bind parameter to current set ( The normal case ) */ /* select stmt with parameters */ if (stmt->param_count > 0 && stmt->dummy_state == ST_DUMMY_UNKNOWN && (stmt->state != ST_PRE_EXECUTED || stmt->state != ST_EXECUTED)) { if(do_dummy_parambind(hstmt) != SQL_SUCCESS) DBUG_RETURN(SQL_ERROR); } if ((error = check_result(stmt)) != SQL_SUCCESS) { DBUG_RETURN(error); } if (!stmt->result || (uint) icol >= stmt->result->field_count) { DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,stmt,MYERR_S1002, "Invalid column number",0)); } if (!stmt->bind) { if (!(stmt->bind=(BIND*) my_malloc(sizeof(BIND)* stmt->result->field_count, MYF(MY_ZEROFILL)))) DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,stmt,MYERR_S1001,NULL, 4001)); stmt->bound_columns=stmt->result->field_count; } mysql_field_seek(stmt->result,icol); stmt->bind[icol].field=mysql_fetch_field(stmt->result); } bind=stmt->bind+icol; bind->fCType=fCType; if (fCType == SQL_C_DEFAULT && stmt->odbc_types) bind->fCType=stmt->odbc_types[icol]; bind->rgbValue=rgbValue; bind->cbValueMax=bind_length(bind->fCType,cbValueMax); bind->pcbValue=pcbValue; DBUG_RETURN(SQL_SUCCESS); } /* @type : myodbc3 internal @purpose : returns the latest resultset(dynamic) */ SQLRETURN set_dynamic_result(STMT FAR *stmt) { if (odbc_stmt(stmt->dbc, stmt->query) != SQL_SUCCESS) return(SQL_ERROR); pthread_mutex_lock(&stmt->dbc->lock); x_free((gptr) stmt->odbc_types); mysql_free_result(stmt->result); stmt->result=0; stmt->odbc_types=0; stmt->cursor_row = 0; stmt->result=mysql_store_result(&stmt->dbc->mysql); if (!stmt->result) { set_handle_error(SQL_HANDLE_STMT,stmt,MYERR_S1000, mysql_error(&stmt->dbc->mysql), mysql_errno(&stmt->dbc->mysql)); pthread_mutex_unlock(&stmt->dbc->lock); return(SQL_ERROR); } fix_result_types(stmt); set_current_cursor_data(stmt,0); pthread_mutex_unlock(&stmt->dbc->lock); return(SQL_SUCCESS); } /* @type : ODBC 1.0 API @purpose : fetches the next rowset of data from the result set and returns data for all bound columns */ SQLRETURN SQL_API SQLFetch(SQLHSTMT hstmt) { MYSQL_ROW values; SQLRETURN res,tmp_res; STMT FAR *stmt=(STMT FAR*) hstmt; DBUG_ENTER("SQLFetch"); if (!stmt->result) DBUG_RETURN(set_stmt_error(stmt,"24000","Fetch without a SELECT",0)); if (stmt->result_array) { if (stmt->current_row >= stmt->result->row_count) DBUG_RETURN(SQL_NO_DATA_FOUND); values= (stmt->result_array+(stmt->current_row++)* stmt->result->field_count); } else { /* check for the previous first row fetch */ if(stmt->current_row == 0 && stmt->rows_found_in_set > 0) mysql_data_seek(stmt->result, 1L); if (!(values=mysql_fetch_row(stmt->result))) DBUG_RETURN(SQL_NO_DATA_FOUND); stmt->current_row++; /* For SQLGetStmtOption */ if (stmt->fix_fields) values=(*stmt->fix_fields)(stmt,values); else stmt->result_lengths=mysql_fetch_lengths(stmt->result); } stmt->current_values=values; /* For SQLGetData */ stmt->last_getdata_col= (uint) ~0; res=SQL_SUCCESS; if (stmt->bind) { ulong *lengths=stmt->result_lengths; BIND *bind,*end; if (!(stmt->dbc->flag & FLAG_NO_LOCALE)) setlocale(LC_NUMERIC,"English"); /* force use of '.' as decimal point */ for (bind=stmt->bind,end=bind + stmt->result->field_count ; bind < end ; bind++,values++) { if (bind->rgbValue || bind->pcbValue) { stmt->getdata_offset= (ulong) ~0L; if ((tmp_res=sql_get_data(stmt,bind->fCType,bind->field,bind->rgbValue, bind->cbValueMax, bind->pcbValue, *values, lengths ? *lengths : *values ? strlen(*values) : 0) ) != SQL_SUCCESS) { if (tmp_res == SQL_SUCCESS_WITH_INFO) { DBUG_PRINT("info",("Problem with column: %d, value: '%s'", (int) (bind - stmt->bind)+1, *values ? *values : "NULL")); if (res == SQL_SUCCESS) res= tmp_res; } else res=SQL_ERROR; } } if (lengths) lengths++; } if (!(stmt->dbc->flag & FLAG_NO_LOCALE)) setlocale(LC_NUMERIC,default_locale); } stmt->getdata_offset= (ulong) ~0L; DBUG_RETURN(res); } /* @type : ODBC 1.0 API @purpose : retrieves data for a single column in the result set. It can be called multiple times to retrieve variable-length data in parts */ SQLRETURN SQL_API SQLGetData(SQLHSTMT hstmt,SQLUSMALLINT icol, SQLSMALLINT fCType,SQLPOINTER rgbValue, SQLINTEGER cbValueMax, SQLINTEGER FAR *pcbValue) { STMT FAR *stmt=(STMT FAR*) hstmt; SQLRETURN result; DBUG_ENTER("SQLGetData"); DBUG_PRINT("enter",("icol:%d, ctype:%d, rgb:0x%x, len:%d, pcb:0x%x)", icol,fCType,rgbValue,cbValueMax,pcbValue)); if (!stmt->result || !stmt->current_values) { set_stmt_error(stmt,"24000","SQLGetData without a preceding SELECT",0); DBUG_RETURN(SQL_ERROR); } icol--; /* Easier code if start from 0 */ if (icol != stmt->last_getdata_col) { /* New column. Reset old offset */ stmt->last_getdata_col=icol; stmt->getdata_offset= (ulong) ~0L; } #ifdef LOG_ALL DBUG_PRINT("QQ",("icol: %d fCType: %d default: %d value: %.10s", icol+1,fCType, stmt->odbc_types[icol], (stmt->current_values[icol] ? stmt->current_values[icol] : "NULL"))); #endif if (!(stmt->dbc->flag & FLAG_NO_LOCALE)) setlocale(LC_NUMERIC,"English"); result=sql_get_data(stmt, (SQLSMALLINT) (fCType == SQL_C_DEFAULT ? stmt->odbc_types[icol] : fCType), stmt->result->fields+icol, rgbValue,cbValueMax,pcbValue, stmt->current_values[icol], (stmt->result_lengths ? stmt->result_lengths[icol] : (stmt->current_values[icol] ? strlen(stmt->current_values[icol]) : 0 ))); if (!(stmt->dbc->flag & FLAG_NO_LOCALE)) setlocale(LC_NUMERIC,default_locale); DBUG_PRINT("exit ",("return:%d, rgb:0x%x, pcb:0x%x)", result,rgbValue,pcbValue)); DBUG_RETURN(result); } /* @type : myodbc3 internal @purpose : returns the column data */ SQLRETURN SQL_API sql_get_data(STMT *stmt, SQLSMALLINT fCType, MYSQL_FIELD *field, SQLPOINTER rgbValue, SQLINTEGER cbValueMax, SQLINTEGER FAR *pcbValue, char *value, uint length) { DBC *dbc=stmt->dbc; long tmp; if (!pcbValue) pcbValue= &tmp; /* Easier code */ if (!value) { *pcbValue=SQL_NULL_DATA; } else switch (fCType) { case SQL_C_CHAR: /* Handle BLOB -> CHAR conversion */ if ((field->flags & (BLOB_FLAG+BINARY_FLAG)) == (BLOB_FLAG+BINARY_FLAG)) return copy_binary_result(dbc,(char*) rgbValue,cbValueMax,pcbValue, value, length, stmt->stmt_options.max_length, &stmt->getdata_offset); /* fall through */ case SQL_C_BINARY: { char buff[21]; if (field->type == FIELD_TYPE_TIMESTAMP && length != 19) { /* MySQL doesn't have '-' in timestamp */ /* Convert timestamp to ANSI format */ char *pos; uint i; if (length == 6 || length == 12) /* YYMMDD or YYMMDDHHMMSS timestamp */ { if (value[0] <= '6') { buff[0]='2'; buff[1]='0'; } else { buff[0]='1'; buff[1]='9'; } } else { buff[0]=value[0]; buff[1]=value[1]; value+=2; length-=2; } buff[2]= *value++; buff[3]= *value++; buff[4]='-'; if (value[0] == '0' && value[1] == '0') { *pcbValue=SQL_NULL_DATA; /* ODBC can't handle 0000-00-00 dates */ break; } pos=buff+5; length&= 30; /* Ensure that length is ok */ for (i=1, length-=2 ; (int) length > 0 ; length-=2,i++) { *pos++= *value++; *pos++= *value++; *pos++= i < 2 ? '-' : (i == 2) ? ' ' : ':'; } for ( ; pos != buff+20 ; i++) { *pos++= '0'; *pos++= '0'; *pos++= i < 2 ? '-' : (i == 2) ? ' ' : ':'; } value=buff; length=19; } return copy_lresult(dbc,(char*) rgbValue,cbValueMax,pcbValue,value, length,stmt->stmt_options.max_length, field->type == FIELD_TYPE_STRING ? field->length : 0L, &stmt->getdata_offset, (my_bool) (fCType == SQL_C_BINARY)); } case SQL_C_BIT: if (rgbValue) *((char*) rgbValue)= (atoi(value) == 0 ? 0 : 1); *pcbValue=1; break; case SQL_C_TINYINT: case SQL_C_STINYINT: if (rgbValue) *((char*) rgbValue)= ((char) atoi(value)); *pcbValue=1; break; case SQL_C_UTINYINT: if (rgbValue) *((uchar*) rgbValue)= ((uchar) (uint) atoi(value)); *pcbValue=1; break; case SQL_C_SHORT: case SQL_C_SSHORT: if (rgbValue) *((short*) rgbValue)= (short) atoi(value); *pcbValue=sizeof(short); break; case SQL_C_USHORT: if (rgbValue) *((ushort*) rgbValue)= (ushort) (uint) atol(value); *pcbValue=sizeof(short); break; case SQL_C_LONG: case SQL_C_SLONG: if (rgbValue) { /*--------------------------------------------*/ /* Check if it could be a date...... :) */ /*--------------------------------------------*/ if (length >= 10 && value[4] == '-' && value[7] == '-' && (!value[10] || value[10] == ' ')) { *((long*) rgbValue)= ((long) atol(value)*10000L+ (long) atol(value+5)*100L+ (long) atol(value+8)); } else *((long*) rgbValue)= (long) atol(value); } *pcbValue=sizeof(long); break; case SQL_C_ULONG: if (rgbValue) { char *end_ptr; *((ulong*) rgbValue)= strtoul(value,&end_ptr,10); } *pcbValue=sizeof(long); break; case SQL_C_FLOAT: if (rgbValue) *((float*) rgbValue)= (float) atof(value); *pcbValue=sizeof(float); break; case SQL_C_DOUBLE: if (rgbValue) *((double*) rgbValue)= (double) atof(value); *pcbValue=sizeof(double); break; case SQL_C_DATE: case SQL_C_TYPE_DATE: { DATE_STRUCT tmp_date; if (!rgbValue) rgbValue=(char*) &tmp_date; if (!str_to_date((DATE_STRUCT *) rgbValue,value,length)) *pcbValue=sizeof(DATE_STRUCT); else { *pcbValue=SQL_NULL_DATA; /* ODBC can't handle 0000-00-00 dates */ } break; } case SQL_C_TIME: case SQL_C_TYPE_TIME: { if (rgbValue) { ulong time=str_to_time(value,length); TIME_STRUCT *time_info=(TIME_STRUCT *) rgbValue; time_info->hour= (SQLUSMALLINT) (time/10000); time_info->minute=(SQLUSMALLINT) (time/100%100); time_info->second=(SQLUSMALLINT) (time%100); } *pcbValue=sizeof(TIME_STRUCT); break; } case SQL_C_TIMESTAMP: case SQL_C_TYPE_TIMESTAMP: { uint year,length; char buff[15],*to; TIMESTAMP_STRUCT tmp_timestamp; TIMESTAMP_STRUCT *time_stamp; if (!rgbValue) rgbValue= (char*) &tmp_timestamp; time_stamp=(TIMESTAMP_STRUCT *) rgbValue; for (to=buff ; *value && to < buff+sizeof(buff)-1 ; value++) { if (isdigit(*value)) *to++ = *value; } length= (uint) (to-buff); if (length == 6 || length == 12) /* YYMMDD or YYMMDDHHMMSS */ { bmove_upp(to+2,to,length); if (buff[0] <= '6') { buff[0]='2'; buff[1]='0'; } else { buff[0]='1'; buff[1]='9'; } length+=2; } if (length < 14) strfill(to,14 - length,'0'); else *to=0; year=(digit(buff[0])*1000+digit(buff[1])*100+digit(buff[2])*10+ digit(buff[3])); if (buff[4] == '0' && buff[5] == '0') *pcbValue=SQL_NULL_DATA; /* ODBC can't handle 0000-00-00 dates */ else { time_stamp->year= year; time_stamp->month= digit(buff[4])*10+digit(buff[5]); time_stamp->day= digit(buff[6])*10+digit(buff[7]); time_stamp->hour= digit(buff[8])*10+digit(buff[9]); time_stamp->minute= digit(buff[10])*10+digit(buff[11]); time_stamp->second= digit(buff[12])*10+digit(buff[13]); time_stamp->fraction=0; *pcbValue=sizeof(*time_stamp); } break; } case SQL_C_SBIGINT: { if (rgbValue) *((longlong*) rgbValue)= (longlong) strtoll(value,NULL,10); *pcbValue=sizeof(longlong); break; } case SQL_C_UBIGINT: { if (rgbValue) *((ulonglong*) rgbValue)= (ulonglong)strtoull(value,NULL,10); *pcbValue=sizeof(ulonglong); break; } } if (stmt->getdata_offset != (ulong) ~0L) /* Second call to getdata */ { return SQL_NO_DATA_FOUND; } stmt->getdata_offset=0L; /* All data is retrevied */ return SQL_SUCCESS; } /* @type : ODBC 1.0 API @purpose : determines whether more results are available on a statement containing SELECT, UPDATE, INSERT, or DELETE statements and, if so, initializes processing for those results */ SQLRETURN SQL_API SQLMoreResults(SQLHSTMT hstmt) { DBUG_ENTER("SQLMoreResults"); DBUG_RETURN(SQL_NO_DATA_FOUND); } /* @type : ODBC 1.0 API @purpose : returns the number of rows affected by an UPDATE, INSERT, or DELETE statement;an SQL_ADD, SQL_UPDATE_BY_BOOKMARK, or SQL_DELETE_BY_BOOKMARK operation in SQLBulkOperations; or an SQL_UPDATE or SQL_DELETE operation in SQLSetPos */ SQLRETURN SQL_API SQLRowCount(SQLHSTMT hstmt, SQLINTEGER FAR *pcrow) { STMT FAR *stmt=(STMT FAR*) hstmt; DBUG_ENTER("SQLRowCount"); if (stmt->result) { *pcrow=(SQLINTEGER) mysql_affected_rows(&stmt->dbc->mysql); DBUG_PRINT("exit",("Rows in set: %ld",*pcrow)); } else { *pcrow=(SQLINTEGER) stmt->affected_rows; DBUG_PRINT("exit",("Affected rows: %ld",*pcrow)); } DBUG_RETURN(SQL_SUCCESS); } /* @type : myodbc3 internal @purpose : fetches the specified rowset of data from the result set and returns data for all bound columns. Rowsets can be specified at an absolute or relative position */ SQLRETURN SQL_API my_SQLExtendedFetch(SQLHSTMT hstmt, SQLUSMALLINT fFetchType, SQLINTEGER irow, SQLUINTEGER FAR *pcrow, SQLUSMALLINT FAR *rgfRowStatus) { ulong rows_to_fetch; long cur_row, max_row; uint i; SQLRETURN res,tmp_res; STMT FAR *stmt=(STMT FAR*) hstmt; MYSQL_ROW values; MYSQL_ROW_OFFSET save_position; SQLUINTEGER dummy_pcrow; DBUG_ENTER("SQLExtendedFetch"); if (!stmt->result) DBUG_RETURN(set_stmt_error(stmt,"24000","Fetch without a SELECT",0)); DBUG_PRINT("enter", ("fetchtype: %d row: %ld current top-row: %ld rows_found: %ld", fFetchType,irow,stmt->current_row,stmt->rows_found_in_set)); if (stmt->stmt_options.cursor_type == SQL_CURSOR_FORWARD_ONLY && fFetchType != SQL_FETCH_NEXT && !(stmt->dbc->flag & FLAG_SAFE)) DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,stmt,MYERR_S1106, "Wrong fetchtype with FORWARD ONLY cursor", 0)); if (stmt->stmt_options.cursor_type == SQL_CURSOR_DYNAMIC && (set_dynamic_result(stmt) != SQL_SUCCESS)) DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,stmt,MYERR_S1000, "Driver Failed to set the internal dynamic result",0)); if (!pcrow) pcrow= &dummy_pcrow; max_row=(long) mysql_num_rows(stmt->result); stmt->last_getdata_col=(uint) ~0; stmt->current_values=0; /* For SQLGetData */ switch (fFetchType) { case SQL_FETCH_NEXT: cur_row = (stmt->current_row < 0 ? 0 : stmt->current_row+stmt->rows_found_in_set); break; case SQL_FETCH_PRIOR: cur_row = (stmt->current_row <= 0 ? -1 : stmt->current_row-stmt->stmt_options.rows_in_set); break; case SQL_FETCH_FIRST: cur_row=0L; break; case SQL_FETCH_LAST: cur_row=max_row-stmt->stmt_options.rows_in_set; break; case SQL_FETCH_ABSOLUTE: if (irow < 0) { /* Fetch from end of result set */ if (max_row+irow < 0 && -irow <= (long) stmt->stmt_options.rows_in_set) { /* | FetchOffset | > LastResultRow AND | FetchOffset | <= RowsetSize */ cur_row = 0; /* Return from beginning */ } else cur_row = max_row+irow; /* Ok if max_row <= -irow */ } else cur_row= (long) irow-1; break; case SQL_FETCH_RELATIVE: cur_row = stmt->current_row + irow; if (stmt->current_row > 0 && cur_row < 0 && (long) -irow <= (long)stmt->stmt_options.rows_in_set) cur_row = 0; break; default: DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,stmt,MYERR_S1106, "Fetch type out of range",0)); } if ( cur_row < 0) { stmt->current_row= -1; /* Before first row */ stmt->rows_found_in_set=0; mysql_data_seek(stmt->result,0L); DBUG_RETURN(SQL_NO_DATA_FOUND); } if (cur_row > max_row) cur_row=max_row; if (!stmt->result_array) { /* If Dynamic, it looses the stmt->end_of_set, so seek to desired row, might have new data or might be deleted */ if (stmt->stmt_options.cursor_type != SQL_CURSOR_DYNAMIC && cur_row && cur_row == (long)(stmt->current_row + stmt->rows_found_in_set)) mysql_row_seek(stmt->result,stmt->end_of_set); else mysql_data_seek(stmt->result,cur_row); } stmt->current_row=cur_row; rows_to_fetch=min(max_row-cur_row, (long)stmt->stmt_options.rows_in_set); if (!rows_to_fetch) { *pcrow=0; stmt->rows_found_in_set=0; DBUG_RETURN(SQL_NO_DATA_FOUND); } if (!(stmt->dbc->flag & FLAG_NO_LOCALE)) setlocale(LC_NUMERIC,"English"); res=SQL_SUCCESS; for (i=0 ; i < rows_to_fetch ; i++) { if (stmt->result_array) { values=stmt->result_array+cur_row*stmt->result->field_count; if (i == 0) stmt->current_values=values; } else { if (i == 0) save_position=mysql_row_tell(stmt->result); if (!(values=mysql_fetch_row(stmt->result))) break; if (stmt->fix_fields) values=(*stmt->fix_fields)(stmt,values); else stmt->result_lengths=mysql_fetch_lengths(stmt->result); stmt->current_values=values; } if (rgfRowStatus) { rgfRowStatus[i]=SQL_ROW_SUCCESS; stmt->stmt_options.rowStatusPtr = rgfRowStatus; } if (stmt->bind) { /* Should always be true */ ulong *lengths=stmt->result_lengths; BIND *bind,*end; for (bind=stmt->bind,end=bind + stmt->result->field_count ; bind < end ; bind++,values++) { if (bind->rgbValue || bind->pcbValue) { ulong offset,pcb_offset; if (stmt->stmt_options.bind_type == SQL_BIND_BY_COLUMN) { offset=bind->cbValueMax*i; pcb_offset=sizeof(SQLINTEGER)*i; } else pcb_offset=offset=stmt->stmt_options.bind_type*i; stmt->getdata_offset= (ulong) ~0L; if ((tmp_res=sql_get_data(stmt,bind->fCType, bind->field, (bind->rgbValue ? (char*) bind->rgbValue + offset : 0), bind->cbValueMax, (bind->pcbValue ? (SQLINTEGER*) ((char*) bind->pcbValue + pcb_offset) : 0), *values, (lengths ? *lengths : *values ? strlen(*values) : 0))) != SQL_SUCCESS) { if (tmp_res == SQL_SUCCESS_WITH_INFO) { if (res == SQL_SUCCESS) res= tmp_res; } else res=SQL_ERROR; } } if (lengths) lengths++; } } cur_row++; } stmt->rows_found_in_set=i; *pcrow=i; if (rgfRowStatus) for ( ; i < stmt->stmt_options.rows_in_set ; i++) rgfRowStatus[i]=SQL_ROW_NOROW; if (!stmt->result_array) { /* read data from first row */ stmt->end_of_set=mysql_row_seek(stmt->result,save_position); if (i > 1) { stmt->current_values=mysql_fetch_row(stmt->result); if (stmt->fix_fields) stmt->current_values=(*stmt->fix_fields)(stmt,stmt->current_values); else stmt->result_lengths=mysql_fetch_lengths(stmt->result); } } if (!(stmt->dbc->flag & FLAG_NO_LOCALE)) setlocale(LC_NUMERIC,default_locale); DBUG_RETURN(res); } /* @type : ODBC 1.0 API @purpose : fetches the specified rowset of data from the result set and returns data for all bound columns. Rowsets can be specified at an absolute or relative position */ SQLRETURN SQL_API SQLExtendedFetch(SQLHSTMT hstmt,SQLUSMALLINT fFetchType, SQLINTEGER irow, SQLUINTEGER FAR *pcrow, SQLUSMALLINT FAR *rgfRowStatus) { return my_SQLExtendedFetch(hstmt, fFetchType, irow, pcrow, rgfRowStatus); } /* @type : ODBC 3.0 API @purpose : fetches the specified rowset of data from the result set and returns data for all bound columns. Rowsets can be specified at an absolute or relative position */ SQLRETURN SQL_API SQLFetchScroll(SQLHSTMT StatementHandle, SQLSMALLINT FetchOrientation, SQLINTEGER FetchOffset) { SQLRETURN result=SQL_SUCCESS; STMT_OPTIONS *options = &((STMT FAR *)StatementHandle)->stmt_options; DBUG_ENTER("SQLFetchScroll"); if (!options->cursor_scollable && FetchOrientation != SQL_FETCH_NEXT) DBUG_RETURN(set_handle_error(SQL_HANDLE_STMT,StatementHandle,MYERR_S1106, NULL,0)); result = my_SQLExtendedFetch(StatementHandle, FetchOrientation, FetchOffset, options->rowsFetchedPtr, options->rowStatusPtr); DBUG_RETURN(result); }