diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..9977864 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,476 @@ +# This file is licensed under the terms of the MIT license https://opensource.org/license/mit +# Copyright (c) 2021-2025 Marat Reymers + +## Golden config for golangci-lint v2.5.0 +# +# This is the best config for golangci-lint based on my experience and opinion. +# It is very strict, but not extremely strict. +# Feel free to adapt it to suit your needs. +# If this config helps you, please consider keeping a link to this file (see the next comment). + +# Based on https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322 + +version: "2" + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + +formatters: + enable: + - goimports # checks if the code and import statements are formatted according to the 'goimports' command + - golines # checks if code is formatted, and fixes long lines + + ## you may want to enable + #- gci # checks if code and import statements are formatted, with additional rules + #- gofmt # checks if the code is formatted according to 'gofmt' command + #- gofumpt # enforces a stricter format than 'gofmt', while being backwards compatible + #- swaggo # formats swaggo comments + + # All settings can be found here https://github.com/golangci/golangci-lint/blob/HEAD/.golangci.reference.yml + settings: + goimports: + # A list of prefixes, which, if set, checks import paths + # with the given prefixes are grouped after 3rd-party packages. + # Default: [] + local-prefixes: + - github.com/my/project + + golines: + # Target maximum line length. + # Default: 100 + max-len: 120 + +linters: + enable: + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks that your code does not contain non-ASCII identifiers + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - canonicalheader # checks whether net/http.Header uses canonical header + - copyloopvar # detects places where loop variables are copied (Go 1.22+) + - cyclop # checks function and package cyclomatic complexity + - depguard # checks if package imports are in a list of acceptable packages + - dupl # tool for code clone detection + - durationcheck # checks for two durations multiplied together + - embeddedstructfieldcheck # checks embedded types in structs + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - exhaustive # checks exhaustiveness of enum switch statements + - exptostd # detects functions from golang.org/x/exp/ that can be replaced by std functions + - fatcontext # detects nested contexts in loops + #- forbidigo # forbids identifiers + - funcorder # checks the order of functions, methods, and constructors + - funlen # tool for detection of long functions + - gocheckcompilerdirectives # validates go compiler directive comments (//go:) + #- gochecknoglobals # checks that no global variables exist + #- gochecknoinits # checks that no init functions are present in Go code + - gochecksumtype # checks exhaustiveness on Go "sum types" + - gocognit # computes and checks the cognitive complexity of functions + #- goconst # finds repeated strings that could be replaced by a constant + - gocritic # provides diagnostics that check for bugs, performance and style issues + - gocyclo # computes and checks the cyclomatic complexity of functions + - godoclint # checks Golang's documentation practice + - godot # checks if comments end in a period + - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod + - goprintffuncname # checks that printf-like functions are named with f at the end + - gosec # inspects source code for security problems + - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - iface # checks the incorrect use of interfaces, helping developers avoid interface pollution + - ineffassign # detects when assignments to existing variables are not used + - intrange # finds places where for loops could make use of an integer range + - iotamixing # checks if iotas are being used in const blocks with other non-iota declarations + - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) + - makezero # finds slice declarations with non-zero initial length + - mirror # reports wrong mirror patterns of bytes/strings usage + #- mnd # detects magic numbers + - musttag # enforces field tags in (un)marshaled structs + - nakedret # finds naked returns in functions greater than a specified function length + - nestif # reports deeply nested if statements + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - nilnesserr # reports that it checks for err != nil, but it returns a different nil value error (powered by nilness and nilerr) + - nilnil # checks that there is no simultaneous return of nil error and an invalid value + - noctx # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - nonamedreturns # reports all named returns + - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL + - perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter # checks Prometheus metrics naming via promlint + - protogetter # reports direct reads from proto message fields when getters should be used + - reassign # checks that package variables are not reassigned + - recvcheck # checks for receiver type consistency + - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint + - rowserrcheck # checks whether Err of rows is checked successfully + - sloglint # ensure consistent code style when using log/slog + - spancheck # checks for mistakes with OpenTelemetry/Census spans + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - staticcheck # is a go vet on steroids, applying a ton of static analysis checks + - testableexamples # checks if examples are testable (have an expected output) + - testifylint # checks usage of github.com/stretchr/testify + - testpackage # makes you use a separate _test package + - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - unqueryvet # detects SELECT * in SQL queries and SQL builders, encouraging explicit column selection + - unused # checks for unused constants, variables, functions and types + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - usetesting # reports uses of functions with replacement inside the testing package + - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace + + ## you may want to enable + #- arangolint # opinionated best practices for arangodb client + #- decorder # checks declaration order and count of types, constants, variables and functions + #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized + #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega + #- godox # detects usage of FIXME, TODO and other keywords inside comments + #- goheader # checks is file header matches to pattern + #- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters + #- interfacebloat # checks the number of methods inside an interface + #- ireturn # accept interfaces, return concrete types + #- noinlineerr # disallows inline error handling `if err := ...; err != nil {` + #- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated + #- tagalign # checks that struct tags are well aligned + #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + #- wrapcheck # checks that errors returned from external packages are wrapped + #- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event + + ## disabled + #- containedctx # detects struct contained context.Context field + #- contextcheck # [too many false positives] checks the function whether use a non-inherited context + #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- dupword # [useless without config] checks for duplicate words in the source code + #- err113 # [too strict] checks the errors handling expressions + #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- gomodguard # [use more powerful depguard] allow and block lists linter for direct Go module dependencies + #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase + #- grouper # analyzes expression groups + #- importas # enforces consistent import aliases + #- lll # [replaced by golines] reports long lines + #- maintidx # measures the maintainability index of each function + #- misspell # [useless] finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # checks the struct tags + #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines + #- wsl_v5 # [too strict and mostly code is not more readable] add or remove empty lines + + # All settings can be found here https://github.com/golangci/golangci-lint/blob/HEAD/.golangci.reference.yml + settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 30 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled. + # Default: 0.0 + package-average: 10.0 + + depguard: + # Rules to apply. + # + # Variables: + # - File Variables + # Use an exclamation mark `!` to negate a variable. + # Example: `!$test` matches any file that is not a go test file. + # + # `$all` - matches all go files + # `$test` - matches all go test files + # + # - Package Variables + # + # `$gostd` - matches all of go's standard library (Pulled from `GOROOT`) + # + # Default (applies if no custom rules are defined): Only allow $gostd in all files. + rules: + "deprecated": + # List of file globs that will match this list of settings to compare against. + # By default, if a path is relative, it is relative to the directory where the golangci-lint command is executed. + # The placeholder '${base-path}' is substituted with a path relative to the mode defined with `run.relative-path-mode`. + # The placeholder '${config-path}' is substituted with a path relative to the configuration file. + # Default: $all + files: + - "$all" + # List of packages that are not allowed. + # Entries can be a variable (starting with $), a string prefix, or an exact match (if ending with $). + # Default: [] + deny: + - pkg: github.com/golang/protobuf + desc: Use google.golang.org/protobuf instead, see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules + - pkg: github.com/satori/go.uuid + desc: Use github.com/google/uuid instead, satori's package is not maintained + - pkg: github.com/gofrs/uuid$ + desc: Use github.com/gofrs/uuid/v5 or later, it was not a go module before v5 + "non-test files": + files: + - "!$test" + deny: + - pkg: math/rand$ + desc: Use math/rand/v2 instead, see https://go.dev/blog/randv2 + "non-main files": + files: + - "!**/main.go" + deny: + - pkg: log$ + desc: Use log/slog instead, see https://go.dev/blog/slog + + embeddedstructfieldcheck: + # Checks that sync.Mutex and sync.RWMutex are not used as embedded fields. + # Default: false + forbid-mutex: true + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + + exhaustruct: + # List of regular expressions to match type names that should be excluded from processing. + # Anonymous structs can be matched by '' alias. + # Has precedence over `include`. + # Each regular expression must match the full type name, including package path. + # For example, to match type `net/http.Cookie` regular expression should be `.*/http\.Cookie`, + # but not `http\.Cookie`. + # Default: [] + exclude: + # std libs + - ^net/http.Client$ + - ^net/http.Cookie$ + - ^net/http.Request$ + - ^net/http.Response$ + - ^net/http.Server$ + - ^net/http.Transport$ + - ^net/url.URL$ + - ^os/exec.Cmd$ + - ^reflect.StructField$ + # public libs + - ^github.com/Shopify/sarama.Config$ + - ^github.com/Shopify/sarama.ProducerMessage$ + - ^github.com/mitchellh/mapstructure.DecoderConfig$ + - ^github.com/prometheus/client_golang/.+Opts$ + - ^github.com/spf13/cobra.Command$ + - ^github.com/spf13/cobra.CompletionOptions$ + - ^github.com/stretchr/testify/mock.Mock$ + - ^github.com/testcontainers/testcontainers-go.+Request$ + - ^github.com/testcontainers/testcontainers-go.FromDockerfile$ + - ^golang.org/x/tools/go/analysis.Analyzer$ + - ^google.golang.org/protobuf/.+Options$ + - ^gopkg.in/yaml.v3.Node$ + # Allows empty structures in return statements. + # Default: false + allow-empty-returns: true + + funcorder: + # Checks if the exported methods of a structure are placed before the non-exported ones. + # Default: true + struct-method: false + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 100 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 50 + + gochecksumtype: + # Presence of `default` case in switch statements satisfies exhaustiveness, if all members are not listed. + # Default: true + default-signifies-exhaustive: false + + gocognit: + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 20 + + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be found at https://go-critic.com/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + godoclint: + # List of rules to enable in addition to the default set. + # Default: empty + enable: + # Assert no unused link in godocs. + # https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#no-unused-link + - no-unused-link + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `GL_DEBUG=govet golangci-lint run --enable=govet` to see default, all available analyzers, and enabled analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + + + inamedparam: + # Skips check for interface methods with only a single parameter. + # Default: false + skip-single-param: true + + mnd: + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date`, + # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, + # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. + # Default: [] + ignored-functions: + - args.Error + - flag.Arg + - flag.Duration.* + - flag.Float.* + - flag.Int.* + - flag.Uint.* + - os.Chmod + - os.Mkdir.* + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets.* + - prometheus.LinearBuckets + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, golines ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + perfsprint: + # Optimizes into strings concatenation. + # Default: true + strconcat: false + + reassign: + # Patterns for global variable names that are checked for reassignment. + # See https://github.com/curioswitch/go-reassign#usage + # Default: ["EOF", "Err.*"] + patterns: + - ".*" + + rowserrcheck: + # database/sql is always checked. + # Default: [] + packages: + - github.com/jmoiron/sqlx + + sloglint: + # Enforce not using global loggers. + # Values: + # - "": disabled + # - "all": report all global loggers + # - "default": report only the default slog logger + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-global + # Default: "" + no-global: all + # Enforce using methods that accept a context. + # Values: + # - "": disabled + # - "all": report all contextless calls + # - "scope": report only if a context exists in the scope of the outermost function + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#context-only + # Default: "" + context: scope + + staticcheck: + # SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks + # Example (to disable some checks): [ "all", "-SA1000", "-SA1001"] + # Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"] + checks: + - all + # Incorrect or missing package comment. + # https://staticcheck.dev/docs/checks/#ST1000 + - -ST1000 + # Use consistent method receiver names. + # https://staticcheck.dev/docs/checks/#ST1016 + - -ST1016 + # Omit embedded fields from selector expression. + # https://staticcheck.dev/docs/checks/#QF1008 + - -QF1008 + + usetesting: + # Enable/disable `os.TempDir()` detections. + # Default: false + os-temp-dir: true + + exclusions: + # Log a warning if an exclusion rule is unused. + # Default: false + warn-unused: true + # Predefined exclusion rules. + # Default: [] + presets: + - std-error-handling + - common-false-positives + # Excluding configuration per-path, per-linter, per-text and per-source. + rules: + - source: 'TODO' + linters: [ godot ] + - text: 'should have a package comment' + linters: [ revive ] + - text: 'exported \S+ \S+ should have comment( \(or a comment on this block\))? or be unexported' + linters: [ revive ] + - text: 'package comment should be of the form ".+"' + source: '// ?(nolint|TODO)' + linters: [ revive ] + - text: 'comment on exported \S+ \S+ should be of the form ".+"' + source: '// ?(nolint|TODO)' + linters: [ revive, staticcheck ] + - path: '_test\.go' + linters: + - bodyclose + - dupl + - errcheck + - funlen + # - goconst + - gosec + - noctx + - wrapcheck + - path: '_test\.go' + text: 'shadow' + linters: + - govet diff --git a/Makefile b/Makefile index 2dc2199..0d49ff6 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ fmt: # Format code .PHONY: lint lint: # Lint code @echo "Linting code..." - @golangci-lint run --timeout 5m + @golangci-lint run --timeout 5m --fix .PHONY: install install: # Install dependencies diff --git a/internal/dialect/dialect.go b/internal/dialect/dialect.go index c9f2fb4..1da8f4a 100644 --- a/internal/dialect/dialect.go +++ b/internal/dialect/dialect.go @@ -21,6 +21,8 @@ func (d Dialect) GooseDialect() database.Dialect { return database.DialectMySQL case Postgres: return database.DialectPostgres + case Unknown: + return database.DialectCustom default: return database.DialectCustom } diff --git a/internal/parser/migration.go b/internal/parser/migration.go index 5e89a25..9c1df46 100644 --- a/internal/parser/migration.go +++ b/internal/parser/migration.go @@ -2,7 +2,7 @@ package parser import "regexp" -func ParseMigrationName(filename string) (tableName string, create bool) { +func ParseMigrationName(filename string) (string, bool) { // Regex patterns for common migration styles createPattern := regexp.MustCompile(`^create_(?P[a-z0-9_]+?)(?:_table)?$`) addColPattern := regexp.MustCompile(`^add_(?P[a-z0-9_]+?)_to_(?P
[a-z0-9_]+?)(?:_table)?$`) diff --git a/internal/util/util.go b/internal/util/util.go index 61a4ca3..f5b7a44 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -1,4 +1,4 @@ -package util +package util //nolint:revive // Helper functions for general purposes. func Optional[T any](defaultValue T, values ...T) T { if len(values) > 0 { diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..8abdb4b --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,24 @@ +pre-commit: + parallel: true + commands: + fmt: + glob: "*.go" + run: go fmt ./... + + mod-tidy: + glob: "{go.mod,go.sum}" + run: go mod tidy + + golangci-lint: + glob: "*.go" + run: golangci-lint run --fix --timeout 5m + +commit-msg: + commands: + check-message: + run: | + if ! grep -qE "^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+" {1}; then + echo "Commit message must follow conventional commits format" + echo "Example: feat: add new feature" + exit 1 + fi diff --git a/migrate.go b/migrate.go index dafc934..35d1aab 100644 --- a/migrate.go +++ b/migrate.go @@ -11,7 +11,7 @@ import ( "github.com/pressly/goose/v3/database" ) -// Migrate handles database migrations +// Migrate handles database migrations. type Migrate struct { dialect dialect.Dialect db *sql.DB @@ -19,7 +19,7 @@ type Migrate struct { tableName string } -// New creates a new Migrate instance +// New creates a new Migrate instance. func New(dialectValue string, opts ...Option) (*Migrate, error) { dialectVal := dialect.FromString(dialectValue) if dialectVal == dialect.Unknown { diff --git a/schema/blueprint.go b/schema/blueprint.go index b0d6e55..1211ad2 100644 --- a/schema/blueprint.go +++ b/schema/blueprint.go @@ -32,12 +32,12 @@ const ( columnTypeTimestampTz string = "timestampTz" columnTypeYear string = "year" columnTypeBinary string = "binary" - columnTypeJson string = "json" - columnTypeJsonb string = "jsonb" + columnTypeJSON string = "json" + columnTypeJSONB string = "jsonb" columnTypeGeography string = "geography" columnTypeGeometry string = "geometry" columnTypePoint string = "point" - columnTypeUuid string = "uuid" + columnTypeUUID string = "uuid" columnTypeEnum string = "enum" ) @@ -305,17 +305,17 @@ func (b *Blueprint) Binary(name string, length ...int) ColumnDefinition { // JSON creates a new JSON column definition in the blueprint. func (b *Blueprint) JSON(name string) ColumnDefinition { - return b.addColumn(columnTypeJson, name) + return b.addColumn(columnTypeJSON, name) } // JSONB creates a new JSONB column definition in the blueprint. func (b *Blueprint) JSONB(name string) ColumnDefinition { - return b.addColumn(columnTypeJsonb, name) + return b.addColumn(columnTypeJSONB, name) } // UUID creates a new UUID column definition in the blueprint. func (b *Blueprint) UUID(name string) ColumnDefinition { - return b.addColumn(columnTypeUuid, name) + return b.addColumn(columnTypeUUID, name) } // Geography creates a new geography column definition in the blueprint. @@ -541,33 +541,48 @@ func (b *Blueprint) addImpliedCommands() { func (b *Blueprint) addFluentIndexes() { for _, col := range b.columns { - if col.primary != nil { - if b.dialect == dialect.MySQL { - continue - } - if !*col.primary && col.change { - b.DropPrimary([]string{col.name}) - col.primary = nil - } + skipped := b.addFluentIndexPrimary(col) + if skipped { + continue } - if col.index != nil { - if *col.index { - b.Index(col.name).Name(col.indexName) - col.index = nil - } else if !*col.index && col.change { - b.DropIndex([]string{col.name}) - col.index = nil - } + b.addFluentIndexIndex(col) + b.addFluentIndexUnique(col) + } +} + +func (b *Blueprint) addFluentIndexPrimary(col *columnDefinition) bool { + if col.primary != nil { + if b.dialect == dialect.MySQL { + return true + } + if !*col.primary && col.change { + b.DropPrimary([]string{col.name}) + col.primary = nil } + } + return false +} - if col.unique != nil { - if *col.unique { - b.Unique(col.name).Name(col.uniqueName) - col.unique = nil - } else if !*col.unique && col.change { - b.DropUnique([]string{col.name}) - col.unique = nil - } +func (b *Blueprint) addFluentIndexIndex(col *columnDefinition) { + if col.index != nil { + if *col.index { + b.Index(col.name).Name(col.indexName) + col.index = nil + } else if !*col.index && col.change { + b.DropIndex([]string{col.name}) + col.index = nil + } + } +} + +func (b *Blueprint) addFluentIndexUnique(col *columnDefinition) { + if col.unique != nil { + if *col.unique { + b.Unique(col.name).Name(col.uniqueName) + col.unique = nil + } else if !*col.unique && col.change { + b.DropUnique([]string{col.name}) + col.unique = nil } } } @@ -585,22 +600,19 @@ func (b *Blueprint) getFluentStatements() []string { } func (b *Blueprint) build(ctx *Context) error { - statements, err := b.toSql() + statements, err := b.toSQL() if err != nil { return err } for _, statement := range statements { - // if b.verbose { - // log.Println(statement) - // } - if _, err := ctx.Exec(statement); err != nil { + if _, err = ctx.Exec(statement); err != nil { return err } } return nil } -func (b *Blueprint) toSql() ([]string, error) { +func (b *Blueprint) toSQL() ([]string, error) { b.addImpliedCommands() var statements []string diff --git a/schema/builder.go b/schema/builder.go index 35f56c0..a35a501 100644 --- a/schema/builder.go +++ b/schema/builder.go @@ -45,6 +45,8 @@ func NewBuilder(dialectValue string) (Builder, error) { return newMysqlBuilder(), nil case dialect.Postgres: return newPostgresBuilder(), nil + case dialect.Unknown: + return nil, errors.New("unsupported dialect: " + dialectValue) default: return nil, errors.New("unsupported dialect: " + dialectValue) } diff --git a/schema/column_definition.go b/schema/column_definition.go index 639ed1a..88cf3b2 100644 --- a/schema/column_definition.go +++ b/schema/column_definition.go @@ -72,7 +72,7 @@ type columnDefinition struct { // // Example: // -// schema.Timestamp("created_at").Default(schema.Expression("CURRENT_TIMESTAMP")) +// schema.Timestamp("created_at").Default(schema.Expression("CURRENT_TIMESTAMP")). type Expression string func (e Expression) String() string { diff --git a/schema/grammar.go b/schema/grammar.go index 4e903ef..e680586 100644 --- a/schema/grammar.go +++ b/schema/grammar.go @@ -1,6 +1,7 @@ package schema import ( + "errors" "fmt" "slices" "strings" @@ -41,7 +42,7 @@ type baseGrammar struct{} func (g *baseGrammar) CompileForeign(blueprint *Blueprint, command *command) (string, error) { if len(command.columns) == 0 || slices.Contains(command.columns, "") || command.on == "" || len(command.references) == 0 || slices.Contains(command.references, "") { - return "", fmt.Errorf("foreign key definition is incomplete: column, on, and references must be set") + return "", errors.New("foreign key definition is incomplete: column, on, and references must be set") } onDelete := "" if command.onDelete != "" { diff --git a/schema/mysql_builder.go b/schema/mysql_builder.go index 281f25e..f216b9d 100644 --- a/schema/mysql_builder.go +++ b/schema/mysql_builder.go @@ -34,13 +34,13 @@ func (b *mysqlBuilder) GetColumns(c *Context, tableName string) ([]*Column, erro if err != nil { return nil, err } - defer rows.Close() //nolint:errcheck + defer rows.Close() var columns []*Column for rows.Next() { var col Column var nullableStr string - if err := rows.Scan( + if err = rows.Scan( &col.Name, &col.TypeName, &col.TypeFull, &col.Collation, &nullableStr, &col.DefaultVal, &col.Comment, @@ -53,6 +53,10 @@ func (b *mysqlBuilder) GetColumns(c *Context, tableName string) ([]*Column, erro } columns = append(columns, &col) } + if err = rows.Err(); err != nil { + return nil, err + } + return columns, nil } @@ -70,18 +74,22 @@ func (b *mysqlBuilder) GetIndexes(c *Context, tableName string) ([]*Index, error if err != nil { return nil, err } - defer rows.Close() //nolint:errcheck + defer rows.Close() var indexes []*Index for rows.Next() { var idx Index var columnsStr string - if err := rows.Scan(&idx.Name, &columnsStr, &idx.Type, &idx.Unique); err != nil { + if err = rows.Scan(&idx.Name, &columnsStr, &idx.Type, &idx.Unique); err != nil { return nil, err } idx.Columns = strings.Split(columnsStr, ",") indexes = append(indexes, &idx) } + if err = rows.Err(); err != nil { + return nil, err + } + return indexes, nil } @@ -98,16 +106,20 @@ func (b *mysqlBuilder) GetTables(c *Context) ([]*TableInfo, error) { if err != nil { return nil, err } - defer rows.Close() //nolint:errcheck + defer rows.Close() var tables []*TableInfo for rows.Next() { var table TableInfo - if err := rows.Scan(&table.Name, &table.Size, &table.Comment, &table.Engine, &table.Collation); err != nil { + if err = rows.Scan(&table.Name, &table.Size, &table.Comment, &table.Engine, &table.Collation); err != nil { return nil, err } tables = append(tables, &table) } + if err = rows.Err(); err != nil { + return nil, err + } + return tables, nil } @@ -142,6 +154,7 @@ func (b *mysqlBuilder) HasColumns(c *Context, tableName string, columnNames []st return true, nil // All specified columns exist } +// nolint: dupl,godoclint // Similar code exists in other builder files func (b *mysqlBuilder) HasIndex(c *Context, tableName string, indexes []string) (bool, error) { if c == nil || tableName == "" { return false, errors.New("invalid arguments: context is nil or table name is empty") @@ -198,7 +211,7 @@ func (b *mysqlBuilder) HasTable(c *Context, name string) (bool, error) { row := c.QueryRow(query) var exists bool - if err := row.Scan(&exists); err != nil { + if err = row.Scan(&exists); err != nil { if errors.Is(err, sql.ErrNoRows) { return false, nil // Table does not exist } diff --git a/schema/mysql_builder_test.go b/schema/mysql_builder_test.go index 0d8a643..dad104b 100644 --- a/schema/mysql_builder_test.go +++ b/schema/mysql_builder_test.go @@ -17,6 +17,7 @@ func TestMysqlBuilderSuite(t *testing.T) { type mysqlBuilderSuite struct { suite.Suite + ctx context.Context db *sql.DB builder schema.Builder @@ -72,7 +73,7 @@ func (s *mysqlBuilderSuite) TestCreate() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -80,17 +81,17 @@ func (s *mysqlBuilderSuite) TestCreate() { err := builder.Create(nil, "test_table", func(table *schema.Blueprint) { table.String("name") }) - s.Error(err) + s.Require().Error(err) }) s.Run("when table name is empty, should return error", func() { err := builder.Create(c, "", func(table *schema.Blueprint) { table.String("name") }) - s.Error(err) + s.Require().Error(err) }) s.Run("when blueprint is nil, should return error", func() { err := builder.Create(c, "test_table", nil) - s.Error(err) + s.Require().Error(err) }) s.Run("when all parameters are valid, should create table successfully", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -100,7 +101,7 @@ func (s *mysqlBuilderSuite) TestCreate() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table with valid parameters") + s.Require().NoError(err, "expected no error when creating table with valid parameters") }) s.Run("when have composite primary key should create it successfully", func() { err = builder.Create(c, "user_roles", func(table *schema.Blueprint) { @@ -109,7 +110,7 @@ func (s *mysqlBuilderSuite) TestCreate() { table.Primary("user_id", "role_id") }) - s.NoError(err, "expected no error when creating table with composite primary key") + s.Require().NoError(err, "expected no error when creating table with composite primary key") }) s.Run("when have foreign key should create it successfully", func() { err = builder.Create(c, "orders", func(table *schema.Blueprint) { @@ -121,7 +122,7 @@ func (s *mysqlBuilderSuite) TestCreate() { table.Foreign("user_id").References("id").On("users").OnDelete("CASCADE").OnUpdate("CASCADE") }) - s.NoError(err, "expected no error when creating table with foreign key") + s.Require().NoError(err, "expected no error when creating table with foreign key") }) s.Run("when have custom index should create it successfully", func() { err = builder.Create(c, "orders_2", func(table *schema.Blueprint) { @@ -132,7 +133,7 @@ func (s *mysqlBuilderSuite) TestCreate() { table.Index("created_at").Name("idx_orders_created_at").Algorithm("BTREE") }) - s.NoError(err, "expected no error when creating table with custom index") + s.Require().NoError(err, "expected no error when creating table with custom index") }) s.Run("when table already exists, should return error", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -140,7 +141,7 @@ func (s *mysqlBuilderSuite) TestCreate() { table.String("name", 255) table.String("email", 255).Unique() }) - s.Error(err, "expected error when creating table that already exists") + s.Require().Error(err, "expected error when creating table that already exists") }) } @@ -148,17 +149,17 @@ func (s *mysqlBuilderSuite) TestDrop() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { err := builder.Drop(nil, "test_table") - s.Error(err) + s.Require().Error(err) }) s.Run("when table name is empty, should return error", func() { err := builder.Drop(c, "") - s.Error(err) + s.Require().Error(err) }) s.Run("when all parameters are valid, should drop table successfully", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -168,13 +169,13 @@ func (s *mysqlBuilderSuite) TestDrop() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before dropping it") + s.Require().NoError(err, "expected no error when creating table before dropping it") err = builder.Drop(c, "users") - s.NoError(err, "expected no error when dropping table with valid parameters") + s.Require().NoError(err, "expected no error when dropping table with valid parameters") }) s.Run("when table does not exist, should return error", func() { err = builder.Drop(c, "non_existent_table") - s.Error(err, "expected error when dropping a table that does not exist") + s.Require().Error(err, "expected error when dropping a table that does not exist") }) } @@ -182,17 +183,17 @@ func (s *mysqlBuilderSuite) TestDropIfExists() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { err := builder.DropIfExists(nil, "test_table") - s.Error(err) + s.Require().Error(err) }) s.Run("when table name is empty, should return error", func() { err := builder.DropIfExists(c, "") - s.Error(err) + s.Require().Error(err) }) s.Run("when all parameters are valid, should drop table successfully", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -202,13 +203,13 @@ func (s *mysqlBuilderSuite) TestDropIfExists() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before dropping it") + s.Require().NoError(err, "expected no error when creating table before dropping it") err = builder.DropIfExists(c, "users") - s.NoError(err, "expected no error when dropping table with valid parameters") + s.Require().NoError(err, "expected no error when dropping table with valid parameters") }) s.Run("when table does not exist, should not return error", func() { err = builder.DropIfExists(c, "non_existent_table") - s.NoError(err, "expected no error when dropping a table that does not exist with DropIfExists") + s.Require().NoError(err, "expected no error when dropping a table that does not exist with DropIfExists") }) } @@ -216,30 +217,30 @@ func (s *mysqlBuilderSuite) TestRename() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { err := builder.Rename(nil, "old_table", "new_table") - s.Error(err) + s.Require().Error(err) }) s.Run("when old table name is empty, should return error", func() { err := builder.Rename(c, "", "new_table") - s.Error(err) + s.Require().Error(err) }) s.Run("when new table name is empty, should return error", func() { err := builder.Rename(c, "old_table", "") - s.Error(err) + s.Require().Error(err) }) s.Run("when all parameters are valid, should rename table successfully", func() { err = builder.Create(c, "old_table", func(table *schema.Blueprint) { table.ID() table.String("name", 255) }) - s.NoError(err, "expected no error when creating old_table before renaming it") + s.Require().NoError(err, "expected no error when creating old_table before renaming it") err = builder.Rename(c, "old_table", "new_table") - s.NoError(err, "expected no error when renaming table with valid parameters") + s.Require().NoError(err, "expected no error when renaming table with valid parameters") }) } @@ -247,7 +248,7 @@ func (s *mysqlBuilderSuite) TestTable() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -278,82 +279,82 @@ func (s *mysqlBuilderSuite) TestTable() { table.FullText("bio") }) - s.NoError(err, "expected no error when creating table before modifying it") + s.Require().NoError(err, "expected no error when creating table before modifying it") s.Run("should add new columns and modify existing ones", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.String("address", 255).Nullable() table.String("phone", 20).Nullable().Unique("uk_users_phone") }) - s.NoError(err, "expected no error when modifying table with valid parameters") + s.Require().NoError(err, "expected no error when modifying table with valid parameters") }) s.Run("should modify existing column", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.String("email", 255).Nullable().Change() }) - s.NoError(err, "expected no error when modifying existing column") + s.Require().NoError(err, "expected no error when modifying existing column") }) s.Run("should drop column and rename existing one", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropColumn("password") table.RenameColumn("name", "full_name") }) - s.NoError(err, "expected no error when dropping column and renaming existing one") + s.Require().NoError(err, "expected no error when dropping column and renaming existing one") }) s.Run("should add index", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.Index("phone").Name("idx_users_phone").Algorithm("BTREE") }) - s.NoError(err, "expected no error when adding index to table") + s.Require().NoError(err, "expected no error when adding index to table") }) s.Run("should rename index", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.RenameIndex("idx_users_phone", "idx_users_contact") }) - s.NoError(err, "expected no error when renaming index in table") + s.Require().NoError(err, "expected no error when renaming index in table") }) s.Run("should drop index", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropIndex("idx_users_contact") }) - s.NoError(err, "expected no error when dropping index from table") + s.Require().NoError(err, "expected no error when dropping index from table") }) s.Run("should drop unique constraint", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropUnique("uk_users_email") }) - s.NoError(err, "expected no error when dropping unique constraint from table") + s.Require().NoError(err, "expected no error when dropping unique constraint from table") }) s.Run("should drop fulltext index", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropFulltext("ft_users_bio") }) - s.NoError(err, "expected no error when dropping fulltext index from table") + s.Require().NoError(err, "expected no error when dropping fulltext index from table") }) s.Run("should add foreign key", func() { err = builder.Create(c, "roles", func(table *schema.Blueprint) { table.UnsignedInteger("id").Primary() table.String("role_name", 255).Unique("uk_roles_role_name") }) - s.NoError(err, "expected no error when creating roles table before adding foreign key") + s.Require().NoError(err, "expected no error when creating roles table before adding foreign key") err = builder.Table(c, "users", func(table *schema.Blueprint) { table.UnsignedInteger("role_id").Nullable() table.Foreign("role_id").References("id").On("roles").OnDelete("SET NULL").OnUpdate("CASCADE") }) - s.NoError(err, "expected no error when adding foreign key to users table") + s.Require().NoError(err, "expected no error when adding foreign key to users table") }) s.Run("should drop foreign key", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropForeign("fk_users_roles") }) - s.NoError(err, "expected no error when dropping foreign key from users table") + s.Require().NoError(err, "expected no error when dropping foreign key from users table") }) s.Run("should drop primary key", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.UnsignedBigInteger("id").Change() table.DropPrimary("users_pkey") }) - s.NoError(err, "expected no error when dropping primary key from users table") + s.Require().NoError(err, "expected no error when dropping primary key from users table") }) }) } @@ -362,23 +363,23 @@ func (s *mysqlBuilderSuite) TestGetColumns() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { columns, err := builder.GetColumns(nil, "test_table") - s.Error(err) + s.Require().Error(err) s.Nil(columns) }) s.Run("when table name is empty, should return error", func() { columns, err := builder.GetColumns(c, "") - s.Error(err) + s.Require().Error(err) s.Nil(columns) }) s.Run("when table does not exist, should return empty slice", func() { columns, err := builder.GetColumns(c, "non_existent_table") - s.NoError(err) + s.Require().NoError(err) s.Empty(columns) }) s.Run("when table exists, should return columns successfully", func() { @@ -389,10 +390,10 @@ func (s *mysqlBuilderSuite) TestGetColumns() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before getting columns") + s.Require().NoError(err, "expected no error when creating table before getting columns") columns, err := builder.GetColumns(c, "users") - s.NoError(err, "expected no error when getting columns from existing table") + s.Require().NoError(err, "expected no error when getting columns from existing table") s.NotEmpty(columns) s.Len(columns, 6, "expected 6 columns in the users table") }) @@ -402,17 +403,17 @@ func (s *mysqlBuilderSuite) TestGetIndexes() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { _, err := builder.GetIndexes(nil, "users_indexes") - s.Error(err, "expected error when context is nil") + s.Require().Error(err, "expected error when context is nil") }) s.Run("when table name is empty, should return error", func() { _, err := builder.GetIndexes(c, "") - s.Error(err, "expected error when table name is empty") + s.Require().Error(err, "expected error when table name is empty") }) s.Run("when all parameters are valid", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -424,16 +425,15 @@ func (s *mysqlBuilderSuite) TestGetIndexes() { table.Index("name").Name("idx_users_name") }) - s.NoError(err, "expected no error when creating table before getting indexes") + s.Require().NoError(err, "expected no error when creating table before getting indexes") indexes, err := builder.GetIndexes(c, "users") - s.NoError(err, "expected no error when getting indexes with valid parameters") + s.Require().NoError(err, "expected no error when getting indexes with valid parameters") s.Len(indexes, 3, "expected 3 index to be returned") - }) s.Run("when table does not exist, should return empty indexes", func() { indexes, err := builder.GetIndexes(c, "non_existent_table") - s.NoError(err, "expected no error when getting indexes of non-existent table") + s.Require().NoError(err, "expected no error when getting indexes of non-existent table") s.Empty(indexes, "expected empty indexes for non-existent table") }) } @@ -442,13 +442,13 @@ func (s *mysqlBuilderSuite) TestGetTables() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when tx is nil, should return error", func() { tables, err := builder.GetTables(nil) - s.Error(err, "expected error when transaction is nil") + s.Require().Error(err, "expected error when transaction is nil") s.Nil(tables) }) s.Run("when all parameters are valid", func() { @@ -459,10 +459,10 @@ func (s *mysqlBuilderSuite) TestGetTables() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before getting tables") + s.Require().NoError(err, "expected no error when creating table before getting tables") tables, err := builder.GetTables(c) - s.NoError(err, "expected no error when getting tables after creating one") + s.Require().NoError(err, "expected no error when getting tables after creating one") s.NotEmpty(tables, "expected non-empty tables slice after creating a table") found := false for _, table := range tables { @@ -479,23 +479,23 @@ func (s *mysqlBuilderSuite) TestHasColumn() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { exists, err := builder.HasColumn(nil, "users", "name") - s.Error(err, "expected error when context is nil") + s.Require().Error(err, "expected error when context is nil") s.False(exists) }) s.Run("when table name is empty, should return error", func() { exists, err := builder.HasColumn(c, "", "name") - s.Error(err, "expected error when table name is empty") + s.Require().Error(err, "expected error when table name is empty") s.False(exists) }) s.Run("when column name is empty, should return error", func() { exists, err := builder.HasColumn(c, "users", "") - s.Error(err, "expected error when column name is empty") + s.Require().Error(err, "expected error when column name is empty") s.False(exists) }) s.Run("when all parameters are valid", func() { @@ -506,14 +506,14 @@ func (s *mysqlBuilderSuite) TestHasColumn() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before checking for column existence") + s.Require().NoError(err, "expected no error when creating table before checking for column existence") exists, err := builder.HasColumn(c, "users", "name") - s.NoError(err, "expected no error when checking for existing column") + s.Require().NoError(err, "expected no error when checking for existing column") s.True(exists, "expected 'name' column to exist in users table") exists, err = builder.HasColumn(c, "users", "non_existent_column") - s.NoError(err, "expected no error when checking for non-existing column") + s.Require().NoError(err, "expected no error when checking for non-existing column") s.False(exists, "expected 'non_existent_column' to not exist in users table") }) } @@ -522,23 +522,23 @@ func (s *mysqlBuilderSuite) TestHasColumns() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { exists, err := builder.HasColumns(nil, "users", []string{"name"}) - s.Error(err, "expected error when context is nil") + s.Require().Error(err, "expected error when context is nil") s.False(exists) }) s.Run("when table name is empty, should return error", func() { exists, err := builder.HasColumns(c, "", []string{"name"}) - s.Error(err, "expected error when table name is empty") + s.Require().Error(err, "expected error when table name is empty") s.False(exists) }) s.Run("when column names are empty, should return error", func() { exists, err := builder.HasColumns(c, "users", []string{}) - s.Error(err, "expected error when column names are empty") + s.Require().Error(err, "expected error when column names are empty") s.False(exists) }) s.Run("when all parameters are valid", func() { @@ -549,14 +549,14 @@ func (s *mysqlBuilderSuite) TestHasColumns() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before checking for columns existence") + s.Require().NoError(err, "expected no error when creating table before checking for columns existence") exists, err := builder.HasColumns(c, "users", []string{"name", "email"}) - s.NoError(err, "expected no error when checking for existing columns") + s.Require().NoError(err, "expected no error when checking for existing columns") s.True(exists, "expected 'name' and 'email' columns to exist in users_has_columns table") exists, err = builder.HasColumns(c, "users", []string{"name", "non_existent_column"}) - s.NoError(err, "expected no error when checking for mixed existing and non-existing columns") + s.Require().NoError(err, "expected no error when checking for mixed existing and non-existing columns") s.False(exists, "expected 'non_existent_column' to not exist in users_has_columns table") }) } @@ -565,18 +565,18 @@ func (s *mysqlBuilderSuite) TestHasIndex() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { exists, err := builder.HasIndex(nil, "orders", []string{"idx_users_name"}) - s.Error(err, "expected error when context is nil") + s.Require().Error(err, "expected error when context is nil") s.False(exists, "expected exists to be false when context is nil") }) s.Run("when table name is empty, should return error", func() { exists, err := builder.HasIndex(c, "", []string{"idx_users_name"}) - s.Error(err, "expected error when table name is empty") + s.Require().Error(err, "expected error when table name is empty") s.False(exists, "expected exists to be false when table name is empty") }) s.Run("when all parameters are valid", func() { @@ -591,18 +591,18 @@ func (s *mysqlBuilderSuite) TestHasIndex() { table.Index("company_id", "user_id") table.Unique("order_id").Name("uk_orders3_order_id").Algorithm("BTREE") }) - s.NoError(err, "expected no error when creating table with index") + s.Require().NoError(err, "expected no error when creating table with index") exists, err := builder.HasIndex(c, "orders", []string{"uk_orders3_order_id"}) - s.NoError(err, "expected no error when checking if index exists with valid parameters") + s.Require().NoError(err, "expected no error when checking if index exists with valid parameters") s.True(exists, "expected exists to be true for existing index") exists, err = builder.HasIndex(c, "orders", []string{"company_id", "user_id"}) - s.NoError(err, "expected no error when checking non-existent index") + s.Require().NoError(err, "expected no error when checking non-existent index") s.True(exists, "expected exists to be true for existing composite index") exists, err = builder.HasIndex(c, "orders", []string{"non_existent_index"}) - s.NoError(err, "expected no error when checking non-existent index") + s.Require().NoError(err, "expected no error when checking non-existent index") s.False(exists, "expected exists to be false for non-existent index") }) } @@ -611,18 +611,18 @@ func (s *mysqlBuilderSuite) TestHasTable() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { exists, err := builder.HasTable(nil, "users") - s.Error(err, "expected error when context is nil") + s.Require().Error(err, "expected error when context is nil") s.False(exists, "expected exists to be false when context is nil") }) s.Run("when table name is empty, should return error", func() { exists, err := builder.HasTable(c, "") - s.Error(err, "expected error when table name is empty") + s.Require().Error(err, "expected error when table name is empty") s.False(exists, "expected exists to be false when table name is empty") }) s.Run("when all parameters are valid", func() { @@ -633,14 +633,15 @@ func (s *mysqlBuilderSuite) TestHasTable() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before checking if it exists") + s.Require().NoError(err, "expected no error when creating table before checking if it exists") - exists, err := builder.HasTable(c, "users") - s.NoError(err, "expected no error when checking if table exists with valid parameters") + var exists bool + exists, err = builder.HasTable(c, "users") + s.Require().NoError(err, "expected no error when checking if table exists with valid parameters") s.True(exists, "expected exists to be true for existing table") exists, err = builder.HasTable(c, "non_existent_table") - s.NoError(err, "expected no error when checking non-existent table") + s.Require().NoError(err, "expected no error when checking non-existent table") s.False(exists, "expected exists to be false for non-existent table") }) } diff --git a/schema/mysql_grammar.go b/schema/mysql_grammar.go index 0217c8f..6b0e892 100644 --- a/schema/mysql_grammar.go +++ b/schema/mysql_grammar.go @@ -1,6 +1,7 @@ package schema import ( + "errors" "fmt" "slices" "strings" @@ -127,7 +128,7 @@ func (g *mysqlGrammar) CompileAdd(blueprint *Blueprint) (string, error) { func (g *mysqlGrammar) CompileChange(bp *Blueprint, command *command) (string, error) { column := command.column if column.name == "" { - return "", fmt.Errorf("column name cannot be empty for change operation") + return "", errors.New("column name cannot be empty for change operation") } sql := fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s %s", bp.name, column.name, g.getType(column)) @@ -144,26 +145,26 @@ func (g *mysqlGrammar) CompileRename(blueprint *Blueprint, command *command) (st func (g *mysqlGrammar) CompileDrop(blueprint *Blueprint) (string, error) { if blueprint.name == "" { - return "", fmt.Errorf("table name cannot be empty") + return "", errors.New("table name cannot be empty") } return fmt.Sprintf("DROP TABLE %s", blueprint.name), nil } func (g *mysqlGrammar) CompileDropIfExists(blueprint *Blueprint) (string, error) { if blueprint.name == "" { - return "", fmt.Errorf("table name cannot be empty") + return "", errors.New("table name cannot be empty") } return fmt.Sprintf("DROP TABLE IF EXISTS %s", blueprint.name), nil } func (g *mysqlGrammar) CompileDropColumn(blueprint *Blueprint, command *command) (string, error) { if len(command.columns) == 0 { - return "", fmt.Errorf("no columns to drop") + return "", errors.New("no columns to drop") } columns := make([]string, len(command.columns)) for i, col := range command.columns { if col == "" { - return "", fmt.Errorf("column name cannot be empty") + return "", errors.New("column name cannot be empty") } columns[i] = col } @@ -173,14 +174,14 @@ func (g *mysqlGrammar) CompileDropColumn(blueprint *Blueprint, command *command) func (g *mysqlGrammar) CompileRenameColumn(blueprint *Blueprint, command *command) (string, error) { if command.from == "" || command.to == "" { - return "", fmt.Errorf("old and new column names cannot be empty") + return "", errors.New("old and new column names cannot be empty") } return fmt.Sprintf("ALTER TABLE %s RENAME COLUMN %s TO %s", blueprint.name, command.from, command.to), nil } func (g *mysqlGrammar) CompileIndex(blueprint *Blueprint, command *command) (string, error) { if slices.Contains(command.columns, "") { - return "", fmt.Errorf("index column cannot be empty") + return "", errors.New("index column cannot be empty") } indexName := command.index @@ -198,7 +199,7 @@ func (g *mysqlGrammar) CompileIndex(blueprint *Blueprint, command *command) (str func (g *mysqlGrammar) CompileUnique(blueprint *Blueprint, command *command) (string, error) { if slices.Contains(command.columns, "") { - return "", fmt.Errorf("unique column cannot be empty") + return "", errors.New("unique column cannot be empty") } indexName := command.index @@ -215,7 +216,7 @@ func (g *mysqlGrammar) CompileUnique(blueprint *Blueprint, command *command) (st func (g *mysqlGrammar) CompileFullText(blueprint *Blueprint, command *command) (string, error) { if slices.Contains(command.columns, "") { - return "", fmt.Errorf("fulltext index column cannot be empty") + return "", errors.New("fulltext index column cannot be empty") } indexName := command.index @@ -223,12 +224,17 @@ func (g *mysqlGrammar) CompileFullText(blueprint *Blueprint, command *command) ( indexName = g.CreateIndexName(blueprint, "fulltext", command.columns...) } - return fmt.Sprintf("CREATE FULLTEXT INDEX %s ON %s (%s)", indexName, blueprint.name, g.Columnize(command.columns)), nil + return fmt.Sprintf( + "CREATE FULLTEXT INDEX %s ON %s (%s)", + indexName, + blueprint.name, + g.Columnize(command.columns), + ), nil } func (g *mysqlGrammar) CompilePrimary(blueprint *Blueprint, command *command) (string, error) { if slices.Contains(command.columns, "") { - return "", fmt.Errorf("primary key column cannot be empty") + return "", errors.New("primary key column cannot be empty") } indexName := command.index @@ -236,19 +242,24 @@ func (g *mysqlGrammar) CompilePrimary(blueprint *Blueprint, command *command) (s indexName = g.CreateIndexName(blueprint, "primary", command.columns...) } - return fmt.Sprintf("ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (%s)", blueprint.name, indexName, g.Columnize(command.columns)), nil + return fmt.Sprintf( + "ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (%s)", + blueprint.name, + indexName, + g.Columnize(command.columns), + ), nil } func (g *mysqlGrammar) CompileDropIndex(blueprint *Blueprint, command *command) (string, error) { if command.index == "" { - return "", fmt.Errorf("index name cannot be empty") + return "", errors.New("index name cannot be empty") } return fmt.Sprintf("ALTER TABLE %s DROP INDEX %s", blueprint.name, command.index), nil } func (g *mysqlGrammar) CompileDropUnique(blueprint *Blueprint, command *command) (string, error) { if command.index == "" { - return "", fmt.Errorf("unique index name cannot be empty") + return "", errors.New("unique index name cannot be empty") } return fmt.Sprintf("ALTER TABLE %s DROP INDEX %s", blueprint.name, command.index), nil } @@ -263,14 +274,14 @@ func (g *mysqlGrammar) CompileDropPrimary(blueprint *Blueprint, _ *command) (str func (g *mysqlGrammar) CompileRenameIndex(blueprint *Blueprint, command *command) (string, error) { if command.from == "" || command.to == "" { - return "", fmt.Errorf("old and new index names cannot be empty") + return "", errors.New("old and new index names cannot be empty") } return fmt.Sprintf("ALTER TABLE %s RENAME INDEX %s TO %s", blueprint.name, command.from, command.to), nil } func (g *mysqlGrammar) CompileDropForeign(blueprint *Blueprint, command *command) (string, error) { if command.index == "" { - return "", fmt.Errorf("foreign key name cannot be empty") + return "", errors.New("foreign key name cannot be empty") } return fmt.Sprintf("ALTER TABLE %s DROP FOREIGN KEY %s", blueprint.name, command.index), nil } @@ -283,7 +294,7 @@ func (g *mysqlGrammar) getColumns(blueprint *Blueprint) ([]string, error) { var columns []string for _, col := range blueprint.getAddedColumns() { if col.name == "" { - return nil, fmt.Errorf("column name cannot be empty") + return nil, errors.New("column name cannot be empty") } sql := col.name + " " + g.getType(col) sql += g.modifyUnsigned(col) @@ -315,6 +326,7 @@ func (g *mysqlGrammar) getConstraints(blueprint *Blueprint) []string { return constrains } +// nolint: dupl // Similar code exists in other grammar files func (g *mysqlGrammar) getType(col *columnDefinition) string { typeFuncMap := map[string]func(*columnDefinition) string{ columnTypeChar: g.typeChar, @@ -333,8 +345,8 @@ func (g *mysqlGrammar) getType(col *columnDefinition) string { columnTypeDecimal: g.typeDecimal, columnTypeBoolean: g.typeBoolean, columnTypeEnum: g.typeEnum, - columnTypeJson: g.typeJson, - columnTypeJsonb: g.typeJsonb, + columnTypeJSON: g.typeJSON, + columnTypeJSONB: g.typeJSONB, columnTypeDate: g.typeDate, columnTypeDateTime: g.typeDateTime, columnTypeDateTimeTz: g.typeDateTimeTz, @@ -344,7 +356,7 @@ func (g *mysqlGrammar) getType(col *columnDefinition) string { columnTypeTimestampTz: g.typeTimestampTz, columnTypeYear: g.typeYear, columnTypeBinary: g.typeBinary, - columnTypeUuid: g.typeUuid, + columnTypeUUID: g.typeUUID, columnTypeGeography: g.typeGeography, columnTypeGeometry: g.typeGeometry, columnTypePoint: g.typePoint, @@ -363,39 +375,39 @@ func (g *mysqlGrammar) typeString(col *columnDefinition) string { return fmt.Sprintf("VARCHAR(%d)", *col.length) } -func (g *mysqlGrammar) typeTinyText(col *columnDefinition) string { +func (g *mysqlGrammar) typeTinyText(_ *columnDefinition) string { return "TINYTEXT" } -func (g *mysqlGrammar) typeText(col *columnDefinition) string { +func (g *mysqlGrammar) typeText(_ *columnDefinition) string { return "TEXT" } -func (g *mysqlGrammar) typeMediumText(col *columnDefinition) string { +func (g *mysqlGrammar) typeMediumText(_ *columnDefinition) string { return "MEDIUMTEXT" } -func (g *mysqlGrammar) typeLongText(col *columnDefinition) string { +func (g *mysqlGrammar) typeLongText(_ *columnDefinition) string { return "LONGTEXT" } -func (g *mysqlGrammar) typeBigInteger(col *columnDefinition) string { +func (g *mysqlGrammar) typeBigInteger(_ *columnDefinition) string { return "BIGINT" } -func (g *mysqlGrammar) typeInteger(col *columnDefinition) string { +func (g *mysqlGrammar) typeInteger(_ *columnDefinition) string { return "INT" } -func (g *mysqlGrammar) typeMediumInteger(col *columnDefinition) string { +func (g *mysqlGrammar) typeMediumInteger(_ *columnDefinition) string { return "MEDIUMINT" } -func (g *mysqlGrammar) typeSmallInteger(col *columnDefinition) string { +func (g *mysqlGrammar) typeSmallInteger(_ *columnDefinition) string { return "SMALLINT" } -func (g *mysqlGrammar) typeTinyInteger(col *columnDefinition) string { +func (g *mysqlGrammar) typeTinyInteger(_ *columnDefinition) string { return "TINYINT" } @@ -406,7 +418,7 @@ func (g *mysqlGrammar) typeFloat(col *columnDefinition) string { return "FLOAT" } -func (g *mysqlGrammar) typeDouble(col *columnDefinition) string { +func (g *mysqlGrammar) typeDouble(_ *columnDefinition) string { return "DOUBLE" } @@ -414,7 +426,7 @@ func (g *mysqlGrammar) typeDecimal(col *columnDefinition) string { return fmt.Sprintf("DECIMAL(%d, %d)", *col.total, *col.places) } -func (g *mysqlGrammar) typeBoolean(col *columnDefinition) string { +func (g *mysqlGrammar) typeBoolean(_ *columnDefinition) string { return "TINYINT(1)" } @@ -426,15 +438,15 @@ func (g *mysqlGrammar) typeEnum(col *columnDefinition) string { return fmt.Sprintf("ENUM(%s)", strings.Join(allowedValues, ", ")) } -func (g *mysqlGrammar) typeJson(col *columnDefinition) string { +func (g *mysqlGrammar) typeJSON(_ *columnDefinition) string { return "JSON" } -func (g *mysqlGrammar) typeJsonb(col *columnDefinition) string { +func (g *mysqlGrammar) typeJSONB(_ *columnDefinition) string { return "JSON" } -func (g *mysqlGrammar) typeDate(col *columnDefinition) string { +func (g *mysqlGrammar) typeDate(_ *columnDefinition) string { return "DATE" } @@ -491,7 +503,7 @@ func (g *mysqlGrammar) typeTimestampTz(col *columnDefinition) string { return g.typeTimestamp(col) } -func (g *mysqlGrammar) typeYear(col *columnDefinition) string { +func (g *mysqlGrammar) typeYear(_ *columnDefinition) string { return "YEAR" } @@ -502,14 +514,17 @@ func (g *mysqlGrammar) typeBinary(col *columnDefinition) string { return "BLOB" } -func (g *mysqlGrammar) typeUuid(col *columnDefinition) string { +func (g *mysqlGrammar) typeUUID(_ *columnDefinition) string { return "CHAR(36)" // Default UUID length } func (g *mysqlGrammar) typeGeometry(col *columnDefinition) string { subtype := util.Ternary(col.subtype != nil, util.PtrOf(strings.ToUpper(*col.subtype)), nil) if subtype != nil { - if !slices.Contains([]string{"POINT", "LINESTRING", "POLYGON", "GEOMETRYCOLLECTION", "MULTIPOINT", "MULTILINESTRING"}, *subtype) { + if !slices.Contains( + []string{"POINT", "LINESTRING", "POLYGON", "GEOMETRYCOLLECTION", "MULTIPOINT", "MULTILINESTRING"}, + *subtype, + ) { subtype = nil } } diff --git a/schema/mysql_grammar_test.go b/schema/mysql_grammar_test.go index 91534f2..37eea9d 100644 --- a/schema/mysql_grammar_test.go +++ b/schema/mysql_grammar_test.go @@ -1,10 +1,11 @@ -package schema +package schema //nolint:testpackage // Need to access unexported members for testing import ( "testing" "github.com/akfaiz/migris/internal/dialect" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMysqlGrammar_CompileCreate(t *testing.T) { @@ -85,10 +86,10 @@ func TestMysqlGrammar_CompileCreate(t *testing.T) { tt.blueprint(bp) got, err := g.CompileCreate(bp) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -153,7 +154,7 @@ func TestMysqlGrammar_CompileAdd(t *testing.T) { { name: "no columns to add returns empty string", table: "users", - blueprint: func(table *Blueprint) { + blueprint: func(_ *Blueprint) { // No columns added }, want: "", @@ -175,10 +176,10 @@ func TestMysqlGrammar_CompileAdd(t *testing.T) { tt.blueprint(bp) got, err := g.CompileAdd(bp) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -197,7 +198,7 @@ func TestMysqlGrammar_CompileChange(t *testing.T) { { name: "no changed columns returns nil", table: "users", - blueprint: func(table *Blueprint) {}, + blueprint: func(_ *Blueprint) {}, want: nil, wantErr: false, }, @@ -274,7 +275,9 @@ func TestMysqlGrammar_CompileChange(t *testing.T) { Comment("User email address"). Change() }, - want: []string{"ALTER TABLE users MODIFY COLUMN email VARCHAR(255) NOT NULL DEFAULT 'example@test.com' COMMENT 'User email address'"}, + want: []string{ + "ALTER TABLE users MODIFY COLUMN email VARCHAR(255) NOT NULL DEFAULT 'example@test.com' COMMENT 'User email address'", + }, wantErr: false, }, { @@ -304,12 +307,12 @@ func TestMysqlGrammar_CompileChange(t *testing.T) { t.Run(tt.name, func(t *testing.T) { bp := &Blueprint{name: tt.table, grammar: g, dialect: dialect.MySQL} tt.blueprint(bp) - statements, err := bp.toSql() + statements, err := bp.toSQL() if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, statements, "Expected SQL to match for test case: %s", tt.name) }) } @@ -356,10 +359,10 @@ func TestMysqlGrammar_CompileRename(t *testing.T) { bp.rename(tt.newName) got, err := g.CompileRename(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -410,10 +413,10 @@ func TestMysqlGrammar_CompileDrop(t *testing.T) { bp := &Blueprint{name: tt.table} got, err := g.CompileDrop(bp) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -464,10 +467,10 @@ func TestMysqlGrammar_CompileDropIfExists(t *testing.T) { bp := &Blueprint{name: tt.table} got, err := g.CompileDropIfExists(bp) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -527,10 +530,10 @@ func TestMysqlGrammar_CompileDropColumn(t *testing.T) { tt.blueprint(bp) got, err := g.CompileDropColumn(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -584,10 +587,10 @@ func TestMysqlGrammar_CompileRenameColumn(t *testing.T) { command := &command{from: tt.oldName, to: tt.newName} got, err := g.CompileRenameColumn(bp, command) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -706,10 +709,10 @@ func TestMysqlGrammar_CompileForeign(t *testing.T) { tt.blueprint(bp) got, err := g.CompileForeign(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -746,10 +749,10 @@ func TestMysqlGrammar_CompileDropForeign(t *testing.T) { command := &command{index: tt.fkName} got, err := g.CompileDropForeign(bp, command) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -826,10 +829,10 @@ func TestMysqlGrammar_CompileIndex(t *testing.T) { tt.blueprint(bp) got, err := g.CompileIndex(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -914,10 +917,10 @@ func TestMysqlGrammar_CompileUnique(t *testing.T) { tt.blueprint(bp) got, err := g.CompileUnique(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -1010,10 +1013,10 @@ func TestMysqlGrammar_CompilePrimary(t *testing.T) { tt.blueprint(bp) got, err := g.CompilePrimary(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -1106,10 +1109,10 @@ func TestMysqlGrammar_CompileFullText(t *testing.T) { tt.blueprint(bp) got, err := g.CompileFullText(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -1146,10 +1149,10 @@ func TestMysqlGrammar_CompileDropIndex(t *testing.T) { command := &command{index: tt.indexName} got, err := g.CompileDropIndex(bp, command) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -1185,10 +1188,10 @@ func TestMysqlGrammar_CompileDropUnique(t *testing.T) { command := &command{index: tt.indexName} got, err := g.CompileDropUnique(bp, command) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -1224,10 +1227,10 @@ func TestMysqlGrammar_CompileDropFulltext(t *testing.T) { command := &command{index: tt.indexName} got, err := g.CompileDropFulltext(bp, command) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -1286,10 +1289,10 @@ func TestMysqlGrammar_CompileDropPrimary(t *testing.T) { command := &command{index: tt.indexName} got, err := g.CompileDropPrimary(bp, command) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -1375,10 +1378,10 @@ func TestMysqlGrammar_CompileRenameIndex(t *testing.T) { command := &command{from: tt.oldName, to: tt.newName} got, err := g.CompileRenameIndex(bp, command) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected SQL to match for test case: %s", tt.name) }) } @@ -1795,10 +1798,10 @@ func TestMysqlGrammar_GetColumns(t *testing.T) { tt.blueprint(bp) got, err := g.getColumns(bp) if tt.wantErr { - assert.Error(t, err, "Expected error for test case: %s", tt.name) + require.Error(t, err, "Expected error for test case: %s", tt.name) return } - assert.NoError(t, err, "Did not expect error for test case: %s", tt.name) + require.NoError(t, err, "Did not expect error for test case: %s", tt.name) assert.Equal(t, tt.want, got, "Expected columns to match for test case: %s", tt.name) }) } diff --git a/schema/postgres_builder.go b/schema/postgres_builder.go index 76c9839..13523e8 100644 --- a/schema/postgres_builder.go +++ b/schema/postgres_builder.go @@ -26,6 +26,8 @@ func (b *postgresBuilder) parseSchemaAndTable(name string) (string, string) { return "", names[0] } +const defaultPostgresSchema = "public" + func (b *postgresBuilder) GetColumns(c *Context, tableName string) ([]*Column, error) { if c == nil || tableName == "" { return nil, errors.New("invalid arguments: context is nil or table name is empty") @@ -33,7 +35,7 @@ func (b *postgresBuilder) GetColumns(c *Context, tableName string) ([]*Column, e schema, name := b.parseSchemaAndTable(tableName) if schema == "" { - schema = "public" // Default schema for PostgreSQL + schema = defaultPostgresSchema } query, err := b.grammar.CompileColumns(schema, name) if err != nil { @@ -44,12 +46,12 @@ func (b *postgresBuilder) GetColumns(c *Context, tableName string) ([]*Column, e if err != nil { return nil, err } - defer rows.Close() //nolint:errcheck + defer rows.Close() var columns []*Column for rows.Next() { var col Column - if err := rows.Scan( + if err = rows.Scan( &col.Name, &col.TypeName, &col.TypeFull, &col.Collation, &col.Nullable, &col.DefaultVal, &col.Comment, ); err != nil { @@ -57,6 +59,9 @@ func (b *postgresBuilder) GetColumns(c *Context, tableName string) ([]*Column, e } columns = append(columns, &col) } + if err = rows.Err(); err != nil { + return nil, err + } return columns, nil } @@ -77,18 +82,21 @@ func (b *postgresBuilder) GetIndexes(c *Context, tableName string) ([]*Index, er if err != nil { return nil, err } - defer rows.Close() //nolint:errcheck + defer rows.Close() var indexes []*Index for rows.Next() { var index Index var columnsStr string - if err := rows.Scan(&index.Name, &columnsStr, &index.Type, &index.Unique, &index.Primary); err != nil { + if err = rows.Scan(&index.Name, &columnsStr, &index.Type, &index.Unique, &index.Primary); err != nil { return nil, err } index.Columns = strings.Split(columnsStr, ",") indexes = append(indexes, &index) } + if err = rows.Err(); err != nil { + return nil, err + } return indexes, nil } @@ -107,16 +115,19 @@ func (b *postgresBuilder) GetTables(c *Context) ([]*TableInfo, error) { if err != nil { return nil, err } - defer rows.Close() //nolint:errcheck + defer rows.Close() var tables []*TableInfo for rows.Next() { var table TableInfo - if err := rows.Scan(&table.Name, &table.Schema, &table.Size, &table.Comment); err != nil { + if err = rows.Scan(&table.Name, &table.Schema, &table.Size, &table.Comment); err != nil { return nil, err } tables = append(tables, &table) } + if err = rows.Err(); err != nil { + return nil, err + } return tables, nil } @@ -157,6 +168,7 @@ func (b *postgresBuilder) HasColumns(c *Context, tableName string, columnNames [ return true, nil // All specified columns exist } +// nolint: dupl,godoclint // Similar code exists in other builder files func (b *postgresBuilder) HasIndex(c *Context, tableName string, indexes []string) (bool, error) { if c == nil || tableName == "" { return false, errors.New("invalid arguments: context is nil or table name is empty") @@ -216,7 +228,7 @@ func (b *postgresBuilder) HasTable(c *Context, name string) (bool, error) { } var exists bool - if err := c.QueryRow(query).Scan(&exists); err != nil { + if err = c.QueryRow(query).Scan(&exists); err != nil { if errors.Is(err, sql.ErrNoRows) { return false, nil // Table does not exist } diff --git a/schema/postgres_builder_test.go b/schema/postgres_builder_test.go index 7e7dc0f..e52cab4 100644 --- a/schema/postgres_builder_test.go +++ b/schema/postgres_builder_test.go @@ -39,6 +39,7 @@ func parseTestConfig() dbConfig { type postgresBuilderSuite struct { suite.Suite + ctx context.Context db *sql.DB builder schema.Builder @@ -49,7 +50,12 @@ func (s *postgresBuilderSuite) SetupSuite() { config := parseTestConfig() - dsn := fmt.Sprintf("host=localhost port=5432 user=%s password=%s dbname=%s sslmode=disable", config.Username, config.Password, config.Database) + dsn := fmt.Sprintf( + "host=localhost port=5432 user=%s password=%s dbname=%s sslmode=disable", + config.Username, + config.Password, + config.Database, + ) db, err := sql.Open("pgx", dsn) s.Require().NoError(err) @@ -70,21 +76,21 @@ func (s *postgresBuilderSuite) TestCreate() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { - err := builder.Create(nil, "test_table", func(table *schema.Blueprint) {}) - s.Error(err, "expected error when context is nil") + err = builder.Create(nil, "test_table", func(_ *schema.Blueprint) {}) + s.Require().Error(err, "expected error when context is nil") }) s.Run("when table name is empty, should return error", func() { - err := builder.Create(c, "", func(table *schema.Blueprint) {}) - s.Error(err, "expected error when table name is empty") + err = builder.Create(c, "", func(_ *schema.Blueprint) {}) + s.Require().Error(err, "expected error when table name is empty") }) s.Run("when blueprint is nil, should return error", func() { - err := builder.Create(c, "test_table", nil) - s.Error(err, "expected error when blueprint is nil") + err = builder.Create(c, "test_table", nil) + s.Require().Error(err, "expected error when blueprint is nil") }) s.Run("when all parameters are valid, should create table successfully", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -94,11 +100,11 @@ func (s *postgresBuilderSuite) TestCreate() { table.String("password").Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table with valid parameters") + s.Require().NoError(err, "expected no error when creating table with valid parameters") }) s.Run("when use custom schema should create it successfully", func() { _, err = tx.Exec("CREATE SCHEMA IF NOT EXISTS custom_public") - s.NoError(err, "expected no error when creating custom schema") + s.Require().NoError(err, "expected no error when creating custom schema") err = builder.Create(c, "custom_public.users", func(table *schema.Blueprint) { table.ID() table.String("name") @@ -106,7 +112,7 @@ func (s *postgresBuilderSuite) TestCreate() { table.String("password").Nullable() table.TimestampsTz() }) - s.NoError(err, "expected no error when creating table with custom schema") + s.Require().NoError(err, "expected no error when creating table with custom schema") }) s.Run("when have composite primary key should create it successfully", func() { err = builder.Create(c, "user_roles", func(table *schema.Blueprint) { @@ -115,7 +121,7 @@ func (s *postgresBuilderSuite) TestCreate() { table.Primary("user_id", "role_id") }) - s.NoError(err, "expected no error when creating table with composite primary key") + s.Require().NoError(err, "expected no error when creating table with composite primary key") }) s.Run("when have foreign key should create it successfully", func() { err = builder.Create(c, "orders", func(table *schema.Blueprint) { @@ -127,7 +133,7 @@ func (s *postgresBuilderSuite) TestCreate() { table.Foreign("user_id").References("id").On("users").OnDelete("CASCADE").OnUpdate("CASCADE") }) - s.NoError(err, "expected no error when creating table with foreign key") + s.Require().NoError(err, "expected no error when creating table with foreign key") }) s.Run("when have custom index should create it successfully", func() { err = builder.Create(c, "orders_2", func(table *schema.Blueprint) { @@ -138,7 +144,7 @@ func (s *postgresBuilderSuite) TestCreate() { table.Index("created_at").Name("idx_orders_created_at").Algorithm("BTREE") }) - s.NoError(err, "expected no error when creating table with custom index") + s.Require().NoError(err, "expected no error when creating table with custom index") }) s.Run("when table already exists, should return error", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -146,7 +152,7 @@ func (s *postgresBuilderSuite) TestCreate() { table.String("name") table.String("email").Unique() }) - s.Error(err, "expected error when creating table that already exists") + s.Require().Error(err, "expected error when creating table that already exists") }) } @@ -154,17 +160,17 @@ func (s *postgresBuilderSuite) TestDrop() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { - err := builder.Drop(nil, "test_table") - s.Error(err, "expected error when context is nil") + err = builder.Drop(nil, "test_table") + s.Require().Error(err, "expected error when context is nil") }) s.Run("when table name is empty, should return error", func() { - err := builder.Drop(c, "") - s.Error(err, "expected error when table name is empty") + err = builder.Drop(c, "") + s.Require().Error(err, "expected error when table name is empty") }) s.Run("when all parameters are valid, should drop table successfully", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -174,13 +180,13 @@ func (s *postgresBuilderSuite) TestDrop() { table.String("password").Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before dropping it") + s.Require().NoError(err, "expected no error when creating table before dropping it") err = builder.Drop(c, "users") - s.NoError(err, "expected no error when dropping table with valid parameters") + s.Require().NoError(err, "expected no error when dropping table with valid parameters") }) s.Run("when table does not exist, should return error", func() { err = builder.Drop(c, "non_existent_table") - s.Error(err, "expected error when dropping table that does not exist") + s.Require().Error(err, "expected error when dropping table that does not exist") }) } @@ -188,21 +194,21 @@ func (s *postgresBuilderSuite) TestDropIfExists() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { - err := builder.DropIfExists(nil, "test_table") - s.Error(err, "expected error when context is nil") + err = builder.DropIfExists(nil, "test_table") + s.Require().Error(err, "expected error when context is nil") }) s.Run("when table name is empty, should return error", func() { - err := builder.DropIfExists(c, "") - s.Error(err, "expected error when table name is empty") + err = builder.DropIfExists(c, "") + s.Require().Error(err, "expected error when table name is empty") }) s.Run("when context is nil, should return error", func() { err = builder.DropIfExists(nil, "test_table") - s.Error(err, "expected error when context is nil") + s.Require().Error(err, "expected error when context is nil") }) s.Run("when all parameters are valid, should drop table successfully", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -212,13 +218,13 @@ func (s *postgresBuilderSuite) TestDropIfExists() { table.String("password").Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before dropping it") + s.Require().NoError(err, "expected no error when creating table before dropping it") err = builder.DropIfExists(c, "users") - s.NoError(err, "expected no error when dropping table with valid parameters") + s.Require().NoError(err, "expected no error when dropping table with valid parameters") }) s.Run("when table does not exist, should not return error", func() { err = builder.DropIfExists(c, "non_existent_table") - s.NoError(err, "expected no error when dropping non-existent table with IF EXISTS clause") + s.Require().NoError(err, "expected no error when dropping non-existent table with IF EXISTS clause") }) } @@ -226,34 +232,34 @@ func (s *postgresBuilderSuite) TestRename() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { - err := builder.Rename(nil, "old_table", "new_table") - s.Error(err, "expected error when context is nil") + err = builder.Rename(nil, "old_table", "new_table") + s.Require().Error(err, "expected error when context is nil") }) s.Run("when old table name is empty, should return error", func() { - err := builder.Rename(c, "", "new_table") - s.Error(err, "expected error when old table name is empty") + err = builder.Rename(c, "", "new_table") + s.Require().Error(err, "expected error when old table name is empty") }) s.Run("when new table name is empty, should return error", func() { - err := builder.Rename(c, "old_table", "") - s.Error(err, "expected error when new table name is empty") + err = builder.Rename(c, "old_table", "") + s.Require().Error(err, "expected error when new table name is empty") }) s.Run("when all parameters are valid, should rename table successfully", func() { err = builder.Create(c, "old_table", func(table *schema.Blueprint) { table.ID() table.String("name", 255) }) - s.NoError(err, "expected no error when creating table before renaming it") + s.Require().NoError(err, "expected no error when creating table before renaming it") err = builder.Rename(c, "old_table", "new_table") - s.NoError(err, "expected no error when renaming table with valid parameters") + s.Require().NoError(err, "expected no error when renaming table with valid parameters") }) s.Run("when renaming non-existent table, should return error", func() { err = builder.Rename(c, "non_existent_table", "new_table") - s.Error(err, "expected error when renaming non-existent table") + s.Require().Error(err, "expected error when renaming non-existent table") s.ErrorContains(err, "does not exist", "expected error message to contain 'does not exist'") }) } @@ -262,21 +268,21 @@ func (s *postgresBuilderSuite) TestTable() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { - err := builder.Table(nil, "test_table", func(table *schema.Blueprint) {}) - s.Error(err, "expected error when context is nil") + err = builder.Table(nil, "test_table", func(_ *schema.Blueprint) {}) + s.Require().Error(err, "expected error when context is nil") }) s.Run("when table name is empty, should return error", func() { - err := builder.Table(c, "", func(table *schema.Blueprint) {}) - s.Error(err, "expected error when table name is empty") + err = builder.Table(c, "", func(_ *schema.Blueprint) {}) + s.Require().Error(err, "expected error when table name is empty") }) s.Run("when blueprint is nil, should return error", func() { - err := builder.Table(c, "test_table", nil) - s.Error(err, "expected error when blueprint is nil") + err = builder.Table(c, "test_table", nil) + s.Require().Error(err, "expected error when blueprint is nil") }) s.Run("when all parameters are valid, should modify table successfully", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -289,81 +295,81 @@ func (s *postgresBuilderSuite) TestTable() { table.FullText("bio") }) - s.NoError(err, "expected no error when creating table before modifying it") + s.Require().NoError(err, "expected no error when creating table before modifying it") s.Run("should add new columns and modify existing ones", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.String("address", 255).Nullable() table.String("phone", 20).Nullable().Unique("uk_users_phone") }) - s.NoError(err, "expected no error when modifying table with valid parameters") + s.Require().NoError(err, "expected no error when modifying table with valid parameters") }) s.Run("should modify existing column", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.String("email", 255).Nullable().Change() }) - s.NoError(err, "expected no error when modifying existing column") + s.Require().NoError(err, "expected no error when modifying existing column") }) s.Run("should drop column and rename existing one", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropColumn("password") table.RenameColumn("name", "full_name") }) - s.NoError(err, "expected no error when dropping column and renaming existing one") + s.Require().NoError(err, "expected no error when dropping column and renaming existing one") }) s.Run("should add index", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.Index("phone").Name("idx_users_phone").Algorithm("BTREE") }) - s.NoError(err, "expected no error when adding index to table") + s.Require().NoError(err, "expected no error when adding index to table") }) s.Run("should rename index", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.RenameIndex("idx_users_phone", "idx_users_contact") }) - s.NoError(err, "expected no error when renaming index in table") + s.Require().NoError(err, "expected no error when renaming index in table") }) s.Run("should drop index", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropIndex("idx_users_contact") }) - s.NoError(err, "expected no error when dropping index from table") + s.Require().NoError(err, "expected no error when dropping index from table") }) s.Run("should drop unique constraint", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropUnique([]string{"email"}) }) - s.NoError(err, "expected no error when dropping unique constraint from table") + s.Require().NoError(err, "expected no error when dropping unique constraint from table") }) s.Run("should drop fulltext index", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropFulltext("ft_users_bio") }) - s.NoError(err, "expected no error when dropping fulltext index from table") + s.Require().NoError(err, "expected no error when dropping fulltext index from table") }) s.Run("should add foreign key", func() { err = builder.Create(c, "roles", func(table *schema.Blueprint) { table.ID() table.String("role_name", 255).Unique("uk_roles_role_name") }) - s.NoError(err, "expected no error when creating roles table before adding foreign key") + s.Require().NoError(err, "expected no error when creating roles table before adding foreign key") err = builder.Table(c, "users", func(table *schema.Blueprint) { table.Integer("role_id").Nullable() table.Foreign("role_id").References("id").On("roles").OnDelete("SET NULL").OnUpdate("CASCADE") }) - s.NoError(err, "expected no error when adding foreign key to users table") + s.Require().NoError(err, "expected no error when adding foreign key to users table") }) s.Run("should drop foreign key", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropForeign("fk_users_roles") }) - s.NoError(err, "expected no error when dropping foreign key from users table") + s.Require().NoError(err, "expected no error when dropping foreign key from users table") }) s.Run("should drop primary key", func() { err = builder.Table(c, "users", func(table *schema.Blueprint) { table.DropPrimary("pk_users") }) - s.NoError(err, "expected no error when dropping primary key from users table") + s.Require().NoError(err, "expected no error when dropping primary key from users table") }) }) } @@ -372,17 +378,17 @@ func (s *postgresBuilderSuite) TestGetColumns() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { - _, err := builder.GetColumns(nil, "users") - s.Error(err, "expected error when context is nil") + _, err = builder.GetColumns(nil, "users") + s.Require().Error(err, "expected error when context is nil") }) s.Run("when table name is empty, should return error", func() { - _, err := builder.GetColumns(c, "") - s.Error(err, "expected error when table name is empty") + _, err = builder.GetColumns(c, "") + s.Require().Error(err, "expected error when table name is empty") }) s.Run("when all parameters are valid", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -392,15 +398,15 @@ func (s *postgresBuilderSuite) TestGetColumns() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before getting columns") + s.Require().NoError(err, "expected no error when creating table before getting columns") columns, err := builder.GetColumns(c, "users") - s.NoError(err, "expected no error when getting columns with valid parameters") + s.Require().NoError(err, "expected no error when getting columns with valid parameters") s.Len(columns, 6, "expected 6 columns to be returned") }) s.Run("when table does not exist, should return empty columns", func() { columns, err := builder.GetColumns(c, "non_existent_table") - s.NoError(err, "expected no error when getting columns of non-existent table") + s.Require().NoError(err, "expected no error when getting columns of non-existent table") s.Empty(columns, "expected empty columns for non-existent table") }) } @@ -409,17 +415,17 @@ func (s *postgresBuilderSuite) TestGetIndexes() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { _, err := builder.GetIndexes(nil, "users") - s.Error(err, "expected error when contexts is nil") + s.Require().Error(err, "expected error when contexts is nil") }) s.Run("when table name is empty, should return error", func() { _, err := builder.GetIndexes(c, "") - s.Error(err, "expected error when table name is empty") + s.Require().Error(err, "expected error when table name is empty") }) s.Run("when all parameters are valid", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -431,16 +437,15 @@ func (s *postgresBuilderSuite) TestGetIndexes() { table.Index("name").Name("idx_users_name") }) - s.NoError(err, "expected no error when creating table before getting indexes") + s.Require().NoError(err, "expected no error when creating table before getting indexes") indexes, err := builder.GetIndexes(c, "users") - s.NoError(err, "expected no error when getting indexes with valid parameters") + s.Require().NoError(err, "expected no error when getting indexes with valid parameters") s.Len(indexes, 3, "expected 3 index to be returned") - }) s.Run("when table does not exist, should return empty indexes", func() { indexes, err := builder.GetIndexes(c, "non_existent_table") - s.NoError(err, "expected no error when getting indexes of non-existent table") + s.Require().NoError(err, "expected no error when getting indexes of non-existent table") s.Empty(indexes, "expected empty indexes for non-existent table") }) } @@ -449,13 +454,13 @@ func (s *postgresBuilderSuite) TestGetTables() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { _, err := builder.GetTables(nil) - s.Error(err, "expected error when context is nil") + s.Require().Error(err, "expected error when context is nil") }) s.Run("when all parameters are valid", func() { err = builder.Create(c, "users", func(table *schema.Blueprint) { @@ -465,10 +470,10 @@ func (s *postgresBuilderSuite) TestGetTables() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before getting tables") + s.Require().NoError(err, "expected no error when creating table before getting tables") tables, err := builder.GetTables(c) - s.NoError(err, "expected no error when getting tables with valid parameters") + s.Require().NoError(err, "expected no error when getting tables with valid parameters") s.Len(tables, 1, "expected 1 table to be returned") userTable := tables[0] s.Equal("users", userTable.Name, "expected table name to be 'users'") @@ -481,18 +486,18 @@ func (s *postgresBuilderSuite) TestHasColumn() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when tx is nil, should return error", func() { exists, err := builder.HasColumn(nil, "users", "name") - s.Error(err, "expected error when transaction is nil") + s.Require().Error(err, "expected error when transaction is nil") s.False(exists, "expected exists to be false when transaction is nil") }) s.Run("when table name is empty, should return error", func() { exists, err := builder.HasColumn(c, "", "name") - s.Error(err, "expected error when table name is empty") + s.Require().Error(err, "expected error when table name is empty") s.False(exists, "expected exists to be false when table name is empty") }) s.Run("when all parameters are valid", func() { @@ -503,14 +508,14 @@ func (s *postgresBuilderSuite) TestHasColumn() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before checking column existence") + s.Require().NoError(err, "expected no error when creating table before checking column existence") exists, err := builder.HasColumn(c, "users", "name") - s.NoError(err, "expected no error when checking if column exists with valid parameters") + s.Require().NoError(err, "expected no error when checking if column exists with valid parameters") s.True(exists, "expected exists to be true for existing column") exists, err = builder.HasColumn(c, "users", "non_existent_column") - s.NoError(err, "expected no error when checking non-existent column") + s.Require().NoError(err, "expected no error when checking non-existent column") s.False(exists, "expected exists to be false for non-existent column") }) } @@ -519,18 +524,18 @@ func (s *postgresBuilderSuite) TestHasColumns() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when tx is nil, should return error", func() { exists, err := builder.HasColumns(nil, "users", []string{"name"}) - s.Error(err, "expected error when transaction is nil") + s.Require().Error(err, "expected error when transaction is nil") s.False(exists, "expected exists to be false when transaction is nil") }) s.Run("when table name is empty, should return error", func() { exists, err := builder.HasColumns(c, "", []string{"name"}) - s.Error(err, "expected error when table name is empty") + s.Require().Error(err, "expected error when table name is empty") s.False(exists, "expected exists to be false when table name is empty") }) s.Run("when all parameters are valid", func() { @@ -541,14 +546,14 @@ func (s *postgresBuilderSuite) TestHasColumns() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before checking column existence") + s.Require().NoError(err, "expected no error when creating table before checking column existence") exists, err := builder.HasColumns(c, "users", []string{"name", "email"}) - s.NoError(err, "expected no error when checking if columns exist with valid parameters") + s.Require().NoError(err, "expected no error when checking if columns exist with valid parameters") s.True(exists, "expected exists to be true for existing columns") exists, err = builder.HasColumns(c, "users", []string{"name", "non_existent_column"}) - s.NoError(err, "expected no error when checking mixed existing and non-existent columns") + s.Require().NoError(err, "expected no error when checking mixed existing and non-existent columns") s.False(exists, "expected exists to be false for mixed existing and non-existent columns") }) } @@ -557,18 +562,18 @@ func (s *postgresBuilderSuite) TestHasIndex() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { exists, err := builder.HasIndex(nil, "users", []string{"idx_users_name"}) - s.Error(err, "expected error when context is nil") + s.Require().Error(err, "expected error when context is nil") s.False(exists, "expected exists to be false when context is nil") }) s.Run("when table name is empty, should return error", func() { exists, err := builder.HasIndex(c, "", []string{"idx_users_name"}) - s.Error(err, "expected error when table name is empty") + s.Require().Error(err, "expected error when table name is empty") s.False(exists, "expected exists to be false when table name is empty") }) s.Run("when all parameters are valid", func() { @@ -586,15 +591,15 @@ func (s *postgresBuilderSuite) TestHasIndex() { s.Require().NoError(err, "expected no error when creating table with index") exists, err := builder.HasIndex(c, "orders", []string{"uk_orders_order_id"}) - s.NoError(err, "expected no error when checking if index exists with valid parameters") + s.Require().NoError(err, "expected no error when checking if index exists with valid parameters") s.True(exists, "expected exists to be true for existing index") exists, err = builder.HasIndex(c, "orders", []string{"company_id", "user_id"}) - s.NoError(err, "expected no error when checking non-existent index") + s.Require().NoError(err, "expected no error when checking non-existent index") s.True(exists, "expected exists to be true for existing composite index") exists, err = builder.HasIndex(c, "orders", []string{"non_existent_index"}) - s.NoError(err, "expected no error when checking non-existent index") + s.Require().NoError(err, "expected no error when checking non-existent index") s.False(exists, "expected exists to be false for non-existent index") }) } @@ -603,18 +608,18 @@ func (s *postgresBuilderSuite) TestHasTable() { builder := s.builder tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when context is nil, should return error", func() { exists, err := builder.HasTable(nil, "users") - s.Error(err, "expected error when context is nil") + s.Require().Error(err, "expected error when context is nil") s.False(exists, "expected exists to be false when context is nil") }) s.Run("when table name is empty, should return error", func() { exists, err := builder.HasTable(c, "") - s.Error(err, "expected error when table name is empty") + s.Require().Error(err, "expected error when table name is empty") s.False(exists, "expected exists to be false when table name is empty") }) s.Run("when all parameters are valid", func() { @@ -625,19 +630,19 @@ func (s *postgresBuilderSuite) TestHasTable() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table before checking existence") + s.Require().NoError(err, "expected no error when creating table before checking existence") exists, err := builder.HasTable(c, "users") - s.NoError(err, "expected no error when checking if table exists with valid parameters") + s.Require().NoError(err, "expected no error when checking if table exists with valid parameters") s.True(exists, "expected exists to be true for existing table") exists, err = builder.HasTable(c, "non_existent_table") - s.NoError(err, "expected no error when checking non-existent table") + s.Require().NoError(err, "expected no error when checking non-existent table") s.False(exists, "expected exists to be false for non-existent table") }) s.Run("when checking table with custom schema, should return true if exists", func() { _, err = tx.Exec("CREATE SCHEMA IF NOT EXISTS custom_publics") - s.NoError(err, "expected no error when creating custom schema") + s.Require().NoError(err, "expected no error when creating custom schema") err = builder.Create(c, "custom_publics.users", func(table *schema.Blueprint) { table.ID() @@ -646,13 +651,13 @@ func (s *postgresBuilderSuite) TestHasTable() { table.String("password", 255).Nullable() table.Timestamps() }) - s.NoError(err, "expected no error when creating table with custom schema") + s.Require().NoError(err, "expected no error when creating table with custom schema") exists, err := builder.HasTable(c, "custom_publics.users") - s.NoError(err, "expected no error when checking if table with custom schema exists") + s.Require().NoError(err, "expected no error when checking if table with custom schema exists") s.True(exists, "expected exists to be true for existing table with custom schema") exists, err = builder.HasTable(c, "custom_publics.non_existent_table") - s.NoError(err, "expected no error when checking non-existent table with custom schema") + s.Require().NoError(err, "expected no error when checking non-existent table with custom schema") s.False(exists, "expected exists to be false for non-existent table with custom schema") }) } diff --git a/schema/postgres_grammar.go b/schema/postgres_grammar.go index 130c1ef..3f661aa 100644 --- a/schema/postgres_grammar.go +++ b/schema/postgres_grammar.go @@ -1,6 +1,7 @@ package schema import ( + "errors" "fmt" "slices" "strings" @@ -22,7 +23,7 @@ func (g *postgresGrammar) CompileTableExists(schema string, table string) (strin ), nil } -func (g *postgresGrammar) CompileTables(schema string) (string, error) { +func (g *postgresGrammar) CompileTables(_ string) (string, error) { return "select c.relname as name, n.nspname as schema, pg_total_relation_size(c.oid) as size, " + "obj_description(c.oid, 'pg_class') as comment from pg_class c, pg_namespace n " + "where c.relkind in ('r', 'p') and n.oid = c.relnamespace and n.nspname not in ('pg_catalog', 'information_schema') " + @@ -96,7 +97,7 @@ func (g *postgresGrammar) CompileAdd(blueprint *Blueprint) (string, error) { func (g *postgresGrammar) CompileChange(bp *Blueprint, command *command) (string, error) { column := command.column if column.name == "" { - return "", fmt.Errorf("column name cannot be empty for change operation") + return "", errors.New("column name cannot be empty for change operation") } var changes []string @@ -137,14 +138,14 @@ func (g *postgresGrammar) CompileDropColumn(blueprint *Blueprint, command *comma func (g *postgresGrammar) CompileRenameColumn(blueprint *Blueprint, command *command) (string, error) { if command.from == "" || command.to == "" { - return "", fmt.Errorf("table name, old column name, and new column name cannot be empty for rename operation") + return "", errors.New("table name, old column name, and new column name cannot be empty for rename operation") } return fmt.Sprintf("ALTER TABLE %s RENAME COLUMN %s TO %s", blueprint.name, command.from, command.to), nil } func (g *postgresGrammar) CompileFullText(blueprint *Blueprint, command *command) (string, error) { if slices.Contains(command.columns, "") { - return "", fmt.Errorf("fulltext index column cannot be empty") + return "", errors.New("fulltext index column cannot be empty") } indexName := command.index if indexName == "" { @@ -159,12 +160,17 @@ func (g *postgresGrammar) CompileFullText(blueprint *Blueprint, command *command columns = append(columns, fmt.Sprintf("to_tsvector(%s, %s)", g.QuoteString(language), col)) } - return fmt.Sprintf("CREATE INDEX %s ON %s USING GIN (%s)", indexName, blueprint.name, strings.Join(columns, " || ")), nil + return fmt.Sprintf( + "CREATE INDEX %s ON %s USING GIN (%s)", + indexName, + blueprint.name, + strings.Join(columns, " || "), + ), nil } func (g *postgresGrammar) CompileIndex(blueprint *Blueprint, command *command) (string, error) { if slices.Contains(command.columns, "") { - return "", fmt.Errorf("index column cannot be empty") + return "", errors.New("index column cannot be empty") } indexName := command.index if indexName == "" { @@ -180,7 +186,7 @@ func (g *postgresGrammar) CompileIndex(blueprint *Blueprint, command *command) ( func (g *postgresGrammar) CompileUnique(blueprint *Blueprint, command *command) (string, error) { if slices.Contains(command.columns, "") { - return "", fmt.Errorf("unique index column cannot be empty") + return "", errors.New("unique index column cannot be empty") } indexName := command.index if indexName == "" { @@ -212,18 +218,23 @@ func (g *postgresGrammar) CompileUnique(blueprint *Blueprint, command *command) func (g *postgresGrammar) CompilePrimary(blueprint *Blueprint, command *command) (string, error) { if slices.Contains(command.columns, "") { - return "", fmt.Errorf("primary key index column cannot be empty") + return "", errors.New("primary key index column cannot be empty") } indexName := command.index if indexName == "" { indexName = g.CreateIndexName(blueprint, "primary", command.columns...) } - return fmt.Sprintf("ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (%s)", blueprint.name, indexName, g.Columnize(command.columns)), nil + return fmt.Sprintf( + "ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (%s)", + blueprint.name, + indexName, + g.Columnize(command.columns), + ), nil } func (g *postgresGrammar) CompileDropIndex(_ *Blueprint, command *command) (string, error) { if command.index == "" { - return "", fmt.Errorf("index name cannot be empty for drop operation") + return "", errors.New("index name cannot be empty for drop operation") } return fmt.Sprintf("DROP INDEX %s", command.index), nil } @@ -234,7 +245,7 @@ func (g *postgresGrammar) CompileDropFulltext(blueprint *Blueprint, command *com func (g *postgresGrammar) CompileDropUnique(blueprint *Blueprint, command *command) (string, error) { if command.index == "" { - return "", fmt.Errorf("index name cannot be empty for drop operation") + return "", errors.New("index name cannot be empty for drop operation") } return fmt.Sprintf("ALTER TABLE %s DROP CONSTRAINT %s", blueprint.name, command.index), nil } @@ -249,7 +260,11 @@ func (g *postgresGrammar) CompileDropPrimary(blueprint *Blueprint, command *comm func (g *postgresGrammar) CompileRenameIndex(_ *Blueprint, command *command) (string, error) { if command.from == "" || command.to == "" { - return "", fmt.Errorf("index names for rename operation cannot be empty: oldName=%s, newName=%s", command.from, command.to) + return "", fmt.Errorf( + "index names for rename operation cannot be empty: oldName=%s, newName=%s", + command.from, + command.to, + ) } return fmt.Sprintf("ALTER INDEX %s RENAME TO %s", command.from, command.to), nil } @@ -280,7 +295,7 @@ func (g *postgresGrammar) CompileForeign(blueprint *Blueprint, command *command) func (g *postgresGrammar) CompileDropForeign(blueprint *Blueprint, command *command) (string, error) { if command.index == "" { - return "", fmt.Errorf("foreign key name cannot be empty for drop operation") + return "", errors.New("foreign key name cannot be empty for drop operation") } return fmt.Sprintf("ALTER TABLE %s DROP CONSTRAINT %s", blueprint.name, command.index), nil } @@ -296,9 +311,8 @@ func (g *postgresGrammar) CompileComment(blueprint *Blueprint, command *command) sql := fmt.Sprintf("COMMENT ON COLUMN %s.%s IS ", blueprint.name, command.column.name) if command.column.comment == nil { return sql + "NULL" - } else { - return sql + fmt.Sprintf("'%s'", *command.column.comment) } + return sql + fmt.Sprintf("'%s'", *command.column.comment) } return "" } @@ -307,7 +321,7 @@ func (g *postgresGrammar) getColumns(blueprint *Blueprint) ([]string, error) { var columns []string for _, col := range blueprint.getAddedColumns() { if col.name == "" { - return nil, fmt.Errorf("column name cannot be empty") + return nil, errors.New("column name cannot be empty") } sql := col.name + " " + g.getType(col) for _, modifier := range g.modifiers() { @@ -333,6 +347,7 @@ func (g *postgresGrammar) getConstraints(blueprint *Blueprint) []string { return constrains } +// nolint: dupl // Similar code exists in other grammar files func (g *postgresGrammar) getType(col *columnDefinition) string { typeMapFunc := map[string]func(*columnDefinition) string{ columnTypeChar: g.typeChar, @@ -351,8 +366,8 @@ func (g *postgresGrammar) getType(col *columnDefinition) string { columnTypeDecimal: g.typeDecimal, columnTypeBoolean: g.typeBoolean, columnTypeEnum: g.typeEnum, - columnTypeJson: g.typeJson, - columnTypeJsonb: g.typeJsonb, + columnTypeJSON: g.typeJSON, + columnTypeJSONB: g.typeJSONB, columnTypeDate: g.typeDate, columnTypeDateTime: g.typeDateTime, columnTypeDateTimeTz: g.typeDateTimeTz, @@ -362,7 +377,7 @@ func (g *postgresGrammar) getType(col *columnDefinition) string { columnTypeTimestampTz: g.typeTimestampTz, columnTypeYear: g.typeYear, columnTypeBinary: g.typeBinary, - columnTypeUuid: g.typeUuid, + columnTypeUUID: g.typeUUID, columnTypeGeography: g.typeGeography, columnTypeGeometry: g.typeGeometry, columnTypePoint: g.typePoint, @@ -456,11 +471,11 @@ func (g *postgresGrammar) typeEnum(col *columnDefinition) string { return "VARCHAR(255) CHECK (" + col.name + " IN (" + strings.Join(enumValues, ", ") + "))" } -func (g *postgresGrammar) typeJson(_ *columnDefinition) string { +func (g *postgresGrammar) typeJSON(_ *columnDefinition) string { return "JSON" } -func (g *postgresGrammar) typeJsonb(_ *columnDefinition) string { +func (g *postgresGrammar) typeJSONB(_ *columnDefinition) string { return "JSONB" } @@ -518,7 +533,7 @@ func (g *postgresGrammar) typeBinary(_ *columnDefinition) string { return "BYTEA" } -func (g *postgresGrammar) typeUuid(_ *columnDefinition) string { +func (g *postgresGrammar) typeUUID(_ *columnDefinition) string { return "UUID" } diff --git a/schema/postgres_grammar_test.go b/schema/postgres_grammar_test.go index 2a805f0..adbaf44 100644 --- a/schema/postgres_grammar_test.go +++ b/schema/postgres_grammar_test.go @@ -1,9 +1,10 @@ -package schema +package schema //nolint:testpackage // Need to access unexported members for testing import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPgGrammar_CompileCreate(t *testing.T) { @@ -57,11 +58,11 @@ func TestPgGrammar_CompileCreate(t *testing.T) { tt.blueprint(bp) got, err := grammar.CompileCreate(bp) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got, "SQL statement mismatch for %s", tt.name) }) } @@ -167,7 +168,7 @@ func TestPgGrammar_CompileAdd(t *testing.T) { { name: "No columns to add", table: "users", - blueprint: func(table *Blueprint) {}, + blueprint: func(_ *Blueprint) {}, want: "", wantErr: false, }, @@ -205,11 +206,11 @@ func TestPgGrammar_CompileAdd(t *testing.T) { tt.blueprint(bp) got, err := grammar.CompileAdd(bp) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -239,7 +240,9 @@ func TestPgGrammar_CompileChange(t *testing.T) { blueprint: func(table *Blueprint) { table.String("email", 500).Default("user@mail.com").Change() }, - want: []string{"ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(500), ALTER COLUMN email SET DEFAULT 'user@mail.com'"}, + want: []string{ + "ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(500), ALTER COLUMN email SET DEFAULT 'user@mail.com'", + }, }, { name: "Change multiple columns", @@ -259,7 +262,9 @@ func TestPgGrammar_CompileChange(t *testing.T) { blueprint: func(table *Blueprint) { table.String("email", 500).Default(nil).Change() }, - want: []string{"ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(500), ALTER COLUMN email SET DEFAULT NULL"}, + want: []string{ + "ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(500), ALTER COLUMN email SET DEFAULT NULL", + }, }, { name: "Add comment to column", @@ -278,7 +283,10 @@ func TestPgGrammar_CompileChange(t *testing.T) { blueprint: func(table *Blueprint) { table.String("email", 500).Comment("").Change() }, - want: []string{"ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(500)", "COMMENT ON COLUMN users.email IS ''"}, + want: []string{ + "ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(500)", + "COMMENT ON COLUMN users.email IS ''", + }, }, { name: "Set column to not nullable", @@ -299,7 +307,7 @@ func TestPgGrammar_CompileChange(t *testing.T) { { name: "No changes", table: "users", - blueprint: func(table *Blueprint) {}, + blueprint: func(_ *Blueprint) {}, wantErr: false, }, } @@ -308,13 +316,13 @@ func TestPgGrammar_CompileChange(t *testing.T) { t.Run(tt.name, func(t *testing.T) { bp := &Blueprint{name: tt.table, grammar: grammar} tt.blueprint(bp) - got, err := bp.toSql() + got, err := bp.toSQL() if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -342,11 +350,11 @@ func TestPgGrammar_CompileDrop(t *testing.T) { bp := &Blueprint{name: tt.table} got, err := grammar.CompileDrop(bp) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -374,11 +382,11 @@ func TestPgGrammar_CompileDropIfExists(t *testing.T) { bp := &Blueprint{name: tt.table} got, err := grammar.CompileDropIfExists(bp) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -409,11 +417,11 @@ func TestPgGrammar_CompileRename(t *testing.T) { bp.rename(tt.newName) got, err := grammar.CompileRename(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -512,13 +520,16 @@ func TestPgGrammar_CompileDropColumn(t *testing.T) { table.DropColumn("email", "phone") table.DropColumn("address") }, - wants: []string{"ALTER TABLE users DROP COLUMN email, DROP COLUMN phone", "ALTER TABLE users DROP COLUMN address"}, + wants: []string{ + "ALTER TABLE users DROP COLUMN email, DROP COLUMN phone", + "ALTER TABLE users DROP COLUMN address", + }, wantErr: false, }, { name: "No columns to drop", table: "users", - blueprint: func(table *Blueprint) {}, + blueprint: func(_ *Blueprint) {}, wants: nil, wantErr: false, }, @@ -528,13 +539,13 @@ func TestPgGrammar_CompileDropColumn(t *testing.T) { t.Run(tt.name, func(t *testing.T) { bp := &Blueprint{name: tt.table, grammar: grammar} tt.blueprint(bp) - got, err := bp.toSql() + got, err := bp.toSQL() if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.wants, got) }) } @@ -588,11 +599,11 @@ func TestPgGrammar_CompileRenameColumn(t *testing.T) { command := &command{from: tt.oldName, to: tt.newName} got, err := grammar.CompileRenameColumn(bp, command) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -633,12 +644,12 @@ func TestPgGrammar_CompileDropIndex(t *testing.T) { command := &command{index: tt.indexName} got, err := grammar.CompileDropIndex(bp, command) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) assert.Empty(t, got) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -679,11 +690,11 @@ func TestPgGrammar_CompileDropPrimary(t *testing.T) { command := &command{index: tt.indexName} got, err := grammar.CompileDropPrimary(tt.blueprint, command) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -745,11 +756,11 @@ func TestPgGrammar_CompileRenameIndex(t *testing.T) { command := &command{from: tt.oldName, to: tt.newName} got, err := grammar.CompileRenameIndex(bp, command) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -923,11 +934,11 @@ func TestPgGrammar_CompileForeign(t *testing.T) { tt.blueprint(bp) got, err := grammar.CompileForeign(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -972,11 +983,11 @@ func TestPgGrammar_CompileDropForeign(t *testing.T) { command := &command{index: tt.foreignKeyName} got, err := grammar.CompileDropForeign(bp, command) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -1052,11 +1063,11 @@ func TestPgGrammar_CompileIndex(t *testing.T) { tt.blueprint(bp) got, err := grammar.CompileIndex(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -1160,11 +1171,11 @@ func TestPgGrammar_CompileUnique(t *testing.T) { tt.blueprint(bp) got, err := grammar.CompileUnique(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -1258,11 +1269,11 @@ func TestPgGrammar_CompileFullText(t *testing.T) { tt.blueprint(bp) got, err := grammar.CompileFullText(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -1309,12 +1320,12 @@ func TestPgGrammar_CompileDropUnique(t *testing.T) { command := &command{index: tt.indexName} got, err := grammar.CompileDropUnique(bp, command) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) assert.Empty(t, got) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -1367,12 +1378,12 @@ func TestPgGrammar_CompileDropFulltext(t *testing.T) { command := &command{index: tt.indexName} got, err := grammar.CompileDropFulltext(bp, command) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) assert.Empty(t, got) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -1448,11 +1459,11 @@ func TestPgGrammar_CompilePrimary(t *testing.T) { tt.blueprint(bp) got, err := grammar.CompilePrimary(bp, bp.commands[0]) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/schema/schema.go b/schema/schema.go index edc0350..26d67da 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -43,7 +43,9 @@ type TableInfo struct { func newBuilder() (Builder, error) { dialectVal := config.GetDialect() if dialectVal == dialect.Unknown { - return nil, errors.New("schema dialect is not set, please call schema.SetDialect() before using schema functions") + return nil, errors.New( + "schema dialect is not set, please call schema.SetDialect() before using schema functions", + ) } builder, err := NewBuilder(dialectVal.String()) diff --git a/schema/schema_test.go b/schema/schema_test.go index a873822..016e92e 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -18,6 +18,7 @@ func TestSchema(t *testing.T) { type schemaTestSuite struct { suite.Suite + ctx context.Context db *sql.DB } @@ -29,7 +30,12 @@ func (s *schemaTestSuite) SetupSuite() { config := parseTestConfig() - dsn := fmt.Sprintf("host=localhost port=5432 user=%s password=%s dbname=%s sslmode=disable", config.Username, config.Password, config.Database) + dsn := fmt.Sprintf( + "host=localhost port=5432 user=%s password=%s dbname=%s sslmode=disable", + config.Username, + config.Password, + config.Database, + ) db, err := sql.Open("pgx", dsn) s.Require().NoError(err) @@ -46,7 +52,7 @@ func (s *schemaTestSuite) TearDownSuite() { func (s *schemaTestSuite) TestCreate() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -59,25 +65,25 @@ func (s *schemaTestSuite) TestCreate() { table.Timestamp("created_at").UseCurrent() table.Timestamp("updated_at").UseCurrent() }) - s.NoError(err) + s.Require().NoError(err) }) s.Run("when table already exists should return error", func() { err := schema.Create(c, "users", func(table *schema.Blueprint) { table.ID() }) - s.Error(err) + s.Require().Error(err) s.ErrorContains(err, "\"users\" already exists") }) s.Run("when table name is empty should return error", func() { err := schema.Create(c, "", func(table *schema.Blueprint) { table.ID() }) - s.Error(err) + s.Require().Error(err) s.ErrorContains(err, "invalid arguments") }) s.Run("when blueprint function is nil should return error", func() { err := schema.Create(c, "test", nil) - s.Error(err) + s.Require().Error(err) s.ErrorContains(err, "invalid arguments") }) } @@ -85,7 +91,7 @@ func (s *schemaTestSuite) TestCreate() { func (s *schemaTestSuite) TestDrop() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -100,20 +106,20 @@ func (s *schemaTestSuite) TestDrop() { }) s.Require().NoError(err) err = schema.Drop(c, "users") - s.NoError(err) + s.Require().NoError(err) }) s.Run("when table does not exist should return error", func() { err := schema.Drop(c, "non_existing_table") - s.Error(err) + s.Require().Error(err) }) s.Run("when table name is empty should return error", func() { err := schema.Drop(c, "") - s.Error(err) + s.Require().Error(err) s.ErrorContains(err, "invalid arguments") }) s.Run("when context is nil should return error", func() { err := schema.Drop(nil, "test") - s.Error(err) + s.Require().Error(err) s.ErrorContains(err, "invalid arguments") }) } @@ -121,7 +127,7 @@ func (s *schemaTestSuite) TestDrop() { func (s *schemaTestSuite) TestDropIfExists() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -136,20 +142,20 @@ func (s *schemaTestSuite) TestDropIfExists() { }) s.Require().NoError(err) err = schema.DropIfExists(c, "users") - s.NoError(err) + s.Require().NoError(err) }) s.Run("when table does not exist should return no error", func() { err := schema.DropIfExists(c, "non_existing_table") - s.NoError(err) + s.Require().NoError(err) }) s.Run("when table name is empty should return error", func() { err := schema.DropIfExists(c, "") - s.Error(err) + s.Require().Error(err) s.ErrorContains(err, "invalid arguments") }) s.Run("when context is nil should return error", func() { err := schema.DropIfExists(nil, "test") - s.Error(err) + s.Require().Error(err) s.ErrorContains(err, "invalid arguments") }) } @@ -157,7 +163,7 @@ func (s *schemaTestSuite) TestDropIfExists() { func (s *schemaTestSuite) TestGetColumns() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -172,23 +178,23 @@ func (s *schemaTestSuite) TestGetColumns() { }) s.Require().NoError(err) columns, err := schema.GetColumns(c, "users") - s.NoError(err) + s.Require().NoError(err) s.NotEmpty(columns) s.Len(columns, 6) }) s.Run("when table does not exist should empty columns", func() { columns, err := schema.GetColumns(c, "non_existing_table") - s.NoError(err) + s.Require().NoError(err) s.Nil(columns) }) s.Run("when table name is empty should return error", func() { columns, err := schema.GetColumns(c, "") - s.Error(err) + s.Require().Error(err) s.Nil(columns) }) s.Run("when context is nil should return error", func() { columns, err := schema.GetColumns(nil, "test") - s.Error(err) + s.Require().Error(err) s.Nil(columns) }) } @@ -196,7 +202,7 @@ func (s *schemaTestSuite) TestGetColumns() { func (s *schemaTestSuite) TestGetIndexes() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -211,23 +217,23 @@ func (s *schemaTestSuite) TestGetIndexes() { }) s.Require().NoError(err) indexes, err := schema.GetIndexes(c, "users") - s.NoError(err) + s.Require().NoError(err) s.NotEmpty(indexes) s.Len(indexes, 2) // Expecting the unique index on email and the primary key index on id }) s.Run("when table does not exist should return empty indexes", func() { indexes, err := schema.GetIndexes(c, "non_existing_table") - s.NoError(err) + s.Require().NoError(err) s.Nil(indexes) }) s.Run("when table name is empty should return error", func() { indexes, err := schema.GetIndexes(c, "") - s.Error(err) + s.Require().Error(err) s.Nil(indexes) }) s.Run("when context is nil should return error", func() { indexes, err := schema.GetIndexes(nil, "test") - s.Error(err) + s.Require().Error(err) s.Nil(indexes) }) } @@ -235,13 +241,13 @@ func (s *schemaTestSuite) TestGetIndexes() { func (s *schemaTestSuite) TestGetTables() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) s.Run("when no tables exist should return empty", func() { tables, err := schema.GetTables(c) - s.NoError(err) + s.Require().NoError(err) s.Empty(tables) }) s.Run("when transaction is valid should return tables", func() { @@ -255,13 +261,13 @@ func (s *schemaTestSuite) TestGetTables() { }) s.Require().NoError(err) tables, err := schema.GetTables(c) - s.NoError(err) + s.Require().NoError(err) s.NotEmpty(tables) s.Len(tables, 1) // Expecting at least the 'users' table created in previous tests }) s.Run("when context is nil should return error", func() { tables, err := schema.GetTables(nil) - s.Error(err) + s.Require().Error(err) s.Nil(tables) }) } @@ -269,7 +275,7 @@ func (s *schemaTestSuite) TestGetTables() { func (s *schemaTestSuite) TestHasColumn() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -285,19 +291,19 @@ func (s *schemaTestSuite) TestHasColumn() { s.Require().NoError(err) exists, err := schema.HasColumn(c, "users", "email") - s.NoError(err) + s.Require().NoError(err) s.True(exists) }) s.Run("when column does not exist should return false", func() { exists, err := schema.HasColumn(c, "users", "non_existing_column") - s.NoError(err) + s.Require().NoError(err) s.False(exists) }) s.Run("when context is nil should return error", func() { exists, err := schema.HasColumn(nil, "users", "email") - s.Error(err) + s.Require().Error(err) s.False(exists) }) } @@ -305,7 +311,7 @@ func (s *schemaTestSuite) TestHasColumn() { func (s *schemaTestSuite) TestHasColumns() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -321,17 +327,17 @@ func (s *schemaTestSuite) TestHasColumns() { s.Require().NoError(err) exists, err := schema.HasColumns(c, "users", []string{"email", "name"}) - s.NoError(err) + s.Require().NoError(err) s.True(exists) }) s.Run("when some columns do not exist should return false", func() { exists, err := schema.HasColumns(c, "users", []string{"email", "non_existing_column"}) - s.NoError(err) + s.Require().NoError(err) s.False(exists) }) s.Run("when no columns are provided should return error", func() { exists, err := schema.HasColumns(c, "users", []string{}) - s.Error(err) + s.Require().Error(err) s.False(exists) }) } @@ -339,7 +345,7 @@ func (s *schemaTestSuite) TestHasColumns() { func (s *schemaTestSuite) TestHasIndex() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -358,20 +364,20 @@ func (s *schemaTestSuite) TestHasIndex() { s.Require().NoError(err) exists, err := schema.HasIndex(c, "users", []string{"email"}) - s.NoError(err) + s.Require().NoError(err) s.True(exists) exists, err = schema.HasIndex(c, "users", []string{"company_id", "id"}) - s.NoError(err) + s.Require().NoError(err) s.True(exists) exists, err = schema.HasIndex(c, "users", []string{"uk_users_email"}) - s.NoError(err) + s.Require().NoError(err) s.True(exists) }) s.Run("when index does not exist should return false", func() { exists, err := schema.HasIndex(c, "users", []string{"non_existing_index"}) - s.NoError(err) + s.Require().NoError(err) s.False(exists) }) } @@ -379,7 +385,7 @@ func (s *schemaTestSuite) TestHasIndex() { func (s *schemaTestSuite) TestHasTable() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -395,22 +401,22 @@ func (s *schemaTestSuite) TestHasTable() { s.Require().NoError(err) exists, err := schema.HasTable(c, "users") - s.NoError(err) + s.Require().NoError(err) s.True(exists) }) s.Run("when table does not exist should return false", func() { exists, err := schema.HasTable(c, "non_existing_table") - s.NoError(err) + s.Require().NoError(err) s.False(exists) }) s.Run("when table name is empty should return error", func() { exists, err := schema.HasTable(c, "") - s.Error(err) + s.Require().Error(err) s.False(exists) }) s.Run("when context is nil should return error", func() { exists, err := schema.HasTable(nil, "users") - s.Error(err) + s.Require().Error(err) s.False(exists) }) } @@ -418,7 +424,7 @@ func (s *schemaTestSuite) TestHasTable() { func (s *schemaTestSuite) TestRename() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -434,34 +440,34 @@ func (s *schemaTestSuite) TestRename() { s.Require().NoError(err) err = schema.Rename(c, "users", "members") - s.NoError(err) + s.Require().NoError(err) columns, err := schema.GetColumns(c, "members") - s.NoError(err) + s.Require().NoError(err) s.NotEmpty(columns) s.Len(columns, 6) }) s.Run("when table does not exist should return error", func() { err := schema.Rename(c, "non_existing_table", "new_name") - s.Error(err) + s.Require().Error(err) }) s.Run("when new name is empty should return error", func() { err := schema.Rename(c, "users", "") - s.Error(err) + s.Require().Error(err) }) s.Run("when context is nil should return error", func() { err := schema.Rename(nil, "users", "new_name") - s.Error(err) + s.Require().Error(err) }) } func (s *schemaTestSuite) TestTable() { tx, err := s.db.BeginTx(s.ctx, nil) s.Require().NoError(err) - defer tx.Rollback() //nolint:errcheck + defer tx.Rollback() c := schema.NewContext(s.ctx, tx) @@ -479,10 +485,10 @@ func (s *schemaTestSuite) TestTable() { err = schema.Table(c, "users", func(table *schema.Blueprint) { table.String("phone").Nullable() }) - s.NoError(err) + s.Require().NoError(err) columns, err := schema.GetColumns(c, "users") - s.NoError(err) + s.Require().NoError(err) s.Len(columns, 7) // Expecting the new 'phone' column to be added }) @@ -490,13 +496,13 @@ func (s *schemaTestSuite) TestTable() { err := schema.Table(c, "non_existing_table", func(table *schema.Blueprint) { table.String("new_column") }) - s.Error(err) + s.Require().Error(err) }) s.Run("when context is nil should return error", func() { err := schema.Table(nil, "users", func(table *schema.Blueprint) { table.String("new_column") }) - s.Error(err) + s.Require().Error(err) }) }