3131import java .sql .Statement ;
3232import java .time .LocalDate ;
3333import java .time .LocalDateTime ;
34+ import java .time .OffsetDateTime ;
35+ import java .time .ZoneOffset ;
3436import java .util .ArrayList ;
3537import java .util .Arrays ;
3638import java .util .List ;
39+ import java .util .UUID ;
3740import java .util .stream .Stream ;
3841
3942import static org .junit .jupiter .api .Assertions .assertEquals ;
@@ -55,18 +58,35 @@ class PostgresIntegrationTest {
5558 private static final String SCHEMA = """
5659 CREATE TABLE users
5760 (
58- id BIGINT PRIMARY KEY,
59- code INTEGER NOT NULL,
60- score SMALLINT,
61- name VARCHAR(255),
62- bio TEXT,
63- active BOOLEAN,
64- birth_date DATE,
65- created_at TIMESTAMP,
66- balance DECIMAL(10, 2)
61+ id BIGINT PRIMARY KEY,
62+ code INTEGER NOT NULL,
63+ score SMALLINT,
64+ name VARCHAR(255),
65+ bio TEXT,
66+ active BOOLEAN,
67+ birth_date DATE,
68+ created_at TIMESTAMP,
69+ balance DECIMAL(10, 2),
70+ serial_id SERIAL,
71+ revision BIGSERIAL,
72+ external_id UUID,
73+ updated_at TIMESTAMP(3) WITH TIME ZONE
6774 );
6875 """ ;
6976
77+ private static final UUID EXTERNAL_ID = UUID .fromString ("3f2504e0-4f89-11d3-9a0c-0305e82c3301" );
78+
79+ private static final OffsetDateTime UPDATED_AT = OffsetDateTime .of (
80+ 2026 ,
81+ 1 ,
82+ 1 ,
83+ 10 ,
84+ 0 ,
85+ 0 ,
86+ 0 ,
87+ ZoneOffset .ofHours (3 )
88+ );
89+
7090 private static PostgreSQLContainer <?> postgres ;
7191
7292 private static DataSource dataSource ;
@@ -291,11 +311,13 @@ void shouldBindAndReadNullValuesThroughGeneratedCode() throws Exception {
291311 Path classesDirectory = generateAndCompile ("""
292312 -- name: InsertUser :exec
293313 INSERT INTO users
294- (id, code, score, name, bio, active, birth_date, created_at, balance)
295- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
314+ (id, code, score, name, bio, active, birth_date, created_at, balance,
315+ external_id, updated_at)
316+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);
296317
297318 -- name: GetUser :one
298- SELECT id, code, score, name, bio, active, birth_date, created_at, balance
319+ SELECT id, code, score, name, bio, active, birth_date, created_at, balance,
320+ external_id, updated_at
299321 FROM users
300322 WHERE id = $1;
301323 """ );
@@ -313,7 +335,9 @@ void shouldBindAndReadNullValuesThroughGeneratedCode() throws Exception {
313335 Boolean .class ,
314336 LocalDate .class ,
315337 LocalDateTime .class ,
316- BigDecimal .class
338+ BigDecimal .class ,
339+ UUID .class ,
340+ OffsetDateTime .class
317341 );
318342
319343 Object affectedRows = insertMethod .invoke (
@@ -326,6 +350,8 @@ void shouldBindAndReadNullValuesThroughGeneratedCode() throws Exception {
326350 null ,
327351 null ,
328352 null ,
353+ null ,
354+ null ,
329355 null
330356 );
331357
@@ -348,6 +374,81 @@ void shouldBindAndReadNullValuesThroughGeneratedCode() throws Exception {
348374 assertNull (component (result , "birth_date" ));
349375 assertNull (component (result , "created_at" ));
350376 assertNull (component (result , "balance" ));
377+ assertNull (component (result , "external_id" ));
378+ assertNull (component (result , "updated_at" ));
379+ }
380+ }
381+
382+ /**
383+ * Covers the PostgreSQL round trip of the serial, UUID, and
384+ * timestamptz mappings: every value is written through the generated
385+ * write method and read back through the generated read method.
386+ *
387+ * <p>PostgreSQL normalizes a {@code timestamptz} to the session time zone,
388+ * so the read value is compared by instant rather than by offset.
389+ */
390+ @ Test
391+ void shouldRoundTripSerialUuidAndTimestampWithTimeZoneValues () throws Exception {
392+ Path classesDirectory = generateAndCompile ("""
393+ -- name: InsertUser :exec
394+ INSERT INTO users (id, code, serial_id, revision, external_id, updated_at)
395+ VALUES ($1, $2, $3, $4, $5, $6);
396+
397+ -- name: GetUser :one
398+ SELECT id, serial_id, revision, external_id, updated_at
399+ FROM users
400+ WHERE id = $1;
401+ """ );
402+
403+ try (URLClassLoader classLoader = classLoader (classesDirectory )) {
404+ Object insert = newQuery (classLoader , "InsertUser" );
405+
406+ Method insertMethod = insert .getClass ().getMethod (
407+ "insertUser" ,
408+ Long .class ,
409+ Integer .class ,
410+ Integer .class ,
411+ Long .class ,
412+ UUID .class ,
413+ OffsetDateTime .class
414+ );
415+
416+ Object affectedRows = insertMethod .invoke (
417+ insert ,
418+ 7L ,
419+ 42 ,
420+ 101 ,
421+ 202L ,
422+ EXTERNAL_ID ,
423+ UPDATED_AT
424+ );
425+
426+ assertEquals (1 , affectedRows );
427+
428+ Object query = newQuery (classLoader , "GetUser" );
429+
430+ Method queryMethod = query .getClass ().getMethod ("getUser" , Long .class );
431+
432+ Object result = queryMethod .invoke (query , 7L );
433+
434+ assertNotNull (result );
435+
436+ assertEquals (
437+ List .of ("id" , "serial_id" , "revision" , "external_id" , "updated_at" ),
438+ recordComponentNames (result )
439+ );
440+
441+ assertEquals (7L , component (result , "id" ));
442+ assertEquals (101 , component (result , "serial_id" ));
443+ assertEquals (202L , component (result , "revision" ));
444+ assertEquals (EXTERNAL_ID , component (result , "external_id" ));
445+
446+ OffsetDateTime updatedAt = assertInstanceOf (
447+ OffsetDateTime .class ,
448+ component (result , "updated_at" )
449+ );
450+
451+ assertEquals (UPDATED_AT .toInstant (), updatedAt .toInstant ());
351452 }
352453 }
353454
0 commit comments