Skip to content

sqldb/v2: introduce sqldb/v2 module#10697

Merged
yyforyongyu merged 18 commits intolightningnetwork:masterfrom
ziggie1984:sqldb-v2-rebase
Apr 1, 2026
Merged

sqldb/v2: introduce sqldb/v2 module#10697
yyforyongyu merged 18 commits intolightningnetwork:masterfrom
ziggie1984:sqldb-v2-rebase

Conversation

@ziggie1984
Copy link
Copy Markdown
Collaborator

Summary

This PR introduces the sqldb/v2 module, a standalone Go module extracted
from the existing sqldb package. It contains only the non-lnd-specific
database infrastructure code, making it reusable across lnd and tapd.

This branch is a clean rebase of origin/sqldb-v2 onto current master.
It contains exactly the 18 commits from that feature branch and nothing else,
so reviewers can verify no unrelated changes are introduced.

Commits (oldest → newest)

  1. sqldb/v2: add base for sqldb/v2 module
  2. sqldb/v2: move all non lnd-specific v1 code to v2
  3. sqldb/v2: introduce MigrationStream
  4. sqldb/v2: Use MigrationStream for migrations
  5. sqldb/v2: introduce sqldb/v2 BaseDB
  6. sqldb/v2: sync features with tapd's sqldb package
  7. sqldb/v2: rename test db helper files
  8. sqldb/v2: clarify no_sqlite SqliteStore intent
  9. sqldb/v2: set predictable NewTestPgFixture container name
  10. sqldb/v2: move Sqlite test helpers to separate file
  11. sqldb/v2: rename ErrRetriesExceeded error
  12. sqldb/v2: rename the postgresErrMsgs list
  13. sqldb/v2: use defaultMaxIdleConns in SqliteStore
  14. sqldb/v2: ensure SqliteConfig.MaxConnections is used
  15. sqldb/v2: clarify config options docs
  16. sqldb/v2: add MaxIdleConnections & ConnMaxLifetime sqlite opts
  17. sqldb/v2: rename MigrationConfig to MigrationDescriptor
  18. sqldb/v2: limit MigrationExecutor interface

Test plan

  • make build passes
  • make lint passes
  • Unit tests for the sqldb/v2 package pass

In the upcoming commits, we will introduce a new sqldb module, sqldb
version 2.

The intention of the new sqldb module, is to make it generalizable so
that it contains no `lnd` specific code, to ensure that it can be reused
in other projects.

This commit adds the base of the new module, but does not include any
implementation yet, as that will be done in the upcoming commits.
This commit moves all non lnd-specific code of sqldb/v1 to the new
sqldb/v2 module.

Note however, that without additional changes, this package still needs
to reference lnd, as references to the lnd `sqlc` package is required
without further changes. Those changes will be introduced in the
upcoming commits, to fully decouple the new sqldb/v2 module from lnd.
This commit introduces a new struct named `MigrationStream`, which
defines a structure for migrations SQL migrations.

The `MigrationStream` struct contains the SQL migrations which will be
applied, as well as corresponding post-migration code migrations which
will be executed afterwards. The struct also contains fields which
define how the execution of the migrations are tracked.

Importantly, it is also possible to define multiple different
`MigrationStream`s which are executed, to for example define one `prod`
and one `dev` migration stream.
This commit updates the `sqldb/v2` package to utilize the new
`MigrationStream` type for executing migrations, instead of passing
`[]MigrationConfig`'s directly.
This commit updates the definition of the `BaseDB` struct to decouple
it from lnd`s `sqlc` package. We also introduce new fields to the struct
to make it possible to track the database type used at runtime.
In order to make it possible to replace `tapd`'s internal `sqldb`
package with the new generic `sqldb/v2` package, we need to make sure
that all features and functionality that currently exist in the `tapd`
package are also present in the new `sqldb/v2` package.

This commit adds such additional missing features to the `sqldb/v2`
package.
Previously, the test db helper files were suffixed with "_test", which
would indicate that the files specifically contained tests.
However, these files actually contain helper functions to be used
in tests, and are not tests themselves. To better reflect their
purpose, the files have been renamed to instead be prefixed with
"test_".
The docs of the `SqliteStore` for no sqlite build environments
previously didn't clarify that the actual `SqliteStore` implementation
under such build tag environments, didn't actually implement a real
sqlite store. This commit clarifies that in the docs.
rename the `ErrRetriesExceeded` error to `ErrTxRetriesExceeded`.
Rename the `postgresErrMsgs` list to `postgresRetriableErrMsgs`, in
order to clarify its usage.
The code previously used `defaultMaxOpenConns` for both the maximum
number of open connections and the maximum number of idle connections in
the `SqliteStore`.

This commit updates the code to use `defaultMaxIdleConns` for the
maximum number of idle connections.
The documentation for the `SqliteConfig.MaxConnections`,
`PostgresConfig.MaxOpenConnections` and
`PostgresConfig.MaxIdleConnections` previously stated that an unlimited
number was used when the value was set to 0. This is not the case
however, as setting the value to 0 will result in the default values
being used.
This commit limits the MigrationExecutor interface due to the following
reasoning:

1. SkipMigrations() and DefaultTarget() should not be on the interface
Both are only used by ApplyAllMigrations, which immediately passes the
results back into the same executor. They are internal implementation
details and should be folded into ExecuteMigrations itself.

2. SetSchemaVersion and GetSchemaVersion are test-only but on the
production interface Every caller of these in sqldb/v2 is in test files.
The SetSchemaVersion comment even says "USE WITH CAUTION" — dangerous
test utilities should not be on an interface that every real consumer
must implement. They should be accessible on the concrete types only and
used directly in tests without going through the interface.

3. ExecuteMigrations should not take a MigrationTarget parameter for the
normal path On the normal startup path, callers just do
executor.ExecuteMigrations(executor.DefaultTarget(), stream) — asking
the executor for its default and handing it straight back. The method
should run to latest by default; a version override for tests can live
on the concrete type instead.
@gemini-code-assist
Copy link
Copy Markdown

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 introduces the sqldb/v2 module, a clean extraction of database infrastructure previously coupled with lnd. By decoupling this logic, the new module provides a unified, backend-agnostic interface for database operations, migrations, and transaction management, facilitating easier maintenance and shared usage across different Lightning Network projects.

Highlights

  • Module Extraction: Extracted non-lnd-specific database infrastructure code into a new standalone Go module sqldb/v2 to improve reusability across projects like lnd and tapd.
  • Database Infrastructure: Introduced MigrationStream and BaseDB to standardize database interactions, migration handling, and configuration management for both SQLite and Postgres backends.
  • Transaction Management: Implemented a robust TransactionExecutor with configurable retry logic for handling serialization and deadlock errors, ensuring atomic operations.
  • Testing Utilities: Added comprehensive test helpers, including a Postgres Docker container fixture and SQLite test utilities, to support consistent database testing across environments.

🧠 New Feature in Public Preview: You can now enable Memory 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 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 counter productive. 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.

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.

Copy link
Copy Markdown

@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 a new version of the sqldb package, providing a database-agnostic abstraction for SQLite and Postgres backends. It includes comprehensive support for migrations, transaction execution with automatic retry logic, and utilities for paginated queries. The review feedback identifies several important improvements: addressing a potential resource leak caused by using defer inside a retry loop, removing unused code in the transaction executor, preventing potential infinite loops in pagination through better configuration validation, and expanding Postgres DSN parsing to support non-URL connection strings.

Comment on lines +321 to +323
defer func() {
_ = tx.Rollback()
}()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Using defer inside a loop is discouraged as it can lead to resource exhaustion. Deferred functions are only executed when the surrounding function returns, not at the end of each loop iteration. In this retry loop, if multiple attempts are made due to serialization errors, multiple rollback functions will be queued on the stack. This can cause memory growth and delayed release of database resources. Consider wrapping the loop body in a closure to ensure the deferred rollback runs at the end of each iteration.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

let's fix this in a follow-up

Comment on lines +163 to +165
func (t *txExecutorOptions) randRetryDelay() time.Duration {
return time.Duration(rand.Int63n(int64(t.maxRetryDelay))) //nolint:gosec
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The method randRetryDelay on txExecutorOptions appears to be unused within this package. The logic for calculating retry delays is instead implemented in the package-level function randRetryDelay at line 220, which is used by ExecuteSQLTransactionWithRetry. Consider removing this unused method to maintain code clarity.

}

// Process items in pages.
for i := 0; i < len(inputItems); i += int(cfg.MaxBatchSize) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If cfg.MaxBatchSize is zero, this loop will result in an infinite loop. While QueryConfig.Validate checks for this, the NewSqliteStore and NewPostgresStore functions do not currently enforce validation of the provided configuration. It is recommended to ensure MaxBatchSize is positive before entering the loop or enforce validation during store initialization.

Comment on lines +75 to +85
func getDatabaseNameFromDSN(dsn string) (string, error) {
// Parse the DSN as a URL
u, err := url.Parse(dsn)
if err != nil {
return "", err
}

// The database name is the last segment of the path. Trim leading slash
// and return the last segment.
return path.Base(u.Path), nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The getDatabaseNameFromDSN function assumes the DSN is always in URL format. Postgres also supports a 'key=value' connection string format (e.g., host=localhost user=test dbname=mydb). For such DSNs, url.Parse will not correctly extract the database name, which may cause issues during migration initialization. Consider adding support for parsing 'key=value' DSNs or documenting that only URL-style DSNs are supported.

@ziggie1984 ziggie1984 self-assigned this Mar 31, 2026
@ziggie1984
Copy link
Copy Markdown
Collaborator Author

replaces #10681

@yyforyongyu yyforyongyu merged commit 37ae765 into lightningnetwork:master Apr 1, 2026
42 of 49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants