From 698c4718565e71600d273c50b65cafa8d9c18f62 Mon Sep 17 00:00:00 2001 From: Lorenz Brun Date: Mon, 16 Oct 2023 00:34:03 +0200 Subject: [PATCH] Improve nullability evaluation This now supports evaluating nullability if subqueries, function-defined tables or sqlc.embed macros are involved. If nullable embeds are generated, this now errors out instead of emitting code which breaks at runtime. Generating correct type definitions for that case would be easy, but scanning data into embed pointers is hard as it would require "peeking" at row nullability to decide if the type should be allocated and scanned into. Otherwise the null columns need to be directed into some kind of sink. Because this is nontrivial to do this is not part of this PR. --- internal/codegen/golang/result.go | 6 +++++- internal/compiler/output_columns.go | 27 ++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/internal/codegen/golang/result.go b/internal/codegen/golang/result.go index 03e2d21fcd..e1ec7afd6c 100644 --- a/internal/codegen/golang/result.go +++ b/internal/codegen/golang/result.go @@ -379,7 +379,11 @@ func columnsToStruct(req *plugin.CodeGenRequest, options *opts, name string, col if c.embed == nil { f.Type = goType(req, options, c.Column) } else { - f.Type = c.embed.modelType + if c.NotNull { + f.Type = c.embed.modelType + } else { + return nil, fmt.Errorf("embedded macro for %q is nullable, this is currently unsupported", c.embed.modelName) + } f.EmbedFields = c.embed.fields } diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index 8dfca0c5d3..eb0382e737 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -270,6 +270,10 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er cols = append(cols, &Column{ Name: embed.Table.Name, EmbedTable: embed.Table, + // In general, embeds should not be nullable. + // If joins require them to be, this will be overriden + // later on. + NotNull: true, }) continue } @@ -399,7 +403,7 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er if n, ok := node.(*ast.SelectStmt); ok { for _, col := range cols { - if !col.NotNull || col.Table == nil || col.skipTableRequiredCheck { + if !col.NotNull || col.skipTableRequiredCheck { continue } for _, f := range n.FromClause.Items { @@ -423,10 +427,27 @@ const ( func isTableRequired(n ast.Node, col *Column, prior int) int { switch n := n.(type) { case *ast.RangeVar: - if n.Alias == nil && *n.Relname == col.Table.Name { + var tblName string + if col.Table != nil { + tblName = col.Table.Name + } else if col.EmbedTable != nil { + tblName = col.EmbedTable.Name + } + if tblName == "" { + return tableNotFound + } + if n.Alias == nil && *n.Relname == tblName { + return prior + } + if n.Alias != nil && *n.Alias.Aliasname == col.TableAlias && *n.Relname == tblName { + return prior + } + case *ast.RangeSubselect: + if n.Alias != nil && n.Alias.Aliasname != nil && *n.Alias.Aliasname == col.TableAlias { return prior } - if n.Alias != nil && *n.Alias.Aliasname == col.TableAlias && *n.Relname == col.Table.Name { + case *ast.RangeFunction: + if n.Alias != nil && n.Alias.Aliasname != nil && *n.Alias.Aliasname == col.TableAlias { return prior } case *ast.JoinExpr: