aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Msi/View.cs
blob: d6542824677dfc852dea27ed6e8c5f708822425d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

namespace WixToolset.Msi
{
    using System;
    using System.ComponentModel;
    using System.Globalization;
    using WixToolset.Core.Native;

    /// <summary>
    /// Enumeration of different modify modes.
    /// </summary>
    public enum ModifyView
    {
        /// <summary>
        /// Writes current data in the cursor to a table row. Updates record if the primary 
        /// keys match an existing row and inserts if they do not match. Fails with a read-only 
        /// database. This mode cannot be used with a view containing joins.
        /// </summary>
        Assign = MsiInterop.MSIMODIFYASSIGN,

        /// <summary>
        /// Remove a row from the table. You must first call the Fetch function with the same
        /// record. Fails if the row has been deleted. Works only with read-write records. This
        /// mode cannot be used with a view containing joins.
        /// </summary>
        Delete = MsiInterop.MSIMODIFYDELETE,

        /// <summary>
        /// Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only
        /// database. This mode cannot be used with a view containing joins.
        /// </summary>
        Insert = MsiInterop.MSIMODIFYINSERT,

        /// <summary>
        /// Inserts a temporary record. The information is not persistent. Fails if a row with the 
        /// same primary key exists. Works only with read-write records. This mode cannot be 
        /// used with a view containing joins.
        /// </summary>
        InsertTemporary = MsiInterop.MSIMODIFYINSERTTEMPORARY,

        /// <summary>
        /// Inserts or validates a record in a table. Inserts if primary keys do not match any row
        /// and validates if there is a match. Fails if the record does not match the data in
        /// the table. Fails if there is a record with a duplicate key that is not identical.
        /// Works only with read-write records. This mode cannot be used with a view containing joins.
        /// </summary>
        Merge = MsiInterop.MSIMODIFYMERGE,

        /// <summary>
        /// Refreshes the information in the record. Must first call Fetch with the
        /// same record. Fails for a deleted row. Works with read-write and read-only records.
        /// </summary>
        Refresh = MsiInterop.MSIMODIFYREFRESH,

        /// <summary>
        /// Updates or deletes and inserts a record into a table. Must first call Fetch with
        /// the same record. Updates record if the primary keys are unchanged. Deletes old row and
        /// inserts new if primary keys have changed. Fails with a read-only database. This mode cannot
        /// be used with a view containing joins.
        /// </summary>
        Replace = MsiInterop.MSIMODIFYREPLACE,

        /// <summary>
        /// Refreshes the information in the supplied record without changing the position in the
        /// result set and without affecting subsequent fetch operations. The record may then
        /// be used for subsequent Update, Delete, and Refresh. All primary key columns of the
        /// table must be in the query and the record must have at least as many fields as the
        /// query. Seek cannot be used with multi-table queries. This mode cannot be used with
        /// a view containing joins. See also the remarks.
        /// </summary>
        Seek = MsiInterop.MSIMODIFYSEEK,

        /// <summary>
        /// Updates an existing record. Non-primary keys only. Must first call Fetch. Fails with a
        /// deleted record. Works only with read-write records.
        /// </summary>
        Update = MsiInterop.MSIMODIFYUPDATE
    }

    /// <summary>
    /// Wrapper class for MSI API views.
    /// </summary>
    internal sealed class View : MsiHandle
    {
        /// <summary>
        /// Constructor that creates a view given a database handle and a query.
        /// </summary>
        /// <param name="db">Handle to the database to run the query on.</param>
        /// <param name="query">Query to be executed.</param>
        public View(Database db, string query)
        {
            if (null == db)
            {
                throw new ArgumentNullException("db");
            }

            if (null == query)
            {
                throw new ArgumentNullException("query");
            }

            uint handle = 0;

            int error = MsiInterop.MsiDatabaseOpenView(db.Handle, query, out handle);
            if (0 != error)
            {
                throw new MsiException(error);
            }

            this.Handle = handle;
        }

        /// <summary>
        /// Executes a view with no customizable parameters.
        /// </summary>
        public void Execute()
        {
            this.Execute(null);
        }

        /// <summary>
        /// Executes a query substituing the values from the records into the customizable parameters 
        /// in the view.
        /// </summary>
        /// <param name="record">Record containing parameters to be substituded into the view.</param>
        public void Execute(Record record)
        {
            int error = MsiInterop.MsiViewExecute(this.Handle, null == record ? 0 : record.Handle);
            if (0 != error)
            {
                throw new MsiException(error);
            }
        }

        /// <summary>
        /// Fetches the next row in the view.
        /// </summary>
        /// <returns>Returns the fetched record; otherwise null.</returns>
        public Record Fetch()
        {
            uint recordHandle;

            int error = MsiInterop.MsiViewFetch(this.Handle, out recordHandle);
            if (259 == error)
            {
                return null;
            }
            else if (0 != error)
            {
                throw new MsiException(error);
            }

            return new Record(recordHandle);
        }

        /// <summary>
        /// Updates a fetched record.
        /// </summary>
        /// <param name="type">Type of modification mode.</param>
        /// <param name="record">Record to be modified.</param>
        public void Modify(ModifyView type, Record record)
        {
            int error = MsiInterop.MsiViewModify(this.Handle, Convert.ToInt32(type, CultureInfo.InvariantCulture), record.Handle);
            if (0 != error)
            {
                throw new MsiException(error);
            }
        }

        /// <summary>
        /// Returns a record containing column names or definitions.
        /// </summary>
        /// <param name="columnType">Specifies a flag indicating what type of information is needed. Either MSICOLINFO_NAMES or MSICOLINFO_TYPES.</param>
        /// <returns>The record containing information about the column.</returns>
        public Record GetColumnInfo(int columnType)
        {
            uint recordHandle;

            int error = MsiInterop.MsiViewGetColumnInfo(this.Handle, columnType, out recordHandle);
            if (0 != error)
            {
                throw new MsiException(error);
            }

            return new Record(recordHandle);
        }
    }
}