11use serde:: Deserialize ;
2- use worker:: { durable_object, wasm_bindgen, Env , Request , Response , Result , SqlStorage , State } ;
2+ use worker:: {
3+ durable_object, wasm_bindgen, Env , Request , Response , Result , SqlStorage , SqlStorageValue ,
4+ State ,
5+ } ;
36
47/// A Durable Object that demonstrates SQL cursor iterator methods.
58///
@@ -26,6 +29,38 @@ struct BadProduct {
2629 in_stock : i32 ,
2730}
2831
32+ #[ derive( Debug ) ]
33+ struct BlobData {
34+ id : i32 ,
35+ name : String ,
36+ data : Vec < u8 > ,
37+ }
38+
39+ impl BlobData {
40+ fn from_raw_row ( row : & [ SqlStorageValue ] ) -> Option < Self > {
41+ if row. len ( ) != 3 {
42+ return None ;
43+ }
44+
45+ let id = match & row[ 0 ] {
46+ SqlStorageValue :: Integer ( i) => * i as i32 ,
47+ _ => return None ,
48+ } ;
49+
50+ let name = match & row[ 1 ] {
51+ SqlStorageValue :: String ( s) => s. clone ( ) ,
52+ _ => return None ,
53+ } ;
54+
55+ let data = match & row[ 2 ] {
56+ SqlStorageValue :: Blob ( bytes) => bytes. clone ( ) ,
57+ _ => return None ,
58+ } ;
59+
60+ Some ( BlobData { id, name, data } )
61+ }
62+ }
63+
2964impl DurableObject for SqlIterator {
3065 fn new ( state : State , _env : Env ) -> Self {
3166 let sql = state. storage ( ) . sql ( ) ;
@@ -36,6 +71,12 @@ impl DurableObject for SqlIterator {
3671 None ,
3772 ) . expect ( "create table" ) ;
3873
74+ sql. exec (
75+ "CREATE TABLE IF NOT EXISTS blob_data(id INTEGER PRIMARY KEY, name TEXT, data BLOB);" ,
76+ None ,
77+ )
78+ . expect ( "create blob table" ) ;
79+
3980 // Check if we need to seed data
4081 let count: Vec < serde_json:: Value > = sql
4182 . exec ( "SELECT COUNT(*) as count FROM products;" , None )
@@ -68,6 +109,38 @@ impl DurableObject for SqlIterator {
68109 }
69110 }
70111
112+ let blob_count: Vec < serde_json:: Value > = sql
113+ . exec ( "SELECT COUNT(*) as count FROM blob_data;" , None )
114+ . expect ( "blob count query" )
115+ . to_array ( )
116+ . expect ( "blob count result" ) ;
117+
118+ if blob_count
119+ . first ( )
120+ . and_then ( |v| v. get ( "count" ) )
121+ . and_then ( serde_json:: Value :: as_i64)
122+ . unwrap_or ( 0 )
123+ == 0
124+ {
125+ let blob_test_data = vec ! [
126+ ( "binary_data" , vec![ 0x00 , 0x01 , 0x02 , 0x03 , 0xFF , 0xFE ] ) ,
127+ ( "empty_blob" , vec![ ] ) ,
128+ ( "text_as_blob" , "Hello, World!" . as_bytes( ) . to_vec( ) ) ,
129+ (
130+ "large_blob" ,
131+ ( 0u8 ..=255 ) . cycle( ) . take( 1000 ) . collect:: <Vec <u8 >>( ) ,
132+ ) ,
133+ ] ;
134+
135+ for ( name, data) in blob_test_data {
136+ sql. exec (
137+ "INSERT INTO blob_data(name, data) VALUES (?, ?);" ,
138+ vec ! [ name. into( ) , SqlStorageValue :: Blob ( data) ] ,
139+ )
140+ . expect ( "insert blob data" ) ;
141+ }
142+ }
143+
71144 Self { sql }
72145 }
73146
@@ -79,7 +152,10 @@ impl DurableObject for SqlIterator {
79152 "/next" => self . handle_next ( ) ,
80153 "/raw" => self . handle_raw ( ) ,
81154 "/next-invalid" => self . handle_next_invalid ( ) ,
82- _ => Response :: ok ( "SQL Iterator Test - try /next, /raw, or /next-invalid endpoints" ) ,
155+ "/blob-next" => self . handle_blob_next ( ) ,
156+ "/blob-raw" => self . handle_blob_raw ( ) ,
157+ "/blob-roundtrip" => self . handle_blob_roundtrip ( ) ,
158+ _ => Response :: ok ( "SQL Iterator Test - try /next, /raw, /next-invalid, /blob-next, /blob-raw, or /blob-roundtrip endpoints" ) ,
83159 }
84160 }
85161}
@@ -166,6 +242,174 @@ impl SqlIterator {
166242
167243 Response :: ok ( response_body)
168244 }
245+
246+ fn handle_blob_next ( & self ) -> Result < Response > {
247+ let cursor = self
248+ . sql
249+ . exec ( "SELECT * FROM blob_data ORDER BY id;" , None ) ?;
250+
251+ let mut results = Vec :: new ( ) ;
252+ let iterator = cursor. raw ( ) ;
253+
254+ for result in iterator {
255+ match result {
256+ Ok ( row) => {
257+ if let Some ( blob_data) = BlobData :: from_raw_row ( & row) {
258+ let data_preview = if blob_data. data . len ( ) <= 10 {
259+ format ! ( "{:?}" , blob_data. data)
260+ } else {
261+ format ! (
262+ "{:?}...[{} bytes total]" ,
263+ & blob_data. data[ ..10 ] ,
264+ blob_data. data. len( )
265+ )
266+ } ;
267+
268+ results. push ( format ! (
269+ "BlobData {}: {} - data: {}" ,
270+ blob_data. id, blob_data. name, data_preview
271+ ) ) ;
272+ } else {
273+ results. push ( "Error: Failed to parse blob row" . to_string ( ) ) ;
274+ }
275+ }
276+ Err ( e) => {
277+ results. push ( format ! ( "Error reading blob row: {e}" ) ) ;
278+ }
279+ }
280+ }
281+
282+ let response_body = format ! ( "blob-next() iterator results:\n {}" , results. join( "\n " ) ) ;
283+
284+ Response :: ok ( response_body)
285+ }
286+
287+ fn handle_blob_raw ( & self ) -> Result < Response > {
288+ let cursor = self
289+ . sql
290+ . exec ( "SELECT * FROM blob_data ORDER BY id;" , None ) ?;
291+
292+ let mut results = Vec :: new ( ) ;
293+ let column_names = cursor. column_names ( ) ;
294+ results. push ( format ! ( "Columns: {}" , column_names. join( ", " ) ) ) ;
295+
296+ let iterator = cursor. raw ( ) ;
297+
298+ for result in iterator {
299+ match result {
300+ Ok ( row) => {
301+ let mut row_str = Vec :: new ( ) ;
302+ for ( i, value) in row. iter ( ) . enumerate ( ) {
303+ if i == 2 {
304+ // 'data' column is index 2 (id=0, name=1, data=2)
305+ match value {
306+ SqlStorageValue :: Blob ( bytes) => {
307+ if bytes. len ( ) <= 10 {
308+ row_str. push ( format ! ( "Blob({:?})" , bytes) ) ;
309+ } else {
310+ row_str. push ( format ! (
311+ "Blob({:?}...[{} bytes])" ,
312+ & bytes[ ..10 ] ,
313+ bytes. len( )
314+ ) ) ;
315+ }
316+ }
317+ _ => row_str. push ( format ! ( "{:?}" , value) ) ,
318+ }
319+ } else {
320+ row_str. push ( format ! ( "{:?}" , value) ) ;
321+ }
322+ }
323+ results. push ( format ! ( "Row: [{}]" , row_str. join( ", " ) ) ) ;
324+ }
325+ Err ( e) => {
326+ results. push ( format ! ( "Error reading blob row: {e}" ) ) ;
327+ }
328+ }
329+ }
330+
331+ let response_body = format ! ( "blob-raw() iterator results:\n {}" , results. join( "\n " ) ) ;
332+
333+ Response :: ok ( response_body)
334+ }
335+
336+ fn handle_blob_roundtrip ( & self ) -> Result < Response > {
337+ // Test data roundtrip: insert a BLOB and immediately read it back
338+ let test_data = vec ! [ 0xDE , 0xAD , 0xBE , 0xEF , 0x00 , 0xFF ] ;
339+ let test_name = "roundtrip_test" ;
340+
341+ // Insert test data
342+ self . sql . exec (
343+ "INSERT INTO blob_data(name, data) VALUES (?, ?);" ,
344+ vec ! [ test_name. into( ) , SqlStorageValue :: Blob ( test_data. clone( ) ) ] ,
345+ ) ?;
346+
347+ // Read it back using both methods (raw iterator approach for both)
348+ let cursor_next = self . sql . exec (
349+ "SELECT * FROM blob_data WHERE name = ? ORDER BY id DESC LIMIT 1;" ,
350+ vec ! [ test_name. into( ) ] ,
351+ ) ?;
352+
353+ let cursor_raw = self . sql . exec (
354+ "SELECT * FROM blob_data WHERE name = ? ORDER BY id DESC LIMIT 1;" ,
355+ vec ! [ test_name. into( ) ] ,
356+ ) ?;
357+
358+ let mut results = Vec :: new ( ) ;
359+ results. push ( format ! ( "Original data: {:?}" , test_data) ) ;
360+
361+ // Test "next()" style result by converting raw data to BlobData struct
362+ let next_raw_iterator = cursor_next. raw ( ) ;
363+ for result in next_raw_iterator {
364+ match result {
365+ Ok ( row) => {
366+ if let Some ( blob_data) = BlobData :: from_raw_row ( & row) {
367+ let matches = blob_data. data == test_data;
368+ results. push ( format ! (
369+ "next() result: {:?}, matches_original: {}" ,
370+ blob_data. data, matches
371+ ) ) ;
372+ } else {
373+ results. push ( "next() error: Failed to parse blob row" . to_string ( ) ) ;
374+ }
375+ }
376+ Err ( e) => {
377+ results. push ( format ! ( "next() error: {e}" ) ) ;
378+ }
379+ }
380+ }
381+
382+ // Test raw iterator
383+ let raw_iterator = cursor_raw. raw ( ) ;
384+ for result in raw_iterator {
385+ match result {
386+ Ok ( row) => {
387+ if let Some ( SqlStorageValue :: Blob ( data) ) = row. get ( 2 ) {
388+ let matches = data == & test_data;
389+ results. push ( format ! (
390+ "raw() result: {:?}, matches_original: {}" ,
391+ data, matches
392+ ) ) ;
393+ } else {
394+ results. push ( "raw() error: data column is not a blob" . to_string ( ) ) ;
395+ }
396+ }
397+ Err ( e) => {
398+ results. push ( format ! ( "raw() error: {e}" ) ) ;
399+ }
400+ }
401+ }
402+
403+ // Clean up test data
404+ self . sql . exec (
405+ "DELETE FROM blob_data WHERE name = ?;" ,
406+ vec ! [ test_name. into( ) ] ,
407+ ) ?;
408+
409+ let response_body = format ! ( "blob-roundtrip test results:\n {}" , results. join( "\n " ) ) ;
410+
411+ Response :: ok ( response_body)
412+ }
169413}
170414
171415#[ worker:: send]
0 commit comments