diff --git a/Makefile b/Makefile index f1b55cfaba274..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/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: 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/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/gen/enum/main.go b/coderd/database/gen/enum/main.go new file mode 100644 index 0000000000000..69aebbefd073d --- /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 := 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 +} diff --git a/coderd/database/generate.sh b/coderd/database/generate.sh index 326fa096b90d1..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 @@ -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); +) 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{{
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: