Find only supports one Column to filter on.
The parser for this will be the same for both Find/Filter with the only different is the Filter supports multiple columns.
In trying to keep this patch small, only WSTR/BSTR/Integer have been supported. (The Common cases).
-- v3: msado15: Implement _Recordset Find
From: Alistair Leslie-Hughes leslie_alistair@hotmail.com
Recordset::Find only supports one Column filter. --- dlls/msado15/recordset.c | 287 ++++++++++++++++++++++++++++++++- dlls/msado15/tests/Makefile.in | 2 +- dlls/msado15/tests/msado15.c | 188 ++++++++++++++++++++- 3 files changed, 473 insertions(+), 4 deletions(-)
diff --git a/dlls/msado15/recordset.c b/dlls/msado15/recordset.c index a70fc2b33ac..908f03782dd 100644 --- a/dlls/msado15/recordset.c +++ b/dlls/msado15/recordset.c @@ -32,6 +32,23 @@
WINE_DEFAULT_DEBUG_CHANNEL(msado15);
+enum eCondition { + eGreater = 1, + eGreaterEqual, + eLess, + eLessEqual, + eNotEqual, + eEqual, + eLike +}; + +struct column_filter +{ + LONG column; + enum eCondition condition; + VARIANT value; +}; + struct fields; struct recordset { @@ -50,6 +67,8 @@ struct recordset IRowset *row_set; EditModeEnum editmode; VARIANT filter; + BSTR criteria; + struct column_filter *criteria_col;
DBTYPE *columntypes; HACCESSOR *haccessors; @@ -1229,6 +1248,13 @@ static void close_recordset( struct recordset *recordset ) if ( recordset->row_set ) IRowset_Release( recordset->row_set ); recordset->row_set = NULL;
+ SysFreeString(recordset->criteria); + if (recordset->criteria_col) + { + VariantClear( &recordset->criteria_col->value ); + free(recordset->criteria_col); + } + VariantClear( &recordset->filter );
if (!recordset->fields) return; @@ -2327,12 +2353,267 @@ static HRESULT WINAPI recordset_put_MarshalOptions( _Recordset *iface, MarshalOp return E_NOTIMPL; }
+static LONG find_column_index(struct fields *fields, WCHAR *name) +{ + VARIANT column; + LONG ret = -1; + ULONG index; + + V_VT(&column) = VT_BSTR; + V_BSTR(&column) = SysAllocString(name); + + if (map_index(fields, &column, &index) == S_OK) + ret = index; + VariantClear(&column); + + return ret; +} + +static BOOL column_match_string(int condition, BSTR col_data, BSTR filter_data) +{ + BOOL match = FALSE; + + switch (condition) + { + case eEqual: + match = lstrcmpW(col_data, filter_data) == 0; + break; + case eNotEqual: + match = lstrcmpW(col_data, filter_data) != 0; + break; + case eLike: + FIXME("LIKE currently not supported\n"); + break; + default: + FIXME("Unsupported condition %d\n", condition); + } + + return match; +} + +static BOOL column_match_integer(int condition, INT col_data, INT filter_data) +{ + BOOL match = FALSE; + + switch (condition) + { + case eEqual: + match = col_data == filter_data; + break; + case eNotEqual: + match = col_data != filter_data; + break; + case eLess: + match = col_data < filter_data; + break; + case eLessEqual: + match = col_data <= filter_data; + break; + case eGreater: + match = col_data > filter_data; + break; + case eGreaterEqual: + match = col_data >= filter_data; + break; + default: + FIXME("Unsupported condition %d\n", condition); + } + + return match; +} + +static HRESULT parse_find_filter(struct recordset *recordset, WCHAR *ptr) +{ + WCHAR data[1024]; + int i = 0; + + /* Find Column name */ + while (*ptr && (iswalnum(*ptr) || *ptr == '_')) + { + data[i++] = *ptr; + ptr++; + } + data[i] = '\0'; + + if(!*data) + return MAKE_ADO_HRESULT( adErrInvalidArgument ); + + recordset->criteria_col->column = find_column_index(recordset->fields, data); + if (recordset->criteria_col->column == -1) + return MAKE_ADO_HRESULT( adErrItemNotFound ); + + /* Ignore whitespace */ + while (*ptr && *ptr == ' ') ptr++; + + /* Operation */ + switch (*ptr) + { + case '=': + recordset->criteria_col->condition = eEqual; + ptr++; + break; + + case '>': + recordset->criteria_col->condition = eGreater; + ptr++; + if (*ptr == '=') + { + recordset->criteria_col->condition = eGreaterEqual; + ptr++; + } + break; + + case '<': + recordset->criteria_col->condition = eLess; + ptr++; + if (*ptr == '=') + { + recordset->criteria_col->condition = eLessEqual; + ptr++; + } + else if (*ptr == '>') + { + recordset->criteria_col->condition = eNotEqual; + ptr++; + } + break; + + case '\0': + return MAKE_ADO_HRESULT( adErrInvalidArgument ); + + default: + FIXME("Currently unsupported operation\n"); + return E_FAIL; + } + + /* Ignore whitespace */ + while (*ptr && *ptr == ' ') ptr++; + + VariantInit(&recordset->criteria_col->value); + + /* Value */ + if (*ptr) + { + BOOL is_float = FALSE; + if (*ptr == ''') + { + ptr++; + V_VT(&recordset->criteria_col->value) = VT_BSTR; + V_BSTR(&recordset->criteria_col->value) = SysAllocStringLen(ptr, wcslen(ptr)-1); + } + else + { + i = 0; + while (*ptr) + { + if(iswdigit(*ptr) || *ptr == '.') + { + if(*ptr == '.') + is_float = TRUE; + + data[i++] = *ptr; + ptr++; + } + else if (*ptr == '\0') + break; + else + return MAKE_ADO_HRESULT( adErrInvalidArgument ); + } + data[i++] = '\0'; + + if(is_float) + { + V_VT(&recordset->criteria_col->value) = VT_R4; + V_R4(&recordset->criteria_col->value) = _wtof(data); + } + else + { + V_VT(&recordset->criteria_col->value) = VT_I4; + V_I4(&recordset->criteria_col->value) = _wtoi(data); + } + } + } + else + return MAKE_ADO_HRESULT( adErrInvalidArgument ); + + return S_OK; +} + static HRESULT WINAPI recordset_Find( _Recordset *iface, BSTR criteria, LONG skip_records, SearchDirectionEnum search_direction, VARIANT start ) { - FIXME( "%p, %s, %ld, %d, %s\n", iface, debugstr_w(criteria), skip_records, search_direction, + struct recordset *recordset = impl_from_Recordset( iface ); + BOOL found = FALSE; + ULONG colcnt; + DataTypeEnum datatype; + + TRACE( "%p, %s, %ld, %d, %s\n", recordset, debugstr_w(criteria), skip_records, search_direction, debugstr_variant(&start) ); - return E_NOTIMPL; + + if (recordset->state != adStateOpen) return MAKE_ADO_HRESULT( adErrObjectClosed ); + + if(!criteria || !*criteria) + return MAKE_ADO_HRESULT( adErrInvalidArgument ); + + if (!recordset->criteria || wcscmp(recordset->criteria, criteria) != 0) + { + HRESULT hr; + + SysFreeString(recordset->criteria); + if (recordset->criteria_col) + free(recordset->criteria_col); + + recordset->criteria = SysAllocString(criteria); + recordset->criteria_col = malloc(sizeof(struct column_filter)); + if (!recordset->criteria_col) + return E_OUTOFMEMORY; + + hr = parse_find_filter(recordset, criteria); + if(FAILED(hr)) + return hr; + + TRACE("Column %ld, condition %u, %s\n", recordset->criteria_col->column, + recordset->criteria_col->condition, debugstr_variant(&recordset->criteria_col->value)); + } + + colcnt = get_column_count(recordset); + + field_get_Type(recordset->fields->field[recordset->criteria_col->column], &datatype); + + recordset->index += skip_records; + for(; recordset->index < recordset->count; recordset->index++) + { + found = FALSE; + + switch(datatype) + { + case adBSTR: + case adWChar: + { + found = column_match_string(recordset->criteria_col->condition, + V_BSTR(&recordset->data[recordset->index * colcnt + recordset->criteria_col->column]), + V_BSTR(&recordset->criteria_col->value)); + break; + } + case adInteger: + { + found = column_match_integer(recordset->criteria_col->condition, + V_I4(&recordset->data[recordset->index * colcnt + recordset->criteria_col->column]), + V_I4(&recordset->criteria_col->value)); + break; + } + default: + FIXME("Unsupported datatype %d\n", datatype); + } + + if (found) + { + TRACE("Return record %ld\n", recordset->index); + break; + } + } + + return S_OK; }
static HRESULT WINAPI recordset_Cancel( _Recordset *iface ) @@ -2739,6 +3020,8 @@ HRESULT Recordset_create( void **obj ) VariantInit( &recordset->filter ); recordset->columntypes = NULL; recordset->haccessors = NULL; + recordset->criteria = NULL; + recordset->criteria_col = NULL;
*obj = &recordset->Recordset_iface; TRACE( "returning iface %p\n", *obj ); diff --git a/dlls/msado15/tests/Makefile.in b/dlls/msado15/tests/Makefile.in index a97f3dd4339..e7ade35fa38 100644 --- a/dlls/msado15/tests/Makefile.in +++ b/dlls/msado15/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = msado15.dll -IMPORTS = oleaut32 ole32 +IMPORTS = oleaut32 ole32 odbccp32
C_SRCS = \ msado15.c diff --git a/dlls/msado15/tests/msado15.c b/dlls/msado15/tests/msado15.c index 724787870e0..b130846b5b8 100644 --- a/dlls/msado15/tests/msado15.c +++ b/dlls/msado15/tests/msado15.c @@ -19,8 +19,11 @@ #include <stdio.h> #define COBJMACROS #include <initguid.h> +#include <msdasc.h> #include <oledb.h> #include <olectl.h> +#include <oledberr.h> +#include <odbcinst.h> #include <msado15_backcompat.h> #include "wine/test.h" #include "msdasql.h" @@ -57,7 +60,7 @@ static void test_Recordset(void) VARIANT missing, val, index; CursorLocationEnum location; CursorTypeEnum cursor; - BSTR name; + BSTR name, crit; HRESULT hr; VARIANT bookmark, filter; EditModeEnum editmode; @@ -143,6 +146,12 @@ static void test_Recordset(void) hr = _Recordset_put_Filter( recordset, filter ); ok( hr == S_OK, "got %08lx\n", hr );
+ crit = SysAllocString(L"colu1=1"); + V_VT( &index ) = VT_EMPTY; + hr = _Recordset_Find( recordset, crit, 0, adSearchForward, index ); + ok( hr == MAKE_ADO_HRESULT( adErrObjectClosed ), "got %08lx\n", hr ); + SysFreeString(crit); + VariantInit( &missing ); hr = _Recordset_AddNew( recordset, missing, missing ); ok( hr == MAKE_ADO_HRESULT( adErrObjectClosed ), "got %08lx\n", hr ); @@ -1335,6 +1344,182 @@ static void test_Command(void) _Command_Release( command ); }
+static BOOL db_created = TRUE; +static char mdbpath[MAX_PATH]; + +static void setup_database(void) +{ + char *driver; + DWORD code; + char buffer[1024]; + WORD size; + + if (winetest_interactive) + { + trace("assuming odbc 'wine_msado15' is available\n"); + db_created = TRUE; + return; + } + + /* + * 32 bit Windows has a default driver for "Microsoft Access Driver" (Windows 7+) + * and has the ability to create files on the fly. + * + * 64 bit Windows ONLY has a driver for "SQL Server", which we cannot use since we don't have a + * server to connect to. + * + * The filename passed to CREATE_DB must end in mdb. + */ + GetTempPathA(sizeof(mdbpath), mdbpath); + strcat(mdbpath, "wine_msado15.mdb"); + + driver = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof("DSN=wine_msado15\0CREATE_DB=") + strlen(mdbpath) + 2); + memcpy(driver, "DSN=wine_msado15\0CREATE_DB=", sizeof("DSN=wine_msado15\0CREATE_DB=")); + strcat(driver+sizeof("DSN=wine_msado15\0CREATE_DB=")-1, mdbpath); + + db_created = SQLConfigDataSource(NULL, ODBC_ADD_DSN, "Microsoft Access Driver (*.mdb)", driver); + if (!db_created) + { + SQLInstallerError(1, &code, buffer, sizeof(buffer), &size); + trace("code %ld, buffer %s, size %d\n", code, debugstr_a(buffer), size); + + HeapFree(GetProcessHeap(), 0, driver); + + return; + } + + memcpy(driver, "DSN=wine_msado15\0DBQ=", sizeof("DSN=wine_msado15\0DBQ=")); + strcat(driver+sizeof("DSN=wine_msado15\0DBQ=")-1, mdbpath); + db_created = SQLConfigDataSource(NULL, ODBC_ADD_DSN, "Microsoft Access Driver (*.mdb)", driver); + + HeapFree(GetProcessHeap(), 0, driver); +} + +static void cleanup_database(void) +{ + BOOL ret; + + if (winetest_interactive) + return; + + ret = SQLConfigDataSource(NULL, ODBC_REMOVE_DSN, "Microsoft Access Driver (*.mdb)", "DSN=wine_msado15\0\0"); + if (!ret) + { + DWORD code; + char buffer[1024]; + WORD size; + + SQLInstallerError(1, &code, buffer, sizeof(buffer), &size); + trace("code %ld, buffer %s, size %d\n", code, debugstr_a(buffer), size); + } + + DeleteFileA(mdbpath); +} + +struct recfilter +{ + const WCHAR *filter; + HRESULT expected; +}; + +static void test_recordset_Find(void) +{ + HRESULT hr; + _Connection *connection; + _Recordset *recordset; + VARIANT start_index; + BSTR str; + int i; + static const struct recfilter filters[] = + { + {L"field1 = 1", MAKE_ADO_HRESULT( adErrItemNotFound ) }, + {L"col1 = 1 OR col3 = 2", MAKE_ADO_HRESULT( adErrInvalidArgument ) }, + {L"col1 = 3", S_OK }, + {L"'testing.col1' = 4", MAKE_ADO_HRESULT( adErrInvalidArgument ) }, + {L"col1 =", MAKE_ADO_HRESULT( adErrInvalidArgument ) }, + {L"col1 <> 5", S_OK }, + {L"col1 = 6", S_OK }, + {L"col1 >= 7", S_OK }, + {L"col1 <= 8", S_OK }, + {L"col3 < 9.4", S_OK }, + {L"", MAKE_ADO_HRESULT( adErrInvalidArgument ) }, + }; + + setup_database(); + + if (!db_created) + { + skip("ODBC source wine_msado15 not available.\n"); + return; + } + + str = SysAllocString(L"Provider=MSDASQL.1;Persist Security Info=False;Data Source=wine_msado15"); + + hr = CoCreateInstance( &CLSID_Connection, NULL, CLSCTX_INPROC_SERVER, &IID__Connection, (void**)&connection ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = _Connection_put_CursorLocation( connection, adUseClient ); + ok( hr == S_OK, "got %08lx\n", hr ); + + hr = _Connection_Open( connection, str, NULL, NULL, adConnectUnspecified ); + ok( hr == S_OK, "got %08lx\n", hr ); + SysFreeString( str ); + + str = SysAllocString(L"CREATE TABLE testing (col1 INT, col2 VARCHAR(20) NOT NULL, col3 FLOAT)"); + hr = _Connection_Execute(connection, str, NULL, 0, &recordset); + ok( hr == S_OK || hr == DB_E_ERRORSINCOMMAND /* Table already exists */, "got %08lx\n", hr ); + if (hr == S_OK) + _Recordset_Release(recordset); + SysFreeString( str ); + str = SysAllocString(L"insert into testing values(1, 'red', 1.0)"); + hr = _Connection_Execute(connection, str, NULL, 0, &recordset); + ok( hr == S_OK, "got %08lx\n", hr ); + _Recordset_Release(recordset); + SysFreeString( str ); + + str = SysAllocString(L"insert into testing values(2, 'blue', 2.0)"); + hr = _Connection_Execute(connection, str, NULL, 0, &recordset); + ok( hr == S_OK, "got %08lx\n", hr ); + _Recordset_Release(recordset); + SysFreeString( str ); + + str = SysAllocString(L"SELECT * from testing"); + hr = _Connection_Execute(connection, str, NULL, 0, &recordset); + ok( hr == S_OK, "got %08lx\n", hr ); + SysFreeString( str ); + + for (i=0; i < ARRAY_SIZE(filters); i++) + { + BSTR crit = SysAllocString( filters[i].filter ); + + hr = _Recordset_MoveFirst( recordset ); + ok( hr == S_OK, "got %08lx\n", hr ); + + V_VT( &start_index ) = VT_I4; + V_I4( &start_index) = 0; + + hr = _Recordset_Find( recordset, crit, 0, adSearchForward, start_index ); + ok( hr == filters[i].expected, "%d got %08lx expected %08lx\n", i, hr, filters[i].expected ); + SysFreeString( crit ); + } + str = SysAllocStringLen( NULL, 0 ); + hr = _Recordset_Find( recordset, str, 0, adSearchForward, start_index ); + ok( hr == MAKE_ADO_HRESULT( adErrInvalidArgument ), "got %08lx\n", hr ); + SysFreeString( str ); + + hr = _Recordset_Find( recordset, NULL, 0, adSearchForward, start_index ); + ok( hr == MAKE_ADO_HRESULT( adErrInvalidArgument ), "got %08lx\n", hr ); + + hr = _Recordset_Close( recordset ); + ok( hr == S_OK, "got %08lx\n", hr ); + _Recordset_Release( recordset ); + + hr = _Connection_Close( connection ); + ok( hr == S_OK, "got %08lx\n", hr ); + + cleanup_database(); +} + struct conn_event { ConnectionEventsVt conn_event_sink; LONG refs; @@ -1554,6 +1739,7 @@ START_TEST(msado15) test_ConnectionPoint(); test_Fields(); test_Recordset(); + test_recordset_Find(); test_Stream(); test_Command(); CoUninitialize();