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