# The following collectors are available:
# - value-first - uses the first character of the value to construct an interval
# - value-last - uses the last character of the value to construct an interval
# - suffix - constructs an interval from the '@timestamp' suffix of each key
# - suffix-point-keys-only - same as "suffix", but only applies to point keys
# - suffix-range-keys-only - same as "suffix", but only applies to range keys
# - nil-points-and-ranges - a trivial collector with neither a point nor range collector

# Single collector.

build collectors=(value-first)
a.SET.1:10
b.SET.2:20
c.SET.3:30
----
point:    [a#1,SET-c#3,SET]
seqnums:  [1-3]

# collectors returns the collectors used when writing the table, keyed by the
# shortID of the collector.
collectors
----
0: value-first

# table-props returns the table-level properties, keyed by the shortID.
table-props
----
0: [1, 4)

# block-props returns the block-level properties. For each block, the separator
# is printed, along with the properties for the block, keyed by the shortID.
block-props
----
c:
  0: [1, 4)

# Multiple collectors.

build collectors=(value-first,value-last)
a.SET.1:17
b.SET.2:29
c.SET.3:38
----
point:    [a#1,SET-c#3,SET]
seqnums:  [1-3]

collectors
----
0: value-first
1: value-last

table-props
----
0: [1, 4)
1: [7, 10)

block-props
----
c:
  0: [1, 4)
  1: [7, 10)

# Reduce the block size to a value such that each block has at most two KV
# pairs.

build block-size=25 collectors=(value-first,value-last)
a.SET.1:15
b.SET.2:86
c.SET.3:72
d.SET.4:21
e.SET.5:47
f.SET.6:54
g.SET.7:63
h.SET.8:38
----
point:    [a#1,SET-h#8,SET]
seqnums:  [1-8]

collectors
----
0: value-first
1: value-last

table-props
----
0: [1, 9)
1: [1, 9)

block-props
----
b:
  0: [1, 9)
  1: [5, 7)
d:
  0: [2, 8)
  1: [1, 3)
f:
  0: [4, 6)
  1: [4, 8)
h:
  0: [3, 7)
  1: [3, 9)

# Range keys contribute to the table-level property but do not affect point key
# data blocks.

build collectors=(suffix)
a@5.SET.1:foo
b@10.SET.2:bar
c@15.SET.3:baz
Span: d@10-e@15:{(#4,RANGEKEYSET,@20,foo)}
Span: e@15-f@20:{(#5,RANGEKEYUNSET,@25)}
Span: f@20-z@25:{(#6,RANGEKEYDEL)}
----
point:    [a@5#1,SET-c@15#3,SET]
rangekey: [d@10#4,RANGEKEYSET-z@25#inf,RANGEKEYDEL]
seqnums:  [1-6]

collectors
----
0: suffix

block-props
----
d:
  0: [5, 16)

table-props
----
0: [5, 26)

# Same as the above, but only collect point key properties.

build collectors=(suffix-point-keys-only)
a@5.SET.1:foo
b@10.SET.2:bar
c@15.SET.3:baz
Span: d@10-e@15:{(#4,RANGEKEYSET,@20,foo)}
Span: e@15-f@20:{(#5,RANGEKEYUNSET,@25)}
Span: f@20-z@25:{(#6,RANGEKEYDEL)}
----
point:    [a@5#1,SET-c@15#3,SET]
rangekey: [d@10#4,RANGEKEYSET-z@25#inf,RANGEKEYDEL]
seqnums:  [1-6]

collectors
----
0: suffix-point-keys-only

block-props
----
d:
  0: [5, 16)

table-props
----
0: [5, 16)

# Same as the above, but only collect range key properties.

build collectors=(suffix-range-keys-only)
a@5.SET.1:foo
b@10.SET.2:bar
c@15.SET.3:baz
Span: d@10-e@15:{(#4,RANGEKEYSET,@20,foo)}
Span: e@15-f@20:{(#5,RANGEKEYUNSET,@25)}
Span: f@20-z@25:{(#6,RANGEKEYDEL)}
----
point:    [a@5#1,SET-c@15#3,SET]
rangekey: [d@10#4,RANGEKEYSET-z@25#inf,RANGEKEYDEL]
seqnums:  [1-6]

collectors
----
0: suffix-range-keys-only

block-props
----
d:

table-props
----
0: [20, 26)

# Create a table with multiple data blocks and a range key block. Two block
# property collectors are used, one for range keys and one for point keys, each
# acting independently.

build block-size=1 collectors=(suffix-point-keys-only,suffix-range-keys-only)
a@5.SET.1:foo
b@10.SET.2:bar
c@15.SET.3:baz
Span: d@10-e@15:{(#4,RANGEKEYSET,@20,foo)}
Span: e@15-f@20:{(#5,RANGEKEYUNSET,@25)}
Span: f@20-z@25:{(#6,RANGEKEYDEL)}
----
point:    [a@5#1,SET-c@15#3,SET]
rangekey: [d@10#4,RANGEKEYSET-z@25#inf,RANGEKEYDEL]
seqnums:  [1-6]

collectors
----
0: suffix-point-keys-only
1: suffix-range-keys-only

block-props
----
a@5:
  0: [5, 6)
b@10:
  0: [10, 11)
d:
  0: [15, 16)

table-props
----
0: [5, 16)
1: [20, 26)

# Partially matching point key filter.

filter point-filter=(suffix-point-keys-only,10,20)
----
points: true, blocks=[1,2]
ranges: true (no filters provided)

# Non-matching point key filter.

filter point-filter=(suffix-point-keys-only,100,200)
----
points: false
ranges: true (no filters provided)

# Partially matching range key filter.

filter range-filter=(suffix-range-keys-only,10,25)
----
points: true (no filters provided)
ranges: true

# Non-matching range key filter.

filter range-filter=(suffix-range-keys-only,100,200)
----
points: true (no filters provided)
ranges: false

# Matching point and range key filter.

filter point-filter=(suffix-point-keys-only,10,20) range-filter=(suffix-range-keys-only,10,25)
----
points: true, blocks=[1,2]
ranges: true

# Matching point key filter and non-matching range key filter.

filter point-filter=(suffix-point-keys-only,10,20) range-filter=(suffix-range-keys-only,100,200)
----
points: true, blocks=[1,2]
ranges: false

# Non-matching point key filter and matching range key filter.

filter point-filter=(suffix-point-keys-only,100,200) range-filter=(suffix-range-keys-only,10,25)
----
points: false
ranges: true

# Non-matching point and range key filter.

filter point-filter=(suffix-point-keys-only,100,200) range-filter=(suffix-range-keys-only,100,100)
----
points: false
ranges: false

# Providing a nil collector for both points and ranges is a user-error.

build collectors=(nil-points-and-ranges)
----
mapper must be provided

# Test a small index-block-size and block-size, so every data block has one KV
# and every index block points to one data block.

build collectors=(suffix-point-keys-only) index-block-size=1 block-size=1
a@1.SET.1:foo
b@10.SET.2:bar
c@15.SET.3:baz
d@25.SET.4:bax
e@3.SET.5:box
f@5.SET.3:mop
----
point:    [a@1#1,SET-f@5#3,SET]
seqnums:  [1-5]

collectors
----
0: suffix-point-keys-only

table-props
----
0: [1, 26)

# Because of the tiny index block size, every index block should have the same
# properties as the single data block contained in the table. Indentation shows
# the hierarchy.

block-props
----
a@1:
  0: [1, 2)
  a@1:
    0: [1, 2)
b@10:
  0: [10, 11)
  b@10:
    0: [10, 11)
c@15:
  0: [15, 16)
  c@15:
    0: [15, 16)
d@25:
  0: [25, 26)
  d@25:
    0: [25, 26)
e@3:
  0: [3, 4)
  e@3:
    0: [3, 4)
g:
  0: [5, 6)
  g:
    0: [5, 6)

# Test the same sstable, but with a larger index block size that fits multiple
# (3) KV pairs. Each entry in the top-level index should hold the unioned ranges
# of all the data blocks' properties.

build collectors=(suffix-point-keys-only) index-block-size=64 block-size=1
a@1.SET.1:foo
b@10.SET.2:bar
c@15.SET.3:baz
d@25.SET.4:bax
e@3.SET.5:box
f@5.SET.3:mop
----
point:    [a@1#1,SET-f@5#3,SET]
seqnums:  [1-5]

collectors
----
0: suffix-point-keys-only

block-props
----
c@15:
  0: [1, 16)
  a@1:
    0: [1, 2)
  b@10:
    0: [10, 11)
  c@15:
    0: [15, 16)
g:
  0: [3, 26)
  d@25:
    0: [25, 26)
  e@3:
    0: [3, 4)
  g:
    0: [5, 6)

iter upper=f point-key-filter=(suffix-point-keys-only,1,2)
first
next-prefix
----
<a@1:1>
.

# Regression test for a bug in boundary checking when skipping over irrelevant
# index blocks in a two-level indexed sstable.

iter lower=a point-key-filter=(suffix-point-keys-only,1,2)
seek-lt h
last
----
<a@1:1>
<a@1:1>

# Same as above, but each index block holds 2 keys. This exercises a variant of
# the above bug. Specifically, the bounds check performed /within/ skipBackward,
# instead of within SeekLT and Last.

build collectors=(suffix-point-keys-only) index-block-size=48 block-size=1
a@1.SET.1:foo
b@10.SET.2:bar
c@15.SET.3:baz
d@25.SET.4:bax
e@3.SET.5:box
f@5.SET.3:mop
----
point:    [a@1#1,SET-f@5#3,SET]
seqnums:  [1-5]

block-props
----
b@10:
  0: [1, 11)
  a@1:
    0: [1, 2)
  b@10:
    0: [10, 11)
d@25:
  0: [15, 26)
  c@15:
    0: [15, 16)
  d@25:
    0: [25, 26)
g:
  0: [3, 6)
  e@3:
    0: [3, 4)
  g:
    0: [5, 6)

# Regression test for a bug in boundary checking when skipping over irrelevant
# index blocks in a two-level indexed sstable.

iter lower=a upper=z point-key-filter=(suffix-point-keys-only,1,2)
seek-lt h
----
<a@1:1>

# Test MaybeFilteredKeys().

# Use timestamp range [1,9), which matches a@1, e@3 and f@5 and filters
# a continuous section of three keys b@10, c@15 and d@25.

iter point-key-filter=(suffix-point-keys-only,1,9)
first
next
next
next
seek-ge b
seek-ge e@3
----
<a@1:1>
<e@3:5>
<f@5:3>
.
<e@3:5>
<e@3:5>


# NB: `seek-ge e` and `seek-ge dog` return MaybeFilteredKeys()=true, despite no
# filtered keys existing within the range [e,e@3) or [dog,e@3). This is a
# consequence of the index separator `e`. After seeking the index block, the
# iterator only knows that the first block MAY contain keys ≤ e. However, it can
# be skipped regardless, because block properties filters exclude it. In this
# case, the iterator still returns MaybeFilteredKeys()=true, since keys MAY have
# been excluded by the filter.

iter point-key-filter=(suffix-point-keys-only,1,9)
seek-ge e
seek-ge dog
----
<e@3:5>
<e@3:5>

iter point-key-filter=(suffix-point-keys-only,1,100)
first
next
next
next
next
next
next
seek-lt d
seek-ge d
----
<a@1:1>
<b@10:2>
<c@15:3>
<d@25:4>
<e@3:5>
<f@5:3>
.
<c@15:3>
<d@25:4>

# [10,16) intersects {b@10, c@15}.

iter point-key-filter=(suffix-point-keys-only,10,16)
last
prev
prev
seek-lt a
seek-lt c
seek-lt ca
seek-lt f
seek-lt e
seek-lt d
----
<c@15:3>
<b@10:2>
.
.
<b@10:2>
<c@15:3>
<c@15:3>
<c@15:3>
<c@15:3>

# Test monotonically increasing bounds optimization, with the first seek
# filtering keys. The subsequent seek must not reuse the current iterator
# position and improperly returning MaybeFilteredKeys=false when keys were
# filtered.

iter point-key-filter=(suffix-point-keys-only,10,16)
set-bounds lower=b upper=ee
seek-ge d
set-bounds lower=ee upper=g
seek-ge ee
----
.
.
.
.

iter point-key-filter=(suffix-point-keys-only,10,16)
set-bounds lower=a upper=b
seek-ge a
set-bounds lower=b upper=e
seek-ge b
seek-ge bb
----
.
.
.
<b@10:2>
<c@15:3>

# Test monotonically decreasing bounds optimization, with the first seek
# filtering keys. The subsequent seek must not reuse the current iterator
# position and improperly returning MaybeFilteredKeys=false when keys were
# filtered.

iter point-key-filter=(suffix-point-keys-only,10,16)
set-bounds lower=e upper=f
seek-lt f
set-bounds lower=c upper=e
seek-lt e
set-bounds lower=a upper=c
seek-lt c
seek-lt b
----
.
.
.
<c@15:3>
.
<b@10:2>
.

# The below case tests try-seek-using-next.
#
# The `seek-ge aa` does not reposition the iterator. This case should preserve
# the existing MaybeFilteredKeys()=true value.
#
# The `seek-ge c@16` and seek-ge c@19` must also return MaybeFilteredKeys()=true.

iter point-key-filter=(suffix-point-keys-only,10,16)
seek-ge a
seek-ge aa true
seek-ge bb true
seek-ge c@16 true
seek-ge c@19 true
----
<b@10:2>
<b@10:2>
<c@15:3>
<c@15:3>
<c@15:3>

# Test another case of monotonically increasing bounds optimization, with a
# different index block structure. The first seek down below should filter keys,
# and leave the top-level index positioned at the last index block. The
# subsequent seek must return MaybeFilteredKeys=true when keys were filtered.

build collectors=(suffix-point-keys-only) index-block-size=1 block-size=64
a@1.SET.1:foo
b@10.SET.2:bar
c@15.SET.3:baz
d@25.SET.4:bax
e@3.SET.5:box
f@5.SET.3:mop
----
point:    [a@1#1,SET-f@5#3,SET]
seqnums:  [1-5]

block-props
----
c@15:
  0: [1, 16)
  c@15:
    0: [1, 16)
g:
  0: [3, 26)
  g:
    0: [3, 26)

iter point-key-filter=(suffix-point-keys-only,1,2)
set-bounds lower=b upper=e
seek-ge d
set-bounds lower=e upper=g
seek-ge ee
----
.
.
.
.

# Test another case of monotonically increasing bounds optimization, with a new
# index block structure: This one has only one level. The first seek down below
# should filter keys, and leave the index positioned at the last index block.
# The subsequent seek must return MaybeFilteredKeys=true when keys were
# filtered.

build collectors=(suffix-point-keys-only) block-size=32
a@1.SET.1:foo
b@10.SET.2:bar
c@15.SET.3:baz
d@25.SET.4:bax
e@3.SET.5:box
f@5.SET.3:mop
----
point:    [a@1#1,SET-f@5#3,SET]
seqnums:  [1-5]

block-props
----
b@10:
  0: [1, 11)
d@25:
  0: [15, 26)
g:
  0: [3, 6)

iter point-key-filter=(suffix-point-keys-only,1,2)
set-bounds lower=b upper=ee
seek-ge d
set-bounds lower=ee upper=g
seek-ge ee
----
.
.
.
.

# Create a table with a single block of point keys to test suffix replacement
# injection during block property filtering

build block-size=1 collectors=(suffix-point-keys-only)
a@5.SET.1:foo
b@8.SET.2:bar
c@7.SET.3:baz
----
point:    [a@5#1,SET-c@7#3,SET]
seqnums:  [1-3]

collectors
----
0: suffix-point-keys-only

block-props
----
a@5:
  0: [5, 6)
b@8:
  0: [8, 9)
d:
  0: [7, 8)

table-props
----
0: [5, 9)

# Blocks only match with synthetic replacement

filter point-filter=(suffix-point-keys-only,10,20) synthetic=12
----
points: true, blocks=[0,1,2]
ranges: true (no filters provided)

filter point-filter=(suffix-point-keys-only,10,20)
----
points: false
ranges: true (no filters provided)

build collectors=(suffix-point-keys-only) index-block-size=48 block-size=1
a@10.SET.1:foo
a@9.SET.2:bar
a@8.SET.2:baz
a@7.SET.2:bax
aa@9.SET.3:mop
aa@8.SET.3:box
f@5.SET.3:mop
----
point:    [a@10#1,SET-f@5#3,SET]
seqnums:  [1-3]

block-props
----
a@9:
  0: [9, 11)
  a@10:
    0: [10, 11)
  a@9:
    0: [9, 10)
a@7:
  0: [7, 9)
  a@8:
    0: [8, 9)
  a@7:
    0: [7, 8)
b:
  0: [8, 10)
  aa@9:
    0: [9, 10)
  b:
    0: [8, 9)
g:
  0: [5, 6)
  g:
    0: [5, 6)

# Regression test for a nil-pointer when we find a second-level index block
# irrelevant and enforce an upper bound.

iter point-key-filter=(suffix-point-keys-only,5,8) upper=c
seek-ge a
next-prefix
----
<a@7:2>
.
