msi: Add test for _Streams table (based on patch by Andrey Turkin).
[wine] / dlls / msi / tests / db.c
1 /*
2  * Copyright (C) 2005 Mike McCormack for CodeWeavers
3  *
4  * A test program for MSI database files.
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 #include <stdio.h>
21
22 #include <windows.h>
23 #include <msi.h>
24 #include <msiquery.h>
25
26 #include "wine/test.h"
27
28 static const char *msifile = "winetest.msi";
29
30 static void test_msidatabase(void)
31 {
32     MSIHANDLE hdb = 0;
33     UINT res;
34
35     DeleteFile(msifile);
36
37     /* create an empty database */
38     res = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb );
39     ok( res == ERROR_SUCCESS , "Failed to create database\n" );
40
41     res = MsiDatabaseCommit( hdb );
42     ok( res == ERROR_SUCCESS , "Failed to commit database\n" );
43
44     res = MsiCloseHandle( hdb );
45     ok( res == ERROR_SUCCESS , "Failed to close database\n" );
46
47     res = DeleteFile( msifile );
48     ok( res == TRUE, "Failed to delete database\n" );
49 }
50
51 static UINT do_query(MSIHANDLE hdb, const char *query, MSIHANDLE *phrec)
52 {
53     MSIHANDLE hview = 0;
54     UINT r, ret;
55
56     /* open a select query */
57     r = MsiDatabaseOpenView(hdb, query, &hview);
58     if (r != ERROR_SUCCESS)
59         return r;
60     r = MsiViewExecute(hview, 0);
61     if (r != ERROR_SUCCESS)
62         return r;
63     ret = MsiViewFetch(hview, phrec);
64     r = MsiViewClose(hview);
65     if (r != ERROR_SUCCESS)
66         return r;
67     r = MsiCloseHandle(hview);
68     if (r != ERROR_SUCCESS)
69         return r;
70     return ret;
71 }
72
73 static void test_msiinsert(void)
74 {
75     MSIHANDLE hdb = 0, hview = 0, hrec = 0;
76     UINT r;
77     const char *query;
78     char buf[80];
79     DWORD sz;
80
81     DeleteFile(msifile);
82
83     /* just MsiOpenDatabase should not create a file */
84     r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
85     ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
86
87     /* create a table */
88     query = "CREATE TABLE `phone` ( "
89             "`id` INT, `name` CHAR(32), `number` CHAR(32) "
90             "PRIMARY KEY `id`)";
91     r = MsiDatabaseOpenView(hdb, query, &hview);
92     ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
93     r = MsiViewExecute(hview, 0);
94     ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
95     r = MsiViewClose(hview);
96     ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
97     r = MsiCloseHandle(hview);
98     ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
99
100     /* insert a value into it */
101     query = "INSERT INTO `phone` ( `id`, `name`, `number` )"
102         "VALUES('1', 'Abe', '8675309')";
103     r = MsiDatabaseOpenView(hdb, query, &hview);
104     ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
105     r = MsiViewExecute(hview, 0);
106     ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
107     r = MsiViewClose(hview);
108     ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
109     r = MsiCloseHandle(hview);
110     ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
111
112     query = "SELECT * FROM `phone` WHERE `id` = 1";
113     r = do_query(hdb, query, &hrec);
114     ok(r == ERROR_SUCCESS, "MsiViewFetch failed\n");
115
116     /* check the record contains what we put in it */
117     r = MsiRecordGetFieldCount(hrec);
118     ok(r == 3, "record count wrong\n");
119
120     todo_wine {
121     r = MsiRecordIsNull(hrec, 0);
122     ok(r == FALSE, "field 0 not null\n");
123     }
124
125     r = MsiRecordGetInteger(hrec, 1);
126     ok(r == 1, "field 1 contents wrong\n");
127     sz = sizeof buf;
128     r = MsiRecordGetString(hrec, 2, buf, &sz);
129     ok(r == ERROR_SUCCESS, "field 2 content fetch failed\n");
130     ok(!strcmp(buf,"Abe"), "field 2 content incorrect\n");
131     sz = sizeof buf;
132     r = MsiRecordGetString(hrec, 3, buf, &sz);
133     ok(r == ERROR_SUCCESS, "field 3 content fetch failed\n");
134     ok(!strcmp(buf,"8675309"), "field 3 content incorrect\n");
135
136     r = MsiCloseHandle(hrec);
137     ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
138
139     /* open a select query */
140     hrec = 100;
141     query = "SELECT * FROM `phone` WHERE `id` >= 10";
142     r = do_query(hdb, query, &hrec);
143     ok(r == ERROR_NO_MORE_ITEMS, "MsiViewFetch failed\n");
144     ok(hrec == 0, "hrec should be null\n");
145
146     r = MsiCloseHandle(hrec);
147     ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
148
149     query = "SELECT * FROM `phone` WHERE `id` < 0";
150     r = do_query(hdb, query, &hrec);
151     ok(r == ERROR_NO_MORE_ITEMS, "MsiViewFetch failed\n");
152
153     query = "SELECT * FROM `phone` WHERE `id` <= 0";
154     r = do_query(hdb, query, &hrec);
155     ok(r == ERROR_NO_MORE_ITEMS, "MsiViewFetch failed\n");
156
157     query = "SELECT * FROM `phone` WHERE `id` <> 1";
158     r = do_query(hdb, query, &hrec);
159     ok(r == ERROR_NO_MORE_ITEMS, "MsiViewFetch failed\n");
160
161     query = "SELECT * FROM `phone` WHERE `id` > 10";
162     r = do_query(hdb, query, &hrec);
163     ok(r == ERROR_NO_MORE_ITEMS, "MsiViewFetch failed\n");
164
165     todo_wine {
166     /* now try a few bad INSERT xqueries */
167     query = "INSERT INTO `phone` ( `id`, `name`, `number` )"
168         "VALUES(?, ?)";
169     r = MsiDatabaseOpenView(hdb, query, &hview);
170     ok(r == ERROR_BAD_QUERY_SYNTAX, "MsiDatabaseOpenView failed\n");
171
172     if (r == ERROR_SUCCESS)
173         r = MsiCloseHandle(hview);
174     }
175
176     /* construct a record to insert */
177     hrec = MsiCreateRecord(4);
178     r = MsiRecordSetInteger(hrec, 1, 2);
179     ok(r == ERROR_SUCCESS, "MsiRecordSetInteger failed\n");
180     r = MsiRecordSetString(hrec, 2, "Adam");
181     ok(r == ERROR_SUCCESS, "MsiRecordSetString failed\n");
182     r = MsiRecordSetString(hrec, 3, "96905305");
183     ok(r == ERROR_SUCCESS, "MsiRecordSetString failed\n");
184
185     /* insert another value, using a record and wildcards */
186     query = "INSERT INTO `phone` ( `id`, `name`, `number` )"
187         "VALUES(?, ?, ?)";
188     r = MsiDatabaseOpenView(hdb, query, &hview);
189     ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
190
191     if (r == ERROR_SUCCESS)
192     {
193         r = MsiViewExecute(hview, hrec);
194         ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
195         r = MsiViewClose(hview);
196         ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
197         r = MsiCloseHandle(hview);
198         ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
199     }
200     r = MsiCloseHandle(hrec);
201     ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
202
203     r = MsiViewFetch(0, NULL);
204     ok(r == ERROR_INVALID_PARAMETER, "MsiViewFetch failed\n");
205
206     r = MsiDatabaseCommit(hdb);
207     ok(r == ERROR_SUCCESS, "MsiDatabaseCommit failed\n");
208
209     r = MsiCloseHandle(hdb);
210     ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
211
212     r = DeleteFile(msifile);
213     ok(r == TRUE, "file didn't exist after commit\n");
214 }
215
216 typedef UINT (WINAPI *fnMsiDecomposeDescriptorA)(LPCSTR, LPCSTR, LPSTR, LPSTR, DWORD *);
217 static fnMsiDecomposeDescriptorA pMsiDecomposeDescriptorA;
218
219 static void test_msidecomposedesc(void)
220 {
221     char prod[MAX_FEATURE_CHARS+1], comp[MAX_FEATURE_CHARS+1], feature[MAX_FEATURE_CHARS+1];
222     const char *desc;
223     UINT r;
224     DWORD len;
225     HMODULE hmod;
226
227     hmod = GetModuleHandle("msi.dll");
228     if (!hmod)
229         return;
230     pMsiDecomposeDescriptorA = (fnMsiDecomposeDescriptorA)
231         GetProcAddress(hmod, "MsiDecomposeDescriptorA");
232     if (!pMsiDecomposeDescriptorA)
233         return;
234
235     /* test a valid feature descriptor */
236     desc = "']gAVn-}f(ZXfeAR6.jiFollowTheWhiteRabbit>3w2x^IGfe?CxI5heAvk.";
237     len = 0;
238     r = pMsiDecomposeDescriptorA(desc, prod, feature, comp, &len);
239     ok(r == ERROR_SUCCESS, "returned an error\n");
240     ok(len == strlen(desc), "length was wrong\n");
241     ok(strcmp(prod,"{90110409-6000-11D3-8CFE-0150048383C9}")==0, "product wrong\n");
242     ok(strcmp(feature,"FollowTheWhiteRabbit")==0, "feature wrong\n");
243     ok(strcmp(comp,"{A7CD68DB-EF74-49C8-FBB2-A7C463B2AC24}")==0,"component wrong\n");
244
245     /* test an invalid feature descriptor with too many characters */
246     desc = "']gAVn-}f(ZXfeAR6.ji"
247            "ThisWillFailIfTheresMoreThanAGuidsChars>"
248            "3w2x^IGfe?CxI5heAvk.";
249     len = 0;
250     r = pMsiDecomposeDescriptorA(desc, prod, feature, comp, &len);
251     ok(r == ERROR_INVALID_PARAMETER, "returned wrong error\n");
252
253     /*
254      * Test a valid feature descriptor with the
255      * maximum number of characters and some trailing characters.
256      */
257     desc = "']gAVn-}f(ZXfeAR6.ji"
258            "ThisWillWorkIfTheresLTEThanAGuidsChars>"
259            "3w2x^IGfe?CxI5heAvk."
260            "extra";
261     len = 0;
262     r = pMsiDecomposeDescriptorA(desc, prod, feature, comp, &len);
263     ok(r == ERROR_SUCCESS, "returned wrong error\n");
264     ok(len == (strlen(desc) - strlen("extra")), "length wrong\n");
265
266     len = 0;
267     r = pMsiDecomposeDescriptorA(desc, prod, feature, NULL, &len);
268     ok(r == ERROR_SUCCESS, "returned wrong error\n");
269     ok(len == (strlen(desc) - strlen("extra")), "length wrong\n");
270
271     len = 0;
272     r = pMsiDecomposeDescriptorA(desc, prod, NULL, NULL, &len);
273     ok(r == ERROR_SUCCESS, "returned wrong error\n");
274     ok(len == (strlen(desc) - strlen("extra")), "length wrong\n");
275
276     len = 0;
277     r = pMsiDecomposeDescriptorA(desc, NULL, NULL, NULL, &len);
278     ok(r == ERROR_SUCCESS, "returned wrong error\n");
279     ok(len == (strlen(desc) - strlen("extra")), "length wrong\n");
280
281     len = 0;
282     r = pMsiDecomposeDescriptorA(NULL, NULL, NULL, NULL, &len);
283     ok(r == ERROR_INVALID_PARAMETER, "returned wrong error\n");
284     ok(len == 0, "length wrong\n");
285 }
286
287 static UINT try_query_param( MSIHANDLE hdb, LPCSTR szQuery, MSIHANDLE hrec )
288 {
289     MSIHANDLE htab = 0;
290     UINT res;
291
292     res = MsiDatabaseOpenView( hdb, szQuery, &htab );
293     if(res == ERROR_SUCCESS )
294     {
295         UINT r;
296
297         r = MsiViewExecute( htab, hrec );
298         if(r != ERROR_SUCCESS )
299             res = r;
300
301         r = MsiViewClose( htab );
302         if(r != ERROR_SUCCESS )
303             res = r;
304
305         r = MsiCloseHandle( htab );
306         if(r != ERROR_SUCCESS )
307             res = r;
308     }
309     return res;
310 }
311
312 static UINT try_query( MSIHANDLE hdb, LPCSTR szQuery )
313 {
314     return try_query_param( hdb, szQuery, 0 );
315 }
316
317 static UINT try_insert_query( MSIHANDLE hdb, LPCSTR szQuery )
318 {
319     MSIHANDLE hrec = 0;
320     UINT r;
321
322     hrec = MsiCreateRecord( 1 );
323     MsiRecordSetString( hrec, 1, "Hello");
324
325     r = try_query_param( hdb, szQuery, hrec );
326
327     MsiCloseHandle( hrec );
328     return r;
329 }
330
331 static void test_msibadqueries(void)
332 {
333     MSIHANDLE hdb = 0;
334     UINT r;
335
336     DeleteFile(msifile);
337
338     /* just MsiOpenDatabase should not create a file */
339     r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
340     ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
341
342     r = MsiDatabaseCommit( hdb );
343     ok(r == ERROR_SUCCESS , "Failed to commit database\n");
344
345     r = MsiCloseHandle( hdb );
346     ok(r == ERROR_SUCCESS , "Failed to close database\n");
347
348     /* open it readonly */
349     r = MsiOpenDatabase(msifile, MSIDBOPEN_READONLY, &hdb );
350     ok(r == ERROR_SUCCESS , "Failed to open database r/o\n");
351
352     /* add a table to it */
353     r = try_query( hdb, "select * from _Tables");
354     ok(r == ERROR_SUCCESS , "query 1 failed\n");
355
356     r = MsiCloseHandle( hdb );
357     ok(r == ERROR_SUCCESS , "Failed to close database r/o\n");
358
359     /* open it read/write */
360     r = MsiOpenDatabase(msifile, MSIDBOPEN_TRANSACT, &hdb );
361     ok(r == ERROR_SUCCESS , "Failed to open database r/w\n");
362
363     /* a bunch of test queries that fail with the native MSI */
364
365     r = try_query( hdb, "CREATE TABLE");
366     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2a return code\n");
367
368     r = try_query( hdb, "CREATE TABLE `a`");
369     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2b return code\n");
370
371     r = try_query( hdb, "CREATE TABLE `a` ()");
372     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2c return code\n");
373
374     r = try_query( hdb, "CREATE TABLE `a` (`b`)");
375     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2d return code\n");
376
377     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) )");
378     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2e return code\n");
379
380     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL)");
381     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2f return code\n");
382
383     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY)");
384     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2g return code\n");
385
386     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY)");
387     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2h return code\n");
388
389     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY)");
390     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2i return code\n");
391
392     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY 'b')");
393     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2j return code\n");
394
395     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY `b')");
396     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2k return code\n");
397
398     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY `b')");
399     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2l return code\n");
400
401     r = try_query( hdb, "CREATE TABLE `a` (`b` CHA(72) NOT NULL PRIMARY KEY `b`)");
402     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2m return code\n");
403
404     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(-1) NOT NULL PRIMARY KEY `b`)");
405     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2n return code\n");
406
407     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(720) NOT NULL PRIMARY KEY `b`)");
408     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2o return code\n");
409
410     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL KEY `b`)");
411     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2p return code\n");
412
413     r = try_query( hdb, "CREATE TABLE `a` (`` CHAR(72) NOT NULL PRIMARY KEY `b`)");
414     ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2p return code\n");
415
416     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY `b`)");
417     ok(r == ERROR_SUCCESS , "valid query 2z failed\n");
418
419     r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY `b`)");
420     ok(r == ERROR_BAD_QUERY_SYNTAX , "created same table again\n");
421
422     r = try_query( hdb, "CREATE TABLE `aa` (`b` CHAR(72) NOT NULL, `c` "
423           "CHAR(72), `d` CHAR(255) NOT NULL LOCALIZABLE PRIMARY KEY `b`)");
424     ok(r == ERROR_SUCCESS , "query 4 failed\n");
425
426     r = MsiDatabaseCommit( hdb );
427     ok(r == ERROR_SUCCESS , "Failed to commit database after write\n");
428
429     r = try_query( hdb, "CREATE TABLE `blah` (`foo` CHAR(72) NOT NULL "
430                           "PRIMARY KEY `foo`)");
431     ok(r == ERROR_SUCCESS , "query 4 failed\n");
432
433     r = try_insert_query( hdb, "insert into a  ( `b` ) VALUES ( ? )");
434     ok(r == ERROR_SUCCESS , "failed to insert record in db\n");
435
436     r = MsiDatabaseCommit( hdb );
437     ok(r == ERROR_SUCCESS , "Failed to commit database after write\n");
438
439     r = try_query( hdb, "CREATE TABLE `boo` (`foo` CHAR(72) NOT NULL "
440                           "PRIMARY KEY `ba`)");
441     ok(r != ERROR_SUCCESS , "query 5 succeeded\n");
442
443     r = try_query( hdb,"CREATE TABLE `bee` (`foo` CHAR(72) NOT NULL )");
444     ok(r != ERROR_SUCCESS , "query 6 succeeded\n");
445
446     r = try_query( hdb, "CREATE TABLE `temp` (`t` CHAR(72) NOT NULL "
447                           "PRIMARY KEY `t`)");
448     ok(r == ERROR_SUCCESS , "query 7 failed\n");
449
450     r = try_query( hdb, "CREATE TABLE `c` (`b` CHAR NOT NULL PRIMARY KEY `b`)");
451     ok(r == ERROR_SUCCESS , "query 8 failed\n");
452
453     r = MsiCloseHandle( hdb );
454     ok(r == ERROR_SUCCESS , "Failed to close database transact\n");
455
456     r = DeleteFile( msifile );
457     ok(r == TRUE, "file didn't exist after commit\n");
458 }
459
460 static UINT run_query( MSIHANDLE hdb, const char *query )
461 {
462     MSIHANDLE hview = 0;
463     UINT r;
464
465     r = MsiDatabaseOpenView(hdb, query, &hview);
466     if( r != ERROR_SUCCESS )
467         return r;
468
469     r = MsiViewExecute(hview, 0);
470     if( r == ERROR_SUCCESS )
471         r = MsiViewClose(hview);
472     MsiCloseHandle(hview);
473     return r;
474 }
475
476 static void test_viewmodify(void)
477 {
478     MSIHANDLE hdb = 0, hview = 0, hrec = 0;
479     UINT r;
480     const char *query;
481     char buffer[0x100];
482     DWORD sz;
483
484     DeleteFile(msifile);
485
486     /* just MsiOpenDatabase should not create a file */
487     r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
488     ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
489
490     query = "CREATE TABLE `phone` ( "
491             "`id` INT, `name` CHAR(32), `number` CHAR(32) "
492             "PRIMARY KEY `id`)";
493     r = run_query( hdb, query );
494     ok(r == ERROR_SUCCESS, "query failed\n");
495
496     /* check what the error function reports without doing anything */
497     sz = 0;
498     /* passing NULL as the 3rd param make function to crash on older platforms */
499     r = MsiViewGetError( 0, NULL, &sz );
500     ok(r == MSIDBERROR_INVALIDARG, "MsiViewGetError return\n");
501
502     /* open a view */
503     query = "SELECT * FROM `phone`";
504     r = MsiDatabaseOpenView(hdb, query, &hview);
505     ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
506
507     /* see what happens with a good hview and bad args */
508     r = MsiViewGetError( hview, NULL, NULL );
509     ok(r == MSIDBERROR_INVALIDARG || r == MSIDBERROR_NOERROR,
510        "MsiViewGetError returns %u (expected -3)\n", r);
511     r = MsiViewGetError( hview, buffer, NULL );
512     ok(r == MSIDBERROR_INVALIDARG, "MsiViewGetError return\n");
513
514     /* see what happens with a zero length buffer */
515     sz = 0;
516     buffer[0] = 'x';
517     r = MsiViewGetError( hview, buffer, &sz );
518     ok(r == MSIDBERROR_MOREDATA, "MsiViewGetError return\n");
519     ok(buffer[0] == 'x', "buffer cleared\n");
520     ok(sz == 0, "size not zero\n");
521
522     /* ok this one is strange */
523     sz = 0;
524     r = MsiViewGetError( hview, NULL, &sz );
525     ok(r == MSIDBERROR_NOERROR, "MsiViewGetError return\n");
526     ok(sz == 0, "size not zero\n");
527
528     /* see if it really has an error */
529     sz = sizeof buffer;
530     buffer[0] = 'x';
531     r = MsiViewGetError( hview, buffer, &sz );
532     ok(r == MSIDBERROR_NOERROR, "MsiViewGetError return\n");
533     ok(buffer[0] == 0, "buffer not cleared\n");
534     ok(sz == 0, "size not zero\n");
535
536     r = MsiViewExecute(hview, 0);
537     ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
538
539     /* try some invalid records */
540     r = MsiViewModify(hview, MSIMODIFY_INSERT, 0 );
541     ok(r == ERROR_INVALID_HANDLE, "MsiViewModify failed\n");
542     r = MsiViewModify(hview, -1, 0 );
543     ok(r == ERROR_INVALID_HANDLE, "MsiViewModify failed\n");
544
545     /* try an small record */
546     hrec = MsiCreateRecord(1);
547     r = MsiViewModify(hview, -1, hrec );
548     ok(r == ERROR_INVALID_DATA, "MsiViewModify failed\n");
549
550     r = MsiCloseHandle(hrec);
551     ok(r == ERROR_SUCCESS, "failed to close record\n");
552
553     /* insert a valid record */
554     hrec = MsiCreateRecord(3);
555
556     r = MsiRecordSetInteger(hrec, 2, 1);
557     ok(r == ERROR_SUCCESS, "failed to set integer\n");
558     r = MsiRecordSetString(hrec, 2, "bob");
559     ok(r == ERROR_SUCCESS, "failed to set integer\n");
560     r = MsiRecordSetString(hrec, 3, "7654321");
561     ok(r == ERROR_SUCCESS, "failed to set integer\n");
562
563     r = MsiViewExecute(hview, 0);
564     ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
565     r = MsiViewModify(hview, MSIMODIFY_INSERT_TEMPORARY, hrec );
566     ok(r == ERROR_SUCCESS, "MsiViewModify failed\n");
567
568     /* insert the same thing again */
569     r = MsiViewExecute(hview, 0);
570     ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
571
572     /* should fail ... */
573     todo_wine {
574     r = MsiViewModify(hview, MSIMODIFY_INSERT_TEMPORARY, hrec );
575     ok(r == ERROR_FUNCTION_FAILED, "MsiViewModify failed\n");
576     }
577
578     r = MsiCloseHandle(hrec);
579     ok(r == ERROR_SUCCESS, "failed to close record\n");
580
581     r = MsiViewClose(hview);
582     ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
583     r = MsiCloseHandle(hview);
584     ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
585
586     r = MsiCloseHandle( hdb );
587     ok(r == ERROR_SUCCESS, "MsiOpenDatabase close failed\n");
588 }
589
590 static MSIHANDLE create_db(void)
591 {
592     MSIHANDLE hdb = 0;
593     UINT res;
594
595     DeleteFile(msifile);
596
597     /* create an empty database */
598     res = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb );
599     ok( res == ERROR_SUCCESS , "Failed to create database\n" );
600     if( res != ERROR_SUCCESS )
601         return hdb;
602
603     res = MsiDatabaseCommit( hdb );
604     ok( res == ERROR_SUCCESS , "Failed to commit database\n" );
605
606     return hdb;
607 }
608
609 static void test_getcolinfo(void)
610 {
611     MSIHANDLE hdb, hview = 0, rec = 0;
612     UINT r;
613     DWORD sz;
614     char buffer[0x20];
615
616     /* create an empty db */
617     hdb = create_db();
618     ok( hdb, "failed to create db\n");
619
620     /* tables should be present */
621     r = MsiDatabaseOpenView(hdb, "select * from _Tables", &hview);
622     ok( r == ERROR_SUCCESS, "failed to open query\n");
623
624     r = MsiViewExecute(hview, 0);
625     ok( r == ERROR_SUCCESS, "failed to execute query\n");
626
627     /* check that NAMES works */
628     rec = 0;
629     r = MsiViewGetColumnInfo( hview, MSICOLINFO_NAMES, &rec );
630     ok( r == ERROR_SUCCESS, "failed to get names\n");
631     sz = sizeof buffer;
632     r = MsiRecordGetString(rec, 1, buffer, &sz );
633     ok( r == ERROR_SUCCESS, "failed to get string\n");
634     ok( !strcmp(buffer,"Name"), "_Tables has wrong column name\n");
635     r = MsiCloseHandle( rec );
636     ok( r == ERROR_SUCCESS, "failed to close record handle\n");
637
638     /* check that TYPES works */
639     rec = 0;
640     r = MsiViewGetColumnInfo( hview, MSICOLINFO_TYPES, &rec );
641     ok( r == ERROR_SUCCESS, "failed to get names\n");
642     sz = sizeof buffer;
643     r = MsiRecordGetString(rec, 1, buffer, &sz );
644     ok( r == ERROR_SUCCESS, "failed to get string\n");
645     ok( !strcmp(buffer,"s64"), "_Tables has wrong column type\n");
646     r = MsiCloseHandle( rec );
647     ok( r == ERROR_SUCCESS, "failed to close record handle\n");
648
649     /* check that invalid values fail */
650     rec = 0;
651     r = MsiViewGetColumnInfo( hview, 100, &rec );
652     ok( r == ERROR_INVALID_PARAMETER, "wrong error code\n");
653     ok( rec == 0, "returned a record\n");
654
655     r = MsiViewGetColumnInfo( hview, MSICOLINFO_TYPES, NULL );
656     ok( r == ERROR_INVALID_PARAMETER, "wrong error code\n");
657
658     r = MsiViewGetColumnInfo( 0, MSICOLINFO_TYPES, &rec );
659     ok( r == ERROR_INVALID_HANDLE, "wrong error code\n");
660
661     r = MsiViewClose(hview);
662     ok( r == ERROR_SUCCESS, "failed to close view\n");
663     r = MsiCloseHandle(hview);
664     ok( r == ERROR_SUCCESS, "failed to close view handle\n");
665     r = MsiCloseHandle(hdb);
666     ok( r == ERROR_SUCCESS, "failed to close database\n");
667 }
668
669 static MSIHANDLE get_column_info(MSIHANDLE hdb, const char *query, MSICOLINFO type)
670 {
671     MSIHANDLE hview = 0, rec = 0;
672     UINT r;
673
674     r = MsiDatabaseOpenView(hdb, query, &hview);
675     if( r != ERROR_SUCCESS )
676         return r;
677
678     r = MsiViewExecute(hview, 0);
679     if( r == ERROR_SUCCESS )
680     {
681         MsiViewGetColumnInfo( hview, type, &rec );
682         MsiViewClose(hview);
683     }
684     MsiCloseHandle(hview);
685     return rec;
686 }
687
688 static UINT get_columns_table_type(MSIHANDLE hdb, const char *table, UINT field)
689 {
690     MSIHANDLE hview = 0, rec = 0;
691     UINT r, type = 0;
692     char query[0x100];
693
694     sprintf(query, "select * from `_Columns` where  `Table` = '%s'", table );
695
696     r = MsiDatabaseOpenView(hdb, query, &hview);
697     if( r != ERROR_SUCCESS )
698         return r;
699
700     r = MsiViewExecute(hview, 0);
701     if( r == ERROR_SUCCESS )
702     {
703         while (1)
704         {
705             r = MsiViewFetch( hview, &rec );
706             if( r != ERROR_SUCCESS)
707                 break;
708             r = MsiRecordGetInteger( rec, 2 );
709             if (r == field)
710                 type = MsiRecordGetInteger( rec, 4 );
711             MsiCloseHandle( rec );
712         }
713
714         MsiViewClose(hview);
715     }
716     MsiCloseHandle(hview);
717     return type;
718 }
719
720 static BOOL check_record( MSIHANDLE rec, UINT field, LPSTR val )
721 {
722     CHAR buffer[0x20];
723     UINT r;
724     DWORD sz;
725
726     sz = sizeof buffer;
727     r = MsiRecordGetString( rec, field, buffer, &sz );
728     return (r == ERROR_SUCCESS ) && !strcmp(val, buffer);
729 }
730
731 static void test_viewgetcolumninfo(void)
732 {
733     MSIHANDLE hdb = 0, rec;
734     UINT r;
735
736     hdb = create_db();
737     ok( hdb, "failed to create db\n");
738
739     r = run_query( hdb,
740             "CREATE TABLE `Properties` "
741             "( `Property` CHAR(255), `Value` CHAR(1)  PRIMARY KEY `Property`)" );
742     ok( r == ERROR_SUCCESS , "Failed to create table\n" );
743
744     /* check the column types */
745     rec = get_column_info( hdb, "select * from `Properties`", MSICOLINFO_TYPES );
746     ok( rec, "failed to get column info record\n" );
747
748     ok( check_record( rec, 1, "S255"), "wrong record type\n");
749     ok( check_record( rec, 2, "S1"), "wrong record type\n");
750
751     MsiCloseHandle( rec );
752
753     /* check the type in _Columns */
754     ok( 0x3dff == get_columns_table_type(hdb, "Properties", 1 ), "_columns table wrong\n");
755     ok( 0x1d01 == get_columns_table_type(hdb, "Properties", 2 ), "_columns table wrong\n");
756
757     /* now try the names */
758     rec = get_column_info( hdb, "select * from `Properties`", MSICOLINFO_NAMES );
759     ok( rec, "failed to get column info record\n" );
760
761     ok( check_record( rec, 1, "Property"), "wrong record type\n");
762     ok( check_record( rec, 2, "Value"), "wrong record type\n");
763
764     MsiCloseHandle( rec );
765
766     r = run_query( hdb,
767             "CREATE TABLE `Binary` "
768             "( `Name` CHAR(255), `Data` OBJECT  PRIMARY KEY `Name`)" );
769     ok( r == ERROR_SUCCESS , "Failed to create table\n" );
770
771     /* check the column types */
772     rec = get_column_info( hdb, "select * from `Binary`", MSICOLINFO_TYPES );
773     ok( rec, "failed to get column info record\n" );
774
775     ok( check_record( rec, 1, "S255"), "wrong record type\n");
776     ok( check_record( rec, 2, "V0"), "wrong record type\n");
777
778     MsiCloseHandle( rec );
779
780     /* check the type in _Columns */
781     ok( 0x3dff == get_columns_table_type(hdb, "Binary", 1 ), "_columns table wrong\n");
782     ok( 0x1900 == get_columns_table_type(hdb, "Binary", 2 ), "_columns table wrong\n");
783
784     /* now try the names */
785     rec = get_column_info( hdb, "select * from `Binary`", MSICOLINFO_NAMES );
786     ok( rec, "failed to get column info record\n" );
787
788     ok( check_record( rec, 1, "Name"), "wrong record type\n");
789     ok( check_record( rec, 2, "Data"), "wrong record type\n");
790     MsiCloseHandle( rec );
791
792     r = run_query( hdb,
793             "CREATE TABLE `UIText` "
794             "( `Key` CHAR(72) NOT NULL, `Text` CHAR(255) LOCALIZABLE PRIMARY KEY `Key`)" );
795     ok( r == ERROR_SUCCESS , "Failed to create table\n" );
796
797     ok( 0x2d48 == get_columns_table_type(hdb, "UIText", 1 ), "_columns table wrong\n");
798     ok( 0x1fff == get_columns_table_type(hdb, "UIText", 2 ), "_columns table wrong\n");
799
800     rec = get_column_info( hdb, "select * from `UIText`", MSICOLINFO_NAMES );
801     ok( rec, "failed to get column info record\n" );
802     ok( check_record( rec, 1, "Key"), "wrong record type\n");
803     ok( check_record( rec, 2, "Text"), "wrong record type\n");
804     MsiCloseHandle( rec );
805
806     rec = get_column_info( hdb, "select * from `UIText`", MSICOLINFO_TYPES );
807     ok( rec, "failed to get column info record\n" );
808     ok( check_record( rec, 1, "s72"), "wrong record type\n");
809     ok( check_record( rec, 2, "L255"), "wrong record type\n");
810     MsiCloseHandle( rec );
811
812     MsiCloseHandle( hdb );
813 }
814
815 static void test_msiexport(void)
816 {
817     MSIHANDLE hdb = 0, hview = 0;
818     UINT r;
819     char *query;
820     char path[MAX_PATH];
821     const char file[] = "phone.txt";
822     HANDLE handle;
823     char buffer[0x100];
824     DWORD length;
825     const char expected[] =
826         "id\tname\tnumber\r\n"
827         "I2\tS32\tS32\r\n"
828         "phone\tid\r\n"
829         "1\tAbe\t8675309\r\n";
830
831     DeleteFile(msifile);
832
833     /* just MsiOpenDatabase should not create a file */
834     r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
835     ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
836
837     /* create a table */
838     query = "CREATE TABLE `phone` ( "
839             "`id` INT, `name` CHAR(32), `number` CHAR(32) "
840             "PRIMARY KEY `id`)";
841     r = MsiDatabaseOpenView(hdb, query, &hview);
842     ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
843     r = MsiViewExecute(hview, 0);
844     ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
845     r = MsiViewClose(hview);
846     ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
847     r = MsiCloseHandle(hview);
848     ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
849
850     /* insert a value into it */
851     query = "INSERT INTO `phone` ( `id`, `name`, `number` )"
852         "VALUES('1', 'Abe', '8675309')";
853     r = MsiDatabaseOpenView(hdb, query, &hview);
854     ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
855     r = MsiViewExecute(hview, 0);
856     ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
857     r = MsiViewClose(hview);
858     ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
859     r = MsiCloseHandle(hview);
860     ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
861
862     GetCurrentDirectory(MAX_PATH, path);
863
864     todo_wine {
865     r = MsiDatabaseExport(hdb, "phone", path, file);
866     ok(r == ERROR_SUCCESS, "MsiDatabaseExport failed\n");
867
868     MsiCloseHandle(hdb);
869
870     lstrcat(path, "\\");
871     lstrcat(path, file);
872
873     /* check the data that was written */
874     length = 0;
875     memset(buffer, 0, sizeof buffer);
876     handle = CreateFile(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
877     if (handle != INVALID_HANDLE_VALUE)
878     {
879         ReadFile(handle, buffer, sizeof buffer, &length, NULL);
880         CloseHandle(handle);
881         DeleteFile(path);
882     }
883     else
884         ok(0, "failed to open file %s\n", path);
885
886     ok( length == strlen(expected), "length of data wrong\n");
887     ok( !lstrcmp(buffer, expected), "data doesn't match\n");
888     }
889     DeleteFile(msifile);
890 }
891
892 static void test_longstrings(void)
893 {
894     const char insert_query[] = 
895         "INSERT INTO `strings` ( `id`, `val` ) VALUES('1', 'Z')";
896     char *str;
897     MSIHANDLE hdb = 0, hview = 0, hrec = 0;
898     DWORD len;
899     UINT r;
900     const DWORD STRING_LENGTH = 0x10005;
901
902     DeleteFile(msifile);
903     /* just MsiOpenDatabase should not create a file */
904     r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
905     ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
906
907     /* create a table */
908     r = try_query( hdb, 
909         "CREATE TABLE `strings` ( `id` INT, `val` CHAR(0) PRIMARY KEY `id`)");
910     ok(r == ERROR_SUCCESS, "query failed\n");
911
912     /* try a insert a very long string */
913     str = HeapAlloc(GetProcessHeap(), 0, STRING_LENGTH+sizeof insert_query);
914     len = strchr(insert_query, 'Z') - insert_query;
915     strcpy(str, insert_query);
916     memset(str+len, 'Z', STRING_LENGTH);
917     strcpy(str+len+STRING_LENGTH, insert_query+len+1);
918     r = try_query( hdb, str );
919     ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
920
921     HeapFree(GetProcessHeap(), 0, str);
922
923     MsiDatabaseCommit(hdb);
924     ok(r == ERROR_SUCCESS, "MsiDatabaseCommit failed\n");
925     MsiCloseHandle(hdb);
926
927     r = MsiOpenDatabase(msifile, MSIDBOPEN_READONLY, &hdb);
928     ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
929
930     r = MsiDatabaseOpenView(hdb, "select * from `strings` where `id` = 1", &hview);
931     ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
932
933     r = MsiViewExecute(hview, 0);
934     ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
935
936     r = MsiViewFetch(hview, &hrec);
937     ok(r == ERROR_SUCCESS, "MsiViewFetch failed\n");
938
939     MsiCloseHandle(hview);
940
941     r = MsiRecordGetString(hrec, 2, NULL, &len);
942     ok(r == ERROR_SUCCESS, "MsiViewFetch failed\n");
943     todo_wine {
944     ok(len == STRING_LENGTH, "string length wrong\n");
945     }
946
947     MsiCloseHandle(hrec);
948     MsiCloseHandle(hdb);
949     DeleteFile(msifile);
950 }
951  
952 static void test_streamtable(void)
953 {
954     MSIHANDLE hdb = 0, rec;
955     UINT r;
956
957     hdb = create_db();
958     ok( hdb, "failed to create db\n");
959
960     r = run_query( hdb,
961             "CREATE TABLE `Properties` "
962             "( `Property` CHAR(255), `Value` CHAR(1)  PRIMARY KEY `Property`)" );
963     ok( r == ERROR_SUCCESS , "Failed to create table\n" );
964
965     /* check the column types */
966     rec = get_column_info( hdb, "select * from `_Streams`", MSICOLINFO_TYPES );
967     ok( rec, "failed to get column info record\n" );
968
969     todo_wine {
970     ok( check_record( rec, 1, "s62"), "wrong record type\n");
971     ok( check_record( rec, 2, "V0"), "wrong record type\n");
972     }
973
974     MsiCloseHandle( rec );
975
976     /* now try the names */
977     rec = get_column_info( hdb, "select * from `_Streams`", MSICOLINFO_NAMES );
978     ok( rec, "failed to get column info record\n" );
979
980     todo_wine {
981     ok( check_record( rec, 1, "Name"), "wrong record type\n");
982     ok( check_record( rec, 2, "Data"), "wrong record type\n");
983     }
984
985     MsiCloseHandle( rec );
986     MsiCloseHandle( hdb );
987     DeleteFile(msifile);
988 }
989
990 START_TEST(db)
991 {
992     test_msidatabase();
993     test_msiinsert();
994     test_msidecomposedesc();
995     test_msibadqueries();
996     test_viewmodify();
997     test_viewgetcolumninfo();
998     test_getcolinfo();
999     test_msiexport();
1000     test_longstrings();
1001     test_streamtable();
1002 }