@@ -154,19 +154,12 @@ class TestRowsResponseAliasing:
154154 """Regression tests for issue 042.
155155
156156 ``RowsResponse`` used to store caller-supplied ``column_names`` and
157- ``column_types`` lists by reference, creating two kinds of silent
158- aliasing:
159-
160- 1. ``column_types`` was stored as ``all_row_types[0]`` inside
161- ``decode_body`` / ``decode_rows_continuation`` via
162- ``if not column_types: column_types = types``. The returned
163- object then had ``r.column_types is r.row_types[0]``, so
164- mutating one list silently rewrote the other.
165- 2. ``column_names`` was passed through ``decode_rows_continuation``
166- unchanged and attached directly to every continuation
167- ``RowsResponse``. Callers who mutated ``msg.column_names`` on
168- one continuation silently mutated every sibling continuation
169- AND the original response.
157+ ``column_types`` lists by reference, creating silent aliasing:
158+
159+ ``column_types`` was stored as ``all_row_types[0]`` inside
160+ ``decode_body`` via ``if not column_types: column_types = types``.
161+ The returned object then had ``r.column_types is r.row_types[0]``,
162+ so mutating one list silently rewrote the other.
170163
171164 The fix copies both lists on construction via ``__post_init__``.
172165 """
@@ -461,60 +454,6 @@ def test_zero_columns_missing_marker_raises(self) -> None:
461454 with pytest .raises (DecodeError , match = "end marker" ):
462455 RowsResponse .decode_body (data )
463456
464- def test_zero_columns_continuation_missing_marker_raises (self ) -> None :
465- """Zero-column continuation with no end marker should raise DecodeError."""
466- import pytest
467-
468- from dqlitewire .exceptions import DecodeError
469-
470- # Empty data for a zero-column continuation
471- with pytest .raises (DecodeError , match = "end marker" ):
472- RowsResponse .decode_rows_continuation (
473- data = b"" ,
474- column_names = [],
475- column_count = 0 ,
476- )
477-
478- def test_decode_rows_continuation (self ) -> None :
479- """Continuation messages (after PART marker) have rows but no column header."""
480- from dqlitewire .tuples import encode_row_header , encode_row_values
481- from dqlitewire .types import encode_uint64
482-
483- column_names = ["id" , "name" ]
484- types = [ValueType .INTEGER , ValueType .TEXT ]
485- # Build a continuation body: rows + DONE marker (no column_count/names prefix)
486- body = b""
487- body += encode_row_header (types )
488- body += encode_row_values ([3 , "Charlie" ], types )
489- body += encode_row_header (types )
490- body += encode_row_values ([4 , "Diana" ], types )
491- body += encode_uint64 (0xFFFFFFFFFFFFFFFF ) # DONE marker
492-
493- decoded = RowsResponse .decode_rows_continuation (body , column_names , len (column_names ))
494- assert decoded .column_names == ["id" , "name" ]
495- assert len (decoded .rows ) == 2
496- assert decoded .rows [0 ] == [3 , "Charlie" ]
497- assert decoded .rows [1 ] == [4 , "Diana" ]
498- assert decoded .has_more is False
499-
500- def test_decode_rows_continuation_truncated_raises (self ) -> None :
501- """Truncated continuation (no marker) must raise DecodeError, not silently return."""
502- import pytest
503-
504- from dqlitewire .exceptions import DecodeError
505- from dqlitewire .tuples import encode_row_header , encode_row_values
506-
507- column_names = ["id" , "name" ]
508- types = [ValueType .INTEGER , ValueType .TEXT ]
509- # Build continuation body with row data but NO end marker
510- body = b""
511- body += encode_row_header (types )
512- body += encode_row_values ([3 , "Charlie" ], types )
513- # No DONE or PART marker at end!
514-
515- with pytest .raises (DecodeError , match = "end marker" ):
516- RowsResponse .decode_rows_continuation (body , column_names , len (column_names ))
517-
518457 def test_decode_body_rejects_non_list_row_header (self ) -> None :
519458 """decode_body must raise DecodeError if decode_row_header returns unexpected type."""
520459 from unittest .mock import patch
@@ -539,27 +478,6 @@ def test_decode_body_rejects_non_list_row_header(self) -> None:
539478 ):
540479 RowsResponse .decode_body (body )
541480
542- def test_decode_rows_continuation_rejects_non_list_row_header (self ) -> None :
543- """Same guard as decode_body but for the continuation path."""
544- from unittest .mock import patch
545-
546- import pytest
547-
548- from dqlitewire .exceptions import DecodeError
549- from dqlitewire .tuples import encode_row_header , encode_row_values
550- from dqlitewire .types import encode_uint64
551-
552- # Build valid continuation body (rows only, no column header)
553- body = encode_row_header ([ValueType .INTEGER ])
554- body += encode_row_values ([42 ], [ValueType .INTEGER ])
555- body += encode_uint64 (0xFFFFFFFFFFFFFFFF ) # DONE
556-
557- with (
558- patch ("dqlitewire.messages.responses.decode_row_header" , return_value = ("bad" , 8 )),
559- pytest .raises (DecodeError , match = "Expected column types list" ),
560- ):
561- RowsResponse .decode_rows_continuation (body , column_names = ["id" ], column_count = 1 )
562-
563481 def test_truncated_body_without_marker_raises (self ) -> None :
564482 """Body exhausted without DONE/PART marker must raise DecodeError."""
565483 import pytest
@@ -626,29 +544,6 @@ def test_max_rows_limit_decode_body(self) -> None:
626544 with pytest .raises (DecodeError , match = "Row count.*exceeds maximum" ):
627545 RowsResponse .decode_body (body , max_rows = 3 )
628546
629- def test_max_rows_limit_decode_rows_continuation (self ) -> None :
630- """decode_rows_continuation should reject messages exceeding the max_rows limit."""
631- import pytest
632-
633- from dqlitewire .exceptions import DecodeError
634- from dqlitewire .tuples import encode_row_header , encode_row_values
635- from dqlitewire .types import encode_uint64
636-
637- types = [ValueType .INTEGER ]
638- body = b""
639- for i in range (5 ):
640- body += encode_row_header (types )
641- body += encode_row_values ([i ], types )
642- body += encode_uint64 (0xFFFFFFFFFFFFFFFF ) # DONE
643-
644- # Should succeed with default limit
645- decoded = RowsResponse .decode_rows_continuation (body , ["x" ], 1 )
646- assert len (decoded .rows ) == 5
647-
648- # Should fail with max_rows=2
649- with pytest .raises (DecodeError , match = "Row count.*exceeds maximum" ):
650- RowsResponse .decode_rows_continuation (body , ["x" ], 1 , max_rows = 2 )
651-
652547 def test_max_rows_exact_boundary_rejects_at_limit (self ) -> None :
653548 """max_rows=3 with exactly 3 rows should raise DecodeError.
654549
@@ -680,42 +575,6 @@ def build_body(n_rows: int) -> bytes:
680575 decoded = RowsResponse .decode_body (build_body (2 ), max_rows = 3 )
681576 assert len (decoded .rows ) == 2
682577
683- def test_continuation_column_count_mismatch_raises (self ) -> None :
684- """decode_rows_continuation should reject mismatched column_names/column_count."""
685- import pytest
686-
687- from dqlitewire .exceptions import DecodeError
688- from dqlitewire .tuples import encode_row_header , encode_row_values
689- from dqlitewire .types import encode_uint64
690-
691- types = [ValueType .INTEGER , ValueType .TEXT ]
692- body = encode_row_header (types )
693- body += encode_row_values ([1 , "hello" ], types )
694- body += encode_uint64 (0xFFFFFFFFFFFFFFFF ) # DONE
695-
696- # column_names has 3 elements but column_count is 2 — mismatch
697- with pytest .raises (DecodeError , match = "column_names.*does not match.*column_count" ):
698- RowsResponse .decode_rows_continuation (
699- body ,
700- column_names = ["a" , "b" , "c" ],
701- column_count = 2 ,
702- )
703-
704- def test_decode_rows_continuation_rejects_excessive_column_count (self ) -> None :
705- """decode_rows_continuation should reject column_count exceeding _MAX_COLUMN_COUNT."""
706- import pytest
707-
708- from dqlitewire .exceptions import DecodeError
709-
710- body = b"\xff " * 8 # DONE marker
711- excessive = 20_000
712- with pytest .raises (DecodeError , match = "exceeds maximum" ):
713- RowsResponse .decode_rows_continuation (
714- body ,
715- column_names = ["c" ] * excessive ,
716- column_count = excessive ,
717- )
718-
719578
720579class TestRowsResponseValueTypes :
721580 """Full RowsResponse round-trips with BOOLEAN, UNIXTIME, ISO8601, and BLOB."""
0 commit comments