Relay all the pixel shader calls from d3d9 to wined3d.
[wine] / dlls / msi / msiquery.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2002-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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include <stdarg.h>
22
23 #define COBJMACROS
24
25 #include "windef.h"
26 #include "winbase.h"
27 #include "winerror.h"
28 #include "wine/debug.h"
29 #include "wine/unicode.h"
30 #include "msi.h"
31 #include "msiquery.h"
32 #include "objbase.h"
33 #include "objidl.h"
34 #include "msipriv.h"
35 #include "winnls.h"
36
37 #include "query.h"
38
39 WINE_DEFAULT_DEBUG_CHANNEL(msi);
40
41 static void MSI_CloseView( MSIOBJECTHDR *arg )
42 {
43     MSIQUERY *query = (MSIQUERY*) arg;
44     struct list *ptr, *t;
45
46     if( query->view && query->view->ops->delete )
47         query->view->ops->delete( query->view );
48     msiobj_release( &query->db->hdr );
49
50     LIST_FOR_EACH_SAFE( ptr, t, &query->mem )
51     {
52         HeapFree( GetProcessHeap(), 0, ptr );
53     }
54 }
55
56 UINT VIEW_find_column( MSIVIEW *table, LPCWSTR name, UINT *n )
57 {
58     LPWSTR col_name;
59     UINT i, count, r;
60
61     r = table->ops->get_dimensions( table, NULL, &count );
62     if( r != ERROR_SUCCESS )
63         return r;
64
65     for( i=1; i<=count; i++ )
66     {
67         INT x;
68
69         col_name = NULL;
70         r = table->ops->get_column_info( table, i, &col_name, NULL );
71         if( r != ERROR_SUCCESS )
72             return r;
73         x = lstrcmpW( name, col_name );
74         HeapFree( GetProcessHeap(), 0, col_name );
75         if( !x )
76         {
77             *n = i;
78             return ERROR_SUCCESS;
79         }
80     }
81
82     return ERROR_INVALID_PARAMETER;
83 }
84
85 UINT WINAPI MsiDatabaseOpenViewA(MSIHANDLE hdb,
86               LPCSTR szQuery, MSIHANDLE *phView)
87 {
88     UINT r;
89     LPWSTR szwQuery;
90
91     TRACE("%ld %s %p\n", hdb, debugstr_a(szQuery), phView);
92
93     if( szQuery )
94     {
95         szwQuery = strdupAtoW( szQuery );
96         if( !szwQuery )
97             return ERROR_FUNCTION_FAILED;
98     }
99     else
100         szwQuery = NULL;
101
102     r = MsiDatabaseOpenViewW( hdb, szwQuery, phView);
103
104     HeapFree( GetProcessHeap(), 0, szwQuery );
105     return r;
106 }
107
108 UINT MSI_DatabaseOpenViewW(MSIDATABASE *db,
109               LPCWSTR szQuery, MSIQUERY **pView)
110 {
111     MSIQUERY *query;
112     UINT r;
113
114     TRACE("%s %p\n", debugstr_w(szQuery), pView);
115
116     if( !szQuery)
117         return ERROR_INVALID_PARAMETER;
118
119     /* pre allocate a handle to hold a pointer to the view */
120     query = alloc_msiobject( MSIHANDLETYPE_VIEW, sizeof (MSIQUERY),
121                               MSI_CloseView );
122     if( !query )
123         return ERROR_FUNCTION_FAILED;
124
125     msiobj_addref( &db->hdr );
126     query->row = 0;
127     query->db = db;
128     query->view = NULL;
129     list_init( &query->mem );
130
131     r = MSI_ParseSQL( db, szQuery, &query->view, &query->mem );
132     if( r == ERROR_SUCCESS )
133     {
134         msiobj_addref( &query->hdr );
135         *pView = query;
136     }
137
138     msiobj_release( &query->hdr );
139     return r;
140 }
141
142 static UINT MSI_OpenQueryV( MSIDATABASE *db, MSIQUERY **view,
143                              LPCWSTR fmt, va_list args )
144 {
145     LPWSTR szQuery;
146     LPCWSTR p;
147     UINT sz, rc;
148     va_list va;
149
150     /* figure out how much space we need to allocate */
151     va = args;
152     sz = lstrlenW(fmt) + 1;
153     p = fmt;
154     while (*p)
155     {
156         p = strchrW(p, '%');
157         if (!p)
158             break;
159         p++;
160         switch (*p)
161         {
162         case 's':  /* a string */
163             sz += lstrlenW(va_arg(va,LPCWSTR));
164             break;
165         case 'd':
166         case 'i':  /* an integer -2147483648 seems to be longest */
167             sz += 3*sizeof(int);
168             (void)va_arg(va,int);
169             break;
170         case '%':  /* a single % - leave it alone */
171             break;
172         default:
173             FIXME("Unhandled character type %c\n",*p);
174         }
175         p++;
176     }
177
178     /* construct the string */
179     szQuery = HeapAlloc(GetProcessHeap(), 0, sz*sizeof(WCHAR));
180     va = args;
181     vsnprintfW(szQuery, sz, fmt, va);
182
183     /* perform the query */
184     rc = MSI_DatabaseOpenViewW(db, szQuery, view);
185     HeapFree(GetProcessHeap(), 0, szQuery);
186     return rc;
187 }
188
189 UINT MSI_OpenQuery( MSIDATABASE *db, MSIQUERY **view, LPCWSTR fmt, ... )
190 {
191     UINT r;
192     va_list va;
193
194     va_start(va, fmt);
195     r = MSI_OpenQueryV( db, view, fmt, va );
196     va_end(va);
197
198     return r;
199 }
200
201 UINT MSI_IterateRecords( MSIQUERY *view, DWORD *count,
202                          record_func func, LPVOID param )
203 {
204     MSIRECORD *rec = NULL;
205     UINT r, n = 0, max = 0;
206
207     r = MSI_ViewExecute( view, NULL );
208     if( r != ERROR_SUCCESS )
209         return r;
210
211     if( count )
212         max = *count;
213
214     /* iterate a query */
215     for( n = 0; (max == 0) || (n < max); n++ )
216     {
217         r = MSI_ViewFetch( view, &rec );
218         if( r != ERROR_SUCCESS )
219             break;
220         r = func( rec, param );
221         msiobj_release( &rec->hdr );
222         if( r != ERROR_SUCCESS )
223             break;
224     }
225
226     MSI_ViewClose( view );
227
228     if( count )
229         *count = n;
230
231     if( r == ERROR_NO_MORE_ITEMS )
232         r = ERROR_SUCCESS;
233
234     return r;
235 }
236
237 /* return a single record from a query */
238 MSIRECORD *MSI_QueryGetRecord( MSIDATABASE *db, LPCWSTR fmt, ... )
239 {
240     MSIRECORD *rec = NULL;
241     MSIQUERY *view = NULL;
242     UINT r;
243     va_list va;
244
245     va_start(va, fmt);
246     r = MSI_OpenQueryV( db, &view, fmt, va );
247     va_end(va);
248
249     if( r == ERROR_SUCCESS )
250     {
251         MSI_ViewExecute( view, NULL );
252         MSI_ViewFetch( view, &rec );
253         MSI_ViewClose( view );
254         msiobj_release( &view->hdr );
255     }
256     return rec;
257 }
258
259 UINT WINAPI MsiDatabaseOpenViewW(MSIHANDLE hdb,
260               LPCWSTR szQuery, MSIHANDLE *phView)
261 {
262     MSIDATABASE *db;
263     MSIQUERY *query = NULL;
264     UINT ret;
265
266     TRACE("%s %p\n", debugstr_w(szQuery), phView);
267
268     db = msihandle2msiinfo( hdb, MSIHANDLETYPE_DATABASE );
269     if( !db )
270         return ERROR_INVALID_HANDLE;
271
272     ret = MSI_DatabaseOpenViewW( db, szQuery, &query );
273     if( ret == ERROR_SUCCESS )
274     {
275         *phView = alloc_msihandle( &query->hdr );
276         msiobj_release( &query->hdr );
277     }
278     msiobj_release( &db->hdr );
279
280     return ret;
281 }
282
283 UINT MSI_ViewFetch(MSIQUERY *query, MSIRECORD **prec)
284 {
285     MSIVIEW *view;
286     MSIRECORD *rec;
287     UINT row_count = 0, col_count = 0, i, ival, ret, type;
288
289     TRACE("%p %p\n", query, prec );
290
291     view = query->view;
292     if( !view )
293         return ERROR_FUNCTION_FAILED;
294
295     ret = view->ops->get_dimensions( view, &row_count, &col_count );
296     if( ret )
297         return ret;
298     if( !col_count )
299         return ERROR_INVALID_PARAMETER;
300
301     if( query->row >= row_count )
302         return ERROR_NO_MORE_ITEMS;
303
304     rec = MSI_CreateRecord( col_count );
305     if( !rec )
306         return ERROR_FUNCTION_FAILED;
307
308     for( i=1; i<=col_count; i++ )
309     {
310         ret = view->ops->get_column_info( view, i, NULL, &type );
311         if( ret )
312         {
313             ERR("Error getting column type for %d\n", i );
314             continue;
315         }
316         if (( type != MSITYPE_BINARY) && (type != (MSITYPE_BINARY |
317                                                    MSITYPE_NULLABLE)))
318         {
319             ret = view->ops->fetch_int( view, query->row, i, &ival );
320             if( ret )
321             {
322                 ERR("Error fetching data for %d\n", i );
323                 continue;
324             }
325             if( ! (type & MSITYPE_VALID ) )
326                 ERR("Invalid type!\n");
327
328             /* check if it's nul (0) - if so, don't set anything */
329             if( !ival )
330                 continue;
331
332             if( type & MSITYPE_STRING )
333             {
334                 LPWSTR sval;
335
336                 sval = MSI_makestring( query->db, ival );
337                 MSI_RecordSetStringW( rec, i, sval );
338                 HeapFree( GetProcessHeap(), 0, sval );
339             }
340             else
341             {
342                 if( (type & MSI_DATASIZEMASK) == 2 )
343                     MSI_RecordSetInteger( rec, i, ival - (1<<15) );
344                 else
345                     MSI_RecordSetInteger( rec, i, ival - (1<<31) );
346             }
347         }
348         else
349         {
350             IStream *stm = NULL;
351
352             ret = view->ops->fetch_stream( view, query->row, i, &stm );
353             if( ( ret == ERROR_SUCCESS ) && stm )
354             {
355                 MSI_RecordSetIStream( rec, i, stm );
356                 IStream_Release( stm );
357             }
358             else
359                 ERR("failed to get stream\n");
360         }
361     }
362     query->row ++;
363
364     *prec = rec;
365
366     return ERROR_SUCCESS;
367 }
368
369 UINT WINAPI MsiViewFetch(MSIHANDLE hView, MSIHANDLE *record)
370 {
371     MSIQUERY *query;
372     MSIRECORD *rec = NULL;
373     UINT ret;
374
375     TRACE("%ld %p\n", hView, record);
376
377     query = msihandle2msiinfo( hView, MSIHANDLETYPE_VIEW );
378     if( !query )
379         return ERROR_INVALID_HANDLE;
380     ret = MSI_ViewFetch( query, &rec );
381     if( ret == ERROR_SUCCESS )
382     {
383         *record = alloc_msihandle( &rec->hdr );
384         msiobj_release( &rec->hdr );
385     }
386     msiobj_release( &query->hdr );
387     return ret;
388 }
389
390 UINT MSI_ViewClose(MSIQUERY *query)
391 {
392     MSIVIEW *view;
393
394     TRACE("%p\n", query );
395
396     view = query->view;
397     if( !view )
398         return ERROR_FUNCTION_FAILED;
399     if( !view->ops->close )
400         return ERROR_FUNCTION_FAILED;
401
402     return view->ops->close( view );
403 }
404
405 UINT WINAPI MsiViewClose(MSIHANDLE hView)
406 {
407     MSIQUERY *query;
408     UINT ret;
409
410     TRACE("%ld\n", hView );
411
412     query = msihandle2msiinfo( hView, MSIHANDLETYPE_VIEW );
413     if( !query )
414         return ERROR_INVALID_HANDLE;
415
416     ret = MSI_ViewClose( query );
417     msiobj_release( &query->hdr );
418     return ret;
419 }
420
421 UINT MSI_ViewExecute(MSIQUERY *query, MSIRECORD *rec )
422 {
423     MSIVIEW *view;
424
425     TRACE("%p %p\n", query, rec);
426
427     view = query->view;
428     if( !view )
429         return ERROR_FUNCTION_FAILED;
430     if( !view->ops->execute )
431         return ERROR_FUNCTION_FAILED;
432     query->row = 0;
433
434     return view->ops->execute( view, rec );
435 }
436
437 UINT WINAPI MsiViewExecute(MSIHANDLE hView, MSIHANDLE hRec)
438 {
439     MSIQUERY *query;
440     MSIRECORD *rec = NULL;
441     UINT ret;
442     
443     TRACE("%ld %ld\n", hView, hRec);
444
445     query = msihandle2msiinfo( hView, MSIHANDLETYPE_VIEW );
446     if( !query )
447         return ERROR_INVALID_HANDLE;
448
449     if( hRec )
450     {
451         rec = msihandle2msiinfo( hRec, MSIHANDLETYPE_RECORD );
452         if( !rec )
453         {
454             ret = ERROR_INVALID_HANDLE;
455             goto out;
456         }
457     }
458
459     msiobj_lock( &rec->hdr );
460     ret = MSI_ViewExecute( query, rec );
461     msiobj_unlock( &rec->hdr );
462
463 out:
464     msiobj_release( &query->hdr );
465     if( rec )
466         msiobj_release( &rec->hdr );
467
468     return ret;
469 }
470
471 UINT WINAPI MsiViewGetColumnInfo(MSIHANDLE hView, MSICOLINFO info, MSIHANDLE *hRec)
472 {
473     MSIVIEW *view = NULL;
474     MSIQUERY *query = NULL;
475     MSIRECORD *rec = NULL;
476     UINT r = ERROR_FUNCTION_FAILED, i, count = 0, type;
477     LPWSTR name;
478
479     TRACE("%ld %d %p\n", hView, info, hRec);
480
481     query = msihandle2msiinfo( hView, MSIHANDLETYPE_VIEW );
482     if( !query )
483         return ERROR_INVALID_HANDLE;
484
485     view = query->view;
486     if( !view )
487         goto out;
488
489     if( !view->ops->get_dimensions )
490         goto out;
491
492     r = view->ops->get_dimensions( view, NULL, &count );
493     if( r )
494         goto out;
495     if( !count )
496     {
497         r = ERROR_INVALID_PARAMETER;
498         goto out;
499     }
500
501     rec = MSI_CreateRecord( count );
502     if( !rec )
503     {
504         r = ERROR_FUNCTION_FAILED;
505         goto out;
506     }
507
508     for( i=0; i<count; i++ )
509     {
510         name = NULL;
511         r = view->ops->get_column_info( view, i+1, &name, &type );
512         if( r != ERROR_SUCCESS )
513             continue;
514         MSI_RecordSetStringW( rec, i+1, name );
515         HeapFree( GetProcessHeap(), 0, name );
516     }
517
518     *hRec = alloc_msihandle( &rec->hdr );
519
520 out:
521     msiobj_release( &query->hdr );
522     if( rec )
523         msiobj_release( &rec->hdr );
524
525     return r;
526 }
527
528 UINT WINAPI MsiViewModify( MSIHANDLE hView, MSIMODIFY eModifyMode,
529                 MSIHANDLE hRecord)
530 {
531     MSIVIEW *view = NULL;
532     MSIQUERY *query = NULL;
533     MSIRECORD *rec = NULL;
534     UINT r = ERROR_FUNCTION_FAILED;
535
536     TRACE("%ld %x %ld\n", hView, eModifyMode, hRecord);
537
538     query = msihandle2msiinfo( hView, MSIHANDLETYPE_VIEW );
539     if( !query )
540         return ERROR_INVALID_HANDLE;
541
542     view = query->view;
543     if( !view )
544         goto out;
545
546     if( !view->ops->modify )
547         goto out;
548
549     rec = msihandle2msiinfo( hRecord, MSIHANDLETYPE_RECORD );
550     if( !rec )
551     {
552         r = ERROR_INVALID_HANDLE;
553         goto out;
554     }
555
556     r = view->ops->modify( view, eModifyMode, rec );
557
558 out:
559     msiobj_release( &query->hdr );
560     if( rec )
561         msiobj_release( &rec->hdr );
562
563     return r;
564 }
565
566 UINT WINAPI MsiViewGetErrorW( MSIHANDLE handle, LPWSTR szColumnNameBuffer,
567                               DWORD *pcchBuf )
568 {
569     MSIQUERY *query = NULL;
570
571     FIXME("%ld %p %p\n", handle, szColumnNameBuffer, pcchBuf );
572
573     if( !pcchBuf )
574         return MSIDBERROR_INVALIDARG;
575
576     query = msihandle2msiinfo( handle, MSIHANDLETYPE_VIEW );
577     if( !query )
578         return MSIDBERROR_INVALIDARG;
579
580     msiobj_release( &query->hdr );
581     return MSIDBERROR_NOERROR;
582 }
583
584 UINT WINAPI MsiViewGetErrorA( MSIHANDLE handle, LPSTR szColumnNameBuffer,
585                               DWORD *pcchBuf )
586 {
587     MSIQUERY *query = NULL;
588
589     FIXME("%ld %p %p\n", handle, szColumnNameBuffer, pcchBuf );
590
591     if( !pcchBuf )
592         return MSIDBERROR_INVALIDARG;
593
594     query = msihandle2msiinfo( handle, MSIHANDLETYPE_VIEW );
595     if( !query )
596         return MSIDBERROR_INVALIDARG;
597
598     msiobj_release( &query->hdr );
599     return MSIDBERROR_NOERROR;
600 }
601
602 UINT WINAPI MsiDatabaseApplyTransformA( MSIHANDLE hdb, 
603                  LPCSTR szTransformFile, int iErrorCond)
604 {
605     FIXME("%ld %s %d\n", hdb, debugstr_a(szTransformFile), iErrorCond);
606     return ERROR_CALL_NOT_IMPLEMENTED;
607 }
608
609 UINT WINAPI MsiDatabaseApplyTransformW( MSIHANDLE hdb, 
610                  LPCWSTR szTransformFile, int iErrorCond)
611 {
612     FIXME("%ld %s %d\n", hdb, debugstr_w(szTransformFile), iErrorCond);
613     return ERROR_CALL_NOT_IMPLEMENTED;
614 }
615
616 UINT WINAPI MsiDatabaseGenerateTransformA( MSIHANDLE hdb, MSIHANDLE hdbref,
617                  LPCSTR szTransformFile, int iReserved1, int iReserved2 )
618 {
619     FIXME("%ld %ld %s %d %d\n", hdb, hdbref, 
620            debugstr_a(szTransformFile), iReserved1, iReserved2);
621     return ERROR_CALL_NOT_IMPLEMENTED;
622 }
623
624 UINT WINAPI MsiDatabaseGenerateTransformW( MSIHANDLE hdb, MSIHANDLE hdbref,
625                  LPCWSTR szTransformFile, int iReserved1, int iReserved2 )
626 {
627     FIXME("%ld %ld %s %d %d\n", hdb, hdbref, 
628            debugstr_w(szTransformFile), iReserved1, iReserved2);
629     return ERROR_CALL_NOT_IMPLEMENTED;
630 }
631
632 UINT WINAPI MsiDatabaseCommit( MSIHANDLE hdb )
633 {
634     MSIDATABASE *db;
635     UINT r;
636
637     TRACE("%ld\n", hdb);
638
639     db = msihandle2msiinfo( hdb, MSIHANDLETYPE_DATABASE );
640     if( !db )
641         return ERROR_INVALID_HANDLE;
642
643     /* FIXME: lock the database */
644
645     r = MSI_CommitTables( db );
646
647     /* FIXME: unlock the database */
648
649     msiobj_release( &db->hdr );
650
651     return r;
652 }
653
654 struct msi_primary_key_record_info
655 {
656     DWORD n;
657     MSIRECORD *rec;
658 };
659
660 static UINT msi_primary_key_iterator( MSIRECORD *rec, LPVOID param )
661 {
662     struct msi_primary_key_record_info *info = param;
663     LPCWSTR name;
664     DWORD type;
665
666     type = MSI_RecordGetInteger( rec, 4 );
667     if( type & MSITYPE_KEY )
668     {
669         info->n++;
670         if( info->rec )
671         {
672             name = MSI_RecordGetString( rec, 3 );
673             MSI_RecordSetStringW( info->rec, info->n, name );
674         }
675     }
676
677     return ERROR_SUCCESS;
678 }
679
680 UINT MSI_DatabaseGetPrimaryKeys( MSIDATABASE *db,
681                 LPCWSTR table, MSIRECORD **prec )
682 {
683     static const WCHAR sql[] = {
684         's','e','l','e','c','t',' ','*',' ',
685         'f','r','o','m',' ','`','_','C','o','l','u','m','n','s','`',' ',
686         'w','h','e','r','e',' ',
687         '`','T','a','b','l','e','`',' ','=',' ','\'','%','s','\'',0 };
688     struct msi_primary_key_record_info info;
689     MSIQUERY *query = NULL;
690     MSIVIEW *view;
691     UINT r;
692     
693     r = MSI_OpenQuery( db, &query, sql, table );
694     if( r != ERROR_SUCCESS )
695         return r;
696
697     view = query->view;
698
699     /* count the number of primary key records */
700     info.n = 0;
701     info.rec = 0;
702     r = MSI_IterateRecords( query, 0, msi_primary_key_iterator, &info );
703     if( r == ERROR_SUCCESS )
704     {
705         TRACE("Found %ld primary keys\n", info.n );
706
707         /* allocate a record and fill in the names of the tables */
708         info.rec = MSI_CreateRecord( info.n );
709         info.n = 0;
710         r = MSI_IterateRecords( query, 0, msi_primary_key_iterator, &info );
711         if( r == ERROR_SUCCESS )
712             *prec = info.rec;
713         else
714             msiobj_release( &info.rec->hdr );
715     }
716     msiobj_release( &query->hdr );
717
718     return r;
719 }
720
721 UINT WINAPI MsiDatabaseGetPrimaryKeysW( MSIHANDLE hdb,
722                     LPCWSTR table, MSIHANDLE* phRec )
723 {
724     MSIRECORD *rec = NULL;
725     MSIDATABASE *db;
726     UINT r;
727
728     TRACE("%ld %s %p\n", hdb, debugstr_w(table), phRec);
729
730     db = msihandle2msiinfo( hdb, MSIHANDLETYPE_DATABASE );
731     if( !db )
732         return ERROR_INVALID_HANDLE;
733
734     r = MSI_DatabaseGetPrimaryKeys( db, table, &rec );
735     if( r == ERROR_SUCCESS )
736     {
737         *phRec = alloc_msihandle( &rec->hdr );
738         msiobj_release( &rec->hdr );
739     }
740     msiobj_release( &db->hdr );
741
742     return r;
743 }
744
745 UINT WINAPI MsiDatabaseGetPrimaryKeysA(MSIHANDLE hdb, 
746                     LPCSTR table, MSIHANDLE* phRec)
747 {
748     LPWSTR szwTable = NULL;
749     UINT r;
750
751     TRACE("%ld %s %p\n", hdb, debugstr_a(table), phRec);
752
753     if( table )
754     {
755         szwTable = strdupAtoW( table );
756         if( !szwTable )
757             return ERROR_OUTOFMEMORY;
758     }
759     r = MsiDatabaseGetPrimaryKeysW( hdb, szwTable, phRec );
760     HeapFree( GetProcessHeap(), 0, szwTable );
761
762     return r;
763 }
764
765 UINT WINAPI MsiDatabaseIsTablePersistentA(
766               MSIHANDLE hDatabase, LPSTR szTableName)
767 {
768     FIXME("%lx %s\n", hDatabase, debugstr_a(szTableName));
769     return ERROR_CALL_NOT_IMPLEMENTED;
770 }
771
772 UINT WINAPI MsiDatabaseIsTablePersistentW(
773               MSIHANDLE hDatabase, LPWSTR szTableName)
774 {
775     FIXME("%lx %s\n", hDatabase, debugstr_w(szTableName));
776     return ERROR_CALL_NOT_IMPLEMENTED;
777 }