From f03889ae823635027b342089641ba1538b928038 Mon Sep 17 00:00:00 2001 From: James Hawkins Date: Wed, 8 Oct 2008 22:42:33 -0500 Subject: [PATCH] msi: Add initial implementation of MsiDatabaseMerge, with tests. --- dlls/msi/database.c | 497 +++++++++++++++++++++++++++++++++++++++++++++++++++- dlls/msi/msipriv.h | 2 + dlls/msi/record.c | 71 ++++++++ dlls/msi/tests/db.c | 449 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 1014 insertions(+), 5 deletions(-) diff --git a/dlls/msi/database.c b/dlls/msi/database.c index 134c6e127cb..7dc1c063d91 100644 --- a/dlls/msi/database.c +++ b/dlls/msi/database.c @@ -35,6 +35,7 @@ #include "objidl.h" #include "objbase.h" #include "msiserver.h" +#include "query.h" #include "initguid.h" @@ -999,13 +1000,505 @@ UINT WINAPI MsiDatabaseMergeA(MSIHANDLE hDatabase, MSIHANDLE hDatabaseMerge, return r; } +typedef struct _tagMERGETABLE +{ + struct list entry; + struct list rows; + LPWSTR name; + DWORD numconflicts; +} MERGETABLE; + +typedef struct _tagMERGEROW +{ + struct list entry; + MSIRECORD *data; +} MERGEROW; + +typedef struct _tagMERGEDATA +{ + MSIDATABASE *db; + MSIDATABASE *merge; + MERGETABLE *curtable; + MSIQUERY *curview; + struct list *tabledata; +} MERGEDATA; + +static UINT merge_verify_colnames(MSIQUERY *dbview, MSIQUERY *mergeview) +{ + MSIRECORD *dbrec, *mergerec; + UINT r, i, count; + + r = MSI_ViewGetColumnInfo(dbview, MSICOLINFO_NAMES, &dbrec); + if (r != ERROR_SUCCESS) + return r; + + r = MSI_ViewGetColumnInfo(mergeview, MSICOLINFO_NAMES, &mergerec); + if (r != ERROR_SUCCESS) + return r; + + count = MSI_RecordGetFieldCount(dbrec); + for (i = 1; i <= count; i++) + { + if (!MSI_RecordGetString(mergerec, i)) + break; + + if (lstrcmpW(MSI_RecordGetString(dbrec, i), + MSI_RecordGetString(mergerec, i))) + { + r = ERROR_DATATYPE_MISMATCH; + goto done; + } + } + + msiobj_release(&dbrec->hdr); + msiobj_release(&mergerec->hdr); + dbrec = mergerec = NULL; + + r = MSI_ViewGetColumnInfo(dbview, MSICOLINFO_TYPES, &dbrec); + if (r != ERROR_SUCCESS) + return r; + + r = MSI_ViewGetColumnInfo(mergeview, MSICOLINFO_TYPES, &mergerec); + if (r != ERROR_SUCCESS) + return r; + + count = MSI_RecordGetFieldCount(dbrec); + for (i = 1; i <= count; i++) + { + if (!MSI_RecordGetString(mergerec, i)) + break; + + if (lstrcmpW(MSI_RecordGetString(dbrec, i), + MSI_RecordGetString(mergerec, i))) + { + r = ERROR_DATATYPE_MISMATCH; + break; + } + } + +done: + msiobj_release(&dbrec->hdr); + msiobj_release(&mergerec->hdr); + + return r; +} + +static UINT merge_verify_primary_keys(MSIDATABASE *db, MSIDATABASE *mergedb, + LPCWSTR table) +{ + MSIRECORD *dbrec, *mergerec = NULL; + UINT r, i, count; + + r = MSI_DatabaseGetPrimaryKeys(db, table, &dbrec); + if (r != ERROR_SUCCESS) + return r; + + r = MSI_DatabaseGetPrimaryKeys(mergedb, table, &mergerec); + if (r != ERROR_SUCCESS) + goto done; + + count = MSI_RecordGetFieldCount(dbrec); + if (count != MSI_RecordGetFieldCount(mergerec)) + { + r = ERROR_DATATYPE_MISMATCH; + goto done; + } + + for (i = 1; i <= count; i++) + { + if (lstrcmpW(MSI_RecordGetString(dbrec, i), + MSI_RecordGetString(mergerec, i))) + { + r = ERROR_DATATYPE_MISMATCH; + goto done; + } + } + +done: + msiobj_release(&dbrec->hdr); + msiobj_release(&mergerec->hdr); + + return r; +} + +static LPWSTR get_key_value(MSIQUERY *view, LPCWSTR key, MSIRECORD *rec) +{ + MSIRECORD *colnames; + LPWSTR str; + UINT r, i = 0; + int cmp; + + r = MSI_ViewGetColumnInfo(view, MSICOLINFO_NAMES, &colnames); + if (r != ERROR_SUCCESS) + return NULL; + + do + { + str = msi_dup_record_field(colnames, ++i); + cmp = lstrcmpW(key, str); + msi_free(str); + } while (cmp); + + msiobj_release(&colnames->hdr); + return msi_dup_record_field(rec, i); +} + +static LPWSTR create_diff_row_query(MSIDATABASE *merge, MSIQUERY *view, + LPWSTR table, MSIRECORD *rec) +{ + LPWSTR query = NULL, clause = NULL; + LPWSTR ptr = NULL, val; + LPCWSTR setptr; + DWORD size = 1, oldsize; + LPCWSTR key; + MSIRECORD *keys; + UINT r, i, count; + + static const WCHAR keyset[] = { + '`','%','s','`',' ','=',' ','%','s',' ','A','N','D',' ',0}; + static const WCHAR lastkeyset[] = { + '`','%','s','`',' ','=',' ','%','s',' ',0}; + static const WCHAR fmt[] = {'S','E','L','E','C','T',' ','*',' ', + 'F','R','O','M',' ','`','%','s','`',' ', + 'W','H','E','R','E',' ','%','s',0}; + + r = MSI_DatabaseGetPrimaryKeys(merge, table, &keys); + if (r != ERROR_SUCCESS) + return NULL; + + clause = msi_alloc_zero(size * sizeof(WCHAR)); + if (!clause) + goto done; + + ptr = clause; + count = MSI_RecordGetFieldCount(keys); + for (i = 1; i <= count; i++) + { + key = MSI_RecordGetString(keys, i); + val = get_key_value(view, key, rec); + + if (i == count) + setptr = lastkeyset; + else + setptr = keyset; + + oldsize = size; + size += lstrlenW(setptr) + lstrlenW(key) + lstrlenW(val) - 4; + clause = msi_realloc(clause, size * sizeof (WCHAR)); + if (!clause) + { + msi_free(val); + goto done; + } + + ptr = clause + oldsize - 1; + sprintfW(ptr, setptr, key, val); + msi_free(val); + } + + size = lstrlenW(fmt) + lstrlenW(table) + lstrlenW(clause) + 1; + query = msi_alloc(size * sizeof(WCHAR)); + if (!query) + goto done; + + sprintfW(query, fmt, table, clause); + +done: + msi_free(clause); + msiobj_release(&keys->hdr); + return query; +} + +static UINT merge_diff_row(MSIRECORD *rec, LPVOID param) +{ + MERGEDATA *data = (MERGEDATA *)param; + MERGETABLE *table = data->curtable; + MERGEROW *mergerow; + MSIQUERY *dbview; + MSIRECORD *row; + LPWSTR query; + UINT r; + + query = create_diff_row_query(data->merge, data->curview, table->name, rec); + if (!query) + return ERROR_OUTOFMEMORY; + + r = MSI_DatabaseOpenViewW(data->db, query, &dbview); + if (r != ERROR_SUCCESS) + goto done; + + r = MSI_ViewExecute(dbview, NULL); + if (r != ERROR_SUCCESS) + goto done; + + r = MSI_ViewFetch(dbview, &row); + if (r == ERROR_SUCCESS && !MSI_RecordsAreEqual(rec, row)) + { + table->numconflicts++; + goto done; + } + else if (r != ERROR_NO_MORE_ITEMS) + goto done; + + mergerow = msi_alloc(sizeof(MERGEROW)); + if (!mergerow) + { + r = ERROR_OUTOFMEMORY; + goto done; + } + + mergerow->data = MSI_CloneRecord(rec); + if (!mergerow->data) + { + r = ERROR_OUTOFMEMORY; + msi_free(mergerow); + goto done; + } + + list_add_tail(&table->rows, &mergerow->entry); + +done: + msi_free(query); + msiobj_release(&row->hdr); + msiobj_release(&dbview->hdr); + return r; +} + +static UINT merge_diff_tables(MSIRECORD *rec, LPVOID param) +{ + MERGEDATA *data = (MERGEDATA *)param; + MERGETABLE *table; + MSIQUERY *dbview = NULL; + MSIQUERY *mergeview = NULL; + LPCWSTR name; + LPWSTR query; + DWORD size; + UINT r; + + static const WCHAR fmt[] = {'S','E','L','E','C','T',' ','*',' ', + 'F','R','O','M',' ','`','%','s','`',0}; + + name = MSI_RecordGetString(rec, 1); + size = lstrlenW(fmt) + lstrlenW(name) - 1; + query = msi_alloc(size * sizeof(WCHAR)); + if (!query) + return ERROR_OUTOFMEMORY; + + sprintfW(query, fmt, name); + + r = MSI_DatabaseOpenViewW(data->db, query, &dbview); + if (r != ERROR_SUCCESS) + goto done; + + r = MSI_DatabaseOpenViewW(data->merge, query, &mergeview); + if (r != ERROR_SUCCESS) + goto done; + + r = merge_verify_colnames(dbview, mergeview); + if (r != ERROR_SUCCESS) + goto done; + + r = merge_verify_primary_keys(data->db, data->merge, name); + if (r != ERROR_SUCCESS) + goto done; + + table = msi_alloc(sizeof(MERGETABLE)); + if (!table) + { + r = ERROR_OUTOFMEMORY; + goto done; + } + + list_init(&table->rows); + table->name = strdupW(name); + table->numconflicts = 0; + data->curtable = table; + data->curview = mergeview; + + r = MSI_IterateRecords(mergeview, NULL, merge_diff_row, data); + if (r != ERROR_SUCCESS) + { + msi_free(table->name); + msi_free(table); + goto done; + } + + list_add_tail(data->tabledata, &table->entry); + +done: + msi_free(query); + msiobj_release(&dbview->hdr); + msiobj_release(&mergeview->hdr); + return r; +} + +static UINT gather_merge_data(MSIDATABASE *db, MSIDATABASE *merge, + struct list *tabledata) +{ + UINT r; + MSIQUERY *view; + MERGEDATA data; + + static const WCHAR query[] = {'S','E','L','E','C','T',' ','*',' ', + 'F','R','O','M',' ','`','_','T','a','b','l','e','s','`',0}; + + r = MSI_DatabaseOpenViewW(merge, query, &view); + if (r != ERROR_SUCCESS) + return r; + + data.db = db; + data.merge = merge; + data.tabledata = tabledata; + r = MSI_IterateRecords(view, NULL, merge_diff_tables, &data); + + msiobj_release(&view->hdr); + return r; +} + +static UINT merge_table(MSIDATABASE *db, MERGETABLE *table) +{ + UINT r; + MERGEROW *row; + MSIVIEW *tv; + + LIST_FOR_EACH_ENTRY(row, &table->rows, MERGEROW, entry) + { + r = TABLE_CreateView(db, table->name, &tv); + if (r != ERROR_SUCCESS) + return r; + + r = tv->ops->insert_row(tv, row->data, FALSE); + tv->ops->delete(tv); + + if (r != ERROR_SUCCESS) + return r; + } + + return ERROR_SUCCESS; +} + +static UINT update_merge_errors(MSIDATABASE *db, LPCWSTR error, + LPWSTR table, DWORD numconflicts) +{ + UINT r; + MSIQUERY *view; + + static const WCHAR create[] = { + 'C','R','E','A','T','E',' ','T','A','B','L','E',' ', + '`','%','s','`',' ','(','`','T','a','b','l','e','`',' ', + 'C','H','A','R','(','2','5','5',')',' ','N','O','T',' ', + 'N','U','L','L',',',' ','`','N','u','m','R','o','w','M','e','r','g','e', + 'C','o','n','f','l','i','c','t','s','`',' ','S','H','O','R','T',' ', + 'N','O','T',' ','N','U','L','L',' ','P','R','I','M','A','R','Y',' ', + 'K','E','Y',' ','`','T','a','b','l','e','`',')',0}; + static const WCHAR insert[] = { + 'I','N','S','E','R','T',' ','I','N','T','O',' ', + '`','%','s','`',' ','(','`','T','a','b','l','e','`',',',' ', + '`','N','u','m','R','o','w','M','e','r','g','e', + 'C','o','n','f','l','i','c','t','s','`',')',' ','V','A','L','U','E','S', + ' ','(','\'','%','s','\'',',',' ','%','d',')',0}; + + if (!TABLE_Exists(db, error)) + { + r = MSI_OpenQuery(db, &view, create, error); + if (r != ERROR_SUCCESS) + return r; + + r = MSI_ViewExecute(view, NULL); + msiobj_release(&view->hdr); + if (r != ERROR_SUCCESS) + return r; + } + + r = MSI_OpenQuery(db, &view, insert, error, table, numconflicts); + if (r != ERROR_SUCCESS) + return r; + + r = MSI_ViewExecute(view, NULL); + msiobj_release(&view->hdr); + return r; +} + +static void merge_free_rows(MERGETABLE *table) +{ + struct list *item, *cursor; + + LIST_FOR_EACH_SAFE(item, cursor, &table->rows) + { + MERGEROW *row = LIST_ENTRY(item, MERGEROW, entry); + + list_remove(&row->entry); + merge_free_rows(table); + msiobj_release(&row->data->hdr); + msi_free(row); + } +} + UINT WINAPI MsiDatabaseMergeW(MSIHANDLE hDatabase, MSIHANDLE hDatabaseMerge, LPCWSTR szTableName) { - FIXME("(%ld, %ld, %s): stub!\n", hDatabase, hDatabaseMerge, + struct list tabledata = LIST_INIT(tabledata); + struct list *item, *cursor; + MSIDATABASE *db, *merge; + MERGETABLE *table; + BOOL conflicts; + UINT r; + + TRACE("(%ld, %ld, %s)\n", hDatabase, hDatabaseMerge, debugstr_w(szTableName)); - return ERROR_CALL_NOT_IMPLEMENTED; + if (szTableName && !*szTableName) + return ERROR_INVALID_TABLE; + + db = msihandle2msiinfo(hDatabase, MSIHANDLETYPE_DATABASE); + merge = msihandle2msiinfo(hDatabaseMerge, MSIHANDLETYPE_DATABASE); + if (!db || !merge) + { + r = ERROR_INVALID_HANDLE; + goto done; + } + + r = gather_merge_data(db, merge, &tabledata); + if (r != ERROR_SUCCESS) + goto done; + + conflicts = FALSE; + LIST_FOR_EACH_ENTRY(table, &tabledata, MERGETABLE, entry) + { + if (table->numconflicts) + { + conflicts = TRUE; + + r = update_merge_errors(db, szTableName, table->name, + table->numconflicts); + if (r != ERROR_SUCCESS) + break; + } + else + { + r = merge_table(db, table); + if (r != ERROR_SUCCESS) + break; + } + } + + LIST_FOR_EACH_SAFE(item, cursor, &tabledata) + { + MERGETABLE *table = LIST_ENTRY(item, MERGETABLE, entry); + + list_remove(&table->entry); + merge_free_rows(table); + msi_free(table->name); + msi_free(table); + } + + if (conflicts) + r = ERROR_FUNCTION_FAILED; + +done: + msiobj_release(&db->hdr); + msiobj_release(&merge->hdr); + return r; } MSIDBSTATE WINAPI MsiGetDatabaseState( MSIHANDLE handle ) diff --git a/dlls/msi/msipriv.h b/dlls/msi/msipriv.h index cea2c380ac3..f174c47aed6 100644 --- a/dlls/msi/msipriv.h +++ b/dlls/msi/msipriv.h @@ -703,6 +703,8 @@ extern UINT MSI_RecordSetStream( MSIRECORD *, UINT, IStream * ); extern UINT MSI_RecordDataSize( MSIRECORD *, UINT ); extern UINT MSI_RecordStreamToFile( MSIRECORD *, UINT, LPCWSTR ); extern UINT MSI_RecordCopyField( MSIRECORD *, UINT, MSIRECORD *, UINT ); +extern MSIRECORD *MSI_CloneRecord( MSIRECORD * ); +extern BOOL MSI_RecordsAreEqual( MSIRECORD *, MSIRECORD * ); /* stream internals */ extern UINT get_raw_stream( MSIHANDLE hdb, LPCWSTR stname, IStream **stm ); diff --git a/dlls/msi/record.c b/dlls/msi/record.c index 9a8abc079f7..8b987f8f738 100644 --- a/dlls/msi/record.c +++ b/dlls/msi/record.c @@ -903,3 +903,74 @@ UINT MSI_RecordStreamToFile( MSIRECORD *rec, UINT iField, LPCWSTR name ) return r; } + +MSIRECORD *MSI_CloneRecord(MSIRECORD *rec) +{ + MSIRECORD *clone; + UINT r, i, count; + + count = MSI_RecordGetFieldCount(rec); + clone = MSI_CreateRecord(count); + if (!clone) + return NULL; + + for (i = 0; i <= count; i++) + { + if (rec->fields[i].type == MSIFIELD_STREAM) + { + if (FAILED(IStream_Clone(rec->fields[i].u.stream, + &clone->fields[i].u.stream))) + { + msiobj_release(&clone->hdr); + return NULL; + } + } + else + { + r = MSI_RecordCopyField(rec, i, clone, i); + if (r != ERROR_SUCCESS) + { + msiobj_release(&clone->hdr); + return NULL; + } + } + } + + return clone; +} + +BOOL MSI_RecordsAreEqual(MSIRECORD *a, MSIRECORD *b) +{ + UINT i; + + if (a->count != b->count) + return FALSE; + + for (i = 0; i <= a->count; i++) + { + if (a->fields[i].type != b->fields[i].type) + return FALSE; + + switch (a->fields[i].type) + { + case MSIFIELD_NULL: + break; + + case MSIFIELD_INT: + if (a->fields[i].u.iVal != b->fields[i].u.iVal) + return FALSE; + break; + + case MSIFIELD_WSTR: + if (lstrcmpW(a->fields[i].u.szwVal, b->fields[i].u.szwVal)) + return FALSE; + break; + + case MSIFIELD_STREAM: + default: + return FALSE; + } + } + + return TRUE; +} diff --git a/dlls/msi/tests/db.c b/dlls/msi/tests/db.c index 162c4e30859..938241d9bdf 100644 --- a/dlls/msi/tests/db.c +++ b/dlls/msi/tests/db.c @@ -144,6 +144,9 @@ static UINT do_query(MSIHANDLE hdb, const char *query, MSIHANDLE *phrec) MSIHANDLE hview = 0; UINT r, ret; + if (phrec) + *phrec = 0; + /* open a select query */ r = MsiDatabaseOpenView(hdb, query, &hview); if (r != ERROR_SUCCESS) @@ -1382,17 +1385,28 @@ static void test_longstrings(void) DeleteFile(msifile); } -static void create_file(const CHAR *name) +static void create_file_data(LPCSTR name, LPCSTR data, DWORD size) { HANDLE file; DWORD written; file = CreateFileA(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); - ok(file != INVALID_HANDLE_VALUE, "Failure to open file %s\n", name); - WriteFile(file, name, strlen(name), &written, NULL); + if (file == INVALID_HANDLE_VALUE) + return; + + WriteFile(file, data, strlen(data), &written, NULL); WriteFile(file, "\n", strlen("\n"), &written, NULL); + + if (size) + { + SetFilePointer(file, size, NULL, FILE_BEGIN); + SetEndOfFile(file); + } + CloseHandle(file); } + +#define create_file(name) create_file_data(name, name, 0) static void test_streamtable(void) { @@ -6532,6 +6546,434 @@ static void test_droptable(void) DeleteFileA(msifile); } +static void test_dbmerge(void) +{ + MSIHANDLE hdb, href, hview, hrec; + CHAR buf[MAX_PATH]; + LPCSTR query; + DWORD size; + UINT r; + + r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + r = MsiOpenDatabase("refdb.msi", MSIDBOPEN_CREATE, &href); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + /* hDatabase is invalid */ + r = MsiDatabaseMergeA(0, href, "MergeErrors"); + ok(r == ERROR_INVALID_HANDLE, + "Expected ERROR_INVALID_HANDLE, got %d\n", r); + + /* hDatabaseMerge is invalid */ + r = MsiDatabaseMergeA(hdb, 0, "MergeErrors"); + ok(r == ERROR_INVALID_HANDLE, + "Expected ERROR_INVALID_HANDLE, got %d\n", r); + + /* szTableName is NULL */ + r = MsiDatabaseMergeA(hdb, href, NULL); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + /* szTableName is empty */ + r = MsiDatabaseMergeA(hdb, href, ""); + ok(r == ERROR_INVALID_TABLE, "Expected ERROR_INVALID_TABLE, got %d\n", r); + + /* both DBs empty, szTableName is valid */ + r = MsiDatabaseMergeA(hdb, href, "MergeErrors"); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT PRIMARY KEY `A` )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` CHAR(72) PRIMARY KEY `A` )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + /* column types don't match */ + r = MsiDatabaseMergeA(hdb, href, "MergeErrors"); + ok(r == ERROR_DATATYPE_MISMATCH, + "Expected ERROR_DATATYPE_MISMATCH, got %d\n", r); + + /* nothing in MergeErrors */ + query = "SELECT * FROM `MergeErrors`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_BAD_QUERY_SYNTAX, + "Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT PRIMARY KEY `A` )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `C` INT PRIMARY KEY `A` )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + /* column names don't match */ + r = MsiDatabaseMergeA(hdb, href, "MergeErrors"); + ok(r == ERROR_DATATYPE_MISMATCH, + "Expected ERROR_DATATYPE_MISMATCH, got %d\n", r); + + /* nothing in MergeErrors */ + query = "SELECT * FROM `MergeErrors`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_BAD_QUERY_SYNTAX, + "Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT PRIMARY KEY `A` )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT PRIMARY KEY `B` )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + /* primary keys don't match */ + r = MsiDatabaseMergeA(hdb, href, "MergeErrors"); + ok(r == ERROR_DATATYPE_MISMATCH, + "Expected ERROR_DATATYPE_MISMATCH, got %d\n", r); + + /* nothing in MergeErrors */ + query = "SELECT * FROM `MergeErrors`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_BAD_QUERY_SYNTAX, + "Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT PRIMARY KEY `A` )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT PRIMARY KEY `A`, `B` )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + /* number of primary keys doesn't match */ + r = MsiDatabaseMergeA(hdb, href, "MergeErrors"); + ok(r == ERROR_DATATYPE_MISMATCH, + "Expected ERROR_DATATYPE_MISMATCH, got %d\n", r); + + /* nothing in MergeErrors */ + query = "SELECT * FROM `MergeErrors`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_BAD_QUERY_SYNTAX, + "Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT, `C` INT PRIMARY KEY `A` )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT PRIMARY KEY `A` )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "INSERT INTO `One` ( `A`, `B` ) VALUES ( 1, 2 )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + /* number of columns doesn't match */ + r = MsiDatabaseMergeA(hdb, href, "MergeErrors"); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "SELECT * FROM `One`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + r = MsiRecordGetInteger(hrec, 1); + ok(r == 1, "Expected 1, got %d\n", r); + + r = MsiRecordGetInteger(hrec, 2); + ok(r == 2, "Expected 2, got %d\n", r); + + r = MsiRecordGetInteger(hrec, 3); + ok(r == MSI_NULL_INTEGER, "Expected MSI_NULL_INTEGER, got %d\n", r); + + MsiCloseHandle(hrec); + + /* nothing in MergeErrors */ + query = "SELECT * FROM `MergeErrors`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_BAD_QUERY_SYNTAX, + "Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT PRIMARY KEY `A` )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT, `C` INT PRIMARY KEY `A` )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "INSERT INTO `One` ( `A`, `B`, `C` ) VALUES ( 1, 2, 3 )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + /* number of columns doesn't match */ + r = MsiDatabaseMergeA(hdb, href, "MergeErrors"); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "SELECT * FROM `One`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + r = MsiRecordGetInteger(hrec, 1); + ok(r == 1, "Expected 1, got %d\n", r); + + r = MsiRecordGetInteger(hrec, 2); + ok(r == 2, "Expected 2, got %d\n", r); + + r = MsiRecordGetInteger(hrec, 3); + ok(r == MSI_NULL_INTEGER, "Expected MSI_NULL_INTEGER, got %d\n", r); + + MsiCloseHandle(hrec); + + /* nothing in MergeErrors */ + query = "SELECT * FROM `MergeErrors`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_BAD_QUERY_SYNTAX, + "Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT PRIMARY KEY `A` )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "INSERT INTO `One` ( `A`, `B` ) VALUES ( 1, 1 )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "INSERT INTO `One` ( `A`, `B` ) VALUES ( 2, 2 )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` INT PRIMARY KEY `A` )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "INSERT INTO `One` ( `A`, `B` ) VALUES ( 1, 2 )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "INSERT INTO `One` ( `A`, `B` ) VALUES ( 2, 3 )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + /* primary keys match, rows do not */ + r = MsiDatabaseMergeA(hdb, href, "MergeErrors"); + ok(r == ERROR_FUNCTION_FAILED, + "Expected ERROR_FUNCTION_FAILED, got %d\n", r); + + /* nothing in MergeErrors */ + query = "SELECT * FROM `MergeErrors`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + size = MAX_PATH; + r = MsiRecordGetStringA(hrec, 1, buf, &size); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + ok(!lstrcmpA(buf, "One"), "Expected \"One\", got \"%s\"\n", buf); + + r = MsiRecordGetInteger(hrec, 2); + ok(r == 2, "Expected 2, got %d\n", r); + + MsiCloseHandle(hrec); + + r = MsiDatabaseOpenViewA(hdb, "SELECT * FROM `MergeErrors`", &hview); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + r = MsiViewGetColumnInfo(hview, MSICOLINFO_NAMES, &hrec); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + size = MAX_PATH; + r = MsiRecordGetString(hrec, 1, buf, &size); + ok(!lstrcmpA(buf, "Table"), "Expected \"Table\", got \"%s\"\n", buf); + + size = MAX_PATH; + r = MsiRecordGetString(hrec, 2, buf, &size); + ok(!lstrcmpA(buf, "NumRowMergeConflicts"), + "Expected \"NumRowMergeConflicts\", got \"%s\"\n", buf); + + MsiCloseHandle(hrec); + + r = MsiViewGetColumnInfo(hview, MSICOLINFO_TYPES, &hrec); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + size = MAX_PATH; + r = MsiRecordGetString(hrec, 1, buf, &size); + ok(!lstrcmpA(buf, "s255"), "Expected \"s255\", got \"%s\"\n", buf); + + size = MAX_PATH; + r = MsiRecordGetString(hrec, 2, buf, &size); + ok(!lstrcmpA(buf, "i2"), "Expected \"i2\", got \"%s\"\n", buf); + + MsiCloseHandle(hrec); + MsiViewClose(hview); + MsiCloseHandle(hview); + + query = "DROP TABLE `MergeErrors`"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + create_file_data("codepage.idt", "\r\n\r\n28603\t_ForceCodepage\r\n", 0); + + GetCurrentDirectoryA(MAX_PATH, buf); + r = MsiDatabaseImportA(hdb, buf, "codepage.idt"); + todo_wine ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( " + "`A` INT, `B` CHAR(72) LOCALIZABLE PRIMARY KEY `A` )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( " + "`A` INT, `B` CHAR(72) LOCALIZABLE PRIMARY KEY `A` )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "INSERT INTO `One` ( `A`, `B` ) VALUES ( 1, 'hi' )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + /* code page does not match */ + r = MsiDatabaseMergeA(hdb, href, "MergeErrors"); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "SELECT * FROM `One`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + r = MsiRecordGetInteger(hrec, 1); + ok(r == 1, "Expected 1, got %d\n", r); + + size = MAX_PATH; + r = MsiRecordGetStringA(hrec, 2, buf, &size); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + ok(!lstrcmpA(buf, "hi"), "Expected \"hi\", got \"%s\"\n", buf); + + MsiCloseHandle(hrec); + + /* nothing in MergeErrors */ + query = "SELECT * FROM `MergeErrors`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_BAD_QUERY_SYNTAX, + "Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "DROP TABLE `One`"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` OBJECT PRIMARY KEY `A` )"; + r = run_query(hdb, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "CREATE TABLE `One` ( `A` INT, `B` OBJECT PRIMARY KEY `A` )"; + r = run_query(href, 0, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + create_file("binary.dat"); + hrec = MsiCreateRecord(1); + MsiRecordSetStreamA(hrec, 1, "binary.dat"); + + query = "INSERT INTO `One` ( `A`, `B` ) VALUES ( 1, ? )"; + r = run_query(href, hrec, query); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + MsiCloseHandle(hrec); + + /* binary data to merge */ + r = MsiDatabaseMergeA(hdb, href, "MergeErrors"); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + query = "SELECT * FROM `One`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + + r = MsiRecordGetInteger(hrec, 1); + ok(r == 1, "Expected 1, got %d\n", r); + + size = MAX_PATH; + ZeroMemory(buf, MAX_PATH); + r = MsiRecordReadStream(hrec, 2, buf, &size); + todo_wine + { + ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + ok(!lstrcmpA(buf, "binary.dat\n"), + "Expected \"binary.dat\\n\", got \"%s\"\n", buf); + } + + MsiCloseHandle(hrec); + + /* nothing in MergeErrors */ + query = "SELECT * FROM `MergeErrors`"; + r = do_query(hdb, query, &hrec); + ok(r == ERROR_BAD_QUERY_SYNTAX, + "Expected ERROR_BAD_QUERY_SYNTAX, got %d\n", r); + + MsiCloseHandle(hdb); + MsiCloseHandle(href); + DeleteFileA(msifile); + DeleteFileA("refdb.msi"); + DeleteFileA("codepage.idt"); +} + START_TEST(db) { test_msidatabase(); @@ -6572,4 +7014,5 @@ START_TEST(db) test_storages_table(); test_dbtopackage(); test_droptable(); + test_dbmerge(); } -- 2.11.4.GIT