@@ -436,11 +436,11 @@ def on_change(
436436 2025 , 4 , 23 , 10 , 13 , tzinfo = datetime .timezone .utc
437437 )
438438 assert row ["i" ].value == SimpleObject ("test" )
439- assert row ["j" ] == pd .Timedelta ("4 days 2 seconds 123 us" ). value // 1_000
439+ assert row ["j" ] == pd .Timedelta ("4 days 2 seconds 123 us" )
440440 assert row ["k" ] == ("abc" , "def" , "ghi" )
441441 assert row ["l" ] == (
442- pd .Timedelta ("4 days 2 seconds 123 us" ). value // 1_000 ,
443- pd .Timedelta ("1 days 2 seconds 3 us" ). value // 1_000 ,
442+ pd .Timedelta ("4 days 2 seconds 123 us" ),
443+ pd .Timedelta ("1 days 2 seconds 3 us" ),
444444 )
445445 assert row ["m" ] == (("a" , "b" ), ("c" , "d" ))
446446
@@ -2117,6 +2117,95 @@ def stream_target():
21172117 assert oor_id not in ids_out , f"Out-of-range row { oor_id } must be skipped"
21182118
21192119
2120+ @pytest .mark .parametrize ("pg_type" , ["BIGINT" , "INTEGER" ])
2121+ def test_static_int_read_as_duration (tmp_path , postgres , pg_type ):
2122+ """Integral columns declared as pw.Duration must be returned as Duration values
2123+ (nanoseconds in jsonlines output) in static read mode."""
2124+
2125+ class InputSchema (pw .Schema ):
2126+ id : int = pw .column_definition (primary_key = True )
2127+ duration_col : pw .Duration
2128+
2129+ output_path = tmp_path / "output.jsonl"
2130+ table_name = postgres .random_table_name ()
2131+
2132+ postgres .execute_sql (
2133+ f"""
2134+ CREATE TABLE { table_name } (
2135+ id { pg_type } PRIMARY KEY,
2136+ duration_col { pg_type } NOT NULL
2137+ );
2138+ """
2139+ )
2140+ # 10 seconds expressed in microseconds, as the output connector writes pw.Duration
2141+ microseconds = 10_000_000
2142+ postgres .execute_sql (
2143+ f"INSERT INTO { table_name } (id, duration_col) VALUES (1, { microseconds } );"
2144+ )
2145+
2146+ table = pw .io .postgres .read (
2147+ postgres_settings = POSTGRES_SETTINGS ,
2148+ table_name = table_name ,
2149+ schema = InputSchema ,
2150+ mode = "static" ,
2151+ )
2152+ pw .io .jsonlines .write (table , output_path )
2153+ run ()
2154+
2155+ rows = []
2156+ with open (output_path ) as f :
2157+ for line in f :
2158+ rows .append (json .loads (line ))
2159+
2160+ assert len (rows ) == 1
2161+ # pw.Duration is serialized to jsonlines as nanoseconds
2162+ assert rows [0 ]["duration_col" ] == microseconds * 1_000
2163+
2164+
2165+ def test_static_bigint_array_read_as_duration_list (tmp_path , postgres ):
2166+ """BIGINT[] columns declared as list[pw.Duration] must be returned as lists of
2167+ Duration values (each element in nanoseconds in jsonlines) in static read mode."""
2168+
2169+ class InputSchema (pw .Schema ):
2170+ id : int = pw .column_definition (primary_key = True )
2171+ durations : list [pw .Duration ]
2172+
2173+ output_path = tmp_path / "output.jsonl"
2174+ table_name = postgres .random_table_name ()
2175+
2176+ postgres .execute_sql (
2177+ f"""
2178+ CREATE TABLE { table_name } (
2179+ id BIGINT PRIMARY KEY,
2180+ durations BIGINT[] NOT NULL
2181+ );
2182+ """
2183+ )
2184+ # Three durations in microseconds: 1s, 2s, 3s
2185+ postgres .execute_sql (
2186+ f"INSERT INTO { table_name } (id, durations)"
2187+ f" VALUES (1, ARRAY[1000000, 2000000, 3000000]::BIGINT[]);"
2188+ )
2189+
2190+ table = pw .io .postgres .read (
2191+ postgres_settings = POSTGRES_SETTINGS ,
2192+ table_name = table_name ,
2193+ schema = InputSchema ,
2194+ mode = "static" ,
2195+ )
2196+ pw .io .jsonlines .write (table , output_path )
2197+ run ()
2198+
2199+ rows = []
2200+ with open (output_path ) as f :
2201+ for line in f :
2202+ rows .append (json .loads (line ))
2203+
2204+ assert len (rows ) == 1
2205+ # Each Duration element serialized as nanoseconds
2206+ assert rows [0 ]["durations" ] == [1_000_000_000 , 2_000_000_000 , 3_000_000_000 ]
2207+
2208+
21202209def test_no_publication (tmp_path , postgres ):
21212210 class InputSchema (pw .Schema ):
21222211 value : str
0 commit comments