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

stephenafamo / bob / 26784991996

01 Jun 2026 10:07PM UTC coverage: 46.164% (+0.3%) from 45.83%
26784991996

Pull #701

github

Roman A. Grigorovich
feat(expr): enhance expression handling with base expression utilities

Add BaseExpression method to Chain for direct access to the base expression. Introduce baseExpression interface and findBaseExpression function to streamline the process of peeling chain wrappers. Update leftRight struct to include omitParens functionality for better SQL rendering in expressions.
Pull Request #701: Render EQ in SET without comparison parens; omit redundant AS in NameAsExpr

51 of 56 new or added lines in 9 files covered. (91.07%)

7 existing lines in 2 files now uncovered.

11349 of 24584 relevant lines covered (46.16%)

663.54 hits per line

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

51.92
/dialect/sqlite/view.go
1
package sqlite
2

3
import (
4
        "context"
5
        "fmt"
6
        "reflect"
7

8
        "github.com/stephenafamo/bob"
9
        "github.com/stephenafamo/bob/dialect/sqlite/dialect"
10
        "github.com/stephenafamo/bob/dialect/sqlite/sm"
11
        "github.com/stephenafamo/bob/expr"
12
        "github.com/stephenafamo/bob/internal/mappings"
13
        "github.com/stephenafamo/bob/orm"
14
        "github.com/stephenafamo/scan"
15
)
16

17
// UseSchema modifies a context to add a schema that will be used when
18
// a tablle/view was generated with an empty schema
19
func UseSchema(ctx context.Context, schema string) context.Context {
×
20
        return context.WithValue(ctx, orm.CtxUseSchema, schema)
×
21
}
×
22

23
func NewView[T any, C bob.Expression](schema, tableName string, columns C) *View[T, []T, C] {
2✔
24
        return NewViewx[T, []T](schema, tableName, columns)
2✔
25
}
2✔
26

27
func NewViewx[T any, Tslice ~[]T, C bob.Expression](schema, tableName string, columns C) *View[T, Tslice, C] {
2✔
28
        v, _ := newView[T, Tslice](schema, tableName, columns)
2✔
29
        return v
2✔
30
}
2✔
31

32
func newView[T any, Tslice ~[]T, C bob.Expression](schema, tableName string, columns C) (*View[T, Tslice, C], mappings.Mapping) {
2✔
33
        mappings := mappings.GetMappings(reflect.TypeOf(*new(T)))
2✔
34
        alias := tableName
2✔
35
        if schema != "" {
2✔
36
                alias = fmt.Sprintf("%s.%s", schema, tableName)
×
37
        }
×
38

39
        allCols := expr.NewColumnsExpr(mappings.All...).WithParent(alias)
2✔
40

2✔
41
        return &View[T, Tslice, C]{
2✔
42
                schema:        schema,
2✔
43
                name:          tableName,
2✔
44
                alias:         alias,
2✔
45
                allCols:       allCols,
2✔
46
                returningCols: allCols.WithParent(tableName),
2✔
47
                scanner:       scan.StructMapper[T](),
2✔
48
                Columns:       columns,
2✔
49
        }, mappings
2✔
50
}
51

52
type View[T any, Tslice ~[]T, C bob.Expression] struct {
53
        schema string
54
        name   string
55
        alias  string
56

57
        allCols       expr.ColumnsExpr
58
        returningCols expr.ColumnsExpr
59
        scanner       scan.Mapper[T]
60

61
        Columns C
62

63
        AfterSelectHooks bob.Hooks[Tslice, bob.SkipModelHooksKey]
64
        SelectQueryHooks bob.Hooks[*dialect.SelectQuery, bob.SkipQueryHooksKey]
65
}
66

67
// NameExpr returns the table name as an expression
68
func (v *View[T, Tslice, C]) NameExpr() Expression {
4✔
69
        // schema is not empty, never override
4✔
70
        if v.schema != "" {
4✔
71
                return Quote(v.schema, v.name)
×
72
        }
×
73

74
        return Expression{}.New(orm.SchemaTable(v.name))
4✔
75
}
76

77
// NameAsExpr returns the table name as an expression with an alias when needed.
78
func (v *View[T, Tslice, C]) NameAsExpr() bob.Expression {
4✔
79
        expr := v.NameExpr()
4✔
80
        if v.schema != "" || v.alias != v.name {
4✔
NEW
81
                return expr.As(v.alias)
×
NEW
82
        }
×
83
        return expr
4✔
84
}
85

86
// Alias returns the alias
87
func (v *View[T, Tslice, C]) Alias() string {
×
88
        return v.alias
×
89
}
×
90

91
// Schema returns the schema name for the view
92
func (v *View[T, Tslice, C]) Schema() string {
×
93
        return v.schema
×
94
}
×
95

96
// Name returns the view (table/view) name
97
func (v *View[T, Tslice, C]) Name() string {
×
98
        return v.name
×
99
}
×
100

101
// ColumnsExpr returns a column list expression
102
func (v *View[T, Tslice, C]) ColumnsExpr() expr.ColumnsExpr {
2✔
103
        // get the schema
2✔
104
        return v.allCols
2✔
105
}
2✔
106

107
// Query starts a select query on the view
108
func (v *View[T, Tslice, C]) Query(queryMods ...bob.Mod[*dialect.SelectQuery]) *ViewQuery[T, Tslice] {
2✔
109
        q := &ViewQuery[T, Tslice]{
2✔
110
                Query: orm.Query[*dialect.SelectQuery, T, Tslice, bob.SliceTransformer[T, Tslice]]{
2✔
111
                        ExecQuery: orm.ExecQuery[*dialect.SelectQuery]{
2✔
112
                                BaseQuery: Select(sm.From(v.NameAsExpr())),
2✔
113
                                Hooks:     &v.SelectQueryHooks,
2✔
114
                        },
2✔
115
                        Scanner: v.scanner,
2✔
116
                },
2✔
117
        }
2✔
118

2✔
119
        q.BaseQuery.Expression.AppendContextualModFunc(
2✔
120
                func(ctx context.Context, q *dialect.SelectQuery) (context.Context, error) {
4✔
121
                        if len(q.SelectList.Columns) == 0 {
4✔
122
                                q.AppendSelect(v.ColumnsExpr())
2✔
123
                        }
2✔
124
                        return ctx, nil
2✔
125
                },
126
        )
127

128
        q.Apply(queryMods...)
2✔
129

2✔
130
        return q
2✔
131
}
132

133
type ViewQuery[T any, Ts ~[]T] struct {
134
        orm.Query[*dialect.SelectQuery, T, Ts, bob.SliceTransformer[T, Ts]]
135
}
136

137
// Count the number of matching rows
138
func (v *ViewQuery[T, Tslice]) Count(ctx context.Context, exec bob.Executor) (int64, error) {
×
139
        ctx, err := v.RunHooks(ctx, exec)
×
140
        if err != nil {
×
141
                return 0, err
×
142
        }
×
143
        return bob.One(ctx, exec, asCountQuery(v.BaseQuery), scan.SingleColumnMapper[int64])
×
144
}
145

146
// Exists checks if there is any matching row
147
func (v *ViewQuery[T, Tslice]) Exists(ctx context.Context, exec bob.Executor) (bool, error) {
×
148
        count, err := v.Count(ctx, exec)
×
149
        return count > 0, err
×
150
}
×
151

152
// asCountQuery clones and rewrites an existing query to a count query
153
func asCountQuery(query bob.BaseQuery[*dialect.SelectQuery]) bob.BaseQuery[*dialect.SelectQuery] {
×
154
        // clone the original query, so it's not being modified silently
×
155
        countQuery := query.Clone()
×
156
        // only select the count
×
157
        countQuery.Expression.SetSelect("count(1)")
×
158
        // don't select any preload columns
×
159
        countQuery.Expression.SetPreloadSelect()
×
160
        // disable mapper mods
×
161
        countQuery.Expression.SetMapperMods()
×
162
        // disable loaders
×
163
        countQuery.Expression.SetLoaders()
×
164
        // set the limit to 1
×
165
        countQuery.Expression.SetLimit(1)
×
166
        // remove ordering
×
167
        countQuery.Expression.ClearOrderBy()
×
168
        // remove group by
×
169
        countQuery.Expression.SetGroups()
×
170
        // remove offset
×
171
        countQuery.Expression.SetOffset(0)
×
172

×
173
        return countQuery
×
174
}
×
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