mirror of
https://github.com/coder/coder.git
synced 2025-07-21 01:28:49 +00:00
feat: Generate DB unique constraints as enums (#3701)
* feat: Generate DB unique constraints as enums This fixes a TODO from #3409.
This commit is contained in:
committed by
GitHub
parent
f4c5020f63
commit
dc9b4155e0
6
Makefile
6
Makefile
@ -56,11 +56,11 @@ build: site/out/index.html $(shell find . -not -path './vendor/*' -type f -name
|
||||
.PHONY: build
|
||||
|
||||
# Runs migrations to output a dump of the database.
|
||||
coderd/database/dump.sql: coderd/database/dump/main.go $(wildcard coderd/database/migrations/*.sql)
|
||||
go run coderd/database/dump/main.go
|
||||
coderd/database/dump.sql: coderd/database/gen/dump/main.go $(wildcard coderd/database/migrations/*.sql)
|
||||
go run coderd/database/gen/dump/main.go
|
||||
|
||||
# Generates Go code for querying the database.
|
||||
coderd/database/querier.go: coderd/database/sqlc.yaml coderd/database/dump.sql $(wildcard coderd/database/queries/*.sql)
|
||||
coderd/database/querier.go: coderd/database/sqlc.yaml coderd/database/dump.sql $(wildcard coderd/database/queries/*.sql) coderd/database/gen/enum/main.go
|
||||
coderd/database/generate.sh
|
||||
|
||||
fmt/prettier:
|
||||
|
@ -6,15 +6,6 @@ import (
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
// UniqueConstraint represents a named unique constraint on a table.
|
||||
type UniqueConstraint string
|
||||
|
||||
// UniqueConstraint enums.
|
||||
// TODO(mafredri): Generate these from the database schema.
|
||||
const (
|
||||
UniqueWorkspacesOwnerIDLowerIdx UniqueConstraint = "workspaces_owner_id_lower_idx"
|
||||
)
|
||||
|
||||
// IsUniqueViolation checks if the error is due to a unique violation.
|
||||
// If one or more specific unique constraints are given as arguments,
|
||||
// the error must be caused by one of them. If no constraints are given,
|
||||
|
@ -88,7 +88,7 @@ func main() {
|
||||
if !ok {
|
||||
panic("couldn't get caller path")
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(mainPath, "..", "..", "dump.sql"), []byte(dump), 0600)
|
||||
err = os.WriteFile(filepath.Join(mainPath, "..", "..", "..", "dump.sql"), []byte(dump), 0o600)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
120
coderd/database/gen/enum/main.go
Normal file
120
coderd/database/gen/enum/main.go
Normal file
@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
const header = `// Code generated by gen/enum. DO NOT EDIT.
|
||||
package database
|
||||
`
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
dump, err := os.Open("dump.sql")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "error: %s must be run in the database directory with dump.sql present\n", os.Args[0])
|
||||
return err
|
||||
}
|
||||
defer dump.Close()
|
||||
|
||||
var uniqueConstraints []string
|
||||
|
||||
s := bufio.NewScanner(dump)
|
||||
query := ""
|
||||
for s.Scan() {
|
||||
line := strings.TrimSpace(s.Text())
|
||||
switch {
|
||||
case strings.HasPrefix(line, "--"):
|
||||
case line == "":
|
||||
case strings.HasSuffix(line, ";"):
|
||||
query += line
|
||||
if isUniqueConstraint(query) {
|
||||
uniqueConstraints = append(uniqueConstraints, query)
|
||||
}
|
||||
query = ""
|
||||
default:
|
||||
query += line + " "
|
||||
}
|
||||
}
|
||||
if err = s.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeContents("unique_constraint.go", uniqueConstraints, generateUniqueConstraints)
|
||||
}
|
||||
|
||||
func isUniqueConstraint(query string) bool {
|
||||
return strings.Contains(query, "UNIQUE")
|
||||
}
|
||||
|
||||
func generateUniqueConstraints(queries []string) ([]byte, error) {
|
||||
s := &bytes.Buffer{}
|
||||
|
||||
_, _ = fmt.Fprint(s, header)
|
||||
_, _ = fmt.Fprint(s, `
|
||||
// UniqueConstraint represents a named unique constraint on a table.
|
||||
type UniqueConstraint string
|
||||
|
||||
// UniqueConstraint enums.
|
||||
const (
|
||||
`)
|
||||
for _, query := range queries {
|
||||
name := ""
|
||||
switch {
|
||||
case strings.Contains(query, "ALTER TABLE") && strings.Contains(query, "ADD CONSTRAINT"):
|
||||
name = strings.Split(query, " ")[6]
|
||||
case strings.Contains(query, "CREATE UNIQUE INDEX"):
|
||||
name = strings.Split(query, " ")[3]
|
||||
default:
|
||||
return nil, xerrors.Errorf("unknown unique constraint format: %s", query)
|
||||
}
|
||||
_, _ = fmt.Fprintf(s, "\tUnique%s UniqueConstraint = %q // %s\n", nameFromSnakeCase(name), name, query)
|
||||
}
|
||||
_, _ = fmt.Fprint(s, ")\n")
|
||||
|
||||
return s.Bytes(), nil
|
||||
}
|
||||
|
||||
func writeContents[T any](dest string, arg T, fn func(T) ([]byte, error)) error {
|
||||
b, err := fn(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(dest, b, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command("goimports", "-w", dest)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func nameFromSnakeCase(s string) string {
|
||||
var ret string
|
||||
for _, ss := range strings.Split(s, "_") {
|
||||
switch ss {
|
||||
case "id":
|
||||
ret += "ID"
|
||||
case "ids":
|
||||
ret += "IDs"
|
||||
case "jwt":
|
||||
ret += "JWT"
|
||||
case "idx":
|
||||
ret += "Index"
|
||||
default:
|
||||
ret += strings.Title(ss)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
@ -14,7 +14,7 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Dump the updated schema.
|
||||
go run dump/main.go
|
||||
go run gen/dump/main.go
|
||||
# The logic below depends on the exact version being correct :(
|
||||
go run github.com/kyleconroy/sqlc/cmd/sqlc@v1.13.0 generate
|
||||
|
||||
@ -49,4 +49,7 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
# suggestions.
|
||||
go mod download
|
||||
goimports -w queries.sql.go
|
||||
|
||||
# Generate enums (e.g. unique constraints).
|
||||
go run gen/enum/main.go
|
||||
)
|
||||
|
26
coderd/database/unique_constraint.go
Normal file
26
coderd/database/unique_constraint.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Code generated by gen/enum. DO NOT EDIT.
|
||||
package database
|
||||
|
||||
// UniqueConstraint represents a named unique constraint on a table.
|
||||
type UniqueConstraint string
|
||||
|
||||
// UniqueConstraint enums.
|
||||
const (
|
||||
UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt);
|
||||
UniqueParameterSchemasJobIDNameKey UniqueConstraint = "parameter_schemas_job_id_name_key" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_name_key UNIQUE (job_id, name);
|
||||
UniqueParameterValuesScopeIDNameKey UniqueConstraint = "parameter_values_scope_id_name_key" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name);
|
||||
UniqueProvisionerDaemonsNameKey UniqueConstraint = "provisioner_daemons_name_key" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_name_key UNIQUE (name);
|
||||
UniqueSiteConfigsKeyKey UniqueConstraint = "site_configs_key_key" // ALTER TABLE ONLY site_configs ADD CONSTRAINT site_configs_key_key UNIQUE (key);
|
||||
UniqueTemplateVersionsTemplateIDNameKey UniqueConstraint = "template_versions_template_id_name_key" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_template_id_name_key UNIQUE (template_id, name);
|
||||
UniqueWorkspaceAppsAgentIDNameKey UniqueConstraint = "workspace_apps_agent_id_name_key" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_name_key UNIQUE (agent_id, name);
|
||||
UniqueWorkspaceBuildsJobIDKey UniqueConstraint = "workspace_builds_job_id_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_key UNIQUE (job_id);
|
||||
UniqueWorkspaceBuildsWorkspaceIDBuildNumberKey UniqueConstraint = "workspace_builds_workspace_id_build_number_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_build_number_key UNIQUE (workspace_id, build_number);
|
||||
UniqueWorkspaceBuildsWorkspaceIDNameKey UniqueConstraint = "workspace_builds_workspace_id_name_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_name_key UNIQUE (workspace_id, name);
|
||||
UniqueIndexOrganizationName UniqueConstraint = "idx_organization_name" // CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name);
|
||||
UniqueIndexOrganizationNameLower UniqueConstraint = "idx_organization_name_lower" // CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name));
|
||||
UniqueIndexUsersEmail UniqueConstraint = "idx_users_email" // CREATE UNIQUE INDEX idx_users_email ON users USING btree (email);
|
||||
UniqueIndexUsersUsername UniqueConstraint = "idx_users_username" // CREATE UNIQUE INDEX idx_users_username ON users USING btree (username);
|
||||
UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false);
|
||||
UniqueUsersUsernameLowerIndex UniqueConstraint = "users_username_lower_idx" // CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username));
|
||||
UniqueWorkspacesOwnerIDLowerIndex UniqueConstraint = "workspaces_owner_id_lower_idx" // CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false);
|
||||
)
|
@ -512,7 +512,7 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
// Check if the name was already in use.
|
||||
if database.IsUniqueViolation(err, database.UniqueWorkspacesOwnerIDLowerIdx) {
|
||||
if database.IsUniqueViolation(err, database.UniqueWorkspacesOwnerIDLowerIndex) {
|
||||
httpapi.Write(rw, http.StatusConflict, codersdk.Response{
|
||||
Message: fmt.Sprintf("Workspace %q already exists.", req.Name),
|
||||
Validations: []codersdk.ValidationError{{
|
||||
|
Reference in New Issue
Block a user