-
Notifications
You must be signed in to change notification settings - Fork 2.2k
[Part 3|*] Implement Second Part for SQL Backend functions #10291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: elle-payment-sql-series-new
Are you sure you want to change the base?
[Part 3|*] Implement Second Part for SQL Backend functions #10291
Conversation
80de0dd to
c0134ed
Compare
f0e3302 to
99ddf07
Compare
|
/gemini review |
There was a problem hiding this 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 implements a significant portion of the insert and delete methods for the new SQL backend for the payments database. The changes are extensive and include new SQL queries, data conversion functions, and the implementation of several methods on the SQLStore.
The use of ExecuteBatchQuery and ExecutePaginatedQuery is great for performance. The new PaymentAndIntent interface is a good abstraction.
I've found a few areas for improvement:
- Adherence to the function commenting style guide for exported functions.
- Consistent use of
context.Contextpropagation instead ofcontext.TODO(). - Opportunities for performance improvements by using batch inserts and optimizing queries to avoid extra fetches.
- A potential code smell related to handling of nil slices from the database.
Overall, this is a solid step forward in the SQL backend implementation. Addressing these points will improve the code's maintainability, robustness, and performance.
| func (s *SQLStore) FetchPayment(paymentHash lntypes.Hash) (*MPPayment, error) { | ||
| ctx := context.TODO() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should accept a context.Context from its caller instead of using context.TODO(). This allows for proper context propagation, which is crucial for handling cancellations and deadlines. This change will require updating the DB interface as well. Several other methods in this file (DeletePayment, DeleteFailedAttempts, InitPayment, RegisterAttempt, SettleAttempt) have the same issue and should also be updated.
| func (s *SQLStore) FetchPayment(paymentHash lntypes.Hash) (*MPPayment, error) { | |
| ctx := context.TODO() | |
| func (s *SQLStore) FetchPayment(ctx context.Context, paymentHash lntypes.Hash) (*MPPayment, error) { | |
| // TODO(ziggie): Can we get rid of this? | ||
| // This has to be in place otherwise the | ||
| // comparison will not match. | ||
| if record.Value == nil { | ||
| record.Value = []byte{} | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TODO here highlights a potential code smell. This workaround of converting a nil slice to an empty slice might be hiding an issue or could be simplified. It would be beneficial to investigate whether the downstream code that consumes these records can be made to handle nil slices gracefully. This would remove the need for this conversion and make the data handling more consistent.
| // QueryPayments queries the payments from the database. | ||
| // | ||
| // This is part of the DB interface. | ||
| func (s *SQLStore) QueryPayments(ctx context.Context, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment for this exported function does not fully adhere to the repository's style guide.1 The style guide requires that function comments begin with the function name and provide detailed information for the caller. Please update this comment to start with QueryPayments and add more details about its behavior, parameters, and return values. This applies to other new exported functions in this file as well, such as FetchPayment, DeletePayment, etc.
Style Guide References
Footnotes
payments/db/sql_store.go
Outdated
| // Get the payment ID from the inserted payment | ||
| // We need to fetch the payment we just inserted to get its ID | ||
| insertedPayment, err := db.FetchPayment(ctx, paymentHash[:]) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to fetch inserted payment: %w", err) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To improve efficiency, this extra FetchPayment call can be avoided. The InsertPayment SQL query can be modified to return the ID of the newly inserted row. You can achieve this by changing the query in sqldb/sqlc/queries/payments.sql from :exec to :one and adding RETURNING id. The Go code would then be simplified to use the returned ID directly.
| func (s *SQLStore) insertRouteHops(ctx context.Context, db SQLQueries, | ||
| hops []*route.Hop, attemptID uint64) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function inserts route hops and their associated data one by one in a loop. For routes with many hops, this can result in a large number of database queries, impacting performance. Consider refactoring this to use batch inserts. sqlc supports batch inserts, which would require modifying the SQL queries to accept slices of parameters and then calling them once with all the data for the hops.
In this commit we add all queries which we will need to insert payment related data into the db.
99ddf07 to
6145682
Compare
|
@ziggie1984, remember to re-request review from reviewers when ready |
| }, func() { | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NoOpReset
| completePayment, err := s.fetchPaymentWithCompleteData( | ||
| ctx, db, fetchPayment, | ||
| ) | ||
| if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i dont think we need to fetch the complete payment here?
| if failedHtlcsOnly { | ||
| return s.db.DeleteFailedAttempts( | ||
| ctx, fetchPayment.Payment.ID, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems like the next commit should be before this one?
| return db.DeleteFailedAttempts( | ||
| ctx, fetchPayment.Payment.ID, | ||
| ) | ||
| } | ||
|
|
||
| // Be careful to not use s.db here, because we are in a | ||
| // transaction, is there a way to make this more secure? | ||
| return db.DeletePayment(ctx, fetchPayment.Payment.ID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these changes should be in the prior commit (was confusing to have it here since it seemed like it might be related to the change below but it is not)
| }, func() { | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sqldb.NoOpReset
| } | ||
|
|
||
| // If payment exists and is failed, delete it first. | ||
| if existingPayment.Payment.ID != 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i find this part quite hard to read. I suggest using a switch with 3 different cases.
Cause here, you are referencing the returned existingPayment in the case that the error is not nil which is an antipattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will only be !=0 in the case where err == nil - so just put it in that block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so like:
existingPayment, err := db.FetchPayment(ctx, paymentHash[:])
switch {
case err == nil:
completePayment, err := s.fetchPaymentWithCompleteData(
ctx, db, existingPayment,
)
if err != nil {
return fmt.Errorf("failed to fetch payment "+
"with complete data: %w", err)
}
// Check if the payment is initializable otherwise
// we'll return early.
err = completePayment.Status.initializable()
if err != nil {
return err
}
// If the initializable check above passes, then the
// existing payment has failed. So we delete it and
// all of its previous artifacts. We rely on
// cascading deletes to clean up the rest.
err = db.DeletePayment(ctx, existingPayment.Payment.ID)
if err != nil {
return fmt.Errorf("failed to delete "+
"payment: %w", err)
}
case !errors.Is(err, sql.ErrNoRows):
// Some other error occurred
return fmt.Errorf("failed to check existing "+
"payment: %w", err)
// The payment does not yet exist.
default:
}
| // Only set the intent ID if it's not nil. | ||
| var intentIDParam sql.NullInt64 | ||
| if intentID != nil { | ||
| intentIDParam = sqldb.SQLInt64(*intentID) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think rather then dont use the sqldb.SQLInt64 helper
var intentID int64
if len(paymentCreationInfo.PaymentRequest) > 0 {
intentID, err = db.InsertPaymentIntent(ctx,
sqlc.InsertPaymentIntentParams{
IntentType: int16(
PaymentIntentTypeBolt11,
),
IntentPayload: paymentCreationInfo.
PaymentRequest,
})
if err != nil {
return fmt.Errorf("failed to initialize "+
"payment intent: %w", err)
}
}
paymentID, err := db.InsertPayment(ctx,
sqlc.InsertPaymentParams{
IntentID: sql.NullInt64{
Int64: intentID,
Valid: intentID != 0,
},
| var mpPayment *MPPayment | ||
|
|
||
| err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { | ||
| // 1. First Fetch the payment and check if it is registrable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: started with numbering and then stopped
| // Add the attempt to the payment without fetching it from the | ||
| // DB again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
| payment, err := db.FetchPayment(ctx, paymentHash[:]) | ||
| if err != nil && !errors.Is(err, sql.ErrNoRows) { | ||
| return fmt.Errorf("failed to fetch payment: %w", err) | ||
| } | ||
| if errors.Is(err, sql.ErrNoRows) { | ||
| return ErrPaymentNotInitiated | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why dont we check Status.updatable() as is done for KVStore?
Depends on #10287
This PR implements most of the Insert and Delete methods we need for the unit tests in the following PR.