|
1 | | -use magnus::prelude::*; |
2 | | -use magnus::{method, Error, Module, RArray, Ruby, Value}; |
| 1 | +use magnus::value::ReprValue; |
| 2 | +use magnus::{method, DataTypeFunctions, Error, Module, Ruby, TryConvert, TypedData, Value}; |
3 | 3 | use std::cell::RefCell; |
4 | 4 | use std::sync::atomic::{AtomicBool, Ordering}; |
5 | 5 | use turso_sdk_kit::rsapi::{TursoStatement, TursoStatusCode}; |
6 | 6 |
|
7 | 7 | use crate::errors::{map_turso_error, statement_closed_error}; |
8 | 8 | use crate::value; |
9 | 9 |
|
10 | | -#[magnus::wrap(class = "Turso::ExecutionResult", free_immediately, size)] |
11 | | -pub struct RbExecutionResult { |
| 10 | +/// ResultSet containing query results and execution metadata. |
| 11 | +/// Implements `Enumerable`, so you can use `.each`, `.map`, `.select`, etc. |
| 12 | +#[derive(TypedData)] |
| 13 | +#[magnus(class = "Turso::ResultSet", mark, size)] |
| 14 | +pub struct RbResultSet { |
| 15 | + rows: Vec<Value>, |
12 | 16 | rows_changed: u64, |
13 | 17 | } |
14 | 18 |
|
15 | | -impl RbExecutionResult { |
16 | | - pub(crate) fn from_rsapi(rows_changed: u64) -> Self { |
17 | | - Self { rows_changed } |
| 19 | + |
| 20 | +unsafe impl Send for RbResultSet {} |
| 21 | +unsafe impl Sync for RbResultSet {} |
| 22 | + |
| 23 | +impl RbResultSet { |
| 24 | + pub(crate) fn new(rows: Vec<Value>, rows_changed: u64) -> Self { |
| 25 | + Self { rows, rows_changed } |
18 | 26 | } |
19 | 27 |
|
20 | 28 | pub fn rows_changed(&self) -> u64 { |
21 | 29 | self.rows_changed |
22 | 30 | } |
| 31 | + |
| 32 | + pub fn each( |
| 33 | + ruby: &Ruby, |
| 34 | + rb_self: Value, |
| 35 | + ) -> Result<magnus::block::Yield<impl Iterator<Item = Value>>, Error> { |
| 36 | + let this: &Self = TryConvert::try_convert(rb_self)?; |
| 37 | + if ruby.block_given() { |
| 38 | + Ok(magnus::block::Yield::Iter(this.rows.iter().copied())) |
| 39 | + } else { |
| 40 | + Ok(magnus::block::Yield::Enumerator( |
| 41 | + rb_self.enumeratorize("each", ()), |
| 42 | + )) |
| 43 | + } |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +impl DataTypeFunctions for RbResultSet { |
| 48 | + fn mark(&self, marker: &magnus::gc::Marker) { |
| 49 | + for row in &self.rows { |
| 50 | + marker.mark(*row); |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + fn size(&self) -> usize { |
| 55 | + std::mem::size_of::<Self>() + self.rows.len() * std::mem::size_of::<Value>() |
| 56 | + } |
23 | 57 | } |
24 | 58 |
|
25 | 59 | #[magnus::wrap(class = "Turso::Statement", free_immediately, size)] |
@@ -68,17 +102,31 @@ impl RbStatement { |
68 | 102 | } |
69 | 103 | } |
70 | 104 |
|
71 | | - pub fn execute(&self, args: &[Value]) -> Result<RbExecutionResult, Error> { |
| 105 | + pub fn execute(&self, args: &[Value]) -> Result<RbResultSet, Error> { |
72 | 106 | self.ensure_open()?; |
73 | 107 | self.bind(args)?; |
74 | 108 |
|
75 | | - let result = self.inner.borrow_mut().execute().map_err(map_turso_error)?; |
76 | | - Ok(RbExecutionResult { |
77 | | - rows_changed: result.rows_changed, |
78 | | - }) |
| 109 | + let mut stmt = self.inner.borrow_mut(); |
| 110 | + let mut rows: Vec<Value> = Vec::new(); |
| 111 | + loop { |
| 112 | + match stmt.step().map_err(map_turso_error)? { |
| 113 | + TursoStatusCode::Row => { |
| 114 | + rows.push(value::extract_row(&stmt)?.as_value()); |
| 115 | + } |
| 116 | + TursoStatusCode::Done => break, |
| 117 | + TursoStatusCode::Io => { |
| 118 | + stmt.run_io().map_err(map_turso_error)?; |
| 119 | + } |
| 120 | + _ => break, |
| 121 | + } |
| 122 | + } |
| 123 | + let changes = stmt.n_change(); |
| 124 | + stmt.reset().map_err(map_turso_error)?; |
| 125 | + |
| 126 | + Ok(RbResultSet::new(rows, changes)) |
79 | 127 | } |
80 | 128 |
|
81 | | - pub fn columns(&self) -> Result<RArray, Error> { |
| 129 | + pub fn columns(&self) -> Result<magnus::RArray, Error> { |
82 | 130 | self.ensure_open()?; |
83 | 131 | let ruby = Ruby::get().expect("Ruby not initialized"); |
84 | 132 | let stmt = self.inner.borrow(); |
@@ -124,9 +172,17 @@ impl Drop for RbStatement { |
124 | 172 | } |
125 | 173 | } |
126 | 174 |
|
| 175 | +pub fn define_result_set(ruby: &Ruby, module: &impl Module) -> Result<(), Error> { |
| 176 | + let class = module.define_class("ResultSet", ruby.class_object())?; |
| 177 | + class.include_module(ruby.module_enumerable())?; |
| 178 | + class.define_method("rows_changed", method!(RbResultSet::rows_changed, 0))?; |
| 179 | + class.define_method("each", method!(RbResultSet::each, 0))?; |
| 180 | + Ok(()) |
| 181 | +} |
| 182 | + |
| 183 | + |
127 | 184 | pub fn define_statement(ruby: &Ruby, module: &impl Module) -> Result<(), Error> { |
128 | | - let result_class = module.define_class("ExecutionResult", ruby.class_object())?; |
129 | | - result_class.define_method("rows_changed", method!(RbExecutionResult::rows_changed, 0))?; |
| 185 | + define_result_set(ruby, module)?; |
130 | 186 |
|
131 | 187 | let class = module.define_class("Statement", ruby.class_object())?; |
132 | 188 | class.define_method("bind", method!(RbStatement::bind, -1))?; |
|
0 commit comments