1use crate::db::connection::sqlite_error;
7use crate::db::row::{FromColumn, Row};
8use crate::db::value::{bind_all_with_count, bind_named_all, ToSql};
9use crate::db::DbError;
10use crate::sqlite_vfs::ffi;
11use std::ffi::c_void;
12use std::ptr::NonNull;
13
14#[cfg(any(test, feature = "canister-api-test-failpoints"))]
15use std::cell::RefCell;
16#[cfg(any(test, feature = "canister-api-test-failpoints"))]
17use std::collections::BTreeMap;
18
19#[cfg(any(test, feature = "canister-api-test-failpoints"))]
20thread_local! {
21 static STEP_FAILPOINTS: RefCell<BTreeMap<crate::stable::memory::ContextId, StepFailpointState>> = const { RefCell::new(BTreeMap::new()) };
22}
23
24#[cfg(any(test, feature = "canister-api-test-failpoints"))]
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub struct StepFailpoint {
27 pub ordinal: u64,
28 pub code: std::ffi::c_int,
29}
30
31#[cfg(any(test, feature = "canister-api-test-failpoints"))]
32#[derive(Clone, Copy, Debug)]
33struct StepFailpointState {
34 failpoint: StepFailpoint,
35 count: u64,
36}
37
38pub struct Statement<'connection> {
39 db: *mut ffi::sqlite3,
40 raw: NonNull<ffi::sqlite3_stmt>,
41 parameter_count: usize,
42 _connection: std::marker::PhantomData<&'connection ()>,
43}
44
45pub struct Rows<'statement, 'connection> {
46 statement: &'statement mut Statement<'connection>,
47 done: bool,
48 clear_bindings_on_drop: bool,
49}
50
51#[cfg(feature = "bench-profile")]
52#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
53pub struct QueryOptionalStringTextProfile {
54 pub reset_bind: u64,
55 pub step: u64,
56 pub column_read: u64,
57}
58
59#[cfg(feature = "bench-profile")]
60#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
61pub struct ExecuteTextTextProfile {
62 pub reset_bind: u64,
63 pub step: u64,
64}
65
66#[cfg(feature = "bench-profile")]
67#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
68pub struct QueryTextLenSumProfile {
69 pub reset_bind: u64,
70 pub row_scan: u64,
71}
72
73impl<'connection> Statement<'connection> {
74 pub(crate) fn new(db: *mut ffi::sqlite3, raw: NonNull<ffi::sqlite3_stmt>) -> Self {
75 let parameter_count =
76 usize::try_from(unsafe { ffi::sqlite3_bind_parameter_count(raw.as_ptr()) })
77 .unwrap_or(0);
78 Self::from_cached_raw(db, raw, parameter_count)
79 }
80
81 pub(crate) fn from_cached_raw(
82 db: *mut ffi::sqlite3,
83 raw: NonNull<ffi::sqlite3_stmt>,
84 parameter_count: usize,
85 ) -> Self {
86 Self {
87 db,
88 raw,
89 parameter_count,
90 _connection: std::marker::PhantomData,
91 }
92 }
93
94 pub(crate) fn parameter_count(&self) -> usize {
95 self.parameter_count
96 }
97
98 pub(crate) fn into_raw(self) -> NonNull<ffi::sqlite3_stmt> {
99 let raw = self.raw;
100 std::mem::forget(self);
101 raw
102 }
103
104 pub fn execute(&mut self, values: &[&dyn ToSql]) -> Result<(), DbError> {
105 self.reset_and_bind(values)?;
106 let rc = step(self.raw.as_ptr())?;
107 if rc == ffi::SQLITE_DONE {
108 Ok(())
109 } else {
110 Err(sqlite_error(self.db, rc))
111 }
112 }
113
114 pub fn execute_named(&mut self, values: &[(&str, &dyn ToSql)]) -> Result<(), DbError> {
115 self.reset_and_bind_named(values)?;
116 let rc = step(self.raw.as_ptr())?;
117 if rc == ffi::SQLITE_DONE {
118 Ok(())
119 } else {
120 Err(sqlite_error(self.db, rc))
121 }
122 }
123
124 #[inline(always)]
129 pub fn execute_text_text(&mut self, first: &str, second: &str) -> Result<(), DbError> {
130 if let Err(error) = self.reset_and_bind_two_text_borrowed(first, second) {
131 self.clear_bindings();
132 return Err(error);
133 }
134 let rc = step(self.raw.as_ptr());
135 let result = match rc {
136 Ok(ffi::SQLITE_DONE) => Ok(()),
137 Ok(rc) => Err(sqlite_error(self.db, rc)),
138 Err(error) => Err(error),
139 };
140 self.clear_bindings();
141 result
142 }
143
144 #[cfg(feature = "bench-profile")]
145 #[doc(hidden)]
146 pub fn execute_text_text_profiled(
147 &mut self,
148 first: &str,
149 second: &str,
150 ) -> Result<ExecuteTextTextProfile, DbError> {
151 let mut profile = ExecuteTextTextProfile::default();
152
153 let start = instruction_counter();
154 if let Err(error) = self.reset_and_bind_two_text_borrowed(first, second) {
155 self.clear_bindings();
156 return Err(error);
157 }
158 profile.reset_bind = instruction_counter().saturating_sub(start);
159
160 let start = instruction_counter();
161 let rc = step(self.raw.as_ptr());
162 profile.step = instruction_counter().saturating_sub(start);
163
164 let result = match rc {
165 Ok(ffi::SQLITE_DONE) => Ok(profile),
166 Ok(rc) => Err(sqlite_error(self.db, rc)),
167 Err(error) => Err(error),
168 };
169 self.clear_bindings();
170 result
171 }
172
173 #[inline(always)]
178 pub fn execute_i64_text(&mut self, first: i64, second: &str) -> Result<(), DbError> {
179 if let Err(error) = self.reset_and_bind_i64_text_borrowed(first, second) {
180 self.clear_bindings();
181 return Err(error);
182 }
183 let rc = step(self.raw.as_ptr());
184 let result = match rc {
185 Ok(ffi::SQLITE_DONE) => Ok(()),
186 Ok(rc) => Err(sqlite_error(self.db, rc)),
187 Err(error) => Err(error),
188 };
189 self.clear_bindings();
190 result
191 }
192
193 #[inline(always)]
198 pub fn execute_i64_blob(&mut self, first: i64, second: &[u8]) -> Result<(), DbError> {
199 if let Err(error) = self.reset_and_bind_i64_blob_borrowed(first, second) {
200 self.clear_bindings();
201 return Err(error);
202 }
203 let rc = step(self.raw.as_ptr());
204 let result = match rc {
205 Ok(ffi::SQLITE_DONE) => Ok(()),
206 Ok(rc) => Err(sqlite_error(self.db, rc)),
207 Err(error) => Err(error),
208 };
209 self.clear_bindings();
210 result
211 }
212
213 #[inline(always)]
218 pub fn execute_i64_i64_text(
219 &mut self,
220 first: i64,
221 second: i64,
222 third: &str,
223 ) -> Result<(), DbError> {
224 if let Err(error) = self.reset_and_bind_i64_i64_text_borrowed(first, second, third) {
225 self.clear_bindings();
226 return Err(error);
227 }
228 let rc = step(self.raw.as_ptr());
229 let result = match rc {
230 Ok(ffi::SQLITE_DONE) => Ok(()),
231 Ok(rc) => Err(sqlite_error(self.db, rc)),
232 Err(error) => Err(error),
233 };
234 self.clear_bindings();
235 result
236 }
237
238 pub fn query<'statement>(
239 &'statement mut self,
240 values: &[&dyn ToSql],
241 ) -> Result<Rows<'statement, 'connection>, DbError> {
242 self.reset_and_bind(values)?;
243 Ok(Rows {
244 statement: self,
245 done: false,
246 clear_bindings_on_drop: false,
247 })
248 }
249
250 #[doc(hidden)]
251 #[inline(always)]
253 pub fn query_i64<'statement>(
254 &'statement mut self,
255 value: i64,
256 ) -> Result<Rows<'statement, 'connection>, DbError> {
257 self.reset_and_bind_single_i64(value)?;
258 Ok(Rows {
259 statement: self,
260 done: false,
261 clear_bindings_on_drop: false,
262 })
263 }
264
265 #[doc(hidden)]
266 pub fn query_texts<'statement>(
268 &'statement mut self,
269 values: &'statement [&'statement str],
270 ) -> Result<Rows<'statement, 'connection>, DbError> {
271 self.reset_and_bind_text_iter(values.iter().copied())?;
272 Ok(Rows {
273 statement: self,
274 done: false,
275 clear_bindings_on_drop: true,
276 })
277 }
278
279 #[doc(hidden)]
280 pub fn query_text_iter<'statement, I>(
282 &'statement mut self,
283 values: I,
284 ) -> Result<Rows<'statement, 'connection>, DbError>
285 where
286 I: ExactSizeIterator<Item = &'statement str>,
287 {
288 self.reset_and_bind_text_iter(values)?;
289 Ok(Rows {
290 statement: self,
291 done: false,
292 clear_bindings_on_drop: true,
293 })
294 }
295
296 #[doc(hidden)]
297 pub fn query_text_iter_ephemeral<'statement, I>(
301 &'statement mut self,
302 values: I,
303 ) -> Result<Rows<'statement, 'connection>, DbError>
304 where
305 I: ExactSizeIterator<Item = &'statement str>,
306 {
307 self.reset_and_bind_text_iter(values)?;
308 Ok(Rows {
309 statement: self,
310 done: false,
311 clear_bindings_on_drop: true,
312 })
313 }
314
315 #[doc(hidden)]
316 pub fn query_text_iter_text_len_sum<'value, I>(&mut self, values: I) -> Result<u64, DbError>
320 where
321 I: ExactSizeIterator<Item = &'value str>,
322 {
323 if let Err(error) = self.reset_and_bind_text_iter(values) {
324 self.clear_bindings();
325 return Err(error);
326 }
327
328 let statement = self.raw.as_ptr();
329 let mut total = 0_u64;
330 loop {
331 match step(statement) {
332 Ok(ffi::SQLITE_ROW) => {
333 total = total.wrapping_add(read_string_column_zero_len(statement) as u64);
334 }
335 Ok(ffi::SQLITE_DONE) => {
336 self.clear_bindings();
337 return Ok(total);
338 }
339 Ok(rc) => {
340 self.clear_bindings();
341 return Err(sqlite_error(self.db, rc));
342 }
343 Err(error) => {
344 self.clear_bindings();
345 return Err(error);
346 }
347 }
348 }
349 }
350
351 #[cfg(feature = "bench-profile")]
352 #[doc(hidden)]
353 pub fn query_text_iter_text_len_sum_profiled<'value, I>(
354 &mut self,
355 values: I,
356 ) -> Result<(u64, QueryTextLenSumProfile), DbError>
357 where
358 I: ExactSizeIterator<Item = &'value str>,
359 {
360 let mut profile = QueryTextLenSumProfile::default();
361
362 let start = instruction_counter();
363 if let Err(error) = self.reset_and_bind_text_iter(values) {
364 self.clear_bindings();
365 return Err(error);
366 }
367 profile.reset_bind = instruction_counter().saturating_sub(start);
368
369 let start = instruction_counter();
370 let statement = self.raw.as_ptr();
371 let mut total = 0_u64;
372 loop {
373 match step(statement) {
374 Ok(ffi::SQLITE_ROW) => {
375 total = total.wrapping_add(read_string_column_zero_len(statement) as u64);
376 }
377 Ok(ffi::SQLITE_DONE) => {
378 profile.row_scan = instruction_counter().saturating_sub(start);
379 self.clear_bindings();
380 return Ok((total, profile));
381 }
382 Ok(rc) => {
383 self.clear_bindings();
384 return Err(sqlite_error(self.db, rc));
385 }
386 Err(error) => {
387 self.clear_bindings();
388 return Err(error);
389 }
390 }
391 }
392 }
393
394 pub fn query_named<'statement>(
395 &'statement mut self,
396 values: &[(&str, &dyn ToSql)],
397 ) -> Result<Rows<'statement, 'connection>, DbError> {
398 self.reset_and_bind_named(values)?;
399 Ok(Rows {
400 statement: self,
401 done: false,
402 clear_bindings_on_drop: false,
403 })
404 }
405
406 pub fn query_one<T, F>(&mut self, values: &[&dyn ToSql], f: F) -> Result<T, DbError>
407 where
408 F: FnOnce(&Row<'_>) -> Result<T, DbError>,
409 {
410 let mut rows = self.query(values)?;
411 match rows.next_row()? {
412 Some(row) => f(&row),
413 None => Err(DbError::NotFound),
414 }
415 }
416
417 pub fn query_one_named<T, F>(
418 &mut self,
419 values: &[(&str, &dyn ToSql)],
420 f: F,
421 ) -> Result<T, DbError>
422 where
423 F: FnOnce(&Row<'_>) -> Result<T, DbError>,
424 {
425 let mut rows = self.query_named(values)?;
426 match rows.next_row()? {
427 Some(row) => f(&row),
428 None => Err(DbError::NotFound),
429 }
430 }
431
432 pub fn query_optional<T, F>(
433 &mut self,
434 values: &[&dyn ToSql],
435 f: F,
436 ) -> Result<Option<T>, DbError>
437 where
438 F: FnOnce(&Row<'_>) -> Result<T, DbError>,
439 {
440 let mut rows = self.query(values)?;
441 match rows.next_row()? {
442 Some(row) => f(&row).map(Some),
443 None => Ok(None),
444 }
445 }
446
447 pub fn query_optional_named<T, F>(
448 &mut self,
449 values: &[(&str, &dyn ToSql)],
450 f: F,
451 ) -> Result<Option<T>, DbError>
452 where
453 F: FnOnce(&Row<'_>) -> Result<T, DbError>,
454 {
455 let mut rows = self.query_named(values)?;
456 match rows.next_row()? {
457 Some(row) => f(&row).map(Some),
458 None => Ok(None),
459 }
460 }
461
462 pub fn query_all<T, F>(&mut self, values: &[&dyn ToSql], mut f: F) -> Result<Vec<T>, DbError>
463 where
464 F: FnMut(&Row<'_>) -> Result<T, DbError>,
465 {
466 let mut rows = self.query(values)?;
467 let mut output = Vec::new();
468 while let Some(row) = rows.next_row()? {
469 output.push(f(&row)?);
470 }
471 Ok(output)
472 }
473
474 pub fn query_all_named<T, F>(
475 &mut self,
476 values: &[(&str, &dyn ToSql)],
477 mut f: F,
478 ) -> Result<Vec<T>, DbError>
479 where
480 F: FnMut(&Row<'_>) -> Result<T, DbError>,
481 {
482 let mut rows = self.query_named(values)?;
483 let mut output = Vec::new();
484 while let Some(row) = rows.next_row()? {
485 output.push(f(&row)?);
486 }
487 Ok(output)
488 }
489
490 pub fn query_scalar<T: FromColumn>(&mut self, values: &[&dyn ToSql]) -> Result<T, DbError> {
491 self.query_one(values, |row| row.get(0))
492 }
493 pub fn query_scalar_named<T: FromColumn>(
494 &mut self,
495 values: &[(&str, &dyn ToSql)],
496 ) -> Result<T, DbError> {
497 self.query_one_named(values, |row| row.get(0))
498 }
499
500 pub fn query_optional_scalar<T: FromColumn>(
501 &mut self,
502 values: &[&dyn ToSql],
503 ) -> Result<Option<T>, DbError> {
504 self.query_optional(values, |row| row.get(0))
505 }
506
507 #[inline(always)]
508 pub fn query_optional_string_text(&mut self, value: &str) -> Result<Option<String>, DbError> {
509 self.query_optional_string_text_borrowed(value)
510 }
511
512 #[inline(always)]
513 pub(crate) fn query_optional_string_text_borrowed(
514 &mut self,
515 value: &str,
516 ) -> Result<Option<String>, DbError> {
517 if let Err(error) = self.reset_and_bind_single_text_borrowed(value) {
518 self.clear_bindings();
519 return Err(error);
520 }
521 let rc = step(self.raw.as_ptr());
522 let result = match rc {
523 Ok(ffi::SQLITE_ROW) => read_string_column_zero(self.raw.as_ptr()).map(Some),
524 Ok(ffi::SQLITE_DONE) => Ok(None),
525 Ok(rc) => Err(sqlite_error(self.db, rc)),
526 Err(error) => Err(error),
527 };
528 self.clear_bindings();
529 result
530 }
531
532 #[inline(always)]
533 pub fn query_optional_string_text_len(
534 &mut self,
535 value: &str,
536 ) -> Result<Option<usize>, DbError> {
537 self.query_optional_string_text_len_borrowed(value)
538 }
539
540 #[inline(always)]
541 pub(crate) fn query_optional_string_text_len_borrowed(
542 &mut self,
543 value: &str,
544 ) -> Result<Option<usize>, DbError> {
545 if let Err(error) = self.reset_and_bind_single_text_borrowed(value) {
546 self.clear_bindings();
547 return Err(error);
548 }
549 let rc = step(self.raw.as_ptr());
550 let result = match rc {
551 Ok(ffi::SQLITE_ROW) => Ok(Some(read_string_column_zero_len(self.raw.as_ptr()))),
552 Ok(ffi::SQLITE_DONE) => Ok(None),
553 Ok(rc) => Err(sqlite_error(self.db, rc)),
554 Err(error) => Err(error),
555 };
556 self.clear_bindings();
557 result
558 }
559
560 #[cfg(feature = "bench-profile")]
561 #[doc(hidden)]
562 pub fn query_optional_string_text_profiled(
563 &mut self,
564 value: &str,
565 ) -> Result<(Option<String>, QueryOptionalStringTextProfile), DbError> {
566 let mut profile = QueryOptionalStringTextProfile::default();
567
568 let start = instruction_counter();
569 if let Err(error) = self.reset_and_bind_single_text_borrowed(value) {
570 self.clear_bindings();
571 return Err(error);
572 }
573 profile.reset_bind = instruction_counter().saturating_sub(start);
574
575 let start = instruction_counter();
576 let rc = step(self.raw.as_ptr());
577 profile.step = instruction_counter().saturating_sub(start);
578
579 match rc {
580 Ok(ffi::SQLITE_ROW) => {
581 let start = instruction_counter();
582 let value = read_string_column_zero(self.raw.as_ptr()).map(Some);
583 profile.column_read = instruction_counter().saturating_sub(start);
584 self.clear_bindings();
585 value.map(|value| (value, profile))
586 }
587 Ok(ffi::SQLITE_DONE) => {
588 self.clear_bindings();
589 Ok((None, profile))
590 }
591 Ok(rc) => {
592 self.clear_bindings();
593 Err(sqlite_error(self.db, rc))
594 }
595 Err(error) => {
596 self.clear_bindings();
597 Err(error)
598 }
599 }
600 }
601
602 #[cfg(feature = "bench-profile")]
603 #[doc(hidden)]
604 pub fn query_optional_string_text_len_profiled(
605 &mut self,
606 value: &str,
607 ) -> Result<(Option<usize>, QueryOptionalStringTextProfile), DbError> {
608 let mut profile = QueryOptionalStringTextProfile::default();
609
610 let start = instruction_counter();
611 if let Err(error) = self.reset_and_bind_single_text_borrowed(value) {
612 self.clear_bindings();
613 return Err(error);
614 }
615 profile.reset_bind = instruction_counter().saturating_sub(start);
616
617 let start = instruction_counter();
618 let rc = step(self.raw.as_ptr());
619 profile.step = instruction_counter().saturating_sub(start);
620
621 match rc {
622 Ok(ffi::SQLITE_ROW) => {
623 let start = instruction_counter();
624 let value = Some(read_string_column_zero_len(self.raw.as_ptr()));
625 profile.column_read = instruction_counter().saturating_sub(start);
626 self.clear_bindings();
627 Ok((value, profile))
628 }
629 Ok(ffi::SQLITE_DONE) => {
630 self.clear_bindings();
631 Ok((None, profile))
632 }
633 Ok(rc) => {
634 self.clear_bindings();
635 Err(sqlite_error(self.db, rc))
636 }
637 Err(error) => {
638 self.clear_bindings();
639 Err(error)
640 }
641 }
642 }
643
644 pub fn query_optional_scalar_named<T: FromColumn>(
645 &mut self,
646 values: &[(&str, &dyn ToSql)],
647 ) -> Result<Option<T>, DbError> {
648 self.query_optional_named(values, |row| row.get(0))
649 }
650
651 pub fn query_column<T: FromColumn>(
652 &mut self,
653 values: &[&dyn ToSql],
654 ) -> Result<Vec<T>, DbError> {
655 self.query_all(values, |row| row.get(0))
656 }
657
658 pub fn query_column_named<T: FromColumn>(
659 &mut self,
660 values: &[(&str, &dyn ToSql)],
661 ) -> Result<Vec<T>, DbError> {
662 self.query_all_named(values, |row| row.get(0))
663 }
664
665 fn reset_and_bind(&mut self, values: &[&dyn ToSql]) -> Result<(), DbError> {
666 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
667 if reset_rc != ffi::SQLITE_OK {
668 return Err(sqlite_error(self.db, reset_rc));
669 }
670 if self.parameter_count == 0 && values.is_empty() {
671 return Ok(());
672 }
673 bind_all_with_count(self.raw.as_ptr(), values, self.parameter_count)
674 }
675
676 #[inline(always)]
677 fn reset_and_bind_single_text_borrowed(&mut self, value: &str) -> Result<(), DbError> {
678 if self.parameter_count != 1 {
679 return Err(DbError::ParameterCountMismatch {
680 expected: self.parameter_count,
681 actual: 1,
682 });
683 }
684 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
685 if reset_rc != ffi::SQLITE_OK {
686 return Err(sqlite_error(self.db, reset_rc));
687 }
688 bind_text_static(self.raw.as_ptr(), 1, value)
689 }
690
691 #[inline(always)]
692 fn reset_and_bind_single_i64(&mut self, value: i64) -> Result<(), DbError> {
693 if self.parameter_count != 1 {
694 return Err(DbError::ParameterCountMismatch {
695 expected: self.parameter_count,
696 actual: 1,
697 });
698 }
699 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
700 if reset_rc != ffi::SQLITE_OK {
701 return Err(sqlite_error(self.db, reset_rc));
702 }
703 bind_i64(self.raw.as_ptr(), 1, value)
704 }
705
706 #[inline(always)]
707 fn reset_and_bind_two_text_borrowed(
708 &mut self,
709 first: &str,
710 second: &str,
711 ) -> Result<(), DbError> {
712 if self.parameter_count != 2 {
713 return Err(DbError::ParameterCountMismatch {
714 expected: self.parameter_count,
715 actual: 2,
716 });
717 }
718 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
719 if reset_rc != ffi::SQLITE_OK {
720 return Err(sqlite_error(self.db, reset_rc));
721 }
722 let first_len =
723 std::ffi::c_int::try_from(first.len()).map_err(|_| DbError::TextTooLarge)?;
724 let first_rc = unsafe {
725 ffi::sqlite3_bind_text(
726 self.raw.as_ptr(),
727 1,
728 first.as_ptr().cast(),
729 first_len,
730 ffi::SQLITE_STATIC(),
731 )
732 };
733 if first_rc != ffi::SQLITE_OK {
734 return Err(DbError::Sqlite(first_rc, "sqlite bind failed".to_string()));
735 }
736 let second_len =
737 std::ffi::c_int::try_from(second.len()).map_err(|_| DbError::TextTooLarge)?;
738 let second_rc = unsafe {
739 ffi::sqlite3_bind_text(
740 self.raw.as_ptr(),
741 2,
742 second.as_ptr().cast(),
743 second_len,
744 ffi::SQLITE_STATIC(),
745 )
746 };
747 if second_rc == ffi::SQLITE_OK {
748 Ok(())
749 } else {
750 Err(DbError::Sqlite(second_rc, "sqlite bind failed".to_string()))
751 }
752 }
753
754 fn reset_and_bind_text_iter<'value, I>(&mut self, values: I) -> Result<(), DbError>
755 where
756 I: ExactSizeIterator<Item = &'value str>,
757 {
758 let actual = values.len();
759 if actual != self.parameter_count {
760 return Err(DbError::ParameterCountMismatch {
761 expected: self.parameter_count,
762 actual,
763 });
764 }
765 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
766 if reset_rc != ffi::SQLITE_OK {
767 return Err(sqlite_error(self.db, reset_rc));
768 }
769 let mut param = 1;
770 for value in values {
771 let len = match std::ffi::c_int::try_from(value.len()) {
772 Ok(len) => len,
773 Err(_) => {
774 self.clear_bindings();
775 return Err(DbError::TextTooLarge);
776 }
777 };
778 let rc = unsafe {
779 ffi::sqlite3_bind_text(
780 self.raw.as_ptr(),
781 param,
782 value.as_ptr().cast(),
783 len,
784 ffi::SQLITE_STATIC(),
785 )
786 };
787 if rc != ffi::SQLITE_OK {
788 self.clear_bindings();
789 return Err(DbError::Sqlite(rc, "sqlite bind failed".to_string()));
790 }
791 param += 1;
792 }
793 Ok(())
794 }
795
796 #[inline(always)]
797 fn reset_and_bind_i64_text_borrowed(
798 &mut self,
799 first: i64,
800 second: &str,
801 ) -> Result<(), DbError> {
802 if self.parameter_count != 2 {
803 return Err(DbError::ParameterCountMismatch {
804 expected: self.parameter_count,
805 actual: 2,
806 });
807 }
808 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
809 if reset_rc != ffi::SQLITE_OK {
810 return Err(sqlite_error(self.db, reset_rc));
811 }
812 bind_i64(self.raw.as_ptr(), 1, first)?;
813 let second_len =
814 std::ffi::c_int::try_from(second.len()).map_err(|_| DbError::TextTooLarge)?;
815 let second_rc = unsafe {
816 ffi::sqlite3_bind_text(
817 self.raw.as_ptr(),
818 2,
819 second.as_ptr().cast(),
820 second_len,
821 ffi::SQLITE_STATIC(),
822 )
823 };
824 if second_rc == ffi::SQLITE_OK {
825 Ok(())
826 } else {
827 Err(DbError::Sqlite(second_rc, "sqlite bind failed".to_string()))
828 }
829 }
830
831 #[inline(always)]
832 fn reset_and_bind_i64_blob_borrowed(
833 &mut self,
834 first: i64,
835 second: &[u8],
836 ) -> Result<(), DbError> {
837 if self.parameter_count != 2 {
838 return Err(DbError::ParameterCountMismatch {
839 expected: self.parameter_count,
840 actual: 2,
841 });
842 }
843 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
844 if reset_rc != ffi::SQLITE_OK {
845 return Err(sqlite_error(self.db, reset_rc));
846 }
847 bind_i64(self.raw.as_ptr(), 1, first)?;
848 bind_blob_static(self.raw.as_ptr(), 2, second)
849 }
850
851 #[inline(always)]
852 fn reset_and_bind_i64_i64_text_borrowed(
853 &mut self,
854 first: i64,
855 second: i64,
856 third: &str,
857 ) -> Result<(), DbError> {
858 if self.parameter_count != 3 {
859 return Err(DbError::ParameterCountMismatch {
860 expected: self.parameter_count,
861 actual: 3,
862 });
863 }
864 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
865 if reset_rc != ffi::SQLITE_OK {
866 return Err(sqlite_error(self.db, reset_rc));
867 }
868 bind_i64(self.raw.as_ptr(), 1, first)?;
869 bind_i64(self.raw.as_ptr(), 2, second)?;
870 let third_len =
871 std::ffi::c_int::try_from(third.len()).map_err(|_| DbError::TextTooLarge)?;
872 let third_rc = unsafe {
873 ffi::sqlite3_bind_text(
874 self.raw.as_ptr(),
875 3,
876 third.as_ptr().cast(),
877 third_len,
878 ffi::SQLITE_STATIC(),
879 )
880 };
881 if third_rc == ffi::SQLITE_OK {
882 Ok(())
883 } else {
884 Err(DbError::Sqlite(third_rc, "sqlite bind failed".to_string()))
885 }
886 }
887
888 fn clear_bindings(&mut self) {
889 unsafe {
890 ffi::sqlite3_clear_bindings(self.raw.as_ptr());
891 }
892 }
893
894 fn reset_and_bind_named(&mut self, values: &[(&str, &dyn ToSql)]) -> Result<(), DbError> {
895 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
896 if reset_rc != ffi::SQLITE_OK {
897 return Err(sqlite_error(self.db, reset_rc));
898 }
899 bind_named_all(self.raw.as_ptr(), values)
900 }
901}
902
903static EMPTY_BLOB: u8 = 0;
904
905#[inline(always)]
906fn read_string_column_zero(statement: *mut ffi::sqlite3_stmt) -> Result<String, DbError> {
907 let actual = unsafe { ffi::sqlite3_column_type(statement, 0) };
908 if actual != ffi::SQLITE_TEXT {
909 return Err(DbError::TypeMismatch {
910 index: 0,
911 expected: "TEXT",
912 actual: sqlite_type_name(actual),
913 });
914 }
915 let text = unsafe { ffi::sqlite3_column_text(statement, 0) };
916 let len = unsafe { ffi::sqlite3_column_bytes(statement, 0) as usize };
917 if len == 0 || text.is_null() {
918 return Ok(String::new());
919 }
920 let bytes = unsafe { std::slice::from_raw_parts(text.cast::<u8>(), len) };
921 match std::str::from_utf8(bytes) {
922 Ok(value) => Ok(value.to_owned()),
923 Err(_) => Ok(String::from_utf8_lossy(bytes).into_owned()),
924 }
925}
926
927#[inline(always)]
928fn read_string_column_zero_len(statement: *mut ffi::sqlite3_stmt) -> usize {
929 unsafe { ffi::sqlite3_column_bytes(statement, 0) as usize }
930}
931
932#[inline]
933fn bind_text_static(
934 statement: *mut ffi::sqlite3_stmt,
935 index: std::ffi::c_int,
936 value: &str,
937) -> Result<(), DbError> {
938 let len = std::ffi::c_int::try_from(value.len()).map_err(|_| DbError::TextTooLarge)?;
939 let rc = unsafe {
940 ffi::sqlite3_bind_text(
941 statement,
942 index,
943 value.as_ptr().cast(),
944 len,
945 ffi::SQLITE_STATIC(),
946 )
947 };
948 if rc == ffi::SQLITE_OK {
949 Ok(())
950 } else {
951 Err(DbError::Sqlite(rc, "sqlite bind failed".to_string()))
952 }
953}
954
955#[inline(always)]
956fn bind_blob_static(
957 statement: *mut ffi::sqlite3_stmt,
958 index: std::ffi::c_int,
959 value: &[u8],
960) -> Result<(), DbError> {
961 let len = std::ffi::c_int::try_from(value.len()).map_err(|_| DbError::BlobTooLarge)?;
962 let ptr = if value.is_empty() {
963 (&EMPTY_BLOB as *const u8).cast::<c_void>()
964 } else {
965 value.as_ptr().cast::<c_void>()
966 };
967 let rc = unsafe { ffi::sqlite3_bind_blob(statement, index, ptr, len, ffi::SQLITE_STATIC()) };
968 if rc == ffi::SQLITE_OK {
969 Ok(())
970 } else {
971 Err(DbError::Sqlite(rc, "sqlite bind failed".to_string()))
972 }
973}
974
975#[inline(always)]
976fn bind_i64(
977 statement: *mut ffi::sqlite3_stmt,
978 index: std::ffi::c_int,
979 value: i64,
980) -> Result<(), DbError> {
981 let rc = unsafe { ffi::sqlite3_bind_int64(statement, index, value) };
982 if rc == ffi::SQLITE_OK {
983 Ok(())
984 } else {
985 Err(DbError::Sqlite(rc, "sqlite bind failed".to_string()))
986 }
987}
988
989fn sqlite_type_name(code: std::ffi::c_int) -> &'static str {
990 match code {
991 ffi::SQLITE_INTEGER => "INTEGER",
992 ffi::SQLITE_FLOAT => "REAL",
993 ffi::SQLITE_TEXT => "TEXT",
994 ffi::SQLITE_BLOB => "BLOB",
995 ffi::SQLITE_NULL => "NULL",
996 _ => "UNKNOWN",
997 }
998}
999
1000impl Rows<'_, '_> {
1001 pub fn next_row(&mut self) -> Result<Option<Row<'_>>, DbError> {
1002 if self.done {
1003 return Ok(None);
1004 }
1005 let rc = step(self.statement.raw.as_ptr())?;
1006 match rc {
1007 ffi::SQLITE_ROW => Ok(Some(Row::new(self.statement.raw.as_ptr()))),
1008 ffi::SQLITE_DONE => {
1009 self.done = true;
1010 self.clear_static_bindings();
1011 Ok(None)
1012 }
1013 _ => Err(sqlite_error(self.statement.db, rc)),
1014 }
1015 }
1016
1017 #[doc(hidden)]
1018 #[inline(always)]
1023 pub fn next_text_len_zero(&mut self) -> Result<Option<usize>, DbError> {
1024 let statement = self.statement.raw.as_ptr();
1025 let rc = step(statement)?;
1026 match rc {
1027 ffi::SQLITE_ROW => Ok(Some(read_string_column_zero_len(statement))),
1028 ffi::SQLITE_DONE => {
1029 self.done = true;
1030 self.clear_static_bindings();
1031 Ok(None)
1032 }
1033 _ => Err(sqlite_error(self.statement.db, rc)),
1034 }
1035 }
1036
1037 fn clear_static_bindings(&mut self) {
1038 if self.clear_bindings_on_drop {
1039 self.statement.clear_bindings();
1040 self.clear_bindings_on_drop = false;
1041 }
1042 }
1043}
1044
1045impl Drop for Rows<'_, '_> {
1046 fn drop(&mut self) {
1047 self.clear_static_bindings();
1048 }
1049}
1050
1051#[inline(always)]
1052fn step(statement: *mut ffi::sqlite3_stmt) -> Result<std::ffi::c_int, DbError> {
1053 #[cfg(any(test, feature = "canister-api-test-failpoints"))]
1054 if let Some(code) = hit_step_failpoint() {
1055 return Err(DbError::Sqlite(code, "sqlite step failpoint".to_string()));
1056 }
1057 Ok(unsafe { ffi::sqlite3_step(statement) })
1058}
1059
1060#[cfg(feature = "bench-profile")]
1061fn instruction_counter() -> u64 {
1062 #[cfg(target_arch = "wasm32")]
1063 {
1064 ic_cdk::api::performance_counter(0)
1065 }
1066 #[cfg(not(target_arch = "wasm32"))]
1067 {
1068 0
1069 }
1070}
1071
1072#[cfg(any(test, feature = "canister-api-test-failpoints"))]
1073pub fn set_step_failpoint(failpoint: StepFailpoint) {
1074 if let Ok(context) = crate::stable::memory::active_context_id() {
1075 STEP_FAILPOINTS.with(|slot| {
1076 slot.borrow_mut().insert(
1077 context,
1078 StepFailpointState {
1079 failpoint,
1080 count: 0,
1081 },
1082 );
1083 });
1084 }
1085}
1086
1087#[cfg(any(test, feature = "canister-api-test-failpoints"))]
1088pub fn clear_step_failpoint() {
1089 STEP_FAILPOINTS.with(|slot| slot.borrow_mut().clear());
1090}
1091
1092#[cfg(any(test, feature = "canister-api-test-failpoints"))]
1093fn hit_step_failpoint() -> Option<std::ffi::c_int> {
1094 let Ok(context) = crate::stable::memory::active_context_id() else {
1095 return None;
1096 };
1097 STEP_FAILPOINTS.with(|slot| {
1098 let mut slot = slot.borrow_mut();
1099 let state = slot.get_mut(&context)?;
1100 state.count += 1;
1101 if state.failpoint.ordinal == state.count {
1102 let code = state.failpoint.code;
1103 slot.remove(&context);
1104 Some(code)
1105 } else {
1106 None
1107 }
1108 })
1109}
1110
1111impl Drop for Statement<'_> {
1112 fn drop(&mut self) {
1113 unsafe {
1114 ffi::sqlite3_finalize(self.raw.as_ptr());
1115 }
1116 }
1117}