Skip to content

Commit e9f0711

Browse files
committed
chore: Start working on workspace build queries
1 parent d30da81 commit e9f0711

File tree

11 files changed

+253
-86
lines changed

11 files changed

+253
-86
lines changed

coderd/database/bindvars.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package database
2+
3+
import (
4+
"database/sql/driver"
5+
"fmt"
6+
"reflect"
7+
"regexp"
8+
"strings"
9+
10+
"github.com/google/uuid"
11+
"github.com/lib/pq"
12+
13+
"github.com/jmoiron/sqlx/reflectx"
14+
15+
"github.com/coder/coder/coderd/util/slice"
16+
)
17+
18+
var nameRegex = regexp.MustCompile(`@([a-zA-Z0-9_]+)`)
19+
20+
// dbmapper grabs struct 'db' tags.
21+
var dbmapper = reflectx.NewMapper("db")
22+
var sqlValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
23+
24+
// bindNamed is an implementation that improves on the SQLx implementation. This
25+
// adjusts the query to use "$#" syntax for arguments instead of "@argument". The
26+
// returned args are the values of the struct fields that match the names in the
27+
// correct order and indexing.
28+
//
29+
// 1. SQLx does not reuse arguments, so "@arg, @arg" will result in two arguments
30+
// "$1, $2" instead of "$1, $1".
31+
// 2. SQLx does not handle uuid arrays.
32+
// 3. SQLx only supports ":name" style arguments and breaks "::" type casting.
33+
func bindNamed(query string, arg interface{}) (newQuery string, args []interface{}, err error) {
34+
// We do not need to implement a sql parser to extract and replace the variable names.
35+
// All names follow a simple regex.
36+
names := nameRegex.FindAllString(query, -1)
37+
// Get all unique names
38+
names = slice.Unique(names)
39+
40+
// Replace all names with the correct index
41+
for i, name := range names {
42+
rpl := fmt.Sprintf("$%d", i+1)
43+
query = strings.ReplaceAll(query, name, rpl)
44+
// Remove the "@" prefix to match to the "db" struct tag.
45+
names[i] = strings.TrimPrefix(name, "@")
46+
}
47+
48+
arglist := make([]interface{}, 0, len(names))
49+
50+
// This comes straight from SQLx's implementation to get the values
51+
// of the struct fields.
52+
v := reflect.ValueOf(arg)
53+
for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; {
54+
v = v.Elem()
55+
}
56+
57+
// If there is only 1 argument, and the argument is not a struct, then
58+
// the only argument is the value passed in. This is a nice shortcut
59+
// for simple queries with 1 param like "id".
60+
if v.Type().Kind() != reflect.Struct && len(names) == 1 {
61+
arglist = append(arglist, pqValue(v))
62+
return query, arglist, nil
63+
}
64+
65+
err = dbmapper.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error {
66+
if len(t) == 0 {
67+
return fmt.Errorf("could not find name %s in %#v", names[i], arg)
68+
}
69+
70+
val := reflectx.FieldByIndexesReadOnly(v, t)
71+
arglist = append(arglist, pqValue(val))
72+
73+
return nil
74+
})
75+
if err != nil {
76+
return "", nil, err
77+
}
78+
79+
return query, arglist, nil
80+
}
81+
82+
func pqValue(val reflect.Value) interface{} {
83+
valI := val.Interface()
84+
// Handle some custom types to make arguments easier to use.
85+
switch valI.(type) {
86+
// Feel free to add more types here as needed.
87+
case []uuid.UUID:
88+
return pq.Array(valI)
89+
default:
90+
return valI
91+
}
92+
}

coderd/database/modelqueries.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,17 @@ func (q *sqlQuerier) GetTemplateGroupRoles(ctx context.Context, id uuid.UUID) ([
177177

178178
type workspaceQuerier interface {
179179
GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspacesParams, prepared rbac.PreparedAuthorized) ([]GetWorkspacesRow, error)
180+
GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (WorkspaceBuildWithOwners, error)
181+
}
182+
183+
type WorkspaceBuildWithOwners struct {
184+
WorkspaceBuild
185+
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
186+
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
187+
}
188+
189+
func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (WorkspaceBuildWithOwners, error) {
190+
return sqlxGet[WorkspaceBuildWithOwners](ctx, q, "GetWorkspaceBuildByID", id)
180191
}
181192

182193
// GetAuthorizedWorkspaces returns all workspaces that the user is authorized to access.

coderd/database/querier.go

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 0 additions & 64 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspacebuilds.sql

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,3 @@
1-
-- name: GetWorkspaceBuildByID :one
2-
SELECT
3-
*
4-
FROM
5-
workspace_builds
6-
WHERE
7-
id = $1
8-
LIMIT
9-
1;
10-
11-
-- name: GetWorkspaceBuildByJobID :one
12-
SELECT
13-
*
14-
FROM
15-
workspace_builds
16-
WHERE
17-
job_id = $1
18-
LIMIT
19-
1;
20-
211
-- name: GetWorkspaceBuildsCreatedAfter :many
222
SELECT * FROM workspace_builds WHERE created_at > $1;
233

coderd/database/sqlx.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package database
2+
3+
import (
4+
"context"
5+
6+
"github.com/coder/coder/coderd/database/sqlxqueries"
7+
"golang.org/x/xerrors"
8+
)
9+
10+
func sqlxGet[RT any](ctx context.Context, q *sqlQuerier, queryName string, argument interface{}) (RT, error) {
11+
var empty RT
12+
13+
query, err := sqlxqueries.Query(queryName, nil)
14+
if err != nil {
15+
return empty, xerrors.Errorf("get query: %w", err)
16+
}
17+
18+
query, args, err := bindNamed(query, argument)
19+
if err != nil {
20+
return empty, xerrors.Errorf("bind named: %w", err)
21+
}
22+
23+
// GetContext maps the results of the query to the items slice by struct
24+
// db tags.
25+
err = q.sdb.GetContext(ctx, &empty, query, args...)
26+
if err != nil {
27+
return empty, xerrors.Errorf("%s: %w", queryName, err)
28+
}
29+
30+
return empty, nil
31+
}

coderd/database/sqlxqueries/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Editor/IDE config
2+
3+
To edit template files, it is best to configure your IDE to work with go template files.
4+
5+
## VSCode
6+
7+
Required extension (Default Golang Extension): https://marketplace.visualstudio.com/items?itemName=golang.Go
8+
9+
The default extension [supports syntax highlighting](https://github.com/golang/vscode-go/wiki/features#go-template-syntax-highlighting), but requires a configuration change. You must add this section to your golang extension settings:
10+
11+
```json
12+
"gopls": {
13+
"ui.semanticTokens": true
14+
},
15+
```
16+
17+
Unfortunately, the VSCode extension does not support both go template and postgres highlighting. You can switch between the two with:
18+
19+
1. `ctl + shift + p`
20+
1. "Change language Mode"
21+
1. "Postgres" or "Go Template File"
22+
- Feel free to create a permanent file association with `*.gosql` files.
23+
24+
25+
## Goland
26+
27+
Goland supports [template highlighting](https://www.jetbrains.com/help/go/integration-with-go-templates.html) out of the box. To associate sql files, add a new file type in **Editor** settings. Select "Go template files". Add a new filename of `*.gosql` and select "postgres" as the "Template Data Language".
28+
29+
30+
![Goland language configuration](./imgs/goland-gosql.png)
31+
32+
It also helps to support the sqlc type variables. You can do this by adding ["User Parameters"](https://www.jetbrains.com/help/datagrip/settings-tools-database-user-parameters.html) in database queries.
33+
34+
![Goland language configuration](./imgs/goland-user-params.png)
35+
36+
You can also add `dump.sql` as a DDL data source for proper table column recognition.
32 KB
Loading
24.7 KB
Loading
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package sqlxqueries
2+
3+
import (
4+
"bytes"
5+
"embed"
6+
"sync"
7+
"text/template"
8+
9+
"golang.org/x/xerrors"
10+
)
11+
12+
//go:embed *.gosql
13+
var sqlxQueries embed.FS
14+
15+
var (
16+
// Only parse the queries once.
17+
once sync.Once
18+
cached *template.Template
19+
cachedError error
20+
)
21+
22+
func queries() (*template.Template, error) {
23+
once.Do(func() {
24+
tpls, err := template.ParseFS(sqlxQueries, "*.gosql")
25+
if err != nil {
26+
cachedError = xerrors.Errorf("developer error parse sqlx queries: %w", err)
27+
}
28+
cached = tpls
29+
})
30+
31+
return cached, cachedError
32+
}
33+
34+
func Query(name string, data interface{}) (string, error) {
35+
tpls, err := queries()
36+
if err != nil {
37+
return "", err
38+
}
39+
40+
var out bytes.Buffer
41+
// TODO: Should we cache these?
42+
err = tpls.ExecuteTemplate(&out, name, data)
43+
if err != nil {
44+
return "", xerrors.Errorf("execute template %s: %w", name, err)
45+
}
46+
return out.String(), nil
47+
}
48+
49+
func GetWorkspaceBuildByID() (string, error) {
50+
return Query("GetWorkspaceBuildByID", nil)
51+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy