Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e9f0711

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

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.
Loading
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+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{{ define "GetWorkspaceBuildByID" }}
2+
SELECT
3+
workspace_builds.*,
4+
workspaces.organization_id AS organization_id,
5+
workspaces.owner_id AS workspace_owner_id
6+
FROM
7+
workspace_builds
8+
INNER JOIN
9+
workspaces ON workspace_builds.workspace_id = workspaces.id
10+
WHERE
11+
workspace_builds.id = @build_id
12+
LIMIT
13+
1;
14+
{{ end }}
15+
16+
{{ define "GetWorkspaceBuildByJobID" }}
17+
SELECT
18+
workspace_builds.*,
19+
workspaces.organization_id AS organization_id,
20+
workspaces.owner_id AS workspace_owner_id
21+
FROM
22+
workspace_builds
23+
INNER JOIN
24+
workspaces ON workspace_builds.workspace_id = workspaces.id
25+
WHERE
26+
workspace_builds.job_id = @job_id
27+
LIMIT
28+
1;
29+
{{ end }}
30+
31+
32+

0 commit comments

Comments
 (0)