• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codenotary / immudb / 30529551194

30 Jul 2026 09:09AM UTC coverage: 84.854% (+0.004%) from 84.85%
30529551194

push

gh-ci

vchaindz
build(deps): bump actions/setup-go from 6 to 7

Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6 to 7.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

45273 of 53354 relevant lines covered (84.85%)

126019.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.69
/embedded/sql/row_reader.go
1
/*
2
Copyright 2026 Codenotary Inc. All rights reserved.
3

4
SPDX-License-Identifier: BUSL-1.1
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    https://mariadb.com/bsl11/
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package sql
18

19
import (
20
        "context"
21
        "crypto/sha256"
22
        "encoding/binary"
23
        "errors"
24
        "fmt"
25
        "math"
26

27
        "github.com/codenotary/immudb/embedded/store"
28
)
29

30
type RowReader interface {
31
        Tx() *SQLTx
32
        TableAlias() string
33
        Parameters() map[string]interface{}
34
        Read(ctx context.Context) (*Row, error)
35
        Close() error
36
        Columns(ctx context.Context) ([]ColDescriptor, error)
37
        OrderBy() []ColDescriptor
38
        ScanSpecs() *ScanSpecs
39
        InferParameters(ctx context.Context, params map[string]SQLValueType) error
40
        colsBySelector(ctx context.Context) (map[string]ColDescriptor, error)
41
        onClose(func())
42
}
43

44
type ScanSpecs struct {
45
        Index             *Index
46
        rangesByColID     map[uint32]*typedValueRange
47
        IncludeHistory    bool
48
        IncludeDiff       bool
49
        IncludeTxMetadata bool
50
        DescOrder         bool
51
        groupBySortExps   []*OrdExp
52
        orderBySortExps   []*OrdExp
53
        // neededColIDs, when non-nil, restricts column decoding to the listed IDs.
54
        // Columns absent from the map are skipped (offset advanced, no allocation).
55
        // nil means decode all columns (backward-compatible default).
56
        neededColIDs map[uint32]bool
57
}
58

59
func (s *ScanSpecs) extraCols() int {
39,893✔
60
        n := 0
39,893✔
61
        if s.IncludeHistory {
39,915✔
62
                n++
22✔
63
        }
22✔
64

65
        if s.IncludeDiff {
39,893✔
66
                n++
×
67
        }
×
68

69
        if s.IncludeTxMetadata {
39,910✔
70
                n++
17✔
71
        }
17✔
72
        return n
39,893✔
73
}
74

75
type Row struct {
76
        ValuesByPosition []TypedValue
77
        ValuesBySelector map[string]TypedValue
78
}
79

80
// rows are selector-compatible if both rows have the same assigned value for all specified selectors
81
func (row *Row) compatible(aRow *Row, selectors []*ColSelector, table string) (bool, error) {
550✔
82
        for _, sel := range selectors {
967✔
83
                c := EncodeSelector(sel.resolve(table))
417✔
84

417✔
85
                val1, ok := row.ValuesBySelector[c]
417✔
86
                if !ok {
417✔
87
                        return false, ErrInvalidColumn
×
88
                }
×
89

90
                val2, ok := aRow.ValuesBySelector[c]
417✔
91
                if !ok {
417✔
92
                        return false, ErrInvalidColumn
×
93
                }
×
94

95
                cmp, err := val1.Compare(val2)
417✔
96
                if err != nil {
417✔
97
                        return false, err
×
98
                }
×
99

100
                if cmp != 0 {
548✔
101
                        return false, nil
131✔
102
                }
131✔
103
        }
104

105
        return true, nil
419✔
106
}
107

108
func (row *Row) digest(cols []ColDescriptor) (d [sha256.Size]byte, err error) {
1,179✔
109
        h := sha256.New()
1,179✔
110

1,179✔
111
        for i, v := range row.ValuesByPosition {
2,387✔
112
                var b [4]byte
1,208✔
113
                binary.BigEndian.PutUint32(b[:], uint32(i))
1,208✔
114
                h.Write(b[:])
1,208✔
115

1,208✔
116
                // a nil slot (column skipped by projection pushdown) digests as NULL
1,208✔
117
                if v == nil {
1,209✔
118
                        continue
1✔
119
                }
120

121
                _, isNull := v.(*NullValue)
1,207✔
122
                if isNull {
1,210✔
123
                        continue
3✔
124
                }
125

126
                encVal, err := EncodeValue(v, v.Type(), 0)
1,204✔
127
                if err != nil {
1,204✔
128
                        return d, err
×
129
                }
×
130

131
                h.Write(encVal)
1,204✔
132
        }
133

134
        copy(d[:], h.Sum(nil))
1,179✔
135

1,179✔
136
        return
1,179✔
137
}
138

139
type rawRowReader struct {
140
        tx         *SQLTx
141
        table      *Table
142
        tableAlias string
143
        colsByPos  []ColDescriptor
144
        colsBySel  map[string]ColDescriptor
145
        scanSpecs  *ScanSpecs
146

147
        // defines a sub-range a transactions based on a combination of tx IDs and timestamps
148
        // the query is resolved only taking into consideration that range of transactioins
149
        period period
150

151
        // underlying store supports reading entries within a range of txs
152
        // the range is calculated based on the period stmt, which is included here to support
153
        // lazy evaluation when parameters are available
154
        txRange *txRange
155

156
        params map[string]interface{}
157

158
        reader          store.KeyReader
159
        onCloseCallback func()
160
}
161

162
type txRange struct {
163
        initialTxID uint64
164
        finalTxID   uint64
165
}
166

167
type ColDescriptor struct {
168
        AggFn  string
169
        Table  string
170
        Column string
171
        Type   SQLValueType
172
}
173

174
func (d *ColDescriptor) Selector() string {
261,606✔
175
        return EncodeSelector(d.AggFn, d.Table, d.Column)
261,606✔
176
}
261,606✔
177

178
type emptyKeyReader struct {
179
}
180

181
func (r emptyKeyReader) Read(ctx context.Context) (key []byte, val store.ValueRef, err error) {
×
182
        return nil, nil, store.ErrNoMoreEntries
×
183
}
×
184

185
func (r emptyKeyReader) ReadBetween(ctx context.Context, initialTxID uint64, finalTxID uint64) (key []byte, val store.ValueRef, err error) {
×
186
        return nil, nil, store.ErrNoMoreEntries
×
187
}
×
188

189
func (r emptyKeyReader) Reset() error {
×
190
        return nil
×
191
}
×
192

193
func (r emptyKeyReader) Close() error {
×
194
        return nil
×
195
}
×
196

197
func newRawRowReader(tx *SQLTx, params map[string]interface{}, table *Table, period period, tableAlias string, scanSpecs *ScanSpecs) (*rawRowReader, error) {
1,308✔
198
        if table == nil || scanSpecs == nil || scanSpecs.Index == nil {
1,308✔
199
                return nil, ErrIllegalArguments
×
200
        }
×
201

202
        rSpec, err := keyReaderSpecFrom(tx.engine.prefix, table, scanSpecs)
1,308✔
203
        if err != nil {
1,308✔
204
                return nil, err
×
205
        }
×
206

207
        var r store.KeyReader
1,308✔
208

1,308✔
209
        // System tables have no storage backing — their rows come from
1,308✔
210
        // Table.systemScan via tableRef.Resolve. If anything still funnels
1,308✔
211
        // a rawRowReader at one (e.g. a direct internal call), fall back to
1,308✔
212
        // an empty reader rather than scanning storage with a key prefix
1,308✔
213
        // that has no entries.
1,308✔
214
        if table.systemScan != nil {
1,308✔
215
                r = &emptyKeyReader{}
×
216
        } else {
1,308✔
217
                r, err = tx.newKeyReader(*rSpec)
1,308✔
218
                if err != nil {
1,309✔
219
                        return nil, err
1✔
220
                }
1✔
221
        }
222

223
        if tableAlias == "" {
2,277✔
224
                tableAlias = table.name
970✔
225
        }
970✔
226

227
        nCols := len(table.cols) + scanSpecs.extraCols()
1,307✔
228

1,307✔
229
        colsByPos := make([]ColDescriptor, nCols)
1,307✔
230
        colsBySel := make(map[string]ColDescriptor, nCols)
1,307✔
231

1,307✔
232
        off := 0
1,307✔
233
        if scanSpecs.IncludeHistory {
1,309✔
234
                colDescriptor := ColDescriptor{
2✔
235
                        Table:  tableAlias,
2✔
236
                        Column: revCol,
2✔
237
                        Type:   IntegerType,
2✔
238
                }
2✔
239

2✔
240
                colsByPos[off] = colDescriptor
2✔
241
                colsBySel[colDescriptor.Selector()] = colDescriptor
2✔
242
                off++
2✔
243
        }
2✔
244

245
        if scanSpecs.IncludeTxMetadata {
1,311✔
246
                colDescriptor := ColDescriptor{
4✔
247
                        Table:  tableAlias,
4✔
248
                        Column: txMetadataCol,
4✔
249
                        Type:   JSONType,
4✔
250
                }
4✔
251

4✔
252
                colsByPos[off] = colDescriptor
4✔
253
                colsBySel[colDescriptor.Selector()] = colDescriptor
4✔
254
                off++
4✔
255
        }
4✔
256

257
        for i, c := range table.cols {
5,199✔
258
                colDescriptor := ColDescriptor{
3,892✔
259
                        Table:  tableAlias,
3,892✔
260
                        Column: c.colName,
3,892✔
261
                        Type:   c.colType,
3,892✔
262
                }
3,892✔
263

3,892✔
264
                colsByPos[off+i] = colDescriptor
3,892✔
265
                colsBySel[colDescriptor.Selector()] = colDescriptor
3,892✔
266
        }
3,892✔
267

268
        return &rawRowReader{
1,307✔
269
                tx:         tx,
1,307✔
270
                table:      table,
1,307✔
271
                period:     period,
1,307✔
272
                tableAlias: tableAlias,
1,307✔
273
                colsByPos:  colsByPos,
1,307✔
274
                colsBySel:  colsBySel,
1,307✔
275
                scanSpecs:  scanSpecs,
1,307✔
276
                params:     params,
1,307✔
277
                reader:     r,
1,307✔
278
        }, nil
1,307✔
279
}
280

281
func keyReaderSpecFrom(sqlPrefix []byte, table *Table, scanSpecs *ScanSpecs) (spec *store.KeyReaderSpec, err error) {
1,310✔
282
        prefix := MapKey(sqlPrefix, MappedPrefix, EncodeID(table.id), EncodeID(scanSpecs.Index.id))
1,310✔
283

1,310✔
284
        var loKey []byte
1,310✔
285
        var loKeyReady bool
1,310✔
286

1,310✔
287
        var hiKey []byte
1,310✔
288
        var hiKeyReady bool
1,310✔
289

1,310✔
290
        loKey = make([]byte, len(prefix))
1,310✔
291
        copy(loKey, prefix)
1,310✔
292

1,310✔
293
        hiKey = make([]byte, len(prefix))
1,310✔
294
        copy(hiKey, prefix)
1,310✔
295

1,310✔
296
        // seekKey and endKey in the loop below are scan prefixes for beginning
1,310✔
297
        // and end of the index scanning range. On each index we try to make them more
1,310✔
298
        // concrete.
1,310✔
299
        for _, col := range scanSpecs.Index.cols {
2,586✔
300
                colRange, ok := scanSpecs.rangesByColID[col.id]
1,276✔
301
                if !ok {
2,281✔
302
                        break
1,005✔
303
                }
304

305
                if !hiKeyReady {
542✔
306
                        if colRange.hRange == nil {
292✔
307
                                hiKeyReady = true
21✔
308
                        } else {
271✔
309
                                encVal, _, err := EncodeValueAsKey(colRange.hRange.val, col.colType, col.MaxLen())
250✔
310
                                if err != nil {
251✔
311
                                        return nil, err
1✔
312
                                }
1✔
313
                                hiKey = append(hiKey, encVal...)
249✔
314
                        }
315
                }
316

317
                if !loKeyReady {
540✔
318
                        if colRange.lRange == nil {
283✔
319
                                loKeyReady = true
13✔
320
                        } else {
270✔
321
                                encVal, _, err := EncodeValueAsKey(colRange.lRange.val, col.colType, col.MaxLen())
257✔
322
                                if err != nil {
258✔
323
                                        return nil, err
1✔
324
                                }
1✔
325
                                loKey = append(loKey, encVal...)
256✔
326
                        }
327
                }
328
        }
329

330
        // Ensure the hiKey is inclusive regarding all values with that prefix
331
        hiKey = append(hiKey, KeyValPrefixUpperBound)
1,308✔
332

1,308✔
333
        seekKey := loKey
1,308✔
334
        endKey := hiKey
1,308✔
335

1,308✔
336
        if scanSpecs.DescOrder {
1,359✔
337
                seekKey, endKey = endKey, seekKey
51✔
338
        }
51✔
339

340
        return &store.KeyReaderSpec{
1,308✔
341
                SeekKey:        seekKey,
1,308✔
342
                InclusiveSeek:  true,
1,308✔
343
                EndKey:         endKey,
1,308✔
344
                InclusiveEnd:   true,
1,308✔
345
                Prefix:         prefix,
1,308✔
346
                DescOrder:      scanSpecs.DescOrder,
1,308✔
347
                Filters:        []store.FilterFn{store.IgnoreExpired, store.IgnoreDeleted},
1,308✔
348
                IncludeHistory: scanSpecs.IncludeHistory,
1,308✔
349
        }, nil
1,308✔
350
}
351

352
func (r *rawRowReader) onClose(callback func()) {
582✔
353
        r.onCloseCallback = callback
582✔
354
}
582✔
355

356
func (r *rawRowReader) Tx() *SQLTx {
732,644✔
357
        return r.tx
732,644✔
358
}
732,644✔
359

360
func (r *rawRowReader) TableAlias() string {
1,131,485✔
361
        return r.tableAlias
1,131,485✔
362
}
1,131,485✔
363

364
func (r *rawRowReader) OrderBy() []ColDescriptor {
22✔
365
        cols := make([]ColDescriptor, len(r.scanSpecs.Index.cols))
22✔
366

22✔
367
        for i, col := range r.scanSpecs.Index.cols {
47✔
368
                cols[i] = ColDescriptor{
25✔
369
                        Table:  r.tableAlias,
25✔
370
                        Column: col.colName,
25✔
371
                        Type:   col.colType,
25✔
372
                }
25✔
373
        }
25✔
374

375
        return cols
22✔
376
}
377

378
func (r *rawRowReader) ScanSpecs() *ScanSpecs {
131✔
379
        return r.scanSpecs
131✔
380
}
131✔
381

382
func (r *rawRowReader) Columns(ctx context.Context) ([]ColDescriptor, error) {
441✔
383
        ret := make([]ColDescriptor, len(r.colsByPos))
441✔
384
        copy(ret, r.colsByPos)
441✔
385
        return ret, nil
441✔
386
}
441✔
387

388
func (r *rawRowReader) colsBySelector(ctx context.Context) (map[string]ColDescriptor, error) {
1,029✔
389
        ret := make(map[string]ColDescriptor, len(r.colsBySel))
1,029✔
390
        for sel := range r.colsBySel {
3,956✔
391
                ret[sel] = r.colsBySel[sel]
2,927✔
392
        }
2,927✔
393
        return ret, nil
1,029✔
394
}
395

396
func (r *rawRowReader) InferParameters(ctx context.Context, params map[string]SQLValueType) error {
152✔
397
        cols, err := r.colsBySelector(ctx)
152✔
398
        if err != nil {
152✔
399
                return err
×
400
        }
×
401

402
        if r.period.start != nil {
162✔
403
                _, err = r.period.start.instant.exp.inferType(cols, params, r.TableAlias())
10✔
404
                if err != nil {
10✔
405
                        return err
×
406
                }
×
407
        }
408

409
        if r.period.end != nil {
162✔
410
                _, err = r.period.end.instant.exp.inferType(cols, params, r.TableAlias())
10✔
411
                if err != nil {
10✔
412
                        return err
×
413
                }
×
414
        }
415

416
        return nil
152✔
417
}
418

419
func (r *rawRowReader) Parameters() map[string]interface{} {
200,082✔
420
        return r.params
200,082✔
421
}
200,082✔
422

423
func (r *rawRowReader) reduceTxRange() (err error) {
39,302✔
424
        if r.txRange != nil || (r.period.start == nil && r.period.end == nil) {
78,527✔
425
                return nil
39,225✔
426
        }
39,225✔
427

428
        txRange := &txRange{
77✔
429
                initialTxID: uint64(0),
77✔
430
                finalTxID:   uint64(math.MaxUint64),
77✔
431
        }
77✔
432

77✔
433
        if r.period.start != nil {
131✔
434
                txRange.initialTxID, err = r.period.start.instant.resolve(r.tx, r.params, true, r.period.start.inclusive)
54✔
435
                if err != nil {
72✔
436
                        return err
18✔
437
                }
18✔
438
        }
439

440
        if r.period.end != nil {
94✔
441
                txRange.finalTxID, err = r.period.end.instant.resolve(r.tx, r.params, false, r.period.end.inclusive)
35✔
442
                if err != nil {
38✔
443
                        return err
3✔
444
                }
3✔
445
        }
446

447
        r.txRange = txRange
56✔
448

56✔
449
        return nil
56✔
450
}
451

452
func (r *rawRowReader) Read(ctx context.Context) (*Row, error) {
39,270✔
453
        if err := ctx.Err(); err != nil {
39,270✔
454
                return nil, err
×
455
        }
×
456

457
        //var mkey []byte
458
        var vref store.ValueRef
39,270✔
459

39,270✔
460
        // evaluation of txRange is postponed to allow parameters to be provided after rowReader initialization
39,270✔
461
        err := r.reduceTxRange()
39,270✔
462
        if errors.Is(err, store.ErrTxNotFound) {
39,281✔
463
                return nil, ErrNoMoreRows
11✔
464
        }
11✔
465
        if err != nil {
39,269✔
466
                return nil, err
10✔
467
        }
10✔
468

469
        if r.txRange == nil {
78,425✔
470
                _, vref, err = r.reader.Read(ctx) //mkey
39,176✔
471
        } else {
39,249✔
472
                _, vref, err = r.reader.ReadBetween(ctx, r.txRange.initialTxID, r.txRange.finalTxID) //mkey
73✔
473
        }
73✔
474
        if err != nil {
39,912✔
475
                return nil, err
663✔
476
        }
663✔
477

478
        v, err := vref.Resolve()
38,586✔
479
        if err != nil {
38,586✔
480
                return nil, err
×
481
        }
×
482

483
        valuesByPosition := make([]TypedValue, len(r.colsByPos))
38,586✔
484
        valuesBySelector := make(map[string]TypedValue, len(r.colsBySel))
38,586✔
485

38,586✔
486
        // extraCols is the number of synthetic leading columns (rev, txMetadata).
38,586✔
487
        // Table columns occupy r.colsByPos[extraCols:], corresponding 1-to-1 with
38,586✔
488
        // r.table.cols. Moved up so the pre-fill loop can use it for the skip check.
38,586✔
489
        extraCols := r.scanSpecs.extraCols()
38,586✔
490

38,586✔
491
        for i, col := range r.colsByPos {
248,545✔
492
                // Skip NullValue pre-fill for table columns that the query does not
209,959✔
493
                // need. The nil zero-value is semantically NULL and is never accessed
209,959✔
494
                // for columns excluded by neededColIDs. Extra synthetic columns (rev,
209,959✔
495
                // txMetadata) are always included regardless of neededColIDs.
209,959✔
496
                if r.scanSpecs.neededColIDs != nil {
236,599✔
497
                        if tableIdx := i - extraCols; tableIdx >= 0 && tableIdx < len(r.table.cols) {
53,280✔
498
                                if !r.scanSpecs.neededColIDs[r.table.cols[tableIdx].id] {
36,566✔
499
                                        continue
9,926✔
500
                                }
501
                        }
502
                }
503

504
                var val TypedValue
200,033✔
505

200,033✔
506
                switch col.Column {
200,033✔
507
                case revCol:
20✔
508
                        val = &Integer{val: int64(vref.HC())}
20✔
509
                case txMetadataCol:
13✔
510
                        val, err = r.parseTxMetadata(vref.TxMetadata())
13✔
511
                        if err != nil {
15✔
512
                                return nil, err
2✔
513
                        }
2✔
514
                default:
200,000✔
515
                        val = &NullValue{t: col.Type}
200,000✔
516
                }
517

518
                valuesByPosition[i] = val
200,031✔
519
                valuesBySelector[col.Selector()] = val
200,031✔
520
        }
521

522
        if len(v) < EncLenLen {
38,584✔
523
                return nil, ErrCorruptedData
×
524
        }
×
525

526

527
        voff := 0
38,584✔
528

38,584✔
529
        cols := int(binary.BigEndian.Uint32(v[voff:]))
38,584✔
530
        voff += EncLenLen
38,584✔
531

38,584✔
532
        for i, pos := 0, 0; i < cols; i++ {
247,999✔
533
                if len(v) < EncIDLen {
209,415✔
534
                        return nil, ErrCorruptedData
×
535
                }
×
536

537
                colID := binary.BigEndian.Uint32(v[voff:])
209,415✔
538
                voff += EncIDLen
209,415✔
539

209,415✔
540
                col, err := r.table.GetColumnByID(colID)
209,415✔
541
                if errors.Is(err, ErrColumnDoesNotExist) && colID <= r.table.maxColID {
209,440✔
542
                        // Dropped column, skip it
25✔
543
                        vlen, n, err := DecodeValueLength(v[voff:])
25✔
544
                        if err != nil {
25✔
545
                                return nil, err
×
546
                        }
×
547
                        voff += n + vlen
25✔
548

25✔
549
                        continue
25✔
550
                }
551
                if err != nil {
209,390✔
552
                        return nil, ErrCorruptedData
×
553
                }
×
554

555
                // Projection pushdown: skip columns not needed by the query.
556
                // We still advance voff so the byte stream stays in sync.
557
                // pos advancement is handled by the loop at the decode site below,
558
                // since that same loop also advances pos past any skipped entries.
559
                if r.scanSpecs.neededColIDs != nil && !r.scanSpecs.neededColIDs[colID] {
219,019✔
560
                        vlen, n, err := DecodeValueLength(v[voff:])
9,629✔
561
                        if err != nil {
9,629✔
562
                                return nil, err
×
563
                        }
×
564
                        voff += n + vlen
9,629✔
565
                        continue
9,629✔
566
                }
567

568
                val, n, err := DecodeValue(v[voff:], col.colType)
199,761✔
569
                if err != nil {
199,761✔
570
                        return nil, err
×
571
                }
×
572

573
                voff += n
199,761✔
574

199,761✔
575
                // make sure value is inserted in the correct position
199,761✔
576
                for pos < len(r.table.cols) && r.table.cols[pos].id < colID {
203,893✔
577
                        pos++
4,132✔
578
                }
4,132✔
579

580
                if pos == len(r.table.cols) || r.table.cols[pos].id != colID {
199,761✔
581
                        return nil, ErrCorruptedData
×
582
                }
×
583

584
                valuesByPosition[pos+extraCols] = val
199,761✔
585

199,761✔
586
                pos++
199,761✔
587

199,761✔
588
                valuesBySelector[EncodeSelector("", r.tableAlias, col.colName)] = val
199,761✔
589
        }
590

591
        if len(v)-voff > 0 {
38,584✔
592
                return nil, ErrCorruptedData
×
593
        }
×
594

595
        return &Row{ValuesByPosition: valuesByPosition, ValuesBySelector: valuesBySelector}, nil
38,584✔
596
}
597

598
// CountAll iterates the scan without decoding column values, returning the
599
// number of matching index entries. Used by the COUNT(*) fast-path.
600
func (r *rawRowReader) CountAll(ctx context.Context) (int64, error) {
26✔
601
        if err := ctx.Err(); err != nil {
26✔
602
                return 0, err
×
603
        }
×
604
        if err := r.reduceTxRange(); errors.Is(err, store.ErrTxNotFound) {
26✔
605
                return 0, nil
×
606
        } else if err != nil {
26✔
607
                return 0, err
×
608
        }
×
609
        var n int64
26✔
610
        for {
1,817✔
611
                var err error
1,791✔
612
                if r.txRange == nil {
3,549✔
613
                        _, _, err = r.reader.Read(ctx)
1,758✔
614
                } else {
1,791✔
615
                        _, _, err = r.reader.ReadBetween(ctx, r.txRange.initialTxID, r.txRange.finalTxID)
33✔
616
                }
33✔
617
                if errors.Is(err, store.ErrNoMoreEntries) {
1,817✔
618
                        return n, nil
26✔
619
                }
26✔
620
                if err != nil {
1,765✔
621
                        return 0, err
×
622
                }
×
623
                n++
1,765✔
624
        }
625
}
626

627
// CountAllWithKeyFilter iterates the index scan and counts entries that
628
// satisfy `where`, evaluating the predicate against values decoded from the
629
// index key alone (no value-reference resolution, no row payload decode).
630
//
631
// The caller must guarantee that every column referenced by `where` belongs
632
// to r.scanSpecs.Index.cols; see SelectStmt.canCountWithKeyOnly. The encoded
633
// key layout is documented in unmapIndexEntry (catalog.go) — sqlPrefix +
634
// MappedPrefix + tableID + indexID + (per-column tag+payload) + PK bytes.
635
func (r *rawRowReader) CountAllWithKeyFilter(ctx context.Context, where ValueExp) (int64, error) {
6✔
636
        if where == nil {
6✔
637
                return r.CountAll(ctx)
×
638
        }
×
639
        if err := ctx.Err(); err != nil {
6✔
640
                return 0, err
×
641
        }
×
642
        if err := r.reduceTxRange(); errors.Is(err, store.ErrTxNotFound) {
6✔
643
                return 0, nil
×
644
        } else if err != nil {
6✔
645
                return 0, err
×
646
        }
×
647

648
        index := r.scanSpecs.Index
6✔
649
        // Bytes preceding the per-column data: sqlPrefix + MappedPrefix +
6✔
650
        // tableID(4) + indexID(4). Anything after this offset is the ordered
6✔
651
        // sequence of indexed-column tag+payload runs, terminated by the PK.
6✔
652
        headerLen := len(r.tx.engine.prefix) + len(MappedPrefix) + 2*EncIDLen
6✔
653

6✔
654
        // Reusable per-iteration buffers; sparse Row keyed only by index columns.
6✔
655
        valuesBySelector := make(map[string]TypedValue, len(index.cols))
6✔
656
        row := &Row{ValuesBySelector: valuesBySelector}
6✔
657

6✔
658
        // Substitute params once: substitute is non-mutating (stmt.go:6755-6759,
6✔
659
        // per issue #1153) and the resulting AST is reusable across reduce calls,
6✔
660
        // so per-row substitution would only churn allocations.
6✔
661
        cond, err := where.substitute(r.params)
6✔
662
        if err != nil {
6✔
663
                return 0, fmt.Errorf("%w: when evaluating WHERE clause", err)
×
664
        }
×
665

666
        // Encoded selector strings are pure functions of (alias, colName); cache
667
        // them by index-column position to avoid per-row string concatenation.
668
        selectors := make([]string, len(index.cols))
6✔
669
        for i, col := range index.cols {
22✔
670
                selectors[i] = EncodeSelector("", r.tableAlias, col.colName)
16✔
671
        }
16✔
672

673
        var n int64
6✔
674
        for {
187✔
675
                var (
181✔
676
                        mkey []byte
181✔
677
                        err  error
181✔
678
                )
181✔
679
                if r.txRange == nil {
362✔
680
                        mkey, _, err = r.reader.Read(ctx)
181✔
681
                } else {
181✔
682
                        mkey, _, err = r.reader.ReadBetween(ctx, r.txRange.initialTxID, r.txRange.finalTxID)
×
683
                }
×
684
                if errors.Is(err, store.ErrNoMoreEntries) {
187✔
685
                        return n, nil
6✔
686
                }
6✔
687
                if err != nil {
175✔
688
                        return 0, err
×
689
                }
×
690

691
                if len(mkey) < headerLen {
175✔
692
                        return 0, ErrCorruptedData
×
693
                }
×
694

695
                off := headerLen
175✔
696
                // Reset map entries from the previous iteration. Re-using the map
175✔
697
                // avoids per-row allocation on a hot count loop.
175✔
698
                for k := range valuesBySelector {
665✔
699
                        delete(valuesBySelector, k)
490✔
700
                }
490✔
701

702
                for i, col := range index.cols {
678✔
703
                        val, consumed, derr := DecodeValueFromKey(mkey[off:], col.colType, col.MaxLen())
503✔
704
                        if derr != nil {
503✔
705
                                return 0, derr
×
706
                        }
×
707
                        off += consumed
503✔
708

503✔
709
                        valuesBySelector[selectors[i]] = val
503✔
710
                }
711

712
                match, isNull, err := reduceBoolValueExp(r.tx, row, r.tableAlias, cond)
175✔
713
                if err != nil {
175✔
714
                        return 0, fmt.Errorf("%w: when evaluating WHERE clause", err)
×
715
                }
×
716
                if isNull {
175✔
717
                        continue
×
718
                }
719
                if match {
276✔
720
                        n++
101✔
721
                }
101✔
722
        }
723
}
724

725
func (r *rawRowReader) parseTxMetadata(txmd *store.TxMetadata) (TypedValue, error) {
13✔
726
        if txmd == nil {
13✔
727
                return &NullValue{t: JSONType}, nil
×
728
        }
×
729

730
        if extra := txmd.Extra(); extra != nil {
26✔
731
                if r.tx.engine.parseTxMetadata == nil {
14✔
732
                        return nil, fmt.Errorf("unable to parse tx metadata")
1✔
733
                }
1✔
734

735
                md, err := r.tx.engine.parseTxMetadata(extra)
12✔
736
                if err != nil {
13✔
737
                        return nil, fmt.Errorf("%w: %s", ErrInvalidTxMetadata, err)
1✔
738
                }
1✔
739
                return &JSON{val: md}, nil
11✔
740
        }
741
        return &NullValue{t: JSONType}, nil
×
742
}
743

744
func (r *rawRowReader) Close() error {
1,315✔
745
        if r.onCloseCallback != nil {
1,896✔
746
                defer r.onCloseCallback()
581✔
747
        }
581✔
748

749
        return r.reader.Close()
1,315✔
750
}
751

752
func ReadAllRows(ctx context.Context, reader RowReader) ([]*Row, error) {
144✔
753
        var rows []*Row
144✔
754
        err := ReadRowsBatch(ctx, reader, 100, func(rowBatch []*Row) error {
551✔
755
                if rows == nil {
526✔
756
                        rows = make([]*Row, 0, len(rowBatch))
119✔
757
                }
119✔
758
                rows = append(rows, rowBatch...)
407✔
759
                return nil
407✔
760
        })
761
        return rows, err
144✔
762
}
763

764
func ReadRowsBatch(ctx context.Context, reader RowReader, batchSize int, onBatch func([]*Row) error) error {
384✔
765
        rows := make([]*Row, batchSize)
384✔
766

384✔
767
        hasMoreRows := true
384✔
768
        for hasMoreRows {
1,079✔
769
                n, err := readNRows(ctx, reader, batchSize, rows)
695✔
770

695✔
771
                if n > 0 {
1,345✔
772
                        if err := onBatch(rows[:n]); err != nil {
651✔
773
                                return err
1✔
774
                        }
1✔
775
                }
776

777
                hasMoreRows = !errors.Is(err, ErrNoMoreRows)
694✔
778
                if err != nil && hasMoreRows {
718✔
779
                        return err
24✔
780
                }
24✔
781
        }
782
        return nil
359✔
783
}
784

785
func readNRows(ctx context.Context, reader RowReader, n int, outRows []*Row) (int, error) {
695✔
786
        for i := 0; i < n; i++ {
34,185✔
787
                r, err := reader.Read(ctx)
33,490✔
788
                if err != nil {
33,873✔
789
                        return i, err
383✔
790
                }
383✔
791
                outRows[i] = r
33,107✔
792
        }
793
        return n, nil
312✔
794
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc