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 );
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=130132
Your paranoid android.
=== debian11 (32 bit report) ===
msado15: Unhandled exception: page fault on read access to 0x00000000 in 32-bit code (0x01b77865).
=== debian11 (32 bit zh:CN report) ===
msado15: Unhandled exception: page fault on read access to 0x00000000 in 32-bit code (0x01bd7865).
=== debian11b (64 bit WoW report) ===
msado15: Unhandled exception: page fault on read access to 0x0000000000000000 in 64-bit code (0x0000006e20706c).