Files
scylladb/tools/scylla-sstable-scripts/slice.lua
Avi Kivity f3eade2f62 treewide: relicense to ScyllaDB-Source-Available-1.0
Drop the AGPL license in favor of a source-available license.
See the blog post [1] for details.

[1] https://www.scylladb.com/2024/12/18/why-were-moving-to-a-source-available-license/
2024-12-18 17:45:13 +02:00

184 lines
5.8 KiB
Lua

--
-- Copyright (C) 2022-present ScyllaDB
--
-- SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
--
-- Filters and dumps the content of the sstable(s).
--
-- With no arguments, this script is identical to the dump-data operation.
-- It demonstrates how to use the high-level JSON write API, as well as how to
-- work with partition and clustering keys and how to accepts arguments from the
-- command-line
-- The script accepts two kind of arguments from the command-line: partition
-- ranges and clustering ranges.
-- Partition ranges are expected to have keys with the format of `prN`, where N
-- is an integer. N can have any value but it should be unique across all other
-- partition-ranges passed to the script. A simple scheme is to use a running
-- counter to number them.
-- Clustering ranges are expected to have keys with the format of `crN`. The same
-- restrictions apply to `N`.
-- The ranges themselves have the format:
-- * [X,Y] - inclusive range from X to Y
-- * [X,Y) - from X (inclusive) to Y (exclusive)
-- * (X,Y] - from X (exclusive) to Y (inclusive)
-- * (X,Y) - exclusive range from X to Y
--
-- The key values should be hex encoded serialized keys. For partition ranges, it
-- is possible to pass tokens, as `tTOKEN`. The special values of `-inf` and
-- `+inf` can be used to denote infinity, but note that infinity should always be
-- an exclusive bound.
--
-- Examples:
--
-- # a single partition range, from key 000400000005 to inf
-- $ scylla sstable script --script-file slice.lua --script-args "pk0=[000400000005,+inf)"
--
-- # two partition ranges
-- $ scylla sstable script --script-file slice.lua --script-args "pk0=(-inf,000400000002):pk1=[000400000005,+inf)"
--
-- # token-range
-- $ scylla sstable script --script-file slice.lua --script-args "pk0=(t-1000,t1000)"
--
-- # a single clustering range, from key 000400000005 to inf
-- $ scylla sstable script --script-file slice.lua --script-args "ck0=[000400000005,+inf)"
--
-- # partition (mixed key and token) and clustering range
-- $ scylla sstable script --script-file slice.lua --script-args "pk0=(000400000001,t1000):ck0=[000400000005,+inf)"
wr = Scylla.new_json_writer()
partition_ranges = {}
clustering_ranges = {}
arg_key_pattern = "^([pc]r)(%d*)$"
arg_value_pattern = "^([%(%[])(.+),(.+)([%]%)])$"
key_pattern = "^([0-9a-f]+)$"
token_pattern = "^t(-?%d+)$"
paren_to_key_weight = {
["["] = 0,
["]"] = 0,
["("] = 1,
[")"] = -1,
}
paren_to_token_weight = {
["["] = -1,
["]"] = 1,
["("] = 1,
[")"] = -1,
}
function make_ring_position(paren, bound)
if bound == '-inf' or bound == 'inf' or bound == '+inf' then
return Scylla.new_ring_position(paren_to_token_weight[paren], nil)
end
local serialized_key = string.match(bound, key_pattern)
if serialized_key ~= nil then
return Scylla.new_ring_position(paren_to_key_weight[paren], Scylla.unserialize_partition_key(serialized_key))
end
local token = string.match(bound, token_pattern)
if token ~= nil then
return Scylla.new_ring_position(paren_to_token_weight[paren], tonumber(token))
end
error(string.format("failed to parse %s as a partition-range bound, expected t$TOKEN, +-inf or a serialized key value", bound))
end
function make_position_in_partition(paren, bound)
local weight = paren_to_key_weight[paren]
if bound == '-inf' or bound == 'inf' or bound == '+inf' then
return Scylla.new_position_in_partition(weight, nil)
end
local serialized_key = string.match(bound, key_pattern)
if serialized_key ~= nil then
return Scylla.new_position_in_partition(weight, Scylla.unserialize_clustering_key(serialized_key))
end
error(string.format("failed to parse %s as a clustering-range bound, expected +-inf or a serialized key value", bound))
end
function parse_ranges(args)
for k, v in pairs(args) do
local kind, index = string.match(k, arg_key_pattern)
if kind == nil then
error(string.format("failed to parse command line argument key: %s", k))
end
local start_paren, start_bound, end_bound, end_paren = string.match(v, arg_value_pattern)
if start_paren == nil then
error(string.format("failed to parse command line argument value for key %s: %s", k, v))
end
if kind == 'pr' then
partition_ranges[#partition_ranges + 1] = {make_ring_position(start_paren, start_bound), make_ring_position(end_paren, end_bound)}
else
clustering_ranges[#clustering_ranges + 1] = {make_position_in_partition(start_paren, start_bound), make_position_in_partition(end_paren, end_bound)}
end
end
end
function filter(point, ranges)
if #ranges == 0 then
return true
end
for _, range in ipairs(ranges) do
if range[1]:tri_cmp(point) <= 0 and range[2]:tri_cmp(point) >= 0 then
return true
end
end
return false
end
function consume_stream_start(args)
parse_ranges(args)
wr:start_stream()
end
function consume_sstable_start(sst)
wr:start_sstable(sst)
end
skip_partition = false
function consume_partition_start(ps)
skip_partition = not filter(Scylla.new_ring_position(0, ps.key, ps.token), partition_ranges)
if skip_partition then
return false
end
wr:start_partition(ps)
end
function consume_static_row(sr)
wr:static_row(sr)
end
function consume_clustering_row(cr)
if filter(Scylla.new_position_in_partition(0, cr.key), clustering_ranges) then
wr:clustering_row(cr)
end
end
function consume_range_tombstone_change(rtc)
wr:range_tombstone_change(rtc)
end
function consume_partition_end()
if skip_partition then
return
end
wr:end_partition()
end
function consume_sstable_end()
wr:end_sstable()
end
function consume_stream_end()
wr:end_stream()
end