Skip to content

Add internal test exports and improve golangci-lint configuration#31

Merged
AlphaOne1 merged 11 commits intomasterfrom
separate_test_package
Aug 25, 2025
Merged

Add internal test exports and improve golangci-lint configuration#31
AlphaOne1 merged 11 commits intomasterfrom
separate_test_package

Conversation

@AlphaOne1
Copy link
Copy Markdown
Owner

@AlphaOne1 AlphaOne1 commented Aug 10, 2025

Summary by CodeRabbit

  • Documentation

    • Minor wording and comment clarifications.
  • Tests

    • Many tests converted to external-package style and updated to exercise the public API.
    • Added test-accessible helpers and exported wrappers to support testing.
  • Public API

    • Core APIs and migration flows now accept context for cancellation/timeouts; signatures updated.
  • Refactor

    • Context propagation added throughout migration and execution flows.
  • Chores

    • Linter/configuration expanded and CI workflow comment/header typo and license header updated.

- Introduced `exports_internal_test.go` for internal testing of private methods.
- Updated `.golangci.yaml` with additional linter settings and exclusions.
- Fixed typos and improved documentation consistency across files.
- Namespaced test imports properly for clarity.
@AlphaOne1 AlphaOne1 self-assigned this Aug 10, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 10, 2025

Warning

Rate limit exceeded

@AlphaOne1 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 4 minutes and 20 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between dece3c8 and 3d9c549.

📒 Files selected for processing (3)
  • dialects_test.go (4 hunks)
  • file_migration_test.go (1 hunks)
  • migration_test.go (9 hunks)

Walkthrough

Context.Context was threaded through public APIs and internal flows; DB/tx operations were made context-aware (BeginTx/ExecContext/QueryContext); internal helpers were exposed as test-only wrappers; many tests moved to external package to exercise the public API; CI comment punctuation and linter config were updated.

Changes

Cohort / File(s) Change Summary
Workflow & linter config
\.github/workflows/codeql.yml, \.golangci\.yaml
Minor punctuation change in CodeQL workflow comment; extensive .golangci.yaml additions and reconfiguration (license header, explicit disabled linters, exclusions, test/example rules, numerous linter settings).
Dialect API & transaction handling
dialects.go
Dialect methods made context-aware (EnsureMigrationTableExists, AppliedMigrations, RegisterMigration now take ctx context.Context); use BeginTx/ExecContext/QueryContext; guarded defer rollback and updated error aggregation.
Migration core (API & flow)
migration.go
Threaded context.Context through Run, Morpher.Run, applyMigrations, Migration interface (Migrate), and Dialect methods; added runOneMigration with context-bound transactions and per-migration cancellation checks.
File migration parsing & execution
file_migration.go
File migration plumbing made context-aware (Migrate, migrationFunc, applyStepsStream); scanner buffer constants added and configured; ExecContext used; improved buffering, step counting, comment handling; WithMigrationsFromFS changed to fs.FS.
Test helpers & exported wrappers
exports_internal_test.go
New test-only exported aliases/wrappers added: TapplyStepsStream, TmigrationFromFileFS, TmigrationOrder, and Morpher.TapplyMigrations(ctx, db, lastMigration) to expose internals for tests.
Tests moved to external package & updated
dialects_test.go, file_migration_test.go, migration_test.go
Tests changed to dmorph_test, import the public dmorph package, call public APIs with context (e.g., dmorph.Run(t.Context(), ...)), and use new exported test wrappers and public errors/constants.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Morpher
  participant Dialect
  participant DB
  participant Migration

  Caller->>Morpher: Run(ctx, db, ...)
  Morpher->>Dialect: EnsureMigrationTableExists(ctx, db, table)
  Dialect->>DB: BeginTx(ctx, ...)
  DB-->>Dialect: tx
  Dialect->>DB: ExecContext(ctx, create-table SQL)
  alt Exec error
    DB-->>Dialect: error
    Dialect->>DB: Rollback(tx)
    Dialect-->>Morpher: error
  else Exec success
    DB-->>Dialect: ok
    Dialect->>DB: Commit()
    Dialect-->>Morpher: nil
  end

  Morpher->>Morpher: AppliedMigrations(ctx, db, table)
  Morpher->>Morpher: applyMigrations(ctx, db, lastMigration)
  loop for each migration
    Morpher->>DB: BeginTx(ctx, ...)
    Morpher->>Migration: Migrate(ctx, tx)
    Migration->>DB: ExecContext(ctx, ...)
    alt migration error or ctx canceled
      DB-->>Morpher: error
      Morpher->>DB: Rollback(tx)
    else success
      Morpher->>Dialect: RegisterMigration(ctx, tx, id, table)
      Morpher->>DB: Commit()
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nibbled a carrot, found a ctx in the loam,
I start txs that listen and bring them home.
Hidden hops revealed for tests to play,
Files and flows now heed timeouts each day.
Linted and tidy — a rabbit's small hooray. 🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch separate_test_package

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (10)
.github/workflows/codeql.yml (1)

85-85: Nit: Prefer “analyze step” (two words) or quote the exact step/action name

Hyphenating as “analyze-step” reads awkwardly and is inconsistent with the rest of the file. Consider either plain “analyze step” or explicitly naming the action/step to avoid ambiguity.

-          # If the analyze-step fails for one of the languages you are analyzing with
+          # If the analyze step fails for one of the languages you are analyzing with
dialects.go (1)

33-37: Optional: avoid redundant Rollback calls

Pattern uses a deferred Rollback plus explicit Rollback on errors, causing Rollback to be invoked twice on error paths (deferred one returns ErrTxDone, ignored). Safe but noisy. If you want to tighten this, prefer a defer that only rolls back when needed (named error + check) or drop the explicit defer and rely on explicit rollbacks and commit.

Also applies to: 39-43

file_migration.go (1)

114-121: Optional: increase Scanner buffer to handle long SQL lines

bufio.Scanner has a default token limit (~64K). Long statements or very long lines can trip “token too long”. Consider configuring a larger buffer or switching to bufio.Reader with custom splitting.

Example:

 scanner := bufio.NewScanner(r)
+// Allow longer lines/statements (up to 10MB here).
+scanner.Buffer(make([]byte, 0, 64*1024), 10*1024*1024)
.golangci.yaml (1)

93-95: Issues block duplicated after move

Once you move exclude-rules into issues (previous comment), keep a single issues section. Combine max-issues-per-linter and max-same-issues there.

dialects_test.go (1)

92-99: Rename short variable for clarity and lint compliance

Static analysis flagged variable name ‘d’ as too short for scope. Rename to dialect for readability.

- d := dmorph.BaseDialect{
+ dialect := dmorph.BaseDialect{
@@
- assert.Error(t, d.EnsureMigrationTableExists(db, "test"), "expected error")
+ assert.Error(t, dialect.EnsureMigrationTableExists(db, "test"), "expected error")
migration.go (2)

283-289: Optional (Go 1.22+): use integer range for indices

You can simplify the index loop if targeting Go 1.22+.

- for i := 0; i < len(appliedMigrations); i++ {
-     if appliedMigrations[i] != m.Migrations[i].Key() {
+ for i := range appliedMigrations {
+     if appliedMigrations[i] != m.Migrations[i].Key() {
         return ErrMigrationsUnrelated
     }
 }

Confirm your minimum Go version before applying.


214-236: Optional: tighten transaction rollback defer within loop

Deferring tx.Rollback() inside the loop piles up defers. It’s safe but noisy. Alternative is a defer that only rolls back on error using a named error, or perform explicit rollback/commit without a defer and ensure all error paths join rollback errors as you already do.

migration_test.go (3)

172-179: Use require for preconditions to fail fast

Where a failure makes subsequent assertions meaningless (e.g., directory opens, morpher creation, pragma exec), prefer require.* to abort the test early.

- assert.NoError(t, migrationsDirErr, "migrations directory could not be opened")
+ require.NoError(t, migrationsDirErr, "migrations directory could not be opened")

…and similarly for morpherErr, execErr, etc.

Also applies to: 538-543, 567-576, 601-605


470-476: Deduplicate test string constant

“utter nonsense” appears multiple times; extract to a const to appease goconst and improve intent.

+const garbageSQL = "utter nonsense"
@@
- dialect.CreateTemplate = "utter nonsense"
+ dialect.CreateTemplate = garbageSQL
@@
- dialect.AppliedTemplate = "utter nonsense"
+ dialect.AppliedTemplate = garbageSQL
@@
- dmorph.WithMigrationFromFile("testData/01_base_table.sql"))
+ dmorph.WithMigrationFromFile("testData/01_base_table.sql"))

(Adapt other occurrences accordingly.)

Also applies to: 502-508, 534-537


569-575: Type assertion: check ok for safety

Although DialectSQLite returns BaseDialect and the assertion should succeed, it’s better style to check ok and fail fast to avoid accidental changes breaking the tests.

- d, _ := morpher.Dialect.(dmorph.BaseDialect)
+ d, ok := morpher.Dialect.(dmorph.BaseDialect)
+ require.True(t, ok, "expected Dialect to be BaseDialect")

Also applies to: 625-627

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a4ac11 and c2610bb.

📒 Files selected for processing (9)
  • .github/workflows/codeql.yml (1 hunks)
  • .golangci.yaml (2 hunks)
  • dialects.go (2 hunks)
  • dialects_test.go (5 hunks)
  • exports_internal_test.go (1 hunks)
  • file_migration.go (4 hunks)
  • file_migration_test.go (6 hunks)
  • migration.go (9 hunks)
  • migration_test.go (23 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
dialects_test.go (8)
dialects.go (1)
  • BaseDialect (15-19)
dialect_csvq.go (1)
  • DialectCSVQ (7-22)
dialect_db2.go (1)
  • DialectDB2 (7-31)
dialect_mssql.go (1)
  • DialectMSSQL (7-27)
dialect_mysql.go (1)
  • DialectMySQL (7-16)
dialect_oracle.go (1)
  • DialectOracle (7-31)
dialect_postgres.go (1)
  • DialectPostgres (7-22)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
exports_internal_test.go (1)
migration.go (1)
  • Morpher (73-78)
migration_test.go (5)
migration.go (16)
  • Run (294-302)
  • WithDialect (84-90)
  • WithMigrations (93-99)
  • ErrMigrationsTooOld (45-45)
  • ErrMigrationsUnrelated (24-24)
  • ErrMigrationsUnsorted (28-28)
  • Migration (55-58)
  • Morpher (73-78)
  • Dialect (48-52)
  • ErrNoDialect (31-31)
  • ErrNoMigrations (34-34)
  • ErrNoMigrationTable (37-37)
  • ErrMigrationTableNameInvalid (40-40)
  • NewMorpher (132-149)
  • WithLog (103-109)
  • WithTableName (113-127)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (4)
  • WithMigrationsFromFS (68-85)
  • WithMigrationFromFileFS (58-64)
  • FileMigration (18-22)
  • WithMigrationFromFile (35-54)
exports_internal_test.go (1)
  • TmigrationOrder (10-10)
dialects.go (1)
  • BaseDialect (15-19)
🪛 golangci-lint (2.2.2)
dialects_test.go

122-122: variable name 'd' is too short for the scope of its usage

(varnamelen)

exports_internal_test.go

8-8: TapplyStepsStream is a global variable

(gochecknoglobals)


9-9: TmigrationFromFileFS is a global variable

(gochecknoglobals)


10-10: TmigrationOrder is a global variable

(gochecknoglobals)

migration.go

283-283: for loop can be changed to use an integer range (Go 1.22+)

(intrange)

migration_test.go

62-62: right hand must be only type assertion

(forcetypeassert)


97-97: right hand must be only type assertion

(forcetypeassert)


168-168: right hand must be only type assertion

(forcetypeassert)


172-172: require-error: for error assertions use require

(testifylint)


203-203: right hand must be only type assertion

(forcetypeassert)


283-283: right hand must be only type assertion

(forcetypeassert)


471-471: string utter nonsense has 3 occurrences, make it a constant

(goconst)


538-538: require-error: for error assertions use require

(testifylint)


567-567: require-error: for error assertions use require

(testifylint)


601-601: require-error: for error assertions use require

(testifylint)


604-604: require-error: for error assertions use require

(testifylint)

🔇 Additional comments (9)
dialects.go (1)

12-15: Comment grammar fix looks good

The pluralization change (“operations”) improves clarity. No functional impact.

file_migration.go (3)

74-77: LGTM: readability-only blank line

The blank line after logging improves readability. No functional change.


87-104: Doc comment addition is fine

Exported-style comment for migrationFromFileFS improves clarity. No functional change.


142-149: LGTM: clarified comment about final-statement cleanup

Comment improvement matches behavior. No logic change.

file_migration_test.go (2)

4-17: External test package switch is correct

Importing dmorph and qualifying usages aligns tests with public API. Good move.


100-121: Good negative tests for applyStepsStream

Feeding malformed SQL to assert errors is appropriate. No changes requested.

dialects_test.go (1)

4-15: External test package conversion is appropriate

Qualified usage of dmorph types/functions improves API-focused testing.

migration.go (1)

232-241: LGTM: clearer rollback comments and spacing

Comment rewording and blank lines around error joins improve readability. Logic unchanged.

Also applies to: 243-253

migration_test.go (1)

62-65: Type assertion on embed.FS is safe—no change needed

The embedded testMigrationsDir is of type embed.FS, which implements fs.ReadDirFS and fs.SubFS. Calls to fs.Sub(testMigrationsDir, …) return an embed.FS that always implements fs.ReadDirFS, so the existing assertions won’t panic. Since the API for WithMigrationsFromFS requires fs.ReadDirFS, the current code is correct and consistent with the signature.

No edits are required in migration_test.go.

Comment thread .golangci.yaml
Comment on lines 14 to 23
default: all
#enable:
# # Default linters
# - errcheck
# - govet
# - ineffassign
# - staticcheck
# - unused
# # Additional linters
# - gosec
# - misspell
# - revive
# - bodyclose
# - noctx

disable:
- exhaustruct
- forbidigo
- noinlineerr
- nonamedreturns
- wsl
- wsl_v5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Invalid key: use linters.enable-all instead of linters.default

golangci-lint doesn’t support linters.default: all. To enable all and selectively disable, use enable-all: true.

Apply this diff:

 linters:
-    default: all
+    enable-all: true
     disable:
         - exhaustruct
         - forbidigo
         - noinlineerr
         - nonamedreturns
         - wsl
         - wsl_v5
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
default: all
#enable:
# # Default linters
# - errcheck
# - govet
# - ineffassign
# - staticcheck
# - unused
# # Additional linters
# - gosec
# - misspell
# - revive
# - bodyclose
# - noctx
disable:
- exhaustruct
- forbidigo
- noinlineerr
- nonamedreturns
- wsl
- wsl_v5
linters:
enable-all: true
disable:
- exhaustruct
- forbidigo
- noinlineerr
- nonamedreturns
- wsl
- wsl_v5
🤖 Prompt for AI Agents
In the .golangci.yaml file between lines 14 and 23, replace the key 'default:
all' with 'enable-all: true' under the linters section to correctly enable all
linters. Keep the 'disable' list as is to selectively disable specific linters.
This change aligns with golangci-lint's configuration syntax requirements.

Comment thread .golangci.yaml
Comment on lines 24 to +42
exclusions:
warn-unused: true

rules:
- path: main_test.go
- path: _test\.go
linters:
- gosec
- cyclop
- dupl
- dupword
- err113
- funlen
- gocognit
- maintidx
- nestif

- path: examples/
linters:
- err113

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Misplaced and non-standard: move “exclusions” under issues.exclude-rules

The “exclusions” block and “warn-unused” aren’t valid under linters. Path- and linter-specific rules belong under issues.exclude-rules. “warn-unused” is not a recognized top-level option.

Proposed structure:

-    exclusions:
-        warn-unused: true
-
-        rules:
-            - path: _test\.go
-              linters:
-                  - cyclop
-                  - dupl
-                  - dupword
-                  - err113
-                  - funlen
-                  - gocognit
-                  - maintidx
-                  - nestif
-
-            - path: examples/
-              linters:
-                  - err113
+issues:
+    max-issues-per-linter: 0
+    max-same-issues: 0
+    exclude-rules:
+        # Example: ignore gochecknoglobals in internal test-exports file
+        - path: ^exports_internal_test\.go$
+          linters:
+              - gochecknoglobals
+        # Example: restrict heavy complexity checks to tests only by excluding them elsewhere
+        - path: ^(?!.*_test\.go).*$  # non-test files
+          linters:
+              - cyclop
+              - dupl
+              - dupword
+              - funlen
+              - gocognit
+              - maintidx
+              - nestif
+        # Only run err113 in examples by excluding it elsewhere, or vice versa per intent
+        - path: ^examples/
+          linters:
+              - $all

Note: golangci-lint doesn’t support “only run these linters for this path”. The approach is to exclude linters in paths where they shouldn’t run.

Please confirm against the official schema; if desired, I can tailor a precise exclude-rules matrix for your intent.


🌐 Web query:

Is "exclusions" a valid top-level option in GolangCI-Lint configuration? 
Does GolangCI-Lint support a top-level "warn-unused" setting? 
Should path- and linter-specific rules be placed under an "exclusions" block, or under "issues.exclude-rules" instead?

💡 Result:

"exclusions" is not a valid top-level option in GolangCI-Lint's configuration.
GolangCI-Lint does not have a top-level "warn-unused" setting.
Path- and linter-specific rules should be placed under "issues.exclude-rules", not an "exclusions" block.

Details and Evidence

  • No "exclusions" top-level block:
    The official GolangCI-Lint [documentation][7], open-source config references[4][9], and blog articles show the recognized top-level keys such as run, output, issues, linters, and linters-settings, but none refer to or support an exclusions key. Further, usage of exemptions is always specified within the issues block, under exclude or exclude-rules[4][9].

  • No "warn-unused" top-level setting:
    There is no mention of a warn-unused option in the config file or CLI switches[7]. Unused code warnings are handled via the unused linter and its specific settings, which are configured within linters-settings: unused: ...[8][10].

  • Where to place rules for excluding issues:

    • To exclude issues by path, linter, or code pattern, use the issues.exclude-rules list within your configuration:
      issues:
        exclude-rules:
          - path: _test\.go
            linters:
              - errcheck
            text: "error return value not checked"
      [3][4]
      This is supported directly in [documentation][3] and observed in community examples.
    • There is only one place for structured exclusion rules; issues.exclude is also supported, but for regex patterns only, not rule objects.

Additional context

  • The run.skip-dirs and run.skip-files options can be used at top-level (under run) for broad file/directory exclusions.
  • The confusion may arise from similar-sounding options in other linter tools or extensions.

Conclusion

For all issue/path/linter-specific exclusions, use issues.exclude-rules. "exclusions" and "warn-unused" are not valid top-level settings in GolangCI-Lint.[3][4][7][8][9]

Citations:


Move “exclusions” and “warn-unused” into issues.exclude-rules

GolangCI-Lint does not recognize a top-level exclusions block or warn-unused setting. All path- and linter-specific ignores must live under issues.exclude-rules.

Please update .golangci.yaml (lines 24–42) as follows:

-    exclusions:
-        warn-unused: true
-
-        rules:
-            - path: _test\.go
-              linters:
-                  - cyclop
-                  - dupl
-                  - dupword
-                  - err113
-                  - funlen
-                  - gocognit
-                  - maintidx
-                  - nestif
-
-            - path: examples/
-              linters:
-                  - err113
+issues:
+  # Prevent any unused warnings; configure via the “unused” linter instead
+  max-issues-per-linter: 0
+  max-same-issues: 0
+  exclude-rules:
+    # Don’t run complexity checks on non-test files
+    - path: ^(?!.*_test\.go).*$   # all files except *_test.go
+      linters:
+        - cyclop
+        - dupl
+        - dupword
+        - funlen
+        - gocognit
+        - maintidx
+        - nestif
+
+    # Only ignore err113 in examples/
+    - path: ^examples/
+      linters:
+        - err113

Tags:

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In .golangci.yaml around lines 24 to 42, the "exclusions" and "warn-unused"
settings are incorrectly placed at the top level. Move these settings under the
"issues.exclude-rules" section to comply with GolangCI-Lint configuration
requirements. Specifically, nest the existing path and linter-specific ignore
rules inside "issues.exclude-rules" and set "warn-unused" as a property within
that section.

Comment thread .golangci.yaml
Comment on lines +43 to 92
settings:
cyclop:
max-complexity: 25

depguard:
rules:
main:
files:
- $all
- "!$test"
- "!**/examples/**/*"
allow:
- $gostd
- gopkg.in/yaml.v3
- github.com/Masterminds/sprig/v3
test:
files:
- $test
- "**/examples/**/*"
allow:
- $gostd
- gopkg.in/yaml.v3
- github.com/Masterminds/sprig/v3
- github.com/stretchr/testify/assert
- github.com/AlphaOne1/dmorph
- modernc.org/sqlite

exhaustive:
default-signifies-exhaustive: true

mnd:
ignored-numbers:
- "2"

paralleltest:
ignore-missing: true

perfsprint:
errorf: false

testpackage:
skip-regexp: internal_test\.go

varnamelen:
max-distance: 10

whitespace:
multi-if: true
multi-func: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Key name should be linters-settings (not settings)

Per config schema, per-linter options live under linters-settings at the top level, not under linters.settings.

Minimal fix:

-    settings:
+linters-settings:
     cyclop:
         max-complexity: 25
     depguard:
         rules:
             main:
                 files:
                     - $all
                     - "!$test"
                     - "!**/examples/**/*"
                 allow:
                     - $gostd
                     - gopkg.in/yaml.v3
                     - github.com/Masterminds/sprig/v3
             test:
                 files:
                     - $test
                     - "**/examples/**/*"
                 allow:
                     - $gostd
                     - gopkg.in/yaml.v3
                     - github.com/Masterminds/sprig/v3
                     - github.com/stretchr/testify/assert
                     - github.com/AlphaOne1/dmorph
                     - modernc.org/sqlite
     exhaustive:
         default-signifies-exhaustive: true
     mnd:
         ignored-numbers:
             - "2"
     paralleltest:
         ignore-missing: true
     perfsprint:
         errorf: false
     testpackage:
         skip-regexp: internal_test\.go
     varnamelen:
         max-distance: 10
     whitespace:
         multi-if: true
         multi-func: true

To ensure no regressions, validate the config with the linter CLI: golangci-lint run -v (or golangci-lint linters to inspect the parsed config).


Replace settings with linters-settings in .golangci.yaml

Per the official golangci-lint schema, per-linter options belong under the top-level linters-settings key—not settings.

Please update your config (in .golangci.yaml, lines 43–92) as follows:

-  settings:
+  linters-settings:
     cyclop:
         max-complexity: 25
     depguard:
         rules:
             main:
               …

After making this change, re-run golangci-lint run -v (or golangci-lint linters) to confirm the config parses correctly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
settings:
cyclop:
max-complexity: 25
depguard:
rules:
main:
files:
- $all
- "!$test"
- "!**/examples/**/*"
allow:
- $gostd
- gopkg.in/yaml.v3
- github.com/Masterminds/sprig/v3
test:
files:
- $test
- "**/examples/**/*"
allow:
- $gostd
- gopkg.in/yaml.v3
- github.com/Masterminds/sprig/v3
- github.com/stretchr/testify/assert
- github.com/AlphaOne1/dmorph
- modernc.org/sqlite
exhaustive:
default-signifies-exhaustive: true
mnd:
ignored-numbers:
- "2"
paralleltest:
ignore-missing: true
perfsprint:
errorf: false
testpackage:
skip-regexp: internal_test\.go
varnamelen:
max-distance: 10
whitespace:
multi-if: true
multi-func: true
linters-settings:
cyclop:
max-complexity: 25
depguard:
rules:
main:
files:
- $all
- "!$test"
- "!**/examples/**/*"
allow:
- $gostd
- gopkg.in/yaml.v3
- github.com/Masterminds/sprig/v3
test:
files:
- $test
- "**/examples/**/*"
allow:
- $gostd
- gopkg.in/yaml.v3
- github.com/Masterminds/sprig/v3
- github.com/stretchr/testify/assert
- github.com/AlphaOne1/dmorph
- modernc.org/sqlite
exhaustive:
default-signifies-exhaustive: true
mnd:
ignored-numbers:
- "2"
paralleltest:
ignore-missing: true
perfsprint:
errorf: false
testpackage:
skip-regexp: internal_test\.go
varnamelen:
max-distance: 10
whitespace:
multi-if: true
multi-func: true
🤖 Prompt for AI Agents
In .golangci.yaml between lines 43 and 92, replace the top-level key "settings"
with "linters-settings" to correctly specify per-linter options according to the
golangci-lint schema. This involves renaming the "settings:" key to
"linters-settings:" while keeping the nested configuration unchanged. After
updating, run golangci-lint to verify the configuration parses without errors.

Comment thread exports_internal_test.go
Comment on lines +7 to +11
var (
TapplyStepsStream = applyStepsStream
TmigrationFromFileFS = migrationFromFileFS
TmigrationOrder = migrationOrder
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace global var aliases with thin wrapper functions to satisfy gochecknoglobals

Global variables trigger gochecknoglobals. Use wrappers instead; behavior unchanged and constrained to _test.go files.

Apply this diff:

-package dmorph
-
-import "database/sql"
-
-// The exported names in this file are only used for internal testing and are not part of the public API.
-
-var (
-    TapplyStepsStream    = applyStepsStream
-    TmigrationFromFileFS = migrationFromFileFS
-    TmigrationOrder      = migrationOrder
-)
-
-func (m *Morpher) TapplyMigrations(db *sql.DB, lastMigration string) error {
-    return m.applyMigrations(db, lastMigration)
-}
+package dmorph
+
+import (
+    "database/sql"
+    "io"
+    "io/fs"
+    "log/slog"
+)
+
+// The exported names in this file are only used for internal testing and are not part of the public API.
+
+func TapplyStepsStream(tx *sql.Tx, r io.Reader, id string, log *slog.Logger) error {
+    return applyStepsStream(tx, r, id, log)
+}
+
+func TmigrationFromFileFS(name string, dir fs.FS, log *slog.Logger) FileMigration {
+    return migrationFromFileFS(name, dir, log)
+}
+
+func TmigrationOrder(m, n Migration) int {
+    return migrationOrder(m, n)
+}
+
+func (m *Morpher) TapplyMigrations(db *sql.DB, lastMigration string) error {
+    return m.applyMigrations(db, lastMigration)
+}
🧰 Tools
🪛 golangci-lint (2.2.2)

8-8: TapplyStepsStream is a global variable

(gochecknoglobals)


9-9: TmigrationFromFileFS is a global variable

(gochecknoglobals)


10-10: TmigrationOrder is a global variable

(gochecknoglobals)

🤖 Prompt for AI Agents
In exports_internal_test.go around lines 7 to 11, replace the global variable
aliases with thin wrapper functions that return the original variables. This
avoids global variables triggering gochecknoglobals while preserving the same
behavior. Define functions named TapplyStepsStream, TmigrationFromFileFS, and
TmigrationOrder that return the respective original variables instead of using
var aliases.

Comment thread file_migration_test.go Outdated
Comment thread file_migration.go Outdated
- Simplified `for` loop in `migration.go` using `range`.
- Replaced `assert` with `require` in tests for stricter checks.
- Updated scanner buffer handling in `applyStepsStream` for performance.
- Adjusted `.golangci.yaml` linter rules to include `require`.
- Minor refactoring in `dialects.go` to inline error handling.
- Clarified comments in `codeql.yml`.
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Aug 12, 2025

Codecov Report

❌ Patch coverage is 91.57895% with 8 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
migration.go 88.63% 3 Missing and 2 partials ⚠️
dialects.go 82.35% 2 Missing and 1 partial ⚠️
Files with missing lines Coverage Δ
file_migration.go 100.00% <100.00%> (ø)
dialects.go 93.47% <82.35%> (-6.53%) ⬇️
migration.go 96.34% <88.63%> (-3.66%) ⬇️

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
file_migration.go (1)

123-128: Bug: leading comments are not removed as documented

The code writes the line to the buffer before checking for a leading "--" comment. As a result, comments are kept, contradicting the updated comment text. Move the write after the comment check.

Apply this diff:

-		buf.Write(scanner.Bytes())
-
-		if newStep && strings.HasPrefix(scanner.Text(), "--") {
-			// skip leading comments
-			continue
-		}
+		if newStep && strings.HasPrefix(scanner.Text(), "--") {
+			// skip leading comments
+			continue
+		}
+
+		buf.Write(scanner.Bytes())
🧹 Nitpick comments (2)
migration_test.go (2)

63-65: Consider addressing the type assertion warning.

The static analysis tool flags unsafe type assertions that could panic if the assertion fails.

Consider using the two-value form for safer type assertion:

-		dmorph.WithMigrationsFromFS(migrationsDir.(fs.ReadDirFS)))
+		dmorph.WithMigrationsFromFS(migrationsDir.(fs.ReadDirFS)))  // Safe: migrationDir guaranteed to implement fs.ReadDirFS

Or handle it explicitly:

+	readDirFS, ok := migrationsDir.(fs.ReadDirFS)
+	require.True(t, ok, "migrationsDir must implement fs.ReadDirFS")
 	runErr := dmorph.Run(db,
 		dmorph.WithDialect(dmorph.DialectSQLite()),
-		dmorph.WithMigrationsFromFS(migrationsDir.(fs.ReadDirFS)))
+		dmorph.WithMigrationsFromFS(readDirFS))

Also applies to: 98-100


472-472: Consider extracting the repeated test string as a constant.

The string "utter nonsense" appears multiple times in the test file.

Consider defining a constant at the package level:

+const invalidSQLTemplate = "utter nonsense"
+
 func TestMigrationRunInvalidCreate(t *testing.T) {
 	// ...
 	dialect := dmorph.DialectSQLite()
-	dialect.CreateTemplate = "utter nonsense"
+	dialect.CreateTemplate = invalidSQLTemplate

And use it in lines 504 and 573 as well.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2610bb and 663fb3c.

📒 Files selected for processing (7)
  • .github/workflows/codeql.yml (1 hunks)
  • .golangci.yaml (2 hunks)
  • dialects.go (2 hunks)
  • dialects_test.go (5 hunks)
  • file_migration.go (4 hunks)
  • migration.go (9 hunks)
  • migration_test.go (23 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/codeql.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .golangci.yaml
🧰 Additional context used
🧬 Code Graph Analysis (3)
dialects_test.go (2)
dialects.go (1)
  • BaseDialect (15-19)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
migration_test.go (5)
migration.go (16)
  • Run (294-302)
  • WithDialect (84-90)
  • WithMigrations (93-99)
  • ErrMigrationsTooOld (45-45)
  • ErrMigrationsUnrelated (24-24)
  • ErrMigrationsUnsorted (28-28)
  • Migration (55-58)
  • Morpher (73-78)
  • Dialect (48-52)
  • ErrNoDialect (31-31)
  • ErrNoMigrations (34-34)
  • ErrNoMigrationTable (37-37)
  • ErrMigrationTableNameInvalid (40-40)
  • NewMorpher (132-149)
  • WithLog (103-109)
  • WithTableName (113-127)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (4)
  • WithMigrationsFromFS (68-85)
  • WithMigrationFromFileFS (58-64)
  • FileMigration (18-22)
  • WithMigrationFromFile (35-54)
exports_internal_test.go (1)
  • TmigrationOrder (10-10)
dialects.go (1)
  • BaseDialect (15-19)
file_migration.go (1)
file_migration_test.go (2)
  • TestMigrationFromFileFSError (67-77)
  • TestWithMigrationFromFileError (41-64)
🪛 golangci-lint (2.2.2)
dialects.go

29-29: (*database/sql.Tx).Exec must not be called. use (*database/sql.Tx).ExecContext

(noctx)

migration_test.go

63-63: right hand must be only type assertion

(forcetypeassert)


98-98: right hand must be only type assertion

(forcetypeassert)


96-96: require-error: for error assertions use require

(testifylint)


169-169: right hand must be only type assertion

(forcetypeassert)


204-204: right hand must be only type assertion

(forcetypeassert)


208-208: require-error: for error assertions use require

(testifylint)


243-243: require-error: for error assertions use require

(testifylint)


284-284: right hand must be only type assertion

(forcetypeassert)


472-472: string utter nonsense has 3 occurrences, make it a constant

(goconst)

file_migration.go

111-111: parameter name 'tx' is too short for the scope of its usage

(varnamelen)

🔇 Additional comments (18)
dialects.go (1)

13-14: LGTM! Documentation improvements and new field addition look good.

The grammar fix in the comment and the addition of the RegisterTemplate field are appropriate changes that enhance the BaseDialect struct.

dialects_test.go (2)

4-14: Good migration to external test package.

Moving the test to dmorph_test and using fully qualified references is a good practice for testing the public API.


119-120: LGTM! Documentation improvements are clear.

The comment split improves readability while maintaining clarity about the test's purpose.

file_migration.go (5)

75-75: Good formatting improvement for log readability.

Adding a blank line after the log statement improves code readability.


87-87: LGTM! Grammar correction in comment.

The article change from "an" to "a" is grammatically correct.


109-110: Good documentation improvements for comment handling.

The updated comments provide clearer guidance about leading vs in-line comments and appropriate hyphenation.


112-118: Good addition of scanner buffer configuration.

The introduction of constants for buffer sizes and configuring the scanner with a larger buffer is a good improvement that will help handle larger migration files without buffer overflow issues.


146-146: Good formatting improvement in comment.

Wrapping the semicolon in backticks improves the documentation clarity.

migration.go (5)

22-44: LGTM! Documentation improvements enhance clarity.

The improvements to error variable comments, including better punctuation and line wrapping, make the documentation more professional and readable.


83-88: Good formatting consistency across option functions.

Adding blank lines before return statements improves code readability and maintains consistency across all option functions.

Also applies to: 92-97, 103-107, 113-125


232-234: Good documentation improvement for transaction safety.

Splitting the rollback safeguard comment into two sentences improves clarity about the purpose of the deferred rollback.


220-221: Consistent formatting improvements throughout the file.

The addition of blank lines improves code readability and maintains consistency across error handling paths and function endings.

Also applies to: 239-240, 245-246, 251-252, 259-260, 268-269, 288-289


278-279: Good improvements to comments and loop syntax.

The grammar improvement from "amount" to "number" is more precise, and using for i := range is cleaner Go syntax.

Also applies to: 283-284

migration_test.go (5)

4-18: Good migration to external test package.

Converting to dmorph_test package and using fully qualified references is consistent with the test refactoring pattern across the codebase.


110-111: Good formatting improvement.

Adding a blank line after the Exec call improves code readability.


147-147: Good documentation improvements for test functions.

The updated comments provide clearer descriptions of what each test is validating.

Also applies to: 182-182, 217-217, 581-582


319-319: Good use of exported test wrapper.

Using dmorph.TmigrationOrder to test the internal migration ordering function is a clean approach.


542-542: Good use of the new test wrapper method.

Using TapplyMigrations method provides controlled access to the internal applyMigrations function for testing purposes.

Also applies to: 577-577, 631-631

Comment thread dialects.go Outdated
Comment thread dialects.go Outdated
- Replaced `assert` with `require` in tests for stricter validation.
- Updated migration directory handling with explicit safety comments.
- Adjusted buffer writes to fix potential order issues in `applyStepsStream`.
- Added safety net with deferred transaction rollback in `dialects.go`.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
dialects.go (1)

29-34: Panic-safe deferred rollback is correct and robust (thanks for integrating it).

The defer guard plus setting tx = nil in error/success paths avoids double rollbacks and leaks on panics. This is the right trade-off with explicit rollback/commit handling.

Also applies to: 38-38, 45-45, 50-50

file_migration.go (1)

122-131: Leading comment skip fixed (write moved after the check).

Reordering the write after the “--” check now actually removes leading comments as documented.

🧹 Nitpick comments (5)
dialects.go (1)

36-36: noctx lint: annotate Exec to match team preference (avoid ExecContext).

Per the repo preference to keep code simple without context plumbing, add a nolint to satisfy golangci-lint’s noctx rule.

Apply this diff:

-	if _, execErr := tx.Exec(fmt.Sprintf(b.CreateTemplate, tableName)); execErr != nil {
+	if _, execErr := tx.Exec(fmt.Sprintf(b.CreateTemplate, tableName)); execErr != nil { //nolint:noctx // team prefers no context plumbing here
file_migration.go (2)

122-137: Handle whitespace-only semicolon lines and avoid buffering the delimiter.

Two small robustness tweaks:

  • Treat lines with only whitespace+semicolon as a delimiter (e.g., " ;").
  • Don’t append the semicolon line into the SQL buffer before Exec. Many drivers accept trailing “;”, but not appending it avoids surprises.

Apply this diff:

-	for i = 0; scanner.Scan(); {
-		if newStep && strings.HasPrefix(scanner.Text(), "--") {
+	for i = 0; scanner.Scan(); {
+		line := scanner.Text()
+		trimmed := strings.TrimSpace(line)
+
+		if newStep && strings.HasPrefix(trimmed, "--") {
 			// skip leading comments
 			continue
 		}
 
-		newStep = false
-
-		buf.Write(scanner.Bytes())
-
-		if scanner.Text() == ";" {
+		// Step delimiter on a line containing only `;` (allow surrounding whitespace)
+		if trimmed == ";" {
 			log.Info("migration step",
 				slog.String("id", id),
 				slog.Int("step", i),
 			)
 			if _, err := tx.Exec(buf.String()); err != nil {
 				return err
 			}
 
 			buf.Reset()
 			i++
+			newStep = true
+			continue
 		}
+
+		// Append the current line (preserve formatting by adding a newline between lines)
+		if buf.Len() > 0 {
+			_ = buf.WriteByte('\n')
+		}
+		buf.WriteString(line)
+		newStep = false

119-121: Rename loop counter ‘i’ to ‘step’ for clarity.

Improves readability and satisfies varnamelen if you keep it enabled.

Apply this diff:

-	newStep := true
-	var i int
+	newStep := true
+	var step int
@@
-	for i = 0; scanner.Scan(); {
+	for step = 0; scanner.Scan(); {
@@
-				slog.Int("step", i),
+				slog.Int("step", step),
 			)
@@
-			i++
+			step++
@@
-			slog.Int("step", i),
+			slog.Int("step", step),

Also applies to: 133-136, 148-151

migration_test.go (2)

611-613: Name clarity: rename ‘d’ to ‘baseDialect’.

Improves readability and satisfies varnamelen.

Apply this diff:

-	d, dialectOK := morpher.Dialect.(dmorph.BaseDialect)
+	baseDialect, dialectOK := morpher.Dialect.(dmorph.BaseDialect)
 	require.True(t, dialectOK, "dialect is not a BaseDialect")
@@
-	morpher.Dialect = d
+	morpher.Dialect = baseDialect

Also applies to: 630-631


473-479: Deduplicate “utter nonsense” with a test-local constant.

This string is used multiple times; a constant improves clarity and satisfies goconst.

Apply these diffs and add a constant near the top of the file (see snippet below):

-	dialect := dmorph.DialectSQLite()
-	dialect.CreateTemplate = "utter nonsense"
+	dialect := dmorph.DialectSQLite()
+	dialect.CreateTemplate = nonsense
-	dialect := dmorph.DialectSQLite()
-	dialect.AppliedTemplate = "utter nonsense"
+	dialect := dmorph.DialectSQLite()
+	dialect.AppliedTemplate = nonsense

Additionally update the similar assignment near line 575:

  • baseDialect.RegisterTemplate = nonsense

Add this constant near the imports:

const nonsense = "utter nonsense"

Also applies to: 505-511

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 663fb3c and c63f274.

📒 Files selected for processing (3)
  • dialects.go (2 hunks)
  • file_migration.go (4 hunks)
  • migration_test.go (23 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-12T20:21:31.632Z
Learnt from: AlphaOne1
PR: AlphaOne1/dmorph#31
File: dialects.go:29-29
Timestamp: 2025-08-12T20:21:31.632Z
Learning: In the dmorph codebase, the maintainer AlphaOne1 prefers to avoid using context.Background() and context handling in database operations to reduce code complexity and clutter, even when suggested by linters like noctx.

Applied to files:

  • dialects.go
🧬 Code Graph Analysis (3)
dialects.go (3)
migration.go (4)
  • Dialect (73-78)
  • EnsureMigrationTableExists (48-52)
  • m (148-166)
  • WithDialect (84-89)
dialects_test.go (1)
  • TestDialectStatements (17-59)
dialect_mysql.go (1)
  • DialectMySQL (7-16)
file_migration.go (2)
file_migration_test.go (2)
  • TestMigrationFromFileFSError (67-77)
  • TestApplyStepsStreamError (80-122)
migration.go (1)
  • m (173-206)
migration_test.go (5)
migration.go (16)
  • Run (294-302)
  • WithDialect (84-90)
  • WithMigrations (93-99)
  • ErrMigrationsUnrelated (24-24)
  • ErrMigrationsUnsorted (28-28)
  • Migration (55-58)
  • Morpher (73-78)
  • Dialect (48-52)
  • ErrNoDialect (31-31)
  • ErrNoMigrations (34-34)
  • ErrNoMigrationTable (37-37)
  • ErrMigrationTableNameInvalid (40-40)
  • NewMorpher (132-149)
  • WithLog (103-109)
  • WithTableName (113-127)
  • m (173-206)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (4)
  • WithMigrationsFromFS (68-85)
  • WithMigrationFromFileFS (58-64)
  • FileMigration (18-22)
  • WithMigrationFromFile (35-54)
exports_internal_test.go (1)
  • TmigrationOrder (10-10)
dialects.go (1)
  • BaseDialect (15-19)
🪛 golangci-lint (2.2.2)
dialects.go

36-36: (*database/sql.Tx).Exec must not be called. use (*database/sql.Tx).ExecContext

(noctx)

file_migration.go

111-111: parameter name 'id' is too short for the scope of its usage

(varnamelen)


120-120: variable name 'i' is too short for the scope of its usage

(varnamelen)

migration_test.go

63-63: right hand must be only type assertion

(forcetypeassert)


99-99: right hand must be only type assertion

(forcetypeassert)


171-171: right hand must be only type assertion

(forcetypeassert)


206-206: right hand must be only type assertion

(forcetypeassert)


210-210: require-error: for error assertions use require

(testifylint)


245-245: require-error: for error assertions use require

(testifylint)


286-286: right hand must be only type assertion

(forcetypeassert)


474-474: string utter nonsense has 3 occurrences, make it a constant

(goconst)


611-611: variable name 'd' is too short for the scope of its usage

(varnamelen)

🔇 Additional comments (1)
file_migration.go (1)

112-119: Scanner buffer configuration: good defensive increase for large statements.

Using a 64 KiB initial and 1 MiB max token size prevents silent token truncation on long lines.

Comment thread migration_test.go Outdated
- Updated all migration methods to include `context.Context` as the first argument.
- Refactored related tests to use `context.Background()` where necessary.
- Adjusted `applyStepsStream` and SQL execution calls to utilize context-aware methods.
- Improved function signatures in `dialects.go` and `file_migration.go` for better consistency with Go best practices.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (7)
exports_internal_test.go (1)

10-14: Consider using wrapper functions instead of global variable aliases

The global variable aliases trigger the gochecknoglobals linter. While this approach works for internal testing, using thin wrapper functions would be cleaner and avoid linter warnings.

Apply this diff to replace global variables with wrapper functions:

-var (
-	TapplyStepsStream    = applyStepsStream
-	TmigrationFromFileFS = migrationFromFileFS
-	TmigrationOrder      = migrationOrder
-)
+func TapplyStepsStream(ctx context.Context, tx *sql.Tx, r io.Reader, id string, log *slog.Logger) error {
+	return applyStepsStream(ctx, tx, r, id, log)
+}
+
+func TmigrationFromFileFS(name string, dir fs.FS, log *slog.Logger) FileMigration {
+	return migrationFromFileFS(name, dir, log)
+}
+
+func TmigrationOrder(m, n Migration) int {
+	return migrationOrder(m, n)
+}

You'll also need to add the necessary imports:

 import (
 	"context"
 	"database/sql"
+	"io"
+	"io/fs"
+	"log/slog"
 )
file_migration_test.go (1)

74-78: Consider using os.DirFS for better portability

The os.OpenRoot function with .FS() method is less common and may have compatibility concerns. Using os.DirFS would be simpler and more portable.

Apply this diff to simplify the directory access:

-	dir, dirErr := os.OpenRoot("testData")
-
-	assert.NoError(t, dirErr, "could not open test data directory")
-
-	mig := dmorph.TmigrationFromFileFS("nonexistent", dir.FS(), slog.Default())
+	fsys := os.DirFS("testData")
+	mig := dmorph.TmigrationFromFileFS("nonexistent", fsys, slog.Default())

Note: os.DirFS has been available since Go 1.16 and removes the need for explicit error handling when opening a local directory.

migration_test.go (5)

60-68: Fix panic-prone forced type assertions inside calls.

The forced type assertion migrationsDir.(fs.ReadDirFS) on line 68 can panic if the assertion fails. Assert the type once before using it.

Apply this diff:

 migrationsDir, migrationsDirErr := fs.Sub(testMigrationsDir, "testData")

 require.NoError(t, migrationsDirErr, "migrations directory could not be opened")

+rd, ok := migrationsDir.(fs.ReadDirFS)
+require.True(t, ok, "migrationsDir does not implement fs.ReadDirFS")
+
 runErr := dmorph.Run(context.Background(),
 	db,
 	dmorph.WithDialect(dmorph.DialectSQLite()),
-	dmorph.WithMigrationsFromFS(
-		migrationsDir.(fs.ReadDirFS))) // Safe: migrationDir guaranteed to implement fs.ReadDirFS
+	dmorph.WithMigrationsFromFS(rd))

95-106: Fix panic-prone forced type assertion inside call.

The forced type assertion on line 106 can panic if the assertion fails.

Apply this diff:

+rd, ok := migrationsDir.(fs.ReadDirFS)
+require.True(t, ok, "migrationsDir does not implement fs.ReadDirFS")
+
 runErr = dmorph.Run(context.Background(),
 	db,
 	dmorph.WithDialect(dmorph.DialectSQLite()),
-	dmorph.WithMigrationsFromFS(
-		migrationsDir.(fs.ReadDirFS))) // Safe: migrationDir guaranteed to implement fs.ReadDirFS
+	dmorph.WithMigrationsFromFS(rd))

176-179: Fix panic-prone forced type assertion inside call.

The forced type assertion on line 179 can panic if the assertion fails.

Apply this diff:

 require.NoError(t, migrationsDirErr, "migrations directory could not be opened")

+rd, ok := migrationsDir.(fs.ReadDirFS)
+require.True(t, ok, "migrationsDir does not implement fs.ReadDirFS")
+
 runErr := dmorph.Run(context.Background(),
 	db,
 	dmorph.WithDialect(dmorph.DialectSQLite()),
-	dmorph.WithMigrationsFromFS(migrationsDir.(fs.ReadDirFS)))
+	dmorph.WithMigrationsFromFS(rd))

213-216: Fix panic-prone forced type assertion inside call.

The forced type assertion on line 216 can panic if the assertion fails.

Apply this diff:

+rd, ok := migrationsDir.(fs.ReadDirFS)
+require.True(t, ok, "migrationsDir does not implement fs.ReadDirFS")
+
 runErr := dmorph.Run(context.Background(),
 	db,
 	dmorph.WithDialect(dmorph.DialectSQLite()),
-	dmorph.WithMigrationsFromFS(migrationsDir.(fs.ReadDirFS)))
+	dmorph.WithMigrationsFromFS(rd))

297-300: Fix panic-prone forced type assertion inside call.

The forced type assertion on line 300 can panic if the assertion fails.

Apply this diff:

+rd, ok := migrationsDir.(fs.ReadDirFS)
+require.True(t, ok, "migrationsDir does not implement fs.ReadDirFS")
+
 runErr := dmorph.Run(context.Background(),
 	db,
 	dmorph.WithDialect(dmorph.DialectSQLite()),
-	dmorph.WithMigrationsFromFS(migrationsDir.(fs.ReadDirFS)))
+	dmorph.WithMigrationsFromFS(rd))
🧹 Nitpick comments (6)
migration_test.go (5)

100-100: Use require for error assertions.

For consistency with other tests and better test failure reporting.

Apply this diff:

-assert.NoError(t, runErr, "preparation migrations could not be run")
+require.NoError(t, runErr, "preparation migrations could not be run")

255-255: Use require for error assertions.

For consistency with other tests and better test failure reporting.

Apply this diff:

-assert.NoError(t, runErr, "preparation migrations could not be run")
+require.NoError(t, runErr, "preparation migrations could not be run")

486-486: Consider extracting repeated string literal as a constant.

The string "utter nonsense" appears multiple times in the test file (lines 486, 518, 587). Consider extracting it as a constant for better maintainability.

Add at the top of the file after other declarations:

const invalidSQLStatement = "utter nonsense"

Then replace all occurrences:

-dialect.CreateTemplate = "utter nonsense"
+dialect.CreateTemplate = invalidSQLStatement

492-492: Use require for error assertions.

For consistency with other tests and better test failure reporting.

Apply this diff:

-assert.NoError(t, morpherErr, "morpher could not be created")
+require.NoError(t, morpherErr, "morpher could not be created")

524-524: Use require for error assertions.

For consistency with other tests and better test failure reporting.

Apply this diff:

-assert.NoError(t, morpherErr, "morpher could not be created")
+require.NoError(t, morpherErr, "morpher could not be created")
migration.go (1)

227-236: Consider adding context check before starting transaction.

Before beginning a transaction, it's good practice to check if the context is already cancelled to avoid unnecessary database operations.

Apply this diff:

 m.Log.Info("applying migration", slog.String("file", migration.Key()))
 startMigration = time.Now()
+
+// Check context before starting transaction
+if err := ctx.Err(); err != nil {
+	return fmt.Errorf("context cancelled before migration %s: %w", migration.Key(), err)
+}
+
 tx, txBeginErr := db.Begin()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c63f274 and f479d28.

📒 Files selected for processing (7)
  • dialects.go (2 hunks)
  • dialects_test.go (5 hunks)
  • exports_internal_test.go (1 hunks)
  • file_migration.go (7 hunks)
  • file_migration_test.go (5 hunks)
  • migration.go (11 hunks)
  • migration_test.go (22 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-12T20:21:31.632Z
Learnt from: AlphaOne1
PR: AlphaOne1/dmorph#31
File: dialects.go:29-29
Timestamp: 2025-08-12T20:21:31.632Z
Learning: In the dmorph codebase, the maintainer AlphaOne1 prefers to avoid using context.Background() and context handling in database operations to reduce code complexity and clutter, even when suggested by linters like noctx.

Applied to files:

  • dialects.go
  • migration.go
  • migration_test.go
🧬 Code Graph Analysis (5)
exports_internal_test.go (1)
migration.go (1)
  • Morpher (74-79)
file_migration_test.go (4)
migration.go (2)
  • Run (295-303)
  • WithDialect (85-91)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (1)
  • WithMigrationFromFile (36-55)
exports_internal_test.go (2)
  • TmigrationFromFileFS (12-12)
  • TapplyStepsStream (11-11)
file_migration.go (1)
migration.go (2)
  • MorphOption (82-82)
  • Morpher (74-79)
dialects_test.go (8)
dialects.go (1)
  • BaseDialect (16-20)
dialect_csvq.go (1)
  • DialectCSVQ (7-22)
dialect_db2.go (1)
  • DialectDB2 (7-31)
dialect_mssql.go (1)
  • DialectMSSQL (7-27)
dialect_mysql.go (1)
  • DialectMySQL (7-16)
dialect_oracle.go (1)
  • DialectOracle (7-31)
dialect_postgres.go (1)
  • DialectPostgres (7-22)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
migration_test.go (5)
migration.go (13)
  • Run (295-303)
  • WithDialect (85-91)
  • WithMigrations (94-100)
  • ErrMigrationsTooOld (46-46)
  • ErrMigrationsUnrelated (25-25)
  • ErrMigrationsUnsorted (29-29)
  • Migration (56-59)
  • Morpher (74-79)
  • Dialect (49-53)
  • ErrMigrationTableNameInvalid (41-41)
  • NewMorpher (133-150)
  • WithLog (104-110)
  • WithTableName (114-128)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (4)
  • WithMigrationsFromFS (69-86)
  • WithMigrationFromFileFS (59-65)
  • FileMigration (19-23)
  • WithMigrationFromFile (36-55)
exports_internal_test.go (1)
  • TmigrationOrder (13-13)
dialects.go (1)
  • BaseDialect (16-20)
🪛 golangci-lint (2.2.2)
exports_internal_test.go

11-11: TapplyStepsStream is a global variable

(gochecknoglobals)


12-12: TmigrationFromFileFS is a global variable

(gochecknoglobals)


13-13: TmigrationOrder is a global variable

(gochecknoglobals)

dialects.go

24-24: variable name 'tx' is too short for the scope of its usage

(varnamelen)


27-27: error returned from external package is unwrapped: sig: func (*database/sql.DB).BeginTx(ctx context.Context, opts *database/sql.TxOptions) (*database/sql.Tx, error)

(wrapcheck)

file_migration_test.go

76-76: require-error: for error assertions use require

(testifylint)

file_migration.go

112-112: parameter name 'tx' is too short for the scope of its usage

(varnamelen)


136-136: error returned from external package is unwrapped: sig: func (*database/sql.Tx).ExecContext(ctx context.Context, query string, args ...any) (database/sql.Result, error)

(wrapcheck)

migration.go

178-178: parameter name 'db' is too short for the scope of its usage

(varnamelen)


215-215: parameter name 'db' is too short for the scope of its usage

(varnamelen)

migration_test.go

64-64: right hand must be only type assertion

(forcetypeassert)


102-102: right hand must be only type assertion

(forcetypeassert)


100-100: require-error: for error assertions use require

(testifylint)


176-176: right hand must be only type assertion

(forcetypeassert)


213-213: right hand must be only type assertion

(forcetypeassert)


255-255: require-error: for error assertions use require

(testifylint)


297-297: right hand must be only type assertion

(forcetypeassert)


486-486: string utter nonsense has 3 occurrences, make it a constant

(goconst)


492-492: require-error: for error assertions use require

(testifylint)


524-524: require-error: for error assertions use require

(testifylint)


620-620: (*database/sql.DB).Exec must not be called. use (*database/sql.DB).ExecContext

(noctx)

🔇 Additional comments (5)
dialects_test.go (1)

93-99: Typo in SQL template will cause test failure

The SQL template has "CRATE TABLE" instead of "CREATE TABLE", which will cause an SQL syntax error as intended for the test.

dialects.go (2)

23-54: Excellent implementation of context propagation and transaction safety

The updated implementation properly handles context throughout the transaction lifecycle and includes a robust safety net for unexpected panics. The pattern of setting tx = nil after explicit rollback/commit prevents double rollback attempts.


57-77: Good context propagation in query operations

The method correctly uses QueryContext for context-aware database queries and maintains proper error handling with errors.Join.

file_migration.go (1)

112-168: Well-structured migration step processing with proper context handling

The implementation correctly:

  • Uses ExecContext for all database operations
  • Handles leading comment removal properly by checking before writing to buffer
  • Preserves SQL formatting with newline handling
  • Processes the final statement without trailing semicolon
  • Provides clear step-based logging

The buffer size constants (64KB initial, 1MB max) are reasonable for typical migration files.

migration.go (1)

1-303: Excellent context propagation implementation!

The addition of context support throughout the migration system is well-implemented and follows Go best practices. The changes enable proper cancellation and timeout support, which is essential for production database operations.

Comment thread migration_test.go
…lidation

- Added context cancellation check before starting a database transaction in `migration.go`.
- Replaced `assert` with `require` in tests for stricter and immediate validation.
- Added safety comments for migration directory handling in tests.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
file_migration_test.go (1)

75-83: Prefer os.DirFS over os.OpenRoot(...).FS() for portability.

DirFS("testData") is simpler and works across Go versions; avoids opening a directory handle and the extra require. This was suggested earlier as well.

Apply this diff:

-dir, dirErr := os.OpenRoot("testData")
-
-require.NoError(t, dirErr, "could not open test data directory")
-
-mig := dmorph.TmigrationFromFileFS("nonexistent", dir.FS(), slog.Default())
+fsys := os.DirFS("testData")
+mig := dmorph.TmigrationFromFileFS("nonexistent", fsys, slog.Default())
migration_test.go (3)

292-299: Use ExecContext for DB ops in tests.

Use db.ExecContext with a ctx to align with the ctx-aware pattern and appease noctx. This was flagged earlier as well.

-	_, execErr := db.Exec(`
+	_, execErr := db.ExecContext(context.Background(), `
 		INSERT INTO migrations (id, create_ts) VALUES ('01_base_table',  '2021-01-02 00:00:00');
 		INSERT INTO migrations (id, create_ts) VALUES ('02_addon_table', '2021-01-01 00:00:00');
 	`)

623-624: Switch to ExecContext for PRAGMA.

As previously noted, use ExecContext to avoid noctx and mirror production behavior.

-	_, execErr := db.Exec("PRAGMA foreign_keys = ON")
+	_, execErr := db.ExecContext(context.Background(), "PRAGMA foreign_keys = ON")
 	require.NoError(t, execErr, "foreign keys checking could not be enabled")

64-69: Avoid forced type assertions that can panic; assert once and reuse.

The migrationsDir.(fs.ReadDirFS) assertions will panic if the underlying FS changes. Use the comma-ok idiom and require.True before passing to WithMigrationsFromFS.

Apply this shape at each occurrence:

-	runErr := dmorph.Run(context.Background(),
+	rd, ok := migrationsDir.(fs.ReadDirFS)
+	require.True(t, ok, "migrationsDir does not implement fs.ReadDirFS")
+
+	runErr := dmorph.Run(context.Background(),
 		db,
 		dmorph.WithDialect(dmorph.DialectSQLite()),
-		dmorph.WithMigrationsFromFS(
-			migrationsDir.(fs.ReadDirFS))) // Safe: migrationDir guaranteed to implement fs.ReadDirFS
+		dmorph.WithMigrationsFromFS(rd))

Also applies to: 102-107, 176-181, 214-219, 299-304

🧹 Nitpick comments (9)
migration.go (4)

239-243: Avoid stacking defers inside a loop; scope the transaction to a helper.

Deferring tx.Rollback() in a loop accumulates defers and attempts a rollback after commit (ErrTxDone), which is benign but noisy and can obscure logic. Encapsulate a single migration in a helper so the defer’s lifetime is limited to one iteration.

For example (outside this hunk):

func (m *Morpher) runOneMigration(ctx context.Context, db *sql.DB, mig Migration) error {
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("begin tx: %w", err)
	}
	defer func() { _ = tx.Rollback() }()

	if err := mig.Migrate(ctx, tx); err != nil {
		return err
	}
	if err := m.Dialect.RegisterMigration(ctx, tx, mig.Key(), m.TableName); err != nil {
		return err
	}
	return tx.Commit()
}

Then in applyMigrations: call m.runOneMigration(ctx, db, migration).


116-119: Return a typed error for empty table names for consistency.

Elsewhere you use sentinel errors; returning fmt.Errorf here is inconsistent and harder to assert in tests. Prefer a typed error.

Apply this diff:

-		if len(tableName) < 1 {
-			return fmt.Errorf("table name empty")
-		}
+		if len(tableName) < 1 {
+			return ErrNoMigrationTable
+		}

23-23: Tiny doc polish: “signals/indicates” reads better than “signalizes/tells”.

Purely wording; improves readability of exported error docs.

-// ErrMigrationsUnrelated signalizes that the set of migrations to apply and the already applied set do not have the
+// ErrMigrationsUnrelated signals that the set of migrations to apply and the already applied set do not have the
@@
-// ErrMigrationsUnsorted tells that the already applied migrations were not registered in the order
+// ErrMigrationsUnsorted indicates that the already applied migrations were not registered in the order
@@
-// ErrNoMigrations signalizes that no migrations were chosen to be applied.
+// ErrNoMigrations indicates that no migrations were chosen to be applied.

Also applies to: 27-28, 34-34


178-179: About varnamelen on “db”: prefer linter config over renaming.

“db” is idiomatic Go for *sql.DB. I’d suppress varnamelen for params like ctx, db, tx in .golangci.yml instead of renaming.

Would you like a PR-ready .golangci.yml snippet to disable varnamelen for these identifiers?

file_migration_test.go (2)

107-112: Use BeginTx with context in tests for consistency.

Tests now exercise ctx-aware flow; use BeginTx to be consistent and future-proof if you add deadlines/timeouts to tests.

-tx, txErr := db.Begin()
+tx, txErr := db.BeginTx(context.Background(), nil)
@@
-tx, txErr = db.Begin()
+tx, txErr = db.BeginTx(context.Background(), nil)

Also applies to: 117-121


38-42: Optional: consider using a test-scoped ctx with timeout.

Using context.Background() is fine, but a test ctx with timeout can help catch hangs and validate cancellation behavior.

If useful, I can sketch a small helper returning ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second).

Also applies to: 63-67, 81-82, 111-111, 124-125

migration_test.go (3)

489-496: DRY: extract repeated “utter nonsense” into a test constant.

This string appears multiple times; factoring it reduces noise and satisfies goconst.

Add near the imports (outside this hunk):

const nonsense = "utter nonsense"

Then update usages:

-	dialect.CreateTemplate = "utter nonsense"
+	dialect.CreateTemplate = nonsense
@@
-	dialect.AppliedTemplate = "utter nonsense"
+	dialect.AppliedTemplate = nonsense

Also applies to: 520-528


25-40: Optional: extract prepareDB into a shared test helper.

Both dmorph_test packages define prepareDB; consider a single helper (e.g., helpers_test.go) to reduce duplication.

If helpful, I can draft a helpers_test.go with prepareDB(t *testing.T) that uses t.Cleanup to remove the file automatically.


60-70: Architecture: consider changing WithMigrationsFromFS to accept fs.FS.

The forced ReadDirFS assertions in tests stem from API shape. If WithMigrationsFromFS accepted fs.FS and internally used fs.ReadDir(d, ".") you could avoid type assertions entirely, making the API friendlier to any FS implementing only fs.FS.

Happy to sketch a backward-compatible overload or a new WithMigrationsFromFS2 accepting fs.FS.

Also applies to: 91-101, 172-182, 210-220, 286-296

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f479d28 and 165ba42.

📒 Files selected for processing (3)
  • file_migration_test.go (5 hunks)
  • migration.go (10 hunks)
  • migration_test.go (22 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-12T20:21:31.662Z
Learnt from: AlphaOne1
PR: AlphaOne1/dmorph#31
File: dialects.go:29-29
Timestamp: 2025-08-12T20:21:31.662Z
Learning: In the dmorph codebase, the maintainer AlphaOne1 prefers to avoid using context.Background() and context handling in database operations to reduce code complexity and clutter, even when suggested by linters like noctx.

Applied to files:

  • migration.go
  • migration_test.go
🧬 Code graph analysis (2)
migration_test.go (5)
migration.go (13)
  • Run (301-309)
  • WithDialect (85-91)
  • WithMigrations (94-100)
  • Migration (56-59)
  • Morpher (74-79)
  • Dialect (49-53)
  • ErrNoDialect (32-32)
  • ErrNoMigrations (35-35)
  • ErrNoMigrationTable (38-38)
  • ErrMigrationTableNameInvalid (41-41)
  • NewMorpher (133-150)
  • WithLog (104-110)
  • WithTableName (114-128)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (4)
  • WithMigrationsFromFS (69-86)
  • WithMigrationFromFileFS (59-65)
  • FileMigration (19-23)
  • WithMigrationFromFile (36-55)
exports_internal_test.go (1)
  • TmigrationOrder (13-13)
dialects.go (1)
  • BaseDialect (16-20)
file_migration_test.go (4)
migration.go (2)
  • Run (301-309)
  • WithDialect (85-91)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (1)
  • WithMigrationFromFile (36-55)
exports_internal_test.go (2)
  • TmigrationFromFileFS (12-12)
  • TapplyStepsStream (11-11)
🪛 golangci-lint (2.2.2)
migration.go

178-178: parameter name 'db' is too short for the scope of its usage

(varnamelen)


233-233: (*database/sql.DB).Begin must not be called. use (*database/sql.DB).BeginTx

(noctx)


215-215: parameter name 'db' is too short for the scope of its usage

(varnamelen)


236-236: error returned from external package is unwrapped: sig: func (*database/sql.DB).Begin() (*database/sql.Tx, error)

(wrapcheck)

migration_test.go

64-64: right hand must be only type assertion

(forcetypeassert)


102-102: right hand must be only type assertion

(forcetypeassert)


176-176: right hand must be only type assertion

(forcetypeassert)


214-214: right hand must be only type assertion

(forcetypeassert)


299-299: right hand must be only type assertion

(forcetypeassert)


489-489: string utter nonsense has 3 occurrences, make it a constant

(goconst)


623-623: (*database/sql.DB).Exec must not be called. use (*database/sql.DB).ExecContext

(noctx)

🔇 Additional comments (6)
migration.go (1)

48-53: Good move: context-aware API across Dialect/Migration and Run.

Threading ctx through the public surface and internal flow is coherent and enables cancellation/timeouts end-to-end. This aligns well with the test/package split and the new wrappers. One note: earlier learnings mention a preference to avoid context.Background in DB ops; assuming this PR marks a directional change toward ctx-first. Please confirm.

We previously learned you prefer to avoid context.Background to reduce clutter. Is adopting ctx across the API now the intended direction for dmorph?

Also applies to: 57-59, 178-211

migration_test.go (5)

111-118: Nice: ctx-aware test migration implementation.

Using tx.ExecContext with the passed ctx aligns the tests with the library’s new ctx-aware contract.


400-408: Great: WithLog option validated end-to-end.

Good coverage that the logger flows via NewMorpher and is the instance used.

Also applies to: 404-412


426-434: Option validation coverage looks solid.

The table name happy-path and failure cases (empty/invalid) are well covered and match the error contracts.

Also applies to: 439-446, 451-458


558-560: Nice negative-path coverage for internal wrappers (TapplyMigrations).

These tests exercise register/commit error paths well.

Also applies to: 593-596, 647-650


290-291: Good direct exercise of the dialect EnsureMigrationTableExists with ctx.

This ensures the dialect ctx-aware methods are reachable from tests.

Comment thread migration.go Outdated
…and precision

- Standardized error descriptions by replacing "signalizes" with "signals" for grammatical correctness.
- Refactored migration logic to encapsulate single migration execution in a new `runOneMigration` method.
- Improved transaction handling with better error propagation and deferred rollback safety.
- Simplified `WithMigrationsFromFS` function signature for better consistency.
- Updated tests to utilize `require` over `assert` for stricter validation.
- Enhanced `.golangci.yaml` configuration to ignore common variable names.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
.golangci.yaml (3)

13-15: Fix invalid key: use linters.enable-all (not linters.default: all).

golangci-lint doesn’t support “default: all”. Use “enable-all: true” and keep the disable list as-is.

Apply:

 linters:
-    default: all
+    enable-all: true

24-42: Move non-existent “exclusions” block under issues.exclude-rules.

The top-level “exclusions” and “warn-unused” keys are not recognized. Place path/linter rules under issues.exclude-rules. Keep your existing max-issues settings.

Apply:

-    exclusions:
-        warn-unused: true
-
-        rules:
-            - path: _test\.go
-              linters:
-                  - cyclop
-                  - dupl
-                  - dupword
-                  - err113
-                  - funlen
-                  - gocognit
-                  - maintidx
-                  - nestif
-
-            - path: examples/
-              linters:
-                  - err113

And extend the issues block:

 issues:
     max-issues-per-linter: 0
     max-same-issues: 0
+    exclude-rules:
+        # Don't run heavy complexity linters on non-test files
+        - path: ^(?!.*_test\.go).*$   # all files except *_test.go
+          linters:
+            - cyclop
+            - dupl
+            - dupword
+            - funlen
+            - gocognit
+            - maintidx
+            - nestif
+        # Scope err113 away from examples/ (or invert per your intent)
+        - path: ^examples/
+          linters:
+            - err113

Run to validate the config parses:

#!/usr/bin/env bash
set -euo pipefail
golangci-lint linters
golangci-lint run -v --timeout=5m

Also applies to: 95-97


43-44: Rename “settings” to top-level “linters-settings”.

Per schema, per-linter options live under linters-settings.

Apply:

-    settings:
+linters-settings:
migration.go (1)

246-276: Transaction flow is solid; BeginTx with ctx and wrapped errors in place.

The defer Rollback plus explicit rollbacks ensures resource cleanup and preserves rollback errors via errors.Join. LGTM.

🧹 Nitpick comments (8)
.golangci.yaml (1)

85-90: Fix YAML indentation under varnamelen.ignore-names.

Current indentation triggers yamllint at Line 86. Indent list items two spaces deeper than the key.

Apply:

         varnamelen:
             max-distance: 10
             ignore-names:
-              - ctx
-              - db
-              - err
-              - tx
+                - ctx
+                - db
+                - err
+                - tx
file_migration_test.go (1)

103-121: Use require for error assertions to fail fast (testifylint).

These checks guard subsequent operations; prefer require.*.

Apply:

-    tx, txErr := db.BeginTx(t.Context(), nil)
-    assert.NoError(t, txErr, "expected no tx error")
+    tx, txErr := db.BeginTx(t.Context(), nil)
+    require.NoError(t, txErr, "expected no tx error")
@@
-    err := dmorph.TapplyStepsStream(context.Background(), tx, &buf, "test", slog.Default())
-    assert.Error(t, err, "expected error")
+    err := dmorph.TapplyStepsStream(context.Background(), tx, &buf, "test", slog.Default())
+    require.Error(t, err, "expected error")
@@
-    tx, txErr = db.BeginTx(t.Context(), nil)
-    assert.NoError(t, txErr, "expected no tx error")
+    tx, txErr = db.BeginTx(t.Context(), nil)
+    require.NoError(t, txErr, "expected no tx error")
@@
-    err = dmorph.TapplyStepsStream(context.Background(), tx, &buf, "test", slog.Default())
-    assert.Error(t, err, "expected error")
+    err = dmorph.TapplyStepsStream(context.Background(), tx, &buf, "test", slog.Default())
+    require.Error(t, err, "expected error")
file_migration.go (4)

129-144: Accept whitespace-only “;” separators.

Compare a trimmed line to “;” so lines like " ; " also delimit steps. Harmless improvement, more robust in real-world SQL dumps.

Apply:

-    if scanner.Text() == ";" {
+    if strings.TrimSpace(scanner.Text()) == ";" {

112-168: Wrap ExecContext errors with step and migration id for better diagnosability.

Currently returns raw driver errors. Include step and migration id context; import fmt.

Apply:

 import (
     "bufio"
     "bytes"
     "context"
     "database/sql"
+    "fmt"
     "io"
@@
-            if _, err := tx.ExecContext(ctx, buf.String()); err != nil {
-                return err
+            if _, err := tx.ExecContext(ctx, buf.String()); err != nil {
+                return fmt.Errorf("apply migration %q step %d: %w", id, step, err)
             }
@@
-        if _, err := tx.ExecContext(ctx, buf.String()); err != nil {
-            return err
+        if _, err := tx.ExecContext(ctx, buf.String()); err != nil {
+            return fmt.Errorf("apply migration %q step %d (final): %w", id, step, err)
         }

112-160: Optional: rename parameter “id” to “migrationID” to satisfy varnamelen and improve clarity.

Alternatively, add “id” to varnamelen.ignore-names in .golangci.yaml.

Apply:

-func applyStepsStream(ctx context.Context, tx *sql.Tx, r io.Reader, id string, log *slog.Logger) error {
+func applyStepsStream(ctx context.Context, tx *sql.Tx, r io.Reader, migrationID string, log *slog.Logger) error {
@@
-            log.Info("migration step",
-                slog.String("id", id),
+            log.Info("migration step",
+                slog.String("id", migrationID),
                 slog.Int("step", step),
             )
@@
-                return fmt.Errorf("apply migration %q step %d: %w", id, step, err)
+                return fmt.Errorf("apply migration %q step %d: %w", migrationID, step, err)
             }
@@
-        log.Info("migration step",
-            slog.String("id", id),
+        log.Info("migration step",
+            slog.String("id", migrationID),
             slog.Int("step", step),
         )
@@
-        if _, err := tx.ExecContext(ctx, buf.String()); err != nil {
-            return fmt.Errorf("apply migration %q step %d (final): %w", id, step, err)
+        if _, err := tx.ExecContext(ctx, buf.String()); err != nil {
+            return fmt.Errorf("apply migration %q step %d (final): %w", migrationID, step, err)
         }

67-85: Optional: filter only “.sql” regular files in WithMigrationsFromFS to avoid accidental non-SQL ingestion.

Safer default, still sorted later.

Apply:

-            if entry.Type().IsRegular() {
+            if entry.Type().IsRegular() && strings.HasSuffix(entry.Name(), ".sql") {
                 morpher.Migrations = append(morpher.Migrations,
                     migrationFromFileFS(entry.Name(), d, morpher.Log))
             }
migration_test.go (2)

92-99: Use require for setup errors to fail fast.

Setup failures invalidate the rest of the test; prefer require.NoError.

Apply:

-assert.NoError(t, migrationsDirErr, "migrations directory could not be opened")
+require.NoError(t, migrationsDirErr, "migrations directory could not be opened")

Replicate at the other occurrences in these ranges.

Also applies to: 207-213, 244-251


484-491: Deduplicate test sentinel string (“utter nonsense”) into a constant.

Reduces noise and satisfies goconst.

Apply:

@@
 import (
@@
 )
 
+const nonsenseSQL = "utter nonsense"
+
@@
-    dialect := dmorph.DialectSQLite()
-    dialect.CreateTemplate = "utter nonsense"
+    dialect := dmorph.DialectSQLite()
+    dialect.CreateTemplate = nonsenseSQL
@@
-    dialect := dmorph.DialectSQLite()
-    dialect.AppliedTemplate = "utter nonsense"
+    dialect := dmorph.DialectSQLite()
+    dialect.AppliedTemplate = nonsenseSQL
@@
-    d.RegisterTemplate = "utter nonsense"
+    d.RegisterTemplate = nonsenseSQL

Also applies to: 515-521, 518-526

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 165ba42 and 7683c18.

📒 Files selected for processing (5)
  • .golangci.yaml (2 hunks)
  • file_migration.go (7 hunks)
  • file_migration_test.go (4 hunks)
  • migration.go (10 hunks)
  • migration_test.go (21 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-12T20:21:31.662Z
Learnt from: AlphaOne1
PR: AlphaOne1/dmorph#31
File: dialects.go:29-29
Timestamp: 2025-08-12T20:21:31.662Z
Learning: In the dmorph codebase, the maintainer AlphaOne1 prefers to avoid using context.Background() and context handling in database operations to reduce code complexity and clutter, even when suggested by linters like noctx.

Applied to files:

  • file_migration_test.go
  • migration.go
  • migration_test.go
🧬 Code graph analysis (3)
file_migration_test.go (4)
migration.go (1)
  • Run (311-319)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (1)
  • WithMigrationFromFile (36-55)
exports_internal_test.go (2)
  • TmigrationFromFileFS (12-12)
  • TapplyStepsStream (11-11)
file_migration.go (1)
migration.go (2)
  • MorphOption (82-82)
  • Morpher (74-79)
migration_test.go (5)
migration.go (13)
  • Run (311-319)
  • WithDialect (85-91)
  • WithMigrations (94-100)
  • Migration (56-59)
  • Morpher (74-79)
  • Dialect (49-53)
  • ErrNoDialect (32-32)
  • ErrNoMigrations (35-35)
  • ErrNoMigrationTable (38-38)
  • ErrMigrationTableNameInvalid (41-41)
  • NewMorpher (133-150)
  • WithLog (104-110)
  • WithTableName (114-128)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (4)
  • WithMigrationsFromFS (69-86)
  • WithMigrationFromFileFS (59-65)
  • FileMigration (19-23)
  • WithMigrationFromFile (36-55)
exports_internal_test.go (1)
  • TmigrationOrder (13-13)
dialects.go (1)
  • BaseDialect (16-20)
🪛 golangci-lint (2.2.2)
file_migration_test.go

105-105: require-error: for error assertions use require

(testifylint)


109-109: require-error: for error assertions use require

(testifylint)


115-115: require-error: for error assertions use require

(testifylint)

file_migration.go

112-112: parameter name 'id' is too short for the scope of its usage

(varnamelen)


136-136: error returned from external package is unwrapped: sig: func (*database/sql.Tx).ExecContext(ctx context.Context, query string, args ...any) (database/sql.Result, error)

(wrapcheck)

migration_test.go

484-484: string utter nonsense has 3 occurrences, make it a constant

(goconst)

🪛 YAMLlint (1.37.1)
.golangci.yaml

[warning] 86-86: wrong indentation: expected 16 but found 14

(indentation)

🔇 Additional comments (5)
file_migration_test.go (1)

73-80: Good portability choice: DirFS.

Switching to os.DirFS simplifies setup and avoids dependency on newer APIs. LGTM.

migration_test.go (2)

60-69: Good: context-aware Run and FS usage.

Using context-aware API and fs.Sub aligns with the new public surface. LGTM.


612-620: Nice: switched to ExecContext with t.Context().

Consistent with the context-aware migration flow.

migration.go (2)

228-235: Good guard: pre-transaction ctx cancellation check.

Catches cancellations before starting work.


183-191: Enhanced error wrapping on dialect calls is appropriate.

Wrapping EnsureMigrationTableExists and AppliedMigrations provides actionable context. LGTM.

Also applies to: 187-211

…ed error clarity

- Replaced `assert` with `require` in tests for immediate validation.
- Added file type check for `.sql` files in `file_migration.go`.
- Enhanced error messages in `applyStepsStream` for better debugging.
- Renamed `id` to `migrationID` for clarity.
- Included `fmt.Errorf` for detailed error propagation.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
migration_test.go (5)

46-58: Fail fast on setup errors: use require.NoError (don’t continue after fatal preconditions).

Continuing after temp-file or DB open failures risks cascading errors and makes failures noisy. Require and abort early.

Apply this shape here (replicate in other tests):

-	if dbFileErr != nil {
-		t.Errorf("DB file could not be created: %v", dbFileErr)
-	} else {
-		defer func() { _ = os.Remove(dbFile) }()
-	}
+	require.NoError(t, dbFileErr, "DB file could not be created")
+	defer func() { _ = os.Remove(dbFile) }()

-	if dbErr != nil {
-		t.Errorf("DB file could not be created: %v", dbErr)
-	} else {
-		defer func() { _ = db.Close() }()
-	}
+	require.NoError(t, dbErr, "DB file could not be created")
+	t.Cleanup(func() { _ = db.Close() })

Replicate the same change in the other tests that have the same if/else pattern.


64-67: Prefer t.Context() over context.Background() in tests.

Tie operations to the test lifecycle so they auto-cancel on failure/timeout and avoid stray background work. This also reduces explicit “background” noise. Noting your preference captured in learnings to avoid context.Background in DB operations, this aligns by using the test-provided context instead of constructing new ones.

Representative edits (apply the same pattern to the other occurrences in the listed ranges):

-	runErr := dmorph.Run(context.Background(),
+	runErr := dmorph.Run(t.Context(),
 		db,
 		dmorph.WithDialect(dmorph.DialectSQLite()),
 		dmorph.WithMigrationsFromFS(migrationsDir))
-	require.NoError(t, dmorph.DialectSQLite().EnsureMigrationTableExists(context.Background(), db, "migrations"))
+	require.NoError(t, dmorph.DialectSQLite().EnsureMigrationTableExists(t.Context(), db, "migrations"))
-	assert.Error(t,
-		morpher.TapplyMigrations(context.Background(), db, ""),
+	assert.Error(t,
+		morpher.TapplyMigrations(t.Context(), db, ""),
 		"morpher should fail to register")

Also applies to: 94-105, 136-139, 147-147, 174-185, 211-221, 248-258, 286-286, 295-298, 459-459, 492-492, 524-524, 553-553, 589-589, 643-643


484-484: Deduplicate repeated literal “utter nonsense” (goconst).

Lift the string to a const to appease the linter and improve clarity.

Add once near the imports:

const nonsenseSQL = "utter nonsense"

Then update these lines:

-	dialect.CreateTemplate = "utter nonsense"
+	dialect.CreateTemplate = nonsenseSQL
-	dialect.AppliedTemplate = "utter nonsense"
+	dialect.AppliedTemplate = nonsenseSQL
-	d.RegisterTemplate = "utter nonsense"
+	d.RegisterTemplate = nonsenseSQL

Also applies to: 516-516, 585-585


440-441: Fix minor grammar in assertion messages.

These messages read oddly and one says the opposite of what the assertion checks.

-	assert.Error(t, err, "morpher could created with empty table name")
+	assert.Error(t, err, "morpher could be created with empty table name")
-	assert.Error(t, err, "morpher could created with invalid table name")
+	assert.Error(t, err, "morpher could be created with invalid table name")
-	assert.Error(t, runErr, "morpher should run")
+	assert.Error(t, runErr, "morpher should not run")

Also applies to: 452-453, 461-461


25-40: Make prepareDB a test helper and simplify with t.TempDir.

This removes manual cleanup and error propagation boilerplate across tests.

Proposed helper (add once in this file):

func prepareDB(t *testing.T) string {
	t.Helper()
	dir := t.TempDir()
	return dir + "/test.db"
}

And a DRY DB open helper:

func openSQLite(t *testing.T, dsn string) *sql.DB {
	t.Helper()
	db, err := sql.Open("sqlite", dsn)
	require.NoError(t, err, "DB could not be opened")
	t.Cleanup(func() { _ = db.Close() })
	return db
}

Then tests become:

dbFile := prepareDB(t)
db := openSQLite(t, dbFile)

If you want, I can push a follow-up commit applying this across the file.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7683c18 and 9699cd8.

📒 Files selected for processing (3)
  • file_migration.go (7 hunks)
  • file_migration_test.go (4 hunks)
  • migration_test.go (21 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • file_migration_test.go
  • file_migration.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-12T20:21:31.662Z
Learnt from: AlphaOne1
PR: AlphaOne1/dmorph#31
File: dialects.go:29-29
Timestamp: 2025-08-12T20:21:31.662Z
Learning: In the dmorph codebase, the maintainer AlphaOne1 prefers to avoid using context.Background() and context handling in database operations to reduce code complexity and clutter, even when suggested by linters like noctx.

Applied to files:

  • migration_test.go
🧬 Code graph analysis (1)
migration_test.go (5)
migration.go (18)
  • Run (311-319)
  • WithDialect (85-91)
  • WithMigrations (94-100)
  • ErrMigrationsTooOld (46-46)
  • ErrMigrationsUnrelated (25-25)
  • ErrMigrationsUnsorted (29-29)
  • Migration (56-59)
  • Morpher (74-79)
  • Dialect (49-53)
  • ErrNoDialect (32-32)
  • ErrNoMigrations (35-35)
  • ErrNoMigrationTable (38-38)
  • ErrMigrationTableNameInvalid (41-41)
  • NewMorpher (133-150)
  • WithLog (104-110)
  • WithTableName (114-128)
  • Run (283-291)
  • m (173-206)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (4)
  • WithMigrationsFromFS (70-87)
  • WithMigrationFromFileFS (60-66)
  • FileMigration (20-24)
  • WithMigrationFromFile (37-56)
exports_internal_test.go (1)
  • TmigrationOrder (13-13)
dialects.go (1)
  • BaseDialect (16-20)
🪛 golangci-lint (2.2.2)
migration_test.go

484-484: string utter nonsense has 3 occurrences, make it a constant

(goconst)

🔇 Additional comments (1)
migration_test.go (1)

399-403: Nice: logger injection is exercised.

Good coverage of WithLog and ensuring the logger is actually set on the Morpher.

- Replaced `context.Background()` with `t.Context()` in tests for better test isolation.
- Standardized error messages in `assert` and `require` for improved clarity and consistency.
- Minor grammatical corrections in error strings.
- Removed unused `context` imports from test files.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (8)
dialects_test.go (2)

156-158: Use ExecContext with t.Context() for consistency with context-aware DB ops

This test already uses context-aware calls elsewhere; switch PRAGMA enabling to ExecContext as well.

-	_, execErr := db.Exec("PRAGMA foreign_keys = ON")
+	_, execErr := db.ExecContext(t.Context(), "PRAGMA foreign_keys = ON")

122-161: Rename short var ‘d’ to ‘dialect’ to satisfy varnamelen and improve readability

The variable spans a long block and multiple uses; a descriptive name reads better and matches linter guidance.

-func TestEnsureMigrationTableExistsCommitError(t *testing.T) {
-	d := dmorph.BaseDialect{
+func TestEnsureMigrationTableExistsCommitError(t *testing.T) {
+	dialect := dmorph.BaseDialect{
@@
-	assert.Error(t, d.EnsureMigrationTableExists(t.Context(), db, "test"), "expected error")
+	assert.Error(t, dialect.EnsureMigrationTableExists(t.Context(), db, "test"), "expected error")
file_migration_test.go (2)

20-34: Prefer require + t.Cleanup to reduce boilerplate in setup/teardown

Several tests manually if-else-defer the cleanup. Using require and t.Cleanup shortens code and fails fast. Consider applying this pattern across the file.

 dbFile, dbFileErr := prepareDB()
-
-if dbFileErr != nil {
-	t.Errorf("DB file could not be created: %v", dbFileErr)
-} else {
-	defer func() { _ = os.Remove(dbFile) }()
-}
+require.NoError(t, dbFileErr, "DB file could not be created")
+t.Cleanup(func() { _ = os.Remove(dbFile) })
 
 db, dbErr := sql.Open("sqlite", dbFile)
-
-if dbErr != nil {
-	t.Errorf("DB file could not be created: %v", dbErr)
-} else {
-	defer func() { _ = db.Close() }()
-}
+require.NoError(t, dbErr, "DB file could not be created")
+t.Cleanup(func() { _ = db.Close() })

73-79: Note: nil tx relies on file-open failure happening first

This test passes a nil transaction to Migrate(). It currently works because the error originates from opening a non-existent file. If migrationFromFileFS changes order (e.g., touches tx before opening the file), this could panic. If you want future-proofing, consider creating a transient DB/tx and still target the non-existent file. Happy to draft that if desired.

migration_test.go (4)

22-26: Deduplicate repeated “utter nonsense” by introducing a file-level constant

golangci-lint flagged this string duplication; a small constant cleans it up and improves maintainability.

 //go:embed testData
 var testMigrationsDir embed.FS
+
+const invalidSQL = "utter nonsense"

477-487: Use the new invalidSQL constant (avoids duplication)

-	dialect := dmorph.DialectSQLite()
-	dialect.CreateTemplate = "utter nonsense"
+	dialect := dmorph.DialectSQLite()
+	dialect.CreateTemplate = invalidSQL

509-521: Use the new invalidSQL constant here as well

-	dialect := dmorph.DialectSQLite()
-	dialect.AppliedTemplate = "utter nonsense"
+	dialect := dmorph.DialectSQLite()
+	dialect.AppliedTemplate = invalidSQL

68-82: Optional: standardize setup/teardown across tests to reduce repetition

Many tests still use if-else-defer patterns for file/DB handling. Consider a small helper like openTempSQLite(t) for consistency and brevity across the suite.

Example helper (place near prepareDB):

func openTempSQLite(t *testing.T) (*sql.DB, string) {
	t.Helper()

	dbFile, err := prepareDB()
	require.NoError(t, err, "DB file could not be created")
	t.Cleanup(func() { _ = os.Remove(dbFile) })

	db, dbErr := sql.Open("sqlite", dbFile)
	require.NoError(t, dbErr, "DB could not be opened")
	t.Cleanup(func() { _ = db.Close() })

	return db, dbFile
}

Then replace per-test boilerplate with:

db, _ := openTempSQLite(t)

Also applies to: 114-128, 148-162, 185-199, 222-236, 260-274

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9699cd8 and 5b9bccc.

📒 Files selected for processing (3)
  • dialects_test.go (6 hunks)
  • file_migration_test.go (5 hunks)
  • migration_test.go (20 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-12T20:21:31.662Z
Learnt from: AlphaOne1
PR: AlphaOne1/dmorph#31
File: dialects.go:29-29
Timestamp: 2025-08-12T20:21:31.662Z
Learning: In the dmorph codebase, the maintainer AlphaOne1 prefers to avoid using context.Background() and context handling in database operations to reduce code complexity and clutter, even when suggested by linters like noctx.

Applied to files:

  • migration_test.go
🧬 Code graph analysis (3)
dialects_test.go (3)
dialects.go (1)
  • BaseDialect (16-20)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
migration.go (2)
  • Dialect (73-78)
  • EnsureMigrationTableExists (48-52)
file_migration_test.go (4)
migration.go (2)
  • Run (311-319)
  • WithDialect (85-91)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (1)
  • WithMigrationFromFile (37-56)
exports_internal_test.go (2)
  • TmigrationFromFileFS (12-12)
  • TapplyStepsStream (11-11)
migration_test.go (5)
migration.go (8)
  • Run (311-319)
  • WithDialect (85-91)
  • WithMigrations (94-100)
  • Migration (56-59)
  • Morpher (74-79)
  • Dialect (49-53)
  • NewMorpher (133-150)
  • m (173-206)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (4)
  • WithMigrationsFromFS (70-87)
  • WithMigrationFromFileFS (60-66)
  • FileMigration (20-24)
  • WithMigrationFromFile (37-56)
exports_internal_test.go (1)
  • TmigrationOrder (13-13)
dialects.go (1)
  • BaseDialect (16-20)
🪛 golangci-lint (2.2.2)
dialects_test.go

122-122: variable name 'd' is too short for the scope of its usage

(varnamelen)

migration_test.go

478-478: string utter nonsense has 3 occurrences, make it a constant

(goconst)

🔇 Additional comments (12)
dialects_test.go (1)

24-33: LGTM: tests exercise only the public dialect constructors from the external package

Switching to dmorph_test and calling the public Dialect* constructors keeps tests aligned with the public API surface. This is the right direction for enforcing API boundaries.

file_migration_test.go (3)

36-40: LGTM: context-aware Run + WithMigrationFromFile

Good use of t.Context() and the public option. This aligns with the rest of the suite’s context propagation.


61-68: LGTM: clear assertion for non-existent file via WithMigrationFromFile

Error path validation looks good; PathError type assertion is a nice touch.


103-121: LGTM: negative-path coverage for applyStepsStream with garbage input

Good use of BeginTx with t.Context(), require for preconditions, and explicit rollback. This hits an important error path.

migration_test.go (8)

46-53: LGTM: setup using require + t.Cleanup improves test hygiene

This style keeps setup concise and failures immediate.


58-62: LGTM: Run now correctly propagates t.Context()

Consistent with the context-aware API introduced in the library.


106-109: LGTM: Migration impl uses ExecContext with ctx

Correctly adheres to the context-aware contract for migrations.


280-287: LGTM: deterministic prep for unordered applied migrations

Ensuring the table exists via the dialect and using ExecContext with t.Context() is solid.


541-550: LGTM: applyMigrations correctly errors on invalid/closed DB

Good coverage for morpher.TapplyMigrations error path with a closed DB.


606-614: LGTM: context-aware ExecContext for PRAGMA

Matches the suite’s context usage and satisfies noctx concerns.


570-585: Reasonable use of BaseDialect override for negative-path testing

Type assertion is validated with require.True; mutating RegisterTemplate to invalid SQL exercises the expected failure path.


141-144: Sanity check: Run(nil DB) error path is covered

Good to keep this guard; it protects accidental API misuse.

… and cleanup

- Introduced `openTempSQLite` helper to simplify and standardize test database handling.
- Replaced repetitive database setup and teardown logic with the new helper across all tests.
- Removed redundant error checks and improved code readability in test files.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
migration_test.go (1)

442-450: Good: assert type once before use

The safe type assertion for BaseDialect is appreciated; matches previous review guidance and prevents forcetypeassert panics.

🧹 Nitpick comments (9)
dialects_test.go (3)

10-13: Switch error assertions to require and check db.Close; fixes testifylint and errcheck findings

  • Use require for error assertions so tests fail fast.
  • Check db.Close return value to satisfy errcheck.
 import (
 	"regexp"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 	"github.com/AlphaOne1/dmorph"
 )
-	db, _ := openTempSQLite(t)
-	db.Close()
+	db, _ := openTempSQLite(t)
+	require.NoError(t, db.Close())
 
-	assert.Error(t,
-		dmorph.DialectSQLite().EnsureMigrationTableExists(t.Context(), db, "irrelevant"),
-		"expected error on closed database")
+	require.Error(t,
+		dmorph.DialectSQLite().EnsureMigrationTableExists(t.Context(), db, "irrelevant"),
+		"expected error on closed database")
 
 	_, err := dmorph.DialectSQLite().AppliedMigrations(t.Context(), db, "irrelevant")
-	assert.Error(t, err, "expected error on closed database")
+	require.Error(t, err, "expected error on closed database")
-	assert.Error(t, dialect.EnsureMigrationTableExists(t.Context(), db, "test"), "expected error")
+	require.Error(t, dialect.EnsureMigrationTableExists(t.Context(), db, "test"), "expected error")
-	_, execErr := db.ExecContext(t.Context(), "PRAGMA foreign_keys = ON")
-	assert.NoError(t, execErr, "foreign keys checking could not be enabled")
+	_, execErr := db.ExecContext(t.Context(), "PRAGMA foreign_keys = ON")
+	require.NoError(t, execErr, "foreign keys checking could not be enabled")
 
-	assert.Error(t, dialect.EnsureMigrationTableExists(t.Context(), db, "test"), "expected error")
+	require.Error(t, dialect.EnsureMigrationTableExists(t.Context(), db, "test"), "expected error")

Also applies to: 63-72, 85-88, 113-118


41-43: Nit: Contains + Regexp are redundant for “%s” placeholder

Either check with Contains or Regexp; keeping both duplicates the same invariant and adds noise. I’d keep Contains for clarity.

-		assert.Regexp(t, re, d.CreateTemplate)
+		// assert.Regexp(t, re, d.CreateTemplate) // redundant with Contains
-		assert.Regexp(t, re, d.AppliedTemplate)
+		// assert.Regexp(t, re, d.AppliedTemplate) // redundant with Contains
-		assert.Regexp(t, re, d.RegisterTemplate)
+		// assert.Regexp(t, re, d.RegisterTemplate) // redundant with Contains

Also applies to: 48-50, 55-57


63-64: Follow-up if you adopt the openTempSQLite signature change

If you refactor openTempSQLite to return only *sql.DB (see suggestion in migration_test.go), update this call site accordingly.

-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
file_migration_test.go (2)

77-79: Consistency: use require for the second error assertion

The earlier assertion in this test uses require. Make the second one require too to fail fast.

-	assert.Error(t, err, "expected error")
+	require.Error(t, err, "expected error")

19-19: *If you simplify openTempSQLite to return only sql.DB, update call sites

See migration_test.go suggestion. Here are the corresponding updates.

-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)

Also applies to: 30-30, 55-55

migration_test.go (4)

42-54: Drop unused return from openTempSQLite; fix all call sites (unparam)

The second return value (dbFile) isn’t used anywhere. Simplify the helper and update callers.

-func openTempSQLite(t *testing.T) (*sql.DB, string) {
+func openTempSQLite(t *testing.T) *sql.DB {
 	t.Helper()
 
 	dbFile, err := prepareDB()
 	require.NoError(t, err, "DB file could not be created")
 	t.Cleanup(func() { _ = os.Remove(dbFile) })
 
 	db, dbErr := sql.Open("sqlite", dbFile)
 	require.NoError(t, dbErr, "DB could not be opened")
 	t.Cleanup(func() { _ = db.Close() })
 
-	return db, dbFile
+	return db
 }
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)
-	db, _ := openTempSQLite(t)
+	db := openTempSQLite(t)

Also applies to: 58-58, 74-74, 106-106, 126-126, 149-149, 172-172, 196-196, 383-383, 401-401, 419-419, 434-434, 456-456


6-17: Wrap external errors to satisfy wrapcheck; add fmt import

Wrap the ExecContext error with context. This helps diagnostics and clears wrapcheck.

 import (
 	"context"
 	"database/sql"
 	"embed"
+	"fmt"
 	"io/fs"
 	"log/slog"
 	"os"
 	"testing"
 func (m TestMigrationImpl) Migrate(ctx context.Context, tx *sql.Tx) error {
-	_, err := tx.ExecContext(ctx, "CREATE TABLE t0 (id INTEGER PRIMARY KEY)")
-	return err
+	if _, err := tx.ExecContext(ctx, "CREATE TABLE t0 (id INTEGER PRIMARY KEY)"); err != nil {
+		return fmt.Errorf("create t0: %w", err)
+	}
+	return nil
 }

Also applies to: 98-101


311-319: Deduplicate repeated “utter nonsense” test string (goconst)

Use a shared constant for the repeated sentinel SQL to appease goconst and make intent clearer.

Add this constant near the top of the file (outside changed ranges):

const nonsenseSQL = "utter nonsense"

Then update usages:

 	morpher, err := dmorph.NewMorpher(
 		dmorph.WithDialect(dmorph.DialectSQLite()),
-		dmorph.WithMigrationFromFile("testData/01_base_table.sql"),
+		dmorph.WithMigrationFromFile("testData/01_base_table.sql"),
 		dmorph.WithLog(l),
 	)
 	dialect := dmorph.DialectSQLite()
-	dialect.CreateTemplate = "utter nonsense"
+	dialect.CreateTemplate = nonsenseSQL
 	dialect := dmorph.DialectSQLite()
-	dialect.AppliedTemplate = "utter nonsense"
+	dialect.AppliedTemplate = nonsenseSQL

Also applies to: 386-389, 403-409


202-203: Minor: you can inline the EnsureMigrationTableExists require

Not necessary, but consider inlining for brevity in tests.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5b9bccc and dece3c8.

📒 Files selected for processing (3)
  • dialects_test.go (4 hunks)
  • file_migration_test.go (1 hunks)
  • migration_test.go (9 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-12T20:21:31.662Z
Learnt from: AlphaOne1
PR: AlphaOne1/dmorph#31
File: dialects.go:29-29
Timestamp: 2025-08-12T20:21:31.662Z
Learning: In the dmorph codebase, the maintainer AlphaOne1 prefers to avoid using context.Background() and context handling in database operations to reduce code complexity and clutter, even when suggested by linters like noctx.

Applied to files:

  • migration_test.go
🧬 Code graph analysis (3)
migration_test.go (5)
migration.go (16)
  • Run (311-319)
  • WithDialect (85-91)
  • WithMigrations (94-100)
  • ErrMigrationsTooOld (46-46)
  • ErrMigrationsUnrelated (25-25)
  • ErrMigrationsUnsorted (29-29)
  • Migration (56-59)
  • Morpher (74-79)
  • Dialect (49-53)
  • ErrNoDialect (32-32)
  • ErrNoMigrations (35-35)
  • ErrNoMigrationTable (38-38)
  • ErrMigrationTableNameInvalid (41-41)
  • NewMorpher (133-150)
  • WithLog (104-110)
  • WithTableName (114-128)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
file_migration.go (4)
  • WithMigrationsFromFS (70-87)
  • WithMigrationFromFileFS (60-66)
  • FileMigration (20-24)
  • WithMigrationFromFile (37-56)
exports_internal_test.go (1)
  • TmigrationOrder (13-13)
dialects.go (1)
  • BaseDialect (16-20)
file_migration_test.go (3)
migration.go (2)
  • Run (311-319)
  • Run (283-291)
file_migration.go (1)
  • WithMigrationFromFile (37-56)
exports_internal_test.go (2)
  • TmigrationFromFileFS (12-12)
  • TapplyStepsStream (11-11)
dialects_test.go (9)
dialects.go (1)
  • BaseDialect (16-20)
dialect_csvq.go (1)
  • DialectCSVQ (7-22)
dialect_db2.go (1)
  • DialectDB2 (7-31)
dialect_mssql.go (1)
  • DialectMSSQL (7-27)
dialect_mysql.go (1)
  • DialectMySQL (7-16)
dialect_oracle.go (1)
  • DialectOracle (7-31)
dialect_postgres.go (1)
  • DialectPostgres (7-22)
dialect_sqlite.go (1)
  • DialectSQLite (7-22)
migration.go (2)
  • Dialect (73-78)
  • EnsureMigrationTableExists (48-52)
🪛 golangci-lint (2.2.2)
migration_test.go

42-42: openTempSQLite - result 1 (string) is never used

(unparam)


101-101: error returned from external package is unwrapped: sig: func (*database/sql.Tx).ExecContext(ctx context.Context, query string, args ...any) (database/sql.Result, error)

(wrapcheck)


386-386: string utter nonsense has 3 occurrences, make it a constant

(goconst)

dialects_test.go

64-64: Error return value of db.Close is not checked

(errcheck)


66-66: require-error: for error assertions use require

(testifylint)


115-115: require-error: for error assertions use require

(testifylint)

🔇 Additional comments (3)
file_migration_test.go (1)

44-47: Nice: using os.DirFS for test data improves portability

Good switch to os.DirFS over os.OpenRoot(...).FS(); simpler and avoids unnecessary handle management.

migration_test.go (2)

202-210: Nice: context-aware Exec and explicit unordered setup

Good use of ExecContext with t.Context() and a single call to seed unordered rows; this makes the failure deterministic.


464-466: Good: PRAGMA foreign_keys uses ExecContext with test context

This aligns with the context-aware pattern across the suite.

@AlphaOne1 AlphaOne1 merged commit a1e170e into master Aug 25, 2025
13 checks passed
@AlphaOne1 AlphaOne1 deleted the separate_test_package branch August 25, 2025 00:23
@coderabbitai coderabbitai Bot mentioned this pull request Oct 2, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants