Skip to content

Conversation

@Serjlee
Copy link

@Serjlee Serjlee commented Nov 12, 2025

Hello there!

This PR exposes a TypeConverter I found in the source code, currently only used by Preloaders to handle NULL results returned by LEFT JOINS. I figured it can be very useful to reuse it for custom queries, so I propose to expose it as part of Bob's API.

Let me know if you think this makes sense: I can help in writing tests o tweaking the PR to make it more likely to be merged.


Use case

Since a while I started using Bob in a few fairly complex projects, and I now find myself building overcomplicated queries that can't really be expressed with just the ORM functions, but need to be built with Bob's query builder to handle all the joins, and CTEs, and subqueries, etc.

The query builder itself works quite well for my use case, but more often than not I want to reuse structs and constants generated by Bob instead of using custom structs or hardcoding strings:

I usually do it with the following approach:

//  struct generated by bob, in thing.bob.go
type Thing struct {
    ID        string              `db:"id,pk" `
    Name      string              `db:"name" `
    Country   sql.Null[string]    `db:"country" `
   }

// my custom row struct
type myRow struct {
    Something  `db:"something"`
    ... so many other cols
    AThing gen.Thing `db:"thing"` // will be populated by selecting thing.id, thing.name, thing.country
    OtherStuff gen.Stuff `db:"stuff"` // same
}


// Somewhere else in the code, my query:
 bob.All(ctx, db,
   psql.Select(
      sm.Columns(
        "something",
         ... a lot of fields...,
         gen.Things.Columns.WithPrefix("thing.")), // select all columns of thing, making sure to add a prefix to auto-populate the struct
         gen.Stuffs.Columns.WithPrefix("stuff.")), // same
      sm.From(...),
      sm.LeftJoin(gen.Things.Name()).As("thing").On(
         gen.Things.Columns.AliasedAs("thing").ID.EQ(...),
         ),
 	), scan.StructMapper[myRow]())

This approach works well but breaks down as soon as the LeftJoin in the example doesn't match any row, causing NULLs to be returned and scanned:

sql: Scan error on column index 7, name \"vendor.id\": converting NULL to string is unsupported"

Using a ThingSetter as a result target almost works but of course it loses all the generated fields.

.. But it turns out the orm package was hiding a TypeConverter apparently perfect for my use case, where I just want NULL relationships to be expressed by empty structs instead of breaking down:

  ...  
), scan.StructMapper[myRow](scan.WithTypeConverter(orm.NullTypeConverter{})))

Which results in a myRow{} struct with a zero AThing field, just like I wanted

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant