xref: /reactos/dll/win32/msi/table.c (revision 5c718f5f)
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include <stdarg.h>
22 #include <assert.h>
23 
24 #define COBJMACROS
25 
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winerror.h"
29 #include "msi.h"
30 #include "msiquery.h"
31 #include "objbase.h"
32 #include "objidl.h"
33 #include "winnls.h"
34 #include "msipriv.h"
35 #include "query.h"
36 
37 #include "wine/debug.h"
38 
39 WINE_DEFAULT_DEBUG_CHANNEL(msidb);
40 
41 #define MSITABLE_HASH_TABLE_SIZE 37
42 
43 typedef struct tagMSICOLUMNHASHENTRY
44 {
45     struct tagMSICOLUMNHASHENTRY *next;
46     UINT value;
47     UINT row;
48 } MSICOLUMNHASHENTRY;
49 
50 typedef struct tagMSICOLUMNINFO
51 {
52     LPCWSTR tablename;
53     UINT    number;
54     LPCWSTR colname;
55     UINT    type;
56     UINT    offset;
57     INT     ref_count;
58     BOOL    temporary;
59     MSICOLUMNHASHENTRY **hash_table;
60 } MSICOLUMNINFO;
61 
62 struct tagMSITABLE
63 {
64     BYTE **data;
65     BOOL *data_persistent;
66     UINT row_count;
67     struct list entry;
68     MSICOLUMNINFO *colinfo;
69     UINT col_count;
70     MSICONDITION persistent;
71     INT ref_count;
72     WCHAR name[1];
73 };
74 
75 /* information for default tables */
76 static const WCHAR szTables[]  = {'_','T','a','b','l','e','s',0};
77 static const WCHAR szTable[]   = {'T','a','b','l','e',0};
78 static const WCHAR szColumns[] = {'_','C','o','l','u','m','n','s',0};
79 static const WCHAR szNumber[]  = {'N','u','m','b','e','r',0};
80 static const WCHAR szType[]    = {'T','y','p','e',0};
81 
82 static const MSICOLUMNINFO _Columns_cols[4] = {
83     { szColumns, 1, szTable,  MSITYPE_VALID | MSITYPE_STRING | MSITYPE_KEY | 64, 0, 0, 0, NULL },
84     { szColumns, 2, szNumber, MSITYPE_VALID | MSITYPE_KEY | 2,     2, 0, 0, NULL },
85     { szColumns, 3, szName,   MSITYPE_VALID | MSITYPE_STRING | 64, 4, 0, 0, NULL },
86     { szColumns, 4, szType,   MSITYPE_VALID | 2,                   6, 0, 0, NULL },
87 };
88 
89 static const MSICOLUMNINFO _Tables_cols[1] = {
90     { szTables,  1, szName,   MSITYPE_VALID | MSITYPE_STRING | MSITYPE_KEY | 64, 0, 0, 0, NULL },
91 };
92 
93 #define MAX_STREAM_NAME 0x1f
94 
95 static inline UINT bytes_per_column( MSIDATABASE *db, const MSICOLUMNINFO *col, UINT bytes_per_strref )
96 {
97     if( MSITYPE_IS_BINARY(col->type) )
98         return 2;
99 
100     if( col->type & MSITYPE_STRING )
101         return bytes_per_strref;
102 
103     if( (col->type & 0xff) <= 2)
104         return 2;
105 
106     if( (col->type & 0xff) != 4 )
107         ERR("Invalid column size %u\n", col->type & 0xff);
108 
109     return 4;
110 }
111 
112 static int utf2mime(int x)
113 {
114     if( (x>='0') && (x<='9') )
115         return x-'0';
116     if( (x>='A') && (x<='Z') )
117         return x-'A'+10;
118     if( (x>='a') && (x<='z') )
119         return x-'a'+10+26;
120     if( x=='.' )
121         return 10+26+26;
122     if( x=='_' )
123         return 10+26+26+1;
124     return -1;
125 }
126 
127 LPWSTR encode_streamname(BOOL bTable, LPCWSTR in)
128 {
129     DWORD count = MAX_STREAM_NAME;
130     DWORD ch, next;
131     LPWSTR out, p;
132 
133     if( !bTable )
134         count = lstrlenW( in )+2;
135     if (!(out = msi_alloc( count*sizeof(WCHAR) ))) return NULL;
136     p = out;
137 
138     if( bTable )
139     {
140          *p++ = 0x4840;
141          count --;
142     }
143     while( count -- )
144     {
145         ch = *in++;
146         if( !ch )
147         {
148             *p = ch;
149             return out;
150         }
151         if( ( ch < 0x80 ) && ( utf2mime(ch) >= 0 ) )
152         {
153             ch = utf2mime(ch) + 0x4800;
154             next = *in;
155             if( next && (next<0x80) )
156             {
157                 next = utf2mime(next);
158                 if( next != -1 )
159                 {
160                      next += 0x3ffffc0;
161                      ch += (next<<6);
162                      in++;
163                 }
164             }
165         }
166         *p++ = ch;
167     }
168     ERR("Failed to encode stream name (%s)\n",debugstr_w(in));
169     msi_free( out );
170     return NULL;
171 }
172 
173 static int mime2utf(int x)
174 {
175     if( x<10 )
176         return x + '0';
177     if( x<(10+26))
178         return x - 10 + 'A';
179     if( x<(10+26+26))
180         return x - 10 - 26 + 'a';
181     if( x == (10+26+26) )
182         return '.';
183     return '_';
184 }
185 
186 BOOL decode_streamname(LPCWSTR in, LPWSTR out)
187 {
188     WCHAR ch;
189     DWORD count = 0;
190 
191     while ( (ch = *in++) )
192     {
193         if( (ch >= 0x3800 ) && (ch < 0x4840 ) )
194         {
195             if( ch >= 0x4800 )
196                 ch = mime2utf(ch-0x4800);
197             else
198             {
199                 ch -= 0x3800;
200                 *out++ = mime2utf(ch&0x3f);
201                 count++;
202                 ch = mime2utf((ch>>6)&0x3f);
203             }
204         }
205         *out++ = ch;
206         count++;
207     }
208     *out = 0;
209     return count;
210 }
211 
212 void enum_stream_names( IStorage *stg )
213 {
214     IEnumSTATSTG *stgenum = NULL;
215     HRESULT r;
216     STATSTG stat;
217     ULONG n, count;
218     WCHAR name[0x40];
219 
220     r = IStorage_EnumElements( stg, 0, NULL, 0, &stgenum );
221     if( FAILED( r ) )
222         return;
223 
224     n = 0;
225     while( 1 )
226     {
227         count = 0;
228         r = IEnumSTATSTG_Next( stgenum, 1, &stat, &count );
229         if( FAILED( r ) || !count )
230             break;
231         decode_streamname( stat.pwcsName, name );
232         TRACE("stream %2d -> %s %s\n", n,
233               debugstr_w(stat.pwcsName), debugstr_w(name) );
234         CoTaskMemFree( stat.pwcsName );
235         n++;
236     }
237 
238     IEnumSTATSTG_Release( stgenum );
239 }
240 
241 UINT read_stream_data( IStorage *stg, LPCWSTR stname, BOOL table,
242                        BYTE **pdata, UINT *psz )
243 {
244     HRESULT r;
245     UINT ret = ERROR_FUNCTION_FAILED;
246     VOID *data;
247     ULONG sz, count;
248     IStream *stm = NULL;
249     STATSTG stat;
250     LPWSTR encname;
251 
252     encname = encode_streamname(table, stname);
253 
254     TRACE("%s -> %s\n",debugstr_w(stname),debugstr_w(encname));
255 
256     r = IStorage_OpenStream(stg, encname, NULL,
257             STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stm);
258     msi_free( encname );
259     if( FAILED( r ) )
260     {
261         WARN("open stream failed r = %08x - empty table?\n", r);
262         return ret;
263     }
264 
265     r = IStream_Stat(stm, &stat, STATFLAG_NONAME );
266     if( FAILED( r ) )
267     {
268         WARN("open stream failed r = %08x!\n", r);
269         goto end;
270     }
271 
272     if( stat.cbSize.QuadPart >> 32 )
273     {
274         WARN("Too big!\n");
275         goto end;
276     }
277 
278     sz = stat.cbSize.QuadPart;
279     data = msi_alloc( sz );
280     if( !data )
281     {
282         WARN("couldn't allocate memory r=%08x!\n", r);
283         ret = ERROR_NOT_ENOUGH_MEMORY;
284         goto end;
285     }
286 
287     r = IStream_Read(stm, data, sz, &count );
288     if( FAILED( r ) || ( count != sz ) )
289     {
290         msi_free( data );
291         WARN("read stream failed r = %08x!\n", r);
292         goto end;
293     }
294 
295     *pdata = data;
296     *psz = sz;
297     ret = ERROR_SUCCESS;
298 
299 end:
300     IStream_Release( stm );
301 
302     return ret;
303 }
304 
305 UINT write_stream_data( IStorage *stg, LPCWSTR stname,
306                         LPCVOID data, UINT sz, BOOL bTable )
307 {
308     HRESULT r;
309     UINT ret = ERROR_FUNCTION_FAILED;
310     ULONG count;
311     IStream *stm = NULL;
312     ULARGE_INTEGER size;
313     LARGE_INTEGER pos;
314     LPWSTR encname;
315 
316     encname = encode_streamname(bTable, stname );
317     r = IStorage_OpenStream( stg, encname, NULL,
318             STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &stm);
319     if( FAILED(r) )
320     {
321         r = IStorage_CreateStream( stg, encname,
322                 STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &stm);
323     }
324     msi_free( encname );
325     if( FAILED( r ) )
326     {
327         WARN("open stream failed r = %08x\n", r);
328         return ret;
329     }
330 
331     size.QuadPart = sz;
332     r = IStream_SetSize( stm, size );
333     if( FAILED( r ) )
334     {
335         WARN("Failed to SetSize\n");
336         goto end;
337     }
338 
339     pos.QuadPart = 0;
340     r = IStream_Seek( stm, pos, STREAM_SEEK_SET, NULL );
341     if( FAILED( r ) )
342     {
343         WARN("Failed to Seek\n");
344         goto end;
345     }
346 
347     if (sz)
348     {
349         r = IStream_Write(stm, data, sz, &count );
350         if( FAILED( r ) || ( count != sz ) )
351         {
352             WARN("Failed to Write\n");
353             goto end;
354         }
355     }
356 
357     ret = ERROR_SUCCESS;
358 
359 end:
360     IStream_Release( stm );
361 
362     return ret;
363 }
364 
365 static void msi_free_colinfo( MSICOLUMNINFO *colinfo, UINT count )
366 {
367     UINT i;
368     for (i = 0; i < count; i++) msi_free( colinfo[i].hash_table );
369 }
370 
371 static void free_table( MSITABLE *table )
372 {
373     UINT i;
374     for( i=0; i<table->row_count; i++ )
375         msi_free( table->data[i] );
376     msi_free( table->data );
377     msi_free( table->data_persistent );
378     msi_free_colinfo( table->colinfo, table->col_count );
379     msi_free( table->colinfo );
380     msi_free( table );
381 }
382 
383 static UINT msi_table_get_row_size( MSIDATABASE *db, const MSICOLUMNINFO *cols, UINT count, UINT bytes_per_strref )
384 {
385     const MSICOLUMNINFO *last_col;
386 
387     if (!count)
388         return 0;
389 
390     if (bytes_per_strref != LONG_STR_BYTES)
391     {
392         UINT i, size = 0;
393         for (i = 0; i < count; i++) size += bytes_per_column( db, &cols[i], bytes_per_strref );
394         return size;
395     }
396     last_col = &cols[count - 1];
397     return last_col->offset + bytes_per_column( db, last_col, bytes_per_strref );
398 }
399 
400 /* add this table to the list of cached tables in the database */
401 static UINT read_table_from_storage( MSIDATABASE *db, MSITABLE *t, IStorage *stg )
402 {
403     BYTE *rawdata = NULL;
404     UINT rawsize = 0, i, j, row_size, row_size_mem;
405 
406     TRACE("%s\n",debugstr_w(t->name));
407 
408     row_size = msi_table_get_row_size( db, t->colinfo, t->col_count, db->bytes_per_strref );
409     row_size_mem = msi_table_get_row_size( db, t->colinfo, t->col_count, LONG_STR_BYTES );
410 
411     /* if we can't read the table, just assume that it's empty */
412     read_stream_data( stg, t->name, TRUE, &rawdata, &rawsize );
413     if( !rawdata )
414         return ERROR_SUCCESS;
415 
416     TRACE("Read %d bytes\n", rawsize );
417 
418     if( rawsize % row_size )
419     {
420         WARN("Table size is invalid %d/%d\n", rawsize, row_size );
421         goto err;
422     }
423 
424     if ((t->row_count = rawsize / row_size))
425     {
426         if (!(t->data = msi_alloc_zero( t->row_count * sizeof(USHORT *) ))) goto err;
427         if (!(t->data_persistent = msi_alloc_zero( t->row_count * sizeof(BOOL) ))) goto err;
428     }
429 
430     /* transpose all the data */
431     TRACE("Transposing data from %d rows\n", t->row_count );
432     for (i = 0; i < t->row_count; i++)
433     {
434         UINT ofs = 0, ofs_mem = 0;
435 
436         t->data[i] = msi_alloc( row_size_mem );
437         if( !t->data[i] )
438             goto err;
439         t->data_persistent[i] = TRUE;
440 
441         for (j = 0; j < t->col_count; j++)
442         {
443             UINT m = bytes_per_column( db, &t->colinfo[j], LONG_STR_BYTES );
444             UINT n = bytes_per_column( db, &t->colinfo[j], db->bytes_per_strref );
445             UINT k;
446 
447             if ( n != 2 && n != 3 && n != 4 )
448             {
449                 ERR("oops - unknown column width %d\n", n);
450                 goto err;
451             }
452             if (t->colinfo[j].type & MSITYPE_STRING && n < m)
453             {
454                 for (k = 0; k < m; k++)
455                 {
456                     if (k < n)
457                         t->data[i][ofs_mem + k] = rawdata[ofs * t->row_count + i * n + k];
458                     else
459                         t->data[i][ofs_mem + k] = 0;
460                 }
461             }
462             else
463             {
464                 for (k = 0; k < n; k++)
465                     t->data[i][ofs_mem + k] = rawdata[ofs * t->row_count + i * n + k];
466             }
467             ofs_mem += m;
468             ofs += n;
469         }
470     }
471 
472     msi_free( rawdata );
473     return ERROR_SUCCESS;
474 err:
475     msi_free( rawdata );
476     return ERROR_FUNCTION_FAILED;
477 }
478 
479 void free_cached_tables( MSIDATABASE *db )
480 {
481     while( !list_empty( &db->tables ) )
482     {
483         MSITABLE *t = LIST_ENTRY( list_head( &db->tables ), MSITABLE, entry );
484 
485         list_remove( &t->entry );
486         free_table( t );
487     }
488 }
489 
490 static MSITABLE *find_cached_table( MSIDATABASE *db, LPCWSTR name )
491 {
492     MSITABLE *t;
493 
494     LIST_FOR_EACH_ENTRY( t, &db->tables, MSITABLE, entry )
495         if( !wcscmp( name, t->name ) )
496             return t;
497 
498     return NULL;
499 }
500 
501 static void table_calc_column_offsets( MSIDATABASE *db, MSICOLUMNINFO *colinfo, DWORD count )
502 {
503     DWORD i;
504 
505     for (i = 0; colinfo && i < count; i++)
506     {
507          assert( i + 1 == colinfo[i].number );
508          if (i) colinfo[i].offset = colinfo[i - 1].offset +
509                                     bytes_per_column( db, &colinfo[i - 1], LONG_STR_BYTES );
510          else colinfo[i].offset = 0;
511 
512          TRACE("column %d is [%s] with type %08x ofs %d\n",
513                colinfo[i].number, debugstr_w(colinfo[i].colname),
514                colinfo[i].type, colinfo[i].offset);
515     }
516 }
517 
518 static UINT get_defaulttablecolumns( MSIDATABASE *db, LPCWSTR name, MSICOLUMNINFO *colinfo, UINT *sz )
519 {
520     const MSICOLUMNINFO *p;
521     DWORD i, n;
522 
523     TRACE("%s\n", debugstr_w(name));
524 
525     if (!wcscmp( name, szTables ))
526     {
527         p = _Tables_cols;
528         n = 1;
529     }
530     else if (!wcscmp( name, szColumns ))
531     {
532         p = _Columns_cols;
533         n = 4;
534     }
535     else return ERROR_FUNCTION_FAILED;
536 
537     for (i = 0; i < n; i++)
538     {
539         if (colinfo && i < *sz) colinfo[i] = p[i];
540         if (colinfo && i >= *sz) break;
541     }
542     table_calc_column_offsets( db, colinfo, n );
543     *sz = n;
544     return ERROR_SUCCESS;
545 }
546 
547 static UINT get_tablecolumns( MSIDATABASE *db, LPCWSTR szTableName, MSICOLUMNINFO *colinfo, UINT *sz );
548 
549 static UINT table_get_column_info( MSIDATABASE *db, LPCWSTR name, MSICOLUMNINFO **pcols, UINT *pcount )
550 {
551     UINT r, column_count = 0;
552     MSICOLUMNINFO *columns;
553 
554     /* get the number of columns in this table */
555     column_count = 0;
556     r = get_tablecolumns( db, name, NULL, &column_count );
557     if (r != ERROR_SUCCESS)
558         return r;
559 
560     *pcount = column_count;
561 
562     /* if there are no columns, there's no table */
563     if (!column_count)
564         return ERROR_INVALID_PARAMETER;
565 
566     TRACE("table %s found\n", debugstr_w(name));
567 
568     columns = msi_alloc( column_count * sizeof(MSICOLUMNINFO) );
569     if (!columns)
570         return ERROR_FUNCTION_FAILED;
571 
572     r = get_tablecolumns( db, name, columns, &column_count );
573     if (r != ERROR_SUCCESS)
574     {
575         msi_free( columns );
576         return ERROR_FUNCTION_FAILED;
577     }
578     *pcols = columns;
579     return r;
580 }
581 
582 static UINT get_table( MSIDATABASE *db, LPCWSTR name, MSITABLE **table_ret )
583 {
584     MSITABLE *table;
585     UINT r;
586 
587     /* first, see if the table is cached */
588     table = find_cached_table( db, name );
589     if (table)
590     {
591         *table_ret = table;
592         return ERROR_SUCCESS;
593     }
594 
595     /* nonexistent tables should be interpreted as empty tables */
596     table = msi_alloc( sizeof(MSITABLE) + lstrlenW( name ) * sizeof(WCHAR) );
597     if (!table)
598         return ERROR_FUNCTION_FAILED;
599 
600     table->row_count = 0;
601     table->data = NULL;
602     table->data_persistent = NULL;
603     table->colinfo = NULL;
604     table->col_count = 0;
605     table->persistent = MSICONDITION_TRUE;
606     lstrcpyW( table->name, name );
607 
608     if (!wcscmp( name, szTables ) || !wcscmp( name, szColumns ))
609         table->persistent = MSICONDITION_NONE;
610 
611     r = table_get_column_info( db, name, &table->colinfo, &table->col_count );
612     if (r != ERROR_SUCCESS)
613     {
614         free_table( table );
615         return r;
616     }
617     r = read_table_from_storage( db, table, db->storage );
618     if (r != ERROR_SUCCESS)
619     {
620         free_table( table );
621         return r;
622     }
623     list_add_head( &db->tables, &table->entry );
624     *table_ret = table;
625     return ERROR_SUCCESS;
626 }
627 
628 static UINT read_table_int( BYTE *const *data, UINT row, UINT col, UINT bytes )
629 {
630     UINT ret = 0, i;
631 
632     for (i = 0; i < bytes; i++)
633         ret += data[row][col + i] << i * 8;
634 
635     return ret;
636 }
637 
638 static UINT get_tablecolumns( MSIDATABASE *db, LPCWSTR szTableName, MSICOLUMNINFO *colinfo, UINT *sz )
639 {
640     UINT r, i, n = 0, table_id, count, maxcount = *sz;
641     MSITABLE *table = NULL;
642 
643     TRACE("%s\n", debugstr_w(szTableName));
644 
645     /* first check if there is a default table with that name */
646     r = get_defaulttablecolumns( db, szTableName, colinfo, sz );
647     if (r == ERROR_SUCCESS && *sz)
648         return r;
649 
650     r = get_table( db, szColumns, &table );
651     if (r != ERROR_SUCCESS)
652     {
653         ERR("couldn't load _Columns table\n");
654         return ERROR_FUNCTION_FAILED;
655     }
656 
657     /* convert table and column names to IDs from the string table */
658     r = msi_string2id( db->strings, szTableName, -1, &table_id );
659     if (r != ERROR_SUCCESS)
660     {
661         WARN("Couldn't find id for %s\n", debugstr_w(szTableName));
662         return r;
663     }
664     TRACE("Table id is %d, row count is %d\n", table_id, table->row_count);
665 
666     /* Note: _Columns table doesn't have non-persistent data */
667 
668     /* if maxcount is non-zero, assume it's exactly right for this table */
669     if (colinfo) memset( colinfo, 0, maxcount * sizeof(*colinfo) );
670     count = table->row_count;
671     for (i = 0; i < count; i++)
672     {
673         if (read_table_int( table->data, i, 0, LONG_STR_BYTES) != table_id) continue;
674         if (colinfo)
675         {
676             UINT id = read_table_int( table->data, i, table->colinfo[2].offset, LONG_STR_BYTES );
677             UINT col = read_table_int( table->data, i, table->colinfo[1].offset, sizeof(USHORT) ) - (1 << 15);
678 
679             /* check the column number is in range */
680             if (col < 1 || col > maxcount)
681             {
682                 ERR("column %d out of range (maxcount: %d)\n", col, maxcount);
683                 continue;
684             }
685             /* check if this column was already set */
686             if (colinfo[col - 1].number)
687             {
688                 ERR("duplicate column %d\n", col);
689                 continue;
690             }
691             colinfo[col - 1].tablename = msi_string_lookup( db->strings, table_id, NULL );
692             colinfo[col - 1].number = col;
693             colinfo[col - 1].colname = msi_string_lookup( db->strings, id, NULL );
694             colinfo[col - 1].type = read_table_int( table->data, i, table->colinfo[3].offset,
695                                                     sizeof(USHORT) ) - (1 << 15);
696             colinfo[col - 1].offset = 0;
697             colinfo[col - 1].ref_count = 0;
698             colinfo[col - 1].hash_table = NULL;
699         }
700         n++;
701     }
702     TRACE("%s has %d columns\n", debugstr_w(szTableName), n);
703 
704     if (colinfo && n != maxcount)
705     {
706         ERR("missing column in table %s\n", debugstr_w(szTableName));
707         msi_free_colinfo( colinfo, maxcount );
708         return ERROR_FUNCTION_FAILED;
709     }
710     table_calc_column_offsets( db, colinfo, n );
711     *sz = n;
712     return ERROR_SUCCESS;
713 }
714 
715 UINT msi_create_table( MSIDATABASE *db, LPCWSTR name, column_info *col_info,
716                        MSICONDITION persistent )
717 {
718     UINT r, nField;
719     MSIVIEW *tv = NULL;
720     MSIRECORD *rec = NULL;
721     column_info *col;
722     MSITABLE *table;
723     UINT i;
724 
725     /* only add tables that don't exist already */
726     if( TABLE_Exists(db, name ) )
727     {
728         WARN("table %s exists\n", debugstr_w(name));
729         return ERROR_BAD_QUERY_SYNTAX;
730     }
731 
732     table = msi_alloc( sizeof (MSITABLE) + lstrlenW(name)*sizeof (WCHAR) );
733     if( !table )
734         return ERROR_FUNCTION_FAILED;
735 
736     table->ref_count = 1;
737     table->row_count = 0;
738     table->data = NULL;
739     table->data_persistent = NULL;
740     table->colinfo = NULL;
741     table->col_count = 0;
742     table->persistent = persistent;
743     lstrcpyW( table->name, name );
744 
745     for( col = col_info; col; col = col->next )
746         table->col_count++;
747 
748     table->colinfo = msi_alloc( table->col_count * sizeof(MSICOLUMNINFO) );
749     if (!table->colinfo)
750     {
751         free_table( table );
752         return ERROR_FUNCTION_FAILED;
753     }
754 
755     for( i = 0, col = col_info; col; i++, col = col->next )
756     {
757         UINT table_id = msi_add_string( db->strings, col->table, -1, persistent );
758         UINT col_id = msi_add_string( db->strings, col->column, -1, persistent );
759 
760         table->colinfo[ i ].tablename = msi_string_lookup( db->strings, table_id, NULL );
761         table->colinfo[ i ].number = i + 1;
762         table->colinfo[ i ].colname = msi_string_lookup( db->strings, col_id, NULL );
763         table->colinfo[ i ].type = col->type;
764         table->colinfo[ i ].offset = 0;
765         table->colinfo[ i ].ref_count = 0;
766         table->colinfo[ i ].hash_table = NULL;
767         table->colinfo[ i ].temporary = col->temporary;
768     }
769     table_calc_column_offsets( db, table->colinfo, table->col_count);
770 
771     r = TABLE_CreateView( db, szTables, &tv );
772     TRACE("CreateView returned %x\n", r);
773     if( r )
774     {
775         free_table( table );
776         return r;
777     }
778 
779     r = tv->ops->execute( tv, 0 );
780     TRACE("tv execute returned %x\n", r);
781     if( r )
782         goto err;
783 
784     rec = MSI_CreateRecord( 1 );
785     if( !rec )
786         goto err;
787 
788     r = MSI_RecordSetStringW( rec, 1, name );
789     if( r )
790         goto err;
791 
792     r = tv->ops->insert_row( tv, rec, -1, persistent == MSICONDITION_FALSE );
793     TRACE("insert_row returned %x\n", r);
794     if( r )
795         goto err;
796 
797     tv->ops->delete( tv );
798     tv = NULL;
799 
800     msiobj_release( &rec->hdr );
801     rec = NULL;
802 
803     if( persistent != MSICONDITION_FALSE )
804     {
805         /* add each column to the _Columns table */
806         r = TABLE_CreateView( db, szColumns, &tv );
807         if( r )
808             goto err;
809 
810         r = tv->ops->execute( tv, 0 );
811         TRACE("tv execute returned %x\n", r);
812         if( r )
813             goto err;
814 
815         rec = MSI_CreateRecord( 4 );
816         if( !rec )
817             goto err;
818 
819         r = MSI_RecordSetStringW( rec, 1, name );
820         if( r )
821             goto err;
822 
823         /*
824          * need to set the table, column number, col name and type
825          * for each column we enter in the table
826          */
827         nField = 1;
828         for( col = col_info; col; col = col->next )
829         {
830             r = MSI_RecordSetInteger( rec, 2, nField );
831             if( r )
832                 goto err;
833 
834             r = MSI_RecordSetStringW( rec, 3, col->column );
835             if( r )
836                 goto err;
837 
838             r = MSI_RecordSetInteger( rec, 4, col->type );
839             if( r )
840                 goto err;
841 
842             r = tv->ops->insert_row( tv, rec, -1, FALSE );
843             if( r )
844                 goto err;
845 
846             nField++;
847         }
848         if( !col )
849             r = ERROR_SUCCESS;
850     }
851 
852 err:
853     if (rec)
854         msiobj_release( &rec->hdr );
855     /* FIXME: remove values from the string table on error */
856     if( tv )
857         tv->ops->delete( tv );
858 
859     if (r == ERROR_SUCCESS)
860         list_add_head( &db->tables, &table->entry );
861     else
862         free_table( table );
863 
864     return r;
865 }
866 
867 static UINT save_table( MSIDATABASE *db, const MSITABLE *t, UINT bytes_per_strref )
868 {
869     BYTE *rawdata = NULL;
870     UINT rawsize, i, j, row_size, row_count;
871     UINT r = ERROR_FUNCTION_FAILED;
872 
873     /* Nothing to do for non-persistent tables */
874     if( t->persistent == MSICONDITION_FALSE )
875         return ERROR_SUCCESS;
876 
877     TRACE("Saving %s\n", debugstr_w( t->name ) );
878 
879     row_size = msi_table_get_row_size( db, t->colinfo, t->col_count, bytes_per_strref );
880     row_count = t->row_count;
881     for (i = 0; i < t->row_count; i++)
882     {
883         if (!t->data_persistent[i])
884         {
885             row_count = 1; /* yes, this is bizarre */
886             break;
887         }
888     }
889     rawsize = row_count * row_size;
890     rawdata = msi_alloc_zero( rawsize );
891     if( !rawdata )
892     {
893         r = ERROR_NOT_ENOUGH_MEMORY;
894         goto err;
895     }
896 
897     rawsize = 0;
898     for (i = 0; i < row_count; i++)
899     {
900         UINT ofs = 0, ofs_mem = 0;
901 
902         if (!t->data_persistent[i]) break;
903 
904         for (j = 0; j < t->col_count; j++)
905         {
906             UINT m = bytes_per_column( db, &t->colinfo[j], LONG_STR_BYTES );
907             UINT n = bytes_per_column( db, &t->colinfo[j], bytes_per_strref );
908             UINT k;
909 
910             if (n != 2 && n != 3 && n != 4)
911             {
912                 ERR("oops - unknown column width %d\n", n);
913                 goto err;
914             }
915             if (t->colinfo[j].type & MSITYPE_STRING && n < m)
916             {
917                 UINT id = read_table_int( t->data, i, ofs_mem, LONG_STR_BYTES );
918                 if (id > 1 << bytes_per_strref * 8)
919                 {
920                     ERR("string id %u out of range\n", id);
921                     goto err;
922                 }
923             }
924             for (k = 0; k < n; k++)
925             {
926                 rawdata[ofs * row_count + i * n + k] = t->data[i][ofs_mem + k];
927             }
928             ofs_mem += m;
929             ofs += n;
930         }
931         rawsize += row_size;
932     }
933 
934     TRACE("writing %d bytes\n", rawsize);
935     r = write_stream_data( db->storage, t->name, rawdata, rawsize, TRUE );
936 
937 err:
938     msi_free( rawdata );
939     return r;
940 }
941 
942 static void msi_update_table_columns( MSIDATABASE *db, LPCWSTR name )
943 {
944     MSITABLE *table;
945     UINT size, offset, old_count;
946     UINT n;
947 
948     if (!(table = find_cached_table( db, name ))) return;
949     old_count = table->col_count;
950     msi_free_colinfo( table->colinfo, table->col_count );
951     msi_free( table->colinfo );
952     table->colinfo = NULL;
953 
954     table_get_column_info( db, name, &table->colinfo, &table->col_count );
955     if (!table->col_count) return;
956 
957     size = msi_table_get_row_size( db, table->colinfo, table->col_count, LONG_STR_BYTES );
958     offset = table->colinfo[table->col_count - 1].offset;
959 
960     for ( n = 0; n < table->row_count; n++ )
961     {
962         table->data[n] = msi_realloc( table->data[n], size );
963         if (old_count < table->col_count)
964             memset( &table->data[n][offset], 0, size - offset );
965     }
966 }
967 
968 /* try to find the table name in the _Tables table */
969 BOOL TABLE_Exists( MSIDATABASE *db, LPCWSTR name )
970 {
971     UINT r, table_id, i;
972     MSITABLE *table;
973 
974     if( !wcscmp( name, szTables ) || !wcscmp( name, szColumns ) ||
975         !wcscmp( name, szStreams ) || !wcscmp( name, szStorages ) )
976         return TRUE;
977 
978     r = msi_string2id( db->strings, name, -1, &table_id );
979     if( r != ERROR_SUCCESS )
980     {
981         TRACE("Couldn't find id for %s\n", debugstr_w(name));
982         return FALSE;
983     }
984 
985     r = get_table( db, szTables, &table );
986     if( r != ERROR_SUCCESS )
987     {
988         ERR("table %s not available\n", debugstr_w(szTables));
989         return FALSE;
990     }
991 
992     for( i = 0; i < table->row_count; i++ )
993     {
994         if( read_table_int( table->data, i, 0, LONG_STR_BYTES ) == table_id )
995             return TRUE;
996     }
997 
998     return FALSE;
999 }
1000 
1001 /* below is the query interface to a table */
1002 
1003 typedef struct tagMSITABLEVIEW
1004 {
1005     MSIVIEW        view;
1006     MSIDATABASE   *db;
1007     MSITABLE      *table;
1008     MSICOLUMNINFO *columns;
1009     UINT           num_cols;
1010     UINT           row_size;
1011     WCHAR          name[1];
1012 } MSITABLEVIEW;
1013 
1014 static UINT TABLE_fetch_int( struct tagMSIVIEW *view, UINT row, UINT col, UINT *val )
1015 {
1016     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1017     UINT offset, n;
1018 
1019     if( !tv->table )
1020         return ERROR_INVALID_PARAMETER;
1021 
1022     if( (col==0) || (col>tv->num_cols) )
1023         return ERROR_INVALID_PARAMETER;
1024 
1025     /* how many rows are there ? */
1026     if( row >= tv->table->row_count )
1027         return ERROR_NO_MORE_ITEMS;
1028 
1029     if( tv->columns[col-1].offset >= tv->row_size )
1030     {
1031         ERR("Stuffed up %d >= %d\n", tv->columns[col-1].offset, tv->row_size );
1032         ERR("%p %p\n", tv, tv->columns );
1033         return ERROR_FUNCTION_FAILED;
1034     }
1035 
1036     n = bytes_per_column( tv->db, &tv->columns[col - 1], LONG_STR_BYTES );
1037     if (n != 2 && n != 3 && n != 4)
1038     {
1039         ERR("oops! what is %d bytes per column?\n", n );
1040         return ERROR_FUNCTION_FAILED;
1041     }
1042 
1043     offset = tv->columns[col-1].offset;
1044     *val = read_table_int(tv->table->data, row, offset, n);
1045 
1046     /* TRACE("Data [%d][%d] = %d\n", row, col, *val ); */
1047 
1048     return ERROR_SUCCESS;
1049 }
1050 
1051 static UINT get_stream_name( const MSITABLEVIEW *tv, UINT row, WCHAR **pstname )
1052 {
1053     LPWSTR p, stname = NULL;
1054     UINT i, r, type, ival;
1055     DWORD len;
1056     LPCWSTR sval;
1057     MSIVIEW *view = (MSIVIEW *) tv;
1058 
1059     TRACE("%p %d\n", tv, row);
1060 
1061     len = lstrlenW( tv->name ) + 1;
1062     stname = msi_alloc( len*sizeof(WCHAR) );
1063     if ( !stname )
1064     {
1065        r = ERROR_OUTOFMEMORY;
1066        goto err;
1067     }
1068 
1069     lstrcpyW( stname, tv->name );
1070 
1071     for ( i = 0; i < tv->num_cols; i++ )
1072     {
1073         type = tv->columns[i].type;
1074         if ( type & MSITYPE_KEY )
1075         {
1076             WCHAR number[0x20];
1077 
1078             r = TABLE_fetch_int( view, row, i+1, &ival );
1079             if ( r != ERROR_SUCCESS )
1080                 goto err;
1081 
1082             if ( tv->columns[i].type & MSITYPE_STRING )
1083             {
1084                 sval = msi_string_lookup( tv->db->strings, ival, NULL );
1085                 if ( !sval )
1086                 {
1087                     r = ERROR_INVALID_PARAMETER;
1088                     goto err;
1089                 }
1090             }
1091             else
1092             {
1093                 static const WCHAR fmt[] = { '%','d',0 };
1094                 UINT n = bytes_per_column( tv->db, &tv->columns[i], LONG_STR_BYTES );
1095 
1096                 switch( n )
1097                 {
1098                 case 2:
1099                     swprintf( number, ARRAY_SIZE(number), fmt, ival-0x8000 );
1100                     break;
1101                 case 4:
1102                     swprintf( number, ARRAY_SIZE(number), fmt, ival^0x80000000 );
1103                     break;
1104                 default:
1105                     ERR( "oops - unknown column width %d\n", n );
1106                     r = ERROR_FUNCTION_FAILED;
1107                     goto err;
1108                 }
1109                 sval = number;
1110             }
1111 
1112             len += lstrlenW( szDot ) + lstrlenW( sval );
1113             p = msi_realloc ( stname, len*sizeof(WCHAR) );
1114             if ( !p )
1115             {
1116                 r = ERROR_OUTOFMEMORY;
1117                 goto err;
1118             }
1119             stname = p;
1120 
1121             lstrcatW( stname, szDot );
1122             lstrcatW( stname, sval );
1123         }
1124         else
1125            continue;
1126     }
1127 
1128     *pstname = stname;
1129     return ERROR_SUCCESS;
1130 
1131 err:
1132     msi_free( stname );
1133     *pstname = NULL;
1134     return r;
1135 }
1136 
1137 /*
1138  * We need a special case for streams, as we need to reference column with
1139  * the name of the stream in the same table, and the table name
1140  * which may not be available at higher levels of the query
1141  */
1142 static UINT TABLE_fetch_stream( struct tagMSIVIEW *view, UINT row, UINT col, IStream **stm )
1143 {
1144     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1145     UINT r;
1146     WCHAR *name;
1147 
1148     if( !view->ops->fetch_int )
1149         return ERROR_INVALID_PARAMETER;
1150 
1151     r = get_stream_name( tv, row, &name );
1152     if (r != ERROR_SUCCESS)
1153     {
1154         ERR("fetching stream, error = %u\n", r);
1155         return r;
1156     }
1157 
1158     r = msi_get_stream( tv->db, name, stm );
1159     if (r != ERROR_SUCCESS)
1160         ERR("fetching stream %s, error = %u\n", debugstr_w(name), r);
1161 
1162     msi_free( name );
1163     return r;
1164 }
1165 
1166 /* Set a table value, i.e. preadjusted integer or string ID. */
1167 static UINT table_set_bytes( MSITABLEVIEW *tv, UINT row, UINT col, UINT val )
1168 {
1169     UINT offset, n, i;
1170 
1171     if( !tv->table )
1172         return ERROR_INVALID_PARAMETER;
1173 
1174     if( (col==0) || (col>tv->num_cols) )
1175         return ERROR_INVALID_PARAMETER;
1176 
1177     if( row >= tv->table->row_count )
1178         return ERROR_INVALID_PARAMETER;
1179 
1180     if( tv->columns[col-1].offset >= tv->row_size )
1181     {
1182         ERR("Stuffed up %d >= %d\n", tv->columns[col-1].offset, tv->row_size );
1183         ERR("%p %p\n", tv, tv->columns );
1184         return ERROR_FUNCTION_FAILED;
1185     }
1186 
1187     msi_free( tv->columns[col-1].hash_table );
1188     tv->columns[col-1].hash_table = NULL;
1189 
1190     n = bytes_per_column( tv->db, &tv->columns[col - 1], LONG_STR_BYTES );
1191     if ( n != 2 && n != 3 && n != 4 )
1192     {
1193         ERR("oops! what is %d bytes per column?\n", n );
1194         return ERROR_FUNCTION_FAILED;
1195     }
1196 
1197     offset = tv->columns[col-1].offset;
1198     for ( i = 0; i < n; i++ )
1199         tv->table->data[row][offset + i] = (val >> i * 8) & 0xff;
1200 
1201     return ERROR_SUCCESS;
1202 }
1203 
1204 static UINT int_to_table_storage( const MSITABLEVIEW *tv, UINT col, int val, UINT *ret )
1205 {
1206     if ((tv->columns[col-1].type & MSI_DATASIZEMASK) == 2)
1207     {
1208         if (val == MSI_NULL_INTEGER)
1209             *ret = 0;
1210         else if ((val + 0x8000) & 0xffff0000)
1211         {
1212             ERR("value %d out of range\n", val);
1213             return ERROR_FUNCTION_FAILED;
1214         }
1215         else
1216             *ret = val + 0x8000;
1217     }
1218     else
1219         *ret = val ^ 0x80000000;
1220 
1221     return ERROR_SUCCESS;
1222 }
1223 
1224 static UINT TABLE_set_int( MSIVIEW *view, UINT row, UINT col, int val )
1225 {
1226     MSITABLEVIEW *tv = (MSITABLEVIEW *)view;
1227     UINT r, table_int;
1228 
1229     TRACE("row %u, col %u, val %d.\n", row, col, val);
1230 
1231     if ((r = int_to_table_storage( tv, col, val, &table_int )))
1232         return r;
1233 
1234     if (tv->columns[col-1].type & MSITYPE_KEY)
1235     {
1236         UINT key;
1237 
1238         if ((r = TABLE_fetch_int( view, row, col, &key )))
1239             return r;
1240         if (key != table_int)
1241         {
1242             ERR("Cannot modify primary key %s.%s.\n",
1243                 debugstr_w(tv->table->name), debugstr_w(tv->columns[col-1].colname));
1244             return ERROR_FUNCTION_FAILED;
1245         }
1246     }
1247 
1248     return table_set_bytes( tv, row, col, table_int );
1249 }
1250 
1251 static UINT TABLE_set_string( MSIVIEW *view, UINT row, UINT col, const WCHAR *val, int len )
1252 {
1253     MSITABLEVIEW *tv = (MSITABLEVIEW *)view;
1254     BOOL persistent;
1255     UINT id, r;
1256 
1257     TRACE("row %u, col %u, val %s.\n", row, col, debugstr_wn(val, len));
1258 
1259     persistent = (tv->table->persistent != MSICONDITION_FALSE)
1260                   && tv->table->data_persistent[row];
1261 
1262     if (val)
1263     {
1264         r = msi_string2id( tv->db->strings, val, len, &id );
1265         if (r != ERROR_SUCCESS)
1266             id = msi_add_string( tv->db->strings, val, len, persistent );
1267     }
1268     else
1269         id = 0;
1270 
1271     if (tv->columns[col-1].type & MSITYPE_KEY)
1272     {
1273         UINT key;
1274 
1275         if ((r = TABLE_fetch_int( view, row, col, &key )))
1276             return r;
1277         if (key != id)
1278         {
1279             ERR("Cannot modify primary key %s.%s.\n",
1280                 debugstr_w(tv->table->name), debugstr_w(tv->columns[col-1].colname));
1281             return ERROR_FUNCTION_FAILED;
1282         }
1283     }
1284 
1285     return table_set_bytes( tv, row, col, id );
1286 }
1287 
1288 static UINT TABLE_get_row( struct tagMSIVIEW *view, UINT row, MSIRECORD **rec )
1289 {
1290     MSITABLEVIEW *tv = (MSITABLEVIEW *)view;
1291 
1292     if (!tv->table)
1293         return ERROR_INVALID_PARAMETER;
1294 
1295     return msi_view_get_row(tv->db, view, row, rec);
1296 }
1297 
1298 static UINT add_stream( MSIDATABASE *db, const WCHAR *name, IStream *data )
1299 {
1300     static const WCHAR insert[] = {
1301         'I','N','S','E','R','T',' ','I','N','T','O',' ',
1302         '`','_','S','t','r','e','a','m','s','`',' ',
1303         '(','`','N','a','m','e','`',',','`','D','a','t','a','`',')',' ',
1304         'V','A','L','U','E','S',' ','(','?',',','?',')',0};
1305     static const WCHAR update[] = {
1306         'U','P','D','A','T','E',' ','`','_','S','t','r','e','a','m','s','`',' ',
1307         'S','E','T',' ','`','D','a','t','a','`',' ','=',' ','?',' ',
1308         'W','H','E','R','E',' ','`','N','a','m','e','`',' ','=',' ','?',0};
1309     MSIQUERY *query;
1310     MSIRECORD *rec;
1311     UINT r;
1312 
1313     TRACE("%p %s %p\n", db, debugstr_w(name), data);
1314 
1315     if (!(rec = MSI_CreateRecord( 2 )))
1316         return ERROR_OUTOFMEMORY;
1317 
1318     r = MSI_RecordSetStringW( rec, 1, name );
1319     if (r != ERROR_SUCCESS)
1320        goto done;
1321 
1322     r = MSI_RecordSetIStream( rec, 2, data );
1323     if (r != ERROR_SUCCESS)
1324        goto done;
1325 
1326     r = MSI_DatabaseOpenViewW( db, insert, &query );
1327     if (r != ERROR_SUCCESS)
1328        goto done;
1329 
1330     r = MSI_ViewExecute( query, rec );
1331     msiobj_release( &query->hdr );
1332     if (r == ERROR_SUCCESS)
1333         goto done;
1334 
1335     msiobj_release( &rec->hdr );
1336     if (!(rec = MSI_CreateRecord( 2 )))
1337         return ERROR_OUTOFMEMORY;
1338 
1339     r = MSI_RecordSetIStream( rec, 1, data );
1340     if (r != ERROR_SUCCESS)
1341        goto done;
1342 
1343     r = MSI_RecordSetStringW( rec, 2, name );
1344     if (r != ERROR_SUCCESS)
1345        goto done;
1346 
1347     r = MSI_DatabaseOpenViewW( db, update, &query );
1348     if (r != ERROR_SUCCESS)
1349         goto done;
1350 
1351     r = MSI_ViewExecute( query, rec );
1352     msiobj_release( &query->hdr );
1353 
1354 done:
1355     msiobj_release( &rec->hdr );
1356     return r;
1357 }
1358 
1359 static UINT TABLE_set_stream( MSIVIEW *view, UINT row, UINT col, IStream *stream )
1360 {
1361     MSITABLEVIEW *tv = (MSITABLEVIEW *)view;
1362     WCHAR *name;
1363     UINT r;
1364 
1365     TRACE("row %u, col %u, stream %p.\n", row, col, stream);
1366 
1367     if ((r = get_stream_name( tv, row - 1, &name )))
1368         return r;
1369 
1370     r = add_stream( tv->db, name, stream );
1371     msi_free( name );
1372     return r;
1373 }
1374 
1375 static UINT get_table_value_from_record( MSITABLEVIEW *tv, MSIRECORD *rec, UINT iField, UINT *pvalue )
1376 {
1377     MSICOLUMNINFO columninfo;
1378     UINT r;
1379 
1380     if (!iField || iField > tv->num_cols || MSI_RecordIsNull( rec, iField ))
1381         return ERROR_FUNCTION_FAILED;
1382 
1383     columninfo = tv->columns[ iField - 1 ];
1384 
1385     if ( MSITYPE_IS_BINARY(columninfo.type) )
1386     {
1387         *pvalue = 1; /* refers to the first key column */
1388     }
1389     else if ( columninfo.type & MSITYPE_STRING )
1390     {
1391         int len;
1392         const WCHAR *sval = msi_record_get_string( rec, iField, &len );
1393         if (sval)
1394         {
1395             r = msi_string2id( tv->db->strings, sval, len, pvalue );
1396             if (r != ERROR_SUCCESS)
1397                 return ERROR_NOT_FOUND;
1398         }
1399         else *pvalue = 0;
1400     }
1401     else
1402         return int_to_table_storage( tv, iField, MSI_RecordGetInteger( rec, iField ), pvalue );
1403 
1404     return ERROR_SUCCESS;
1405 }
1406 
1407 static UINT TABLE_set_row( struct tagMSIVIEW *view, UINT row, MSIRECORD *rec, UINT mask )
1408 {
1409     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1410     UINT i, val, r = ERROR_SUCCESS;
1411 
1412     if ( !tv->table )
1413         return ERROR_INVALID_PARAMETER;
1414 
1415     /* test if any of the mask bits are invalid */
1416     if ( mask >= (1<<tv->num_cols) )
1417         return ERROR_INVALID_PARAMETER;
1418 
1419     for ( i = 0; i < tv->num_cols; i++ )
1420     {
1421         BOOL persistent;
1422 
1423         /* only update the fields specified in the mask */
1424         if ( !(mask&(1<<i)) )
1425             continue;
1426 
1427         persistent = (tv->table->persistent != MSICONDITION_FALSE) &&
1428                      (tv->table->data_persistent[row]);
1429         /* FIXME: should we allow updating keys? */
1430 
1431         val = 0;
1432         if ( !MSI_RecordIsNull( rec, i + 1 ) )
1433         {
1434             r = get_table_value_from_record (tv, rec, i + 1, &val);
1435             if ( MSITYPE_IS_BINARY(tv->columns[ i ].type) )
1436             {
1437                 IStream *stm;
1438                 LPWSTR stname;
1439 
1440                 if ( r != ERROR_SUCCESS )
1441                     return ERROR_FUNCTION_FAILED;
1442 
1443                 r = MSI_RecordGetIStream( rec, i + 1, &stm );
1444                 if ( r != ERROR_SUCCESS )
1445                     return r;
1446 
1447                 r = get_stream_name( tv, row, &stname );
1448                 if ( r != ERROR_SUCCESS )
1449                 {
1450                     IStream_Release( stm );
1451                     return r;
1452                 }
1453 
1454                 r = add_stream( tv->db, stname, stm );
1455                 IStream_Release( stm );
1456                 msi_free ( stname );
1457 
1458                 if ( r != ERROR_SUCCESS )
1459                     return r;
1460             }
1461             else if ( tv->columns[i].type & MSITYPE_STRING )
1462             {
1463                 UINT x;
1464 
1465                 if ( r != ERROR_SUCCESS )
1466                 {
1467                     int len;
1468                     const WCHAR *sval = msi_record_get_string( rec, i + 1, &len );
1469                     val = msi_add_string( tv->db->strings, sval, len, persistent );
1470                 }
1471                 else
1472                 {
1473                     TABLE_fetch_int(&tv->view, row, i + 1, &x);
1474                     if (val == x)
1475                         continue;
1476                 }
1477             }
1478             else
1479             {
1480                 if ( r != ERROR_SUCCESS )
1481                     return ERROR_FUNCTION_FAILED;
1482             }
1483         }
1484 
1485         r = table_set_bytes( tv, row, i+1, val );
1486         if ( r != ERROR_SUCCESS )
1487             break;
1488     }
1489     return r;
1490 }
1491 
1492 static UINT table_create_new_row( struct tagMSIVIEW *view, UINT *num, BOOL temporary )
1493 {
1494     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1495     BYTE **p, *row;
1496     BOOL *b;
1497     UINT sz;
1498     BYTE ***data_ptr;
1499     BOOL **data_persist_ptr;
1500     UINT *row_count;
1501 
1502     TRACE("%p %s\n", view, temporary ? "TRUE" : "FALSE");
1503 
1504     if( !tv->table )
1505         return ERROR_INVALID_PARAMETER;
1506 
1507     row = msi_alloc_zero( tv->row_size );
1508     if( !row )
1509         return ERROR_NOT_ENOUGH_MEMORY;
1510 
1511     row_count = &tv->table->row_count;
1512     data_ptr = &tv->table->data;
1513     data_persist_ptr = &tv->table->data_persistent;
1514     if (*num == -1)
1515         *num = tv->table->row_count;
1516 
1517     sz = (*row_count + 1) * sizeof (BYTE*);
1518     if( *data_ptr )
1519         p = msi_realloc( *data_ptr, sz );
1520     else
1521         p = msi_alloc( sz );
1522     if( !p )
1523     {
1524         msi_free( row );
1525         return ERROR_NOT_ENOUGH_MEMORY;
1526     }
1527 
1528     sz = (*row_count + 1) * sizeof (BOOL);
1529     if( *data_persist_ptr )
1530         b = msi_realloc( *data_persist_ptr, sz );
1531     else
1532         b = msi_alloc( sz );
1533     if( !b )
1534     {
1535         msi_free( row );
1536         msi_free( p );
1537         return ERROR_NOT_ENOUGH_MEMORY;
1538     }
1539 
1540     *data_ptr = p;
1541     (*data_ptr)[*row_count] = row;
1542 
1543     *data_persist_ptr = b;
1544     (*data_persist_ptr)[*row_count] = !temporary;
1545 
1546     (*row_count)++;
1547 
1548     return ERROR_SUCCESS;
1549 }
1550 
1551 static UINT TABLE_execute( struct tagMSIVIEW *view, MSIRECORD *record )
1552 {
1553     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1554 
1555     TRACE("%p %p\n", tv, record);
1556 
1557     TRACE("There are %d columns\n", tv->num_cols );
1558 
1559     return ERROR_SUCCESS;
1560 }
1561 
1562 static UINT TABLE_close( struct tagMSIVIEW *view )
1563 {
1564     TRACE("%p\n", view );
1565 
1566     return ERROR_SUCCESS;
1567 }
1568 
1569 static UINT TABLE_get_dimensions( struct tagMSIVIEW *view, UINT *rows, UINT *cols)
1570 {
1571     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1572 
1573     TRACE("%p %p %p\n", view, rows, cols );
1574 
1575     if( cols )
1576         *cols = tv->num_cols;
1577     if( rows )
1578     {
1579         if( !tv->table )
1580             return ERROR_INVALID_PARAMETER;
1581         *rows = tv->table->row_count;
1582     }
1583 
1584     return ERROR_SUCCESS;
1585 }
1586 
1587 static UINT TABLE_get_column_info( struct tagMSIVIEW *view,
1588                 UINT n, LPCWSTR *name, UINT *type, BOOL *temporary,
1589                 LPCWSTR *table_name )
1590 {
1591     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1592 
1593     TRACE("%p %d %p %p\n", tv, n, name, type );
1594 
1595     if( ( n == 0 ) || ( n > tv->num_cols ) )
1596         return ERROR_INVALID_PARAMETER;
1597 
1598     if( name )
1599     {
1600         *name = tv->columns[n-1].colname;
1601         if( !*name )
1602             return ERROR_FUNCTION_FAILED;
1603     }
1604 
1605     if( table_name )
1606     {
1607         *table_name = tv->columns[n-1].tablename;
1608         if( !*table_name )
1609             return ERROR_FUNCTION_FAILED;
1610     }
1611 
1612     if( type )
1613         *type = tv->columns[n-1].type;
1614 
1615     if( temporary )
1616         *temporary = tv->columns[n-1].temporary;
1617 
1618     return ERROR_SUCCESS;
1619 }
1620 
1621 static UINT msi_table_find_row( MSITABLEVIEW *tv, MSIRECORD *rec, UINT *row, UINT *column );
1622 
1623 static UINT table_validate_new( MSITABLEVIEW *tv, MSIRECORD *rec, UINT *column )
1624 {
1625     UINT r, row, i;
1626 
1627     /* check there are no null values where they're not allowed */
1628     for( i = 0; i < tv->num_cols; i++ )
1629     {
1630         if ( tv->columns[i].type & MSITYPE_NULLABLE )
1631             continue;
1632 
1633         if ( MSITYPE_IS_BINARY(tv->columns[i].type) )
1634             TRACE("skipping binary column\n");
1635         else if ( tv->columns[i].type & MSITYPE_STRING )
1636         {
1637             int len;
1638             const WCHAR *str = msi_record_get_string( rec, i+1, &len );
1639 
1640             if (!str || (!str[0] && !len))
1641             {
1642                 if (column) *column = i;
1643                 return ERROR_INVALID_DATA;
1644             }
1645         }
1646         else
1647         {
1648             UINT n;
1649 
1650             n = MSI_RecordGetInteger( rec, i+1 );
1651             if (n == MSI_NULL_INTEGER)
1652             {
1653                 if (column) *column = i;
1654                 return ERROR_INVALID_DATA;
1655             }
1656         }
1657     }
1658 
1659     /* check there are no duplicate keys */
1660     r = msi_table_find_row( tv, rec, &row, column );
1661     if (r == ERROR_SUCCESS)
1662         return ERROR_FUNCTION_FAILED;
1663 
1664     return ERROR_SUCCESS;
1665 }
1666 
1667 static int compare_record( MSITABLEVIEW *tv, UINT row, MSIRECORD *rec )
1668 {
1669     UINT r, i, ivalue, x;
1670 
1671     for (i = 0; i < tv->num_cols; i++ )
1672     {
1673         if (!(tv->columns[i].type & MSITYPE_KEY)) continue;
1674 
1675         r = get_table_value_from_record( tv, rec, i + 1, &ivalue );
1676         if (r != ERROR_SUCCESS)
1677             return 1;
1678 
1679         r = TABLE_fetch_int( &tv->view, row, i + 1, &x );
1680         if (r != ERROR_SUCCESS)
1681         {
1682             WARN("TABLE_fetch_int should not fail here %u\n", r);
1683             return -1;
1684         }
1685         if (ivalue > x)
1686         {
1687             return 1;
1688         }
1689         else if (ivalue == x)
1690         {
1691             if (i < tv->num_cols - 1) continue;
1692             return 0;
1693         }
1694         else
1695             return -1;
1696     }
1697     return 1;
1698 }
1699 
1700 static int find_insert_index( MSITABLEVIEW *tv, MSIRECORD *rec )
1701 {
1702     int idx, c, low = 0, high = tv->table->row_count - 1;
1703 
1704     TRACE("%p %p\n", tv, rec);
1705 
1706     while (low <= high)
1707     {
1708         idx = (low + high) / 2;
1709         c = compare_record( tv, idx, rec );
1710 
1711         if (c < 0)
1712             high = idx - 1;
1713         else if (c > 0)
1714             low = idx + 1;
1715         else
1716         {
1717             TRACE("found %u\n", idx);
1718             return idx;
1719         }
1720     }
1721     TRACE("found %u\n", high + 1);
1722     return high + 1;
1723 }
1724 
1725 static UINT TABLE_insert_row( struct tagMSIVIEW *view, MSIRECORD *rec, UINT row, BOOL temporary )
1726 {
1727     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1728     UINT i, r;
1729 
1730     TRACE("%p %p %s\n", tv, rec, temporary ? "TRUE" : "FALSE" );
1731 
1732     /* check that the key is unique - can we find a matching row? */
1733     r = table_validate_new( tv, rec, NULL );
1734     if( r != ERROR_SUCCESS )
1735         return ERROR_FUNCTION_FAILED;
1736 
1737     if (row == -1)
1738         row = find_insert_index( tv, rec );
1739 
1740     r = table_create_new_row( view, &row, temporary );
1741     TRACE("insert_row returned %08x\n", r);
1742     if( r != ERROR_SUCCESS )
1743         return r;
1744 
1745     /* shift the rows to make room for the new row */
1746     for (i = tv->table->row_count - 1; i > row; i--)
1747     {
1748         memmove(&(tv->table->data[i][0]),
1749                 &(tv->table->data[i - 1][0]), tv->row_size);
1750         tv->table->data_persistent[i] = tv->table->data_persistent[i - 1];
1751     }
1752 
1753     /* Re-set the persistence flag */
1754     tv->table->data_persistent[row] = !temporary;
1755     return TABLE_set_row( view, row, rec, (1<<tv->num_cols) - 1 );
1756 }
1757 
1758 static UINT TABLE_delete_row( struct tagMSIVIEW *view, UINT row )
1759 {
1760     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1761     UINT r, num_rows, num_cols, i;
1762 
1763     TRACE("%p %d\n", tv, row);
1764 
1765     if ( !tv->table )
1766         return ERROR_INVALID_PARAMETER;
1767 
1768     r = TABLE_get_dimensions( view, &num_rows, &num_cols );
1769     if ( r != ERROR_SUCCESS )
1770         return r;
1771 
1772     if ( row >= num_rows )
1773         return ERROR_FUNCTION_FAILED;
1774 
1775     num_rows = tv->table->row_count;
1776     tv->table->row_count--;
1777 
1778     /* reset the hash tables */
1779     for (i = 0; i < tv->num_cols; i++)
1780     {
1781         msi_free( tv->columns[i].hash_table );
1782         tv->columns[i].hash_table = NULL;
1783     }
1784 
1785     for (i = row + 1; i < num_rows; i++)
1786     {
1787         memcpy(tv->table->data[i - 1], tv->table->data[i], tv->row_size);
1788         tv->table->data_persistent[i - 1] = tv->table->data_persistent[i];
1789     }
1790 
1791     msi_free(tv->table->data[num_rows - 1]);
1792 
1793     return ERROR_SUCCESS;
1794 }
1795 
1796 static UINT msi_table_update(struct tagMSIVIEW *view, MSIRECORD *rec, UINT row)
1797 {
1798     MSITABLEVIEW *tv = (MSITABLEVIEW *)view;
1799     UINT r, new_row;
1800 
1801     /* FIXME: MsiViewFetch should set rec index 0 to some ID that
1802      * sets the fetched record apart from other records
1803      */
1804 
1805     if (!tv->table)
1806         return ERROR_INVALID_PARAMETER;
1807 
1808     r = msi_table_find_row(tv, rec, &new_row, NULL);
1809     if (r != ERROR_SUCCESS)
1810     {
1811         ERR("can't find row to modify\n");
1812         return ERROR_FUNCTION_FAILED;
1813     }
1814 
1815     /* the row cannot be changed */
1816     if (row != new_row)
1817         return ERROR_FUNCTION_FAILED;
1818 
1819     return TABLE_set_row(view, new_row, rec, (1 << tv->num_cols) - 1);
1820 }
1821 
1822 static UINT msi_table_assign(struct tagMSIVIEW *view, MSIRECORD *rec)
1823 {
1824     MSITABLEVIEW *tv = (MSITABLEVIEW *)view;
1825     UINT r, row;
1826 
1827     if (!tv->table)
1828         return ERROR_INVALID_PARAMETER;
1829 
1830     r = msi_table_find_row(tv, rec, &row, NULL);
1831     if (r == ERROR_SUCCESS)
1832         return TABLE_set_row(view, row, rec, (1 << tv->num_cols) - 1);
1833     else
1834         return TABLE_insert_row( view, rec, -1, FALSE );
1835 }
1836 
1837 static UINT msi_refresh_record( struct tagMSIVIEW *view, MSIRECORD *rec, UINT row )
1838 {
1839     MSIRECORD *curr;
1840     UINT r, i, count;
1841 
1842     r = TABLE_get_row(view, row, &curr);
1843     if (r != ERROR_SUCCESS)
1844         return r;
1845 
1846     /* Close the original record */
1847     MSI_CloseRecord(&rec->hdr);
1848 
1849     count = MSI_RecordGetFieldCount(rec);
1850     for (i = 0; i < count; i++)
1851         MSI_RecordCopyField(curr, i + 1, rec, i + 1);
1852 
1853     msiobj_release(&curr->hdr);
1854     return ERROR_SUCCESS;
1855 }
1856 
1857 static UINT TABLE_modify( struct tagMSIVIEW *view, MSIMODIFY eModifyMode,
1858                           MSIRECORD *rec, UINT row)
1859 {
1860     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1861     UINT r, frow, column;
1862 
1863     TRACE("%p %d %p\n", view, eModifyMode, rec );
1864 
1865     switch (eModifyMode)
1866     {
1867     case MSIMODIFY_DELETE:
1868         r = TABLE_delete_row( view, row );
1869         break;
1870     case MSIMODIFY_VALIDATE_NEW:
1871         r = table_validate_new( tv, rec, &column );
1872         if (r != ERROR_SUCCESS)
1873         {
1874             tv->view.error = MSIDBERROR_DUPLICATEKEY;
1875             tv->view.error_column = tv->columns[column].colname;
1876             r = ERROR_INVALID_DATA;
1877         }
1878         break;
1879 
1880     case MSIMODIFY_INSERT:
1881         r = table_validate_new( tv, rec, NULL );
1882         if (r != ERROR_SUCCESS)
1883             break;
1884         r = TABLE_insert_row( view, rec, -1, FALSE );
1885         break;
1886 
1887     case MSIMODIFY_INSERT_TEMPORARY:
1888         r = table_validate_new( tv, rec, NULL );
1889         if (r != ERROR_SUCCESS)
1890             break;
1891         r = TABLE_insert_row( view, rec, -1, TRUE );
1892         break;
1893 
1894     case MSIMODIFY_REFRESH:
1895         r = msi_refresh_record( view, rec, row );
1896         break;
1897 
1898     case MSIMODIFY_UPDATE:
1899         r = msi_table_update( view, rec, row );
1900         break;
1901 
1902     case MSIMODIFY_ASSIGN:
1903         r = msi_table_assign( view, rec );
1904         break;
1905 
1906     case MSIMODIFY_MERGE:
1907         /* check row that matches this record */
1908         r = msi_table_find_row( tv, rec, &frow, &column );
1909         if (r != ERROR_SUCCESS)
1910         {
1911             r = table_validate_new( tv, rec, NULL );
1912             if (r == ERROR_SUCCESS)
1913                 r = TABLE_insert_row( view, rec, -1, FALSE );
1914         }
1915         break;
1916 
1917     case MSIMODIFY_REPLACE:
1918     case MSIMODIFY_VALIDATE:
1919     case MSIMODIFY_VALIDATE_FIELD:
1920     case MSIMODIFY_VALIDATE_DELETE:
1921         FIXME("%p %d %p - mode not implemented\n", view, eModifyMode, rec );
1922         r = ERROR_CALL_NOT_IMPLEMENTED;
1923         break;
1924 
1925     default:
1926         r = ERROR_INVALID_DATA;
1927     }
1928 
1929     return r;
1930 }
1931 
1932 static UINT TABLE_delete( struct tagMSIVIEW *view )
1933 {
1934     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1935 
1936     TRACE("%p\n", view );
1937 
1938     tv->table = NULL;
1939     tv->columns = NULL;
1940 
1941     msi_free( tv );
1942 
1943     return ERROR_SUCCESS;
1944 }
1945 
1946 static UINT TABLE_add_ref(struct tagMSIVIEW *view)
1947 {
1948     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1949     UINT i;
1950 
1951     TRACE("%p %d\n", view, tv->table->ref_count);
1952 
1953     for (i = 0; i < tv->table->col_count; i++)
1954     {
1955         if (tv->table->colinfo[i].type & MSITYPE_TEMPORARY)
1956             InterlockedIncrement(&tv->table->colinfo[i].ref_count);
1957     }
1958 
1959     return InterlockedIncrement(&tv->table->ref_count);
1960 }
1961 
1962 static UINT TABLE_remove_column(struct tagMSIVIEW *view, LPCWSTR table, UINT number)
1963 {
1964     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
1965     MSIRECORD *rec = NULL;
1966     MSIVIEW *columns = NULL;
1967     UINT row, r;
1968 
1969     if (tv->table->col_count != number)
1970         return ERROR_BAD_QUERY_SYNTAX;
1971 
1972     if (tv->table->colinfo[number-1].temporary)
1973     {
1974         UINT size = tv->table->colinfo[number-1].offset;
1975         tv->table->col_count--;
1976         tv->table->colinfo = msi_realloc( tv->table->colinfo, sizeof(*tv->table->colinfo) * tv->table->col_count );
1977 
1978         for (row = 0; row < tv->table->row_count; row++)
1979             tv->table->data[row] = msi_realloc( tv->table->data[row], size );
1980         return ERROR_SUCCESS;
1981     }
1982 
1983     rec = MSI_CreateRecord(2);
1984     if (!rec)
1985         return ERROR_OUTOFMEMORY;
1986 
1987     MSI_RecordSetStringW(rec, 1, table);
1988     MSI_RecordSetInteger(rec, 2, number);
1989 
1990     r = TABLE_CreateView(tv->db, szColumns, &columns);
1991     if (r != ERROR_SUCCESS)
1992     {
1993         msiobj_release(&rec->hdr);
1994         return r;
1995     }
1996 
1997     r = msi_table_find_row((MSITABLEVIEW *)columns, rec, &row, NULL);
1998     if (r != ERROR_SUCCESS)
1999         goto done;
2000 
2001     r = TABLE_delete_row(columns, row);
2002     if (r != ERROR_SUCCESS)
2003         goto done;
2004 
2005     msi_update_table_columns(tv->db, table);
2006 
2007 done:
2008     msiobj_release(&rec->hdr);
2009     columns->ops->delete(columns);
2010     return r;
2011 }
2012 
2013 static UINT TABLE_release(struct tagMSIVIEW *view)
2014 {
2015     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
2016     INT ref = tv->table->ref_count;
2017     UINT r;
2018     INT i;
2019 
2020     TRACE("%p %d\n", view, ref);
2021 
2022     for (i = tv->table->col_count - 1; i >= 0; i--)
2023     {
2024         if (tv->table->colinfo[i].type & MSITYPE_TEMPORARY)
2025         {
2026             ref = InterlockedDecrement(&tv->table->colinfo[i].ref_count);
2027             if (ref == 0)
2028             {
2029                 r = TABLE_remove_column(view, tv->table->colinfo[i].tablename,
2030                                         tv->table->colinfo[i].number);
2031                 if (r != ERROR_SUCCESS)
2032                     break;
2033             }
2034         }
2035     }
2036 
2037     ref = InterlockedDecrement(&tv->table->ref_count);
2038     if (ref == 0)
2039     {
2040         if (!tv->table->row_count)
2041         {
2042             list_remove(&tv->table->entry);
2043             free_table(tv->table);
2044             TABLE_delete(view);
2045         }
2046     }
2047 
2048     return ref;
2049 }
2050 
2051 static UINT TABLE_add_column(struct tagMSIVIEW *view, LPCWSTR table, UINT number,
2052                              LPCWSTR column, UINT type, BOOL hold)
2053 {
2054     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
2055     MSITABLE *msitable;
2056     MSIRECORD *rec;
2057     UINT r, i;
2058 
2059     rec = MSI_CreateRecord(4);
2060     if (!rec)
2061         return ERROR_OUTOFMEMORY;
2062 
2063     MSI_RecordSetStringW(rec, 1, table);
2064     MSI_RecordSetInteger(rec, 2, number);
2065     MSI_RecordSetStringW(rec, 3, column);
2066     MSI_RecordSetInteger(rec, 4, type);
2067 
2068     r = TABLE_insert_row(&tv->view, rec, -1, FALSE);
2069     if (r != ERROR_SUCCESS)
2070         goto done;
2071 
2072     msi_update_table_columns(tv->db, table);
2073 
2074     if (!hold)
2075         goto done;
2076 
2077     msitable = find_cached_table(tv->db, table);
2078     for (i = 0; i < msitable->col_count; i++)
2079     {
2080         if (!wcscmp( msitable->colinfo[i].colname, column ))
2081         {
2082             InterlockedIncrement(&msitable->colinfo[i].ref_count);
2083             break;
2084         }
2085     }
2086 
2087 done:
2088     msiobj_release(&rec->hdr);
2089     return r;
2090 }
2091 
2092 static UINT TABLE_drop(struct tagMSIVIEW *view)
2093 {
2094     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
2095     MSIVIEW *tables = NULL;
2096     MSIRECORD *rec = NULL;
2097     UINT r, row;
2098     INT i;
2099 
2100     TRACE("dropping table %s\n", debugstr_w(tv->name));
2101 
2102     for (i = tv->table->col_count - 1; i >= 0; i--)
2103     {
2104         r = TABLE_remove_column(view, tv->table->colinfo[i].tablename,
2105                                 tv->table->colinfo[i].number);
2106         if (r != ERROR_SUCCESS)
2107             return r;
2108     }
2109 
2110     rec = MSI_CreateRecord(1);
2111     if (!rec)
2112         return ERROR_OUTOFMEMORY;
2113 
2114     MSI_RecordSetStringW(rec, 1, tv->name);
2115 
2116     r = TABLE_CreateView(tv->db, szTables, &tables);
2117     if (r != ERROR_SUCCESS)
2118     {
2119         msiobj_release(&rec->hdr);
2120         return r;
2121     }
2122 
2123     r = msi_table_find_row((MSITABLEVIEW *)tables, rec, &row, NULL);
2124     if (r != ERROR_SUCCESS)
2125         goto done;
2126 
2127     r = TABLE_delete_row(tables, row);
2128     if (r != ERROR_SUCCESS)
2129         goto done;
2130 
2131     list_remove(&tv->table->entry);
2132     free_table(tv->table);
2133 
2134 done:
2135     msiobj_release(&rec->hdr);
2136     tables->ops->delete(tables);
2137 
2138     return r;
2139 }
2140 
2141 static const MSIVIEWOPS table_ops =
2142 {
2143     TABLE_fetch_int,
2144     TABLE_fetch_stream,
2145     TABLE_set_int,
2146     TABLE_set_string,
2147     TABLE_set_stream,
2148     TABLE_set_row,
2149     TABLE_insert_row,
2150     TABLE_delete_row,
2151     TABLE_execute,
2152     TABLE_close,
2153     TABLE_get_dimensions,
2154     TABLE_get_column_info,
2155     TABLE_modify,
2156     TABLE_delete,
2157     TABLE_add_ref,
2158     TABLE_release,
2159     TABLE_add_column,
2160     NULL,
2161     TABLE_drop,
2162 };
2163 
2164 UINT TABLE_CreateView( MSIDATABASE *db, LPCWSTR name, MSIVIEW **view )
2165 {
2166     MSITABLEVIEW *tv ;
2167     UINT r, sz;
2168 
2169     TRACE("%p %s %p\n", db, debugstr_w(name), view );
2170 
2171     if ( !wcscmp( name, szStreams ) )
2172         return STREAMS_CreateView( db, view );
2173     else if ( !wcscmp( name, szStorages ) )
2174         return STORAGES_CreateView( db, view );
2175 
2176     sz = FIELD_OFFSET( MSITABLEVIEW, name[lstrlenW( name ) + 1] );
2177     tv = msi_alloc_zero( sz );
2178     if( !tv )
2179         return ERROR_FUNCTION_FAILED;
2180 
2181     r = get_table( db, name, &tv->table );
2182     if( r != ERROR_SUCCESS )
2183     {
2184         msi_free( tv );
2185         WARN("table not found\n");
2186         return r;
2187     }
2188 
2189     TRACE("table %p found with %d columns\n", tv->table, tv->table->col_count);
2190 
2191     /* fill the structure */
2192     tv->view.ops = &table_ops;
2193     tv->db = db;
2194     tv->columns = tv->table->colinfo;
2195     tv->num_cols = tv->table->col_count;
2196     tv->row_size = msi_table_get_row_size( db, tv->table->colinfo, tv->table->col_count, LONG_STR_BYTES );
2197 
2198     TRACE("%s one row is %d bytes\n", debugstr_w(name), tv->row_size );
2199 
2200     *view = (MSIVIEW*) tv;
2201     lstrcpyW( tv->name, name );
2202 
2203     return ERROR_SUCCESS;
2204 }
2205 
2206 static WCHAR* create_key_string(MSITABLEVIEW *tv, MSIRECORD *rec)
2207 {
2208     DWORD i, p, len, key_len = 0;
2209     WCHAR *key;
2210 
2211     for (i = 0; i < tv->num_cols; i++)
2212     {
2213         if (!(tv->columns[i].type & MSITYPE_KEY))
2214             continue;
2215         if (MSI_RecordGetStringW( rec, i+1, NULL, &len ) == ERROR_SUCCESS)
2216             key_len += len;
2217         key_len++;
2218     }
2219 
2220     key = msi_alloc( key_len * sizeof(WCHAR) );
2221     if(!key)
2222         return NULL;
2223 
2224     p = 0;
2225     for (i = 0; i < tv->num_cols; i++)
2226     {
2227         if (!(tv->columns[i].type & MSITYPE_KEY))
2228             continue;
2229         if (p)
2230             key[p++] = '\t';
2231         len = key_len - p;
2232         if (MSI_RecordGetStringW( rec, i+1, key + p, &len ) == ERROR_SUCCESS)
2233             p += len;
2234     }
2235     return key;
2236 }
2237 
2238 static UINT msi_record_stream_name( const MSITABLEVIEW *tv, MSIRECORD *rec, LPWSTR name, UINT *len )
2239 {
2240     UINT p = 0, l, i, r;
2241 
2242     l = wcslen( tv->name );
2243     if (name && *len > l)
2244         memcpy(name, tv->name, l * sizeof(WCHAR));
2245     p += l;
2246 
2247     for ( i = 0; i < tv->num_cols; i++ )
2248     {
2249         if (!(tv->columns[i].type & MSITYPE_KEY))
2250             continue;
2251 
2252         if (name && *len > p + 1)
2253             name[p] = '.';
2254         p++;
2255 
2256         l = (*len > p ? *len - p : 0);
2257         r = MSI_RecordGetStringW( rec, i + 1, name ? name + p : NULL, &l );
2258         if (r != ERROR_SUCCESS)
2259             return r;
2260         p += l;
2261     }
2262 
2263     if (name && *len > p)
2264         name[p] = 0;
2265 
2266     *len = p;
2267     return ERROR_SUCCESS;
2268 }
2269 
2270 static UINT TransformView_fetch_int( MSIVIEW *view, UINT row, UINT col, UINT *val )
2271 {
2272     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
2273 
2274     if (!tv->table || col > tv->table->col_count)
2275     {
2276         *val = 0;
2277         return ERROR_SUCCESS;
2278     }
2279     return TABLE_fetch_int( view, row, col, val );
2280 }
2281 
2282 static UINT TransformView_fetch_stream( MSIVIEW *view, UINT row, UINT col, IStream **stm )
2283 {
2284     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
2285 
2286     if (!tv->table || col > tv->table->col_count)
2287     {
2288         *stm = NULL;
2289         return ERROR_SUCCESS;
2290     }
2291     return TABLE_fetch_stream( view, row, col, stm );
2292 }
2293 
2294 static UINT TransformView_set_row( MSIVIEW *view, UINT row, MSIRECORD *rec, UINT mask )
2295 {
2296     static const WCHAR query_pfx[] =
2297         L"INSERT INTO `_TransformView` (`Table`, `Column`, `Row`, `Data`, `Current`) VALUES ('";
2298 
2299     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
2300     WCHAR buf[256], *query = buf;
2301     MSIRECORD *old_rec;
2302     MSIQUERY *q;
2303     WCHAR *key;
2304     UINT i, p, r, len, qlen;
2305 
2306     if (!wcscmp( tv->name, szColumns ))
2307     {
2308         ERR( "trying to modify existing column\n" );
2309         return ERROR_INSTALL_TRANSFORM_FAILURE;
2310     }
2311 
2312     if (!wcscmp( tv->name, szTables ))
2313     {
2314         ERR( "trying to modify existing table\n" );
2315         return ERROR_INSTALL_TRANSFORM_FAILURE;
2316     }
2317 
2318     key = create_key_string( tv, rec );
2319     if (!key)
2320         return ERROR_OUTOFMEMORY;
2321 
2322     r = msi_view_get_row( tv->db, view, row, &old_rec );
2323     if (r != ERROR_SUCCESS)
2324         old_rec = NULL;
2325 
2326     for (i = 0; i < tv->num_cols; i++)
2327     {
2328         if (!(mask & (1 << i)))
2329             continue;
2330         if (tv->columns[i].type & MSITYPE_KEY)
2331             continue;
2332 
2333         qlen = p = wcslen( query_pfx );
2334         qlen += wcslen( tv->name ) + 3; /* strlen("','") */
2335         qlen += wcslen( tv->columns[i].colname ) + 3;
2336         qlen += wcslen( key ) + 3;
2337         if (MSITYPE_IS_BINARY( tv->columns[i].type ))
2338             r = msi_record_stream_name( tv, rec, NULL, &len );
2339         else
2340             r = MSI_RecordGetStringW( rec, i + 1, NULL, &len );
2341         if (r != ERROR_SUCCESS)
2342         {
2343             if (old_rec)
2344                 msiobj_release( &old_rec->hdr );
2345             msi_free( key );
2346             return r;
2347         }
2348         qlen += len + 3;
2349         if (old_rec && (r = MSI_RecordGetStringW( old_rec, i+1, NULL, &len )))
2350         {
2351             msiobj_release( &old_rec->hdr );
2352             msi_free( key );
2353             return r;
2354         }
2355         qlen += len + 3; /* strlen("')") + 1 */
2356 
2357         if (qlen > ARRAY_SIZE(buf))
2358         {
2359             query = msi_alloc( qlen * sizeof(WCHAR) );
2360             if (!query)
2361             {
2362                 if (old_rec)
2363                     msiobj_release( &old_rec->hdr );
2364                 msi_free( key );
2365                 return ERROR_OUTOFMEMORY;
2366             }
2367         }
2368 
2369         memcpy( query, query_pfx, p * sizeof(WCHAR) );
2370         len = wcslen( tv->name );
2371         memcpy( query + p, tv->name, len * sizeof(WCHAR) );
2372         p += len;
2373         query[p++] = '\'';
2374         query[p++] = ',';
2375         query[p++] = '\'';
2376         len = wcslen( tv->columns[i].colname );
2377         memcpy( query + p, tv->columns[i].colname, len * sizeof(WCHAR) );
2378         p += len;
2379         query[p++] = '\'';
2380         query[p++] = ',';
2381         query[p++] = '\'';
2382         len = wcslen( key );
2383         memcpy( query + p, key, len * sizeof(WCHAR) );
2384         p += len;
2385         query[p++] = '\'';
2386         query[p++] = ',';
2387         query[p++] = '\'';
2388         len = qlen - p;
2389         if (MSITYPE_IS_BINARY( tv->columns[i].type ))
2390             msi_record_stream_name( tv, rec, query + p, &len );
2391         else
2392             MSI_RecordGetStringW( rec, i + 1, query + p, &len );
2393         p += len;
2394         query[p++] = '\'';
2395         query[p++] = ',';
2396         query[p++] = '\'';
2397         if (old_rec)
2398         {
2399             len = qlen - p;
2400             MSI_RecordGetStringW( old_rec, i + 1, query + p, &len );
2401             p += len;
2402         }
2403         query[p++] = '\'';
2404         query[p++] = ')';
2405         query[p++] = 0;
2406 
2407         r = MSI_DatabaseOpenViewW( tv->db, query, &q );
2408         if (query != buf)
2409             msi_free( query );
2410         if (r != ERROR_SUCCESS)
2411         {
2412             if (old_rec)
2413                 msiobj_release( &old_rec->hdr );
2414             msi_free( key );
2415             return r;
2416         }
2417 
2418         r = MSI_ViewExecute( q, NULL );
2419         msiobj_release( &q->hdr );
2420         if (r != ERROR_SUCCESS)
2421         {
2422             if (old_rec)
2423                 msiobj_release( &old_rec->hdr );
2424             msi_free( key );
2425             return r;
2426         }
2427     }
2428 
2429     if (old_rec)
2430         msiobj_release( &old_rec->hdr );
2431     msi_free( key );
2432     return ERROR_SUCCESS;
2433 }
2434 
2435 static UINT TransformView_create_table( MSITABLEVIEW *tv, MSIRECORD *rec )
2436 {
2437     static const WCHAR query_fmt[] =
2438         L"INSERT INTO `_TransformView` (`Table`, `Column`) VALUES ('%s', 'CREATE')";
2439 
2440     WCHAR buf[256], *query = buf;
2441     const WCHAR *name;
2442     MSIQUERY *q;
2443     int len;
2444     UINT r;
2445 
2446     name = msi_record_get_string( rec, 1, &len );
2447     if (!name)
2448         return ERROR_INSTALL_TRANSFORM_FAILURE;
2449 
2450     len = _snwprintf( NULL, 0, query_fmt, name ) + 1;
2451     if (len > ARRAY_SIZE(buf))
2452     {
2453         query = msi_alloc( len * sizeof(WCHAR) );
2454         if (!query)
2455             return ERROR_OUTOFMEMORY;
2456     }
2457     swprintf( query, len, query_fmt, name );
2458 
2459     r = MSI_DatabaseOpenViewW( tv->db, query, &q );
2460     if (query != buf)
2461         msi_free( query );
2462     if (r != ERROR_SUCCESS)
2463         return r;
2464 
2465     r = MSI_ViewExecute( q, NULL );
2466     msiobj_release( &q->hdr );
2467     return r;
2468 }
2469 
2470 static UINT TransformView_add_column( MSITABLEVIEW *tv, MSIRECORD *rec )
2471 {
2472     static const WCHAR query_pfx[] =
2473         L"INSERT INTO `_TransformView` (`Table`, `Current`, `Column`, `Data`) VALUES ('";
2474 
2475     WCHAR buf[256], *query = buf;
2476     UINT i, p, len, r, qlen;
2477     MSIQUERY *q;
2478 
2479     qlen = p = wcslen( query_pfx );
2480     for (i = 1; i <= 4; i++)
2481     {
2482         r = MSI_RecordGetStringW( rec, i, NULL, &len );
2483         if (r != ERROR_SUCCESS)
2484             return r;
2485         qlen += len + 3; /* strlen( "','" ) */
2486     }
2487 
2488     if (qlen > ARRAY_SIZE(buf))
2489     {
2490         query = msi_alloc( len * sizeof(WCHAR) );
2491         qlen = len;
2492         if (!query)
2493             return ERROR_OUTOFMEMORY;
2494     }
2495 
2496     memcpy( query, query_pfx, p * sizeof(WCHAR) );
2497     for (i = 1; i <= 4; i++)
2498     {
2499         len = qlen - p;
2500         MSI_RecordGetStringW( rec, i, query + p, &len );
2501         p += len;
2502         query[p++] = '\'';
2503         if (i != 4)
2504         {
2505             query[p++] = ',';
2506             query[p++] = '\'';
2507         }
2508     }
2509     query[p++] = ')';
2510     query[p++] = 0;
2511 
2512     r = MSI_DatabaseOpenViewW( tv->db, query, &q );
2513     if (query != buf)
2514         msi_free( query );
2515     if (r != ERROR_SUCCESS)
2516         return r;
2517 
2518     r = MSI_ViewExecute( q, NULL );
2519     msiobj_release( &q->hdr );
2520     return r;
2521 }
2522 
2523 static UINT TransformView_insert_row( MSIVIEW *view, MSIRECORD *rec, UINT row, BOOL temporary )
2524 {
2525     static const WCHAR query_fmt[] =
2526         L"INSERT INTO `_TransformView` (`Table`, `Column`, `Row`) VALUES ('%s', 'INSERT', '%s')";
2527 
2528     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
2529     WCHAR buf[256], *query = buf;
2530     MSIQUERY *q;
2531     WCHAR *key;
2532     int len;
2533     UINT r;
2534 
2535     if (!wcscmp(tv->name, szTables))
2536         return TransformView_create_table( tv, rec );
2537 
2538     if (!wcscmp(tv->name, szColumns))
2539         return TransformView_add_column( tv, rec );
2540 
2541     key = create_key_string( tv, rec );
2542     if (!key)
2543         return ERROR_OUTOFMEMORY;
2544 
2545     len = _snwprintf( NULL, 0, query_fmt, tv->name, key ) + 1;
2546     if (len > ARRAY_SIZE(buf))
2547     {
2548         query = msi_alloc( len * sizeof(WCHAR) );
2549         if (!query)
2550         {
2551             msi_free( key );
2552             return ERROR_OUTOFMEMORY;
2553         }
2554     }
2555     swprintf( query, len, query_fmt, tv->name, key );
2556     msi_free( key );
2557 
2558     r = MSI_DatabaseOpenViewW( tv->db, query, &q );
2559     if (query != buf)
2560         msi_free( query );
2561     if (r != ERROR_SUCCESS)
2562         return r;
2563 
2564     r = MSI_ViewExecute( q, NULL );
2565     msiobj_release( &q->hdr );
2566     if (r != ERROR_SUCCESS)
2567         return r;
2568 
2569     return TransformView_set_row( view, row, rec, ~0 );
2570 }
2571 
2572 static UINT TransformView_drop_table( MSITABLEVIEW *tv, UINT row )
2573 {
2574     static const WCHAR query_pfx[] = L"INSERT INTO `_TransformView` ( `Table`, `Column` ) VALUES ( '";
2575     static const WCHAR query_sfx[] = L"', 'DROP' )";
2576 
2577     WCHAR buf[256], *query = buf;
2578     UINT r, table_id, len;
2579     const WCHAR *table;
2580     int table_len;
2581     MSIQUERY *q;
2582 
2583     r = TABLE_fetch_int( &tv->view, row, 1, &table_id );
2584     if (r != ERROR_SUCCESS)
2585         return r;
2586 
2587     table = msi_string_lookup( tv->db->strings, table_id, &table_len );
2588     if (!table)
2589         return ERROR_INSTALL_TRANSFORM_FAILURE;
2590 
2591     len = ARRAY_SIZE(query_pfx) - 1 + table_len + ARRAY_SIZE(query_sfx);
2592     if (len > ARRAY_SIZE(buf))
2593     {
2594         query = msi_alloc( len * sizeof(WCHAR) );
2595         if (!query)
2596             return ERROR_OUTOFMEMORY;
2597     }
2598 
2599     memcpy( query, query_pfx, ARRAY_SIZE(query_pfx) * sizeof(WCHAR) );
2600     len = ARRAY_SIZE(query_pfx) - 1;
2601     memcpy( query + len, table, table_len * sizeof(WCHAR) );
2602     len += table_len;
2603     memcpy( query + len, query_sfx, ARRAY_SIZE(query_sfx) * sizeof(WCHAR) );
2604 
2605     r = MSI_DatabaseOpenViewW( tv->db, query, &q );
2606     if (query != buf)
2607         msi_free( query );
2608     if (r != ERROR_SUCCESS)
2609         return r;
2610 
2611     r = MSI_ViewExecute( q, NULL );
2612     msiobj_release( &q->hdr );
2613     return r;
2614 }
2615 
2616 static UINT TransformView_delete_row( MSIVIEW *view, UINT row )
2617 {
2618     static const WCHAR query_pfx[] = L"INSERT INTO `_TransformView` ( `Table`, `Column`, `Row`) VALUES ( '";
2619     static const WCHAR query_column[] = L"', 'DELETE', '";
2620     static const WCHAR query_sfx[] = L"')";
2621 
2622     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
2623     WCHAR *key, buf[256], *query = buf;
2624     UINT r, len, name_len, key_len;
2625     MSIRECORD *rec;
2626     MSIQUERY *q;
2627 
2628     if (!wcscmp( tv->name, szColumns ))
2629     {
2630         ERR("trying to remove column\n");
2631         return ERROR_INSTALL_TRANSFORM_FAILURE;
2632     }
2633 
2634     if (!wcscmp( tv->name, szTables ))
2635         return TransformView_drop_table( tv, row );
2636 
2637     r = msi_view_get_row( tv->db, view, row, &rec );
2638     if (r != ERROR_SUCCESS)
2639         return r;
2640 
2641     key = create_key_string( tv, rec );
2642     msiobj_release( &rec->hdr );
2643     if (!key)
2644         return ERROR_OUTOFMEMORY;
2645 
2646     name_len = wcslen( tv->name );
2647     key_len = wcslen( key );
2648     len = ARRAY_SIZE(query_pfx) + name_len + ARRAY_SIZE(query_column) + key_len + ARRAY_SIZE(query_sfx) - 2;
2649     if (len > ARRAY_SIZE(buf))
2650     {
2651         query = msi_alloc( len * sizeof(WCHAR) );
2652         if (!query)
2653         {
2654             msi_free( tv );
2655             msi_free( key );
2656             return ERROR_OUTOFMEMORY;
2657         }
2658     }
2659 
2660     memcpy( query, query_pfx, ARRAY_SIZE(query_pfx) * sizeof(WCHAR) );
2661     len = ARRAY_SIZE(query_pfx) - 1;
2662     memcpy( query + len, tv->name, name_len * sizeof(WCHAR) );
2663     len += name_len;
2664     memcpy( query + len, query_column, ARRAY_SIZE(query_column) * sizeof(WCHAR) );
2665     len += ARRAY_SIZE(query_column) - 1;
2666     memcpy( query + len, key, key_len * sizeof(WCHAR) );
2667     len += key_len;
2668     memcpy( query + len, query_sfx, ARRAY_SIZE(query_sfx) * sizeof(WCHAR) );
2669     msi_free( key );
2670 
2671     r = MSI_DatabaseOpenViewW( tv->db, query, &q );
2672     if (query != buf)
2673         msi_free( query );
2674     if (r != ERROR_SUCCESS)
2675         return r;
2676 
2677     r = MSI_ViewExecute( q, NULL );
2678     msiobj_release( &q->hdr );
2679     return r;
2680 }
2681 
2682 static UINT TransformView_execute( MSIVIEW *view, MSIRECORD *record )
2683 {
2684     return ERROR_SUCCESS;
2685 }
2686 
2687 static UINT TransformView_close( MSIVIEW *view )
2688 {
2689     return ERROR_SUCCESS;
2690 }
2691 
2692 static UINT TransformView_get_dimensions( MSIVIEW *view, UINT *rows, UINT *cols )
2693 {
2694     return TABLE_get_dimensions( view, rows, cols );
2695 }
2696 
2697 static UINT TransformView_get_column_info( MSIVIEW *view, UINT n, LPCWSTR *name, UINT *type,
2698                              BOOL *temporary, LPCWSTR *table_name )
2699 {
2700     return TABLE_get_column_info( view, n, name, type, temporary, table_name );
2701 }
2702 
2703 static UINT TransformView_delete( MSIVIEW *view )
2704 {
2705     MSITABLEVIEW *tv = (MSITABLEVIEW*)view;
2706     if (!tv->table || tv->columns != tv->table->colinfo)
2707         msi_free( tv->columns );
2708     return TABLE_delete( view );
2709 }
2710 
2711 static const MSIVIEWOPS transform_view_ops =
2712 {
2713     TransformView_fetch_int,
2714     TransformView_fetch_stream,
2715     NULL,
2716     NULL,
2717     NULL,
2718     TransformView_set_row,
2719     TransformView_insert_row,
2720     TransformView_delete_row,
2721     TransformView_execute,
2722     TransformView_close,
2723     TransformView_get_dimensions,
2724     TransformView_get_column_info,
2725     NULL,
2726     TransformView_delete,
2727     NULL,
2728     NULL,
2729     NULL,
2730     NULL,
2731     NULL
2732 };
2733 
2734 UINT TransformView_Create( MSIDATABASE *db, string_table *st, LPCWSTR name, MSIVIEW **view )
2735 {
2736     static const WCHAR query_pfx[] = L"SELECT `Column`, `Data`, `Current` FROM `_TransformView` WHERE `Table`='";
2737     static const WCHAR query_sfx[] = L"' AND `Row` IS NULL AND `Current` IS NOT NULL";
2738 
2739     WCHAR buf[256], *query = buf;
2740     UINT r, len, name_len, size, add_col;
2741     MSICOLUMNINFO *colinfo;
2742     MSITABLEVIEW *tv;
2743     MSIRECORD *rec;
2744     MSIQUERY *q;
2745 
2746     name_len = wcslen( name );
2747 
2748     r = TABLE_CreateView( db, name, view );
2749     if (r == ERROR_INVALID_PARAMETER)
2750     {
2751         /* table does not exist */
2752         size = FIELD_OFFSET( MSITABLEVIEW, name[name_len + 1] );
2753         tv = msi_alloc_zero( size );
2754         if (!tv)
2755             return ERROR_OUTOFMEMORY;
2756 
2757         tv->db = db;
2758         memcpy( tv->name, name, name_len * sizeof(WCHAR) );
2759         *view = (MSIVIEW*)tv;
2760     }
2761     else if (r != ERROR_SUCCESS)
2762     {
2763         return r;
2764     }
2765     else
2766     {
2767         tv = (MSITABLEVIEW*)*view;
2768     }
2769 
2770     tv->view.ops = &transform_view_ops;
2771 
2772     len = ARRAY_SIZE(query_pfx) + name_len + ARRAY_SIZE(query_sfx) - 1;
2773     if (len > ARRAY_SIZE(buf))
2774     {
2775         query = msi_alloc( len * sizeof(WCHAR) );
2776         if (!query)
2777         {
2778             msi_free( tv );
2779             return ERROR_OUTOFMEMORY;
2780         }
2781     }
2782     memcpy( query, query_pfx, ARRAY_SIZE(query_pfx) * sizeof(WCHAR) );
2783     len = ARRAY_SIZE(query_pfx) - 1;
2784     memcpy( query + len, name, name_len * sizeof(WCHAR) );
2785     len += name_len;
2786     memcpy( query + len, query_sfx, ARRAY_SIZE(query_sfx) * sizeof(WCHAR) );
2787 
2788     r = MSI_DatabaseOpenViewW( tv->db, query, &q );
2789     if (query != buf)
2790         msi_free( query );
2791     if (r != ERROR_SUCCESS)
2792     {
2793         msi_free( tv );
2794         return r;
2795     }
2796 
2797     r = MSI_ViewExecute( q, NULL );
2798     if (r != ERROR_SUCCESS)
2799     {
2800         msi_free( tv );
2801         return r;
2802     }
2803 
2804     r = q->view->ops->get_dimensions( q->view, &add_col, NULL );
2805     if (r != ERROR_SUCCESS)
2806     {
2807         MSI_ViewClose( q );
2808         msiobj_release( &q->hdr );
2809         msi_free( tv );
2810         return r;
2811     }
2812     if (!add_col)
2813     {
2814         MSI_ViewClose( q );
2815         msiobj_release( &q->hdr );
2816         return ERROR_SUCCESS;
2817     }
2818 
2819     colinfo = msi_alloc_zero( (add_col + tv->num_cols) * sizeof(*colinfo) );
2820     if (!colinfo)
2821     {
2822         MSI_ViewClose( q );
2823         msiobj_release( &q->hdr );
2824         msi_free( tv );
2825         return r;
2826     }
2827 
2828     while (MSI_ViewFetch( q, &rec ) == ERROR_SUCCESS)
2829     {
2830         int name_len;
2831         const WCHAR *name = msi_record_get_string( rec, 1, &name_len );
2832         const WCHAR *type = msi_record_get_string( rec, 2, NULL );
2833         UINT name_id, idx;
2834 
2835         idx = _wtoi( msi_record_get_string(rec, 3, NULL) );
2836         colinfo[idx - 1].number = idx;
2837         colinfo[idx - 1].type = _wtoi( type );
2838 
2839         r = msi_string2id( st, name, name_len, &name_id );
2840         if (r == ERROR_SUCCESS)
2841             colinfo[idx - 1].colname = msi_string_lookup( st, name_id, NULL );
2842         else
2843             ERR( "column name %s is not defined in strings table\n", wine_dbgstr_w(name) );
2844     }
2845     MSI_ViewClose( q );
2846     msiobj_release( &q->hdr );
2847 
2848     memcpy( colinfo, tv->columns, tv->num_cols * sizeof(*colinfo) );
2849     tv->columns = colinfo;
2850     tv->num_cols += add_col;
2851     return ERROR_SUCCESS;
2852 }
2853 
2854 UINT MSI_CommitTables( MSIDATABASE *db )
2855 {
2856     UINT r, bytes_per_strref;
2857     HRESULT hr;
2858     MSITABLE *table = NULL;
2859 
2860     TRACE("%p\n",db);
2861 
2862     r = msi_save_string_table( db->strings, db->storage, &bytes_per_strref );
2863     if( r != ERROR_SUCCESS )
2864     {
2865         WARN("failed to save string table r=%08x\n",r);
2866         return r;
2867     }
2868 
2869     LIST_FOR_EACH_ENTRY( table, &db->tables, MSITABLE, entry )
2870     {
2871         r = save_table( db, table, bytes_per_strref );
2872         if( r != ERROR_SUCCESS )
2873         {
2874             WARN("failed to save table %s (r=%08x)\n",
2875                   debugstr_w(table->name), r);
2876             return r;
2877         }
2878     }
2879 
2880     hr = IStorage_Commit( db->storage, 0 );
2881     if (FAILED( hr ))
2882     {
2883         WARN("failed to commit changes 0x%08x\n", hr);
2884         r = ERROR_FUNCTION_FAILED;
2885     }
2886     return r;
2887 }
2888 
2889 MSICONDITION MSI_DatabaseIsTablePersistent( MSIDATABASE *db, LPCWSTR table )
2890 {
2891     MSITABLE *t;
2892     UINT r;
2893 
2894     TRACE("%p %s\n", db, debugstr_w(table));
2895 
2896     if (!table)
2897         return MSICONDITION_ERROR;
2898 
2899     r = get_table( db, table, &t );
2900     if (r != ERROR_SUCCESS)
2901         return MSICONDITION_NONE;
2902 
2903     return t->persistent;
2904 }
2905 
2906 static UINT read_raw_int(const BYTE *data, UINT col, UINT bytes)
2907 {
2908     UINT ret = 0, i;
2909 
2910     for (i = 0; i < bytes; i++)
2911         ret += (data[col + i] << i * 8);
2912 
2913     return ret;
2914 }
2915 
2916 static UINT msi_record_encoded_stream_name( const MSITABLEVIEW *tv, MSIRECORD *rec, LPWSTR *pstname )
2917 {
2918     UINT r, len;
2919     WCHAR *name;
2920 
2921     TRACE("%p %p\n", tv, rec);
2922 
2923     r = msi_record_stream_name( tv, rec, NULL, &len );
2924     if (r != ERROR_SUCCESS)
2925         return r;
2926     len++;
2927 
2928     name = msi_alloc( len * sizeof(WCHAR) );
2929     if (!name)
2930         return ERROR_OUTOFMEMORY;
2931 
2932     r = msi_record_stream_name( tv, rec, name, &len );
2933     if (r != ERROR_SUCCESS)
2934     {
2935         msi_free( name );
2936         return r;
2937     }
2938 
2939     *pstname = encode_streamname( FALSE, name );
2940     msi_free( name );
2941     return ERROR_SUCCESS;
2942 }
2943 
2944 static MSIRECORD *msi_get_transform_record( const MSITABLEVIEW *tv, const string_table *st,
2945                                             IStorage *stg, const BYTE *rawdata, UINT bytes_per_strref )
2946 {
2947     UINT i, val, ofs = 0;
2948     USHORT mask;
2949     MSICOLUMNINFO *columns = tv->columns;
2950     MSIRECORD *rec;
2951 
2952     mask = rawdata[0] | (rawdata[1] << 8);
2953     rawdata += 2;
2954 
2955     rec = MSI_CreateRecord( tv->num_cols );
2956     if( !rec )
2957         return rec;
2958 
2959     TRACE("row ->\n");
2960     for( i=0; i<tv->num_cols; i++ )
2961     {
2962         if ( (mask&1) && (i>=(mask>>8)) )
2963             break;
2964         /* all keys must be present */
2965         if ( (~mask&1) && (~columns[i].type & MSITYPE_KEY) && ((1<<i) & ~mask) )
2966             continue;
2967 
2968         if( MSITYPE_IS_BINARY(tv->columns[i].type) )
2969         {
2970             LPWSTR encname;
2971             IStream *stm = NULL;
2972             UINT r;
2973 
2974             ofs += bytes_per_column( tv->db, &columns[i], bytes_per_strref );
2975 
2976             r = msi_record_encoded_stream_name( tv, rec, &encname );
2977             if ( r != ERROR_SUCCESS )
2978             {
2979                 msiobj_release( &rec->hdr );
2980                 return NULL;
2981             }
2982             r = IStorage_OpenStream( stg, encname, NULL, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stm );
2983             if ( r != ERROR_SUCCESS )
2984             {
2985                 msiobj_release( &rec->hdr );
2986                 msi_free( encname );
2987                 return NULL;
2988             }
2989 
2990             MSI_RecordSetStream( rec, i+1, stm );
2991             TRACE(" field %d [%s]\n", i+1, debugstr_w(encname));
2992             msi_free( encname );
2993         }
2994         else if( columns[i].type & MSITYPE_STRING )
2995         {
2996             int len;
2997             const WCHAR *sval;
2998 
2999             val = read_raw_int(rawdata, ofs, bytes_per_strref);
3000             sval = msi_string_lookup( st, val, &len );
3001             msi_record_set_string( rec, i+1, sval, len );
3002             TRACE(" field %d [%s]\n", i+1, debugstr_wn(sval, len));
3003             ofs += bytes_per_strref;
3004         }
3005         else
3006         {
3007             UINT n = bytes_per_column( tv->db, &columns[i], bytes_per_strref );
3008             switch( n )
3009             {
3010             case 2:
3011                 val = read_raw_int(rawdata, ofs, n);
3012                 if (val)
3013                     MSI_RecordSetInteger( rec, i+1, val-0x8000 );
3014                 TRACE(" field %d [0x%04x]\n", i+1, val );
3015                 break;
3016             case 4:
3017                 val = read_raw_int(rawdata, ofs, n);
3018                 if (val)
3019                     MSI_RecordSetInteger( rec, i+1, val^0x80000000 );
3020                 TRACE(" field %d [0x%08x]\n", i+1, val );
3021                 break;
3022             default:
3023                 ERR("oops - unknown column width %d\n", n);
3024                 break;
3025             }
3026             ofs += n;
3027         }
3028     }
3029     return rec;
3030 }
3031 
3032 static void dump_table( const string_table *st, const USHORT *rawdata, UINT rawsize )
3033 {
3034     UINT i;
3035     for (i = 0; i < rawsize / 2; i++)
3036     {
3037         int len;
3038         const WCHAR *sval = msi_string_lookup( st, rawdata[i], &len );
3039         MESSAGE(" %04x %s\n", rawdata[i], debugstr_wn(sval, len) );
3040     }
3041 }
3042 
3043 static UINT* msi_record_to_row( const MSITABLEVIEW *tv, MSIRECORD *rec )
3044 {
3045     UINT i, r, *data;
3046 
3047     data = msi_alloc( tv->num_cols *sizeof (UINT) );
3048     for( i=0; i<tv->num_cols; i++ )
3049     {
3050         data[i] = 0;
3051 
3052         if ( ~tv->columns[i].type & MSITYPE_KEY )
3053             continue;
3054 
3055         /* turn the transform column value into a row value */
3056         if ( ( tv->columns[i].type & MSITYPE_STRING ) &&
3057              ! MSITYPE_IS_BINARY(tv->columns[i].type) )
3058         {
3059             int len;
3060             const WCHAR *str = msi_record_get_string( rec, i+1, &len );
3061             if (str)
3062             {
3063                 r = msi_string2id( tv->db->strings, str, len, &data[i] );
3064 
3065                 /* if there's no matching string in the string table,
3066                    these keys can't match any record, so fail now. */
3067                 if (r != ERROR_SUCCESS)
3068                 {
3069                     msi_free( data );
3070                     return NULL;
3071                 }
3072             }
3073             else data[i] = 0;
3074         }
3075         else
3076         {
3077             if (int_to_table_storage( tv, i + 1, MSI_RecordGetInteger( rec, i + 1 ), &data[i] ))
3078             {
3079                 msi_free( data );
3080                 return NULL;
3081             }
3082         }
3083     }
3084     return data;
3085 }
3086 
3087 static UINT msi_row_matches( MSITABLEVIEW *tv, UINT row, const UINT *data, UINT *column )
3088 {
3089     UINT i, r, x, ret = ERROR_FUNCTION_FAILED;
3090 
3091     for( i=0; i<tv->num_cols; i++ )
3092     {
3093         if ( ~tv->columns[i].type & MSITYPE_KEY )
3094             continue;
3095 
3096         /* turn the transform column value into a row value */
3097         r = TABLE_fetch_int( &tv->view, row, i+1, &x );
3098         if ( r != ERROR_SUCCESS )
3099         {
3100             ERR("TABLE_fetch_int shouldn't fail here\n");
3101             break;
3102         }
3103 
3104         /* if this key matches, move to the next column */
3105         if ( x != data[i] )
3106         {
3107             ret = ERROR_FUNCTION_FAILED;
3108             break;
3109         }
3110         if (column) *column = i;
3111         ret = ERROR_SUCCESS;
3112     }
3113     return ret;
3114 }
3115 
3116 static UINT msi_table_find_row( MSITABLEVIEW *tv, MSIRECORD *rec, UINT *row, UINT *column )
3117 {
3118     UINT i, r = ERROR_FUNCTION_FAILED, *data;
3119 
3120     data = msi_record_to_row( tv, rec );
3121     if( !data )
3122         return r;
3123     for( i = 0; i < tv->table->row_count; i++ )
3124     {
3125         r = msi_row_matches( tv, i, data, column );
3126         if( r == ERROR_SUCCESS )
3127         {
3128             *row = i;
3129             break;
3130         }
3131     }
3132     msi_free( data );
3133     return r;
3134 }
3135 
3136 typedef struct
3137 {
3138     struct list entry;
3139     LPWSTR name;
3140 } TRANSFORMDATA;
3141 
3142 static UINT msi_table_load_transform( MSIDATABASE *db, IStorage *stg,
3143                                       string_table *st, TRANSFORMDATA *transform,
3144                                       UINT bytes_per_strref, int err_cond )
3145 {
3146     BYTE *rawdata = NULL;
3147     MSITABLEVIEW *tv = NULL;
3148     UINT r, n, sz, i, mask, num_cols, colcol = 0, rawsize = 0;
3149     MSIRECORD *rec = NULL;
3150     WCHAR coltable[32];
3151     const WCHAR *name;
3152 
3153     if (!transform)
3154         return ERROR_SUCCESS;
3155 
3156     name = transform->name;
3157 
3158     coltable[0] = 0;
3159     TRACE("%p %p %p %s\n", db, stg, st, debugstr_w(name) );
3160 
3161     /* read the transform data */
3162     read_stream_data( stg, name, TRUE, &rawdata, &rawsize );
3163     if ( !rawdata )
3164     {
3165         TRACE("table %s empty\n", debugstr_w(name) );
3166         return ERROR_INVALID_TABLE;
3167     }
3168 
3169     /* create a table view */
3170     if ( err_cond & MSITRANSFORM_ERROR_VIEWTRANSFORM )
3171         r = TransformView_Create( db, st, name, (MSIVIEW**) &tv );
3172     else
3173         r = TABLE_CreateView( db, name, (MSIVIEW**) &tv );
3174     if( r != ERROR_SUCCESS )
3175         goto err;
3176 
3177     r = tv->view.ops->execute( &tv->view, NULL );
3178     if( r != ERROR_SUCCESS )
3179         goto err;
3180 
3181     TRACE("name = %s columns = %u row_size = %u raw size = %u\n",
3182           debugstr_w(name), tv->num_cols, tv->row_size, rawsize );
3183 
3184     /* interpret the data */
3185     for (n = 0; n < rawsize;)
3186     {
3187         mask = rawdata[n] | (rawdata[n + 1] << 8);
3188         if (mask & 1)
3189         {
3190             /*
3191              * if the low bit is set, columns are continuous and
3192              * the number of columns is specified in the high byte
3193              */
3194             sz = 2;
3195             num_cols = mask >> 8;
3196             if (num_cols > tv->num_cols)
3197             {
3198                 ERR("excess columns in transform: %u > %u\n", num_cols, tv->num_cols);
3199                 break;
3200             }
3201 
3202             for (i = 0; i < num_cols; i++)
3203             {
3204                 if( (tv->columns[i].type & MSITYPE_STRING) &&
3205                     ! MSITYPE_IS_BINARY(tv->columns[i].type) )
3206                     sz += bytes_per_strref;
3207                 else
3208                     sz += bytes_per_column( tv->db, &tv->columns[i], bytes_per_strref );
3209             }
3210         }
3211         else
3212         {
3213             /*
3214              * If the low bit is not set, mask is a bitmask.
3215              * Excepting for key fields, which are always present,
3216              *  each bit indicates that a field is present in the transform record.
3217              *
3218              * mask == 0 is a special case ... only the keys will be present
3219              * and it means that this row should be deleted.
3220              */
3221             sz = 2;
3222             num_cols = tv->num_cols;
3223             for (i = 0; i < num_cols; i++)
3224             {
3225                 if ((tv->columns[i].type & MSITYPE_KEY) || ((1 << i) & mask))
3226                 {
3227                     if ((tv->columns[i].type & MSITYPE_STRING) &&
3228                         !MSITYPE_IS_BINARY(tv->columns[i].type))
3229                         sz += bytes_per_strref;
3230                     else
3231                         sz += bytes_per_column( tv->db, &tv->columns[i], bytes_per_strref );
3232                 }
3233             }
3234         }
3235 
3236         /* check we didn't run of the end of the table */
3237         if (n + sz > rawsize)
3238         {
3239             ERR("borked.\n");
3240             dump_table( st, (USHORT *)rawdata, rawsize );
3241             break;
3242         }
3243 
3244         rec = msi_get_transform_record( tv, st, stg, &rawdata[n], bytes_per_strref );
3245         if (rec)
3246         {
3247             WCHAR table[32];
3248             DWORD sz = 32;
3249             UINT number = MSI_NULL_INTEGER;
3250             UINT row = 0;
3251 
3252             if (!wcscmp( name, szColumns ))
3253             {
3254                 MSI_RecordGetStringW( rec, 1, table, &sz );
3255                 number = MSI_RecordGetInteger( rec, 2 );
3256 
3257                 /*
3258                  * Native msi seems writes nul into the Number (2nd) column of
3259                  * the _Columns table when there are new columns
3260                  */
3261                 if ( number == MSI_NULL_INTEGER )
3262                 {
3263                     /* reset the column number on a new table */
3264                     if (wcscmp( coltable, table ))
3265                     {
3266                         colcol = 0;
3267                         lstrcpyW( coltable, table );
3268                     }
3269 
3270                     /* fix nul column numbers */
3271                     MSI_RecordSetInteger( rec, 2, ++colcol );
3272                 }
3273             }
3274 
3275             if (TRACE_ON(msidb)) dump_record( rec );
3276 
3277             if (tv->table)
3278                 r = msi_table_find_row( tv, rec, &row, NULL );
3279             else
3280                 r = ERROR_FUNCTION_FAILED;
3281             if (r == ERROR_SUCCESS)
3282             {
3283                 if (!mask)
3284                 {
3285                     TRACE("deleting row [%d]:\n", row);
3286                     r = tv->view.ops->delete_row( &tv->view, row );
3287                     if (r != ERROR_SUCCESS)
3288                         WARN("failed to delete row %u\n", r);
3289                 }
3290                 else if (mask & 1)
3291                 {
3292                     TRACE("modifying full row [%d]:\n", row);
3293                     r = tv->view.ops->set_row( &tv->view, row, rec, (1 << tv->num_cols) - 1 );
3294                     if (r != ERROR_SUCCESS)
3295                         WARN("failed to modify row %u\n", r);
3296                 }
3297                 else
3298                 {
3299                     TRACE("modifying masked row [%d]:\n", row);
3300                     r = tv->view.ops->set_row( &tv->view, row, rec, mask );
3301                     if (r != ERROR_SUCCESS)
3302                         WARN("failed to modify row %u\n", r);
3303                 }
3304             }
3305             else
3306             {
3307                 TRACE("inserting row\n");
3308                 r = tv->view.ops->insert_row( &tv->view, rec, -1, FALSE );
3309                 if (r != ERROR_SUCCESS)
3310                     WARN("failed to insert row %u\n", r);
3311             }
3312 
3313             if (!(err_cond & MSITRANSFORM_ERROR_VIEWTRANSFORM) &&
3314                     !wcscmp( name, szColumns ))
3315                 msi_update_table_columns( db, table );
3316 
3317             msiobj_release( &rec->hdr );
3318         }
3319 
3320         n += sz;
3321     }
3322 
3323 err:
3324     /* no need to free the table, it's associated with the database */
3325     msi_free( rawdata );
3326     if( tv )
3327         tv->view.ops->delete( &tv->view );
3328 
3329     return ERROR_SUCCESS;
3330 }
3331 
3332 /*
3333  * msi_table_apply_transform
3334  *
3335  * Enumerate the table transforms in a transform storage and apply each one.
3336  */
3337 UINT msi_table_apply_transform( MSIDATABASE *db, IStorage *stg, int err_cond )
3338 {
3339     struct list transforms;
3340     IEnumSTATSTG *stgenum = NULL;
3341     TRANSFORMDATA *transform;
3342     TRANSFORMDATA *tables = NULL, *columns = NULL;
3343     HRESULT hr;
3344     STATSTG stat;
3345     string_table *strings;
3346     UINT ret = ERROR_FUNCTION_FAILED;
3347     UINT bytes_per_strref;
3348     BOOL property_update = FALSE;
3349     BOOL free_transform_view = FALSE;
3350 
3351     TRACE("%p %p\n", db, stg );
3352 
3353     strings = msi_load_string_table( stg, &bytes_per_strref );
3354     if( !strings )
3355         goto end;
3356 
3357     hr = IStorage_EnumElements( stg, 0, NULL, 0, &stgenum );
3358     if (FAILED( hr ))
3359         goto end;
3360 
3361     list_init(&transforms);
3362 
3363     while ( TRUE )
3364     {
3365         MSITABLEVIEW *tv = NULL;
3366         WCHAR name[0x40];
3367         ULONG count = 0;
3368 
3369         hr = IEnumSTATSTG_Next( stgenum, 1, &stat, &count );
3370         if (FAILED( hr ) || !count)
3371             break;
3372 
3373         decode_streamname( stat.pwcsName, name );
3374         CoTaskMemFree( stat.pwcsName );
3375         if ( name[0] != 0x4840 )
3376             continue;
3377 
3378         if ( !wcscmp( name+1, szStringPool ) ||
3379              !wcscmp( name+1, szStringData ) )
3380             continue;
3381 
3382         transform = msi_alloc_zero( sizeof(TRANSFORMDATA) );
3383         if ( !transform )
3384             break;
3385 
3386         list_add_tail( &transforms, &transform->entry );
3387 
3388         transform->name = strdupW( name + 1 );
3389 
3390         if ( !wcscmp( transform->name, szTables ) )
3391             tables = transform;
3392         else if (!wcscmp( transform->name, szColumns ) )
3393             columns = transform;
3394         else if (!wcscmp( transform->name, szProperty ))
3395             property_update = TRUE;
3396 
3397         TRACE("transform contains stream %s\n", debugstr_w(name));
3398 
3399         /* load the table */
3400         if (TABLE_CreateView( db, transform->name, (MSIVIEW**) &tv ) != ERROR_SUCCESS)
3401             continue;
3402 
3403         if (tv->view.ops->execute( &tv->view, NULL ) != ERROR_SUCCESS)
3404         {
3405             tv->view.ops->delete( &tv->view );
3406             continue;
3407         }
3408 
3409         tv->view.ops->delete( &tv->view );
3410     }
3411 
3412     if (err_cond & MSITRANSFORM_ERROR_VIEWTRANSFORM)
3413     {
3414         static const WCHAR create_query[] = L"CREATE TABLE `_TransformView` ( "
3415             L"`Table` CHAR(0) NOT NULL TEMPORARY, `Column` CHAR(0) NOT NULL TEMPORARY, "
3416             L"`Row` CHAR(0) TEMPORARY, `Data` CHAR(0) TEMPORARY, `Current` CHAR(0) TEMPORARY "
3417             L"PRIMARY KEY `Table`, `Column`, `Row` ) HOLD";
3418         MSIQUERY *query;
3419         UINT r;
3420 
3421         r = MSI_DatabaseOpenViewW( db, create_query, &query );
3422         if (r != ERROR_SUCCESS)
3423             goto end;
3424 
3425         r = MSI_ViewExecute( query, NULL );
3426         if (r == ERROR_SUCCESS)
3427             MSI_ViewClose( query );
3428         msiobj_release( &query->hdr );
3429         if (r == ERROR_BAD_QUERY_SYNTAX)
3430             FIXME( "support adding to _TransformView\n" );
3431         if (r != ERROR_SUCCESS)
3432             goto end;
3433         free_transform_view = TRUE;
3434     }
3435 
3436     /*
3437      * Apply _Tables and _Columns transforms first so that
3438      * the table metadata is correct, and empty tables exist.
3439      */
3440     ret = msi_table_load_transform( db, stg, strings, tables, bytes_per_strref, err_cond );
3441     if (ret != ERROR_SUCCESS && ret != ERROR_INVALID_TABLE)
3442         goto end;
3443 
3444     ret = msi_table_load_transform( db, stg, strings, columns, bytes_per_strref, err_cond );
3445     if (ret != ERROR_SUCCESS && ret != ERROR_INVALID_TABLE)
3446         goto end;
3447 
3448     ret = ERROR_SUCCESS;
3449 
3450     while ( !list_empty( &transforms ) )
3451     {
3452         transform = LIST_ENTRY( list_head( &transforms ), TRANSFORMDATA, entry );
3453 
3454         if ( wcscmp( transform->name, szColumns ) &&
3455              wcscmp( transform->name, szTables ) &&
3456              ret == ERROR_SUCCESS )
3457         {
3458             ret = msi_table_load_transform( db, stg, strings, transform, bytes_per_strref, err_cond );
3459         }
3460 
3461         list_remove( &transform->entry );
3462         msi_free( transform->name );
3463         msi_free( transform );
3464     }
3465 
3466     if ( ret == ERROR_SUCCESS )
3467     {
3468         append_storage_to_db( db, stg );
3469         if (property_update) msi_clone_properties( db );
3470     }
3471 
3472 end:
3473     if ( stgenum )
3474         IEnumSTATSTG_Release( stgenum );
3475     if ( strings )
3476         msi_destroy_stringtable( strings );
3477     if (ret != ERROR_SUCCESS && free_transform_view)
3478     {
3479         MSIQUERY *query;
3480         if (MSI_DatabaseOpenViewW( db, L"ALTER TABLE `_TransformView` FREE",
3481                     &query ) == ERROR_SUCCESS)
3482         {
3483             if (MSI_ViewExecute( query, NULL ) == ERROR_SUCCESS)
3484                 MSI_ViewClose( query );
3485             msiobj_release( &query->hdr );
3486         }
3487     }
3488 
3489     return ret;
3490 }
3491