Skip to content

Commit af1e59d

Browse files
authored
Merge pull request #432 from stephenafamo/stdlib-null
Use sql.Null and pointers for null and optional
2 parents 22a1ea2 + bf91008 commit af1e59d

File tree

21 files changed

+278
-172
lines changed

21 files changed

+278
-172
lines changed

.github/workflows/test.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ jobs:
99
runs-on: ubuntu-latest
1010
strategy:
1111
matrix:
12-
go: [ 'oldstable', 'stable' ]
12+
go: [ 'stable' ]
13+
# Disable oldstable until 1.25 is released
14+
# tests with `sql.Null[T]` break when T is primitive, but not one of the driver.Value types
15+
# go: [ 'oldstable', 'stable' ]
1316

1417
steps:
1518
- name: Checkout Repo

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
- Removed the `New` and `NewQueryer` functions. Any of `bob.NewDB`, `bob.NewTx` or `bob.NewConn` can be used instead.
2525
- Removed the `StdInterface` interface as it is unnecessary.
2626
- Remove redundant `orm.Model` interface.
27+
- Remove dependence on `github.com/aarondl/opt` in generated code.
28+
- Nullable values are now wrapped `database/sql.Null` with instead of `github.com/aarondl/opt/null.Val`.
29+
- Optional values are now represented as pointers instead of `github.com/aarondl/opt/omit.Val[T]`.
2730

2831
## [v0.36.1] - 2025-05-27
2932

gen/bobgen-helpers/helpers.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -505,10 +505,12 @@ func Types() drivers.Types {
505505
DependsOn: []string{"string"},
506506
Imports: language.ImportList{`"github.com/stephenafamo/bob/types/pgtypes"`},
507507
RandomExpr: `hs := make(pgtypes.HStore)
508-
for i := range f.IntBetween(1, 5) {
509-
hs[random_string(f)] = null.FromCond(random_string(f), f.Bool())
508+
for range f.IntBetween(1, 5) {
509+
hs[random_string(f)] = sql.Null[string]{V: random_string(f, limits...), Valid: f.Bool()}
510510
}
511511
return hs`,
512+
RandomExprImports: language.ImportList{`"database/sql"`},
513+
CompareExpr: `AAA.String() == BBB.String()`,
512514
},
513515
"types.JSON[json.RawMessage]": {
514516
Imports: language.ImportList{

gen/bobgen-mysql/templates/models/table/100_blocks.go.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (s *{{$tAlias.UpSingular}}Setter) Apply(q *dialect.InsertQuery) {
3131
{{range $index, $column := $table.NonGeneratedColumns -}}
3232
bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error){
3333
{{$colAlias := $tAlias.Column $column.Name -}}
34-
if s.{{$colAlias}}.IsUnset() {
34+
if s.{{$colAlias}} == nil {
3535
return {{$.Dialect}}.Raw("DEFAULT").WriteSQL(ctx, w, d, start)
3636
}
3737
return {{$.Dialect}}.Arg(s.{{$colAlias}}).WriteSQL(ctx, w, d, start)

gen/bobgen-psql/driver/exclude-tables.golden.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,6 +2411,30 @@
24112411
"type": "pgtypes.Snapshot",
24122412
"type_limits": null
24132413
},
2414+
{
2415+
"name": "hstore_null",
2416+
"db_type": "hstore",
2417+
"default": "NULL",
2418+
"comment": "",
2419+
"nullable": true,
2420+
"generated": false,
2421+
"autoincr": false,
2422+
"domain_name": "",
2423+
"type": "pgtypes.HStore",
2424+
"type_limits": null
2425+
},
2426+
{
2427+
"name": "hstore_nnull",
2428+
"db_type": "hstore",
2429+
"default": "",
2430+
"comment": "",
2431+
"nullable": false,
2432+
"generated": false,
2433+
"autoincr": false,
2434+
"domain_name": "",
2435+
"type": "pgtypes.HStore",
2436+
"type_limits": null
2437+
},
24142438
{
24152439
"name": "xml_null",
24162440
"db_type": "xml",
@@ -4851,6 +4875,30 @@
48514875
"type": "pgtypes.Snapshot",
48524876
"type_limits": null
48534877
},
4878+
{
4879+
"name": "hstore_null",
4880+
"db_type": "hstore",
4881+
"default": "NULL",
4882+
"comment": "",
4883+
"nullable": true,
4884+
"generated": false,
4885+
"autoincr": false,
4886+
"domain_name": "",
4887+
"type": "pgtypes.HStore",
4888+
"type_limits": null
4889+
},
4890+
{
4891+
"name": "hstore_nnull",
4892+
"db_type": "hstore",
4893+
"default": "NULL",
4894+
"comment": "",
4895+
"nullable": true,
4896+
"generated": false,
4897+
"autoincr": false,
4898+
"domain_name": "",
4899+
"type": "pgtypes.HStore",
4900+
"type_limits": null
4901+
},
48544902
{
48554903
"name": "xml_null",
48564904
"db_type": "xml",

gen/bobgen-psql/driver/psql.golden.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2593,6 +2593,30 @@
25932593
"type": "pgtypes.Snapshot",
25942594
"type_limits": null
25952595
},
2596+
{
2597+
"name": "hstore_null",
2598+
"db_type": "hstore",
2599+
"default": "NULL",
2600+
"comment": "",
2601+
"nullable": true,
2602+
"generated": false,
2603+
"autoincr": false,
2604+
"domain_name": "",
2605+
"type": "pgtypes.HStore",
2606+
"type_limits": null
2607+
},
2608+
{
2609+
"name": "hstore_nnull",
2610+
"db_type": "hstore",
2611+
"default": "",
2612+
"comment": "",
2613+
"nullable": false,
2614+
"generated": false,
2615+
"autoincr": false,
2616+
"domain_name": "",
2617+
"type": "pgtypes.HStore",
2618+
"type_limits": null
2619+
},
25962620
{
25972621
"name": "xml_null",
25982622
"db_type": "xml",
@@ -5033,6 +5057,30 @@
50335057
"type": "pgtypes.Snapshot",
50345058
"type_limits": null
50355059
},
5060+
{
5061+
"name": "hstore_null",
5062+
"db_type": "hstore",
5063+
"default": "NULL",
5064+
"comment": "",
5065+
"nullable": true,
5066+
"generated": false,
5067+
"autoincr": false,
5068+
"domain_name": "",
5069+
"type": "pgtypes.HStore",
5070+
"type_limits": null
5071+
},
5072+
{
5073+
"name": "hstore_nnull",
5074+
"db_type": "hstore",
5075+
"default": "NULL",
5076+
"comment": "",
5077+
"nullable": true,
5078+
"generated": false,
5079+
"autoincr": false,
5080+
"domain_name": "",
5081+
"type": "pgtypes.HStore",
5082+
"type_limits": null
5083+
},
50365084
{
50375085
"name": "xml_null",
50385086
"db_type": "xml",

gen/bobgen-sqlite/templates/models/table/100_blocks.go.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func (s *{{$tAlias.UpSingular}}Setter) Apply(q *dialect.InsertQuery) {
2323
vals := make([]bob.Expression, 0, {{len $table.NonGeneratedColumns}})
2424
{{range $index, $column := $table.NonGeneratedColumns -}}
2525
{{$colAlias := $tAlias.Column $column.Name -}}
26-
if !s.{{$colAlias}}.IsUnset() {
26+
if s.{{$colAlias}} != nil {
2727
vals = append(vals, {{$.Dialect}}.Arg(s.{{$colAlias}}))
2828
}
2929

gen/drivers/query.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,10 @@ func (c QueryArg) RandomExpr(i language.Importer, types Types) string {
281281
if len(c.Children) == 0 {
282282
if c.Col.Nullable != nil && *c.Col.Nullable {
283283
i.Import("database/sql")
284-
typ = strings.TrimPrefix(typ, "sql.Null[")
285-
typ = strings.TrimSuffix(typ, "]")
286-
normalized := internal.TypesReplacer.Replace(typ)
287-
fmt.Fprintf(&sb, "null.From(random_%s(nil))", normalized)
284+
normalized := strings.TrimPrefix(typ, "sql.Null[")
285+
normalized = strings.TrimSuffix(normalized, "]")
286+
normalized = internal.TypesReplacer.Replace(normalized)
287+
fmt.Fprintf(&sb, "%s{V: random_%s(nil), Valid: true}", typ, normalized)
288288
} else {
289289
normalized := internal.TypesReplacer.Replace(typ)
290290
fmt.Fprintf(&sb, "random_%s(nil)", normalized)

gen/drivers/tables.go

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func (tables Tables[C, I]) ColumnGetter(alias TableAlias, table, column string)
4646
return colAlias
4747
}
4848

49-
return fmt.Sprintf("%s.GetOrZero()", colAlias)
49+
return fmt.Sprintf("%s.V", colAlias)
5050
}
5151

5252
panic("unknown table " + table)
@@ -58,51 +58,90 @@ func (dummyImporter) Import(...string) string { return "" }
5858
func (dummyImporter) ImportList(language.ImportList) string { return "" }
5959
func (dummyImporter) ToList() language.ImportList { return nil }
6060

61-
func (tables Tables[C, I]) ColumnSetter(i language.Importer, aliases Aliases, fromTName, toTName, fromColName, toColName, varName string, fromOpt, toOpt bool) string {
61+
//nolint:gocyclo
62+
func (tables Tables[C, I]) ColumnSetter(i language.Importer, types Types, aliases Aliases, fromTName, toTName, fromColName, toColName, varName string, fromOpt, toOpt bool) string {
6263
fromTable := tables.Get(fromTName)
6364
fromCol := fromTable.GetColumn(fromColName)
6465

6566
toTable := tables.Get(toTName)
6667
toCol := toTable.GetColumn(toColName)
6768
to := fmt.Sprintf("%s.%s", varName, aliases[toTName].Columns[toColName])
6869

70+
typDef := types[toCol.Type]
71+
colType := typDef.AliasOf
72+
if colType == "" {
73+
colType = toCol.Type
74+
}
75+
6976
switch {
77+
//-------------------------------------------
78+
// Same optionality, same nullability
79+
//-------------------------------------------
7080
case (fromOpt == toOpt) && (toCol.Nullable == fromCol.Nullable):
7181
// If both type match, return it plainly
7282
return to
83+
//-------------------------------------------
7384

74-
case !fromOpt && !fromCol.Nullable:
75-
// if from is concrete, then use MustGet()
76-
return fmt.Sprintf("%s.MustGet()", to)
85+
//-------------------------------------------
86+
// Same nullability
87+
//-------------------------------------------
88+
case !fromOpt && toOpt && (fromCol.Nullable == toCol.Nullable):
89+
return fmt.Sprintf("*%s", to)
7790

78-
case fromOpt && fromCol.Nullable && !toOpt && !toCol.Nullable:
79-
i.Import("github.com/aarondl/opt/omitnull")
80-
return fmt.Sprintf("omitnull.From(%s)", to)
91+
case fromOpt && !toOpt && (fromCol.Nullable == toCol.Nullable):
92+
return fmt.Sprintf("&%s", to)
93+
//-------------------------------------------
8194

82-
case fromOpt && fromCol.Nullable && !toOpt && toCol.Nullable:
83-
i.Import("github.com/aarondl/opt/omitnull")
84-
return fmt.Sprintf("omitnull.FromNull(%s)", to)
95+
//-------------------------------------------
96+
// From is nullable
97+
//-------------------------------------------
98+
case fromOpt && fromCol.Nullable && !toOpt && !toCol.Nullable:
99+
i.Import("database/sql")
100+
return fmt.Sprintf("&sql.Null[%s]{V:%s, Valid: true}", colType, to)
85101

86102
case fromOpt && fromCol.Nullable && toOpt && !toCol.Nullable:
87-
i.Import("github.com/aarondl/opt/omitnull")
88-
return fmt.Sprintf("omitnull.FromOmit(%s)", to)
103+
i.Import("database/sql")
104+
return fmt.Sprintf(`func() &sql.Null[%s] {
105+
if %s == nil { return nil }
106+
return &sql.Null[%s]{V: %s, Valid: true}
107+
}()`, colType, to, colType, to)
108+
109+
case !fromOpt && fromCol.Nullable && !toOpt && !toCol.Nullable:
110+
i.Import("database/sql")
111+
return fmt.Sprintf("sql.Null[%s]{V:%s, Valid: true}", colType, to)
112+
113+
case !fromOpt && fromCol.Nullable && toOpt && !toCol.Nullable:
114+
i.Import("database/sql")
115+
return fmt.Sprintf(`func() sql.Null[%s] {
116+
if %s == nil { return nil }
117+
return sql.Null[%s]{V: %s, Valid: true}
118+
}()`, colType, to, colType, to)
119+
//-------------------------------------------
120+
121+
//-------------------------------------------
122+
// From is NOT nullable
123+
//-------------------------------------------
124+
case fromOpt && !fromCol.Nullable && !toOpt && toCol.Nullable:
125+
return fmt.Sprintf("&%s.V", to)
126+
127+
case fromOpt && !fromCol.Nullable && toOpt && toCol.Nullable:
128+
return fmt.Sprintf(`func() *%s {
129+
if %s == nil { return nil }
130+
return &%s.V
131+
}()`, colType, to, to)
132+
133+
case !fromOpt && !fromCol.Nullable && !toOpt && toCol.Nullable:
134+
return fmt.Sprintf("%s.V", to)
135+
136+
case !fromOpt && !fromCol.Nullable && toOpt && toCol.Nullable:
137+
return fmt.Sprintf(`func() %s {
138+
if %s == nil { return nil }
139+
return %s.V
140+
}()`, colType, to, to)
141+
//-------------------------------------------
89142

90143
default:
91-
// from is either omit or null
92-
val := "omit"
93-
if fromCol.Nullable {
94-
val = "null"
95-
}
96-
97-
i.Import(fmt.Sprintf("github.com/aarondl/opt/%s", val))
98-
99-
switch {
100-
case !toOpt && !toCol.Nullable:
101-
return fmt.Sprintf("%s.From(%s)", val, to)
102-
103-
default:
104-
return fmt.Sprintf("%s.FromCond(%s.GetOrZero(), %s.IsSet())", val, to, to)
105-
}
144+
panic(fmt.Sprintf("unknown column setter case: %s.%s -> %s.%s", fromTName, fromColName, toTName, toColName))
106145
}
107146
}
108147

@@ -239,7 +278,7 @@ func (tables Tables[C, I]) RelDependenciesTypSet(aliases Aliases, r orm.Relation
239278
return strings.Join(ma, "\n")
240279
}
241280

242-
func (tables Tables[C, I]) SetFactoryDeps(i language.Importer, aliases Aliases, r orm.Relationship, inLoop bool) string {
281+
func (tables Tables[C, I]) SetFactoryDeps(i language.Importer, types Types, aliases Aliases, r orm.Relationship, inLoop bool) string {
243282
local := r.Local()
244283
foreign := r.Foreign()
245284
ksides := r.ValuedSides()
@@ -290,7 +329,8 @@ func (tables Tables[C, I]) SetFactoryDeps(i language.Importer, aliases Aliases,
290329

291330
extObjVarName := getVarName(aliases, mapp.ExternalTable, mapp.ExternalStart, mapp.ExternalEnd, false)
292331

293-
oSetter := tables.ColumnSetter(i, aliases,
332+
oSetter := tables.ColumnSetter(
333+
i, types, aliases,
294334
kside.TableName, mapp.ExternalTable,
295335
mapp.Column, mapp.ExternalColumn,
296336
extObjVarName, false, false)

gen/templates/factory/table/01_types.go.tpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ type {{$tAlias.UpSingular}}Template struct {
3030
{{- $.Importer.ImportList $typDef.Imports -}}
3131
{{- $colAlias := $tAlias.Column $column.Name -}}
3232
{{- if $column.Nullable -}}
33-
{{- $.Importer.Import "github.com/aarondl/opt/null" -}}
34-
{{- $colTyp = printf "null.Val[%s]" $colTyp -}}
33+
{{- $.Importer.Import "database/sql" -}}
34+
{{- $colTyp = printf "sql.Null[%s]" $colTyp -}}
3535
{{- end -}}
3636
{{$colAlias}} func() {{$colTyp}}
3737
{{end -}}

gen/templates/factory/table/02_build.go.tpl

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ func (t {{$tAlias.UpSingular}}Template) setModelRels(o *models.{{$tAlias.UpSingu
2424
rel.R.{{$invAlias}} = append(rel.R.{{$invAlias}}, o)
2525
{{- end}}
2626
{{- end}}
27-
{{$.Tables.SetFactoryDeps $.Importer $.Aliases . false}}
27+
{{$.Tables.SetFactoryDeps $.Importer $.Types $.Aliases . false}}
2828
{{- else -}}
2929
rel := models.{{$ftable.UpSingular}}Slice{}
3030
for _, r := range t.r.{{$relAlias}} {
3131
related := r.o.toModels(r.number)
32-
{{- $setter := $.Tables.SetFactoryDeps $.Importer $.Aliases . false}}
32+
{{- $setter := $.Tables.SetFactoryDeps $.Importer $.Types $.Aliases . false}}
3333
{{- if or $setter (and (not $.NoBackReferencing) $invRel.Name) }}
3434
for _, rel := range related {
3535
{{$setter}}
@@ -61,13 +61,8 @@ func (o {{$tAlias.UpSingular}}Template) BuildSetter() *models.{{$tAlias.UpSingul
6161
{{- if $column.Generated}}{{continue}}{{end -}}
6262
{{$colAlias := $tAlias.Column $column.Name -}}
6363
if o.{{$colAlias}} != nil {
64-
{{if $column.Nullable -}}
65-
{{- $.Importer.Import "github.com/aarondl/opt/omitnull" -}}
66-
m.{{$colAlias}} = omitnull.FromNull(o.{{$colAlias}}())
67-
{{else -}}
68-
{{- $.Importer.Import "github.com/aarondl/opt/omit" -}}
69-
m.{{$colAlias}} = omit.From(o.{{$colAlias}}())
70-
{{end -}}
64+
val := o.{{$colAlias}}()
65+
m.{{$colAlias}} = &val
7166
}
7267
{{end}}
7368

0 commit comments

Comments
 (0)