Skip to content

Commit 8064d86

Browse files
committed
Check that Connection::execute has no tail
Idem for Connection::query_row / Connection::query_row_and_then
1 parent c98cb98 commit 8064d86

4 files changed

Lines changed: 27 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: 26 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,23 @@ 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 {
715+
eprintln!("TAIL: {}", &sql[tail..]);
716+
}
717+
if tail != 0 && !self.prepare(&sql[tail..])?.stmt.is_null() {
718+
Err(Error::MultipleStatement)
719+
} else {
720+
Ok(())
721+
}
722+
}
723+
709724
// https://sqlite.org/tclsqlite.html#onecolumn
710725
#[cfg(test)]
711726
pub(crate) fn one_column<T: types::FromSql>(&self, sql: &str) -> Result<T> {
@@ -745,7 +760,7 @@ impl Connection {
745760
E: From<Error>,
746761
{
747762
let mut stmt = self.prepare(sql)?;
748-
stmt.check_no_tail()?;
763+
self.check_no_tail(&stmt, sql)?;
749764
let mut rows = stmt.query(params)?;
750765

751766
rows.get_expected_row().map_err(E::from).and_then(f)
@@ -1549,7 +1564,6 @@ mod test {
15491564
}
15501565

15511566
#[test]
1552-
#[cfg(feature = "extra_check")]
15531567
fn test_execute_multiple() {
15541568
let db = checked_memory_handle();
15551569
let err = db
@@ -1562,11 +1576,8 @@ mod test {
15621576
Error::MultipleStatement => (),
15631577
_ => panic!("Unexpected error: {err}"),
15641578
}
1565-
if false {
1566-
// FIXME
1567-
db.execute("CREATE TABLE t(c); -- bim", [])
1568-
.expect("Tail comment should be ignored");
1569-
}
1579+
db.execute("CREATE TABLE t(c); -- bim", [])
1580+
.expect("Tail comment should be ignored");
15701581
}
15711582

15721583
#[test]
@@ -1679,9 +1690,12 @@ mod test {
16791690
err => panic!("Unexpected error {err}"),
16801691
}
16811692

1682-
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()));
1693+
db.query_row("NOT A PROPER QUERY; test123", [], |_| Ok(()))
1694+
.unwrap_err();
1695+
1696+
db.query_row("SELECT 1; SELECT 2;", [], |_| Ok(()))
1697+
.unwrap_err();
16831698

1684-
bad_query_result.unwrap_err();
16851699
Ok(())
16861700
}
16871701

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)