Skip to content

Commit d370ac3

Browse files
authored
Support partial results (#118)
User may need only a subset of the query fields, but receives the values ​​of all fields, which leads to unnecessary network load. The solution is fields filtration on storage side by user-specified option. `fields` option allows to get only a subset of fields. This parameter contains the names of the fields that the user requests.
1 parent b8f2f8f commit d370ac3

File tree

11 files changed

+520
-32
lines changed

11 files changed

+520
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1616
### Added
1717

1818
* Support for UUID field types and UUID values
19+
* `fields` option for simple operations to get partial result
1920

2021
## [0.4.0] - 2020-12-02
2122

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ where:
7171
* `opts`:
7272
* `timeout` (`?number`) - `vshard.call` timeout (in seconds)
7373
* `bucket_id` (`?number|cdata`) - bucket ID
74+
* `fields` (`?table`) - field names for getting only a subset of fields
7475

7576
Returns metadata and array contains one inserted row, error.
7677

@@ -114,6 +115,7 @@ where:
114115
* `opts`:
115116
* `timeout` (`?number`) - `vshard.call` timeout (in seconds)
116117
* `bucket_id` (`?number|cdata`) - bucket ID
118+
* `fields` (`?table`) - field names for getting only a subset of fields
117119

118120
Returns metadata and array contains one row, error.
119121

@@ -146,6 +148,7 @@ where:
146148
* `opts`:
147149
* `timeout` (`?number`) - `vshard.call` timeout (in seconds)
148150
* `bucket_id` (`?number|cdata`) - bucket ID
151+
* `fields` (`?table`) - field names for getting only a subset of fields
149152

150153
Returns metadata and array contains one updated row, error.
151154

@@ -177,6 +180,7 @@ where:
177180
* `opts`:
178181
* `timeout` (`?number`) - `vshard.call` timeout (in seconds)
179182
* `bucket_id` (`?number|cdata`) - bucket ID
183+
* `fields` (`?table`) - field names for getting only a subset of fields
180184

181185
Returns metadata and array contains one deleted row (empty for vinyl), error.
182186

@@ -210,6 +214,7 @@ where:
210214
* `opts`:
211215
* `timeout` (`?number`) - `vshard.call` timeout (in seconds)
212216
* `bucket_id` (`?number|cdata`) - bucket ID
217+
* `fields` (`?table`) - field names for getting only a subset of fields
213218

214219
Returns inserted or replaced rows and metadata or nil with error.
215220

@@ -257,6 +262,7 @@ where:
257262
* `opts`:
258263
* `timeout` (`?number`) - `vshard.call` timeout (in seconds)
259264
* `bucket_id` (`?number|cdata`) - bucket ID
265+
* `fields` (`?table`) - field names for getting only a subset of fields
260266

261267
Returns metadata and empty array of rows or nil, error.
262268

crud/common/schema.lua

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ local errors = require('errors')
66
local log = require('log')
77

88
local ReloadSchemaError = errors.new_class('ReloadSchemaError', {capture_stack = false})
9+
local FilterFieldsError = errors.new_class('FilterFieldsError', {capture_stack = false})
910

1011
local const = require('crud.common.const')
12+
local dev_checks = require('crud.common.dev_checks')
1113

1214
local schema = {}
1315

@@ -133,22 +135,49 @@ local function get_space_schema_hash(space)
133135
return digest.murmur(msgpack.encode(space_info))
134136
end
135137

138+
local function filter_result_fields(tuple, field_names)
139+
if field_names == nil or tuple == nil then
140+
return tuple
141+
end
142+
143+
local result = {}
144+
145+
for i, field_name in ipairs(field_names) do
146+
result[i] = tuple[field_name]
147+
if result[i] == nil then
148+
return nil, FilterFieldsError:new(
149+
'Space format doesn\'t contain field named %q', field_name
150+
)
151+
end
152+
end
153+
154+
return result
155+
end
156+
136157
-- schema.wrap_box_space_func_result pcalls some box.space function
137158
-- and returns its result as a table
138159
-- `{res = ..., err = ..., space_schema_hash = ...}`
139160
-- space_schema_hash is computed if function failed and
140161
-- `add_space_schema_hash` is true
141-
function schema.wrap_box_space_func_result(add_space_schema_hash, space, func_name, ...)
162+
function schema.wrap_box_space_func_result(space, func_name, args, opts)
163+
dev_checks('table', 'string', 'table', 'table')
164+
142165
local result = {}
166+
local err
143167

144-
local ok, func_res = pcall(space[func_name], space, ...)
168+
opts = opts or {}
169+
170+
local ok, func_res = pcall(space[func_name], space, unpack(args))
145171
if not ok then
146172
result.err = func_res
147-
if add_space_schema_hash then
173+
if opts.add_space_schema_hash then
148174
result.space_schema_hash = get_space_schema_hash(space)
149175
end
150176
else
151-
result.res = func_res
177+
result.res, err = filter_result_fields(func_res, opts.field_names)
178+
if err ~= nil then
179+
return nil, err
180+
end
152181
end
153182

154183
return result

crud/common/utils.lua

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ local UnflattenError = errors.new_class("UnflattenError", {capture_stack = false
1010
local ParseOperationsError = errors.new_class('ParseOperationsError', {capture_stack = false})
1111
local ShardingError = errors.new_class('ShardingError', {capture_stack = false})
1212
local GetSpaceFormatError = errors.new_class('GetSpaceFormatError', {capture_stack = false})
13+
local FilterFieldsError = errors.new_class('FilterFieldsError', {capture_stack = false})
1314

1415
local utils = {}
1516

17+
local space_format_cache = setmetatable({}, {__mode = 'k'})
18+
1619
function utils.table_count(table)
1720
dev_checks("table")
1821

@@ -282,11 +285,57 @@ function utils.is_uuid(value)
282285
return ffi.istype(uuid_t, value)
283286
end
284287

285-
function utils.format_result(rows, space)
286-
return {
287-
metadata = table.copy(space:format()),
288-
rows = rows,
289-
}
288+
local function get_field_format(space_format, field_name)
289+
dev_checks('table', 'string')
290+
291+
local metadata = space_format_cache[space_format]
292+
if metadata ~= nil then
293+
return metadata[field_name]
294+
end
295+
296+
space_format_cache[space_format] = {}
297+
for _, field in ipairs(space_format) do
298+
space_format_cache[space_format][field.name] = field
299+
end
300+
301+
return space_format_cache[space_format][field_name]
302+
end
303+
304+
local function filter_format_fields(space_format, field_names)
305+
dev_checks('table', 'table')
306+
307+
local filtered_space_format = {}
308+
309+
for i, field_name in ipairs(field_names) do
310+
filtered_space_format[i] = get_field_format(space_format, field_name)
311+
if filtered_space_format[i] == nil then
312+
return nil, FilterFieldsError:new(
313+
'Space format doesn\'t contain field named %q', field_name
314+
)
315+
end
316+
end
317+
318+
return filtered_space_format
319+
end
320+
321+
function utils.format_result(rows, space, field_names)
322+
local result = {}
323+
local err
324+
local space_format = space:format()
325+
result.rows = rows
326+
327+
if field_names == nil then
328+
result.metadata = table.copy(space_format)
329+
return result
330+
end
331+
332+
result.metadata, err = filter_format_fields(space_format, field_names)
333+
334+
if err ~= nil then
335+
return nil, err
336+
end
337+
338+
return result
290339
end
291340

292341
local function flatten_obj(space_name, obj)

crud/delete.lua

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ local delete = {}
1414

1515
local DELETE_FUNC_NAME = '_crud.delete_on_storage'
1616

17-
local function delete_on_storage(space_name, key)
18-
dev_checks('string', '?')
17+
local function delete_on_storage(space_name, key, field_names)
18+
dev_checks('string', '?', '?table')
1919

2020
local space = box.space[space_name]
2121
if space == nil then
@@ -24,7 +24,10 @@ local function delete_on_storage(space_name, key)
2424

2525
-- add_space_schema_hash is false because
2626
-- reloading space format on router can't avoid delete error on storage
27-
return schema.wrap_box_space_func_result(false, space, 'delete', key)
27+
return schema.wrap_box_space_func_result(space, 'delete', {key}, {
28+
add_space_schema_hash = false,
29+
field_names = field_names,
30+
})
2831
end
2932

3033
function delete.init()
@@ -38,6 +41,7 @@ local function call_delete_on_router(space_name, key, opts)
3841
dev_checks('string', '?', {
3942
timeout = '?number',
4043
bucket_id = '?number|cdata',
44+
fields = '?table',
4145
})
4246

4347
opts = opts or {}
@@ -54,7 +58,7 @@ local function call_delete_on_router(space_name, key, opts)
5458
local bucket_id = sharding.key_get_bucket_id(key, opts.bucket_id)
5559
local storage_result, err = call.rw_single(
5660
bucket_id, DELETE_FUNC_NAME,
57-
{space_name, key},
61+
{space_name, key, opts.fields},
5862
{timeout = opts.timeout}
5963
)
6064

@@ -68,7 +72,7 @@ local function call_delete_on_router(space_name, key, opts)
6872

6973
local tuple = storage_result.res
7074

71-
return utils.format_result({tuple}, space)
75+
return utils.format_result({tuple}, space, opts.fields)
7276
end
7377

7478
--- Deletes tuple from the specified space by key
@@ -96,6 +100,7 @@ function delete.call(space_name, key, opts)
96100
checks('string', '?', {
97101
timeout = '?number',
98102
bucket_id = '?number|cdata',
103+
fields = '?table',
99104
})
100105

101106
return schema.wrap_func_reload(call_delete_on_router, space_name, key, opts)

crud/get.lua

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ local get = {}
1414

1515
local GET_FUNC_NAME = '_crud.get_on_storage'
1616

17-
local function get_on_storage(space_name, key)
18-
dev_checks('string', '?')
17+
local function get_on_storage(space_name, key, field_names)
18+
dev_checks('string', '?', '?table')
1919

2020
local space = box.space[space_name]
2121
if space == nil then
@@ -24,7 +24,10 @@ local function get_on_storage(space_name, key)
2424

2525
-- add_space_schema_hash is false because
2626
-- reloading space format on router can't avoid get error on storage
27-
return schema.wrap_box_space_func_result(false, space, 'get', key)
27+
return schema.wrap_box_space_func_result(space, 'get', {key}, {
28+
add_space_schema_hash = false,
29+
field_names = field_names,
30+
})
2831
end
2932

3033
function get.init()
@@ -38,6 +41,7 @@ local function call_get_on_router(space_name, key, opts)
3841
dev_checks('string', '?', {
3942
timeout = '?number',
4043
bucket_id = '?number|cdata',
44+
fields = '?table',
4145
})
4246

4347
opts = opts or {}
@@ -58,7 +62,7 @@ local function call_get_on_router(space_name, key, opts)
5862
-- a stale result.
5963
local storage_result, err = call.rw_single(
6064
bucket_id, GET_FUNC_NAME,
61-
{space_name, key},
65+
{space_name, key, opts.fields},
6266
{timeout = opts.timeout}
6367
)
6468

@@ -77,7 +81,7 @@ local function call_get_on_router(space_name, key, opts)
7781
tuple = nil
7882
end
7983

80-
return utils.format_result({tuple}, space)
84+
return utils.format_result({tuple}, space, opts.fields)
8185
end
8286

8387
--- Get tuple from the specified space by key
@@ -105,6 +109,7 @@ function get.call(space_name, key, opts)
105109
checks('string', '?', {
106110
timeout = '?number',
107111
bucket_id = '?number|cdata',
112+
fields = '?table',
108113
})
109114

110115
return schema.wrap_func_reload(call_get_on_router, space_name, key, opts)

crud/insert.lua

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ local INSERT_FUNC_NAME = '_crud.insert_on_storage'
1717
local function insert_on_storage(space_name, tuple, opts)
1818
dev_checks('string', 'table', {
1919
add_space_schema_hash = '?boolean',
20+
fields = '?table',
2021
})
2122

2223
opts = opts or {}
@@ -29,7 +30,10 @@ local function insert_on_storage(space_name, tuple, opts)
2930
-- add_space_schema_hash is true only in case of insert_object
3031
-- the only one case when reloading schema can avoid insert error
3132
-- is flattening object on router
32-
return schema.wrap_box_space_func_result(opts.add_space_schema_hash, space, 'insert', tuple)
33+
return schema.wrap_box_space_func_result(space, 'insert', {tuple}, {
34+
add_space_schema_hash = opts.add_space_schema_hash,
35+
field_names = opts.fields,
36+
})
3337
end
3438

3539
function insert.init()
@@ -44,6 +48,7 @@ local function call_insert_on_router(space_name, tuple, opts)
4448
timeout = '?number',
4549
bucket_id = '?number|cdata',
4650
add_space_schema_hash = '?boolean',
51+
fields = '?table',
4752
})
4853

4954
opts = opts or {}
@@ -60,6 +65,7 @@ local function call_insert_on_router(space_name, tuple, opts)
6065

6166
local insert_on_storage_opts = {
6267
add_space_schema_hash = opts.add_space_schema_hash,
68+
fields = opts.fields,
6369
}
6470

6571
local storage_result, err = call.rw_single(
@@ -79,7 +85,7 @@ local function call_insert_on_router(space_name, tuple, opts)
7985

8086
local tuple = storage_result.res
8187

82-
return utils.format_result({tuple}, space)
88+
return utils.format_result({tuple}, space, opts.fields)
8389
end
8490

8591
--- Inserts a tuple to the specified space
@@ -108,6 +114,7 @@ function insert.tuple(space_name, tuple, opts)
108114
timeout = '?number',
109115
bucket_id = '?number|cdata',
110116
add_space_schema_hash = '?boolean',
117+
fields = '?table',
111118
})
112119

113120
return schema.wrap_func_reload(call_insert_on_router, space_name, tuple, opts)

0 commit comments

Comments
 (0)