2 * Implementation of the Microsoft Installer (msi.dll)
4 * Copyright 2002-2005 Mike McCormack for CodeWeavers
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.
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.
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
28 #include "wine/debug.h"
29 #include "wine/unicode.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(msi
);
43 static void MSI_CloseView( MSIOBJECTHDR
*arg
)
45 MSIQUERY
*query
= (MSIQUERY
*) arg
;
48 if( query
->view
&& query
->view
->ops
->delete )
49 query
->view
->ops
->delete( query
->view
);
50 msiobj_release( &query
->db
->hdr
);
52 LIST_FOR_EACH_SAFE( ptr
, t
, &query
->mem
)
58 UINT
VIEW_find_column( MSIVIEW
*table
, LPCWSTR name
, UINT
*n
)
63 r
= table
->ops
->get_dimensions( table
, NULL
, &count
);
64 if( r
!= ERROR_SUCCESS
)
67 for( i
=1; i
<=count
; i
++ )
72 r
= table
->ops
->get_column_info( table
, i
, &col_name
, NULL
);
73 if( r
!= ERROR_SUCCESS
)
75 x
= lstrcmpW( name
, col_name
);
84 return ERROR_INVALID_PARAMETER
;
87 UINT WINAPI
MsiDatabaseOpenViewA(MSIHANDLE hdb
,
88 LPCSTR szQuery
, MSIHANDLE
*phView
)
93 TRACE("%ld %s %p\n", hdb
, debugstr_a(szQuery
), phView
);
97 szwQuery
= strdupAtoW( szQuery
);
99 return ERROR_FUNCTION_FAILED
;
104 r
= MsiDatabaseOpenViewW( hdb
, szwQuery
, phView
);
106 msi_free( szwQuery
);
110 UINT
MSI_DatabaseOpenViewW(MSIDATABASE
*db
,
111 LPCWSTR szQuery
, MSIQUERY
**pView
)
116 TRACE("%s %p\n", debugstr_w(szQuery
), pView
);
119 return ERROR_INVALID_PARAMETER
;
121 /* pre allocate a handle to hold a pointer to the view */
122 query
= alloc_msiobject( MSIHANDLETYPE_VIEW
, sizeof (MSIQUERY
),
125 return ERROR_FUNCTION_FAILED
;
127 msiobj_addref( &db
->hdr
);
131 list_init( &query
->mem
);
133 r
= MSI_ParseSQL( db
, szQuery
, &query
->view
, &query
->mem
);
134 if( r
== ERROR_SUCCESS
)
136 msiobj_addref( &query
->hdr
);
140 msiobj_release( &query
->hdr
);
144 UINT
MSI_OpenQuery( MSIDATABASE
*db
, MSIQUERY
**view
, LPCWSTR fmt
, ... )
150 /* construct the string */
154 query
= msi_alloc( size
*sizeof(WCHAR
) );
156 res
= vsnprintfW(query
, size
, fmt
, va
);
158 if (res
== -1) size
*= 2;
159 else if (res
>= size
) size
= res
+ 1;
163 /* perform the query */
164 r
= MSI_DatabaseOpenViewW(db
, query
, view
);
169 UINT
MSI_IterateRecords( MSIQUERY
*view
, LPDWORD count
,
170 record_func func
, LPVOID param
)
172 MSIRECORD
*rec
= NULL
;
173 UINT r
, n
= 0, max
= 0;
175 r
= MSI_ViewExecute( view
, NULL
);
176 if( r
!= ERROR_SUCCESS
)
182 /* iterate a query */
183 for( n
= 0; (max
== 0) || (n
< max
); n
++ )
185 r
= MSI_ViewFetch( view
, &rec
);
186 if( r
!= ERROR_SUCCESS
)
189 r
= func( rec
, param
);
190 msiobj_release( &rec
->hdr
);
191 if( r
!= ERROR_SUCCESS
)
195 MSI_ViewClose( view
);
200 if( r
== ERROR_NO_MORE_ITEMS
)
206 /* return a single record from a query */
207 MSIRECORD
*MSI_QueryGetRecord( MSIDATABASE
*db
, LPCWSTR fmt
, ... )
209 MSIRECORD
*rec
= NULL
;
210 MSIQUERY
*view
= NULL
;
215 /* construct the string */
219 query
= msi_alloc( size
*sizeof(WCHAR
) );
221 res
= vsnprintfW(query
, size
, fmt
, va
);
223 if (res
== -1) size
*= 2;
224 else if (res
>= size
) size
= res
+ 1;
228 /* perform the query */
229 r
= MSI_DatabaseOpenViewW(db
, query
, &view
);
232 if( r
== ERROR_SUCCESS
)
234 MSI_ViewExecute( view
, NULL
);
235 MSI_ViewFetch( view
, &rec
);
236 MSI_ViewClose( view
);
237 msiobj_release( &view
->hdr
);
242 UINT WINAPI
MsiDatabaseOpenViewW(MSIHANDLE hdb
,
243 LPCWSTR szQuery
, MSIHANDLE
*phView
)
246 MSIQUERY
*query
= NULL
;
249 TRACE("%s %p\n", debugstr_w(szQuery
), phView
);
251 db
= msihandle2msiinfo( hdb
, MSIHANDLETYPE_DATABASE
);
253 return ERROR_INVALID_HANDLE
;
255 ret
= MSI_DatabaseOpenViewW( db
, szQuery
, &query
);
256 if( ret
== ERROR_SUCCESS
)
258 *phView
= alloc_msihandle( &query
->hdr
);
260 ret
= ERROR_NOT_ENOUGH_MEMORY
;
261 msiobj_release( &query
->hdr
);
263 msiobj_release( &db
->hdr
);
268 UINT
msi_view_get_row(MSIDATABASE
*db
, MSIVIEW
*view
, UINT row
, MSIRECORD
**rec
)
270 UINT row_count
= 0, col_count
= 0, i
, ival
, ret
, type
;
272 TRACE("%p %p %d %p\n", db
, view
, row
, rec
);
274 ret
= view
->ops
->get_dimensions(view
, &row_count
, &col_count
);
279 return ERROR_INVALID_PARAMETER
;
281 if (row
>= row_count
)
282 return ERROR_NO_MORE_ITEMS
;
284 *rec
= MSI_CreateRecord(col_count
);
286 return ERROR_FUNCTION_FAILED
;
288 for (i
= 1; i
<= col_count
; i
++)
290 ret
= view
->ops
->get_column_info(view
, i
, NULL
, &type
);
293 ERR("Error getting column type for %d\n", i
);
297 if (MSITYPE_IS_BINARY(type
))
301 ret
= view
->ops
->fetch_stream(view
, row
, i
, &stm
);
302 if ((ret
== ERROR_SUCCESS
) && stm
)
304 MSI_RecordSetIStream(*rec
, i
, stm
);
305 IStream_Release(stm
);
308 ERR("failed to get stream\n");
313 ret
= view
->ops
->fetch_int(view
, row
, i
, &ival
);
316 ERR("Error fetching data for %d\n", i
);
320 if (! (type
& MSITYPE_VALID
))
321 ERR("Invalid type!\n");
323 /* check if it's nul (0) - if so, don't set anything */
327 if (type
& MSITYPE_STRING
)
331 sval
= msi_string_lookup_id(db
->strings
, ival
);
332 MSI_RecordSetStringW(*rec
, i
, sval
);
336 if ((type
& MSI_DATASIZEMASK
) == 2)
337 MSI_RecordSetInteger(*rec
, i
, ival
- (1<<15));
339 MSI_RecordSetInteger(*rec
, i
, ival
- (1<<31));
343 return ERROR_SUCCESS
;
346 UINT
MSI_ViewFetch(MSIQUERY
*query
, MSIRECORD
**prec
)
351 TRACE("%p %p\n", query
, prec
);
355 return ERROR_FUNCTION_FAILED
;
357 r
= msi_view_get_row(query
->db
, view
, query
->row
, prec
);
358 if (r
== ERROR_SUCCESS
)
364 UINT WINAPI
MsiViewFetch(MSIHANDLE hView
, MSIHANDLE
*record
)
367 MSIRECORD
*rec
= NULL
;
370 TRACE("%ld %p\n", hView
, record
);
373 return ERROR_INVALID_PARAMETER
;
376 query
= msihandle2msiinfo( hView
, MSIHANDLETYPE_VIEW
);
378 return ERROR_INVALID_HANDLE
;
379 ret
= MSI_ViewFetch( query
, &rec
);
380 if( ret
== ERROR_SUCCESS
)
382 *record
= alloc_msihandle( &rec
->hdr
);
384 ret
= ERROR_NOT_ENOUGH_MEMORY
;
385 msiobj_release( &rec
->hdr
);
387 msiobj_release( &query
->hdr
);
391 UINT
MSI_ViewClose(MSIQUERY
*query
)
395 TRACE("%p\n", query
);
399 return ERROR_FUNCTION_FAILED
;
400 if( !view
->ops
->close
)
401 return ERROR_FUNCTION_FAILED
;
403 return view
->ops
->close( view
);
406 UINT WINAPI
MsiViewClose(MSIHANDLE hView
)
411 TRACE("%ld\n", hView
);
413 query
= msihandle2msiinfo( hView
, MSIHANDLETYPE_VIEW
);
415 return ERROR_INVALID_HANDLE
;
417 ret
= MSI_ViewClose( query
);
418 msiobj_release( &query
->hdr
);
422 UINT
MSI_ViewExecute(MSIQUERY
*query
, MSIRECORD
*rec
)
426 TRACE("%p %p\n", query
, rec
);
430 return ERROR_FUNCTION_FAILED
;
431 if( !view
->ops
->execute
)
432 return ERROR_FUNCTION_FAILED
;
435 return view
->ops
->execute( view
, rec
);
438 UINT WINAPI
MsiViewExecute(MSIHANDLE hView
, MSIHANDLE hRec
)
441 MSIRECORD
*rec
= NULL
;
444 TRACE("%ld %ld\n", hView
, hRec
);
446 query
= msihandle2msiinfo( hView
, MSIHANDLETYPE_VIEW
);
448 return ERROR_INVALID_HANDLE
;
452 rec
= msihandle2msiinfo( hRec
, MSIHANDLETYPE_RECORD
);
455 ret
= ERROR_INVALID_HANDLE
;
460 msiobj_lock( &rec
->hdr
);
461 ret
= MSI_ViewExecute( query
, rec
);
462 msiobj_unlock( &rec
->hdr
);
465 msiobj_release( &query
->hdr
);
467 msiobj_release( &rec
->hdr
);
472 static UINT
msi_set_record_type_string( MSIRECORD
*rec
, UINT field
, UINT type
)
474 static const WCHAR fmt
[] = { '%','d',0 };
477 if (MSITYPE_IS_BINARY(type
))
479 else if (type
& MSITYPE_LOCALIZABLE
)
481 else if (type
& MSITYPE_STRING
)
485 if (type
& MSITYPE_NULLABLE
)
488 sprintfW( &szType
[1], fmt
, (type
&0xff) );
490 TRACE("type %04x -> %s\n", type
, debugstr_w(szType
) );
492 return MSI_RecordSetStringW( rec
, field
, szType
);
495 UINT
MSI_ViewGetColumnInfo( MSIQUERY
*query
, MSICOLINFO info
, MSIRECORD
**prec
)
497 UINT r
= ERROR_FUNCTION_FAILED
, i
, count
= 0, type
;
499 MSIVIEW
*view
= query
->view
;
503 return ERROR_FUNCTION_FAILED
;
505 if( !view
->ops
->get_dimensions
)
506 return ERROR_FUNCTION_FAILED
;
508 r
= view
->ops
->get_dimensions( view
, NULL
, &count
);
509 if( r
!= ERROR_SUCCESS
)
512 return ERROR_INVALID_PARAMETER
;
514 rec
= MSI_CreateRecord( count
);
516 return ERROR_FUNCTION_FAILED
;
518 for( i
=0; i
<count
; i
++ )
521 r
= view
->ops
->get_column_info( view
, i
+1, &name
, &type
);
522 if( r
!= ERROR_SUCCESS
)
524 if (info
== MSICOLINFO_NAMES
)
525 MSI_RecordSetStringW( rec
, i
+1, name
);
527 msi_set_record_type_string( rec
, i
+1, type
);
532 return ERROR_SUCCESS
;
535 UINT WINAPI
MsiViewGetColumnInfo(MSIHANDLE hView
, MSICOLINFO info
, MSIHANDLE
*hRec
)
537 MSIQUERY
*query
= NULL
;
538 MSIRECORD
*rec
= NULL
;
541 TRACE("%ld %d %p\n", hView
, info
, hRec
);
544 return ERROR_INVALID_PARAMETER
;
546 if( info
!= MSICOLINFO_NAMES
&& info
!= MSICOLINFO_TYPES
)
547 return ERROR_INVALID_PARAMETER
;
549 query
= msihandle2msiinfo( hView
, MSIHANDLETYPE_VIEW
);
551 return ERROR_INVALID_HANDLE
;
553 r
= MSI_ViewGetColumnInfo( query
, info
, &rec
);
554 if ( r
== ERROR_SUCCESS
)
556 *hRec
= alloc_msihandle( &rec
->hdr
);
558 r
= ERROR_NOT_ENOUGH_MEMORY
;
559 msiobj_release( &rec
->hdr
);
562 msiobj_release( &query
->hdr
);
567 UINT
MSI_ViewModify( MSIQUERY
*query
, MSIMODIFY mode
, MSIRECORD
*rec
)
569 MSIVIEW
*view
= NULL
;
571 if ( !query
|| !rec
)
572 return ERROR_INVALID_HANDLE
;
575 if ( !view
|| !view
->ops
->modify
)
576 return ERROR_FUNCTION_FAILED
;
578 return view
->ops
->modify( view
, mode
, rec
, query
->row
);
581 UINT WINAPI
MsiViewModify( MSIHANDLE hView
, MSIMODIFY eModifyMode
,
584 MSIQUERY
*query
= NULL
;
585 MSIRECORD
*rec
= NULL
;
586 UINT r
= ERROR_FUNCTION_FAILED
;
588 TRACE("%ld %x %ld\n", hView
, eModifyMode
, hRecord
);
590 query
= msihandle2msiinfo( hView
, MSIHANDLETYPE_VIEW
);
592 return ERROR_INVALID_HANDLE
;
594 rec
= msihandle2msiinfo( hRecord
, MSIHANDLETYPE_RECORD
);
595 r
= MSI_ViewModify( query
, eModifyMode
, rec
);
597 msiobj_release( &query
->hdr
);
599 msiobj_release( &rec
->hdr
);
604 MSIDBERROR WINAPI
MsiViewGetErrorW( MSIHANDLE handle
, LPWSTR szColumnNameBuffer
,
607 MSIQUERY
*query
= NULL
;
608 static const WCHAR szError
[] = { 0 };
609 MSIDBERROR r
= MSIDBERROR_NOERROR
;
612 FIXME("%ld %p %p - returns empty error string\n",
613 handle
, szColumnNameBuffer
, pcchBuf
);
616 return MSIDBERROR_INVALIDARG
;
618 query
= msihandle2msiinfo( handle
, MSIHANDLETYPE_VIEW
);
620 return MSIDBERROR_INVALIDARG
;
622 len
= lstrlenW( szError
);
623 if( szColumnNameBuffer
)
626 lstrcpyW( szColumnNameBuffer
, szError
);
628 r
= MSIDBERROR_MOREDATA
;
632 msiobj_release( &query
->hdr
);
636 MSIDBERROR WINAPI
MsiViewGetErrorA( MSIHANDLE handle
, LPSTR szColumnNameBuffer
,
639 static const CHAR szError
[] = { 0 };
640 MSIQUERY
*query
= NULL
;
641 MSIDBERROR r
= MSIDBERROR_NOERROR
;
644 FIXME("%ld %p %p - returns empty error string\n",
645 handle
, szColumnNameBuffer
, pcchBuf
);
648 return MSIDBERROR_INVALIDARG
;
650 query
= msihandle2msiinfo( handle
, MSIHANDLETYPE_VIEW
);
652 return MSIDBERROR_INVALIDARG
;
654 len
= lstrlenA( szError
);
655 if( szColumnNameBuffer
)
658 lstrcpyA( szColumnNameBuffer
, szError
);
660 r
= MSIDBERROR_MOREDATA
;
664 msiobj_release( &query
->hdr
);
668 MSIHANDLE WINAPI
MsiGetLastErrorRecord( void )
674 DEFINE_GUID( CLSID_MsiTransform
, 0x000c1082, 0x0000, 0x0000, 0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);
676 UINT
MSI_DatabaseApplyTransformW( MSIDATABASE
*db
,
677 LPCWSTR szTransformFile
, int iErrorCond
)
680 UINT ret
= ERROR_FUNCTION_FAILED
;
681 IStorage
*stg
= NULL
;
684 TRACE("%p %s %d\n", db
, debugstr_w(szTransformFile
), iErrorCond
);
686 r
= StgOpenStorage( szTransformFile
, NULL
,
687 STGM_DIRECT
|STGM_READ
|STGM_SHARE_DENY_WRITE
, NULL
, 0, &stg
);
691 r
= IStorage_Stat( stg
, &stat
, STATFLAG_NONAME
);
695 if ( !IsEqualGUID( &stat
.clsid
, &CLSID_MsiTransform
) )
698 if( TRACE_ON( msi
) )
699 enum_stream_names( stg
);
701 ret
= msi_table_apply_transform( db
, stg
);
704 IStorage_Release( stg
);
709 UINT WINAPI
MsiDatabaseApplyTransformW( MSIHANDLE hdb
,
710 LPCWSTR szTransformFile
, int iErrorCond
)
715 db
= msihandle2msiinfo( hdb
, MSIHANDLETYPE_DATABASE
);
717 return ERROR_INVALID_HANDLE
;
719 r
= MSI_DatabaseApplyTransformW( db
, szTransformFile
, iErrorCond
);
720 msiobj_release( &db
->hdr
);
724 UINT WINAPI
MsiDatabaseApplyTransformA( MSIHANDLE hdb
,
725 LPCSTR szTransformFile
, int iErrorCond
)
730 TRACE("%ld %s %d\n", hdb
, debugstr_a(szTransformFile
), iErrorCond
);
732 wstr
= strdupAtoW( szTransformFile
);
733 if( szTransformFile
&& !wstr
)
734 return ERROR_NOT_ENOUGH_MEMORY
;
736 ret
= MsiDatabaseApplyTransformW( hdb
, wstr
, iErrorCond
);
743 UINT WINAPI
MsiDatabaseGenerateTransformA( MSIHANDLE hdb
, MSIHANDLE hdbref
,
744 LPCSTR szTransformFile
, int iReserved1
, int iReserved2
)
746 FIXME("%ld %ld %s %d %d\n", hdb
, hdbref
,
747 debugstr_a(szTransformFile
), iReserved1
, iReserved2
);
748 return ERROR_CALL_NOT_IMPLEMENTED
;
751 UINT WINAPI
MsiDatabaseGenerateTransformW( MSIHANDLE hdb
, MSIHANDLE hdbref
,
752 LPCWSTR szTransformFile
, int iReserved1
, int iReserved2
)
754 FIXME("%ld %ld %s %d %d\n", hdb
, hdbref
,
755 debugstr_w(szTransformFile
), iReserved1
, iReserved2
);
756 return ERROR_CALL_NOT_IMPLEMENTED
;
759 UINT WINAPI
MsiDatabaseCommit( MSIHANDLE hdb
)
766 db
= msihandle2msiinfo( hdb
, MSIHANDLETYPE_DATABASE
);
768 return ERROR_INVALID_HANDLE
;
770 /* FIXME: lock the database */
772 r
= MSI_CommitTables( db
);
774 /* FIXME: unlock the database */
776 msiobj_release( &db
->hdr
);
778 if (r
== ERROR_SUCCESS
)
780 msi_free( db
->deletefile
);
781 db
->deletefile
= NULL
;
787 struct msi_primary_key_record_info
793 static UINT
msi_primary_key_iterator( MSIRECORD
*rec
, LPVOID param
)
795 struct msi_primary_key_record_info
*info
= param
;
799 type
= MSI_RecordGetInteger( rec
, 4 );
800 if( type
& MSITYPE_KEY
)
805 name
= MSI_RecordGetString( rec
, 3 );
806 MSI_RecordSetStringW( info
->rec
, info
->n
, name
);
810 return ERROR_SUCCESS
;
813 UINT
MSI_DatabaseGetPrimaryKeys( MSIDATABASE
*db
,
814 LPCWSTR table
, MSIRECORD
**prec
)
816 static const WCHAR sql
[] = {
817 's','e','l','e','c','t',' ','*',' ',
818 'f','r','o','m',' ','`','_','C','o','l','u','m','n','s','`',' ',
819 'w','h','e','r','e',' ',
820 '`','T','a','b','l','e','`',' ','=',' ','\'','%','s','\'',0 };
821 struct msi_primary_key_record_info info
;
822 MSIQUERY
*query
= NULL
;
826 r
= MSI_OpenQuery( db
, &query
, sql
, table
);
827 if( r
!= ERROR_SUCCESS
)
832 /* count the number of primary key records */
835 r
= MSI_IterateRecords( query
, 0, msi_primary_key_iterator
, &info
);
836 if( r
== ERROR_SUCCESS
)
838 TRACE("Found %d primary keys\n", info
.n
);
840 /* allocate a record and fill in the names of the tables */
841 info
.rec
= MSI_CreateRecord( info
.n
);
843 r
= MSI_IterateRecords( query
, 0, msi_primary_key_iterator
, &info
);
844 if( r
== ERROR_SUCCESS
)
847 msiobj_release( &info
.rec
->hdr
);
849 msiobj_release( &query
->hdr
);
854 UINT WINAPI
MsiDatabaseGetPrimaryKeysW( MSIHANDLE hdb
,
855 LPCWSTR table
, MSIHANDLE
* phRec
)
857 MSIRECORD
*rec
= NULL
;
861 TRACE("%ld %s %p\n", hdb
, debugstr_w(table
), phRec
);
863 db
= msihandle2msiinfo( hdb
, MSIHANDLETYPE_DATABASE
);
865 return ERROR_INVALID_HANDLE
;
867 r
= MSI_DatabaseGetPrimaryKeys( db
, table
, &rec
);
868 if( r
== ERROR_SUCCESS
)
870 *phRec
= alloc_msihandle( &rec
->hdr
);
872 r
= ERROR_NOT_ENOUGH_MEMORY
;
873 msiobj_release( &rec
->hdr
);
875 msiobj_release( &db
->hdr
);
880 UINT WINAPI
MsiDatabaseGetPrimaryKeysA(MSIHANDLE hdb
,
881 LPCSTR table
, MSIHANDLE
* phRec
)
883 LPWSTR szwTable
= NULL
;
886 TRACE("%ld %s %p\n", hdb
, debugstr_a(table
), phRec
);
890 szwTable
= strdupAtoW( table
);
892 return ERROR_OUTOFMEMORY
;
894 r
= MsiDatabaseGetPrimaryKeysW( hdb
, szwTable
, phRec
);
895 msi_free( szwTable
);
900 MSICONDITION WINAPI
MsiDatabaseIsTablePersistentA(
901 MSIHANDLE hDatabase
, LPCSTR szTableName
)
903 LPWSTR szwTableName
= NULL
;
906 TRACE("%lx %s\n", hDatabase
, debugstr_a(szTableName
));
910 szwTableName
= strdupAtoW( szTableName
);
912 return MSICONDITION_ERROR
;
914 r
= MsiDatabaseIsTablePersistentW( hDatabase
, szwTableName
);
915 msi_free( szwTableName
);
920 MSICONDITION WINAPI
MsiDatabaseIsTablePersistentW(
921 MSIHANDLE hDatabase
, LPCWSTR szTableName
)
926 TRACE("%lx %s\n", hDatabase
, debugstr_w(szTableName
));
928 db
= msihandle2msiinfo( hDatabase
, MSIHANDLETYPE_DATABASE
);
930 return MSICONDITION_ERROR
;
932 r
= MSI_DatabaseIsTablePersistent( db
, szTableName
);
934 msiobj_release( &db
->hdr
);