- Merge from trunk
[reactos.git] / dll / win32 / msi / database.c
index 2900860..3007d48 100644 (file)
 #include "objidl.h"
 #include "objbase.h"
 #include "msiserver.h"
+#include "query.h"
 
 #include "initguid.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(msi);
 
-DEFINE_GUID( CLSID_MsiDatabase, 0x000c1084, 0x0000, 0x0000,
-             0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);
-DEFINE_GUID( CLSID_MsiPatch, 0x000c1086, 0x0000, 0x0000,
-             0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);
-
 /*
  *  .MSI  file format
  *
@@ -55,20 +51,245 @@ DEFINE_GUID( CLSID_MsiPatch, 0x000c1086, 0x0000, 0x0000,
  *  Any binary data in a table is a reference to a stream.
  */
 
+#define IS_INTMSIDBOPEN(x)      (((ULONG_PTR)(x) >> 16) == 0)
+
+typedef struct tagMSITRANSFORM {
+    struct list entry;
+    IStorage *stg;
+} MSITRANSFORM;
+
+typedef struct tagMSISTREAM {
+    struct list entry;
+    IStream *stm;
+} MSISTREAM;
+
+static UINT find_open_stream( MSIDATABASE *db, LPCWSTR name, IStream **stm )
+{
+    MSISTREAM *stream;
+
+    LIST_FOR_EACH_ENTRY( stream, &db->streams, MSISTREAM, entry )
+    {
+        HRESULT r;
+        STATSTG stat;
+
+        r = IStream_Stat( stream->stm, &stat, 0 );
+        if( FAILED( r ) )
+        {
+            WARN("failed to stat stream r = %08x!\n", r);
+            continue;
+        }
+
+        if( !lstrcmpW( name, stat.pwcsName ) )
+        {
+            TRACE("found %s\n", debugstr_w(name));
+            *stm = stream->stm;
+            CoTaskMemFree( stat.pwcsName );
+            return ERROR_SUCCESS;
+        }
+
+        CoTaskMemFree( stat.pwcsName );
+    }
+
+    return ERROR_FUNCTION_FAILED;
+}
+
+static UINT clone_open_stream( MSIDATABASE *db, LPCWSTR name, IStream **stm )
+{
+    IStream *stream;
+
+    if (find_open_stream( db, name, &stream ) == ERROR_SUCCESS)
+    {
+        HRESULT r;
+        LARGE_INTEGER pos;
+
+        r = IStream_Clone( stream, stm );
+        if( FAILED( r ) )
+        {
+            WARN("failed to clone stream r = %08x!\n", r);
+            return ERROR_FUNCTION_FAILED;
+        }
+
+        pos.QuadPart = 0;
+        r = IStream_Seek( *stm, pos, STREAM_SEEK_SET, NULL );
+        if( FAILED( r ) )
+        {
+            IStream_Release( *stm );
+            return ERROR_FUNCTION_FAILED;
+        }
+
+        return ERROR_SUCCESS;
+    }
+
+    return ERROR_FUNCTION_FAILED;
+}
+
+UINT db_get_raw_stream( MSIDATABASE *db, LPCWSTR stname, IStream **stm )
+{
+    HRESULT r;
+    WCHAR decoded[MAX_STREAM_NAME_LEN];
+
+    decode_streamname( stname, decoded );
+    TRACE("%s -> %s\n", debugstr_w(stname), debugstr_w(decoded));
+
+    if (clone_open_stream( db, stname, stm ) == ERROR_SUCCESS)
+        return ERROR_SUCCESS;
+
+    r = IStorage_OpenStream( db->storage, stname, NULL,
+                             STGM_READ | STGM_SHARE_EXCLUSIVE, 0, stm );
+    if( FAILED( r ) )
+    {
+        MSITRANSFORM *transform;
+
+        LIST_FOR_EACH_ENTRY( transform, &db->transforms, MSITRANSFORM, entry )
+        {
+            r = IStorage_OpenStream( transform->stg, stname, NULL,
+                                     STGM_READ | STGM_SHARE_EXCLUSIVE, 0, stm );
+            if (SUCCEEDED(r))
+                break;
+        }
+    }
+
+    if( SUCCEEDED(r) )
+    {
+        MSISTREAM *stream;
+
+        stream = msi_alloc( sizeof(MSISTREAM) );
+        if( !stream )
+            return ERROR_NOT_ENOUGH_MEMORY;
+
+        stream->stm = *stm;
+        IStream_AddRef( *stm );
+        list_add_tail( &db->streams, &stream->entry );
+    }
+
+    return SUCCEEDED(r) ? ERROR_SUCCESS : ERROR_FUNCTION_FAILED;
+}
+
+static void free_transforms( MSIDATABASE *db )
+{
+    while( !list_empty( &db->transforms ) )
+    {
+        MSITRANSFORM *t = LIST_ENTRY( list_head( &db->transforms ),
+                                      MSITRANSFORM, entry );
+        list_remove( &t->entry );
+        IStorage_Release( t->stg );
+        msi_free( t );
+    }
+}
+
+void db_destroy_stream( MSIDATABASE *db, LPCWSTR stname )
+{
+    MSISTREAM *stream, *stream2;
+
+    LIST_FOR_EACH_ENTRY_SAFE( stream, stream2, &db->streams, MSISTREAM, entry )
+    {
+        HRESULT r;
+        STATSTG stat;
+
+        r = IStream_Stat( stream->stm, &stat, 0 );
+        if (FAILED(r))
+        {
+            WARN("failed to stat stream r = %08x\n", r);
+            continue;
+        }
+
+        if (!strcmpW( stname, stat.pwcsName ))
+        {
+            TRACE("destroying %s\n", debugstr_w(stname));
+
+            list_remove( &stream->entry );
+            IStream_Release( stream->stm );
+            msi_free( stream );
+            IStorage_DestroyElement( db->storage, stname );
+            CoTaskMemFree( stat.pwcsName );
+            break;
+        }
+        CoTaskMemFree( stat.pwcsName );
+    }
+}
+
+static void free_streams( MSIDATABASE *db )
+{
+    while( !list_empty( &db->streams ) )
+    {
+        MSISTREAM *s = LIST_ENTRY( list_head( &db->streams ),
+                                   MSISTREAM, entry );
+        list_remove( &s->entry );
+        IStream_Release( s->stm );
+        msi_free( s );
+    }
+}
+
+void append_storage_to_db( MSIDATABASE *db, IStorage *stg )
+{
+    MSITRANSFORM *t;
+
+    t = msi_alloc( sizeof *t );
+    t->stg = stg;
+    IStorage_AddRef( stg );
+    list_add_head( &db->transforms, &t->entry );
+
+    /* the transform may add or replace streams */
+    free_streams( db );
+}
+
 static VOID MSI_CloseDatabase( MSIOBJECTHDR *arg )
 {
     MSIDATABASE *db = (MSIDATABASE *) arg;
 
     msi_free(db->path);
     free_cached_tables( db );
-    msi_free_transforms( db );
-    msi_destroy_stringtable( db->strings );
+    free_streams( db );
+    free_transforms( db );
+    if (db->strings) msi_destroy_stringtable( db->strings );
     IStorage_Release( db->storage );
     if (db->deletefile)
     {
         DeleteFileW( db->deletefile );
         msi_free( db->deletefile );
     }
+    if (db->localfile)
+    {
+        DeleteFileW( db->localfile );
+        msi_free( db->localfile );
+    }
+}
+
+static HRESULT db_initialize( IStorage *stg, const GUID *clsid )
+{
+    static const WCHAR szTables[]  = { '_','T','a','b','l','e','s',0 };
+    HRESULT hr;
+
+    hr = IStorage_SetClass( stg, clsid );
+    if (FAILED( hr ))
+    {
+        WARN("failed to set class id 0x%08x\n", hr);
+        return hr;
+    }
+
+    /* create the _Tables stream */
+    hr = write_stream_data( stg, szTables, NULL, 0, TRUE );
+    if (FAILED( hr ))
+    {
+        WARN("failed to create _Tables stream 0x%08x\n", hr);
+        return hr;
+    }
+
+    hr = msi_init_string_table( stg );
+    if (FAILED( hr ))
+    {
+        WARN("failed to initialize string table 0x%08x\n", hr);
+        return hr;
+    }
+
+    hr = IStorage_Commit( stg, 0 );
+    if (FAILED( hr ))
+    {
+        WARN("failed to commit changes 0x%08x\n", hr);
+        return hr;
+    }
+
+    return S_OK;
 }
 
 UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb)
@@ -79,12 +300,9 @@ UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb)
     UINT ret = ERROR_FUNCTION_FAILED;
     LPCWSTR szMode, save_path;
     STATSTG stat;
-    BOOL created = FALSE;
+    BOOL created = FALSE, patch = FALSE;
     WCHAR path[MAX_PATH];
 
-    static const WCHAR backslash[] = {'\\',0};
-    static const WCHAR szTables[]  = { '_','T','a','b','l','e','s',0 };
-
     TRACE("%s %s\n",debugstr_w(szDBPath),debugstr_w(szPersist) );
 
     if( !pdb )
@@ -95,11 +313,12 @@ UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb)
     {
         TRACE("Database is a patch\n");
         szPersist -= MSIDBOPEN_PATCHFILE;
+        patch = TRUE;
     }
 
     save_path = szDBPath;
     szMode = szPersist;
-    if( HIWORD( szPersist ) )
+    if( !IS_INTMSIDBOPEN(szPersist) )
     {
         if (!CopyFileW( szDBPath, szPersist, FALSE ))
             return ERROR_OPEN_FAILED;
@@ -114,28 +333,28 @@ UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb)
         r = StgOpenStorage( szDBPath, NULL,
               STGM_DIRECT|STGM_READ|STGM_SHARE_DENY_WRITE, NULL, 0, &stg);
     }
-    else if( szPersist == MSIDBOPEN_CREATE || szPersist == MSIDBOPEN_CREATEDIRECT )
+    else if( szPersist == MSIDBOPEN_CREATE )
     {
-        /* FIXME: MSIDBOPEN_CREATE should case STGM_TRANSACTED flag to be
-         * used here: */
         r = StgCreateDocfile( szDBPath,
-              STGM_CREATE|STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 0, &stg);
-        if( r == ERROR_SUCCESS )
-        {
-            IStorage_SetClass( stg, &CLSID_MsiDatabase );
-            /* create the _Tables stream */
-            r = write_stream_data(stg, szTables, NULL, 0, TRUE);
-            if (!FAILED(r))
-                r = msi_init_string_table( stg );
-        }
+              STGM_CREATE|STGM_TRANSACTED|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 0, &stg );
+
+        if( SUCCEEDED(r) )
+            r = db_initialize( stg, patch ? &CLSID_MsiPatch : &CLSID_MsiDatabase );
+        created = TRUE;
+    }
+    else if( szPersist == MSIDBOPEN_CREATEDIRECT )
+    {
+        r = StgCreateDocfile( szDBPath,
+              STGM_CREATE|STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 0, &stg );
+
+        if( SUCCEEDED(r) )
+            r = db_initialize( stg, patch ? &CLSID_MsiPatch : &CLSID_MsiDatabase );
         created = TRUE;
     }
     else if( szPersist == MSIDBOPEN_TRANSACT )
     {
-        /* FIXME: MSIDBOPEN_TRANSACT should case STGM_TRANSACTED flag to be
-         * used here: */
         r = StgOpenStorage( szDBPath, NULL,
-              STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg);
+              STGM_TRANSACTED|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg);
     }
     else if( szPersist == MSIDBOPEN_DIRECT )
     {
@@ -162,13 +381,22 @@ UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb)
     }
 
     if ( !IsEqualGUID( &stat.clsid, &CLSID_MsiDatabase ) &&
-         !IsEqualGUID( &stat.clsid, &CLSID_MsiPatch ) ) 
+         !IsEqualGUID( &stat.clsid, &CLSID_MsiPatch ) &&
+         !IsEqualGUID( &stat.clsid, &CLSID_MsiTransform ) )
     {
         ERR("storage GUID is not a MSI database GUID %s\n",
              debugstr_guid(&stat.clsid) );
         goto end;
     }
 
+    if ( patch && !IsEqualGUID( &stat.clsid, &CLSID_MsiPatch ) )
+    {
+        ERR("storage GUID is not the MSI patch GUID %s\n",
+             debugstr_guid(&stat.clsid) );
+        ret = ERROR_OPEN_FAILED;
+        goto end;
+    }
+
     db = alloc_msiobject( MSIHANDLETYPE_DATABASE, sizeof (MSIDATABASE),
                               MSI_CloseDatabase );
     if( !db )
@@ -180,7 +408,7 @@ UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb)
     if (!strchrW( save_path, '\\' ))
     {
         GetCurrentDirectoryW( MAX_PATH, path );
-        lstrcatW( path, backslash );
+        lstrcatW( path, szBackSlash );
         lstrcatW( path, save_path );
     }
     else
@@ -195,16 +423,14 @@ UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb)
     db->mode = szMode;
     if (created)
         db->deletefile = strdupW( szDBPath );
-    else
-        db->deletefile = NULL;
     list_init( &db->tables );
     list_init( &db->transforms );
+    list_init( &db->streams );
 
     db->strings = msi_load_string_table( stg, &db->bytes_per_strref );
     if( !db->strings )
         goto end;
 
-    msi_table_set_strref( db->bytes_per_strref );
     ret = ERROR_SUCCESS;
 
     msiobj_addref( &db->hdr );
@@ -253,7 +479,7 @@ UINT WINAPI MsiOpenDatabaseA(LPCSTR szDBPath, LPCSTR szPersist, MSIHANDLE *phDB)
             goto end;
     }
 
-    if( HIWORD(szPersist) )
+    if( !IS_INTMSIDBOPEN(szPersist) )
     {
         szwPersist = strdupAtoW( szPersist );
         if( !szwPersist )
@@ -265,7 +491,7 @@ UINT WINAPI MsiOpenDatabaseA(LPCSTR szDBPath, LPCSTR szPersist, MSIHANDLE *phDB)
     r = MsiOpenDatabaseW( szwDBPath, szwPersist, phDB );
 
 end:
-    if( HIWORD(szPersist) )
+    if( !IS_INTMSIDBOPEN(szPersist) )
         msi_free( szwPersist );
     msi_free( szwDBPath );
 
@@ -377,6 +603,7 @@ static LPWSTR msi_build_createsql_columns(LPWSTR *columns_data, LPWSTR *types, D
     static const WCHAR type_char[] = {'C','H','A','R',0};
     static const WCHAR type_int[] = {'I','N','T',0};
     static const WCHAR type_long[] = {'L','O','N','G',0};
+    static const WCHAR type_object[] = {'O','B','J','E','C','T',0};
     static const WCHAR type_notnull[] = {' ','N','O','T',' ','N','U','L','L',0};
     static const WCHAR localizable[] = {' ','L','O','C','A','L','I','Z','A','B','L','E',0};
 
@@ -416,10 +643,21 @@ static LPWSTR msi_build_createsql_columns(LPWSTR *columns_data, LPWSTR *types, D
             case 'i':
                 lstrcpyW(extra, type_notnull);
             case 'I':
-                if (len == 2)
+                if (len <= 2)
                     type = type_int;
-                else
+                else if (len == 4)
                     type = type_long;
+                else
+                {
+                    WARN("invalid int width %u\n", len);
+                    msi_free(columns);
+                    return NULL;
+                }
+                break;
+            case 'v':
+                lstrcpyW(extra, type_notnull);
+            case 'V':
+                type = type_object;
                 break;
             default:
                 ERR("Unknown type: %c\n", types[i][0]);
@@ -483,10 +721,10 @@ done:
 
 static UINT msi_add_table_to_db(MSIDATABASE *db, LPWSTR *columns, LPWSTR *types, LPWSTR *labels, DWORD num_labels, DWORD num_columns)
 {
-    UINT r;
+    UINT r = ERROR_OUTOFMEMORY;
     DWORD size;
     MSIQUERY *view;
-    LPWSTR create_sql;
+    LPWSTR create_sql = NULL;
     LPWSTR prelude, columns_sql, postlude;
 
     prelude = msi_build_createsql_prelude(labels[0]);
@@ -494,36 +732,59 @@ static UINT msi_add_table_to_db(MSIDATABASE *db, LPWSTR *columns, LPWSTR *types,
     postlude = msi_build_createsql_postlude(labels + 1, num_labels - 1); /* skip over table name */
 
     if (!prelude || !columns_sql || !postlude)
-        return ERROR_OUTOFMEMORY;
+        goto done;
 
     size = lstrlenW(prelude) + lstrlenW(columns_sql) + lstrlenW(postlude) + 1;
     create_sql = msi_alloc(size * sizeof(WCHAR));
     if (!create_sql)
-        return ERROR_OUTOFMEMORY;
+        goto done;
 
     lstrcpyW(create_sql, prelude);
     lstrcatW(create_sql, columns_sql);
     lstrcatW(create_sql, postlude);
 
-    msi_free(prelude);
-    msi_free(columns_sql);
-    msi_free(postlude);
-
     r = MSI_DatabaseOpenViewW( db, create_sql, &view );
-    msi_free(create_sql);
-
     if (r != ERROR_SUCCESS)
-        return r;
+        goto done;
 
     r = MSI_ViewExecute(view, NULL);
     MSI_ViewClose(view);
     msiobj_release(&view->hdr);
 
+done:
+    msi_free(prelude);
+    msi_free(columns_sql);
+    msi_free(postlude);
+    msi_free(create_sql);
     return r;
 }
 
+static LPWSTR msi_import_stream_filename(LPCWSTR path, LPCWSTR name)
+{
+    DWORD len;
+    LPWSTR fullname, ptr;
+
+    len = lstrlenW(path) + lstrlenW(name) + 1;
+    fullname = msi_alloc(len*sizeof(WCHAR));
+    if (!fullname)
+       return NULL;
+
+    lstrcpyW( fullname, path );
+
+    /* chop off extension from path */
+    ptr = strrchrW(fullname, '.');
+    if (!ptr)
+    {
+        msi_free (fullname);
+        return NULL;
+    }
+    *ptr++ = '\\';
+    lstrcpyW( ptr, name );
+    return fullname;
+}
+
 static UINT construct_record(DWORD num_columns, LPWSTR *types,
-                             LPWSTR *data, MSIRECORD **rec)
+                             LPWSTR *data, LPWSTR path, MSIRECORD **rec)
 {
     UINT i;
 
@@ -542,6 +803,20 @@ static UINT construct_record(DWORD num_columns, LPWSTR *types,
                 if (*data[i])
                     MSI_RecordSetInteger(*rec, i + 1, atoiW(data[i]));
                 break;
+            case 'V': case 'v':
+                if (*data[i])
+                {
+                    UINT r;
+                    LPWSTR file = msi_import_stream_filename(path, data[i]);
+                    if (!file)
+                        return ERROR_FUNCTION_FAILED;
+
+                    r = MSI_RecordSetStreamFromFileW(*rec, i + 1, file);
+                    msi_free (file);
+                    if (r != ERROR_SUCCESS)
+                        return ERROR_FUNCTION_FAILED;
+                }
+                break;
             default:
                 ERR("Unhandled column type: %c\n", types[i][0]);
                 msiobj_release(&(*rec)->hdr);
@@ -554,41 +829,34 @@ static UINT construct_record(DWORD num_columns, LPWSTR *types,
 
 static UINT msi_add_records_to_table(MSIDATABASE *db, LPWSTR *columns, LPWSTR *types,
                                      LPWSTR *labels, LPWSTR **records,
-                                     int num_columns, int num_records)
+                                     int num_columns, int num_records,
+                                     LPWSTR path)
 {
     UINT r;
-    DWORD i, size;
+    int i;
     MSIQUERY *view;
     MSIRECORD *rec;
-    LPWSTR query;
 
     static const WCHAR select[] = {
         'S','E','L','E','C','T',' ','*',' ',
         'F','R','O','M',' ','`','%','s','`',0
     };
 
-    size = lstrlenW(select) + lstrlenW(labels[0]) - 1;
-    query = msi_alloc(size * sizeof(WCHAR));
-    if (!query)
-        return ERROR_OUTOFMEMORY;
-
-    sprintfW(query, select, labels[0]);
-
-    r = MSI_DatabaseOpenViewW(db, query, &view);
-    msi_free(query);
+    r = MSI_OpenQuery(db, &view, select, labels[0]);
     if (r != ERROR_SUCCESS)
         return r;
 
     while (MSI_ViewFetch(view, &rec) != ERROR_NO_MORE_ITEMS)
     {
         r = MSI_ViewModify(view, MSIMODIFY_DELETE, rec);
+        msiobj_release(&rec->hdr);
         if (r != ERROR_SUCCESS)
             goto done;
     }
 
     for (i = 0; i < num_records; i++)
     {
-        r = construct_record(num_columns, types, records[i], &rec);
+        r = construct_record(num_columns, types, records[i], path, &rec);
         if (r != ERROR_SUCCESS)
             goto done;
 
@@ -607,7 +875,7 @@ done:
     return r;
 }
 
-UINT MSI_DatabaseImport(MSIDATABASE *db, LPCWSTR folder, LPCWSTR file)
+static UINT MSI_DatabaseImport(MSIDATABASE *db, LPCWSTR folder, LPCWSTR file)
 {
     UINT r;
     DWORD len, i;
@@ -618,20 +886,21 @@ UINT MSI_DatabaseImport(MSIDATABASE *db, LPCWSTR folder, LPCWSTR file)
     LPWSTR **records = NULL;
     LPWSTR **temp_records;
 
-    static const WCHAR backslash[] = {'\\',0};
+    static const WCHAR suminfo[] =
+        {'_','S','u','m','m','a','r','y','I','n','f','o','r','m','a','t','i','o','n',0};
 
     TRACE("%p %s %s\n", db, debugstr_w(folder), debugstr_w(file) );
 
     if( folder == NULL || file == NULL )
         return ERROR_INVALID_PARAMETER;
 
-    len = lstrlenW(folder) + lstrlenW(backslash) + lstrlenW(file) + 1;
+    len = lstrlenW(folder) + lstrlenW(szBackSlash) + lstrlenW(file) + 1;
     path = msi_alloc( len * sizeof(WCHAR) );
     if (!path)
         return ERROR_OUTOFMEMORY;
 
     lstrcpyW( path, folder );
-    lstrcatW( path, backslash );
+    lstrcatW( path, szBackSlash );
     lstrcatW( path, file );
 
     data = msi_read_text_archive( path );
@@ -669,17 +938,29 @@ UINT MSI_DatabaseImport(MSIDATABASE *db, LPCWSTR folder, LPCWSTR file)
         records = temp_records;
     }
 
-    if (!TABLE_Exists(db, labels[0]))
+    if (!strcmpW(labels[0], suminfo))
     {
-        r = msi_add_table_to_db( db, columns, types, labels, num_labels, num_columns );
+        r = msi_add_suminfo( db, records, num_records, num_columns );
         if (r != ERROR_SUCCESS)
         {
             r = ERROR_FUNCTION_FAILED;
             goto done;
         }
     }
+    else
+    {
+        if (!TABLE_Exists(db, labels[0]))
+        {
+            r = msi_add_table_to_db( db, columns, types, labels, num_labels, num_columns );
+            if (r != ERROR_SUCCESS)
+            {
+                r = ERROR_FUNCTION_FAILED;
+                goto done;
+            }
+        }
 
-    r = msi_add_records_to_table( db, columns, types, labels, records, num_columns, num_records );
+        r = msi_add_records_to_table( db, columns, types, labels, records, num_columns, num_records, path );
+    }
 
 done:
     msi_free(path);
@@ -701,7 +982,7 @@ UINT WINAPI MsiDatabaseImportW(MSIHANDLE handle, LPCWSTR szFolder, LPCWSTR szFil
     MSIDATABASE *db;
     UINT r;
 
-    TRACE("%lx %s %s\n",handle,debugstr_w(szFolder), debugstr_w(szFilename));
+    TRACE("%x %s %s\n",handle,debugstr_w(szFolder), debugstr_w(szFilename));
 
     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
     if( !db )
@@ -729,7 +1010,7 @@ UINT WINAPI MsiDatabaseImportA( MSIHANDLE handle,
     LPWSTR path = NULL, file = NULL;
     UINT r = ERROR_OUTOFMEMORY;
 
-    TRACE("%lx %s %s\n", handle, debugstr_a(szFolder), debugstr_a(szFilename));
+    TRACE("%x %s %s\n", handle, debugstr_a(szFolder), debugstr_a(szFilename));
 
     if( szFolder )
     {
@@ -821,12 +1102,11 @@ static UINT msi_export_forcecodepage( HANDLE handle )
     return ERROR_SUCCESS;
 }
 
-UINT MSI_DatabaseExport( MSIDATABASE *db, LPCWSTR table,
+static UINT MSI_DatabaseExport( MSIDATABASE *db, LPCWSTR table,
                LPCWSTR folder, LPCWSTR file )
 {
     static const WCHAR query[] = {
         's','e','l','e','c','t',' ','*',' ','f','r','o','m',' ','%','s',0 };
-    static const WCHAR szbs[] = { '\\', 0 };
     static const WCHAR forcecodepage[] = {
         '_','F','o','r','c','e','C','o','d','e','p','a','g','e',0 };
     MSIRECORD *rec = NULL;
@@ -847,7 +1127,7 @@ UINT MSI_DatabaseExport( MSIDATABASE *db, LPCWSTR table,
         return ERROR_OUTOFMEMORY;
 
     lstrcpyW( filename, folder );
-    lstrcatW( filename, szbs );
+    lstrcatW( filename, szBackSlash );
     lstrcatW( filename, file );
 
     handle = CreateFileW( filename, GENERIC_READ | GENERIC_WRITE, 0,
@@ -921,7 +1201,7 @@ UINT WINAPI MsiDatabaseExportW( MSIHANDLE handle, LPCWSTR szTable,
     MSIDATABASE *db;
     UINT r;
 
-    TRACE("%lx %s %s %s\n", handle, debugstr_w(szTable),
+    TRACE("%x %s %s %s\n", handle, debugstr_w(szTable),
           debugstr_w(szFolder), debugstr_w(szFilename));
 
     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
@@ -950,7 +1230,7 @@ UINT WINAPI MsiDatabaseExportA( MSIHANDLE handle, LPCSTR szTable,
     LPWSTR path = NULL, file = NULL, table = NULL;
     UINT r = ERROR_OUTOFMEMORY;
 
-    TRACE("%lx %s %s %s\n", handle, debugstr_a(szTable),
+    TRACE("%x %s %s %s\n", handle, debugstr_a(szTable),
           debugstr_a(szFolder), debugstr_a(szFilename));
 
     if( szTable )
@@ -984,12 +1264,747 @@ end:
     return r;
 }
 
+UINT WINAPI MsiDatabaseMergeA(MSIHANDLE hDatabase, MSIHANDLE hDatabaseMerge,
+                              LPCSTR szTableName)
+{
+    UINT r;
+    LPWSTR table;
+
+    TRACE("(%d, %d, %s)\n", hDatabase, hDatabaseMerge,
+          debugstr_a(szTableName));
+
+    table = strdupAtoW(szTableName);
+    r = MsiDatabaseMergeW(hDatabase, hDatabaseMerge, table);
+
+    msi_free(table);
+    return r;
+}
+
+typedef struct _tagMERGETABLE
+{
+    struct list entry;
+    struct list rows;
+    LPWSTR name;
+    DWORD numconflicts;
+    LPWSTR *columns;
+    DWORD numcolumns;
+    LPWSTR *types;
+    DWORD numtypes;
+    LPWSTR *labels;
+    DWORD numlabels;
+} 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 BOOL merge_type_match(LPCWSTR type1, LPCWSTR type2)
+{
+    if (((type1[0] == 'l') || (type1[0] == 's')) &&
+        ((type2[0] == 'l') || (type2[0] == 's')))
+        return TRUE;
+
+    if (((type1[0] == 'L') || (type1[0] == 'S')) &&
+        ((type2[0] == 'L') || (type2[0] == 'S')))
+        return TRUE;
+
+    return !lstrcmpW(type1, type2);
+}
+
+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 (!merge_type_match(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, val;
+    UINT r, i = 0, sz = 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);
+
+    r = MSI_RecordGetStringW(rec, i, NULL, &sz);
+    if (r != ERROR_SUCCESS)
+        return NULL;
+    sz++;
+
+    if (MSI_RecordGetString(rec, i))  /* check record field is a string */
+    {
+        /* quote string record fields */
+        const WCHAR szQuote[] = {'\'', 0};
+        sz += 2;
+        val = msi_alloc(sz*sizeof(WCHAR));
+        if (!val)
+            return NULL;
+
+        lstrcpyW(val, szQuote);
+        r = MSI_RecordGetStringW(rec, i, val+1, &sz);
+        lstrcpyW(val+1+sz, szQuote);
+    }
+    else
+    {
+        /* do not quote integer record fields */
+        val = msi_alloc(sz*sizeof(WCHAR));
+        if (!val)
+            return NULL;
+
+        r = MSI_RecordGetStringW(rec, i, val, &sz);
+    }
+
+    if (r != ERROR_SUCCESS)
+    {
+        ERR("failed to get string!\n");
+        msi_free(val);
+        return NULL;
+    }
+
+    return val;
+}
+
+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 = param;
+    MERGETABLE *table = data->curtable;
+    MERGEROW *mergerow;
+    MSIQUERY *dbview = NULL;
+    MSIRECORD *row = NULL;
+    LPWSTR query = NULL;
+    UINT r = ERROR_SUCCESS;
+
+    if (TABLE_Exists(data->db, table->name))
+    {
+        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;
+
+        r = ERROR_SUCCESS;
+    }
+
+    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 msi_get_table_labels(MSIDATABASE *db, LPCWSTR table, LPWSTR **labels, DWORD *numlabels)
+{
+    UINT r, i, count;
+    MSIRECORD *prec = NULL;
+
+    r = MSI_DatabaseGetPrimaryKeys(db, table, &prec);
+    if (r != ERROR_SUCCESS)
+        return r;
+
+    count = MSI_RecordGetFieldCount(prec);
+    *numlabels = count + 1;
+    *labels = msi_alloc((*numlabels)*sizeof(LPWSTR));
+    if (!*labels)
+    {
+        r = ERROR_OUTOFMEMORY;
+        goto end;
+    }
+
+    (*labels)[0] = strdupW(table);
+    for (i=1; i<=count; i++ )
+    {
+        (*labels)[i] = strdupW(MSI_RecordGetString(prec, i));
+    }
+
+end:
+    msiobj_release( &prec->hdr );
+    return r;
+}
+
+static UINT msi_get_query_columns(MSIQUERY *query, LPWSTR **columns, DWORD *numcolumns)
+{
+    UINT r, i, count;
+    MSIRECORD *prec = NULL;
+
+    r = MSI_ViewGetColumnInfo(query, MSICOLINFO_NAMES, &prec);
+    if (r != ERROR_SUCCESS)
+        return r;
+
+    count = MSI_RecordGetFieldCount(prec);
+    *columns = msi_alloc(count*sizeof(LPWSTR));
+    if (!*columns)
+    {
+        r = ERROR_OUTOFMEMORY;
+        goto end;
+    }
+
+    for (i=1; i<=count; i++ )
+    {
+        (*columns)[i-1] = strdupW(MSI_RecordGetString(prec, i));
+    }
+
+    *numcolumns = count;
+
+end:
+    msiobj_release( &prec->hdr );
+    return r;
+}
+
+static UINT msi_get_query_types(MSIQUERY *query, LPWSTR **types, DWORD *numtypes)
+{
+    UINT r, i, count;
+    MSIRECORD *prec = NULL;
+
+    r = MSI_ViewGetColumnInfo(query, MSICOLINFO_TYPES, &prec);
+    if (r != ERROR_SUCCESS)
+        return r;
+
+    count = MSI_RecordGetFieldCount(prec);
+    *types = msi_alloc(count*sizeof(LPWSTR));
+    if (!*types)
+    {
+        r = ERROR_OUTOFMEMORY;
+        goto end;
+    }
+
+    *numtypes = count;
+    for (i=1; i<=count; i++ )
+    {
+        (*types)[i-1] = strdupW(MSI_RecordGetString(prec, i));
+    }
+
+end:
+    msiobj_release( &prec->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);
+        msiobj_release(&row->data->hdr);
+        msi_free(row);
+    }
+}
+
+static void free_merge_table(MERGETABLE *table)
+{
+    UINT i;
+
+    if (table->labels != NULL)
+    {
+        for (i = 0; i < table->numlabels; i++)
+            msi_free(table->labels[i]);
+
+        msi_free(table->labels);
+    }
+
+    if (table->columns != NULL)
+    {
+        for (i = 0; i < table->numcolumns; i++)
+            msi_free(table->columns[i]);
+
+        msi_free(table->columns);
+    }
+
+    if (table->types != NULL)
+    {
+        for (i = 0; i < table->numtypes; i++)
+            msi_free(table->types[i]);
+
+        msi_free(table->types);
+    }
+
+    msi_free(table->name);
+    merge_free_rows(table);
+
+    msi_free(table);
+}
+
+static UINT msi_get_merge_table (MSIDATABASE *db, LPCWSTR name, MERGETABLE **ptable)
+{
+    UINT r;
+    MERGETABLE *table;
+    MSIQUERY *mergeview = NULL;
+
+    static const WCHAR query[] = {'S','E','L','E','C','T',' ','*',' ',
+        'F','R','O','M',' ','`','%','s','`',0};
+
+    table = msi_alloc_zero(sizeof(MERGETABLE));
+    if (!table)
+    {
+       *ptable = NULL;
+       return ERROR_OUTOFMEMORY;
+    }
+
+    r = msi_get_table_labels(db, name, &table->labels, &table->numlabels);
+    if (r != ERROR_SUCCESS)
+        goto err;
+
+    r = MSI_OpenQuery(db, &mergeview, query, name);
+    if (r != ERROR_SUCCESS)
+        goto err;
+
+    r = msi_get_query_columns(mergeview, &table->columns, &table->numcolumns);
+    if (r != ERROR_SUCCESS)
+        goto err;
+
+    r = msi_get_query_types(mergeview, &table->types, &table->numtypes);
+    if (r != ERROR_SUCCESS)
+        goto err;
+
+    list_init(&table->rows);
+
+    table->name = strdupW(name);
+    table->numconflicts = 0;
+
+    msiobj_release(&mergeview->hdr);
+    *ptable = table;
+    return ERROR_SUCCESS;
+
+err:
+    msiobj_release(&mergeview->hdr);
+    free_merge_table(table);
+    *ptable = NULL;
+    return r;
+}
+
+static UINT merge_diff_tables(MSIRECORD *rec, LPVOID param)
+{
+    MERGEDATA *data = param;
+    MERGETABLE *table;
+    MSIQUERY *dbview = NULL;
+    MSIQUERY *mergeview = NULL;
+    LPCWSTR name;
+    UINT r;
+
+    static const WCHAR query[] = {'S','E','L','E','C','T',' ','*',' ',
+        'F','R','O','M',' ','`','%','s','`',0};
+
+    name = MSI_RecordGetString(rec, 1);
+
+    r = MSI_OpenQuery(data->merge, &mergeview, query, name);
+    if (r != ERROR_SUCCESS)
+        goto done;
+
+    if (TABLE_Exists(data->db, name))
+    {
+        r = MSI_OpenQuery(data->db, &dbview, query, name);
+        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;
+    }
+
+    r = msi_get_merge_table(data->merge, name, &table);
+    if (r != ERROR_SUCCESS)
+        goto done;
+
+    data->curtable = table;
+    data->curview = mergeview;
+    r = MSI_IterateRecords(mergeview, NULL, merge_diff_row, data);
+    if (r != ERROR_SUCCESS)
+    {
+        free_merge_table(table);
+        goto done;
+    }
+
+    list_add_tail(data->tabledata, &table->entry);
+
+done:
+    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;
+
+    if (!TABLE_Exists(db, table->name))
+    {
+        r = msi_add_table_to_db(db, table->columns, table->types,
+               table->labels, table->numlabels, table->numcolumns);
+        if (r != ERROR_SUCCESS)
+           return ERROR_FUNCTION_FAILED;
+    }
+
+    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, -1, 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;
+}
+
+UINT WINAPI MsiDatabaseMergeW(MSIHANDLE hDatabase, MSIHANDLE hDatabaseMerge,
+                              LPCWSTR szTableName)
+{
+    struct list tabledata = LIST_INIT(tabledata);
+    struct list *item, *cursor;
+    MSIDATABASE *db, *merge;
+    MERGETABLE *table;
+    BOOL conflicts;
+    UINT r;
+
+    TRACE("(%d, %d, %s)\n", hDatabase, hDatabaseMerge,
+          debugstr_w(szTableName));
+
+    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);
+        free_merge_table(table);
+    }
+
+    if (conflicts)
+        r = ERROR_FUNCTION_FAILED;
+
+done:
+    msiobj_release(&db->hdr);
+    msiobj_release(&merge->hdr);
+    return r;
+}
+
 MSIDBSTATE WINAPI MsiGetDatabaseState( MSIHANDLE handle )
 {
     MSIDBSTATE ret = MSIDBSTATE_READ;
     MSIDATABASE *db;
 
-    TRACE("%ld\n", handle);
+    TRACE("%d\n", handle);
 
     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
     if( !db )
@@ -1060,18 +2075,18 @@ static ULONG WINAPI mrd_Release( IWineMsiRemoteDatabase *iface )
 }
 
 static HRESULT WINAPI mrd_IsTablePersistent( IWineMsiRemoteDatabase *iface,
-                                             BSTR table, MSICONDITION *persistent )
+                                             LPCWSTR table, MSICONDITION *persistent )
 {
     msi_remote_database_impl *This = mrd_from_IWineMsiRemoteDatabase( iface );
-    *persistent = MsiDatabaseIsTablePersistentW(This->database, (LPWSTR)table);
+    *persistent = MsiDatabaseIsTablePersistentW(This->database, table);
     return S_OK;
 }
 
 static HRESULT WINAPI mrd_GetPrimaryKeys( IWineMsiRemoteDatabase *iface,
-                                          BSTR table, MSIHANDLE *keys )
+                                          LPCWSTR table, MSIHANDLE *keys )
 {
     msi_remote_database_impl *This = mrd_from_IWineMsiRemoteDatabase( iface );
-    UINT r = MsiDatabaseGetPrimaryKeysW(This->database, (LPWSTR)table, keys);
+    UINT r = MsiDatabaseGetPrimaryKeysW(This->database, table, keys);
     return HRESULT_FROM_WIN32(r);
 }
 
@@ -1084,10 +2099,10 @@ static HRESULT WINAPI mrd_GetSummaryInformation( IWineMsiRemoteDatabase *iface,
 }
 
 static HRESULT WINAPI mrd_OpenView( IWineMsiRemoteDatabase *iface,
-                                    BSTR query, MSIHANDLE *view )
+                                    LPCWSTR query, MSIHANDLE *view )
 {
     msi_remote_database_impl *This = mrd_from_IWineMsiRemoteDatabase( iface );
-    UINT r = MsiDatabaseOpenViewW(This->database, (LPWSTR)query, view);
+    UINT r = MsiDatabaseOpenViewW(This->database, query, view);
     return HRESULT_FROM_WIN32(r);
 }