Skip to content

Commit a6d7d99

Browse files
authored
Merge pull request #1679 from gwenn/single_query
Check that Connection::execute has no tail
2 parents c98cb98 + 79b6ba3 commit a6d7d99

4 files changed

Lines changed: 24 additions & 37 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
135135
* `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected.
136136
* `uuid` allows storing and retrieving `Uuid` values from the [`uuid`](https://docs.rs/uuid/) crate using blobs.
137137
* [`session`](https://sqlite.org/sessionintro.html), Session module extension. Requires `buildtime_bindgen` feature. (Implies `hooks`.)
138-
* `extra_check` fails when a query passed to `execute` has tail or is readonly and has a column count > 0.
138+
* `extra_check` fails when a query passed to `execute` is readonly and has a column count > 0.
139139
* `column_decltype` provides `columns()` method for Statements and Rows; omit if linking to a version of SQLite/SQLCipher compiled with `-DSQLITE_OMIT_DECLTYPE`.
140140
* `collation` exposes [`sqlite3_create_collation_v2`](https://sqlite.org/c3ref/create_collation.html).
141141
* `serialize` exposes [`sqlite3_serialize`](http://sqlite.org/c3ref/serialize.html) (3.23.0).

src/lib.rs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -634,8 +634,10 @@ impl Connection {
634634
/// or if the underlying SQLite call fails.
635635
#[inline]
636636
pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize> {
637-
self.prepare(sql)
638-
.and_then(|mut stmt| stmt.check_no_tail().and_then(|()| stmt.execute(params)))
637+
self.prepare(sql).and_then(|mut stmt| {
638+
self.check_no_tail(&stmt, sql)
639+
.and_then(|()| stmt.execute(params))
640+
})
639641
}
640642

641643
/// Returns the path to the database file, if one exists and is known.
@@ -702,10 +704,20 @@ impl Connection {
702704
F: FnOnce(&Row<'_>) -> Result<T>,
703705
{
704706
let mut stmt = self.prepare(sql)?;
705-
stmt.check_no_tail()?;
707+
self.check_no_tail(&stmt, sql)?;
706708
stmt.query_row(params, f)
707709
}
708710

711+
#[inline]
712+
fn check_no_tail(&self, stmt: &Statement, sql: &str) -> Result<()> {
713+
let tail = stmt.stmt.tail();
714+
if tail != 0 && !self.prepare(&sql[tail..])?.stmt.is_null() {
715+
Err(Error::MultipleStatement)
716+
} else {
717+
Ok(())
718+
}
719+
}
720+
709721
// https://sqlite.org/tclsqlite.html#onecolumn
710722
#[cfg(test)]
711723
pub(crate) fn one_column<T: types::FromSql>(&self, sql: &str) -> Result<T> {
@@ -745,7 +757,7 @@ impl Connection {
745757
E: From<Error>,
746758
{
747759
let mut stmt = self.prepare(sql)?;
748-
stmt.check_no_tail()?;
760+
self.check_no_tail(&stmt, sql)?;
749761
let mut rows = stmt.query(params)?;
750762

751763
rows.get_expected_row().map_err(E::from).and_then(f)
@@ -1549,7 +1561,6 @@ mod test {
15491561
}
15501562

15511563
#[test]
1552-
#[cfg(feature = "extra_check")]
15531564
fn test_execute_multiple() {
15541565
let db = checked_memory_handle();
15551566
let err = db
@@ -1562,11 +1573,8 @@ mod test {
15621573
Error::MultipleStatement => (),
15631574
_ => panic!("Unexpected error: {err}"),
15641575
}
1565-
if false {
1566-
// FIXME
1567-
db.execute("CREATE TABLE t(c); -- bim", [])
1568-
.expect("Tail comment should be ignored");
1569-
}
1576+
db.execute("CREATE TABLE t(c); -- bim", [])
1577+
.expect("Tail comment should be ignored");
15701578
}
15711579

15721580
#[test]
@@ -1679,9 +1687,12 @@ mod test {
16791687
err => panic!("Unexpected error {err}"),
16801688
}
16811689

1682-
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()));
1690+
db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()))
1691+
.unwrap_err();
1692+
1693+
db.query_row("SELECT 1; SELECT 2;", [], |_| Ok(()))
1694+
.unwrap_err();
16831695

1684-
bad_query_result.unwrap_err();
16851696
Ok(())
16861697
}
16871698

src/raw_statement.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,6 @@ impl RawStatement {
250250
unsafe { stmt_status(self.ptr, status, reset) }
251251
}
252252

253-
#[inline]
254-
#[cfg(feature = "extra_check")]
255-
pub fn has_tail(&self) -> bool {
256-
self.tail != 0
257-
}
258-
259253
#[inline]
260254
pub fn tail(&self) -> usize {
261255
self.tail

src/statement.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,6 @@ impl Statement<'_> {
672672
#[cfg(feature = "extra_check")]
673673
#[inline]
674674
fn check_update(&self) -> Result<()> {
675-
// sqlite3_column_count works for DML but not for DDL (ie ALTER)
676675
if self.column_count() > 0 && self.stmt.readonly() {
677676
return Err(Error::ExecuteReturnedResults);
678677
}
@@ -723,23 +722,6 @@ impl Statement<'_> {
723722
self.stmt.readonly()
724723
}
725724

726-
#[cfg(feature = "extra_check")]
727-
#[inline]
728-
pub(crate) fn check_no_tail(&self) -> Result<()> {
729-
if self.stmt.has_tail() {
730-
Err(Error::MultipleStatement)
731-
} else {
732-
Ok(())
733-
}
734-
}
735-
736-
#[cfg(not(feature = "extra_check"))]
737-
#[inline]
738-
#[expect(clippy::unnecessary_wraps)]
739-
pub(crate) fn check_no_tail(&self) -> Result<()> {
740-
Ok(())
741-
}
742-
743725
/// Safety: This is unsafe, because using `sqlite3_stmt` after the
744726
/// connection has closed is illegal, but `RawStatement` does not enforce
745727
/// this, as it loses our protective `'conn` lifetime bound.

0 commit comments

Comments
 (0)