Skip to content

Commit db72ac0

Browse files
authored
Add View support (#11)
* Add View support Also adds lifetimes to make sure nothing (except for a new Record) outlives a Session or any of its ancestors. * Use pwsh shell
1 parent 838187b commit db72ac0

File tree

10 files changed

+289
-41
lines changed

10 files changed

+289
-41
lines changed

.github/workflows/release.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ jobs:
3838
with:
3939
command: test
4040
args: --all
41-
- uses: actions-rs/cargo@v1
41+
- name: Release
42+
run: gh release create ${{ github.ref_name }} --generate-notes
43+
env:
44+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45+
- name: Publish
46+
uses: actions-rs/cargo@v1
4247
with:
4348
command: publish
4449
env:

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
[package]
55
name = "msica"
6-
version = "0.1.0"
6+
version = "0.2.0"
77
edition = "2021"
88
license = "MIT"
99
description = "Rust for Windows Installer Custom Actions"

examples/deferred/deferred.wxs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,22 @@
1111
<InstallExecuteSequence>
1212
<Custom Action="DeferredExampleCustomAction" After="InstallInitialize" />
1313
</InstallExecuteSequence>
14+
15+
<CustomTable Id="DeferredExample">
16+
<Column Id="Cardinal" Type="int" Category="Integer" Width="2" PrimaryKey="yes" />
17+
<Column Id="Ordinal" Type="string" Category="LowerCase" Localizable="yes" />
18+
<Row>
19+
<Data Column="Cardinal">1</Data>
20+
<Data Column="Ordinal">first</Data>
21+
</Row>
22+
<Row>
23+
<Data Column="Cardinal">2</Data>
24+
<Data Column="Ordinal">second</Data>
25+
</Row>
26+
<Row>
27+
<Data Column="Cardinal">3</Data>
28+
<Data Column="Ordinal">third</Data>
29+
</Row>
30+
</CustomTable>
1431
</Fragment>
1532
</Wix>

examples/deferred/lib.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@ const ERROR_SUCCESS: u32 = 0;
88
pub extern "C" fn DeferredExampleCustomAction(h: MSIHANDLE) -> u32 {
99
let session = Session::from(h);
1010

11-
// Simulate reading data from a custom table.
12-
for i in 0..5 {
13-
session.do_deferred_action("DeferredExampleCustomActionDeferred", &i.to_string())
11+
let database = session.database();
12+
let mut view = database
13+
.open_view("SELECT `Cardinal`, `Ordinal` FROM `DeferredExample` ORDER BY `Cardinal`");
14+
view.execute(None);
15+
for record in view.into_iter() {
16+
let data = format!(
17+
"{}\t{}",
18+
record.integer_data(1).unwrap(),
19+
record.string_data(2)
20+
);
21+
session.do_deferred_action("DeferredExampleCustomActionDeferred", &data);
1422
}
1523
ERROR_SUCCESS
1624
}
@@ -22,9 +30,13 @@ pub extern "C" fn DeferredExampleCustomActionDeferred(h: MSIHANDLE) -> u32 {
2230
// Process the custom action data passed by the immediate custom action.
2331
// This data is always made available in a property named "CustomActionData".
2432
let data = session.property("CustomActionData");
33+
let fields: Vec<&str> = data.split('\t').collect();
2534
let record = Record::with_fields(
26-
Some("Running deferred custom action [1]"),
27-
vec![Field::StringData(data)],
35+
Some("Running the [2] ([1]) deferred custom action"),
36+
vec![
37+
Field::StringData(fields[0].to_string()),
38+
Field::StringData(fields[1].to_string()),
39+
],
2840
);
2941
session.message(MessageType::Info, &record);
3042
ERROR_SUCCESS

src/database.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,48 @@
11
// Copyright 2022 Heath Stewart.
22
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
33

4-
use super::{MSIHANDLE, PMSIHANDLE};
4+
use super::ffi;
5+
use super::{Record, View, MSIHANDLE, PMSIHANDLE};
6+
use std::ffi::CString;
57

68
/// The database for the current install session.
7-
pub struct Database {
8-
h: PMSIHANDLE,
9+
pub struct Database<'a> {
10+
h: PMSIHANDLE<'a>,
911
}
1012

11-
impl From<MSIHANDLE> for Database {
13+
impl<'a> Database<'a> {
14+
/// Returns a [`View`] object that represents the query specified by a
15+
/// [SQL string](https://docs.microsoft.com/windows/win32/msi/sql-syntax).
16+
pub fn open_view(&'a self, sql: &str) -> View<'a> {
17+
unsafe {
18+
let mut h = MSIHANDLE::null();
19+
let sql = CString::new(sql).unwrap();
20+
21+
// TODO: Return Result<View<'a>, ?>.
22+
ffi::MsiDatabaseOpenView(*self.h, sql.as_ptr(), &mut h);
23+
24+
View::from(h)
25+
}
26+
}
27+
28+
/// Returns a [`Record`] object containing the table name in field 0 and the column names
29+
/// (comprising the primary keys) in succeeding fields corresponding to their column numbers.
30+
///
31+
/// The field count of the record is the count of primary key columns.
32+
pub fn primary_keys(&'a self, table: &str) -> Record<'a> {
33+
unsafe {
34+
let mut h = MSIHANDLE::null();
35+
let table = CString::new(table).unwrap();
36+
37+
// TODO: Return Result<View<'a>, ?>.
38+
ffi::MsiDatabaseGetPrimaryKeys(*self.h, table.as_ptr(), &mut h);
39+
40+
Record::from(h)
41+
}
42+
}
43+
}
44+
45+
impl<'a> From<MSIHANDLE> for Database<'a> {
1246
fn from(h: MSIHANDLE) -> Self {
1347
Database { h: h.to_owned() }
1448
}

src/ffi.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub const ERROR_INSTALL_FAILURE: u32 = 1603;
1414
pub const ERROR_FUNCTION_NOT_CALLED: u32 = 1626;
1515

1616
pub(crate) const ERROR_MORE_DATA: u32 = 234;
17+
pub(crate) const MSI_NULL_INTEGER: i32 = -0x8000_0000;
1718

1819
// cspell:ignore pcch
1920
#[link(name = "msi")]
@@ -22,6 +23,20 @@ extern "C" {
2223

2324
pub fn MsiCreateRecord(cParams: u32) -> MSIHANDLE;
2425

26+
#[link_name = "MsiDatabaseGetPrimaryKeysA"]
27+
pub fn MsiDatabaseGetPrimaryKeys(
28+
hDatabase: MSIHANDLE,
29+
szTableName: LPCSTR,
30+
hRecord: &mut MSIHANDLE,
31+
) -> u32;
32+
33+
#[link_name = "MsiDatabaseOpenViewA"]
34+
pub fn MsiDatabaseOpenView(
35+
hDatabase: MSIHANDLE,
36+
szQuery: LPCSTR,
37+
phView: &mut MSIHANDLE,
38+
) -> u32;
39+
2540
#[link_name = "MsiDoActionA"]
2641
pub fn MsiDoAction(hInstall: MSIHANDLE, szAction: LPCSTR) -> u32;
2742

@@ -76,6 +91,12 @@ extern "C" {
7691

7792
#[link_name = "MsiSetPropertyA"]
7893
pub fn MsiSetProperty(hInstall: MSIHANDLE, szName: LPCSTR, szValue: LPCSTR) -> u32;
94+
95+
pub fn MsiViewClose(hView: MSIHANDLE) -> u32;
96+
97+
pub fn MsiViewExecute(hView: MSIHANDLE, hRecord: MSIHANDLE) -> u32;
98+
99+
pub fn MsiViewFetch(hView: MSIHANDLE, phRecord: &mut MSIHANDLE) -> u32;
79100
}
80101

81102
#[derive(Copy, Clone)]

src/lib.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod database;
1414
mod ffi;
1515
mod record;
1616
mod session;
17+
mod view;
1718

1819
pub use database::Database;
1920
pub use ffi::{
@@ -22,8 +23,9 @@ pub use ffi::{
2223
};
2324
pub use record::{Field, Record};
2425
pub use session::Session;
26+
pub use view::{View, ViewIterator};
2527

26-
use std::{fmt::Debug, ops::Deref};
28+
use std::{fmt::Debug, marker::PhantomData, ops::Deref};
2729

2830
/// Message types that can be processed by a custom action.
2931
#[repr(u32)]
@@ -84,7 +86,7 @@ pub enum RunMode {
8486
/// println!("last error: {}", error.format_text());
8587
/// }
8688
/// ```
87-
pub fn last_error_record() -> Option<Record> {
89+
pub fn last_error_record<'a>() -> Option<Record<'a>> {
8890
unsafe {
8991
match ffi::MsiGetLastErrorRecord() {
9092
h if !h.is_null() => Some(h.into()),
@@ -94,7 +96,7 @@ pub fn last_error_record() -> Option<Record> {
9496
}
9597

9698
/// A Windows Installer handle. This handle is not automatically closed.
97-
#[derive(Clone, Copy)]
99+
#[derive(Clone, Copy, PartialEq)]
98100
#[repr(transparent)]
99101
pub struct MSIHANDLE(u32);
100102

@@ -103,8 +105,12 @@ impl MSIHANDLE {
103105
MSIHANDLE(0)
104106
}
105107

106-
fn to_owned(&self) -> PMSIHANDLE {
107-
PMSIHANDLE(*self)
108+
fn to_owned<'a>(&self) -> PMSIHANDLE<'a> {
109+
PMSIHANDLE {
110+
h: *self,
111+
_owned: true,
112+
_phantom: PhantomData,
113+
}
108114
}
109115

110116
fn is_null(&self) -> bool {
@@ -133,27 +139,52 @@ impl Deref for MSIHANDLE {
133139
}
134140

135141
/// A Windows Installer handle. This handle is automatically closed when dropped.
136-
#[repr(transparent)]
137-
struct PMSIHANDLE(MSIHANDLE);
142+
struct PMSIHANDLE<'a> {
143+
h: MSIHANDLE,
144+
_owned: bool,
145+
_phantom: PhantomData<&'a ()>,
146+
}
147+
148+
impl<'a> Clone for PMSIHANDLE<'a> {
149+
fn clone(&self) -> Self {
150+
PMSIHANDLE {
151+
h: self.h,
152+
_owned: false,
153+
_phantom: PhantomData,
154+
}
155+
}
156+
}
138157

139-
impl Debug for PMSIHANDLE {
158+
impl<'a> Debug for PMSIHANDLE<'a> {
140159
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141-
write!(f, "MSIHANDLE ({})", *self.0)
160+
write!(f, "MSIHANDLE ({})", *self.h)
142161
}
143162
}
144163

145-
impl Drop for PMSIHANDLE {
164+
impl<'a> Drop for PMSIHANDLE<'a> {
146165
fn drop(&mut self) {
147166
unsafe {
148-
ffi::MsiCloseHandle(**self);
167+
if self._owned {
168+
ffi::MsiCloseHandle(**self);
169+
}
149170
}
150171
}
151172
}
152173

153-
impl Deref for PMSIHANDLE {
174+
impl<'a> Deref for PMSIHANDLE<'a> {
154175
type Target = MSIHANDLE;
155176

156177
fn deref(&self) -> &Self::Target {
157-
&self.0
178+
&self.h
179+
}
180+
}
181+
182+
#[cfg(test)]
183+
mod tests {
184+
use super::*;
185+
186+
#[test]
187+
fn is_null() {
188+
assert!(MSIHANDLE::null().is_null());
158189
}
159190
}

src/record.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ impl From<&str> for Field {
2525
}
2626

2727
/// A collection of [`Field`] containing strings, integers, and byte streams.
28-
pub struct Record {
29-
h: PMSIHANDLE,
28+
#[derive(Clone)]
29+
pub struct Record<'a> {
30+
h: PMSIHANDLE<'a>,
3031
}
3132

32-
impl Record {
33+
impl<'a> Record<'a> {
3334
/// Creates an empty [`Record`] with capacity for the count of fields specified.
3435
///
3536
/// Field indices are 1-based.
@@ -213,10 +214,15 @@ impl Record {
213214
/// Some("this is [1] [2]"),
214215
/// vec![Field::IntegerData(1), Field::StringData("example".to_owned())],
215216
/// );
216-
/// assert_eq!(record.integer_data(1), 1);
217+
/// assert_eq!(record.integer_data(1), Some(1));
217218
/// ```
218-
pub fn integer_data(&self, field: u32) -> i32 {
219-
unsafe { ffi::MsiRecordGetInteger(*self.h, field) }
219+
pub fn integer_data(&self, field: u32) -> Option<i32> {
220+
unsafe {
221+
match ffi::MsiRecordGetInteger(*self.h, field) {
222+
i if i == ffi::MSI_NULL_INTEGER => None,
223+
i => Some(i),
224+
}
225+
}
220226
}
221227

222228
/// Sets an integer field in a [`Record`].
@@ -230,14 +236,22 @@ impl Record {
230236
///
231237
/// let mut record = Record::new(1);
232238
/// record.set_integer_data(1, 42);
233-
/// assert_eq!(record.integer_data(1), 42);
239+
/// assert_eq!(record.integer_data(1), Some(42));
234240
/// ```
235241
pub fn set_integer_data(&self, field: u32, value: i32) {
236242
unsafe {
237243
ffi::MsiRecordSetInteger(*self.h, field, value);
238244
}
239245
}
240246

247+
/// Reads bytes from a record field that contains stream data.
248+
///
249+
/// Field indices are 1-based.
250+
#[allow(unused_variables)]
251+
pub fn stream_data(&self, field: u32) -> Vec<u8> {
252+
todo!()
253+
}
254+
241255
/// Gets whether a field is null in a [`Record`].
242256
///
243257
/// Field indices are 1-based.
@@ -255,21 +269,21 @@ impl Record {
255269
}
256270
}
257271

258-
impl Deref for Record {
272+
impl<'a> Deref for Record<'a> {
259273
type Target = MSIHANDLE;
260274

261275
fn deref(&self) -> &Self::Target {
262276
&*self.h
263277
}
264278
}
265279

266-
impl From<MSIHANDLE> for Record {
280+
impl<'a> From<MSIHANDLE> for Record<'a> {
267281
fn from(h: MSIHANDLE) -> Self {
268282
Record { h: h.to_owned() }
269283
}
270284
}
271285

272-
impl From<&str> for Record {
286+
impl<'a> From<&str> for Record<'a> {
273287
fn from(s: &str) -> Self {
274288
unsafe {
275289
let h = ffi::MsiCreateRecord(0u32);
@@ -282,7 +296,7 @@ impl From<&str> for Record {
282296
}
283297
}
284298

285-
impl From<String> for Record {
299+
impl<'a> From<String> for Record<'a> {
286300
fn from(s: String) -> Self {
287301
unsafe {
288302
let h = ffi::MsiCreateRecord(0u32);
@@ -318,5 +332,12 @@ mod tests {
318332

319333
record.set_string_data(1, None);
320334
assert!(record.is_null(1));
335+
assert_eq!(record.string_data(1), "");
336+
}
337+
338+
#[test]
339+
fn integer_data_from_string() {
340+
let record = Record::with_fields(None, vec![Field::StringData("test".to_owned())]);
341+
assert_eq!(record.integer_data(1), None);
321342
}
322343
}

0 commit comments

Comments
 (0)