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 for (param, value) in (1..).zip(values) {
770 let len = match std::ffi::c_int::try_from(value.len()) {
771 Ok(len) => len,
772 Err(_) => {
773 self.clear_bindings();
774 return Err(DbError::TextTooLarge);
775 }
776 };
777 let rc = unsafe {
778 ffi::sqlite3_bind_text(
779 self.raw.as_ptr(),
780 param,
781 value.as_ptr().cast(),
782 len,
783 ffi::SQLITE_STATIC(),
784 )
785 };
786 if rc != ffi::SQLITE_OK {
787 self.clear_bindings();
788 return Err(DbError::Sqlite(rc, "sqlite bind failed".to_string()));
789 }
790 }
791 Ok(())
792 }
793
794 #[inline(always)]
795 fn reset_and_bind_i64_text_borrowed(
796 &mut self,
797 first: i64,
798 second: &str,
799 ) -> Result<(), DbError> {
800 if self.parameter_count != 2 {
801 return Err(DbError::ParameterCountMismatch {
802 expected: self.parameter_count,
803 actual: 2,
804 });
805 }
806 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
807 if reset_rc != ffi::SQLITE_OK {
808 return Err(sqlite_error(self.db, reset_rc));
809 }
810 bind_i64(self.raw.as_ptr(), 1, first)?;
811 let second_len =
812 std::ffi::c_int::try_from(second.len()).map_err(|_| DbError::TextTooLarge)?;
813 let second_rc = unsafe {
814 ffi::sqlite3_bind_text(
815 self.raw.as_ptr(),
816 2,
817 second.as_ptr().cast(),
818 second_len,
819 ffi::SQLITE_STATIC(),
820 )
821 };
822 if second_rc == ffi::SQLITE_OK {
823 Ok(())
824 } else {
825 Err(DbError::Sqlite(second_rc, "sqlite bind failed".to_string()))
826 }
827 }
828
829 #[inline(always)]
830 fn reset_and_bind_i64_blob_borrowed(
831 &mut self,
832 first: i64,
833 second: &[u8],
834 ) -> Result<(), DbError> {
835 if self.parameter_count != 2 {
836 return Err(DbError::ParameterCountMismatch {
837 expected: self.parameter_count,
838 actual: 2,
839 });
840 }
841 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
842 if reset_rc != ffi::SQLITE_OK {
843 return Err(sqlite_error(self.db, reset_rc));
844 }
845 bind_i64(self.raw.as_ptr(), 1, first)?;
846 bind_blob_static(self.raw.as_ptr(), 2, second)
847 }
848
849 #[inline(always)]
850 fn reset_and_bind_i64_i64_text_borrowed(
851 &mut self,
852 first: i64,
853 second: i64,
854 third: &str,
855 ) -> Result<(), DbError> {
856 if self.parameter_count != 3 {
857 return Err(DbError::ParameterCountMismatch {
858 expected: self.parameter_count,
859 actual: 3,
860 });
861 }
862 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
863 if reset_rc != ffi::SQLITE_OK {
864 return Err(sqlite_error(self.db, reset_rc));
865 }
866 bind_i64(self.raw.as_ptr(), 1, first)?;
867 bind_i64(self.raw.as_ptr(), 2, second)?;
868 let third_len =
869 std::ffi::c_int::try_from(third.len()).map_err(|_| DbError::TextTooLarge)?;
870 let third_rc = unsafe {
871 ffi::sqlite3_bind_text(
872 self.raw.as_ptr(),
873 3,
874 third.as_ptr().cast(),
875 third_len,
876 ffi::SQLITE_STATIC(),
877 )
878 };
879 if third_rc == ffi::SQLITE_OK {
880 Ok(())
881 } else {
882 Err(DbError::Sqlite(third_rc, "sqlite bind failed".to_string()))
883 }
884 }
885
886 fn clear_bindings(&mut self) {
887 unsafe {
888 ffi::sqlite3_clear_bindings(self.raw.as_ptr());
889 }
890 }
891
892 fn reset_and_bind_named(&mut self, values: &[(&str, &dyn ToSql)]) -> Result<(), DbError> {
893 let reset_rc = unsafe { ffi::sqlite3_reset(self.raw.as_ptr()) };
894 if reset_rc != ffi::SQLITE_OK {
895 return Err(sqlite_error(self.db, reset_rc));
896 }
897 bind_named_all(self.raw.as_ptr(), values)
898 }
899}
900
901static EMPTY_BLOB: u8 = 0;
902
903#[inline(always)]
904fn read_string_column_zero(statement: *mut ffi::sqlite3_stmt) -> Result<String, DbError> {
905 let actual = unsafe { ffi::sqlite3_column_type(statement, 0) };
906 if actual != ffi::SQLITE_TEXT {
907 return Err(DbError::TypeMismatch {
908 index: 0,
909 expected: "TEXT",
910 actual: sqlite_type_name(actual),
911 });
912 }
913 let text = unsafe { ffi::sqlite3_column_text(statement, 0) };
914 let len = unsafe { ffi::sqlite3_column_bytes(statement, 0) as usize };
915 if len == 0 || text.is_null() {
916 return Ok(String::new());
917 }
918 let bytes = unsafe { std::slice::from_raw_parts(text.cast::<u8>(), len) };
919 match std::str::from_utf8(bytes) {
920 Ok(value) => Ok(value.to_owned()),
921 Err(_) => Ok(String::from_utf8_lossy(bytes).into_owned()),
922 }
923}
924
925#[inline(always)]
926fn read_string_column_zero_len(statement: *mut ffi::sqlite3_stmt) -> usize {
927 unsafe { ffi::sqlite3_column_bytes(statement, 0) as usize }
928}
929
930#[inline]
931fn bind_text_static(
932 statement: *mut ffi::sqlite3_stmt,
933 index: std::ffi::c_int,
934 value: &str,
935) -> Result<(), DbError> {
936 let len = std::ffi::c_int::try_from(value.len()).map_err(|_| DbError::TextTooLarge)?;
937 let rc = unsafe {
938 ffi::sqlite3_bind_text(
939 statement,
940 index,
941 value.as_ptr().cast(),
942 len,
943 ffi::SQLITE_STATIC(),
944 )
945 };
946 if rc == ffi::SQLITE_OK {
947 Ok(())
948 } else {
949 Err(DbError::Sqlite(rc, "sqlite bind failed".to_string()))
950 }
951}
952
953#[inline(always)]
954fn bind_blob_static(
955 statement: *mut ffi::sqlite3_stmt,
956 index: std::ffi::c_int,
957 value: &[u8],
958) -> Result<(), DbError> {
959 let len = std::ffi::c_int::try_from(value.len()).map_err(|_| DbError::BlobTooLarge)?;
960 let ptr = if value.is_empty() {
961 (&EMPTY_BLOB as *const u8).cast::<c_void>()
962 } else {
963 value.as_ptr().cast::<c_void>()
964 };
965 let rc = unsafe { ffi::sqlite3_bind_blob(statement, index, ptr, len, ffi::SQLITE_STATIC()) };
966 if rc == ffi::SQLITE_OK {
967 Ok(())
968 } else {
969 Err(DbError::Sqlite(rc, "sqlite bind failed".to_string()))
970 }
971}
972
973#[inline(always)]
974fn bind_i64(
975 statement: *mut ffi::sqlite3_stmt,
976 index: std::ffi::c_int,
977 value: i64,
978) -> Result<(), DbError> {
979 let rc = unsafe { ffi::sqlite3_bind_int64(statement, index, value) };
980 if rc == ffi::SQLITE_OK {
981 Ok(())
982 } else {
983 Err(DbError::Sqlite(rc, "sqlite bind failed".to_string()))
984 }
985}
986
987fn sqlite_type_name(code: std::ffi::c_int) -> &'static str {
988 match code {
989 ffi::SQLITE_INTEGER => "INTEGER",
990 ffi::SQLITE_FLOAT => "REAL",
991 ffi::SQLITE_TEXT => "TEXT",
992 ffi::SQLITE_BLOB => "BLOB",
993 ffi::SQLITE_NULL => "NULL",
994 _ => "UNKNOWN",
995 }
996}
997
998impl Rows<'_, '_> {
999 pub fn next_row(&mut self) -> Result<Option<Row<'_>>, DbError> {
1000 if self.done {
1001 return Ok(None);
1002 }
1003 let rc = step(self.statement.raw.as_ptr())?;
1004 match rc {
1005 ffi::SQLITE_ROW => Ok(Some(Row::new(self.statement.raw.as_ptr()))),
1006 ffi::SQLITE_DONE => {
1007 self.done = true;
1008 self.clear_static_bindings();
1009 Ok(None)
1010 }
1011 _ => Err(sqlite_error(self.statement.db, rc)),
1012 }
1013 }
1014
1015 #[doc(hidden)]
1016 #[inline(always)]
1021 pub fn next_text_len_zero(&mut self) -> Result<Option<usize>, DbError> {
1022 let statement = self.statement.raw.as_ptr();
1023 let rc = step(statement)?;
1024 match rc {
1025 ffi::SQLITE_ROW => Ok(Some(read_string_column_zero_len(statement))),
1026 ffi::SQLITE_DONE => {
1027 self.done = true;
1028 self.clear_static_bindings();
1029 Ok(None)
1030 }
1031 _ => Err(sqlite_error(self.statement.db, rc)),
1032 }
1033 }
1034
1035 fn clear_static_bindings(&mut self) {
1036 if self.clear_bindings_on_drop {
1037 self.statement.clear_bindings();
1038 self.clear_bindings_on_drop = false;
1039 }
1040 }
1041}
1042
1043impl Drop for Rows<'_, '_> {
1044 fn drop(&mut self) {
1045 self.clear_static_bindings();
1046 }
1047}
1048
1049#[inline(always)]
1050fn step(statement: *mut ffi::sqlite3_stmt) -> Result<std::ffi::c_int, DbError> {
1051 #[cfg(any(test, feature = "canister-api-test-failpoints"))]
1052 if let Some(code) = hit_step_failpoint() {
1053 return Err(DbError::Sqlite(code, "sqlite step failpoint".to_string()));
1054 }
1055 Ok(unsafe { ffi::sqlite3_step(statement) })
1056}
1057
1058#[cfg(feature = "bench-profile")]
1059fn instruction_counter() -> u64 {
1060 #[cfg(target_arch = "wasm32")]
1061 {
1062 ic_cdk::api::performance_counter(0)
1063 }
1064 #[cfg(not(target_arch = "wasm32"))]
1065 {
1066 0
1067 }
1068}
1069
1070#[cfg(any(test, feature = "canister-api-test-failpoints"))]
1071pub fn set_step_failpoint(failpoint: StepFailpoint) {
1072 if let Ok(context) = crate::stable::memory::active_context_id() {
1073 STEP_FAILPOINTS.with(|slot| {
1074 slot.borrow_mut().insert(
1075 context,
1076 StepFailpointState {
1077 failpoint,
1078 count: 0,
1079 },
1080 );
1081 });
1082 }
1083}
1084
1085#[cfg(any(test, feature = "canister-api-test-failpoints"))]
1086pub fn clear_step_failpoint() {
1087 STEP_FAILPOINTS.with(|slot| slot.borrow_mut().clear());
1088}
1089
1090#[cfg(any(test, feature = "canister-api-test-failpoints"))]
1091fn hit_step_failpoint() -> Option<std::ffi::c_int> {
1092 let Ok(context) = crate::stable::memory::active_context_id() else {
1093 return None;
1094 };
1095 STEP_FAILPOINTS.with(|slot| {
1096 let mut slot = slot.borrow_mut();
1097 let state = slot.get_mut(&context)?;
1098 state.count += 1;
1099 if state.failpoint.ordinal == state.count {
1100 let code = state.failpoint.code;
1101 slot.remove(&context);
1102 Some(code)
1103 } else {
1104 None
1105 }
1106 })
1107}
1108
1109impl Drop for Statement<'_> {
1110 fn drop(&mut self) {
1111 unsafe {
1112 ffi::sqlite3_finalize(self.raw.as_ptr());
1113 }
1114 }
1115}