bitbucket.org/number571/tendermint@v0.8.14/state/indexer/sink/psql/psql.go (about) 1 package psql 2 3 import ( 4 "context" 5 "database/sql" 6 "errors" 7 "fmt" 8 "time" 9 10 abci "bitbucket.org/number571/tendermint/abci/types" 11 "bitbucket.org/number571/tendermint/libs/pubsub/query" 12 "bitbucket.org/number571/tendermint/state/indexer" 13 "bitbucket.org/number571/tendermint/types" 14 sq "github.com/Masterminds/squirrel" 15 proto "github.com/gogo/protobuf/proto" 16 ) 17 18 var _ indexer.EventSink = (*EventSink)(nil) 19 20 const ( 21 TableEventBlock = "block_events" 22 TableEventTx = "tx_events" 23 TableResultTx = "tx_results" 24 DriverName = "postgres" 25 ) 26 27 // EventSink is an indexer backend providing the tx/block index services. 28 type EventSink struct { 29 store *sql.DB 30 chainID string 31 } 32 33 func NewEventSink(connStr string, chainID string) (indexer.EventSink, *sql.DB, error) { 34 db, err := sql.Open(DriverName, connStr) 35 if err != nil { 36 return nil, nil, err 37 } 38 39 return &EventSink{ 40 store: db, 41 chainID: chainID, 42 }, db, nil 43 } 44 45 func (es *EventSink) Type() indexer.EventSinkType { 46 return indexer.PSQL 47 } 48 49 func (es *EventSink) IndexBlockEvents(h types.EventDataNewBlockHeader) error { 50 sqlStmt := sq. 51 Insert(TableEventBlock). 52 Columns("key", "value", "height", "type", "created_at", "chain_id"). 53 PlaceholderFormat(sq.Dollar). 54 Suffix("ON CONFLICT (key,height)"). 55 Suffix("DO NOTHING") 56 57 ts := time.Now() 58 // index the reserved block height index 59 sqlStmt = sqlStmt. 60 Values(types.BlockHeightKey, fmt.Sprint(h.Header.Height), h.Header.Height, "", ts, es.chainID) 61 62 // index begin_block events 63 sqlStmt, err := indexBlockEvents( 64 sqlStmt, h.ResultBeginBlock.Events, types.EventTypeBeginBlock, h.Header.Height, ts, es.chainID) 65 if err != nil { 66 return err 67 } 68 69 // index end_block events 70 sqlStmt, err = indexBlockEvents( 71 sqlStmt, h.ResultEndBlock.Events, types.EventTypeEndBlock, h.Header.Height, ts, es.chainID) 72 if err != nil { 73 return err 74 } 75 76 _, err = sqlStmt.RunWith(es.store).Exec() 77 return err 78 } 79 80 func (es *EventSink) IndexTxEvents(txr []*abci.TxResult) error { 81 // index the tx result 82 var txid uint32 83 sqlStmtTxResult := sq. 84 Insert(TableResultTx). 85 Columns("tx_result", "created_at"). 86 PlaceholderFormat(sq.Dollar). 87 RunWith(es.store). 88 Suffix("ON CONFLICT (tx_result)"). 89 Suffix("DO NOTHING"). 90 Suffix("RETURNING \"id\"") 91 92 sqlStmtEvents := sq. 93 Insert(TableEventTx). 94 Columns("key", "value", "height", "hash", "tx_result_id", "created_at", "chain_id"). 95 PlaceholderFormat(sq.Dollar). 96 Suffix("ON CONFLICT (key,hash)"). 97 Suffix("DO NOTHING") 98 99 ts := time.Now() 100 for _, tx := range txr { 101 txBz, err := proto.Marshal(tx) 102 if err != nil { 103 return err 104 } 105 106 sqlStmtTxResult = sqlStmtTxResult.Values(txBz, ts) 107 108 // execute sqlStmtTxResult db query and retrieve the txid 109 r, err := sqlStmtTxResult.Query() 110 if err != nil { 111 return err 112 } 113 defer r.Close() 114 115 if !r.Next() { 116 return nil 117 } 118 119 if err := r.Scan(&txid); err != nil { 120 return err 121 } 122 123 // index the reserved height and hash indices 124 hash := fmt.Sprintf("%X", types.Tx(tx.Tx).Hash()) 125 126 sqlStmtEvents = sqlStmtEvents.Values(types.TxHashKey, hash, tx.Height, hash, txid, ts, es.chainID) 127 sqlStmtEvents = sqlStmtEvents.Values(types.TxHeightKey, fmt.Sprint(tx.Height), tx.Height, hash, txid, ts, es.chainID) 128 for _, event := range tx.Result.Events { 129 // only index events with a non-empty type 130 if len(event.Type) == 0 { 131 continue 132 } 133 134 for _, attr := range event.Attributes { 135 if len(attr.Key) == 0 { 136 continue 137 } 138 139 // index if `index: true` is set 140 compositeTag := fmt.Sprintf("%s.%s", event.Type, attr.Key) 141 142 // ensure event does not conflict with a reserved prefix key 143 if compositeTag == types.TxHashKey || compositeTag == types.TxHeightKey { 144 return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeTag) 145 } 146 147 if attr.GetIndex() { 148 sqlStmtEvents = sqlStmtEvents.Values(compositeTag, attr.Value, tx.Height, hash, txid, ts, es.chainID) 149 } 150 } 151 } 152 } 153 154 // execute sqlStmtEvents db query... 155 _, err := sqlStmtEvents.RunWith(es.store).Exec() 156 return err 157 } 158 159 func (es *EventSink) SearchBlockEvents(ctx context.Context, q *query.Query) ([]int64, error) { 160 return nil, errors.New("block search is not supported via the postgres event sink") 161 } 162 163 func (es *EventSink) SearchTxEvents(ctx context.Context, q *query.Query) ([]*abci.TxResult, error) { 164 return nil, errors.New("tx search is not supported via the postgres event sink") 165 } 166 167 func (es *EventSink) GetTxByHash(hash []byte) (*abci.TxResult, error) { 168 return nil, errors.New("getTxByHash is not supported via the postgres event sink") 169 } 170 171 func (es *EventSink) HasBlock(h int64) (bool, error) { 172 return false, errors.New("hasBlock is not supported via the postgres event sink") 173 } 174 175 func indexBlockEvents( 176 sqlStmt sq.InsertBuilder, 177 events []abci.Event, 178 ty string, 179 height int64, 180 ts time.Time, 181 chainID string, 182 ) (sq.InsertBuilder, error) { 183 for _, event := range events { 184 // only index events with a non-empty type 185 if len(event.Type) == 0 { 186 continue 187 } 188 189 for _, attr := range event.Attributes { 190 if len(attr.Key) == 0 { 191 continue 192 } 193 194 // index iff the event specified index:true and it's not a reserved event 195 compositeKey := fmt.Sprintf("%s.%s", event.Type, attr.Key) 196 if compositeKey == types.BlockHeightKey { 197 return sqlStmt, fmt.Errorf( 198 "event type and attribute key \"%s\" is reserved; please use a different key", compositeKey) 199 } 200 201 if attr.GetIndex() { 202 sqlStmt = sqlStmt.Values(compositeKey, attr.Value, height, ty, ts, chainID) 203 } 204 } 205 } 206 return sqlStmt, nil 207 } 208 209 func (es *EventSink) Stop() error { 210 return es.store.Close() 211 }