Skip to content

fix(ers): lazily register database/sql driver in SQL ERS provider#3543

Open
jp-ayyappan wants to merge 3 commits into
mainfrom
fix/ers-sql-provider-register-postgres-driver
Open

fix(ers): lazily register database/sql driver in SQL ERS provider#3543
jp-ayyappan wants to merge 3 commits into
mainfrom
fix/ers-sql-provider-register-postgres-driver

Conversation

@jp-ayyappan
Copy link
Copy Markdown
Contributor

@jp-ayyappan jp-ayyappan commented May 28, 2026

Summary

Fixes #3539.

The multi-strategy ERS SQL provider called sql.Open(config.Driver, ...) but relied on consumers to register the named driver via a blank import (e.g. _ "github.com/lib/pq"). This forced every consumer — even those not using the SQL ERS tier — to register the driver globally at init() time.

Changes

Lazy driver registration in NewProvider() via ensureDriverRegistered():

  1. Normalizes config.Driver to lowercase + trimmed at the top of NewProvider — ensures both ensureDriverRegistered and sql.Open receive the canonical lowercase name, preventing mixed-case inputs (e.g., "Postgres") from bypassing the pre-check or failing sql.Open with "unknown driver"
  2. Checks registeredDrivers map (fast path for repeated calls)
  3. Checks sql.Drivers() with strings.EqualFold — gracefully handles drivers already registered by the consumer binary
  4. Registers "postgres" via pgx/v5/stdlib.GetDefaultDriver() on first use — no new dependency (pgx/v5 is already a direct dependency of this module)

Uses a sync.Mutex guard so concurrent NewProvider() calls are safe.

Behavior

Scenario Result
First SQL provider created, "postgres" not yet registered Registers via pgx stdlib
Second SQL provider created (same or different driver name) No-op (already in registeredDrivers)
Consumer already registered "postgres" (blank import) No-op (detected via sql.Drivers() pre-check)
Consumer passes driver: Postgres (mixed case) Normalized to "postgres" in NewProvider — no panic, no "unknown driver" error
Consumer passes driver: mysql Not auto-registered (no mysql dep in this module); consumer must import driver

Testing

  • Existing SQL provider unit tests pass (go test ./entityresolution/multi-strategy/providers/sql/...)
  • Build verified (go build ./entityresolution/multi-strategy/...)

Notes

  • mysql and sqlite drivers are documented as requiring consumer-side imports until their dependencies are added to this module's go.mod
  • Consumers that previously added _ "github.com/lib/pq" continue to work correctly — the sql.Drivers() pre-check detects the existing registration and skips sql.Register

Previously the SQL provider required consumers to add a blank driver
import (e.g. _ "github.com/lib/pq") to their own binaries, causing
the driver to be registered globally at init time even when the SQL
ERS tier was not configured.

This change moves driver registration into NewProvider() using
pgx/v5/stdlib (already a direct dependency of this module), guarded by
a sync.Mutex and a sql.Drivers() pre-check to prevent duplicate-register
panics when consumers have already registered the driver themselves.

The registration is now lazy: it happens only when a SQL ERS provider is
actually instantiated, and only if the driver has not already been
registered by the consumer or another code path.

Fixes: #3539
@jp-ayyappan jp-ayyappan requested a review from a team as a code owner May 28, 2026 20:27
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the SQL ERS provider to handle database driver registration internally and lazily. By moving registration from global init() calls to the provider instantiation phase, the module reduces unnecessary dependencies for consumers who do not utilize the SQL ERS tier, while maintaining backward compatibility for those who continue to register drivers manually.

Highlights

  • Lazy Driver Registration: Implemented a thread-safe lazy registration mechanism for database/sql drivers, removing the requirement for consumers to perform global blank imports.
  • PostgreSQL Support: Integrated pgx/v5/stdlib to automatically register the 'postgres' driver when a SQL ERS provider is instantiated.
  • Compatibility: Added a pre-check against sql.Drivers() to ensure that existing consumer-side driver registrations are respected and do not cause duplicate-registration panics.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


The driver waits in silent sleep, Until the provider starts to creep. No global import needed now, We register with a lazy bow.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

📝 Walkthrough

Walkthrough

The SQL provider now auto-registers the PostgreSQL database/sql driver lazily and concurrency-safely: a mutex-backed registry and ensureDriverRegistered normalize/check drivers and register postgres via pgx/v5/stdlib when NewProvider() runs; MySQL/SQLite still require consumer imports.

Changes

Lazy PostgreSQL Driver Auto-Registration

Layer / File(s) Summary
Driver auto-registration mechanism
service/entityresolution/multi-strategy/providers/sql/sql_provider.go
Global sync-protected registry tracks registered driver names. ensureDriverRegistered() normalizes the driver name, checks sql.Drivers() case-insensitively, and lazily registers only postgres using pgx/v5/stdlib.
Provider initialization integration
service/entityresolution/multi-strategy/providers/sql/sql_provider.go
NewProvider() now calls ensureDriverRegistered(config.Driver) before opening the DB connection so the configured driver is registered automatically on provider creation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I nibble through code at dawn's first light,
I stitch a driver in the quiet night,
Lazy and safe, no blank imports to show,
Postgres wakes when the provider says go,
A small hop, a tidy, concurrent glow.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: lazy registration of database/sql driver in the SQL ERS provider.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from #3539: lazy driver registration in NewProvider(), duplicate-registration protection via pre-checks, no new dependencies, and case-insensitive handling.
Out of Scope Changes check ✅ Passed All changes in sql_provider.go are directly scoped to implementing lazy driver registration as specified in #3539; no extraneous modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ers-sql-provider-register-postgres-driver

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.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces lazy driver registration for SQL providers to avoid requiring consumers to manually import database drivers. It adds a helper function ensureDriverRegistered that registers the postgres driver using pgx/v5/stdlib if it is not already registered. Feedback on this change highlights a potential issue where mixed-case driver names (e.g., 'Postgres') can bypass the registration check, leading to a duplicate registration panic or an unknown driver error. Normalizing config.Driver to lowercase before registration is recommended to prevent these issues.

Comment on lines 63 to +66
func NewProvider(ctx context.Context, name string, config Config) (*Provider, error) {
// Register the database/sql driver for this provider's configured driver name
// if it has not already been registered.
ensureDriverRegistered(config.Driver)
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.

high

If config.Driver is provided with mixed casing (e.g., "Postgres" or "POSTGRES"), it can lead to two issues:

  1. Panic in ensureDriverRegistered: If "postgres" is already registered externally (e.g., via lib/pq in the consumer binary), the case-sensitive check d == driver (where d is "postgres" and driver is "Postgres") will fail. The function will then proceed to call sql.Register("postgres", ...) which panics because the driver is already registered.
  2. Unknown driver error in sql.Open: sql.Open(config.Driver, ...) will be called with "Postgres", which will fail because the registered driver name is lowercase "postgres".

Normalizing config.Driver to lowercase at the beginning of NewProvider resolves both issues cleanly.

Suggested change
func NewProvider(ctx context.Context, name string, config Config) (*Provider, error) {
// Register the database/sql driver for this provider's configured driver name
// if it has not already been registered.
ensureDriverRegistered(config.Driver)
func NewProvider(ctx context.Context, name string, config Config) (*Provider, error) {
config.Driver = strings.ToLower(config.Driver)
// Register the database/sql driver for this provider's configured driver name
// if it has not already been registered.
ensureDriverRegistered(config.Driver)

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 179.887088ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 91.089647ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 433.789716ms
Throughput 230.53 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 43.442746309s
Average Latency 431.74303ms
Throughput 115.09 requests/second

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@service/entityresolution/multi-strategy/providers/sql/sql_provider.go`:
- Around line 32-48: The ensureDriverRegistered function should normalize the
driver name (e.g., via strings.ToLower) before any checks and keying to avoid
duplicate registration panics: use a lowercasedDriver variable for lookups into
registeredDrivers and comparisons against sql.Drivers(), store the normalized
key into registeredDrivers, and use the normalized value in the switch so
sql.Register("postgres", ...) is only called when no existing "postgres" driver
was found; update references to registeredDrivers, sql.Drivers(), and the switch
in ensureDriverRegistered accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 065a65f0-ebb5-452a-9d63-c23933b085c3

📥 Commits

Reviewing files that changed from the base of the PR and between 5d0508f and 4828bd8.

📒 Files selected for processing (1)
  • service/entityresolution/multi-strategy/providers/sql/sql_provider.go

Both the registeredDrivers map lookup and the sql.Drivers() pre-check
were case-sensitive, while the switch used strings.ToLower. A consumer
passing driver: Postgres (capital P) would bypass the pre-check and
trigger a duplicate sql.Register panic.

Normalize to strings.ToLower(strings.TrimSpace(driver)) at the top of
ensureDriverRegistered and use strings.EqualFold for the sql.Drivers()
comparison so all case variants map to a single canonical key.

Addresses review feedback from gemini-code-assist and coderabbitai.
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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@service/entityresolution/multi-strategy/providers/sql/sql_provider.go`:
- Around line 29-31: ensureDriverRegistered lowercases the local driver but
NewProvider still calls sql.Open(config.Driver,...), causing unknown driver
errors; change the flow so the normalized driver is used for opening the DB —
either have ensureDriverRegistered return the normalized driver (or set
config.Driver = normalized inside it) and then call sql.Open(normalizedDriver,
cfg.ConnectionString) in NewProvider (references: ensureDriverRegistered,
NewProvider, sql.Open, config.Driver).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 76124089-096c-473f-bda9-db8191e3b3cd

📥 Commits

Reviewing files that changed from the base of the PR and between 4828bd8 and b2cf821.

📒 Files selected for processing (1)
  • service/entityresolution/multi-strategy/providers/sql/sql_provider.go

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 148.362773ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 79.031597ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 405.419888ms
Throughput 246.66 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 41.392929377s
Average Latency 412.698266ms
Throughput 120.79 requests/second

ensureDriverRegistered already normalizes its input to lowercase, but
NewProvider was still passing the raw config.Driver to sql.Open. A
mixed-case driver (e.g. 'Postgres') would pass through ensureDriverRegistered
correctly but then fail sql.Open with 'unknown driver' since database/sql
matches driver names by exact string.

Normalize config.Driver at the top of NewProvider so both
ensureDriverRegistered and sql.Open receive the canonical lowercase name.

Addresses review feedback from coderabbitai and gemini-code-assist.
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • otdfctl
  • sdk
  • service
  • lib/fixtures
  • tests-bdd

See the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 185.854726ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 101.708203ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 414.975939ms
Throughput 240.98 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 45.038496371s
Average Latency 448.816346ms
Throughput 111.02 requests/second

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

multi-strategy ERS SQL provider should register its own database/sql driver

1 participant