diff --git a/compiler/semantic/op.go b/compiler/semantic/op.go index 1998da939..68389c94c 100644 --- a/compiler/semantic/op.go +++ b/compiler/semantic/op.go @@ -156,7 +156,7 @@ func (t *translator) sqlTableExpr(e ast.SQLTableExpr, seq sem.Seq) (sem.Seq, rel } var typ super.Type seq, typ, name = t.fromSource(input.Source, input.Args, seq) - table = newTableFromType(typ) + table = t.newTableFromType(typ) if _, ok := table.(*dynamicTable); !ok && alias == nil { alias = &ast.TableAlias{Name: name, Loc: input.Loc} } diff --git a/compiler/semantic/schema.go b/compiler/semantic/schema.go index 4dc7c2e14..edd01a717 100644 --- a/compiler/semantic/schema.go +++ b/compiler/semantic/schema.go @@ -114,19 +114,19 @@ type ( } ) -func newTableFromType(typ super.Type) relTable { +func (t *translator) newTableFromType(typ super.Type) relTable { if typ == badType { return badTable } if typ != nil { - if recType := recordOf(typ); recType != nil { + if recType := t.recordOf(typ); recType != nil { return &staticTable{typ: recType} } } return &dynamicTable{} } -func recordOf(typ super.Type) *super.TypeRecord { +func (t *translator) recordOf(typ super.Type) *super.TypeRecord { switch typ := super.TypeUnder(typ).(type) { case *super.TypeRecord: return typ @@ -134,15 +134,22 @@ func recordOf(typ super.Type) *super.TypeRecord { if hasUnknown(typ) { return nil } - // We assume here this is a fused type and thus has only one record - // type in the union. + // A fused type has at most one unnamed record type in a union, but + // the fuser keeps named types apart, so a union like foo|{b:int64} + // can have more than one record type. Fuse them so the table has + // the columns of all of them. + var recs []super.Type for _, typ := range typ.Types { - if typ := recordOf(typ); typ != nil { - return typ + if typ := t.recordOf(typ); typ != nil { + recs = append(recs, typ) } } + if len(recs) > 0 { + recType, _ := super.TypeUnder(t.checker.fuse(recs)).(*super.TypeRecord) + return recType + } case *super.TypeFusion: - return recordOf(typ.Type) + return t.recordOf(typ.Type) } return nil } diff --git a/compiler/semantic/sql.go b/compiler/semantic/sql.go index b36fbcb8d..7c43812e7 100644 --- a/compiler/semantic/sql.go +++ b/compiler/semantic/sql.go @@ -301,9 +301,9 @@ func (t *translator) selectFrom(loc ast.Loc, from ast.SQLTableExpr, seq sem.Seq, // in which case we should model an empty table. if inType == nil || inType == super.TypeNull { // Model no or null input as an input table so that column checks fail. - return seq, newTableFromType(t.sctx.MustLookupTypeRecord([]super.Field{})) + return seq, t.newTableFromType(t.sctx.MustLookupTypeRecord([]super.Field{})) } - return seq, newTableFromType(inType) + return seq, t.newTableFromType(inType) } off := len(seq) hasParent := off > 0 @@ -372,7 +372,7 @@ func (t *translator) sqlFromPipe(pipe *ast.SQLPipe, seq sem.Seq) (sem.Seq, relTa // join where the input to the pipe might depend on the table-expression to the left // may change how we wire things up here. seq, typ := t.seq(pipe.Body, super.TypeNull) - return seq, newTableFromType(typ) + return seq, t.newTableFromType(typ) } func maybeSQLQueryBody(pipe *ast.SQLPipe) (ast.SQLQueryBody, bool) { diff --git a/compiler/ztests/sql/issue-7468.yaml b/compiler/ztests/sql/issue-7468.yaml index c82ebb226..63fc8b05a 100644 --- a/compiler/ztests/sql/issue-7468.yaml +++ b/compiler/ztests/sql/issue-7468.yaml @@ -1,17 +1,28 @@ # A SQL column reference to a field the BSUP reader projects from a record -# type that doesn't have it is null rather than an error. +# type that doesn't have it is null rather than an error, and the columns of +# input with a named record type include the fields of its other record types. script: | super -f bsup -o t1.bsup -c "type foo={a:int64} values cast({a:1},foo), {b:2}" super -f bsup -o t2.bsup -c "values {a:1}, {b:2}" super -s -c "SELECT * FROM 't1.bsup'" echo === + super -s -c "SELECT b FROM 't1.bsup'" + echo === super -s -c "SELECT a FROM 't2.bsup'" + echo === + super -s -c "type foo={a:int64} type bar={b:int64} values cast({a:1},foo), cast({b:2},bar) | SELECT *" outputs: - name: stdout data: | - {a:1} - {a:null} + {a:1,b:null} + {a:null,b:2} + === + {b:null} + {b:2} === {a:1} {a:null} + === + {a:1,b:null} + {a:null,b:2}