From fe25b61c6e152e6245a096b58a6f98e848e660c6 Mon Sep 17 00:00:00 2001 From: tamirms Date: Tue, 8 Oct 2024 23:03:51 +0100 Subject: [PATCH] services/horizon/internal/db2/history: Include lower bound on descending history queries (#5465) --- services/horizon/internal/actions/effects.go | 10 +- services/horizon/internal/actions/ledger.go | 3 +- .../horizon/internal/actions/operation.go | 2 +- services/horizon/internal/actions/trade.go | 9 +- .../horizon/internal/actions/transaction.go | 6 +- .../horizon/internal/db2/history/effect.go | 31 +- .../effect_batch_insert_builder_test.go | 44 +- .../internal/db2/history/effect_test.go | 29 +- .../horizon/internal/db2/history/ledger.go | 6 +- services/horizon/internal/db2/history/main.go | 12 +- .../horizon/internal/db2/history/operation.go | 8 +- .../internal/db2/history/operation_test.go | 134 +++++- .../horizon/internal/db2/history/trade.go | 33 +- .../internal/db2/history/trade_test.go | 28 +- .../internal/db2/history/transaction.go | 7 +- .../internal/db2/history/transaction_test.go | 404 ++++++++++++++---- 16 files changed, 588 insertions(+), 178 deletions(-) diff --git a/services/horizon/internal/actions/effects.go b/services/horizon/internal/actions/effects.go index d8620fc11f..aab7fcb699 100644 --- a/services/horizon/internal/actions/effects.go +++ b/services/horizon/internal/actions/effects.go @@ -72,7 +72,7 @@ func (handler GetEffectsHandler) GetResourcePage(w HeaderWriter, r *http.Request return nil, err } - records, err := loadEffectRecords(r.Context(), historyQ, qp, pq) + records, err := loadEffectRecords(r.Context(), historyQ, qp, pq, handler.LedgerState.CurrentStatus().HistoryElder) if err != nil { return nil, errors.Wrap(err, "loading transaction records") } @@ -94,12 +94,12 @@ func (handler GetEffectsHandler) GetResourcePage(w HeaderWriter, r *http.Request return result, nil } -func loadEffectRecords(ctx context.Context, hq *history.Q, qp EffectsQuery, pq db2.PageQuery) ([]history.Effect, error) { +func loadEffectRecords(ctx context.Context, hq *history.Q, qp EffectsQuery, pq db2.PageQuery, oldestLedger int32) ([]history.Effect, error) { switch { case qp.AccountID != "": - return hq.EffectsForAccount(ctx, qp.AccountID, pq) + return hq.EffectsForAccount(ctx, qp.AccountID, pq, oldestLedger) case qp.LiquidityPoolID != "": - return hq.EffectsForLiquidityPool(ctx, qp.LiquidityPoolID, pq) + return hq.EffectsForLiquidityPool(ctx, qp.LiquidityPoolID, pq, oldestLedger) case qp.OperationID > 0: return hq.EffectsForOperation(ctx, int64(qp.OperationID), pq) case qp.LedgerID > 0: @@ -107,7 +107,7 @@ func loadEffectRecords(ctx context.Context, hq *history.Q, qp EffectsQuery, pq d case qp.TxHash != "": return hq.EffectsForTransaction(ctx, qp.TxHash, pq) default: - return hq.Effects(ctx, pq) + return hq.Effects(ctx, pq, oldestLedger) } } diff --git a/services/horizon/internal/actions/ledger.go b/services/horizon/internal/actions/ledger.go index 0836ba9b10..5fe9c294e4 100644 --- a/services/horizon/internal/actions/ledger.go +++ b/services/horizon/internal/actions/ledger.go @@ -33,7 +33,8 @@ func (handler GetLedgersHandler) GetResourcePage(w HeaderWriter, r *http.Request } var records []history.Ledger - if err = historyQ.Ledgers().Page(pq).Select(r.Context(), &records); err != nil { + err = historyQ.Ledgers().Page(pq, handler.LedgerState.CurrentStatus().HistoryElder).Select(r.Context(), &records) + if err != nil { return nil, err } diff --git a/services/horizon/internal/actions/operation.go b/services/horizon/internal/actions/operation.go index 6b200cba29..f12dc95eaa 100644 --- a/services/horizon/internal/actions/operation.go +++ b/services/horizon/internal/actions/operation.go @@ -122,7 +122,7 @@ func (handler GetOperationsHandler) GetResourcePage(w HeaderWriter, r *http.Requ query.OnlyPayments() } - ops, txs, err := query.Page(pq).Fetch(ctx) + ops, txs, err := query.Page(pq, handler.LedgerState.CurrentStatus().HistoryElder).Fetch(ctx) if err != nil { return nil, err } diff --git a/services/horizon/internal/actions/trade.go b/services/horizon/internal/actions/trade.go index 57d97593cf..40c0fa61de 100644 --- a/services/horizon/internal/actions/trade.go +++ b/services/horizon/internal/actions/trade.go @@ -189,19 +189,20 @@ func (handler GetTradesHandler) GetResourcePage(w HeaderWriter, r *http.Request) return nil, err } + oldestLedger := handler.LedgerState.CurrentStatus().HistoryElder if baseAsset != nil { counterAsset, err = qp.Counter() if err != nil { return nil, err } - records, err = historyQ.GetTradesForAssets(ctx, pq, qp.AccountID, qp.TradeType, *baseAsset, *counterAsset) + records, err = historyQ.GetTradesForAssets(ctx, pq, oldestLedger, qp.AccountID, qp.TradeType, *baseAsset, *counterAsset) } else if qp.OfferID != 0 { - records, err = historyQ.GetTradesForOffer(ctx, pq, int64(qp.OfferID)) + records, err = historyQ.GetTradesForOffer(ctx, pq, oldestLedger, int64(qp.OfferID)) } else if qp.PoolID != "" { - records, err = historyQ.GetTradesForLiquidityPool(ctx, pq, qp.PoolID) + records, err = historyQ.GetTradesForLiquidityPool(ctx, pq, oldestLedger, qp.PoolID) } else { - records, err = historyQ.GetTrades(ctx, pq, qp.AccountID, qp.TradeType) + records, err = historyQ.GetTrades(ctx, pq, oldestLedger, qp.AccountID, qp.TradeType) } if err != nil { return nil, err diff --git a/services/horizon/internal/actions/transaction.go b/services/horizon/internal/actions/transaction.go index ed579f9d6f..c1649e7524 100644 --- a/services/horizon/internal/actions/transaction.go +++ b/services/horizon/internal/actions/transaction.go @@ -119,7 +119,7 @@ func (handler GetTransactionsHandler) GetResourcePage(w HeaderWriter, r *http.Re return nil, err } - records, err := loadTransactionRecords(ctx, historyQ, qp, pq) + records, err := loadTransactionRecords(ctx, historyQ, qp, pq, handler.LedgerState.CurrentStatus().HistoryElder) if err != nil { return nil, errors.Wrap(err, "loading transaction records") } @@ -141,7 +141,7 @@ func (handler GetTransactionsHandler) GetResourcePage(w HeaderWriter, r *http.Re // loadTransactionRecords returns a slice of transaction records of an // account/ledger identified by accountID/ledgerID based on pq and // includeFailedTx. -func loadTransactionRecords(ctx context.Context, hq *history.Q, qp TransactionsQuery, pq db2.PageQuery) ([]history.Transaction, error) { +func loadTransactionRecords(ctx context.Context, hq *history.Q, qp TransactionsQuery, pq db2.PageQuery, oldestLedger int32) ([]history.Transaction, error) { var records []history.Transaction txs := hq.Transactions() @@ -160,7 +160,7 @@ func loadTransactionRecords(ctx context.Context, hq *history.Q, qp TransactionsQ txs.IncludeFailed() } - err := txs.Page(pq).Select(ctx, &records) + err := txs.Page(pq, oldestLedger).Select(ctx, &records) if err != nil { return nil, errors.Wrap(err, "executing transaction records query") } diff --git a/services/horizon/internal/db2/history/effect.go b/services/horizon/internal/db2/history/effect.go index bdf1e2dfb0..0f0b25ec03 100644 --- a/services/horizon/internal/db2/history/effect.go +++ b/services/horizon/internal/db2/history/effect.go @@ -14,6 +14,8 @@ import ( "github.com/stellar/go/toid" ) +const genesisLedger = 2 + // UnmarshalDetails unmarshals the details of this effect into `dest` func (r *Effect) UnmarshalDetails(dest interface{}) error { if !r.DetailsString.Valid { @@ -70,7 +72,7 @@ func (r *Effect) PagingToken() string { } // Effects returns a page of effects without any filters besides the cursor -func (q *Q) Effects(ctx context.Context, page db2.PageQuery) ([]Effect, error) { +func (q *Q) Effects(ctx context.Context, page db2.PageQuery, oldestLedger int32) ([]Effect, error) { op, idx, err := parseEffectsCursor(page) if err != nil { return nil, err @@ -87,6 +89,9 @@ func (q *Q) Effects(ctx context.Context, page db2.PageQuery) ([]Effect, error) { Where("(heff.history_operation_id, heff.order) > (?, ?)", op, idx). OrderBy("heff.history_operation_id asc, heff.order asc") case "desc": + if lowerBound := lowestLedgerBound(oldestLedger); lowerBound > 0 { + query = query.Where("heff.history_operation_id > ?", lowerBound) + } query = query. Where("(heff.history_operation_id, heff.order) < (?, ?)", op, idx). OrderBy("heff.history_operation_id desc, heff.order desc") @@ -101,14 +106,14 @@ func (q *Q) Effects(ctx context.Context, page db2.PageQuery) ([]Effect, error) { } // EffectsForAccount returns a page of effects for a given account -func (q *Q) EffectsForAccount(ctx context.Context, aid string, page db2.PageQuery) ([]Effect, error) { +func (q *Q) EffectsForAccount(ctx context.Context, aid string, page db2.PageQuery, oldestLedger int32) ([]Effect, error) { var account Account if err := q.AccountByAddress(ctx, &account, aid); err != nil { return nil, err } query := selectEffect.Where("heff.history_account_id = ?", account.ID) - return q.selectEffectsPage(ctx, query, page) + return q.selectEffectsPage(ctx, query, page, oldestLedger) } // EffectsForLedger returns a page of effects for a given ledger sequence @@ -125,7 +130,7 @@ func (q *Q) EffectsForLedger(ctx context.Context, seq int32, page db2.PageQuery) start.ToInt64(), end.ToInt64(), ) - return q.selectEffectsPage(ctx, query, page) + return q.selectEffectsPage(ctx, query, page, 0) } // EffectsForOperation returns a page of effects for a given operation id. @@ -138,11 +143,11 @@ func (q *Q) EffectsForOperation(ctx context.Context, id int64, page db2.PageQuer start.ToInt64(), end.ToInt64(), ) - return q.selectEffectsPage(ctx, query, page) + return q.selectEffectsPage(ctx, query, page, 0) } // EffectsForLiquidityPool returns a page of effects for a given liquidity pool. -func (q *Q) EffectsForLiquidityPool(ctx context.Context, id string, page db2.PageQuery) ([]Effect, error) { +func (q *Q) EffectsForLiquidityPool(ctx context.Context, id string, page db2.PageQuery, oldestLedger int32) ([]Effect, error) { op, _, err := page.CursorInt64Pair(db2.DefaultPairSep) if err != nil { return nil, err @@ -173,6 +178,7 @@ func (q *Q) EffectsForLiquidityPool(ctx context.Context, id string, page db2.Pag "heff.history_operation_id": liquidityPoolOperationIDs, }), page, + oldestLedger, ) } @@ -194,6 +200,7 @@ func (q *Q) EffectsForTransaction(ctx context.Context, hash string, page db2.Pag end.ToInt64(), ), page, + 0, ) } @@ -209,7 +216,14 @@ func parseEffectsCursor(page db2.PageQuery) (int64, int64, error) { return op, idx, nil } -func (q *Q) selectEffectsPage(ctx context.Context, query sq.SelectBuilder, page db2.PageQuery) ([]Effect, error) { +func lowestLedgerBound(oldestLedger int32) int64 { + if oldestLedger <= genesisLedger { + return 0 + } + return toid.AfterLedger(oldestLedger - 1).ToInt64() +} + +func (q *Q) selectEffectsPage(ctx context.Context, query sq.SelectBuilder, page db2.PageQuery, oldestLedger int32) ([]Effect, error) { op, idx, err := parseEffectsCursor(page) if err != nil { return nil, err @@ -230,6 +244,9 @@ func (q *Q) selectEffectsPage(ctx context.Context, query sq.SelectBuilder, page ))`, op, op, op, idx). OrderBy("heff.history_operation_id asc, heff.order asc") case "desc": + if lowerBound := lowestLedgerBound(oldestLedger); lowerBound > 0 { + query = query.Where("heff.history_operation_id > ?", lowerBound) + } query = query. Where(`( heff.history_operation_id <= ? diff --git a/services/horizon/internal/db2/history/effect_batch_insert_builder_test.go b/services/horizon/internal/db2/history/effect_batch_insert_builder_test.go index e917983b8f..2a3b0c9ab8 100644 --- a/services/horizon/internal/db2/history/effect_batch_insert_builder_test.go +++ b/services/horizon/internal/db2/history/effect_batch_insert_builder_test.go @@ -2,6 +2,7 @@ package history import ( "encoding/json" + "fmt" "testing" "github.com/guregu/null" @@ -16,7 +17,7 @@ func TestAddEffect(t *testing.T) { defer tt.Finish() test.ResetHorizonDB(t, tt.HorizonDB) q := &Q{tt.HorizonSession()} - tt.Assert.NoError(q.Begin(tt.Ctx)) + tt.Require.NoError(q.Begin(tt.Ctx)) address := "GAQAA5L65LSYH7CQ3VTJ7F3HHLGCL3DSLAR2Y47263D56MNNGHSQSTVY" muxedAddres := "MAQAA5L65LSYH7CQ3VTJ7F3HHLGCL3DSLAR2Y47263D56MNNGHSQSAAAAAAAAAAE2LP26" @@ -37,25 +38,42 @@ func TestAddEffect(t *testing.T) { 3, details, ) - tt.Assert.NoError(err) + tt.Require.NoError(err) - tt.Assert.NoError(accountLoader.Exec(tt.Ctx, q)) - tt.Assert.NoError(builder.Exec(tt.Ctx, q)) - tt.Assert.NoError(q.Commit()) + tt.Require.NoError(accountLoader.Exec(tt.Ctx, q)) + tt.Require.NoError(builder.Exec(tt.Ctx, q)) + tt.Require.NoError(q.Commit()) effects, err := q.Effects(tt.Ctx, db2.PageQuery{ Cursor: "0-0", Order: "asc", Limit: 200, - }) + }, 0) tt.Require.NoError(err) - tt.Assert.Len(effects, 1) + tt.Require.Len(effects, 1) effect := effects[0] - tt.Assert.Equal(address, effect.Account) - tt.Assert.Equal(muxedAddres, effect.AccountMuxed.String) - tt.Assert.Equal(int64(240518172673), effect.HistoryOperationID) - tt.Assert.Equal(int32(1), effect.Order) - tt.Assert.Equal(EffectType(3), effect.Type) - tt.Assert.Equal("{\"amount\": \"1000.0000000\", \"asset_type\": \"native\"}", effect.DetailsString.String) + tt.Require.Equal(address, effect.Account) + tt.Require.Equal(muxedAddres, effect.AccountMuxed.String) + tt.Require.Equal(int64(240518172673), effect.HistoryOperationID) + tt.Require.Equal(int32(1), effect.Order) + tt.Require.Equal(EffectType(3), effect.Type) + tt.Require.Equal("{\"amount\": \"1000.0000000\", \"asset_type\": \"native\"}", effect.DetailsString.String) + + effects, err = q.Effects(tt.Ctx, db2.PageQuery{ + Cursor: fmt.Sprintf("%d-0", toid.New(sequence+2, 0, 0).ToInt64()), + Order: "desc", + Limit: 200, + }, sequence-3) + tt.Require.NoError(err) + tt.Require.Len(effects, 1) + tt.Require.Equal(effects[0], effect) + + effects, err = q.Effects(tt.Ctx, db2.PageQuery{ + Cursor: fmt.Sprintf("%d-0", toid.New(sequence+5, 0, 0).ToInt64()), + Order: "desc", + Limit: 200, + }, sequence+2) + tt.Require.NoError(err) + tt.Require.Empty(effects) } diff --git a/services/horizon/internal/db2/history/effect_test.go b/services/horizon/internal/db2/history/effect_test.go index ba59cf3fd4..e3eb79d941 100644 --- a/services/horizon/internal/db2/history/effect_test.go +++ b/services/horizon/internal/db2/history/effect_test.go @@ -56,17 +56,34 @@ func TestEffectsForLiquidityPool(t *testing.T) { tt.Assert.NoError(q.Commit()) - var result []Effect - result, err = q.EffectsForLiquidityPool(tt.Ctx, liquidityPoolID, db2.PageQuery{ + var effects []Effect + effects, err = q.EffectsForLiquidityPool(tt.Ctx, liquidityPoolID, db2.PageQuery{ Cursor: "0-0", Order: "asc", Limit: 10, - }) + }, 0) tt.Assert.NoError(err) - tt.Assert.Len(result, 1) - tt.Assert.Equal(result[0].Account, address) + tt.Assert.Len(effects, 1) + effect := effects[0] + tt.Assert.Equal(effect.Account, address) + + effects, err = q.EffectsForLiquidityPool(tt.Ctx, liquidityPoolID, db2.PageQuery{ + Cursor: fmt.Sprintf("%d-0", toid.New(sequence+2, 0, 0).ToInt64()), + Order: "desc", + Limit: 200, + }, sequence-3) + tt.Require.NoError(err) + tt.Require.Len(effects, 1) + tt.Require.Equal(effects[0], effect) + effects, err = q.EffectsForLiquidityPool(tt.Ctx, liquidityPoolID, db2.PageQuery{ + Cursor: fmt.Sprintf("%d-0", toid.New(sequence+5, 0, 0).ToInt64()), + Order: "desc", + Limit: 200, + }, sequence+2) + tt.Require.NoError(err) + tt.Require.Empty(effects) } func TestEffectsForTrustlinesSponsorshipEmptyAssetType(t *testing.T) { @@ -160,7 +177,7 @@ func TestEffectsForTrustlinesSponsorshipEmptyAssetType(t *testing.T) { Cursor: "0-0", Order: "asc", Limit: 200, - }) + }, 0) tt.Require.NoError(err) tt.Require.Len(results, len(tests)) diff --git a/services/horizon/internal/db2/history/ledger.go b/services/horizon/internal/db2/history/ledger.go index c2d1f6b3c9..5059cb7f49 100644 --- a/services/horizon/internal/db2/history/ledger.go +++ b/services/horizon/internal/db2/history/ledger.go @@ -72,11 +72,15 @@ func (q *Q) LedgerCapacityUsageStats(ctx context.Context, currentSeq int32, dest } // Page specifies the paging constraints for the query being built by `q`. -func (q *LedgersQ) Page(page db2.PageQuery) *LedgersQ { +func (q *LedgersQ) Page(page db2.PageQuery, oldestLedger int32) *LedgersQ { if q.Err != nil { return q } + if lowerBound := lowestLedgerBound(oldestLedger); lowerBound > 0 && page.Order == "desc" { + q.sql = q.sql. + Where("hl.id > ?", lowerBound) + } q.sql, q.Err = page.ApplyTo(q.sql, "hl.id") return q } diff --git a/services/horizon/internal/db2/history/main.go b/services/horizon/internal/db2/history/main.go index 59d828d091..7cb7097508 100644 --- a/services/horizon/internal/db2/history/main.go +++ b/services/horizon/internal/db2/history/main.go @@ -790,6 +790,7 @@ type OperationsQ struct { opIdCol string includeFailed bool includeTransactions bool + boundedIdQuery bool } // Q is a helper struct on which to hang common_trades queries against a history @@ -878,11 +879,12 @@ func (t *Transaction) HasPreconditions() bool { // TransactionsQ is a helper struct to aid in configuring queries that loads // slices of transaction structs. type TransactionsQ struct { - Err error - parent *Q - sql sq.SelectBuilder - includeFailed bool - txIdCol string + Err error + parent *Q + sql sq.SelectBuilder + includeFailed bool + txIdCol string + boundedIdQuery bool } // TrustLine is row of data from the `trust_lines` table from horizon DB diff --git a/services/horizon/internal/db2/history/operation.go b/services/horizon/internal/db2/history/operation.go index 04a6d00f50..02a72add6f 100644 --- a/services/horizon/internal/db2/history/operation.go +++ b/services/horizon/internal/db2/history/operation.go @@ -210,6 +210,7 @@ func (q *OperationsQ) ForLedger(ctx context.Context, seq int32) *OperationsQ { start.ToInt64(), end.ToInt64(), ) + q.boundedIdQuery = true return q } @@ -231,6 +232,7 @@ func (q *OperationsQ) ForTransaction(ctx context.Context, hash string) *Operatio start.ToInt64(), end.ToInt64(), ) + q.boundedIdQuery = true return q } @@ -266,11 +268,15 @@ func (q *OperationsQ) IncludeTransactions() *OperationsQ { } // Page specifies the paging constraints for the query being built by `q`. -func (q *OperationsQ) Page(page db2.PageQuery) *OperationsQ { +func (q *OperationsQ) Page(page db2.PageQuery, oldestLedger int32) *OperationsQ { if q.Err != nil { return q } + if lowerBound := lowestLedgerBound(oldestLedger); !q.boundedIdQuery && lowerBound > 0 && page.Order == "desc" { + q.sql = q.sql. + Where(q.opIdCol+" > ?", lowerBound) + } q.sql, q.Err = page.ApplyTo(q.sql, q.opIdCol) return q } diff --git a/services/horizon/internal/db2/history/operation_test.go b/services/horizon/internal/db2/history/operation_test.go index 8899bfcdf6..341751347a 100644 --- a/services/horizon/internal/db2/history/operation_test.go +++ b/services/horizon/internal/db2/history/operation_test.go @@ -141,7 +141,7 @@ func TestOperationByLiquidityPool(t *testing.T) { Order: "asc", Limit: 2, } - ops, _, err := q.Operations().ForLiquidityPool(tt.Ctx, liquidityPoolID).Page(pq).Fetch(tt.Ctx) + ops, _, err := q.Operations().ForLiquidityPool(tt.Ctx, liquidityPoolID).Page(pq, 0).Fetch(tt.Ctx) tt.Assert.NoError(err) tt.Assert.Len(ops, 2) tt.Assert.Equal(ops[0].ID, opID1) @@ -149,7 +149,7 @@ func TestOperationByLiquidityPool(t *testing.T) { // Check descending order pq.Order = "desc" - ops, _, err = q.Operations().ForLiquidityPool(tt.Ctx, liquidityPoolID).Page(pq).Fetch(tt.Ctx) + ops, _, err = q.Operations().ForLiquidityPool(tt.Ctx, liquidityPoolID).Page(pq, 0).Fetch(tt.Ctx) tt.Assert.NoError(err) tt.Assert.Len(ops, 2) tt.Assert.Equal(ops[0].ID, opID2) @@ -162,23 +162,119 @@ func TestOperationQueryBuilder(t *testing.T) { defer tt.Finish() q := &Q{tt.HorizonSession()} - opsQ := q.Operations().ForAccount(tt.Ctx, "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON").Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}) - tt.Assert.NoError(opsQ.Err) - got, _, err := opsQ.sql.ToSql() - tt.Assert.NoError(err) - - // Operations for account queries will use hopp.history_operation_id in their predicates. - want := "SELECT hop.id, hop.transaction_id, hop.application_order, hop.type, hop.details, hop.source_account, hop.source_account_muxed, COALESCE(hop.is_payment, false) as is_payment, ht.transaction_hash, ht.tx_result, COALESCE(ht.successful, true) as transaction_successful FROM history_operations hop LEFT JOIN history_transactions ht ON ht.id = hop.transaction_id JOIN history_operation_participants hopp ON hopp.history_operation_id = hop.id WHERE hopp.history_account_id = ? AND hopp.history_operation_id > ? ORDER BY hopp.history_operation_id asc LIMIT 10" - tt.Assert.EqualValues(want, got) - - opsQ = q.Operations().ForLedger(tt.Ctx, 2).Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}) - tt.Assert.NoError(opsQ.Err) - got, _, err = opsQ.sql.ToSql() - tt.Assert.NoError(err) - - // Other operation queries will use hop.id in their predicates. - want = "SELECT hop.id, hop.transaction_id, hop.application_order, hop.type, hop.details, hop.source_account, hop.source_account_muxed, COALESCE(hop.is_payment, false) as is_payment, ht.transaction_hash, ht.tx_result, COALESCE(ht.successful, true) as transaction_successful FROM history_operations hop LEFT JOIN history_transactions ht ON ht.id = hop.transaction_id WHERE hop.id >= ? AND hop.id < ? AND hop.id > ? ORDER BY hop.id asc LIMIT 10" - tt.Assert.EqualValues(want, got) + for _, testCase := range []struct { + q *OperationsQ + expectedSQL string + expectedArgs []interface{} + }{ + { + q.Operations().ForAccount(tt.Ctx, "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"). + Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}, 50), + "SELECT " + + "hop.id, hop.transaction_id, hop.application_order, hop.type, hop.details, hop.source_account, " + + "hop.source_account_muxed, COALESCE(hop.is_payment, false) as is_payment, ht.transaction_hash, " + + "ht.tx_result, COALESCE(ht.successful, true) as transaction_successful " + + "FROM history_operations hop " + + "LEFT JOIN history_transactions ht ON ht.id = hop.transaction_id " + + "JOIN history_operation_participants hopp ON hopp.history_operation_id = hop.id " + + "WHERE hopp.history_account_id = ? AND " + + "hopp.history_operation_id > ? " + + "ORDER BY hopp.history_operation_id asc LIMIT 10", + []interface{}{ + int64(2), + int64(8589938689), + }, + }, + { + q.Operations().ForAccount(tt.Ctx, "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"). + Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 50), + "SELECT " + + "hop.id, hop.transaction_id, hop.application_order, hop.type, hop.details, hop.source_account, " + + "hop.source_account_muxed, COALESCE(hop.is_payment, false) as is_payment, ht.transaction_hash, " + + "ht.tx_result, COALESCE(ht.successful, true) as transaction_successful " + + "FROM history_operations hop " + + "LEFT JOIN history_transactions ht ON ht.id = hop.transaction_id " + + "JOIN history_operation_participants hopp ON hopp.history_operation_id = hop.id " + + "WHERE hopp.history_account_id = ? AND " + + "hopp.history_operation_id > ? AND " + + "hopp.history_operation_id < ? " + + "ORDER BY hopp.history_operation_id desc LIMIT 10", + []interface{}{ + int64(2), + int64(214748364799), + int64(8589938689), + }, + }, + { + q.Operations().ForAccount(tt.Ctx, "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"). + Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 0), + "SELECT " + + "hop.id, hop.transaction_id, hop.application_order, hop.type, hop.details, hop.source_account, " + + "hop.source_account_muxed, COALESCE(hop.is_payment, false) as is_payment, ht.transaction_hash, " + + "ht.tx_result, COALESCE(ht.successful, true) as transaction_successful " + + "FROM history_operations hop " + + "LEFT JOIN history_transactions ht ON ht.id = hop.transaction_id " + + "JOIN history_operation_participants hopp ON hopp.history_operation_id = hop.id " + + "WHERE hopp.history_account_id = ? AND " + + "hopp.history_operation_id < ? " + + "ORDER BY hopp.history_operation_id desc LIMIT 10", + []interface{}{ + int64(2), + int64(8589938689), + }, + }, + { + q.Operations().ForLedger(tt.Ctx, 2). + Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}, 50), + "SELECT " + + "hop.id, hop.transaction_id, hop.application_order, hop.type, hop.details, hop.source_account, " + + "hop.source_account_muxed, COALESCE(hop.is_payment, false) as is_payment, ht.transaction_hash, " + + "ht.tx_result, COALESCE(ht.successful, true) as transaction_successful FROM history_operations " + + "hop LEFT JOIN history_transactions ht ON ht.id = hop.transaction_id " + + "WHERE hop.id >= ? AND hop.id < ? AND hop.id > ? ORDER BY hop.id asc LIMIT 10", + []interface{}{ + int64(8589934592), + int64(12884901888), + int64(8589938689), + }, + }, + { + q.Operations().ForLedger(tt.Ctx, 2). + Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 50), + "SELECT " + + "hop.id, hop.transaction_id, hop.application_order, hop.type, hop.details, hop.source_account, " + + "hop.source_account_muxed, COALESCE(hop.is_payment, false) as is_payment, ht.transaction_hash, " + + "ht.tx_result, COALESCE(ht.successful, true) as transaction_successful FROM history_operations " + + "hop LEFT JOIN history_transactions ht ON ht.id = hop.transaction_id " + + "WHERE hop.id >= ? AND hop.id < ? AND hop.id < ? ORDER BY hop.id desc LIMIT 10", + []interface{}{ + int64(8589934592), + int64(12884901888), + int64(8589938689), + }, + }, + { + q.Operations().ForLedger(tt.Ctx, 2). + Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 0), + "SELECT " + + "hop.id, hop.transaction_id, hop.application_order, hop.type, hop.details, hop.source_account, " + + "hop.source_account_muxed, COALESCE(hop.is_payment, false) as is_payment, ht.transaction_hash, " + + "ht.tx_result, COALESCE(ht.successful, true) as transaction_successful FROM history_operations " + + "hop LEFT JOIN history_transactions ht ON ht.id = hop.transaction_id " + + "WHERE hop.id >= ? AND hop.id < ? AND hop.id < ? ORDER BY hop.id desc LIMIT 10", + []interface{}{ + int64(8589934592), + int64(12884901888), + int64(8589938689), + }, + }, + } { + tt.Assert.NoError(testCase.q.Err) + got, args, err := testCase.q.sql.ToSql() + tt.Assert.NoError(err) + tt.Assert.Equal(got, testCase.expectedSQL) + tt.Assert.Equal(args, testCase.expectedArgs) + } } // TestOperationSuccessfulOnly tests if default query returns operations in diff --git a/services/horizon/internal/db2/history/trade.go b/services/horizon/internal/db2/history/trade.go index 6d8a7fca56..07ee5f6cfb 100644 --- a/services/horizon/internal/db2/history/trade.go +++ b/services/horizon/internal/db2/history/trade.go @@ -39,36 +39,36 @@ type tradesQuery struct { } func (q *Q) GetTrades( - ctx context.Context, page db2.PageQuery, account string, tradeType string, + ctx context.Context, page db2.PageQuery, oldestLedger int32, account string, tradeType string, ) ([]Trade, error) { - return q.getTrades(ctx, page, tradesQuery{ + return q.getTrades(ctx, page, oldestLedger, tradesQuery{ account: account, tradeType: tradeType, }) } func (q *Q) GetTradesForOffer( - ctx context.Context, page db2.PageQuery, offerID int64, + ctx context.Context, page db2.PageQuery, oldestLedger int32, offerID int64, ) ([]Trade, error) { - return q.getTrades(ctx, page, tradesQuery{ + return q.getTrades(ctx, page, oldestLedger, tradesQuery{ offer: offerID, tradeType: AllTrades, }) } func (q *Q) GetTradesForLiquidityPool( - ctx context.Context, page db2.PageQuery, poolID string, + ctx context.Context, page db2.PageQuery, oldestLedger int32, poolID string, ) ([]Trade, error) { - return q.getTrades(ctx, page, tradesQuery{ + return q.getTrades(ctx, page, oldestLedger, tradesQuery{ liquidityPool: poolID, tradeType: AllTrades, }) } func (q *Q) GetTradesForAssets( - ctx context.Context, page db2.PageQuery, account, tradeType string, baseAsset, counterAsset xdr.Asset, + ctx context.Context, page db2.PageQuery, oldestLedger int32, account, tradeType string, baseAsset, counterAsset xdr.Asset, ) ([]Trade, error) { - return q.getTrades(ctx, page, tradesQuery{ + return q.getTrades(ctx, page, oldestLedger, tradesQuery{ account: account, baseAsset: &baseAsset, counterAsset: &counterAsset, @@ -86,7 +86,7 @@ type historyTradesQuery struct { tradeType string } -func (q *Q) getTrades(ctx context.Context, page db2.PageQuery, query tradesQuery) ([]Trade, error) { +func (q *Q) getTrades(ctx context.Context, page db2.PageQuery, oldestLedger int32, query tradesQuery) ([]Trade, error) { // Add explicit query type for prometheus metrics, since we use raw sql. ctx = context.WithValue(ctx, &db.QueryTypeContextKey, db.SelectQueryType) @@ -94,7 +94,7 @@ func (q *Q) getTrades(ctx context.Context, page db2.PageQuery, query tradesQuery if err != nil { return nil, errors.Wrap(err, "invalid trade query") } - rawSQL, args, err := createTradesSQL(page, internalTradesQuery) + rawSQL, args, err := createTradesSQL(page, oldestLedger, internalTradesQuery) if err != nil { return nil, errors.Wrap(err, "could not create trades sql query") } @@ -149,7 +149,7 @@ func (q *Q) transformTradesQuery(ctx context.Context, query tradesQuery) (histor return internalQuery, nil } -func createTradesSQL(page db2.PageQuery, query historyTradesQuery) (string, []interface{}, error) { +func createTradesSQL(page db2.PageQuery, oldestLedger int32, query historyTradesQuery) (string, []interface{}, error) { base := selectTradeFields if !query.orderPreserved { base = selectReverseTradeFields @@ -204,8 +204,8 @@ func createTradesSQL(page db2.PageQuery, query historyTradesQuery) (string, []in secondSelect = sql.Where("htrd.counter_liquidity_pool_id = ?", query.poolID) } - firstSelect = appendOrdering(firstSelect, op, idx, page.Order) - secondSelect = appendOrdering(secondSelect, op, idx, page.Order) + firstSelect = appendOrdering(firstSelect, oldestLedger, op, idx, page.Order) + secondSelect = appendOrdering(secondSelect, oldestLedger, op, idx, page.Order) firstSQL, firstArgs, err := firstSelect.ToSql() if err != nil { return "", nil, errors.Wrap(err, "error building a firstSelect query") @@ -229,7 +229,7 @@ func createTradesSQL(page db2.PageQuery, query historyTradesQuery) (string, []in rawSQL = rawSQL + fmt.Sprintf("LIMIT %d", page.Limit) return rawSQL, args, nil } else { - sql = appendOrdering(sql, op, idx, page.Order) + sql = appendOrdering(sql, oldestLedger, op, idx, page.Order) sql = sql.Limit(page.Limit) rawSQL, args, err := sql.ToSql() if err != nil { @@ -239,7 +239,7 @@ func createTradesSQL(page db2.PageQuery, query historyTradesQuery) (string, []in } } -func appendOrdering(sel sq.SelectBuilder, op, idx int64, order string) sq.SelectBuilder { +func appendOrdering(sel sq.SelectBuilder, oldestLedger int32, op, idx int64, order string) sq.SelectBuilder { // NOTE: Remember to test the queries below with EXPLAIN / EXPLAIN ANALYZE // before changing them. // This condition is using multicolumn index and it's easy to write it in a way that @@ -255,6 +255,9 @@ func appendOrdering(sel sq.SelectBuilder, op, idx int64, order string) sq.Select ))`, op, op, op, idx). OrderBy("htrd.history_operation_id asc, htrd.order asc") case "desc": + if lowerBound := lowestLedgerBound(oldestLedger); lowerBound > 0 { + sel = sel.Where("htrd.history_operation_id > ?", lowerBound) + } return sel. Where(`( htrd.history_operation_id <= ? diff --git a/services/horizon/internal/db2/history/trade_test.go b/services/horizon/internal/db2/history/trade_test.go index e91a1f7b4f..157142e296 100644 --- a/services/horizon/internal/db2/history/trade_test.go +++ b/services/horizon/internal/db2/history/trade_test.go @@ -1,9 +1,11 @@ package history import ( - "github.com/stellar/go/xdr" "testing" + "github.com/stellar/go/toid" + "github.com/stellar/go/xdr" + "github.com/stellar/go/services/horizon/internal/db2" "github.com/stellar/go/services/horizon/internal/test" ) @@ -45,16 +47,18 @@ func TestSelectTrades(t *testing.T) { test.ResetHorizonDB(t, tt.HorizonDB) q := &Q{tt.HorizonSession()} fixtures := TradeScenario(tt, q) + afterTradesSeq := toid.Parse(fixtures.Trades[0].HistoryOperationID).LedgerSequence + 1 + beforeTradesSeq := afterTradesSeq - 2 for _, account := range append([]string{allAccounts}, fixtures.Addresses...) { for _, tradeType := range []string{AllTrades, OrderbookTrades, LiquidityPoolTrades} { expected := filterByAccount(FilterTradesByType(fixtures.Trades, tradeType), account) - rows, err := q.GetTrades(tt.Ctx, ascPQ, account, tradeType) + rows, err := q.GetTrades(tt.Ctx, ascPQ, 0, account, tradeType) tt.Assert.NoError(err) assertTradesAreEqual(tt, expected, rows) - rows, err = q.GetTrades(tt.Ctx, descPQ, account, tradeType) + rows, err = q.GetTrades(tt.Ctx, descPQ, beforeTradesSeq, account, tradeType) tt.Assert.NoError(err) start, end := 0, len(rows)-1 for start < end { @@ -64,6 +68,10 @@ func TestSelectTrades(t *testing.T) { } assertTradesAreEqual(tt, expected, rows) + + rows, err = q.GetTrades(tt.Ctx, descPQ, afterTradesSeq, account, tradeType) + tt.Assert.NoError(err) + tt.Assert.Empty(rows) } } } @@ -85,6 +93,7 @@ func TestSelectTradesCursor(t *testing.T) { rows, err := q.GetTrades( tt.Ctx, db2.MustPageQuery(expected[0].PagingToken(), false, "asc", 100), + 0, account, tradeType, ) @@ -98,6 +107,7 @@ func TestSelectTradesCursor(t *testing.T) { rows, err = q.GetTrades( tt.Ctx, db2.MustPageQuery(expected[1].PagingToken(), false, "asc", 100), + 0, account, tradeType, ) @@ -116,13 +126,14 @@ func TestTradesQueryForOffer(t *testing.T) { tt.Assert.NotEmpty(fixtures.TradesByOffer) for offer, expected := range fixtures.TradesByOffer { - trades, err := q.GetTradesForOffer(tt.Ctx, ascPQ, offer) + trades, err := q.GetTradesForOffer(tt.Ctx, ascPQ, 0, offer) tt.Assert.NoError(err) assertTradesAreEqual(tt, expected, trades) trades, err = q.GetTradesForOffer( tt.Ctx, db2.MustPageQuery(expected[0].PagingToken(), false, "asc", 100), + 0, offer, ) tt.Assert.NoError(err) @@ -139,13 +150,14 @@ func TestTradesQueryForLiquidityPool(t *testing.T) { tt.Assert.NotEmpty(fixtures.TradesByOffer) for poolID, expected := range fixtures.TradesByPool { - trades, err := q.GetTradesForLiquidityPool(tt.Ctx, ascPQ, poolID) + trades, err := q.GetTradesForLiquidityPool(tt.Ctx, ascPQ, 0, poolID) tt.Assert.NoError(err) assertTradesAreEqual(tt, expected, trades) trades, err = q.GetTradesForLiquidityPool( tt.Ctx, db2.MustPageQuery(expected[0].PagingToken(), false, "asc", 100), + 0, poolID, ) tt.Assert.NoError(err) @@ -167,7 +179,7 @@ func TestTradesForAssetPair(t *testing.T) { for _, tradeType := range []string{AllTrades, OrderbookTrades, LiquidityPoolTrades} { expected := filterByAccount(FilterTradesByType(allTrades, tradeType), account) - trades, err := q.GetTradesForAssets(tt.Ctx, ascPQ, account, tradeType, chfAsset, eurAsset) + trades, err := q.GetTradesForAssets(tt.Ctx, ascPQ, 0, account, tradeType, chfAsset, eurAsset) tt.Assert.NoError(err) assertTradesAreEqual(tt, expected, trades) @@ -178,6 +190,7 @@ func TestTradesForAssetPair(t *testing.T) { trades, err = q.GetTradesForAssets( tt.Ctx, db2.MustPageQuery(expected[0].PagingToken(), false, "asc", 100), + 0, account, tradeType, chfAsset, @@ -219,7 +232,7 @@ func TestTradesForReverseAssetPair(t *testing.T) { expected[i] = reverseTrade(expected[i]) } - trades, err := q.GetTradesForAssets(tt.Ctx, ascPQ, account, tradeType, eurAsset, chfAsset) + trades, err := q.GetTradesForAssets(tt.Ctx, ascPQ, 0, account, tradeType, eurAsset, chfAsset) tt.Assert.NoError(err) assertTradesAreEqual(tt, expected, trades) @@ -230,6 +243,7 @@ func TestTradesForReverseAssetPair(t *testing.T) { trades, err = q.GetTradesForAssets( tt.Ctx, db2.MustPageQuery(expected[0].PagingToken(), false, "asc", 100), + 0, account, tradeType, eurAsset, diff --git a/services/horizon/internal/db2/history/transaction.go b/services/horizon/internal/db2/history/transaction.go index 8e349f5f9d..ca0f08bb21 100644 --- a/services/horizon/internal/db2/history/transaction.go +++ b/services/horizon/internal/db2/history/transaction.go @@ -163,6 +163,7 @@ func (q *TransactionsQ) ForLedger(ctx context.Context, seq int32) *TransactionsQ start.ToInt64(), end.ToInt64(), ) + q.boundedIdQuery = true return q } @@ -174,11 +175,15 @@ func (q *TransactionsQ) IncludeFailed() *TransactionsQ { } // Page specifies the paging constraints for the query being built by `q`. -func (q *TransactionsQ) Page(page db2.PageQuery) *TransactionsQ { +func (q *TransactionsQ) Page(page db2.PageQuery, oldestLedger int32) *TransactionsQ { if q.Err != nil { return q } + if lowerBound := lowestLedgerBound(oldestLedger); !q.boundedIdQuery && lowerBound > 0 && page.Order == "desc" { + q.sql = q.sql. + Where(q.txIdCol+" > ?", lowerBound) + } q.sql, q.Err = page.ApplyTo(q.sql, q.txIdCol) return q } diff --git a/services/horizon/internal/db2/history/transaction_test.go b/services/horizon/internal/db2/history/transaction_test.go index bd8cb1673c..1a6497f41a 100644 --- a/services/horizon/internal/db2/history/transaction_test.go +++ b/services/horizon/internal/db2/history/transaction_test.go @@ -957,93 +957,319 @@ func TestTransactionQueryBuilder(t *testing.T) { tt.Assert.NoError(q.Commit()) - txQ := q.Transactions().ForAccount(tt.Ctx, address).Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}) - tt.Assert.NoError(txQ.Err) - got, _, err := txQ.sql.ToSql() - tt.Assert.NoError(err) - // Transactions for account queries will use - // history_transaction_participants.history_transaction_id in their predicates. - want := "SELECT " + - "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + - "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + - "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + - "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + - "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + - "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + - "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + - "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + - "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + - "FROM history_transactions ht " + - "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + - "JOIN history_transaction_participants htp ON htp.history_transaction_id = ht.id " + - "WHERE htp.history_account_id = ? AND htp.history_transaction_id > ? " + - "ORDER BY htp.history_transaction_id asc LIMIT 10" - tt.Assert.EqualValues(want, got) - - txQ = q.Transactions().ForClaimableBalance(tt.Ctx, cbID).Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}) - tt.Assert.NoError(txQ.Err) - got, _, err = txQ.sql.ToSql() - tt.Assert.NoError(err) - // Transactions for claimable balance queries will use - // history_transaction_claimable_balances.history_transaction_id in their predicates. - want = "SELECT " + - "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + - "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + - "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + - "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + - "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + - "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + - "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + - "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + - "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + - "FROM history_transactions ht " + - "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + - "JOIN history_transaction_claimable_balances htcb ON htcb.history_transaction_id = ht.id " + - "WHERE htcb.history_claimable_balance_id = ? AND htcb.history_transaction_id > ? " + - "ORDER BY htcb.history_transaction_id asc LIMIT 10" - tt.Assert.EqualValues(want, got) - - txQ = q.Transactions().ForLiquidityPool(tt.Ctx, lpID).Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}) - tt.Assert.NoError(txQ.Err) - got, _, err = txQ.sql.ToSql() - tt.Assert.NoError(err) - // Transactions for liquidity pool queries will use - // history_transaction_liquidity_pools.history_transaction_id in their predicates. - want = "SELECT " + - "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + - "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + - "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + - "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + - "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + - "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + - "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + - "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + - "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + - "FROM history_transactions ht " + - "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + - "JOIN history_transaction_liquidity_pools htlp ON htlp.history_transaction_id = ht.id " + - "WHERE htlp.history_liquidity_pool_id = ? AND htlp.history_transaction_id > ? " + - "ORDER BY htlp.history_transaction_id asc LIMIT 10" - tt.Assert.EqualValues(want, got) - - txQ = q.Transactions().Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}) - tt.Assert.NoError(txQ.Err) - got, _, err = txQ.sql.ToSql() - tt.Assert.NoError(err) - // Other Transaction queries will use history_transactions.id in their predicates. - want = "SELECT " + - "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + - "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + - "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + - "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + - "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + - "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + - "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + - "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + - "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + - "FROM history_transactions ht " + - "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + - "WHERE ht.id > ? " + - "ORDER BY ht.id asc LIMIT 10" - tt.Assert.EqualValues(want, got) + for _, testCase := range []struct { + q *TransactionsQ + expectedSQL string + expectedArgs []interface{} + }{ + { + q.Transactions().ForAccount(tt.Ctx, address).Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}, 0), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_participants htp ON htp.history_transaction_id = ht.id " + + "WHERE htp.history_account_id = ? AND htp.history_transaction_id > ? " + + "ORDER BY htp.history_transaction_id asc LIMIT 10", + []interface{}{int64(1), int64(8589938689)}, + }, + { + q.Transactions().ForClaimableBalance(tt.Ctx, cbID).Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}, 0), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_claimable_balances htcb ON htcb.history_transaction_id = ht.id " + + "WHERE htcb.history_claimable_balance_id = ? AND htcb.history_transaction_id > ? " + + "ORDER BY htcb.history_transaction_id asc LIMIT 10", + []interface{}{int64(1), int64(8589938689)}, + }, + { + q.Transactions().ForLiquidityPool(tt.Ctx, lpID).Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}, 0), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_liquidity_pools htlp ON htlp.history_transaction_id = ht.id " + + "WHERE htlp.history_liquidity_pool_id = ? AND htlp.history_transaction_id > ? " + + "ORDER BY htlp.history_transaction_id asc LIMIT 10", + []interface{}{int64(1), int64(8589938689)}, + }, + { + q.Transactions().Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}, 0), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "WHERE ht.id > ? " + + "ORDER BY ht.id asc LIMIT 10", + []interface{}{int64(8589938689)}, + }, + { + q.Transactions().ForAccount(tt.Ctx, address).Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}, 100), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_participants htp ON htp.history_transaction_id = ht.id " + + "WHERE htp.history_account_id = ? AND htp.history_transaction_id > ? " + + "ORDER BY htp.history_transaction_id asc LIMIT 10", + []interface{}{int64(1), int64(8589938689)}, + }, + { + q.Transactions().ForClaimableBalance(tt.Ctx, cbID).Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}, 100), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_claimable_balances htcb ON htcb.history_transaction_id = ht.id " + + "WHERE htcb.history_claimable_balance_id = ? AND htcb.history_transaction_id > ? " + + "ORDER BY htcb.history_transaction_id asc LIMIT 10", + []interface{}{int64(1), int64(8589938689)}, + }, + { + q.Transactions().ForLiquidityPool(tt.Ctx, lpID).Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}, 100), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_liquidity_pools htlp ON htlp.history_transaction_id = ht.id " + + "WHERE htlp.history_liquidity_pool_id = ? AND htlp.history_transaction_id > ? " + + "ORDER BY htlp.history_transaction_id asc LIMIT 10", + []interface{}{int64(1), int64(8589938689)}, + }, + { + q.Transactions().Page(db2.PageQuery{Cursor: "8589938689", Order: "asc", Limit: 10}, 100), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "WHERE ht.id > ? " + + "ORDER BY ht.id asc LIMIT 10", + []interface{}{int64(8589938689)}, + }, + { + q.Transactions().ForAccount(tt.Ctx, address).Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 0), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_participants htp ON htp.history_transaction_id = ht.id " + + "WHERE htp.history_account_id = ? AND htp.history_transaction_id < ? " + + "ORDER BY htp.history_transaction_id desc LIMIT 10", + []interface{}{int64(1), int64(8589938689)}, + }, + { + q.Transactions().ForClaimableBalance(tt.Ctx, cbID).Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 0), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_claimable_balances htcb ON htcb.history_transaction_id = ht.id " + + "WHERE htcb.history_claimable_balance_id = ? AND htcb.history_transaction_id < ? " + + "ORDER BY htcb.history_transaction_id desc LIMIT 10", + []interface{}{int64(1), int64(8589938689)}, + }, + { + q.Transactions().ForLiquidityPool(tt.Ctx, lpID).Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 0), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_liquidity_pools htlp ON htlp.history_transaction_id = ht.id " + + "WHERE htlp.history_liquidity_pool_id = ? AND htlp.history_transaction_id < ? " + + "ORDER BY htlp.history_transaction_id desc LIMIT 10", + []interface{}{int64(1), int64(8589938689)}, + }, + { + q.Transactions().Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 0), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "WHERE ht.id < ? " + + "ORDER BY ht.id desc LIMIT 10", + []interface{}{int64(8589938689)}, + }, + { + q.Transactions().ForAccount(tt.Ctx, address).Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 100), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_participants htp ON htp.history_transaction_id = ht.id " + + "WHERE htp.history_account_id = ? AND htp.history_transaction_id > ? " + + "AND htp.history_transaction_id < ? " + + "ORDER BY htp.history_transaction_id desc LIMIT 10", + []interface{}{int64(1), int64(429496729599), int64(8589938689)}, + }, + { + q.Transactions().ForClaimableBalance(tt.Ctx, cbID).Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 100), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_claimable_balances htcb ON htcb.history_transaction_id = ht.id " + + "WHERE htcb.history_claimable_balance_id = ? AND htcb.history_transaction_id > ? " + + "AND htcb.history_transaction_id < ? " + + "ORDER BY htcb.history_transaction_id desc LIMIT 10", + []interface{}{int64(1), int64(429496729599), int64(8589938689)}, + }, + { + q.Transactions().ForLiquidityPool(tt.Ctx, lpID).Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 100), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "JOIN history_transaction_liquidity_pools htlp ON htlp.history_transaction_id = ht.id " + + "WHERE htlp.history_liquidity_pool_id = ? AND htlp.history_transaction_id > ? " + + "AND htlp.history_transaction_id < ? " + + "ORDER BY htlp.history_transaction_id desc LIMIT 10", + []interface{}{int64(1), int64(429496729599), int64(8589938689)}, + }, + { + q.Transactions().Page(db2.PageQuery{Cursor: "8589938689", Order: "desc", Limit: 10}, 100), + "SELECT " + + "ht.id, ht.transaction_hash, ht.ledger_sequence, ht.application_order, " + + "ht.account, ht.account_muxed, ht.account_sequence, ht.max_fee, " + + "COALESCE(ht.fee_charged, ht.max_fee) as fee_charged, ht.operation_count, " + + "ht.tx_envelope, ht.tx_result, ht.tx_meta, ht.tx_fee_meta, ht.created_at, " + + "ht.updated_at, COALESCE(ht.successful, true) as successful, ht.signatures, " + + "ht.memo_type, ht.memo, ht.time_bounds, ht.ledger_bounds, ht.min_account_sequence, " + + "ht.min_account_sequence_age, ht.min_account_sequence_ledger_gap, ht.extra_signers, " + + "hl.closed_at AS ledger_close_time, ht.inner_transaction_hash, ht.fee_account, " + + "ht.fee_account_muxed, ht.new_max_fee, ht.inner_signatures " + + "FROM history_transactions ht " + + "LEFT JOIN history_ledgers hl ON ht.ledger_sequence = hl.sequence " + + "WHERE ht.id > ? AND ht.id < ? " + + "ORDER BY ht.id desc LIMIT 10", + []interface{}{int64(429496729599), int64(8589938689)}, + }, + } { + tt.Assert.NoError(testCase.q.Err) + got, args, err := testCase.q.sql.ToSql() + tt.Assert.NoError(err) + tt.Assert.Equal(got, testCase.expectedSQL) + tt.Assert.Equal(args, testCase.expectedArgs) + } }