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

stephenafamo / bob / 26289753839

22 May 2026 12:48PM UTC coverage: 45.45% (+0.04%) from 45.408%
26289753839

Pull #692

github

Roman A. Grigorovich
feat(dialect)!: add NameExpr/NameAsExpr and fix schema column qualifiers

BREAKING CHANGE: View and Table Name() now return the bare table or view
name as a string. Use NameExpr() and NameAsExpr() in query builders
(they replace the former Name() and NameAs() expression methods).

- Add Schema() on PostgreSQL and SQLite View types
- Add tableColumnAlias codegen helper so build*Columns, pkEQ, and pkIN
  match View.Alias() when schema is set at construction
- Update codegen templates, orm.Preload, factory tests, and docs
Pull Request #692: feat(dialect): add NameExpr/NameAsExpr and fix schema column qualifiers

16 of 32 new or added lines in 8 files covered. (50.0%)

1 existing line in 1 file now uncovered.

11127 of 24482 relevant lines covered (45.45%)

663.93 hits per line

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

0.0
/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] {
×
24
        return NewViewx[T, []T](schema, tableName, columns)
×
25
}
×
26

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

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

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

×
41
        return &View[T, Tslice, C]{
×
42
                schema:        schema,
×
43
                name:          tableName,
×
44
                alias:         alias,
×
45
                allCols:       allCols,
×
46
                returningCols: allCols.WithParent(tableName),
×
47
                scanner:       scan.StructMapper[T](),
×
48
                Columns:       columns,
×
49
        }, mappings
×
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
NEW
68
func (v *View[T, Tslice, C]) NameExpr() Expression {
×
69
        // schema is not empty, never override
×
70
        if v.schema != "" {
×
71
                return Quote(v.schema, v.name)
×
72
        }
×
73

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

77
// NameAsExpr returns the table name as an expression with an alias
NEW
78
func (v *View[T, Tslice, C]) NameAsExpr() bob.Expression {
×
NEW
79
        return v.NameExpr().As(v.alias)
×
UNCOV
80
}
×
81

82
// Alias returns the alias
83
func (v *View[T, Tslice, C]) Alias() string {
×
84
        return v.alias
×
85
}
×
86

87
// Schema returns the schema name for the view
88
func (v *View[T, Tslice, C]) Schema() string {
×
89
        return v.schema
×
90
}
×
91

92
// Name returns the view (table/view) name
NEW
93
func (v *View[T, Tslice, C]) Name() string {
×
94
        return v.name
×
95
}
×
96

97
// ColumnsExpr returns a column list expression
98
func (v *View[T, Tslice, C]) ColumnsExpr() expr.ColumnsExpr {
×
99
        // get the schema
×
100
        return v.allCols
×
101
}
×
102

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

×
115
        q.BaseQuery.Expression.AppendContextualModFunc(
×
116
                func(ctx context.Context, q *dialect.SelectQuery) (context.Context, error) {
×
117
                        if len(q.SelectList.Columns) == 0 {
×
118
                                q.AppendSelect(v.ColumnsExpr())
×
119
                        }
×
120
                        return ctx, nil
×
121
                },
122
        )
123

124
        q.Apply(queryMods...)
×
125

×
126
        return q
×
127
}
128

129
type ViewQuery[T any, Ts ~[]T] struct {
130
        orm.Query[*dialect.SelectQuery, T, Ts, bob.SliceTransformer[T, Ts]]
131
}
132

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

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

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

×
169
        return countQuery
×
170
}
×
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