From: Alistair Leslie-Hughes leslie_alistair@hotmail.com
--- dlls/msado15/recordset.c | 404 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 402 insertions(+), 2 deletions(-)
diff --git a/dlls/msado15/recordset.c b/dlls/msado15/recordset.c index c22b2460147..1f4cf69737e 100644 --- a/dlls/msado15/recordset.c +++ b/dlls/msado15/recordset.c @@ -24,6 +24,7 @@ #include "objbase.h" #include "msado15_backcompat.h" #include "oledb.h" +#include "sqlucode.h"
#include "wine/debug.h"
@@ -49,6 +50,9 @@ struct recordset IRowset *row_set; EditModeEnum editmode; VARIANT filter; + + DBTYPE *columntypes; + HACCESSOR *haccessors; };
struct fields @@ -1201,8 +1205,12 @@ static void close_recordset( struct recordset *recordset ) { ULONG row, col, col_count; ULONG i; + IAccessor *accessor;
- if ( recordset->row_set ) IRowset_Release( recordset->row_set ); + if (recordset->fields) + IRowset_QueryInterface(recordset->row_set, &IID_IAccessor, (void**)&accessor); + + if ( recordset->row_set ) return; recordset->row_set = NULL;
VariantClear( &recordset->filter ); @@ -1210,12 +1218,18 @@ static void close_recordset( struct recordset *recordset ) if (!recordset->fields) return; col_count = get_column_count( recordset );
+ free(recordset->columntypes); + for (i = 0; i < col_count; i++) { struct field *field = impl_from_Field( recordset->fields->field[i] ); field->recordset = NULL; Field_Release(&field->Field_iface); + + IAccessor_ReleaseAccessor(accessor, recordset->haccessors[i], NULL); } + free(recordset->haccessors); + IAccessor_Release(accessor); recordset->fields->count = 0; Fields_Release( &recordset->fields->Fields_iface ); recordset->fields = NULL; @@ -1236,6 +1250,7 @@ static ULONG WINAPI recordset_Release( _Recordset *iface ) if (!refs) { TRACE( "destroying %p\n", recordset ); + close_recordset( recordset ); free( recordset ); } @@ -1672,6 +1687,349 @@ static HRESULT create_command_text(IUnknown *session, BSTR command, ICommandText return S_OK; }
+#define ROUND_SIZE(size) (((size) + sizeof(void *) - 1) & ~(sizeof(void *) - 1)) + +DEFINE_GUID(DBPROPSET_ROWSET, 0xc8b522be, 0x5cf3, 0x11ce, 0xad, 0xe5, 0x00, 0xaa, 0x00, 0x44, 0x77, 0x3d); + +static VARIANT_BOOL properties_multiple_objects(IUnknown *unk) +{ + VARIANT_BOOL supported = VARIANT_FALSE; + IRowsetInfo *info; + HRESULT hr; + ULONG propcnt; + DBPROPIDSET propidset; + DBPROPSET *propset; + DBPROPID row_props[1]; + + hr = IUnknown_QueryInterface(unk, &IID_IRowsetInfo, (void**)&info); + if (FAILED(hr)) + return supported; + + row_props[0] = DBPROP_MULTIPLESTORAGEOBJECTS; + propidset.rgPropertyIDs = row_props; + propidset.cPropertyIDs = 1; + propidset.guidPropertySet = DBPROPSET_ROWSET; + + hr = IRowsetInfo_GetProperties(info, 1, &propidset, &propcnt, &propset); + if (SUCCEEDED(hr)) + supported = V_BOOL(&propset->rgProperties[0].vValue); + + CoTaskMemFree(propset[0].rgProperties); + CoTaskMemFree(propset); + + IRowsetInfo_Release(info); + + return supported; +} + +static HRESULT create_bindings(IUnknown *rowset, struct recordset *recordset, DBBINDING **bind, DBBYTEOFFSET *size) +{ + HRESULT hr; + IColumnsInfo *columninfo; + IAccessor *accessor; + DBORDINAL columns; + DBCOLUMNINFO *colinfo; + OLECHAR *stringsbuffer; + DBBINDING *bindings; + DBBYTEOFFSET offset; + VARIANT_BOOL multiple; + + *size = 0; + + hr = IUnknown_QueryInterface(rowset, &IID_IColumnsInfo, (void**)&columninfo); + if (FAILED(hr)) + return hr; + + hr = IUnknown_QueryInterface(rowset, &IID_IAccessor, (void**)&accessor); + if (FAILED(hr)) + { + IColumnsInfo_Release(columninfo); + return hr; + } + + multiple = properties_multiple_objects(rowset); + TRACE("Multiple Object streams supported %d\n", multiple); + + hr = IColumnsInfo_GetColumnInfo(columninfo, &columns, &colinfo, &stringsbuffer); + if (SUCCEEDED(hr)) + { + ULONG i; + DBOBJECT *dbobj; + offset = 1; + + recordset->columntypes = malloc(sizeof(DBTYPE) * columns); + recordset->haccessors = calloc(1, sizeof(HACCESSOR) * columns ); + + /* Do one allocation for the bindings and append the DBOBJECTS to the end. + * This is to save on multiple allocations vs a little bit of extra memory. + */ + bindings = CoTaskMemAlloc( (sizeof(DBBINDING) + sizeof(DBOBJECT)) * columns); + dbobj = (DBOBJECT *)((char*)bindings + (sizeof(DBBINDING) * columns)); + + for (i=0; i < columns; i++) + { + TRACE("Column %lu, pwszName: %s, pTypeInfo %p, iOrdinal %Iu, dwFlags 0x%08lx, " + "ulColumnSize %Iu, wType %d, bPrecision %d, bScale %d\n", + i, debugstr_w(colinfo[i].pwszName), colinfo[i].pTypeInfo, colinfo[i].iOrdinal, + colinfo[i].dwFlags, colinfo[i].ulColumnSize, colinfo[i].wType, + colinfo[i].bPrecision, colinfo[i].bScale); + + hr = append_field(recordset->fields, colinfo[i].pwszName, colinfo[i].wType, colinfo[i].ulColumnSize, + colinfo[i].dwFlags, NULL); + + bindings[i].iOrdinal = colinfo[i].iOrdinal; + bindings[i].obValue = offset; + bindings[i].pTypeInfo = NULL; + /* Always assigned the pObject even if it's not used. */ + bindings[i].pObject = &dbobj[i]; + bindings[i].pObject->dwFlags = 0; + bindings[i].pObject->iid = IID_ISequentialStream; + bindings[i].pBindExt = NULL; + bindings[i].dwPart = DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS; + bindings[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED; + bindings[i].eParamIO = 0; + + recordset->columntypes[i] = colinfo[i].wType; + if (colinfo[i].dwFlags & DBCOLUMNFLAGS_ISLONG) + { + colinfo[i].wType = DBTYPE_IUNKNOWN; + + bindings[i].cbMaxLen = (colinfo[i].ulColumnSize + 1) * sizeof(WCHAR); + offset += sizeof(ISequentialStream*); + } + else if(colinfo[i].wType == DBTYPE_WSTR) + { + /* ulColumnSize is the number of characters in the string not the actual buffer size */ + bindings[i].cbMaxLen = colinfo[i].ulColumnSize * sizeof(WCHAR); + offset += bindings[i].cbMaxLen; + } + else + { + bindings[i].cbMaxLen = colinfo[i].ulColumnSize; + offset += bindings[i].cbMaxLen; + } + + bindings[i].dwFlags = 0; + bindings[i].wType = colinfo[i].wType; + bindings[i].bPrecision = colinfo[i].bPrecision; + bindings[i].bScale = colinfo[i].bScale; + } + + offset = ROUND_SIZE(offset); + for (i=0; i < columns; i++) + { + bindings[i].obLength = offset; + bindings[i].obStatus = offset + sizeof(DBBYTEOFFSET); + + offset += sizeof(DBBYTEOFFSET) + sizeof(DBBYTEOFFSET); + + hr = IAccessor_CreateAccessor(accessor, DBACCESSOR_ROWDATA, 1, &bindings[i], 0, &recordset->haccessors[i], NULL); + if (FAILED(hr)) + FIXME("IAccessor_CreateAccessor Failed 0x%0lx\n", hr); + } + + *size = offset; + *bind = bindings; + + CoTaskMemFree(colinfo); + CoTaskMemFree(stringsbuffer); + } + + IAccessor_Release(accessor); + + IColumnsInfo_Release(columninfo); + + return hr; +} + +static HRESULT load_all_recordset_data(struct recordset *recordset, IUnknown *rowset, DBBINDING *bindings, + DBBYTEOFFSET datasize) +{ + IRowset *rowset2; + DBORDINAL columns; + HRESULT hr; + DBCOUNTITEM obtained; + HROW *row = NULL; + int datarow = 0, datacol; + char *data; + + columns = get_column_count(recordset); + + /* Create the data array */ + if (!resize_recordset( recordset, recordset->count )) + { + WARN("Failed to resize recordset\n"); + return E_OUTOFMEMORY; + } + + hr = IUnknown_QueryInterface(rowset, &IID_IRowset, (void**)&rowset2); + if (FAILED(hr)) + { + WARN("Failed to get IRowset interface (0x%08lx)\n", hr); + return hr; + } + + data = malloc (datasize); + if (!data) + { + ERR("Failed to allocate row data (%Iu)\n", datasize); + IRowset_Release(rowset2); + return E_OUTOFMEMORY; + } + + hr = IRowset_GetNextRows(rowset2, 0, 0, 1, &obtained, &row); + while (hr == S_OK) + { + VARIANT copy; + + for (datacol = 0; datacol < columns; datacol++) + { + hr = IRowset_GetData(rowset2, *row, recordset->haccessors[datacol], data); + if (FAILED(hr)) + { + ERR("GetData Failed on Column %d (0x%08lx), status %Id\n", datacol, hr, + *(DBBYTEOFFSET*)(data + bindings[datacol].obStatus)); + break; + } + + VariantInit(©); + + /* For most cases DBTYPE_* = VT_* type */ + V_VT(©) = bindings[datacol].wType; + switch(bindings[datacol].wType) + { + case DBTYPE_IUNKNOWN: + { + ISequentialStream *seq; + IUnknown *unk = NULL; + + if ( *(DBBYTEOFFSET*)(data + bindings[datacol].obStatus) == DBSTATUS_S_ISNULL) + { + V_VT(©) = VT_NULL; + break; + } + + unk = *(IUnknown**)(data + bindings[datacol].obValue); + TRACE("Reading DBTYPE_IUNKNOWN %p\n", unk); + + if(IUnknown_QueryInterface(unk, &IID_ISequentialStream, (void**)&seq) == S_OK) + { + char unkdata[2048]; + ULONG size = 4096, dataRead = 0, total = 0; + char *buffer = malloc(size), *p = buffer; + HRESULT hr2; + + do + { + dataRead = 0; + hr2 = ISequentialStream_Read(seq, unkdata, sizeof(unkdata), &dataRead); + if (FAILED(hr) || !dataRead) break; + + total += dataRead; + + memcpy(p, unkdata, dataRead); + p += dataRead; + + if (dataRead <= size) + { + size *= 2; /* Double buffer */ + buffer = realloc(buffer, size); + p = buffer + total; + } + } while(hr2 == S_OK); + + if (recordset->columntypes[datacol] == DBTYPE_WSTR) + { + V_VT(©) = VT_BSTR; + V_BSTR(©) = SysAllocStringLen( (WCHAR*)buffer, total / sizeof(WCHAR) ); + } + else if (recordset->columntypes[datacol] == DBTYPE_BYTES) + { + SAFEARRAYBOUND sab; + + sab.lLbound = 0; + sab.cElements = total; + + V_VT(©) = (VT_ARRAY|VT_UI1); + V_ARRAY(©) = SafeArrayCreate(VT_UI1, 1, &sab); + + memcpy( (BYTE*)V_ARRAY(©)->pvData, buffer, total); + } + else + { + FIXME("Unsupported conversion (%d)\n", recordset->columntypes[datacol]); + V_VT(©) = VT_NULL; + } + + free(buffer); + ISequentialStream_Release(seq); + } + + break; + } + case DBTYPE_R8: + V_R8(©) = *(DOUBLE*)(data + bindings[datacol].obValue); + break; + case DBTYPE_I8: + V_VT(©) = VT_I4; + V_I4(©) = *(LONG*)(data + bindings[datacol].obValue); + break; + case DBTYPE_I4: + V_I4(©) = *(LONG*)(data + bindings[datacol].obValue); + break; + case DBTYPE_WSTR: + V_VT(©) = VT_BSTR; + V_BSTR(©) = SysAllocString( (WCHAR*)(data + bindings[datacol].obValue) ); + break; + case DBTYPE_DBTIMESTAMP: + { + SYSTEMTIME st; + DBTIMESTAMP *ts = (DBTIMESTAMP *)(data + bindings[datacol].obValue); + DATE d; + + V_VT(©) = VT_DATE; + + st.wYear = ts->year; + st.wMonth = ts->month; + st.wDay = ts->day; + st.wHour = ts->hour; + st.wMinute = ts->minute; + st.wSecond = ts->second; + st.wMilliseconds = ts->fraction/1000000; + hr = (SystemTimeToVariantTime(&st, &d) ? S_OK : E_FAIL); + + V_DATE(©) = d; + break; + } + default: + V_I2(©) = 0; + FIXME("Unknown Type %d\n", bindings[datacol].wType); + } + + VariantInit( &recordset->data[datarow * columns + datacol] ); + if ((hr = VariantCopy( &recordset->data[datarow * columns + datacol] , ©)) != S_OK) + { + ERR("Column %d copy failed. Data %s\n", datacol, debugstr_variant(©)); + } + + VariantClear(©); + } + + datarow++; + + hr = IRowset_ReleaseRows(rowset2, 1, row, NULL, NULL, NULL); + if (FAILED(hr)) + ERR("Failed to ReleaseRows 0x%08lx\n", hr); + + hr = IRowset_GetNextRows(rowset2, 0, 0, 1, &obtained, &row); + } + + free(data); + IRowset_Release(rowset2); + + return S_OK; +} + static HRESULT WINAPI recordset_Open( _Recordset *iface, VARIANT source, VARIANT active_connection, CursorTypeEnum cursor_type, LockTypeEnum lock_type, LONG options ) { @@ -1682,8 +2040,10 @@ static HRESULT WINAPI recordset_Open( _Recordset *iface, VARIANT source, VARIANT DBROWCOUNT affected; IUnknown *rowset; HRESULT hr; + DBBINDING *bindings; + DBBYTEOFFSET datasize;
- FIXME( "%p, %s, %s, %d, %d, %ld Semi-stub\n", recordset, debugstr_variant(&source), debugstr_variant(&active_connection), + TRACE( "%p, %s, %s, %d, %d, %ld\n", recordset, debugstr_variant(&source), debugstr_variant(&active_connection), cursor_type, lock_type, options );
if (recordset->state == adStateOpen) return MAKE_ADO_HRESULT( adErrObjectOpen ); @@ -1726,6 +2086,44 @@ static HRESULT WINAPI recordset_Open( _Recordset *iface, VARIANT source, VARIANT if (FAILED(hr) || !rowset) return hr;
+ /* We want to create the field member variable without mapping the rowset fields, this will + * save quering the fields twice. Fields will be added while we create the bindings + */ + hr = fields_create( recordset, &recordset->fields ); + if (FAILED(hr)) + { + IUnknown_Release(rowset); + return hr; + } + + hr = create_bindings(rowset, recordset, &bindings, &datasize); + if (FAILED(hr)) + { + WARN("Failed to load bindings (%lx)\n", hr); + IUnknown_Release(rowset); + return hr; + } + + recordset->count = affected; + recordset->index = affected ? 0 : -1; + + /* + * We can safely just return with an empty recordset here + */ + if (affected > 0) + { + hr = load_all_recordset_data(recordset, rowset, bindings, datasize); + if (FAILED(hr)) + { + WARN("Failed to load all recordset data (%lx)\n", hr); + CoTaskMemFree(bindings); + IUnknown_Release(rowset); + return hr; + } + } + + CoTaskMemFree(bindings); + ADORecordsetConstruction_put_Rowset(&recordset->ADORecordsetConstruction_iface, rowset); recordset->cursor_type = cursor_type; recordset->state = adStateOpen; @@ -2339,6 +2737,8 @@ HRESULT Recordset_create( void **obj ) recordset->row_set = NULL; recordset->editmode = adEditNone; VariantInit( &recordset->filter ); + recordset->columntypes = NULL; + recordset->haccessors = NULL;
*obj = &recordset->Recordset_iface; TRACE( "returning iface %p\n", *obj );