Skip to content

Refactor common_mysql - #37245

Merged
getvictor merged 29 commits into
mainfrom
victor/37244-refactor-common_mysql
Jan 7, 2026
Merged

Refactor common_mysql#37245
getvictor merged 29 commits into
mainfrom
victor/37244-refactor-common_mysql

Conversation

@getvictor

@getvictor getvictor commented Dec 14, 2025

Copy link
Copy Markdown
Member

Related issue: Resolves #37244

Goal: Make common_mysql package independent of domain packages so it can be reused by future bounded contexts.

Changes made:

  1. List options decoupling

The AppendListOptionsToSQL functions previously required fleet.ListOptions directly. Now common_mysql defines its own interface that describes what a list options type must provide (page number, per-page limit, sort order, etc.). The fleet.ListOptions type implements this interface through new getter methods. This lets any bounded context use the SQL helpers without importing the fleet package.

  1. Error types moved

Database-specific error types like IsDuplicate and IsChildForeignKeyError were moved from fleet package to common_mysql where they belong. A new http/errors.go file was created for the HTTP-specific error helpers that remain in the platform layer.

  1. Configuration restructuring

MySQL configuration types and functions were moved to common_mysql/config.go, reducing coupling between packages.

  1. Architecture tests added

A new arch_test.go file enforces that common_mysql doesn't import domain packages like fleet, preventing future regressions.

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • New Features

    • Added cursor-based pagination support for list queries with improved sorting capabilities including secondary order keys.
  • Bug Fixes

    • Improved database connection initialization with separate connection management and error handling.
  • Refactor

    • Consolidated error handling interfaces and decoupled configuration structures for better modularity.

✏️ Tip: You can customize this high-level summary in your review settings.

  - Extract common HTTP error types to server/platform/http package to enable decoupling bounded contexts from the fleet package
  - Make CommonEndpointer fully generic ([H any]) so handler function types can be defined in their respective packages
  - Replace deprecated AuthFunc/FleetService fields with pre-built AuthMiddleware for cleaner dependency injection
  - Remove fleet import from endpoint_utils package (now only imports contexts/*, platform/http, and middleware/*)
…-endpoint_utils

# Conflicts:
#	server/service/endpoint_utils.go
@codecov

codecov Bot commented Dec 14, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.79245% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.83%. Comparing base (a3cc31d) to head (4cb4c74).
⚠️ Report is 118 commits behind head on main.

Files with missing lines Patch % Lines
server/datastore/mysql/mysql.go 84.41% 7 Missing and 5 partials ⚠️
cmd/fleet/serve.go 0.00% 4 Missing ⚠️
...erver/datastore/mysql/common_mysql/list_options.go 95.45% 2 Missing ⚠️
server/fleet/app.go 83.33% 1 Missing and 1 partial ⚠️
server/datastore/mysql/config.go 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #37245      +/-   ##
==========================================
+ Coverage   65.82%   65.83%   +0.01%     
==========================================
  Files        2367     2368       +1     
  Lines      187665   187752      +87     
  Branches     8012     8012              
==========================================
+ Hits       123523   123600      +77     
- Misses      52845    52856      +11     
+ Partials    11297    11296       -1     
Flag Coverage Δ
backend 67.67% <86.79%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…into victor/37244-refactor-common_mysql

# Conflicts:
#	server/datastore/mysql/common_mysql/common.go
#	server/datastore/mysql/mysql.go
#	server/service/middleware/endpoint_utils/endpoint_utils.go
@getvictor getvictor changed the title Refactor common_mysql (plus previous changes) Refactor common_mysql Dec 31, 2025
@getvictor
getvictor force-pushed the victor/37244-refactor-common_mysql branch from b92ceee to b19db6b Compare December 31, 2025 16:49
…-common_mysql

# Conflicts:
#	server/contexts/ctxerr/ctxerr.go
#	server/contexts/ctxerr/ctxerr_otel_test.go
#	server/contexts/ctxerr/ctxerr_test.go
#	server/contexts/ctxerr/metadata.go
#	server/contexts/ctxerr/statistics.go
#	server/contexts/host/host.go
#	server/contexts/license/license.go
#	server/contexts/logging/logging.go
#	server/contexts/viewer/viewer.go
#	server/mdm/android/service/endpoint_utils.go
#	server/platform/http/errors.go
#	server/service/appconfig.go
#	server/service/endpoint_middleware.go
#	server/service/endpoint_utils.go
#	server/service/middleware/auth/http_auth.go
#	server/service/middleware/endpoint_utils/endpoint_utils.go
#	server/service/middleware/endpoint_utils/transport_error.go
@getvictor
getvictor force-pushed the victor/37244-refactor-common_mysql branch from b19db6b to 1babe78 Compare December 31, 2025 17:05
@getvictor

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Dec 31, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Dec 31, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This PR refactors the MySQL datastore layer to introduce shared database connections and decouple configuration types. It moves DB configuration structures into the common_mysql package, introduces a DBConnections struct for connection sharing, refactors error types to use platform_http aliases, and adds list-options pagination helpers.

Changes

Cohort / File(s) Summary
DB Connection Initialization
cmd/fleet/serve.go, server/datastore/mysql/mysql.go
Introduces NewDBConnections() to create shared Primary/Replica connections and refactors NewDatastore() to accept pre-wired connections, enabling connection reuse across multiple datastore instances. Updated initialization flow with proper error handling.
Configuration Type Extraction
server/datastore/mysql/common_mysql/config.go, server/datastore/mysql/common_mysql/common.go, server/datastore/mysql/config.go
Extracts MysqlConfig and LoggingConfig types into common_mysql to reduce external dependencies. Updates DBOptions and NewDB signatures to use local types instead of global config package types. Adds conversion helpers between local and global config representations.
Database Transaction Interfaces
server/datastore/mysql/common_mysql/types.go, server/datastore/mysql/common_mysql/retry.go, server/fleet/db.go, server/datastore/mysql/apple_mdm.go
Moves DBReadTx interface from fleet package to common_mysql; updates transaction parameter types in datastore helpers from fleet.DBReadTx to common_mysql.DBReadTx. Removes public DBReadTx from fleet package.
Error Type Migration
server/fleet/datastore.go, server/fleet/errors.go, server/platform/http/errors.go, server/datastore/mysql/common_mysql/errors.go
Converts fleet error interfaces (NotFoundError, AlreadyExistsError, Errorer) to type aliases pointing to platform_http equivalents. Adds NotFoundError, IsNotFound(), and AlreadyExistsError definitions to platform_http. Updates compile-time interface checks in MySQL datastore.
List Options & Pagination
server/datastore/mysql/common_mysql/list_options.go, server/fleet/app.go, server/datastore/mysql/mysql.go
Introduces ListOptions interface and SQL helper functions (SanitizeColumn, AppendListOptions, AppendListOptionsWithParams) in common_mysql for cursor and offset-based pagination. Implements interface methods in fleet ListOptions struct. Refactors MySQL datastore to delegate list-option handling to common helpers.
Testing & Utilities
server/datastore/mysql/common_mysql/testing_utils/testing_utils.go, server/datastore/mysql/common_mysql/arch_test.go, server/datastore/mysql/testing_utils.go, tools/mysql-tests/rds/iam_auth.go
Updates test helpers to use common_mysql.MysqlConfig; adds architecture test to enforce package dependency boundaries; updates IAM auth setup to use refactored DB constructor.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

~csa

Suggested reviewers

  • lucasmrod
  • sgress454
  • cdcme

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.06% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Refactor common_mysql' is vague and generic, providing no specific detail about the refactoring work, scope, or outcome. Consider a more descriptive title such as 'Move common_mysql to platform/mysql and refactor DB connection handling' to better convey the primary changes.
Out of Scope Changes check ❓ Inconclusive Changes in server/fleet/datastore.go and server/platform/http/errors.go introduce type aliases pointing to platform_http error interfaces, which appear beyond the explicit scope of moving common_mysql to platform/mysql. Clarify whether centralizing error types in platform/http is part of the original refactoring scope (#37244) or a separate concern to be addressed in future work.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements all stated requirements from #37244: moving common_mysql to platform/mysql, updating imports across the codebase, and refactoring DB connection handling with new DBConnections abstraction and conversion helpers.
Description check ✅ Passed PR description provided with clear objectives, related issue, and detailed change summary. Author completed relevant checklist items appropriately.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch victor/37244-refactor-common_mysql

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/platform/http/errors.go (1)

199-218: Centralizing NotFound/AlreadyExists interfaces and IsNotFound helper looks good

Defining NotFoundError / AlreadyExistsError here and adding IsNotFound gives a clear, shared contract for not-found semantics and mirrors the existing IsForeignKey pattern.

If you anticipate similar call-site ergonomics for “already exists” conditions, consider adding a small helper:

func IsExists(err error) bool {
	var aee AlreadyExistsError
	if errors.As(err, &aee) {
		return aee.IsExists()
	}
	return false
}

This would keep the API symmetric and allow fleet-level aliases the same way IsNotFound is exposed.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3cc31d and d007eee.

📒 Files selected for processing (19)
  • cmd/fleet/serve.go
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/common_mysql/arch_test.go
  • server/datastore/mysql/common_mysql/common.go
  • server/datastore/mysql/common_mysql/config.go
  • server/datastore/mysql/common_mysql/errors.go
  • server/datastore/mysql/common_mysql/list_options.go
  • server/datastore/mysql/common_mysql/retry.go
  • server/datastore/mysql/common_mysql/testing_utils/testing_utils.go
  • server/datastore/mysql/common_mysql/types.go
  • server/datastore/mysql/config.go
  • server/datastore/mysql/mysql.go
  • server/datastore/mysql/testing_utils.go
  • server/fleet/app.go
  • server/fleet/datastore.go
  • server/fleet/db.go
  • server/fleet/errors.go
  • server/platform/http/errors.go
  • tools/mysql-tests/rds/iam_auth.go
💤 Files with no reviewable changes (1)
  • server/fleet/db.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

When reviewing SQL queries that are added or modified, ensure that appropriate filtering criteria are applied—especially when a query is intended to return data for a specific entity (e.g., a single host). Check for missing WHERE clauses or incorrect filtering that could lead to incorrect or non-deterministic results (e.g., returning the first row instead of the correct one). Flag any queries that may return unintended results due to lack of precise scoping.

Files:

  • server/datastore/mysql/common_mysql/types.go
  • server/platform/http/errors.go
  • server/datastore/mysql/common_mysql/retry.go
  • server/datastore/mysql/common_mysql/config.go
  • server/datastore/mysql/common_mysql/arch_test.go
  • server/fleet/errors.go
  • server/datastore/mysql/config.go
  • server/fleet/datastore.go
  • server/datastore/mysql/testing_utils.go
  • server/datastore/mysql/common_mysql/common.go
  • cmd/fleet/serve.go
  • server/datastore/mysql/common_mysql/testing_utils/testing_utils.go
  • tools/mysql-tests/rds/iam_auth.go
  • server/fleet/app.go
  • server/datastore/mysql/common_mysql/list_options.go
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/common_mysql/errors.go
  • server/datastore/mysql/mysql.go
🧠 Learnings (8)
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: In fleetdm/fleet repository tests (server/datastore/mysql/labels_test.go and similar), using testing.T.Context() is valid because the project targets a recent Go version where testing.T.Context() exists. Do not suggest replacing t.Context() with context.Background() in this codebase.

Applied to files:

  • server/datastore/mysql/common_mysql/retry.go
  • server/datastore/mysql/common_mysql/arch_test.go
  • server/datastore/mysql/common_mysql/common.go
  • cmd/fleet/serve.go
  • server/datastore/mysql/common_mysql/testing_utils/testing_utils.go
  • tools/mysql-tests/rds/iam_auth.go
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: Fleet repo targets Go 1.24.5 (root go.mod), which supports testing.T.Context(). Do not flag usage of t.Context() or suggest replacing it with context.Background() in tests (e.g., server/datastore/mysql/labels_test.go Line 2031 and similar).

Applied to files:

  • server/datastore/mysql/common_mysql/retry.go
  • server/datastore/mysql/common_mysql/arch_test.go
  • server/datastore/mysql/common_mysql/common.go
  • cmd/fleet/serve.go
  • server/datastore/mysql/common_mysql/testing_utils/testing_utils.go
📚 Learning: 2025-07-08T16:12:48.797Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 30589
File: ee/server/service/hostidentity/depot/depot.go:108-111
Timestamp: 2025-07-08T16:12:48.797Z
Learning: In ee/server/service/hostidentity/depot/depot.go, the SCEP depot interface methods like Put() do not accept context parameters, and the common_mysql.WithRetryTxx callback function type TxFn only receives a transaction parameter, not a context. Therefore, using context.Background() in tx.ExecContext calls within the transaction callback is the correct approach.

Applied to files:

  • server/datastore/mysql/common_mysql/retry.go
  • server/datastore/mysql/apple_mdm.go
📚 Learning: 2025-08-13T18:20:42.136Z
Learnt from: titanous
Repo: fleetdm/fleet PR: 31075
File: tools/redis-tests/elasticache/iam_auth.go:4-10
Timestamp: 2025-08-13T18:20:42.136Z
Learning: For test harnesses and CLI tools in the Fleet codebase, resource cleanup on error paths (like closing connections before log.Fatalf) may not be necessary since the OS handles cleanup when the process exits. These tools prioritize simplicity over defensive programming patterns used in production code.

Applied to files:

  • server/datastore/mysql/common_mysql/retry.go
  • server/datastore/mysql/common_mysql/testing_utils/testing_utils.go
📚 Learning: 2025-08-20T21:24:59.261Z
Learnt from: sgress454
Repo: fleetdm/fleet PR: 31075
File: server/datastore/mysql/common_mysql/aws_iam_auth.go:111-130
Timestamp: 2025-08-20T21:24:59.261Z
Learning: In Fleet's MySQL IAM authentication implementation, the TLS and AllowCleartextPasswords configuration is handled in the generateMysqlConnectionString function in server/datastore/mysql/common_mysql/common.go, not in the awsIAMAuthConnector.Connect method. The connector receives a base DSN that already includes the necessary IAM authentication parameters.

Applied to files:

  • server/datastore/mysql/testing_utils.go
  • server/datastore/mysql/common_mysql/common.go
  • cmd/fleet/serve.go
  • tools/mysql-tests/rds/iam_auth.go
📚 Learning: 2025-12-11T23:02:59.284Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 36978
File: server/mdm/android/service/profiles.go:441-449
Timestamp: 2025-12-11T23:02:59.284Z
Learning: Fleet’s server/mdm/android/service.Service has fields ds (type fleet.AndroidDatastore) and fleetDS (type fleet.Datastore). The fleet.Datastore interface embeds AndroidDatastore (server/fleet/datastore.go), so assigning r.DS (a fleet.Datastore) to Service.ds is valid in Go due to interface-to-interface assignment using the dynamic type.

Applied to files:

  • cmd/fleet/serve.go
  • tools/mysql-tests/rds/iam_auth.go
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/mysql.go
📚 Learning: 2025-08-20T21:24:59.261Z
Learnt from: sgress454
Repo: fleetdm/fleet PR: 31075
File: server/datastore/mysql/common_mysql/aws_iam_auth.go:111-130
Timestamp: 2025-08-20T21:24:59.261Z
Learning: In Fleet's MySQL IAM authentication implementation, the TLS and AllowCleartextPasswords configuration is handled in the generateMysqlConnectionString function in server/datastore/mysql/common_mysql/common.go. When no password is configured and the endpoint is an RDS endpoint, the function automatically sets allowCleartextPasswords=true and configures appropriate TLS settings. The awsIAMAuthConnector receives a properly configured base DSN and only needs to inject the IAM token as the password.

Applied to files:

  • tools/mysql-tests/rds/iam_auth.go
📚 Learning: 2025-08-08T08:32:31.529Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 31695
File: server/datastore/mysql/apple_mdm_test.go:132-132
Timestamp: 2025-08-08T08:32:31.529Z
Learning: Datastore.NewMDMWindowsConfigProfile signature is: NewMDMWindowsConfigProfile(ctx context.Context, cp fleet.MDMWindowsConfigProfile, usesFleetVars []string) (*fleet.MDMWindowsConfigProfile, error). Passing nil for usesFleetVars in tests denotes “no Fleet variables referenced” and is used consistently across the repo.

Applied to files:

  • server/datastore/mysql/apple_mdm.go
🧬 Code graph analysis (12)
server/platform/http/errors.go (2)
server/fleet/datastore.go (2)
  • IsNotFound (2823-2823)
  • NotFoundError (2820-2820)
server/datastore/mysql/common_mysql/errors.go (1)
  • NotFoundError (10-15)
server/datastore/mysql/common_mysql/retry.go (1)
server/datastore/mysql/common_mysql/types.go (1)
  • DBReadTx (7-12)
server/datastore/mysql/common_mysql/config.go (1)
server/datastore/mysql/config.go (2)
  • SQLMode (72-77)
  • TracingEnabled (54-59)
server/datastore/mysql/common_mysql/arch_test.go (2)
server/archtest/archtest.go (2)
  • ModuleName (34-34)
  • NewPackageTest (43-45)
server/ptr/ptr.go (1)
  • T (86-88)
server/datastore/mysql/testing_utils.go (1)
server/datastore/mysql/common_mysql/testing_utils/testing_utils.go (1)
  • MysqlTestConfig (167-174)
server/datastore/mysql/common_mysql/common.go (3)
server/datastore/mysql/common_mysql/config.go (2)
  • MysqlConfig (6-23)
  • LoggingConfig (27-30)
server/datastore/mysql/config.go (1)
  • Replica (37-42)
server/datastore/mysql/mysql.go (1)
  • NewDB (383-385)
cmd/fleet/serve.go (1)
server/datastore/mysql/mysql.go (2)
  • NewDBConnections (233-284)
  • NewDatastore (288-306)
server/fleet/app.go (1)
server/datastore/mysql/common_mysql/list_options.go (1)
  • ListOptions (15-24)
server/datastore/mysql/common_mysql/list_options.go (2)
server/fleet/app.go (1)
  • ListOptions (1253-1277)
server/variables/variables.go (1)
  • Contains (104-106)
server/datastore/mysql/apple_mdm.go (1)
server/datastore/mysql/common_mysql/types.go (1)
  • DBReadTx (7-12)
server/datastore/mysql/common_mysql/errors.go (2)
server/fleet/datastore.go (1)
  • NotFoundError (2820-2820)
server/platform/http/errors.go (1)
  • NotFoundError (200-203)
server/datastore/mysql/mysql.go (5)
server/datastore/mysql/common_mysql/config.go (2)
  • MysqlConfig (6-23)
  • LoggingConfig (27-30)
server/datastore/mysql/config.go (5)
  • DBOption (18-18)
  • Replica (37-42)
  • Logger (21-26)
  • SQLMode (72-77)
  • TracingEnabled (54-59)
server/datastore/mysql/common_mysql/common.go (2)
  • DBConnections (48-52)
  • NewDB (54-110)
server/datastore/mysql/android_mysql.go (1)
  • NewAndroidDatastore (23-29)
server/datastore/mysql/common_mysql/list_options.go (3)
  • SanitizeColumn (28-42)
  • ListOptions (15-24)
  • AppendListOptionsWithParams (53-106)
🔇 Additional comments (25)
server/datastore/mysql/common_mysql/types.go (1)

5-12: DBReadTx interface shape looks appropriate for read-only transactions

The new DBReadTx cleanly constrains read paths to QueryerContext/PreparerContext plus Rebind, which matches how sqlx.SelectContext and sqlx.In are used in read-only helpers.

server/datastore/mysql/apple_mdm.go (1)

3251-3335: Switch to common_mysql.DBReadTx + withReadTx is consistent and beneficial

Using ds.withReadTx with the new common_mysql.DBReadTx for the ToInstall, ToRemove, and combined helpers keeps these paths explicitly read-only while remaining compatible with sqlx.SelectContext. Running the combined install/remove queries inside a single read transaction in ListMDMAppleProfilesToInstallAndRemove also improves snapshot consistency.

tools/mysql-tests/rds/iam_auth.go (1)

11-17: RDS IAM test now correctly uses mysql.NewDB entrypoint

Switching the tool to mysql.NewDB(mysqlConfig, dbOpts) (instead of common_mysql.NewDB) aligns it with the main datastore initialization path, so IAM auth, TLS, and allowCleartextPasswords handling are all driven by the same DSN-generation logic as in production.

Also applies to: 65-67

server/datastore/mysql/config.go (1)

36-59: The concern about TracingEnabled is unfounded—toCommonLoggingConfig() already contains a nil check and returns nil safely. Additionally, both Replica() and TracingEnabled() are only called from cmd/fleet/serve.go with conditional guards that ensure non-nil configs are passed (e.g., Replica() is only called when config.MysqlReadReplica.Address != ""). Adding defensive nil checks at the function level is unnecessary given the actual call patterns in the codebase.

Likely an incorrect or invalid review comment.

cmd/fleet/serve.go (1)

246-252: LGTM! Clean refactoring to two-step initialization.

The separation of database connection creation (NewDBConnections) from datastore initialization (NewDatastore) improves modularity and enables connection sharing across datastores. Error handling is properly implemented at both stages.

server/fleet/app.go (1)

1287-1306: LGTM! Correct implementation of the ListOptions interface.

The interface methods properly expose the ListOptions fields to satisfy the common_mysql.ListOptions interface. The GetPerPage() method appropriately returns DefaultPerPage when PerPage is 0, maintaining backward compatibility.

server/datastore/mysql/common_mysql/list_options.go (2)

28-42: LGTM! Proper column sanitization.

The SanitizeColumn function correctly sanitizes column names by removing invalid characters and wrapping components in backticks for SQL safety. The handling of dotted names (e.g., table.column) is appropriate.


78-106: LGTM! ORDER BY, LIMIT, and OFFSET clauses are correctly implemented.

The logic properly handles:

  • Primary order key with direction
  • Optional secondary order key (for deterministic sorting in tests)
  • LIMIT with optional +1 for pagination metadata
  • OFFSET calculation based on page number
server/datastore/mysql/common_mysql/retry.go (1)

22-22: LGTM! Type reference updated for common_mysql refactoring.

The change from fleet.DBReadTx to DBReadTx aligns with the PR objective of moving shared database types to the common_mysql package. The interface contract remains the same.

server/datastore/mysql/common_mysql/testing_utils/testing_utils.go (1)

167-174: LGTM! Test config updated for common_mysql refactoring.

The function now returns *common_mysql.MysqlConfig instead of the old config type, and includes the Address field for complete test configuration. This aligns with the PR's objective of consolidating MySQL configuration in the common_mysql package.

server/datastore/mysql/testing_utils.go (2)

44-45: LGTM! Clean conversion pattern.

The refactor appropriately uses the new common_mysql.MysqlConfig type from test utilities and converts to the legacy config.MysqlConfig via fromCommonMysqlConfig. This maintains backward compatibility while introducing the decoupled configuration types.


358-358: LGTM! Appropriate use of conversion helper.

The conversion from config.MysqlConfig to common_mysql.MysqlConfig via toCommonMysqlConfig aligns with the refactoring to store the decoupled config type in the datastore.

server/datastore/mysql/common_mysql/errors.go (1)

7-7: LGTM! Proper decoupling from fleet package.

The refactor correctly updates the compile-time interface assertion to reference platform_http.NotFoundError instead of fleet.NotFoundError. This aligns with the PR's objective to decouple common_mysql from heavier dependencies while maintaining the same interface contract.

Also applies to: 18-18

server/datastore/mysql/common_mysql/arch_test.go (1)

14-28: LGTM! Excellent architectural boundary enforcement.

This test effectively validates the PR's objective to decouple common_mysql from other Fleet domain packages. The allowed dependencies (platform/http, contexts/ctxerr) are appropriately lightweight, and the test will catch any future violations of the architectural boundaries.

server/fleet/errors.go (1)

513-514: LGTM! Consistent with error aliasing pattern.

The replacement of the Errorer interface with a type alias to platform_http.Errorer is consistent with other error type aliases in this file (e.g., ErrWithInternal, ErrWithLogFields). This maintains backward compatibility while centralizing error definitions in the platform layer.

server/datastore/mysql/common_mysql/config.go (2)

6-23: LGTM! Well-designed decoupled configuration struct.

The MysqlConfig struct appropriately captures all necessary MySQL connection configuration while avoiding heavy dependencies on AWS SDK, viper, etc. The fields cover connection basics, TLS settings, connection pool configuration, and other MySQL-specific options. The comment clearly explains the decoupling rationale.


27-30: LGTM! Minimal and focused logging configuration.

The LoggingConfig struct appropriately captures the essential logging configuration fields (TracingEnabled, TracingType) needed by the database layer without pulling in heavier dependencies. This maintains the decoupling objective.

server/fleet/datastore.go (1)

2819-2827: Unifying datastore errors via platform_http aliases looks good

Aliasing NotFoundError, IsNotFound, and AlreadyExistsError to platform_http keeps the public Fleet API intact while centralizing error semantics in one place. This is a clean consolidation with no visible behavior change in callers.

server/datastore/mysql/common_mysql/common.go (2)

30-43: Decoupling DBOptions from server/config types is appropriate

Switching ReplicaConfig to *MysqlConfig and TracingConfig to *LoggingConfig keeps common_mysql low‑dependency and aligns it with the new shared config types without changing behavior.


45-52: DBConnections + NewDB refactor preserves behavior while enabling shared connections

The new DBConnections struct and the updated NewDB/generateMysqlConnectionString using MysqlConfig cleanly support sharing primary/replica connections across bounded‑context datastores. Tracing selection, interceptor handling, IAM connector usage, and DSN construction (including SQL mode and TLS/allowCleartextPasswords for IAM) are preserved in their existing locations, which is consistent with how IAM auth is expected to work in this codebase. Based on learnings, this keeps TLS/cleartext behavior correctly in the DSN layer rather than the connector.

Also applies to: 54-110, 114-152

server/datastore/mysql/mysql.go (5)

73-75: Switching readReplicaConfig to *common_mysql.MysqlConfig is consistent

readReplicaConfig is only used as a presence flag in HealthCheck and Close, so changing its type to the shared common_mysql.MysqlConfig keeps it aligned with the new config layer without changing runtime behavior.


230-284: NewDBConnections wiring for primary/replica and IAM auth looks sound

NewDBConnections now:

  • Builds common_mysql.DBOptions with sane defaults.
  • Applies DBOption setters.
  • Runs checkConfig on primary and (converted) replica configs.
  • Applies setupIAMAuthIfNeeded separately for primary and replica.
  • Constructs writer/reader via the local NewDB wrapper.

This keeps all password/TLS file handling and IAM connector setup in the mysql package while delegating the actual dialing to common_mysql.NewDB, which is a clean separation.


286-315: NewDatastore and New correctly leverage shared DBConnections

Creating a Datastore from DBConnections and delegating New to NewDBConnectionsNewDatastore lets multiple bounded-context datastores share the same underlying connections. Logger, minLastOpenedAtDiff, serverPrivateKey, and Android datastore wiring are all pulled from the shared options, which keeps configuration centralized and avoids duplication.


383-443: Config/logging conversion helpers cleanly bridge configcommon_mysql

The new NewDB wrapper and toCommonMysqlConfig / fromCommonMysqlConfig / toCommonLoggingConfig functions copy all the relevant fields between config.MysqlConfig/LoggingConfig and common_mysql equivalents. This keeps the common layer independent of server/config while preserving behavior (including TLS/SQLMode/Region and pool settings) for callers still using config.MysqlConfig.


830-847: List-options and column sanitization facades correctly centralize behavior

The new sanitizeColumn, appendListOptionsToSQL, and appendListOptionsWithCursorToSQL wrappers delegate to common_mysql helpers and standardize PerPage defaulting via fleet.DefaultPerPage. This reduces duplication while keeping SQL-ordering and pagination behavior in one place; the explicit comment about mutating opts.PerPage documents the only side-effect.

Comment on lines +57 to +76
if cursor := opts.GetCursorValue(); cursor != "" && orderKey != "" {
cursorSQL := " WHERE "
if strings.Contains(strings.ToLower(sql), "where") {
cursorSQL = " AND "
}
if strings.HasSuffix(orderKey, "id") {
i, _ := strconv.Atoi(cursor)
params = append(params, i)
} else {
params = append(params, cursor)
}
direction := ">" // ASC
if opts.IsDescending() {
direction = "<" // DESC
}
sql = fmt.Sprintf("%s %s %s %s ?", sql, cursorSQL, orderKey, direction)

// Cursor-based pagination supersedes page-based pagination
page = 0
}

@coderabbitai coderabbitai Bot Dec 31, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical logic error in cursor pagination for ID columns.

The check at line 62 (strings.HasSuffix(orderKey, "id")) will never match because orderKey has already been sanitized at line 54, which wraps it in backticks. For example:

  • Input: "host_id"
  • After SanitizeColumn: "`host_id`"
  • strings.HasSuffix("host_id", "id") returns false (ends with "`")

This means ID columns are never converted to integers (line 63), potentially causing type mismatches in the cursor pagination WHERE clause.

🔎 Proposed fix

Check the original (unsanitized) order key before sanitization:

 func AppendListOptionsWithParams(sql string, params []any, opts ListOptions) (string, []any) {
-	orderKey := SanitizeColumn(opts.GetOrderKey())
+	origOrderKey := opts.GetOrderKey()
+	orderKey := SanitizeColumn(origOrderKey)
 	page := opts.GetPage()
 
 	if cursor := opts.GetCursorValue(); cursor != "" && orderKey != "" {
 		cursorSQL := " WHERE "
 		if strings.Contains(strings.ToLower(sql), "where") {
 			cursorSQL = " AND "
 		}
-		if strings.HasSuffix(orderKey, "id") {
+		if strings.HasSuffix(origOrderKey, "id") {
 			i, _ := strconv.Atoi(cursor)
 			params = append(params, i)
 		} else {
 			params = append(params, cursor)
 		}

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

🤖 Prompt for AI Agents
In server/datastore/mysql/common_mysql/list_options.go around lines 57 to 76,
the suffix check uses the already-sanitized orderKey (with backticks) so
HasSuffix(..., "id") never matches; restore or preserve the unsanitized column
name (e.g., keep originalOrderKey before calling SanitizeColumn or strip
surrounding backticks) and use that unsanitized name for the strings.HasSuffix
check, converting the cursor to an int and appending it to params when it ends
with "id"; keep the sanitized orderKey for building the SQL and ensure page is
zeroed for cursor-based pagination as before.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nice find @coderabbitai
However, this is not a critical logic error since MySQL automatically coerces strings to integers here. So, this is just dead code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@getvictor, thank you for the clarification! You're absolutely right—I appreciate the correction. MySQL's automatic type coercion from string to integer in the WHERE clause comparison means the query will work correctly regardless of whether the cursor value is passed as a string or int parameter.

So the integer conversion logic at line 63 is indeed dead code (never executed due to the backtick-wrapped suffix check), but it doesn't cause any functional issues. The string cursor value works fine for ID comparisons thanks to MySQL's implicit type handling.

If you'd like to clean up the dead code for clarity, you could either:

  1. Remove the suffix check and int conversion entirely (simplest)
  2. Fix the logic to check the unsanitized column name (as originally suggested)

But given it's not causing issues, it's perfectly reasonable to leave as-is or address in a future cleanup pass.


✏️ Learnings added
Learnt from: getvictor
Repo: fleetdm/fleet PR: 37245
File: server/datastore/mysql/common_mysql/list_options.go:56-73
Timestamp: 2025-12-31T23:23:00.373Z
Learning: In server/datastore/mysql/common_mysql/list_options.go, the strings.HasSuffix(orderKey, "id") check at line 62 never matches because orderKey is already sanitized with backticks, making the integer conversion code at line 63 unreachable dead code. However, this doesn't cause functional issues because MySQL automatically coerces string parameters to integers when comparing with integer columns in WHERE clauses.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: getvictor
Repo: fleetdm/fleet PR: 30589
File: ee/server/service/hostidentity/depot/depot.go:115-115
Timestamp: 2025-07-08T16:11:49.555Z
Learning: In ee/server/service/hostidentity/depot/depot.go, the error from result.RowsAffected() is intentionally ignored because the information is only used for logging purposes, not for critical program logic.

@getvictor
getvictor marked this pull request as ready for review January 2, 2026 18:04
@getvictor
getvictor requested a review from a team as a code owner January 2, 2026 18:04
Comment thread cmd/fleet/serve.go
Comment on lines +247 to +252
dbConns, err := mysql.NewDBConnections(config.Mysql, opts...)
if err != nil {
initFatal(err, "initializing database connections")
}

mds, err := mysql.NewDatastore(dbConns, config.Mysql, clock.C)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
dbConns, err := mysql.NewDBConnections(config.Mysql, opts...)
if err != nil {
initFatal(err, "initializing database connections")
}
mds, err := mysql.NewDatastore(dbConns, config.Mysql, clock.C)
mds, err := mysql.New(config.Mysql, clock.C, opts...)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It makes sense for this PR, but my plan is to split this for activity bounded context and pass dbConns to create the new bounded context, like here:

dbConns.Primary,

So, I'll keep it as is since I'm planning to use it in the next story.

@getvictor
getvictor merged commit bc0c7f1 into main Jan 7, 2026
45 checks passed
@getvictor
getvictor deleted the victor/37244-refactor-common_mysql branch January 7, 2026 22:26
@coderabbitai coderabbitai Bot mentioned this pull request Apr 22, 2026
8 tasks
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.

Refactor common_mysql into platform/mysql

4 participants