From d32e9e8c9751c557ea7440fd9a51a73668dc481a Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 26 Aug 2022 12:36:38 +0300 Subject: [PATCH 1/6] feat: Generate DB unique constraints as enums This fixes a TODO from #3409. --- coderd/database/errors.go | 9 -- coderd/database/gen/enum/main.go | 120 +++++++++++++++++++++++++++ coderd/database/generate.sh | 3 + coderd/database/unique_constraint.go | 26 ++++++ 4 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 coderd/database/gen/enum/main.go create mode 100644 coderd/database/unique_constraint.go diff --git a/coderd/database/errors.go b/coderd/database/errors.go index e7333e4c6635b..5028e6281a39c 100644 --- a/coderd/database/errors.go +++ b/coderd/database/errors.go @@ -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, diff --git a/coderd/database/gen/enum/main.go b/coderd/database/gen/enum/main.go new file mode 100644 index 0000000000000..91272a190b182 --- /dev/null +++ b/coderd/database/gen/enum/main.go @@ -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 := s.Text() + switch { + case strings.HasPrefix(line, "--"): + case line == "": + case strings.HasSuffix(line, ";"): + query += strings.TrimSpace(line) + if isUniqueConstraint(query) { + uniqueConstraints = append(uniqueConstraints, query) + } + query = "" + default: + query += strings.TrimSpace(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 +} diff --git a/coderd/database/generate.sh b/coderd/database/generate.sh index 326fa096b90d1..1315ed1c2a96f 100755 --- a/coderd/database/generate.sh +++ b/coderd/database/generate.sh @@ -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 ) diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go new file mode 100644 index 0000000000000..1edb0a408ed20 --- /dev/null +++ b/coderd/database/unique_constraint.go @@ -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); +) From 1ab885c7372db8059b65477d7615d4ec313a442c Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 26 Aug 2022 12:44:31 +0300 Subject: [PATCH 2/6] chore: Move dump to gen/dump, update Makefile --- Makefile | 4 ++-- coderd/database/{ => gen}/dump/main.go | 2 +- coderd/database/generate.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename coderd/database/{ => gen}/dump/main.go (94%) diff --git a/Makefile b/Makefile index f1b55cfaba274..d08bdd6fc3ee9 100644 --- a/Makefile +++ b/Makefile @@ -56,8 +56,8 @@ 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) coderd/database/gen/enum/main.go + 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) diff --git a/coderd/database/dump/main.go b/coderd/database/gen/dump/main.go similarity index 94% rename from coderd/database/dump/main.go rename to coderd/database/gen/dump/main.go index 1744fc337a463..43c694b36a959 100644 --- a/coderd/database/dump/main.go +++ b/coderd/database/gen/dump/main.go @@ -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) } diff --git a/coderd/database/generate.sh b/coderd/database/generate.sh index 1315ed1c2a96f..5663c1ed85b14 100755 --- a/coderd/database/generate.sh +++ b/coderd/database/generate.sh @@ -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 From 9f263949bd1e24570fe43a1eeb4319e0ec8be173 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 26 Aug 2022 12:47:41 +0300 Subject: [PATCH 3/6] fix: Renamed index --- coderd/workspaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index b57235c06452c..af8c4eddec618 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -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{{ From 27adb558b119356eb8f0e6b13d07ef375b5e6c9d Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 26 Aug 2022 13:18:05 +0300 Subject: [PATCH 4/6] fix: Consistency --- coderd/database/gen/enum/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/gen/enum/main.go b/coderd/database/gen/enum/main.go index 91272a190b182..fa06cdf340a68 100644 --- a/coderd/database/gen/enum/main.go +++ b/coderd/database/gen/enum/main.go @@ -24,7 +24,7 @@ func main() { 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]) + _, _ = 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() From 23fd378485ccbd7336b56008fa8a52e8fde86357 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 26 Aug 2022 13:21:32 +0300 Subject: [PATCH 5/6] Cleanup --- coderd/database/gen/enum/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coderd/database/gen/enum/main.go b/coderd/database/gen/enum/main.go index fa06cdf340a68..69aebbefd073d 100644 --- a/coderd/database/gen/enum/main.go +++ b/coderd/database/gen/enum/main.go @@ -34,18 +34,18 @@ func run() error { s := bufio.NewScanner(dump) query := "" for s.Scan() { - line := s.Text() + line := strings.TrimSpace(s.Text()) switch { case strings.HasPrefix(line, "--"): case line == "": case strings.HasSuffix(line, ";"): - query += strings.TrimSpace(line) + query += line if isUniqueConstraint(query) { uniqueConstraints = append(uniqueConstraints, query) } query = "" default: - query += strings.TrimSpace(line) + " " + query += line + " " } } if err = s.Err(); err != nil { From 9a7c833b91a53f25e5031a2ad05e5a795807a28c Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 26 Aug 2022 13:30:12 +0300 Subject: [PATCH 6/6] Fix makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d08bdd6fc3ee9..3867003a92ea5 100644 --- a/Makefile +++ b/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/gen/dump/main.go $(wildcard coderd/database/migrations/*.sql) coderd/database/gen/enum/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: 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