• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stephenafamo / bob / 26222913083

21 May 2026 10:57AM UTC coverage: 45.443% (+0.2%) from 45.289%
26222913083

Pull #690

github

Roman A. Grigorovich
feat(psql): add multiple FROM/USING sources, join chains, and Joinable typing

Support variadic FROM/USING with inline joins, table functions, JoinChain[Q Joinable],
and AppendJoin on the last from_item for UPDATE/DELETE queries.
Pull Request #690: feat(psql): multiple FROM/USING sources, join chains, and table functions

108 of 162 new or added lines in 14 files covered. (66.67%)

21 existing lines in 2 files now uncovered.

11115 of 24459 relevant lines covered (45.44%)

656.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.88
/dialect/psql/dialect/function.go
1
package dialect
2

3
import (
4
        "context"
5
        "io"
6

7
        "github.com/stephenafamo/bob"
8
        "github.com/stephenafamo/bob/clause"
9
        "github.com/stephenafamo/bob/expr"
10
)
11

12
func NewFunction(name string, args ...any) *Function {
42✔
13
        f := &Function{name: name, args: args}
42✔
14
        f.Chain = expr.Chain[Expression, Expression]{Base: f}
42✔
15

42✔
16
        return f
42✔
17
}
42✔
18

19
type Function struct {
20
        name string
21
        args []any
22

23
        Distinct    bool
24
        WithinGroup bool
25
        clause.OrderBy
26
        Filter []any
27
        w      *clause.Window
28

29
        Alias   string // used when there should be an alias before the columns
30
        Columns []columnDef
31

32
        expr.Chain[Expression, Expression]
33
}
34

35
func (f *Function) SetWindow(w clause.Window) {
6✔
36
        f.w = &w
6✔
37
}
6✔
38

39
func (f *Function) AppendColumn(name, datatype string) {
14✔
40
        f.Columns = append(f.Columns, columnDef{
14✔
41
                name:     name,
14✔
42
                dataType: datatype,
14✔
43
        })
14✔
44
}
14✔
45

46
func (f *Function) WriteSQL(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
42✔
47
        if f.name == "" {
42✔
48
                return nil, nil
×
49
        }
×
50

51
        w.WriteString(f.name)
42✔
52
        w.WriteString("(")
42✔
53

42✔
54
        if f.Distinct {
42✔
55
                w.WriteString("DISTINCT ")
×
56
        }
×
57

58
        args, err := bob.ExpressSlice(ctx, w, d, start, f.args, "", ", ", "")
42✔
59
        if err != nil {
42✔
60
                return nil, err
×
61
        }
×
62

63
        if !f.WithinGroup {
84✔
64
                orderArgs, err := bob.ExpressIf(ctx, w, d, start+len(args), f.OrderBy,
42✔
65
                        len(f.OrderBy.Expressions) > 0, " ", "")
42✔
66
                if err != nil {
42✔
67
                        return nil, err
×
68
                }
×
69
                args = append(args, orderArgs...)
42✔
70
        }
71
        w.WriteString(")")
42✔
72

42✔
73
        if f.WithinGroup {
42✔
74
                orderArgs, err := bob.ExpressIf(ctx, w, d, start+len(args), f.OrderBy,
×
75
                        len(f.OrderBy.Expressions) > 0, " WITHIN GROUP (", ")")
×
76
                if err != nil {
×
77
                        return nil, err
×
78
                }
×
79
                args = append(args, orderArgs...)
×
80
        }
81

82
        filterArgs, err := bob.ExpressSlice(ctx, w, d, start, f.Filter, " FILTER (WHERE ", " AND ", ")")
42✔
83
        if err != nil {
42✔
84
                return nil, err
×
85
        }
×
86
        args = append(args, filterArgs...)
42✔
87

42✔
88
        if len(f.Columns) > 0 || len(f.Alias) > 0 {
54✔
89
                w.WriteString(" AS ")
12✔
90
        }
12✔
91

92
        if len(f.Alias) > 0 {
42✔
93
                w.WriteString(f.Alias)
×
94
                w.WriteString(" ")
×
95
        }
×
96

97
        colArgs, err := bob.ExpressSlice(ctx, w, d, start+len(args), f.Columns, "(", ", ", ")")
42✔
98
        if err != nil {
42✔
99
                return nil, err
×
100
        }
×
101
        args = append(args, colArgs...)
42✔
102

42✔
103
        winargs, err := bob.ExpressIf(ctx, w, d, start+len(args), f.w, f.w != nil, "OVER (", ")")
42✔
104
        if err != nil {
42✔
105
                return nil, err
×
106
        }
×
107
        args = append(args, winargs...)
42✔
108

42✔
109
        return args, nil
42✔
110
}
111

112
type columnDef struct {
113
        name     string
114
        dataType string
115
}
116

117
func (c columnDef) WriteSQL(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
14✔
118
        w.WriteString(c.name + " " + c.dataType)
14✔
119

14✔
120
        return nil, nil
14✔
121
}
14✔
122

123
// Functions renders ROWS FROM (f1, f2, ...) for multiple table functions in one
124
// from_item.
125
type Functions []*Function
126

127
// TableFunctions returns a FROM/USING expression for table functions: a single
128
// function_name(...) or ROWS FROM (...) when multiple are given.
129
func TableFunctions(funcs ...*Function) bob.Expression {
18✔
130
        switch len(funcs) {
18✔
NEW
131
        case 0:
×
NEW
132
                return nil
×
133
        case 1:
6✔
134
                return funcs[0]
6✔
135
        default:
12✔
136
                return Functions(funcs)
12✔
137
        }
138
}
139

140
func (f Functions) WriteSQL(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
12✔
141
        if len(f) > 1 {
24✔
142
                w.WriteString("ROWS FROM (")
12✔
143
        }
12✔
144

145
        args, err := bob.ExpressSlice(ctx, w, d, start, f, "", ", ", "")
12✔
146
        if err != nil {
12✔
147
                return nil, err
×
148
        }
×
149

150
        if len(f) > 1 {
24✔
151
                w.WriteString(")")
12✔
152
        }
12✔
153

154
        return args, nil
12✔
155
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc