xref: /reactos/dll/win32/msi/database.c (revision cc439606)
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2002,2003,2004,2005 Mike McCormack for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include <stdarg.h>
22 #include <stdio.h>
23 
24 #define COBJMACROS
25 
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winreg.h"
29 #include "winnls.h"
30 #include "wine/debug.h"
31 #include "wine/unicode.h"
32 #include "msi.h"
33 #include "msiquery.h"
34 #include "msipriv.h"
35 #include "objidl.h"
36 #include "objbase.h"
37 #include "msiserver.h"
38 #include "query.h"
39 
40 #include "initguid.h"
41 
42 WINE_DEFAULT_DEBUG_CHANNEL(msi);
43 
44 /*
45  *  .MSI  file format
46  *
47  *  An .msi file is a structured storage file.
48  *  It contains a number of streams.
49  *  A stream for each table in the database.
50  *  Two streams for the string table in the database.
51  *  Any binary data in a table is a reference to a stream.
52  */
53 
54 #define IS_INTMSIDBOPEN(x)      (((ULONG_PTR)(x) >> 16) == 0)
55 
56 struct row_export_info
57 {
58     HANDLE handle;
59     LPCWSTR folder;
60     LPCWSTR table;
61 };
62 
63 static void free_transforms( MSIDATABASE *db )
64 {
65     while( !list_empty( &db->transforms ) )
66     {
67         MSITRANSFORM *t = LIST_ENTRY( list_head( &db->transforms ), MSITRANSFORM, entry );
68         list_remove( &t->entry );
69         IStorage_Release( t->stg );
70         msi_free( t );
71     }
72 }
73 
74 static void free_streams( MSIDATABASE *db )
75 {
76     UINT i;
77     for (i = 0; i < db->num_streams; i++)
78     {
79         if (db->streams[i].stream) IStream_Release( db->streams[i].stream );
80     }
81     msi_free( db->streams );
82 }
83 
84 void append_storage_to_db( MSIDATABASE *db, IStorage *stg )
85 {
86     MSITRANSFORM *t;
87 
88     t = msi_alloc( sizeof *t );
89     t->stg = stg;
90     IStorage_AddRef( stg );
91     list_add_head( &db->transforms, &t->entry );
92 }
93 
94 static VOID MSI_CloseDatabase( MSIOBJECTHDR *arg )
95 {
96     MSIDATABASE *db = (MSIDATABASE *) arg;
97 
98     msi_free(db->path);
99     free_streams( db );
100     free_cached_tables( db );
101     free_transforms( db );
102     if (db->strings) msi_destroy_stringtable( db->strings );
103     IStorage_Release( db->storage );
104     if (db->deletefile)
105     {
106         DeleteFileW( db->deletefile );
107         msi_free( db->deletefile );
108     }
109     msi_free( db->tempfolder );
110 }
111 
112 static HRESULT db_initialize( IStorage *stg, const GUID *clsid )
113 {
114     static const WCHAR szTables[]  = { '_','T','a','b','l','e','s',0 };
115     HRESULT hr;
116 
117     hr = IStorage_SetClass( stg, clsid );
118     if (FAILED( hr ))
119     {
120         WARN("failed to set class id 0x%08x\n", hr);
121         return hr;
122     }
123 
124     /* create the _Tables stream */
125     hr = write_stream_data( stg, szTables, NULL, 0, TRUE );
126     if (FAILED( hr ))
127     {
128         WARN("failed to create _Tables stream 0x%08x\n", hr);
129         return hr;
130     }
131 
132     hr = msi_init_string_table( stg );
133     if (FAILED( hr ))
134     {
135         WARN("failed to initialize string table 0x%08x\n", hr);
136         return hr;
137     }
138 
139     hr = IStorage_Commit( stg, 0 );
140     if (FAILED( hr ))
141     {
142         WARN("failed to commit changes 0x%08x\n", hr);
143         return hr;
144     }
145 
146     return S_OK;
147 }
148 
149 UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb)
150 {
151     IStorage *stg = NULL;
152     HRESULT r;
153     MSIDATABASE *db = NULL;
154     UINT ret = ERROR_FUNCTION_FAILED;
155     LPCWSTR szMode, save_path;
156     STATSTG stat;
157     BOOL created = FALSE, patch = FALSE;
158     WCHAR path[MAX_PATH];
159 
160     TRACE("%s %s\n",debugstr_w(szDBPath),debugstr_w(szPersist) );
161 
162     if( !pdb )
163         return ERROR_INVALID_PARAMETER;
164 
165     if (szPersist - MSIDBOPEN_PATCHFILE <= MSIDBOPEN_CREATEDIRECT)
166     {
167         TRACE("Database is a patch\n");
168         szPersist -= MSIDBOPEN_PATCHFILE;
169         patch = TRUE;
170     }
171 
172     save_path = szDBPath;
173     szMode = szPersist;
174     if( !IS_INTMSIDBOPEN(szPersist) )
175     {
176         if (!CopyFileW( szDBPath, szPersist, FALSE ))
177             return ERROR_OPEN_FAILED;
178 
179         szDBPath = szPersist;
180         szPersist = MSIDBOPEN_TRANSACT;
181         created = TRUE;
182     }
183 
184     if( szPersist == MSIDBOPEN_READONLY )
185     {
186         r = StgOpenStorage( szDBPath, NULL,
187               STGM_DIRECT|STGM_READ|STGM_SHARE_DENY_WRITE, NULL, 0, &stg);
188     }
189     else if( szPersist == MSIDBOPEN_CREATE )
190     {
191         r = StgCreateDocfile( szDBPath,
192               STGM_CREATE|STGM_TRANSACTED|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 0, &stg );
193 
194         if( SUCCEEDED(r) )
195             r = db_initialize( stg, patch ? &CLSID_MsiPatch : &CLSID_MsiDatabase );
196         created = TRUE;
197     }
198     else if( szPersist == MSIDBOPEN_CREATEDIRECT )
199     {
200         r = StgCreateDocfile( szDBPath,
201               STGM_CREATE|STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 0, &stg );
202 
203         if( SUCCEEDED(r) )
204             r = db_initialize( stg, patch ? &CLSID_MsiPatch : &CLSID_MsiDatabase );
205         created = TRUE;
206     }
207     else if( szPersist == MSIDBOPEN_TRANSACT )
208     {
209         r = StgOpenStorage( szDBPath, NULL,
210               STGM_TRANSACTED|STGM_READWRITE|STGM_SHARE_DENY_WRITE, NULL, 0, &stg);
211     }
212     else if( szPersist == MSIDBOPEN_DIRECT )
213     {
214         r = StgOpenStorage( szDBPath, NULL,
215               STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg);
216     }
217     else
218     {
219         ERR("unknown flag %p\n",szPersist);
220         return ERROR_INVALID_PARAMETER;
221     }
222 
223     if( FAILED( r ) || !stg )
224     {
225         WARN("open failed r = %08x for %s\n", r, debugstr_w(szDBPath));
226         return ERROR_FUNCTION_FAILED;
227     }
228 
229     r = IStorage_Stat( stg, &stat, STATFLAG_NONAME );
230     if( FAILED( r ) )
231     {
232         FIXME("Failed to stat storage\n");
233         goto end;
234     }
235 
236     if ( !IsEqualGUID( &stat.clsid, &CLSID_MsiDatabase ) &&
237          !IsEqualGUID( &stat.clsid, &CLSID_MsiPatch ) &&
238          !IsEqualGUID( &stat.clsid, &CLSID_MsiTransform ) )
239     {
240         ERR("storage GUID is not a MSI database GUID %s\n",
241              debugstr_guid(&stat.clsid) );
242         goto end;
243     }
244 
245     if ( patch && !IsEqualGUID( &stat.clsid, &CLSID_MsiPatch ) )
246     {
247         ERR("storage GUID is not the MSI patch GUID %s\n",
248              debugstr_guid(&stat.clsid) );
249         ret = ERROR_OPEN_FAILED;
250         goto end;
251     }
252 
253     db = alloc_msiobject( MSIHANDLETYPE_DATABASE, sizeof (MSIDATABASE),
254                               MSI_CloseDatabase );
255     if( !db )
256     {
257         FIXME("Failed to allocate a handle\n");
258         goto end;
259     }
260 
261     if (!strchrW( save_path, '\\' ))
262     {
263         GetCurrentDirectoryW( MAX_PATH, path );
264         lstrcatW( path, szBackSlash );
265         lstrcatW( path, save_path );
266     }
267     else
268         lstrcpyW( path, save_path );
269 
270     db->path = strdupW( path );
271     db->media_transform_offset = MSI_INITIAL_MEDIA_TRANSFORM_OFFSET;
272     db->media_transform_disk_id = MSI_INITIAL_MEDIA_TRANSFORM_DISKID;
273 
274     if( TRACE_ON( msi ) )
275         enum_stream_names( stg );
276 
277     db->storage = stg;
278     db->mode = szMode;
279     if (created)
280         db->deletefile = strdupW( szDBPath );
281     list_init( &db->tables );
282     list_init( &db->transforms );
283 
284     db->strings = msi_load_string_table( stg, &db->bytes_per_strref );
285     if( !db->strings )
286         goto end;
287 
288     ret = ERROR_SUCCESS;
289 
290     msiobj_addref( &db->hdr );
291     IStorage_AddRef( stg );
292     *pdb = db;
293 
294 end:
295     if( db )
296         msiobj_release( &db->hdr );
297     if( stg )
298         IStorage_Release( stg );
299 
300     return ret;
301 }
302 
303 UINT WINAPI MsiOpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIHANDLE *phDB)
304 {
305     MSIDATABASE *db;
306     UINT ret;
307 
308     TRACE("%s %s %p\n",debugstr_w(szDBPath),debugstr_w(szPersist), phDB);
309 
310     ret = MSI_OpenDatabaseW( szDBPath, szPersist, &db );
311     if( ret == ERROR_SUCCESS )
312     {
313         *phDB = alloc_msihandle( &db->hdr );
314         if (! *phDB)
315             ret = ERROR_NOT_ENOUGH_MEMORY;
316         msiobj_release( &db->hdr );
317     }
318 
319     return ret;
320 }
321 
322 UINT WINAPI MsiOpenDatabaseA(LPCSTR szDBPath, LPCSTR szPersist, MSIHANDLE *phDB)
323 {
324     HRESULT r = ERROR_FUNCTION_FAILED;
325     LPWSTR szwDBPath = NULL, szwPersist = NULL;
326 
327     TRACE("%s %s %p\n", debugstr_a(szDBPath), debugstr_a(szPersist), phDB);
328 
329     if( szDBPath )
330     {
331         szwDBPath = strdupAtoW( szDBPath );
332         if( !szwDBPath )
333             goto end;
334     }
335 
336     if( !IS_INTMSIDBOPEN(szPersist) )
337     {
338         szwPersist = strdupAtoW( szPersist );
339         if( !szwPersist )
340             goto end;
341     }
342     else
343         szwPersist = (LPWSTR)(DWORD_PTR)szPersist;
344 
345     r = MsiOpenDatabaseW( szwDBPath, szwPersist, phDB );
346 
347 end:
348     if( !IS_INTMSIDBOPEN(szPersist) )
349         msi_free( szwPersist );
350     msi_free( szwDBPath );
351 
352     return r;
353 }
354 
355 static LPWSTR msi_read_text_archive(LPCWSTR path, DWORD *len)
356 {
357     HANDLE file;
358     LPSTR data = NULL;
359     LPWSTR wdata = NULL;
360     DWORD read, size = 0;
361 
362     file = CreateFileW( path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );
363     if (file == INVALID_HANDLE_VALUE)
364         return NULL;
365 
366     size = GetFileSize( file, NULL );
367     if (!(data = msi_alloc( size ))) goto done;
368 
369     if (!ReadFile( file, data, size, &read, NULL ) || read != size) goto done;
370 
371     while (!data[size - 1]) size--;
372     *len = MultiByteToWideChar( CP_ACP, 0, data, size, NULL, 0 );
373     if ((wdata = msi_alloc( (*len + 1) * sizeof(WCHAR) )))
374     {
375         MultiByteToWideChar( CP_ACP, 0, data, size, wdata, *len );
376         wdata[*len] = 0;
377     }
378 
379 done:
380     CloseHandle( file );
381     msi_free( data );
382     return wdata;
383 }
384 
385 static void msi_parse_line(LPWSTR *line, LPWSTR **entries, DWORD *num_entries, DWORD *len)
386 {
387     LPWSTR ptr = *line, save;
388     DWORD i, count = 1, chars_left = *len;
389 
390     *entries = NULL;
391 
392     /* stay on this line */
393     while (chars_left && *ptr != '\n')
394     {
395         /* entries are separated by tabs */
396         if (*ptr == '\t')
397             count++;
398 
399         ptr++;
400         chars_left--;
401     }
402 
403     *entries = msi_alloc(count * sizeof(LPWSTR));
404     if (!*entries)
405         return;
406 
407     /* store pointers into the data */
408     chars_left = *len;
409     for (i = 0, ptr = *line; i < count; i++)
410     {
411         while (chars_left && *ptr == '\r')
412         {
413             ptr++;
414             chars_left--;
415         }
416         save = ptr;
417 
418         while (chars_left && *ptr != '\t' && *ptr != '\n' && *ptr != '\r')
419         {
420             if (!*ptr) *ptr = '\n'; /* convert embedded nulls to \n */
421             if (ptr > *line && *ptr == '\x19' && *(ptr - 1) == '\x11')
422             {
423                 *ptr = '\n';
424                 *(ptr - 1) = '\r';
425             }
426             ptr++;
427             chars_left--;
428         }
429 
430         /* NULL-separate the data */
431         if (*ptr == '\n' || *ptr == '\r')
432         {
433             while (chars_left && (*ptr == '\n' || *ptr == '\r'))
434             {
435                 *(ptr++) = 0;
436                 chars_left--;
437             }
438         }
439         else if (*ptr)
440         {
441             *(ptr++) = 0;
442             chars_left--;
443         }
444         (*entries)[i] = save;
445     }
446 
447     /* move to the next line if there's more, else EOF */
448     *line = ptr;
449     *len = chars_left;
450     if (num_entries)
451         *num_entries = count;
452 }
453 
454 static LPWSTR msi_build_createsql_prelude(LPWSTR table)
455 {
456     LPWSTR prelude;
457     DWORD size;
458 
459     static const WCHAR create_fmt[] = {'C','R','E','A','T','E',' ','T','A','B','L','E',' ','`','%','s','`',' ','(',' ',0};
460 
461     size = sizeof(create_fmt)/sizeof(create_fmt[0]) + lstrlenW(table) - 2;
462     prelude = msi_alloc(size * sizeof(WCHAR));
463     if (!prelude)
464         return NULL;
465 
466     sprintfW(prelude, create_fmt, table);
467     return prelude;
468 }
469 
470 static LPWSTR msi_build_createsql_columns(LPWSTR *columns_data, LPWSTR *types, DWORD num_columns)
471 {
472     LPWSTR columns, p;
473     LPCWSTR type;
474     DWORD sql_size = 1, i, len;
475     WCHAR expanded[128], *ptr;
476     WCHAR size[10], comma[2], extra[30];
477 
478     static const WCHAR column_fmt[] = {'`','%','s','`',' ','%','s','%','s','%','s','%','s',' ',0};
479     static const WCHAR size_fmt[] = {'(','%','s',')',0};
480     static const WCHAR type_char[] = {'C','H','A','R',0};
481     static const WCHAR type_int[] = {'I','N','T',0};
482     static const WCHAR type_long[] = {'L','O','N','G',0};
483     static const WCHAR type_object[] = {'O','B','J','E','C','T',0};
484     static const WCHAR type_notnull[] = {' ','N','O','T',' ','N','U','L','L',0};
485     static const WCHAR localizable[] = {' ','L','O','C','A','L','I','Z','A','B','L','E',0};
486 
487     columns = msi_alloc_zero(sql_size * sizeof(WCHAR));
488     if (!columns)
489         return NULL;
490 
491     for (i = 0; i < num_columns; i++)
492     {
493         type = NULL;
494         comma[1] = size[0] = extra[0] = '\0';
495 
496         if (i == num_columns - 1)
497             comma[0] = '\0';
498         else
499             comma[0] = ',';
500 
501         ptr = &types[i][1];
502         len = atolW(ptr);
503         extra[0] = '\0';
504 
505         switch (types[i][0])
506         {
507             case 'l':
508                 lstrcpyW(extra, type_notnull);
509                 /* fall through */
510             case 'L':
511                 lstrcatW(extra, localizable);
512                 type = type_char;
513                 sprintfW(size, size_fmt, ptr);
514                 break;
515             case 's':
516                 lstrcpyW(extra, type_notnull);
517                 /* fall through */
518             case 'S':
519                 type = type_char;
520                 sprintfW(size, size_fmt, ptr);
521                 break;
522             case 'i':
523                 lstrcpyW(extra, type_notnull);
524                 /* fall through */
525             case 'I':
526                 if (len <= 2)
527                     type = type_int;
528                 else if (len == 4)
529                     type = type_long;
530                 else
531                 {
532                     WARN("invalid int width %u\n", len);
533                     msi_free(columns);
534                     return NULL;
535                 }
536                 break;
537             case 'v':
538                 lstrcpyW(extra, type_notnull);
539                 /* fall through */
540             case 'V':
541                 type = type_object;
542                 break;
543             default:
544                 ERR("Unknown type: %c\n", types[i][0]);
545                 msi_free(columns);
546                 return NULL;
547         }
548 
549         sprintfW(expanded, column_fmt, columns_data[i], type, size, extra, comma);
550         sql_size += lstrlenW(expanded);
551 
552         p = msi_realloc(columns, sql_size * sizeof(WCHAR));
553         if (!p)
554         {
555             msi_free(columns);
556             return NULL;
557         }
558         columns = p;
559 
560         lstrcatW(columns, expanded);
561     }
562 
563     return columns;
564 }
565 
566 static LPWSTR msi_build_createsql_postlude(LPWSTR *primary_keys, DWORD num_keys)
567 {
568     LPWSTR postlude, keys, ptr;
569     DWORD size, i;
570 
571     static const WCHAR key_fmt[] = {'`','%','s','`',',',' ',0};
572     static const WCHAR postlude_fmt[] = {'P','R','I','M','A','R','Y',' ','K','E','Y',' ','%','s',')',0};
573 
574     for (i = 0, size = 1; i < num_keys; i++)
575         size += lstrlenW(key_fmt) + lstrlenW(primary_keys[i]) - 2;
576 
577     keys = msi_alloc(size * sizeof(WCHAR));
578     if (!keys)
579         return NULL;
580 
581     for (i = 0, ptr = keys; i < num_keys; i++)
582     {
583         ptr += sprintfW(ptr, key_fmt, primary_keys[i]);
584     }
585 
586     /* remove final ', ' */
587     *(ptr - 2) = '\0';
588 
589     size = lstrlenW(postlude_fmt) + size - 1;
590     postlude = msi_alloc(size * sizeof(WCHAR));
591     if (!postlude)
592         goto done;
593 
594     sprintfW(postlude, postlude_fmt, keys);
595 
596 done:
597     msi_free(keys);
598     return postlude;
599 }
600 
601 static UINT msi_add_table_to_db(MSIDATABASE *db, LPWSTR *columns, LPWSTR *types, LPWSTR *labels, DWORD num_labels, DWORD num_columns)
602 {
603     UINT r = ERROR_OUTOFMEMORY;
604     DWORD size;
605     MSIQUERY *view;
606     LPWSTR create_sql = NULL;
607     LPWSTR prelude, columns_sql, postlude;
608 
609     prelude = msi_build_createsql_prelude(labels[0]);
610     columns_sql = msi_build_createsql_columns(columns, types, num_columns);
611     postlude = msi_build_createsql_postlude(labels + 1, num_labels - 1); /* skip over table name */
612 
613     if (!prelude || !columns_sql || !postlude)
614         goto done;
615 
616     size = lstrlenW(prelude) + lstrlenW(columns_sql) + lstrlenW(postlude) + 1;
617     create_sql = msi_alloc(size * sizeof(WCHAR));
618     if (!create_sql)
619         goto done;
620 
621     lstrcpyW(create_sql, prelude);
622     lstrcatW(create_sql, columns_sql);
623     lstrcatW(create_sql, postlude);
624 
625     r = MSI_DatabaseOpenViewW( db, create_sql, &view );
626     if (r != ERROR_SUCCESS)
627         goto done;
628 
629     r = MSI_ViewExecute(view, NULL);
630     MSI_ViewClose(view);
631     msiobj_release(&view->hdr);
632 
633 done:
634     msi_free(prelude);
635     msi_free(columns_sql);
636     msi_free(postlude);
637     msi_free(create_sql);
638     return r;
639 }
640 
641 static LPWSTR msi_import_stream_filename(LPCWSTR path, LPCWSTR name)
642 {
643     DWORD len;
644     LPWSTR fullname, ptr;
645 
646     len = lstrlenW(path) + lstrlenW(name) + 1;
647     fullname = msi_alloc(len*sizeof(WCHAR));
648     if (!fullname)
649        return NULL;
650 
651     lstrcpyW( fullname, path );
652 
653     /* chop off extension from path */
654     ptr = strrchrW(fullname, '.');
655     if (!ptr)
656     {
657         msi_free (fullname);
658         return NULL;
659     }
660     *ptr++ = '\\';
661     lstrcpyW( ptr, name );
662     return fullname;
663 }
664 
665 static UINT construct_record(DWORD num_columns, LPWSTR *types,
666                              LPWSTR *data, LPWSTR path, MSIRECORD **rec)
667 {
668     UINT i;
669 
670     *rec = MSI_CreateRecord(num_columns);
671     if (!*rec)
672         return ERROR_OUTOFMEMORY;
673 
674     for (i = 0; i < num_columns; i++)
675     {
676         switch (types[i][0])
677         {
678             case 'L': case 'l': case 'S': case 's':
679                 MSI_RecordSetStringW(*rec, i + 1, data[i]);
680                 break;
681             case 'I': case 'i':
682                 if (*data[i])
683                     MSI_RecordSetInteger(*rec, i + 1, atoiW(data[i]));
684                 break;
685             case 'V': case 'v':
686                 if (*data[i])
687                 {
688                     UINT r;
689                     LPWSTR file = msi_import_stream_filename(path, data[i]);
690                     if (!file)
691                         return ERROR_FUNCTION_FAILED;
692 
693                     r = MSI_RecordSetStreamFromFileW(*rec, i + 1, file);
694                     msi_free (file);
695                     if (r != ERROR_SUCCESS)
696                         return ERROR_FUNCTION_FAILED;
697                 }
698                 break;
699             default:
700                 ERR("Unhandled column type: %c\n", types[i][0]);
701                 msiobj_release(&(*rec)->hdr);
702                 return ERROR_FUNCTION_FAILED;
703         }
704     }
705 
706     return ERROR_SUCCESS;
707 }
708 
709 static UINT msi_add_records_to_table(MSIDATABASE *db, LPWSTR *columns, LPWSTR *types,
710                                      LPWSTR *labels, LPWSTR **records,
711                                      int num_columns, int num_records,
712                                      LPWSTR path)
713 {
714     UINT r;
715     int i;
716     MSIQUERY *view;
717     MSIRECORD *rec;
718 
719     static const WCHAR select[] = {
720         'S','E','L','E','C','T',' ','*',' ',
721         'F','R','O','M',' ','`','%','s','`',0
722     };
723 
724     r = MSI_OpenQuery(db, &view, select, labels[0]);
725     if (r != ERROR_SUCCESS)
726         return r;
727 
728     while (MSI_ViewFetch(view, &rec) != ERROR_NO_MORE_ITEMS)
729     {
730         r = MSI_ViewModify(view, MSIMODIFY_DELETE, rec);
731         msiobj_release(&rec->hdr);
732         if (r != ERROR_SUCCESS)
733             goto done;
734     }
735 
736     for (i = 0; i < num_records; i++)
737     {
738         r = construct_record(num_columns, types, records[i], path, &rec);
739         if (r != ERROR_SUCCESS)
740             goto done;
741 
742         r = MSI_ViewModify(view, MSIMODIFY_INSERT, rec);
743         if (r != ERROR_SUCCESS)
744         {
745             msiobj_release(&rec->hdr);
746             goto done;
747         }
748 
749         msiobj_release(&rec->hdr);
750     }
751 
752 done:
753     msiobj_release(&view->hdr);
754     return r;
755 }
756 
757 static UINT MSI_DatabaseImport(MSIDATABASE *db, LPCWSTR folder, LPCWSTR file)
758 {
759     UINT r;
760     DWORD len, i;
761     DWORD num_labels, num_types;
762     DWORD num_columns, num_records = 0;
763     LPWSTR *columns, *types, *labels;
764     LPWSTR path, ptr, data;
765     LPWSTR **records = NULL;
766     LPWSTR **temp_records;
767 
768     static const WCHAR suminfo[] =
769         {'_','S','u','m','m','a','r','y','I','n','f','o','r','m','a','t','i','o','n',0};
770     static const WCHAR forcecodepage[] =
771         {'_','F','o','r','c','e','C','o','d','e','p','a','g','e',0};
772 
773     TRACE("%p %s %s\n", db, debugstr_w(folder), debugstr_w(file) );
774 
775     if( folder == NULL || file == NULL )
776         return ERROR_INVALID_PARAMETER;
777 
778     len = lstrlenW(folder) + lstrlenW(szBackSlash) + lstrlenW(file) + 1;
779     path = msi_alloc( len * sizeof(WCHAR) );
780     if (!path)
781         return ERROR_OUTOFMEMORY;
782 
783     lstrcpyW( path, folder );
784     lstrcatW( path, szBackSlash );
785     lstrcatW( path, file );
786 
787     data = msi_read_text_archive( path, &len );
788     if (!data)
789     {
790         msi_free(path);
791         return ERROR_FUNCTION_FAILED;
792     }
793 
794     ptr = data;
795     msi_parse_line( &ptr, &columns, &num_columns, &len );
796     msi_parse_line( &ptr, &types, &num_types, &len );
797     msi_parse_line( &ptr, &labels, &num_labels, &len );
798 
799     if (num_columns == 1 && !columns[0][0] && num_labels == 1 && !labels[0][0] &&
800         num_types == 2 && !strcmpW( types[1], forcecodepage ))
801     {
802         r = msi_set_string_table_codepage( db->strings, atoiW( types[0] ) );
803         goto done;
804     }
805 
806     if (num_columns != num_types)
807     {
808         r = ERROR_FUNCTION_FAILED;
809         goto done;
810     }
811 
812     records = msi_alloc(sizeof(LPWSTR *));
813     if (!records)
814     {
815         r = ERROR_OUTOFMEMORY;
816         goto done;
817     }
818 
819     /* read in the table records */
820     while (len)
821     {
822         msi_parse_line( &ptr, &records[num_records], NULL, &len );
823 
824         num_records++;
825         temp_records = msi_realloc(records, (num_records + 1) * sizeof(LPWSTR *));
826         if (!temp_records)
827         {
828             r = ERROR_OUTOFMEMORY;
829             goto done;
830         }
831         records = temp_records;
832     }
833 
834     if (!strcmpW(labels[0], suminfo))
835     {
836         r = msi_add_suminfo( db, records, num_records, num_columns );
837         if (r != ERROR_SUCCESS)
838         {
839             r = ERROR_FUNCTION_FAILED;
840             goto done;
841         }
842     }
843     else
844     {
845         if (!TABLE_Exists(db, labels[0]))
846         {
847             r = msi_add_table_to_db( db, columns, types, labels, num_labels, num_columns );
848             if (r != ERROR_SUCCESS)
849             {
850                 r = ERROR_FUNCTION_FAILED;
851                 goto done;
852             }
853         }
854 
855         r = msi_add_records_to_table( db, columns, types, labels, records, num_columns, num_records, path );
856     }
857 
858 done:
859     msi_free(path);
860     msi_free(data);
861     msi_free(columns);
862     msi_free(types);
863     msi_free(labels);
864 
865     for (i = 0; i < num_records; i++)
866         msi_free(records[i]);
867 
868     msi_free(records);
869 
870     return r;
871 }
872 
873 UINT WINAPI MsiDatabaseImportW(MSIHANDLE handle, LPCWSTR szFolder, LPCWSTR szFilename)
874 {
875     MSIDATABASE *db;
876     UINT r;
877 
878     TRACE("%x %s %s\n",handle,debugstr_w(szFolder), debugstr_w(szFilename));
879 
880     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
881     if( !db )
882     {
883         IWineMsiRemoteDatabase *remote_database;
884 
885         remote_database = (IWineMsiRemoteDatabase *)msi_get_remote( handle );
886         if ( !remote_database )
887             return ERROR_INVALID_HANDLE;
888 
889         IWineMsiRemoteDatabase_Release( remote_database );
890         WARN("MsiDatabaseImport not allowed during a custom action!\n");
891 
892         return ERROR_SUCCESS;
893     }
894 
895     r = MSI_DatabaseImport( db, szFolder, szFilename );
896     msiobj_release( &db->hdr );
897     return r;
898 }
899 
900 UINT WINAPI MsiDatabaseImportA( MSIHANDLE handle,
901                LPCSTR szFolder, LPCSTR szFilename )
902 {
903     LPWSTR path = NULL, file = NULL;
904     UINT r = ERROR_OUTOFMEMORY;
905 
906     TRACE("%x %s %s\n", handle, debugstr_a(szFolder), debugstr_a(szFilename));
907 
908     if( szFolder )
909     {
910         path = strdupAtoW( szFolder );
911         if( !path )
912             goto end;
913     }
914 
915     if( szFilename )
916     {
917         file = strdupAtoW( szFilename );
918         if( !file )
919             goto end;
920     }
921 
922     r = MsiDatabaseImportW( handle, path, file );
923 
924 end:
925     msi_free( path );
926     msi_free( file );
927 
928     return r;
929 }
930 
931 static UINT msi_export_field( HANDLE handle, MSIRECORD *row, UINT field )
932 {
933     char *buffer;
934     BOOL bret;
935     DWORD sz;
936     UINT r;
937 
938     sz = 0x100;
939     buffer = msi_alloc( sz );
940     if (!buffer)
941         return ERROR_OUTOFMEMORY;
942 
943     r = MSI_RecordGetStringA( row, field, buffer, &sz );
944     if (r == ERROR_MORE_DATA)
945     {
946         char *p;
947 
948         sz++; /* leave room for NULL terminator */
949         p = msi_realloc( buffer, sz );
950         if (!p)
951         {
952             msi_free( buffer );
953             return ERROR_OUTOFMEMORY;
954         }
955         buffer = p;
956 
957         r = MSI_RecordGetStringA( row, field, buffer, &sz );
958         if (r != ERROR_SUCCESS)
959         {
960             msi_free( buffer );
961             return r;
962         }
963     }
964     else if (r != ERROR_SUCCESS)
965         return r;
966 
967     bret = WriteFile( handle, buffer, sz, &sz, NULL );
968     msi_free( buffer );
969     if (!bret)
970         return ERROR_FUNCTION_FAILED;
971 
972     return r;
973 }
974 
975 static UINT msi_export_stream( LPCWSTR folder, LPCWSTR table, MSIRECORD *row, UINT field,
976                                UINT start )
977 {
978     static const WCHAR fmt_file[] = { '%','s','/','%','s','/','%','s',0 };
979     static const WCHAR fmt_folder[] = { '%','s','/','%','s',0 };
980     WCHAR stream_name[256], stream_filename[MAX_PATH];
981     DWORD sz, read_size, write_size;
982     char buffer[1024];
983     HANDLE file;
984     UINT r;
985 
986     /* get the name of the file */
987     sz = sizeof(stream_name)/sizeof(WCHAR);
988     r = MSI_RecordGetStringW( row, start, stream_name, &sz );
989     if (r != ERROR_SUCCESS)
990         return r;
991 
992     /* if the destination folder does not exist then create it (folder name = table name) */
993     snprintfW( stream_filename, sizeof(stream_filename)/sizeof(WCHAR), fmt_folder, folder, table );
994     if (GetFileAttributesW( stream_filename ) == INVALID_FILE_ATTRIBUTES)
995     {
996         if (!CreateDirectoryW( stream_filename, NULL ))
997             return ERROR_PATH_NOT_FOUND;
998     }
999 
1000     /* actually create the file */
1001     snprintfW( stream_filename, sizeof(stream_filename)/sizeof(WCHAR), fmt_file, folder, table, stream_name );
1002     file = CreateFileW( stream_filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
1003                         NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
1004     if (file == INVALID_HANDLE_VALUE)
1005         return ERROR_FILE_NOT_FOUND;
1006 
1007     /* copy the stream to the file */
1008     read_size = sizeof(buffer);
1009     while (read_size == sizeof(buffer))
1010     {
1011         r = MSI_RecordReadStream( row, field, buffer, &read_size );
1012         if (r != ERROR_SUCCESS)
1013         {
1014             CloseHandle( file );
1015             return r;
1016         }
1017         if (!WriteFile( file, buffer, read_size, &write_size, NULL ) || read_size != write_size)
1018         {
1019             CloseHandle( file );
1020             return ERROR_WRITE_FAULT;
1021         }
1022     }
1023     CloseHandle( file );
1024     return r;
1025 }
1026 
1027 static UINT msi_export_record( struct row_export_info *row_export_info, MSIRECORD *row, UINT start )
1028 {
1029     HANDLE handle = row_export_info->handle;
1030     UINT i, count, r = ERROR_SUCCESS;
1031     const char *sep;
1032     DWORD sz;
1033 
1034     count = MSI_RecordGetFieldCount( row );
1035     for (i = start; i <= count; i++)
1036     {
1037         r = msi_export_field( handle, row, i );
1038         if (r == ERROR_INVALID_PARAMETER)
1039         {
1040             r = msi_export_stream( row_export_info->folder, row_export_info->table, row, i, start );
1041             if (r != ERROR_SUCCESS)
1042                 return r;
1043 
1044             /* exporting a binary stream, repeat the "Name" field */
1045             r = msi_export_field( handle, row, start );
1046             if (r != ERROR_SUCCESS)
1047                 return r;
1048         }
1049         else if (r != ERROR_SUCCESS)
1050             return r;
1051 
1052         sep = (i < count) ? "\t" : "\r\n";
1053         if (!WriteFile( handle, sep, strlen(sep), &sz, NULL ))
1054             return ERROR_FUNCTION_FAILED;
1055     }
1056     return r;
1057 }
1058 
1059 static UINT msi_export_row( MSIRECORD *row, void *arg )
1060 {
1061     return msi_export_record( arg, row, 1 );
1062 }
1063 
1064 static UINT msi_export_forcecodepage( HANDLE handle, UINT codepage )
1065 {
1066     static const char fmt[] = "\r\n\r\n%u\t_ForceCodepage\r\n";
1067     char data[sizeof(fmt) + 10];
1068     DWORD sz;
1069 
1070     sprintf( data, fmt, codepage );
1071 
1072     sz = lstrlenA(data) + 1;
1073     if (!WriteFile(handle, data, sz, &sz, NULL))
1074         return ERROR_FUNCTION_FAILED;
1075 
1076     return ERROR_SUCCESS;
1077 }
1078 
1079 static UINT msi_export_summaryinformation( MSIDATABASE *db, HANDLE handle )
1080 {
1081     static const char header[] = "PropertyId\tValue\r\n"
1082                                  "i2\tl255\r\n"
1083                                  "_SummaryInformation\tPropertyId\r\n";
1084     DWORD sz;
1085 
1086     sz = lstrlenA(header);
1087     if (!WriteFile(handle, header, sz, &sz, NULL))
1088         return ERROR_WRITE_FAULT;
1089 
1090     return msi_export_suminfo( db, handle );
1091 }
1092 
1093 static UINT MSI_DatabaseExport( MSIDATABASE *db, LPCWSTR table,
1094                LPCWSTR folder, LPCWSTR file )
1095 {
1096     static const WCHAR summaryinformation[] = {
1097         '_','S','u','m','m','a','r','y','I','n','f','o','r','m','a','t','i','o','n',0 };
1098     static const WCHAR query[] = {
1099         's','e','l','e','c','t',' ','*',' ','f','r','o','m',' ','%','s',0 };
1100     static const WCHAR forcecodepage[] = {
1101         '_','F','o','r','c','e','C','o','d','e','p','a','g','e',0 };
1102     MSIRECORD *rec = NULL;
1103     MSIQUERY *view = NULL;
1104     LPWSTR filename;
1105     HANDLE handle;
1106     UINT len, r;
1107 
1108     TRACE("%p %s %s %s\n", db, debugstr_w(table),
1109           debugstr_w(folder), debugstr_w(file) );
1110 
1111     if( folder == NULL || file == NULL )
1112         return ERROR_INVALID_PARAMETER;
1113 
1114     len = lstrlenW(folder) + lstrlenW(file) + 2;
1115     filename = msi_alloc(len * sizeof (WCHAR));
1116     if (!filename)
1117         return ERROR_OUTOFMEMORY;
1118 
1119     lstrcpyW( filename, folder );
1120     lstrcatW( filename, szBackSlash );
1121     lstrcatW( filename, file );
1122 
1123     handle = CreateFileW( filename, GENERIC_READ | GENERIC_WRITE, 0,
1124                           NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
1125     msi_free( filename );
1126     if (handle == INVALID_HANDLE_VALUE)
1127         return ERROR_FUNCTION_FAILED;
1128 
1129     if (!strcmpW( table, forcecodepage ))
1130     {
1131         UINT codepage = msi_get_string_table_codepage( db->strings );
1132         r = msi_export_forcecodepage( handle, codepage );
1133         goto done;
1134     }
1135 
1136     if (!strcmpW( table, summaryinformation ))
1137     {
1138         r = msi_export_summaryinformation( db, handle );
1139         goto done;
1140     }
1141 
1142     r = MSI_OpenQuery( db, &view, query, table );
1143     if (r == ERROR_SUCCESS)
1144     {
1145         struct row_export_info row_export_info = { handle, folder, table };
1146 
1147         /* write out row 1, the column names */
1148         r = MSI_ViewGetColumnInfo(view, MSICOLINFO_NAMES, &rec);
1149         if (r == ERROR_SUCCESS)
1150         {
1151             msi_export_record( &row_export_info, rec, 1 );
1152             msiobj_release( &rec->hdr );
1153         }
1154 
1155         /* write out row 2, the column types */
1156         r = MSI_ViewGetColumnInfo(view, MSICOLINFO_TYPES, &rec);
1157         if (r == ERROR_SUCCESS)
1158         {
1159             msi_export_record( &row_export_info, rec, 1 );
1160             msiobj_release( &rec->hdr );
1161         }
1162 
1163         /* write out row 3, the table name + keys */
1164         r = MSI_DatabaseGetPrimaryKeys( db, table, &rec );
1165         if (r == ERROR_SUCCESS)
1166         {
1167             MSI_RecordSetStringW( rec, 0, table );
1168             msi_export_record( &row_export_info, rec, 0 );
1169             msiobj_release( &rec->hdr );
1170         }
1171 
1172         /* write out row 4 onwards, the data */
1173         r = MSI_IterateRecords( view, 0, msi_export_row, &row_export_info );
1174         msiobj_release( &view->hdr );
1175     }
1176 
1177 done:
1178     CloseHandle( handle );
1179     return r;
1180 }
1181 
1182 /***********************************************************************
1183  * MsiExportDatabaseW        [MSI.@]
1184  *
1185  * Writes a file containing the table data as tab separated ASCII.
1186  *
1187  * The format is as follows:
1188  *
1189  * row1 : colname1 <tab> colname2 <tab> .... colnameN <cr> <lf>
1190  * row2 : coltype1 <tab> coltype2 <tab> .... coltypeN <cr> <lf>
1191  * row3 : tablename <tab> key1 <tab> key2 <tab> ... keyM <cr> <lf>
1192  *
1193  * Followed by the data, starting at row 1 with one row per line
1194  *
1195  * row4 : data <tab> data <tab> data <tab> ... data <cr> <lf>
1196  */
1197 UINT WINAPI MsiDatabaseExportW( MSIHANDLE handle, LPCWSTR szTable,
1198                LPCWSTR szFolder, LPCWSTR szFilename )
1199 {
1200     MSIDATABASE *db;
1201     UINT r;
1202 
1203     TRACE("%x %s %s %s\n", handle, debugstr_w(szTable),
1204           debugstr_w(szFolder), debugstr_w(szFilename));
1205 
1206     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
1207     if( !db )
1208     {
1209         IWineMsiRemoteDatabase *remote_database;
1210 
1211         remote_database = (IWineMsiRemoteDatabase *)msi_get_remote( handle );
1212         if ( !remote_database )
1213             return ERROR_INVALID_HANDLE;
1214 
1215         IWineMsiRemoteDatabase_Release( remote_database );
1216         WARN("MsiDatabaseExport not allowed during a custom action!\n");
1217 
1218         return ERROR_SUCCESS;
1219     }
1220 
1221     r = MSI_DatabaseExport( db, szTable, szFolder, szFilename );
1222     msiobj_release( &db->hdr );
1223     return r;
1224 }
1225 
1226 UINT WINAPI MsiDatabaseExportA( MSIHANDLE handle, LPCSTR szTable,
1227                LPCSTR szFolder, LPCSTR szFilename )
1228 {
1229     LPWSTR path = NULL, file = NULL, table = NULL;
1230     UINT r = ERROR_OUTOFMEMORY;
1231 
1232     TRACE("%x %s %s %s\n", handle, debugstr_a(szTable),
1233           debugstr_a(szFolder), debugstr_a(szFilename));
1234 
1235     if( szTable )
1236     {
1237         table = strdupAtoW( szTable );
1238         if( !table )
1239             goto end;
1240     }
1241 
1242     if( szFolder )
1243     {
1244         path = strdupAtoW( szFolder );
1245         if( !path )
1246             goto end;
1247     }
1248 
1249     if( szFilename )
1250     {
1251         file = strdupAtoW( szFilename );
1252         if( !file )
1253             goto end;
1254     }
1255 
1256     r = MsiDatabaseExportW( handle, table, path, file );
1257 
1258 end:
1259     msi_free( table );
1260     msi_free( path );
1261     msi_free( file );
1262 
1263     return r;
1264 }
1265 
1266 UINT WINAPI MsiDatabaseMergeA(MSIHANDLE hDatabase, MSIHANDLE hDatabaseMerge,
1267                               LPCSTR szTableName)
1268 {
1269     UINT r;
1270     LPWSTR table;
1271 
1272     TRACE("(%d, %d, %s)\n", hDatabase, hDatabaseMerge,
1273           debugstr_a(szTableName));
1274 
1275     table = strdupAtoW(szTableName);
1276     r = MsiDatabaseMergeW(hDatabase, hDatabaseMerge, table);
1277 
1278     msi_free(table);
1279     return r;
1280 }
1281 
1282 typedef struct _tagMERGETABLE
1283 {
1284     struct list entry;
1285     struct list rows;
1286     LPWSTR name;
1287     DWORD numconflicts;
1288     LPWSTR *columns;
1289     DWORD numcolumns;
1290     LPWSTR *types;
1291     DWORD numtypes;
1292     LPWSTR *labels;
1293     DWORD numlabels;
1294 } MERGETABLE;
1295 
1296 typedef struct _tagMERGEROW
1297 {
1298     struct list entry;
1299     MSIRECORD *data;
1300 } MERGEROW;
1301 
1302 typedef struct _tagMERGEDATA
1303 {
1304     MSIDATABASE *db;
1305     MSIDATABASE *merge;
1306     MERGETABLE *curtable;
1307     MSIQUERY *curview;
1308     struct list *tabledata;
1309 } MERGEDATA;
1310 
1311 static BOOL merge_type_match(LPCWSTR type1, LPCWSTR type2)
1312 {
1313     if (((type1[0] == 'l') || (type1[0] == 's')) &&
1314         ((type2[0] == 'l') || (type2[0] == 's')))
1315         return TRUE;
1316 
1317     if (((type1[0] == 'L') || (type1[0] == 'S')) &&
1318         ((type2[0] == 'L') || (type2[0] == 'S')))
1319         return TRUE;
1320 
1321     return !strcmpW( type1, type2 );
1322 }
1323 
1324 static UINT merge_verify_colnames(MSIQUERY *dbview, MSIQUERY *mergeview)
1325 {
1326     MSIRECORD *dbrec, *mergerec;
1327     UINT r, i, count;
1328 
1329     r = MSI_ViewGetColumnInfo(dbview, MSICOLINFO_NAMES, &dbrec);
1330     if (r != ERROR_SUCCESS)
1331         return r;
1332 
1333     r = MSI_ViewGetColumnInfo(mergeview, MSICOLINFO_NAMES, &mergerec);
1334     if (r != ERROR_SUCCESS)
1335     {
1336         msiobj_release(&dbrec->hdr);
1337         return r;
1338     }
1339 
1340     count = MSI_RecordGetFieldCount(dbrec);
1341     for (i = 1; i <= count; i++)
1342     {
1343         if (!MSI_RecordGetString(mergerec, i))
1344             break;
1345 
1346         if (strcmpW( MSI_RecordGetString( dbrec, i ), MSI_RecordGetString( mergerec, i ) ))
1347         {
1348             r = ERROR_DATATYPE_MISMATCH;
1349             goto done;
1350         }
1351     }
1352 
1353     msiobj_release(&dbrec->hdr);
1354     msiobj_release(&mergerec->hdr);
1355     dbrec = mergerec = NULL;
1356 
1357     r = MSI_ViewGetColumnInfo(dbview, MSICOLINFO_TYPES, &dbrec);
1358     if (r != ERROR_SUCCESS)
1359         return r;
1360 
1361     r = MSI_ViewGetColumnInfo(mergeview, MSICOLINFO_TYPES, &mergerec);
1362     if (r != ERROR_SUCCESS)
1363     {
1364         msiobj_release(&dbrec->hdr);
1365         return r;
1366     }
1367 
1368     count = MSI_RecordGetFieldCount(dbrec);
1369     for (i = 1; i <= count; i++)
1370     {
1371         if (!MSI_RecordGetString(mergerec, i))
1372             break;
1373 
1374         if (!merge_type_match(MSI_RecordGetString(dbrec, i),
1375                      MSI_RecordGetString(mergerec, i)))
1376         {
1377             r = ERROR_DATATYPE_MISMATCH;
1378             break;
1379         }
1380     }
1381 
1382 done:
1383     msiobj_release(&dbrec->hdr);
1384     msiobj_release(&mergerec->hdr);
1385 
1386     return r;
1387 }
1388 
1389 static UINT merge_verify_primary_keys(MSIDATABASE *db, MSIDATABASE *mergedb,
1390                                       LPCWSTR table)
1391 {
1392     MSIRECORD *dbrec, *mergerec = NULL;
1393     UINT r, i, count;
1394 
1395     r = MSI_DatabaseGetPrimaryKeys(db, table, &dbrec);
1396     if (r != ERROR_SUCCESS)
1397         return r;
1398 
1399     r = MSI_DatabaseGetPrimaryKeys(mergedb, table, &mergerec);
1400     if (r != ERROR_SUCCESS)
1401         goto done;
1402 
1403     count = MSI_RecordGetFieldCount(dbrec);
1404     if (count != MSI_RecordGetFieldCount(mergerec))
1405     {
1406         r = ERROR_DATATYPE_MISMATCH;
1407         goto done;
1408     }
1409 
1410     for (i = 1; i <= count; i++)
1411     {
1412         if (strcmpW( MSI_RecordGetString( dbrec, i ), MSI_RecordGetString( mergerec, i ) ))
1413         {
1414             r = ERROR_DATATYPE_MISMATCH;
1415             goto done;
1416         }
1417     }
1418 
1419 done:
1420     msiobj_release(&dbrec->hdr);
1421     msiobj_release(&mergerec->hdr);
1422 
1423     return r;
1424 }
1425 
1426 static LPWSTR get_key_value(MSIQUERY *view, LPCWSTR key, MSIRECORD *rec)
1427 {
1428     MSIRECORD *colnames;
1429     LPWSTR str, val;
1430     UINT r, i = 0, sz = 0;
1431     int cmp;
1432 
1433     r = MSI_ViewGetColumnInfo(view, MSICOLINFO_NAMES, &colnames);
1434     if (r != ERROR_SUCCESS)
1435         return NULL;
1436 
1437     do
1438     {
1439         str = msi_dup_record_field(colnames, ++i);
1440         cmp = strcmpW( key, str );
1441         msi_free(str);
1442     } while (cmp);
1443 
1444     msiobj_release(&colnames->hdr);
1445 
1446     r = MSI_RecordGetStringW(rec, i, NULL, &sz);
1447     if (r != ERROR_SUCCESS)
1448         return NULL;
1449     sz++;
1450 
1451     if (MSI_RecordGetString(rec, i))  /* check record field is a string */
1452     {
1453         /* quote string record fields */
1454         const WCHAR szQuote[] = {'\'', 0};
1455         sz += 2;
1456         val = msi_alloc(sz*sizeof(WCHAR));
1457         if (!val)
1458             return NULL;
1459 
1460         lstrcpyW(val, szQuote);
1461         r = MSI_RecordGetStringW(rec, i, val+1, &sz);
1462         lstrcpyW(val+1+sz, szQuote);
1463     }
1464     else
1465     {
1466         /* do not quote integer record fields */
1467         val = msi_alloc(sz*sizeof(WCHAR));
1468         if (!val)
1469             return NULL;
1470 
1471         r = MSI_RecordGetStringW(rec, i, val, &sz);
1472     }
1473 
1474     if (r != ERROR_SUCCESS)
1475     {
1476         ERR("failed to get string!\n");
1477         msi_free(val);
1478         return NULL;
1479     }
1480 
1481     return val;
1482 }
1483 
1484 static LPWSTR create_diff_row_query(MSIDATABASE *merge, MSIQUERY *view,
1485                                     LPWSTR table, MSIRECORD *rec)
1486 {
1487     LPWSTR query = NULL, clause = NULL, val;
1488     LPCWSTR setptr, key;
1489     DWORD size, oldsize;
1490     MSIRECORD *keys;
1491     UINT r, i, count;
1492 
1493     static const WCHAR keyset[] = {
1494         '`','%','s','`',' ','=',' ','%','s',' ','A','N','D',' ',0};
1495     static const WCHAR lastkeyset[] = {
1496         '`','%','s','`',' ','=',' ','%','s',' ',0};
1497     static const WCHAR fmt[] = {'S','E','L','E','C','T',' ','*',' ',
1498         'F','R','O','M',' ','`','%','s','`',' ',
1499         'W','H','E','R','E',' ','%','s',0};
1500 
1501     r = MSI_DatabaseGetPrimaryKeys(merge, table, &keys);
1502     if (r != ERROR_SUCCESS)
1503         return NULL;
1504 
1505     clause = msi_alloc_zero(sizeof(WCHAR));
1506     if (!clause)
1507         goto done;
1508 
1509     size = 1;
1510     count = MSI_RecordGetFieldCount(keys);
1511     for (i = 1; i <= count; i++)
1512     {
1513         key = MSI_RecordGetString(keys, i);
1514         val = get_key_value(view, key, rec);
1515 
1516         if (i == count)
1517             setptr = lastkeyset;
1518         else
1519             setptr = keyset;
1520 
1521         oldsize = size;
1522         size += lstrlenW(setptr) + lstrlenW(key) + lstrlenW(val) - 4;
1523         clause = msi_realloc(clause, size * sizeof (WCHAR));
1524         if (!clause)
1525         {
1526             msi_free(val);
1527             goto done;
1528         }
1529 
1530         sprintfW(clause + oldsize - 1, setptr, key, val);
1531         msi_free(val);
1532     }
1533 
1534     size = lstrlenW(fmt) + lstrlenW(table) + lstrlenW(clause) + 1;
1535     query = msi_alloc(size * sizeof(WCHAR));
1536     if (!query)
1537         goto done;
1538 
1539     sprintfW(query, fmt, table, clause);
1540 
1541 done:
1542     msi_free(clause);
1543     msiobj_release(&keys->hdr);
1544     return query;
1545 }
1546 
1547 static UINT merge_diff_row(MSIRECORD *rec, LPVOID param)
1548 {
1549     MERGEDATA *data = param;
1550     MERGETABLE *table = data->curtable;
1551     MERGEROW *mergerow;
1552     MSIQUERY *dbview = NULL;
1553     MSIRECORD *row = NULL;
1554     LPWSTR query = NULL;
1555     UINT r = ERROR_SUCCESS;
1556 
1557     if (TABLE_Exists(data->db, table->name))
1558     {
1559         query = create_diff_row_query(data->merge, data->curview, table->name, rec);
1560         if (!query)
1561             return ERROR_OUTOFMEMORY;
1562 
1563         r = MSI_DatabaseOpenViewW(data->db, query, &dbview);
1564         if (r != ERROR_SUCCESS)
1565             goto done;
1566 
1567         r = MSI_ViewExecute(dbview, NULL);
1568         if (r != ERROR_SUCCESS)
1569             goto done;
1570 
1571         r = MSI_ViewFetch(dbview, &row);
1572         if (r == ERROR_SUCCESS && !MSI_RecordsAreEqual(rec, row))
1573         {
1574             table->numconflicts++;
1575             goto done;
1576         }
1577         else if (r != ERROR_NO_MORE_ITEMS)
1578             goto done;
1579 
1580         r = ERROR_SUCCESS;
1581     }
1582 
1583     mergerow = msi_alloc(sizeof(MERGEROW));
1584     if (!mergerow)
1585     {
1586         r = ERROR_OUTOFMEMORY;
1587         goto done;
1588     }
1589 
1590     mergerow->data = MSI_CloneRecord(rec);
1591     if (!mergerow->data)
1592     {
1593         r = ERROR_OUTOFMEMORY;
1594         msi_free(mergerow);
1595         goto done;
1596     }
1597 
1598     list_add_tail(&table->rows, &mergerow->entry);
1599 
1600 done:
1601     msi_free(query);
1602     msiobj_release(&row->hdr);
1603     msiobj_release(&dbview->hdr);
1604     return r;
1605 }
1606 
1607 static UINT msi_get_table_labels(MSIDATABASE *db, LPCWSTR table, LPWSTR **labels, DWORD *numlabels)
1608 {
1609     UINT r, i, count;
1610     MSIRECORD *prec = NULL;
1611 
1612     r = MSI_DatabaseGetPrimaryKeys(db, table, &prec);
1613     if (r != ERROR_SUCCESS)
1614         return r;
1615 
1616     count = MSI_RecordGetFieldCount(prec);
1617     *numlabels = count + 1;
1618     *labels = msi_alloc((*numlabels)*sizeof(LPWSTR));
1619     if (!*labels)
1620     {
1621         r = ERROR_OUTOFMEMORY;
1622         goto end;
1623     }
1624 
1625     (*labels)[0] = strdupW(table);
1626     for (i=1; i<=count; i++ )
1627     {
1628         (*labels)[i] = strdupW(MSI_RecordGetString(prec, i));
1629     }
1630 
1631 end:
1632     msiobj_release( &prec->hdr );
1633     return r;
1634 }
1635 
1636 static UINT msi_get_query_columns(MSIQUERY *query, LPWSTR **columns, DWORD *numcolumns)
1637 {
1638     UINT r, i, count;
1639     MSIRECORD *prec = NULL;
1640 
1641     r = MSI_ViewGetColumnInfo(query, MSICOLINFO_NAMES, &prec);
1642     if (r != ERROR_SUCCESS)
1643         return r;
1644 
1645     count = MSI_RecordGetFieldCount(prec);
1646     *columns = msi_alloc(count*sizeof(LPWSTR));
1647     if (!*columns)
1648     {
1649         r = ERROR_OUTOFMEMORY;
1650         goto end;
1651     }
1652 
1653     for (i=1; i<=count; i++ )
1654     {
1655         (*columns)[i-1] = strdupW(MSI_RecordGetString(prec, i));
1656     }
1657 
1658     *numcolumns = count;
1659 
1660 end:
1661     msiobj_release( &prec->hdr );
1662     return r;
1663 }
1664 
1665 static UINT msi_get_query_types(MSIQUERY *query, LPWSTR **types, DWORD *numtypes)
1666 {
1667     UINT r, i, count;
1668     MSIRECORD *prec = NULL;
1669 
1670     r = MSI_ViewGetColumnInfo(query, MSICOLINFO_TYPES, &prec);
1671     if (r != ERROR_SUCCESS)
1672         return r;
1673 
1674     count = MSI_RecordGetFieldCount(prec);
1675     *types = msi_alloc(count*sizeof(LPWSTR));
1676     if (!*types)
1677     {
1678         r = ERROR_OUTOFMEMORY;
1679         goto end;
1680     }
1681 
1682     *numtypes = count;
1683     for (i=1; i<=count; i++ )
1684     {
1685         (*types)[i-1] = strdupW(MSI_RecordGetString(prec, i));
1686     }
1687 
1688 end:
1689     msiobj_release( &prec->hdr );
1690     return r;
1691 }
1692 
1693 static void merge_free_rows(MERGETABLE *table)
1694 {
1695     struct list *item, *cursor;
1696 
1697     LIST_FOR_EACH_SAFE(item, cursor, &table->rows)
1698     {
1699         MERGEROW *row = LIST_ENTRY(item, MERGEROW, entry);
1700 
1701         list_remove(&row->entry);
1702         msiobj_release(&row->data->hdr);
1703         msi_free(row);
1704     }
1705 }
1706 
1707 static void free_merge_table(MERGETABLE *table)
1708 {
1709     UINT i;
1710 
1711     if (table->labels != NULL)
1712     {
1713         for (i = 0; i < table->numlabels; i++)
1714             msi_free(table->labels[i]);
1715 
1716         msi_free(table->labels);
1717     }
1718 
1719     if (table->columns != NULL)
1720     {
1721         for (i = 0; i < table->numcolumns; i++)
1722             msi_free(table->columns[i]);
1723 
1724         msi_free(table->columns);
1725     }
1726 
1727     if (table->types != NULL)
1728     {
1729         for (i = 0; i < table->numtypes; i++)
1730             msi_free(table->types[i]);
1731 
1732         msi_free(table->types);
1733     }
1734 
1735     msi_free(table->name);
1736     merge_free_rows(table);
1737 
1738     msi_free(table);
1739 }
1740 
1741 static UINT msi_get_merge_table (MSIDATABASE *db, LPCWSTR name, MERGETABLE **ptable)
1742 {
1743     UINT r;
1744     MERGETABLE *table;
1745     MSIQUERY *mergeview = NULL;
1746 
1747     static const WCHAR query[] = {'S','E','L','E','C','T',' ','*',' ',
1748         'F','R','O','M',' ','`','%','s','`',0};
1749 
1750     table = msi_alloc_zero(sizeof(MERGETABLE));
1751     if (!table)
1752     {
1753        *ptable = NULL;
1754        return ERROR_OUTOFMEMORY;
1755     }
1756 
1757     r = msi_get_table_labels(db, name, &table->labels, &table->numlabels);
1758     if (r != ERROR_SUCCESS)
1759         goto err;
1760 
1761     r = MSI_OpenQuery(db, &mergeview, query, name);
1762     if (r != ERROR_SUCCESS)
1763         goto err;
1764 
1765     r = msi_get_query_columns(mergeview, &table->columns, &table->numcolumns);
1766     if (r != ERROR_SUCCESS)
1767         goto err;
1768 
1769     r = msi_get_query_types(mergeview, &table->types, &table->numtypes);
1770     if (r != ERROR_SUCCESS)
1771         goto err;
1772 
1773     list_init(&table->rows);
1774 
1775     table->name = strdupW(name);
1776     table->numconflicts = 0;
1777 
1778     msiobj_release(&mergeview->hdr);
1779     *ptable = table;
1780     return ERROR_SUCCESS;
1781 
1782 err:
1783     msiobj_release(&mergeview->hdr);
1784     free_merge_table(table);
1785     *ptable = NULL;
1786     return r;
1787 }
1788 
1789 static UINT merge_diff_tables(MSIRECORD *rec, LPVOID param)
1790 {
1791     MERGEDATA *data = param;
1792     MERGETABLE *table;
1793     MSIQUERY *dbview = NULL;
1794     MSIQUERY *mergeview = NULL;
1795     LPCWSTR name;
1796     UINT r;
1797 
1798     static const WCHAR query[] = {'S','E','L','E','C','T',' ','*',' ',
1799         'F','R','O','M',' ','`','%','s','`',0};
1800 
1801     name = MSI_RecordGetString(rec, 1);
1802 
1803     r = MSI_OpenQuery(data->merge, &mergeview, query, name);
1804     if (r != ERROR_SUCCESS)
1805         goto done;
1806 
1807     if (TABLE_Exists(data->db, name))
1808     {
1809         r = MSI_OpenQuery(data->db, &dbview, query, name);
1810         if (r != ERROR_SUCCESS)
1811             goto done;
1812 
1813         r = merge_verify_colnames(dbview, mergeview);
1814         if (r != ERROR_SUCCESS)
1815             goto done;
1816 
1817         r = merge_verify_primary_keys(data->db, data->merge, name);
1818         if (r != ERROR_SUCCESS)
1819             goto done;
1820     }
1821 
1822     r = msi_get_merge_table(data->merge, name, &table);
1823     if (r != ERROR_SUCCESS)
1824         goto done;
1825 
1826     data->curtable = table;
1827     data->curview = mergeview;
1828     r = MSI_IterateRecords(mergeview, NULL, merge_diff_row, data);
1829     if (r != ERROR_SUCCESS)
1830     {
1831         free_merge_table(table);
1832         goto done;
1833     }
1834 
1835     list_add_tail(data->tabledata, &table->entry);
1836 
1837 done:
1838     msiobj_release(&dbview->hdr);
1839     msiobj_release(&mergeview->hdr);
1840     return r;
1841 }
1842 
1843 static UINT gather_merge_data(MSIDATABASE *db, MSIDATABASE *merge,
1844                               struct list *tabledata)
1845 {
1846     static const WCHAR query[] = {
1847         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1848         '`','_','T','a','b','l','e','s','`',0};
1849     MSIQUERY *view;
1850     MERGEDATA data;
1851     UINT r;
1852 
1853     r = MSI_DatabaseOpenViewW(merge, query, &view);
1854     if (r != ERROR_SUCCESS)
1855         return r;
1856 
1857     data.db = db;
1858     data.merge = merge;
1859     data.tabledata = tabledata;
1860     r = MSI_IterateRecords(view, NULL, merge_diff_tables, &data);
1861     msiobj_release(&view->hdr);
1862     return r;
1863 }
1864 
1865 static UINT merge_table(MSIDATABASE *db, MERGETABLE *table)
1866 {
1867     UINT r;
1868     MERGEROW *row;
1869     MSIVIEW *tv;
1870 
1871     if (!TABLE_Exists(db, table->name))
1872     {
1873         r = msi_add_table_to_db(db, table->columns, table->types,
1874                table->labels, table->numlabels, table->numcolumns);
1875         if (r != ERROR_SUCCESS)
1876            return ERROR_FUNCTION_FAILED;
1877     }
1878 
1879     LIST_FOR_EACH_ENTRY(row, &table->rows, MERGEROW, entry)
1880     {
1881         r = TABLE_CreateView(db, table->name, &tv);
1882         if (r != ERROR_SUCCESS)
1883             return r;
1884 
1885         r = tv->ops->insert_row(tv, row->data, -1, FALSE);
1886         tv->ops->delete(tv);
1887 
1888         if (r != ERROR_SUCCESS)
1889             return r;
1890     }
1891 
1892     return ERROR_SUCCESS;
1893 }
1894 
1895 static UINT update_merge_errors(MSIDATABASE *db, LPCWSTR error,
1896                                 LPWSTR table, DWORD numconflicts)
1897 {
1898     UINT r;
1899     MSIQUERY *view;
1900 
1901     static const WCHAR create[] = {
1902         'C','R','E','A','T','E',' ','T','A','B','L','E',' ',
1903         '`','%','s','`',' ','(','`','T','a','b','l','e','`',' ',
1904         'C','H','A','R','(','2','5','5',')',' ','N','O','T',' ',
1905         'N','U','L','L',',',' ','`','N','u','m','R','o','w','M','e','r','g','e',
1906         'C','o','n','f','l','i','c','t','s','`',' ','S','H','O','R','T',' ',
1907         'N','O','T',' ','N','U','L','L',' ','P','R','I','M','A','R','Y',' ',
1908         'K','E','Y',' ','`','T','a','b','l','e','`',')',0};
1909     static const WCHAR insert[] = {
1910         'I','N','S','E','R','T',' ','I','N','T','O',' ',
1911         '`','%','s','`',' ','(','`','T','a','b','l','e','`',',',' ',
1912         '`','N','u','m','R','o','w','M','e','r','g','e',
1913         'C','o','n','f','l','i','c','t','s','`',')',' ','V','A','L','U','E','S',
1914         ' ','(','\'','%','s','\'',',',' ','%','d',')',0};
1915 
1916     if (!TABLE_Exists(db, error))
1917     {
1918         r = MSI_OpenQuery(db, &view, create, error);
1919         if (r != ERROR_SUCCESS)
1920             return r;
1921 
1922         r = MSI_ViewExecute(view, NULL);
1923         msiobj_release(&view->hdr);
1924         if (r != ERROR_SUCCESS)
1925             return r;
1926     }
1927 
1928     r = MSI_OpenQuery(db, &view, insert, error, table, numconflicts);
1929     if (r != ERROR_SUCCESS)
1930         return r;
1931 
1932     r = MSI_ViewExecute(view, NULL);
1933     msiobj_release(&view->hdr);
1934     return r;
1935 }
1936 
1937 UINT WINAPI MsiDatabaseMergeW(MSIHANDLE hDatabase, MSIHANDLE hDatabaseMerge,
1938                               LPCWSTR szTableName)
1939 {
1940     struct list tabledata = LIST_INIT(tabledata);
1941     struct list *item, *cursor;
1942     MSIDATABASE *db, *merge;
1943     MERGETABLE *table;
1944     BOOL conflicts;
1945     UINT r;
1946 
1947     TRACE("(%d, %d, %s)\n", hDatabase, hDatabaseMerge,
1948           debugstr_w(szTableName));
1949 
1950     if (szTableName && !*szTableName)
1951         return ERROR_INVALID_TABLE;
1952 
1953     db = msihandle2msiinfo(hDatabase, MSIHANDLETYPE_DATABASE);
1954     merge = msihandle2msiinfo(hDatabaseMerge, MSIHANDLETYPE_DATABASE);
1955     if (!db || !merge)
1956     {
1957         r = ERROR_INVALID_HANDLE;
1958         goto done;
1959     }
1960 
1961     r = gather_merge_data(db, merge, &tabledata);
1962     if (r != ERROR_SUCCESS)
1963         goto done;
1964 
1965     conflicts = FALSE;
1966     LIST_FOR_EACH_ENTRY(table, &tabledata, MERGETABLE, entry)
1967     {
1968         if (table->numconflicts)
1969         {
1970             conflicts = TRUE;
1971 
1972             r = update_merge_errors(db, szTableName, table->name,
1973                                     table->numconflicts);
1974             if (r != ERROR_SUCCESS)
1975                 break;
1976         }
1977         else
1978         {
1979             r = merge_table(db, table);
1980             if (r != ERROR_SUCCESS)
1981                 break;
1982         }
1983     }
1984 
1985     LIST_FOR_EACH_SAFE(item, cursor, &tabledata)
1986     {
1987         MERGETABLE *table = LIST_ENTRY(item, MERGETABLE, entry);
1988         list_remove(&table->entry);
1989         free_merge_table(table);
1990     }
1991 
1992     if (conflicts)
1993         r = ERROR_FUNCTION_FAILED;
1994 
1995 done:
1996     msiobj_release(&db->hdr);
1997     msiobj_release(&merge->hdr);
1998     return r;
1999 }
2000 
2001 MSIDBSTATE WINAPI MsiGetDatabaseState( MSIHANDLE handle )
2002 {
2003     MSIDBSTATE ret = MSIDBSTATE_READ;
2004     MSIDATABASE *db;
2005 
2006     TRACE("%d\n", handle);
2007 
2008     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
2009     if( !db )
2010     {
2011         WARN("MsiGetDatabaseState not allowed during a custom action!\n");
2012         return MSIDBSTATE_ERROR;
2013     }
2014 
2015     if (db->mode != MSIDBOPEN_READONLY )
2016         ret = MSIDBSTATE_WRITE;
2017     msiobj_release( &db->hdr );
2018 
2019     return ret;
2020 }
2021 
2022 typedef struct _msi_remote_database_impl {
2023     IWineMsiRemoteDatabase IWineMsiRemoteDatabase_iface;
2024     MSIHANDLE database;
2025     LONG refs;
2026 } msi_remote_database_impl;
2027 
2028 static inline msi_remote_database_impl *impl_from_IWineMsiRemoteDatabase( IWineMsiRemoteDatabase *iface )
2029 {
2030     return CONTAINING_RECORD(iface, msi_remote_database_impl, IWineMsiRemoteDatabase_iface);
2031 }
2032 
2033 static HRESULT WINAPI mrd_QueryInterface( IWineMsiRemoteDatabase *iface,
2034                                           REFIID riid,LPVOID *ppobj)
2035 {
2036     if( IsEqualCLSID( riid, &IID_IUnknown ) ||
2037         IsEqualCLSID( riid, &IID_IWineMsiRemoteDatabase ) )
2038     {
2039         IWineMsiRemoteDatabase_AddRef( iface );
2040         *ppobj = iface;
2041         return S_OK;
2042     }
2043 
2044     return E_NOINTERFACE;
2045 }
2046 
2047 static ULONG WINAPI mrd_AddRef( IWineMsiRemoteDatabase *iface )
2048 {
2049     msi_remote_database_impl* This = impl_from_IWineMsiRemoteDatabase( iface );
2050 
2051     return InterlockedIncrement( &This->refs );
2052 }
2053 
2054 static ULONG WINAPI mrd_Release( IWineMsiRemoteDatabase *iface )
2055 {
2056     msi_remote_database_impl* This = impl_from_IWineMsiRemoteDatabase( iface );
2057     ULONG r;
2058 
2059     r = InterlockedDecrement( &This->refs );
2060     if (r == 0)
2061     {
2062         MsiCloseHandle( This->database );
2063         msi_free( This );
2064     }
2065     return r;
2066 }
2067 
2068 static HRESULT WINAPI mrd_IsTablePersistent( IWineMsiRemoteDatabase *iface,
2069                                              LPCWSTR table, MSICONDITION *persistent )
2070 {
2071     msi_remote_database_impl *This = impl_from_IWineMsiRemoteDatabase( iface );
2072     *persistent = MsiDatabaseIsTablePersistentW(This->database, table);
2073     return S_OK;
2074 }
2075 
2076 static HRESULT WINAPI mrd_GetPrimaryKeys( IWineMsiRemoteDatabase *iface,
2077                                           LPCWSTR table, MSIHANDLE *keys )
2078 {
2079     msi_remote_database_impl *This = impl_from_IWineMsiRemoteDatabase( iface );
2080     UINT r = MsiDatabaseGetPrimaryKeysW(This->database, table, keys);
2081     return HRESULT_FROM_WIN32(r);
2082 }
2083 
2084 static HRESULT WINAPI mrd_GetSummaryInformation( IWineMsiRemoteDatabase *iface,
2085                                                 UINT updatecount, MSIHANDLE *suminfo )
2086 {
2087     msi_remote_database_impl *This = impl_from_IWineMsiRemoteDatabase( iface );
2088     UINT r = MsiGetSummaryInformationW(This->database, NULL, updatecount, suminfo);
2089     return HRESULT_FROM_WIN32(r);
2090 }
2091 
2092 static HRESULT WINAPI mrd_OpenView( IWineMsiRemoteDatabase *iface,
2093                                     LPCWSTR query, MSIHANDLE *view )
2094 {
2095     msi_remote_database_impl *This = impl_from_IWineMsiRemoteDatabase( iface );
2096     UINT r = MsiDatabaseOpenViewW(This->database, query, view);
2097     return HRESULT_FROM_WIN32(r);
2098 }
2099 
2100 static HRESULT WINAPI mrd_SetMsiHandle( IWineMsiRemoteDatabase *iface, MSIHANDLE handle )
2101 {
2102     msi_remote_database_impl* This = impl_from_IWineMsiRemoteDatabase( iface );
2103     This->database = handle;
2104     return S_OK;
2105 }
2106 
2107 static const IWineMsiRemoteDatabaseVtbl msi_remote_database_vtbl =
2108 {
2109     mrd_QueryInterface,
2110     mrd_AddRef,
2111     mrd_Release,
2112     mrd_IsTablePersistent,
2113     mrd_GetPrimaryKeys,
2114     mrd_GetSummaryInformation,
2115     mrd_OpenView,
2116     mrd_SetMsiHandle,
2117 };
2118 
2119 HRESULT create_msi_remote_database( IUnknown *pOuter, LPVOID *ppObj )
2120 {
2121     msi_remote_database_impl *This;
2122 
2123     This = msi_alloc( sizeof *This );
2124     if (!This)
2125         return E_OUTOFMEMORY;
2126 
2127     This->IWineMsiRemoteDatabase_iface.lpVtbl = &msi_remote_database_vtbl;
2128     This->database = 0;
2129     This->refs = 1;
2130 
2131     *ppObj = &This->IWineMsiRemoteDatabase_iface;
2132 
2133     return S_OK;
2134 }
2135