From caa347a1fbbbf0a3c4d0afbd36f59aaabb91af8d Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Mon, 19 Apr 2021 23:16:01 +0300 Subject: [PATCH 1/4] Jsonpath --- CHANGELOG.md | 1 + crud/common/utils.lua | 23 ++ crud/compare/conditions.lua | 2 +- crud/select/compat/select.lua | 12 +- crud/select/compat/select_old.lua | 12 +- crud/select/filters.lua | 102 +++++---- crud/select/plan.lua | 35 --- test/entrypoint/srv_select.lua | 62 ++++++ test/integration/select_test.lua | 126 +++++++++++ test/integration/updated_shema_test.lua | 7 +- test/unit/parse_conditions_test.lua | 88 +++++++- test/unit/select_filters_test.lua | 274 +++++++++++++++++++++++- test/unit/select_filters_uuid_test.lua | 34 +-- test/unit/select_plan_test.lua | 10 - 14 files changed, 657 insertions(+), 131 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11de79d7..fc1a9485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * Option flag `force_map_call` for `select()`/`pairs()` to disable the `bucket_id` computation from primary key. * `crud.min` and `crud.max` functions to find the minimum and maximum values in the specified index. +* Added support for jsonpath for select. ## [0.6.0] - 2021-03-29 diff --git a/crud/common/utils.lua b/crud/common/utils.lua index b7ec5de0..d1607918 100644 --- a/crud/common/utils.lua +++ b/crud/common/utils.lua @@ -205,6 +205,13 @@ local function determine_enabled_features() -- since Tarantool 2.4 enabled_tarantool_features.uuids = major >= 2 and (minor > 4 or minor == 4 and patch >= 1) + + -- since Tarantool 1.10 + enabled_tarantool_features.jsonpath_filters = major >= 2 or (major >= 1 and minor >= 10) + + -- since Tarantool 2.6.3 / 2.7.2 / 2.8.1 + enabled_tarantool_features.jsonpath_indexes = major >= 2 and ((minor >= 8 and patch >= 1) or + (minor >= 6 and patch >= 3) or (minor >= 7 and patch >= 2)) end function utils.tarantool_supports_fieldpaths() @@ -223,6 +230,22 @@ function utils.tarantool_supports_uuids() return enabled_tarantool_features.uuids end +function utils.tarantool_supports_jsonpath_filters() + if enabled_tarantool_features.jsonpath_filters == nil then + determine_enabled_features() + end + + return enabled_tarantool_features.jsonpath_filters +end + +function utils.tarantool_supports_jsonpath_indexes() + if enabled_tarantool_features.jsonpath_indexes == nil then + determine_enabled_features() + end + + return enabled_tarantool_features.jsonpath_indexes +end + local function add_nullable_fields_recursive(operations, operations_map, space_format, tuple, id) if id < 2 or tuple[id - 1] ~= box.NULL then return operations diff --git a/crud/compare/conditions.lua b/crud/compare/conditions.lua index 17396b6c..3d963650 100644 --- a/crud/compare/conditions.lua +++ b/crud/compare/conditions.lua @@ -63,7 +63,7 @@ for func_name, operator in pairs(cond_operators_by_func_names) do return new_condition({ operator = operator, operand = operand, - values = values + values = values, }) end end diff --git a/crud/select/compat/select.lua b/crud/select/compat/select.lua index 2d44f83b..f6318452 100644 --- a/crud/select/compat/select.lua +++ b/crud/select/compat/select.lua @@ -34,12 +34,6 @@ local function build_select_iterator(space_name, user_conditions, opts) return nil, SelectError:new("batch_size should be > 0") end - -- check conditions - local conditions, err = compare_conditions.parse(user_conditions) - if err ~= nil then - return nil, SelectError:new("Failed to parse conditions: %s", err) - end - local replicasets, err = vshard.router.routeall() if err ~= nil then return nil, SelectError:new("Failed to get all replicasets: %s", err) @@ -51,6 +45,12 @@ local function build_select_iterator(space_name, user_conditions, opts) end local space_format = space:format() + -- check conditions + local conditions, err = select_conditions.parse(user_conditions, space_format) + if err ~= nil then + return nil, SelectError:new("Failed to parse conditions: %s", err) + end + -- plan select local plan, err = select_plan.new(space, conditions, { first = opts.first, diff --git a/crud/select/compat/select_old.lua b/crud/select/compat/select_old.lua index 6e98b508..6586759a 100644 --- a/crud/select/compat/select_old.lua +++ b/crud/select/compat/select_old.lua @@ -86,12 +86,6 @@ local function build_select_iterator(space_name, user_conditions, opts) local batch_size = opts.batch_size or common.DEFAULT_BATCH_SIZE - -- check conditions - local conditions, err = compare_conditions.parse(user_conditions) - if err ~= nil then - return nil, SelectError:new("Failed to parse conditions: %s", err) - end - local replicasets, err = vshard.router.routeall() if err ~= nil then return nil, SelectError:new("Failed to get all replicasets: %s", err) @@ -103,6 +97,12 @@ local function build_select_iterator(space_name, user_conditions, opts) end local space_format = space:format() + -- check conditions + local conditions, err = select_conditions.parse(user_conditions, space_format) + if err ~= nil then + return nil, SelectError:new("Failed to parse conditions: %s", err) + end + -- plan select local plan, err = select_plan.new(space, conditions, { first = opts.first, diff --git a/crud/select/filters.lua b/crud/select/filters.lua index a11e42ce..27cc7edd 100644 --- a/crud/select/filters.lua +++ b/crud/select/filters.lua @@ -1,4 +1,3 @@ -local json = require('json') local errors = require('errors') local utils = require('crud.common.utils') @@ -6,7 +5,6 @@ local dev_checks = require('crud.common.dev_checks') local collations = require('crud.common.collations') local compare_conditions = require('crud.compare.conditions') -local ParseConditionsError = errors.new_class('ParseConditionsError', {capture_stack = false}) local GenFiltersError = errors.new_class('GenFiltersError', {capture_stack = false}) local filters = {} @@ -97,31 +95,42 @@ local function parse(space, conditions, opts) for i, condition in ipairs(conditions) do if i ~= opts.scan_condition_num then -- Index check (including one and multicolumn) - local fieldnos - local fields_types + local fields + local fields_types = {} local values_opts local index = space_indexes[condition.operand] if index ~= nil then - fieldnos = get_index_fieldnos(index) + fields = get_index_fieldnos(index) fields_types = get_index_fields_types(index) values_opts = get_values_opts(index) - elseif fieldnos_by_names[condition.operand] ~= nil then - local fiendno = fieldnos_by_names[condition.operand] - fieldnos = {fiendno} - local field_format = space_format[fiendno] - fields_types = {field_format.type} - local is_nullable = field_format.is_nullable == true + else + local fieldno = fieldnos_by_names[condition.operand] + + if fieldno ~= nil then + fields = {fieldno} + else + -- We assume this is jsonpath, so it is + -- not in fieldnos_by_name map. + fields = {condition.operand} + end + + local field_format = space_format[fieldno] + local is_nullable + + if field_format ~= nil then + fields_types = {field_format.type} + is_nullable = field_format.is_nullable == true + end + values_opts = { {is_nullable = is_nullable, collation = nil}, } - else - return nil, ParseConditionsError('No field or index is found for condition %s', json.encode(condition)) end table.insert(filter_conditions, { - fieldnos = fieldnos, + fields = fields, operator = condition.operator, values = condition.values, types = fields_types, @@ -156,12 +165,30 @@ end local PARSE_ARGS_TEMPLATE = 'local tuple = ...' local LIB_FUNC_HEADER_TEMPLATE = 'function M.%s(%s)' +local function format_path(path) + local path_type = type(path) + if path_type == 'number' then + return tostring(path) + elseif path_type == 'string' then + return ('%q'):format(path) + end + + assert(false, ('Unexpected format: %s'):format(path_type)) +end + local function concat_conditions(conditions, operator) return '(' .. table.concat(conditions, (' %s '):format(operator)) .. ')' end -local function get_field_variable_name(fieldno) - return string.format('field_%s', fieldno) +local function get_field_variable_name(field) + local field_type = type(field) + if field_type == 'number' then + field = tostring(field) + elseif field_type == 'string' then + field = string.gsub(field, '([().^$%[%]%+%-%*%?%%\'"])', '_') + end + + return string.format('field_%s', field) end local function get_eq_func_name(id) @@ -173,16 +200,17 @@ local function get_cmp_func_name(id) end local function gen_tuple_fields_def_code(filter_conditions) - -- get field numbers - local fieldnos_added = {} - local fieldnos = {} + -- get field names + local fields_added = {} + local fields = {} for _, cond in ipairs(filter_conditions) do for i = 1, #cond.values do - local fieldno = cond.fieldnos[i] - if not fieldnos_added[fieldno] then - table.insert(fieldnos, fieldno) - fieldnos_added[fieldno] = true + local field = cond.fields[i] + + if not fields_added[field] then + table.insert(fields, field) + fields_added[field] = true end end end @@ -190,21 +218,21 @@ local function gen_tuple_fields_def_code(filter_conditions) -- gen definitions for all used fields local fields_def_parts = {} - for _, fieldno in ipairs(fieldnos) do + for _, field in ipairs(fields) do table.insert(fields_def_parts, string.format( 'local %s = tuple[%s]', - get_field_variable_name(fieldno), fieldno + get_field_variable_name(field), format_path(field) )) end return table.concat(fields_def_parts, '\n') end -local function format_comp_with_value(fieldno, func_name, value) +local function format_comp_with_value(field, func_name, value) return string.format( '%s(%s, %s)', func_name, - get_field_variable_name(fieldno), + get_field_variable_name(field), format_value(value) ) end @@ -238,7 +266,7 @@ local function format_eq(cond) local values_opts = cond.values_opts or {} for j = 1, #cond.values do - local fieldno = cond.fieldnos[j] + local field = cond.fields[j] local value = cond.values[j] local value_type = cond.types[j] local value_opts = values_opts[j] or {} @@ -254,7 +282,7 @@ local function format_eq(cond) func_name = 'eq_uuid' end - table.insert(cond_strings, format_comp_with_value(fieldno, func_name, value)) + table.insert(cond_strings, format_comp_with_value(field, func_name, value)) end return cond_strings @@ -265,7 +293,7 @@ local function format_lt(cond) local values_opts = cond.values_opts or {} for j = 1, #cond.values do - local fieldno = cond.fieldnos[j] + local field = cond.fields[j] local value = cond.values[j] local value_type = cond.types[j] local value_opts = values_opts[j] or {} @@ -279,9 +307,10 @@ local function format_lt(cond) elseif value_type == 'uuid' then func_name = 'lt_uuid' end + func_name = add_strict_postfix(func_name, value_opts) - table.insert(cond_strings, format_comp_with_value(fieldno, func_name, value)) + table.insert(cond_strings, format_comp_with_value(field, func_name, value)) end return cond_strings @@ -366,10 +395,10 @@ local function gen_cmp_array_func_code(operator, func_name, cond, func_args_code return table.concat(func_code_lines, '\n') end -local function function_args_by_fieldnos(fieldnos) +local function function_args_by_field(fields) local arg_names = {} - for _, fieldno in ipairs(fieldnos) do - table.insert(arg_names, get_field_variable_name(fieldno)) + for _, field in ipairs(fields) do + table.insert(arg_names, get_field_variable_name(field)) end return table.concat(arg_names, ', ') end @@ -408,8 +437,8 @@ local function gen_filter_code(filter_conditions) table.insert(filter_code_parts, '') for i, cond in ipairs(filter_conditions) do - local args_fieldnos = { unpack(cond.fieldnos, 1, #cond.values) } - local func_args_code = function_args_by_fieldnos(args_fieldnos) + local args_fields = { unpack(cond.fields, 1, #cond.values) } + local func_args_code = function_args_by_field(args_fields) local library_func_name, library_func_code = gen_library_func(i, cond, func_args_code) table.insert(library_funcs_code_parts, library_func_code) @@ -623,6 +652,7 @@ function filters.gen_func(space, conditions, opts) scan_condition_num = opts.scan_condition_num, tarantool_iter = opts.tarantool_iter, }) + if err ~= nil then return nil, GenFiltersError:new("Failed to generate filters for specified conditions: %s", err) end diff --git a/crud/select/plan.lua b/crud/select/plan.lua index d529e681..f6dbf5c6 100644 --- a/crud/select/plan.lua +++ b/crud/select/plan.lua @@ -6,9 +6,7 @@ local dev_checks = require('crud.common.dev_checks') local select_plan = {} -local SelectPlanError = errors.new_class('SelectPlanError', {capture_stack = false}) local IndexTypeError = errors.new_class('IndexTypeError', {capture_stack = false}) -local ValidateConditionsError = errors.new_class('ValidateConditionsError', {capture_stack = false}) local FilterFieldsError = errors.new_class('FilterFieldsError', {capture_stack = false}) local function index_is_allowed(index) @@ -42,34 +40,6 @@ local function get_index_for_condition(space_indexes, space_format, condition) end end -local function validate_conditions(conditions, space_indexes, space_format) - local field_names = {} - for _, field_format in ipairs(space_format) do - field_names[field_format.name] = true - end - - local index_names = {} - - -- If we use # (not table.maxn), we may lose indexes, when user drop some indexes. - -- E.g: we have table with indexes id {1, 2, 3, nil, nil, 6}. - -- If we use #{1, 2, 3, nil, nil, 6} (== 3) we will lose index with id = 6. - -- See details: https://github.com/tarantool/crud/issues/103 - for i = 0, table.maxn(space_indexes) do - local index = space_indexes[i] - if index ~= nil then - index_names[index.name] = true - end - end - - for _, condition in ipairs(conditions) do - if index_names[condition.operand] == nil and field_names[condition.operand] == nil then - return false, ValidateConditionsError:new("No field or index %q found", condition.operand) - end - end - - return true -end - local function extract_sharding_key_from_scan_value(scan_value, scan_index, sharding_index) if #scan_value < #sharding_index.parts then return nil @@ -157,11 +127,6 @@ function select_plan.new(space, conditions, opts) local space_indexes = space.index local space_format = space:format() - local ok, err = validate_conditions(conditions, space_indexes, space_format) - if not ok then - return nil, SelectPlanError:new('Passed bad conditions: %s', err) - end - if conditions == nil then -- also cdata conditions = {} end diff --git a/test/entrypoint/srv_select.lua b/test/entrypoint/srv_select.lua index 15d08188..5e1fef6f 100755 --- a/test/entrypoint/srv_select.lua +++ b/test/entrypoint/srv_select.lua @@ -126,6 +126,68 @@ package.preload['customers-storage'] = function() unique = false, if_not_exists = true, }) + + local developers_space = box.schema.space.create('developers', { + format = { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'last_name', type = 'string'}, + {name = 'age', type = 'number'}, + {name = 'additional', type = 'any'}, + }, + if_not_exists = true, + engine = engine, + }) + + -- primary index + developers_space:create_index('id_index', { + parts = { 'id' }, + if_not_exists = true, + }) + + developers_space:create_index('bucket_id', { + parts = { 'bucket_id' }, + unique = false, + if_not_exists = true, + }) + + local cars_space = box.schema.space.create('cars', { + format = { + {name = 'id', type = 'map'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'age', type = 'number'}, + {name = 'manufacturer', type = 'string'}, + {name = 'data', type = 'map'} + }, + if_not_exists = true, + engine = engine, + }) + + -- primary index + cars_space:create_index('id_index', { + parts = { + {1, 'unsigned', path = 'dev_id'} + }, + if_not_exists = true, + }) + + cars_space:create_index('bucket_id', { + parts = { 'bucket_id' }, + unique = false, + if_not_exists = true, + }) + + if crud_utils.tarantool_supports_jsonpath_indexes() then + cars_space:create_index('data_index', { + parts = { + {5, 'str', path = 'car.color'}, + --{5, 'str', path = 'car.model'} + }, + unique = false, + if_not_exists = true, + }) + end end, } end diff --git a/test/integration/select_test.lua b/test/integration/select_test.lua index a75ffb3c..2c84a572 100644 --- a/test/integration/select_test.lua +++ b/test/integration/select_test.lua @@ -31,6 +31,7 @@ pgroup:set_after_all(function(g) helpers.stop_cluster(g.cluster) end) pgroup:set_before_each(function(g) helpers.truncate_space_on_cluster(g.cluster, 'customers') + helpers.truncate_space_on_cluster(g.cluster, 'developers') end) @@ -1199,3 +1200,128 @@ pgroup:add('test_select_force_map_call', function(g) table.sort(objects, function(obj1, obj2) return obj1.bucket_id < obj2.bucket_id end) t.assert_equals(objects, customers) end) + +pgroup:add('test_jsonpath', function(g) + t.skip_if(not crud_utils.tarantool_supports_jsonpath_filters(), "Jsonpath is not supported on Tarantool < 1.10") + + helpers.insert_objects(g, 'developers', { + { + id = 1, name = "Alexey", last_name = "Smith", + age = 20, additional = { a = { b = 140 } }, + }, { + id = 2, name = "Sergey", last_name = "Choppa", + age = 21, additional = { a = { b = 120 } }, + }, { + id = 3, name = "Mikhail", last_name = "Crossman", + age = 42, additional = {}, + }, { + id = 4, name = "Pavel", last_name = "White", + age = 51, additional = { a = { b = 50 } }, + }, { + id = 5, name = "Tatyana", last_name = "May", + age = 17, additional = { a = 55 }, + }, + }) + + local result, err = g.cluster.main_server.net_box:call('crud.select', + {'developers', {{'>=', '[5]', 40}}, {fields = {'name', 'last_name'}}}) + t.assert_equals(err, nil) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + local expected_objects = { + {id = 3, name = "Mikhail", last_name = "Crossman"}, + {id = 4, name = "Pavel", last_name = "White"}, + } + t.assert_equals(objects, expected_objects) + + local result, err = g.cluster.main_server.net_box:call('crud.select', + {'developers', {{'<', '["age"]', 21}}, {fields = {'name', 'last_name'}}}) + t.assert_equals(err, nil) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + local expected_objects = { + {id = 1, name = "Alexey", last_name = "Smith"}, + {id = 5, name = "Tatyana", last_name = "May"}, + } + t.assert_equals(objects, expected_objects) + + local result, err = g.cluster.main_server.net_box:call('crud.select', + {'developers', {{'>=', '[6].a.b', 55}}, {fields = {'name', 'last_name'}}}) + t.assert_equals(err, nil) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + local expected_objects = { + {id = 1, name = "Alexey", last_name = "Smith"}, + {id = 2, name = "Sergey", last_name = "Choppa"}, + } + t.assert_equals(objects, expected_objects) +end) + +pgroup:add('test_jsonpath_index_field', function(g) + t.skip_if( + not crud_utils.tarantool_supports_jsonpath_indexes(), + "Jsonpath indexes supported since 2.6.3/2.7.2/2.8.1" + ) + + helpers.insert_objects(g, 'cars', { + { + id = {dev_id = 1}, age = 16, manufacturer = 'VAG', + data = {car = { model = 'BMW', color = 'Black' }}, + }, { + id = {dev_id = 2}, age = 41, manufacturer = 'FIAT', + data = {car = { model = 'Cadillac', color = 'White' }}, + }, { + id = {dev_id = 3}, age = 25, manufacturer = 'Ford', + data = {car = { model = 'BMW', color = 'Yellow' }}, + }, { + id = {dev_id = 4}, age = 37, manufacturer = 'General Motors', + data = {car = { model = 'Mercedes', color = 'Yellow' }}, + }, + }) + + local result, err = g.cluster.main_server.net_box:call('crud.select', + {'cars', {{'>=', 'id_index', 3}}, {fields = {'age'}}}) + t.assert_equals(err, nil) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + local expected_objects = { + {id = 3, age = 25}, + {id = 4, age = 37}, + } + t.assert_equals(objects, expected_objects) + + local result, err = g.cluster.main_server.net_box:call('crud.select', + {'cars', {{'==', '["id"]', 1}}}) + t.assert_equals(err, nil) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + local expected_objects = {{ + id = 1, age = 16, bucket_id = 477, manufacturer = 'VAG', + data = {car = { model = 'BMW', color = 'Black' }}, + }} + + t.assert_equals(objects, expected_objects) + + local result, err = g.cluster.main_server.net_box:call('crud.select', + {'cars', {{'>=', '["id"]', 2}, {'>=', 'data_index', 'String'}}}) + --t.assert_equals(err, nil) + + --local objects = crud.unflatten_rows(result.rows, result.metadata) + local expected_objects = {{ + id = 2, age = 41, bucket_id = 477, manufacturer = 'FIAT', + data = {car = { model = 'Cadillac', color = 'White' }}, + }} + + --t.assert_equals(objects, expected_objects) + + local result, err = g.cluster.main_server.net_box:call('crud.select', + {'cars', {{'==', 'data_index', {'Yellow', 'Mercedes'}}}}) + t.assert_equals(err, nil) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + local expected_objects = {{ + id = 4, age = 37, bucket_id = 1161, manufacturer = 'General Motors', + data = {car = {color = "Yellow", model = "Mercedes"}}}, + } + t.assert_equals(objects, expected_objects) +end) diff --git a/test/integration/updated_shema_test.lua b/test/integration/updated_shema_test.lua index 035cc0d5..b4f57d60 100644 --- a/test/integration/updated_shema_test.lua +++ b/test/integration/updated_shema_test.lua @@ -594,14 +594,13 @@ pgroup:add('test_select_field_added', function(g) server.net_box:call('create_bucket_id_index') end) - -- unknown field error + -- unknown field (no results) local obj, err = g.cluster.main_server.net_box:call( 'crud.select', {'customers', {{'==', 'extra', 'EXTRRRRA'}}} ) - t.assert_equals(obj, nil) - t.assert_is_not(err, nil) - t.assert_str_contains(err.err, "No field or index \"extra\" found") + t.assert_equals(obj.rows, {}) + t.assert_equals(err, nil) -- add extra field helpers.call_on_servers(g.cluster, {'s1-master', 's2-master'}, function(server) diff --git a/test/unit/parse_conditions_test.lua b/test/unit/parse_conditions_test.lua index b82b8643..131e13e6 100644 --- a/test/unit/parse_conditions_test.lua +++ b/test/unit/parse_conditions_test.lua @@ -14,7 +14,7 @@ g.test_parse = function() {'>=', 'f', {3, 3, 4}}, } - local conditions, err = compare_conditions.parse(user_conditions) + local conditions, err = select_conditions.parse(user_conditions) t.assert(err == nil) t.assert_equals(conditions, { cond_funcs.eq('aaaa', nil), @@ -30,7 +30,7 @@ g.test_parse_errors = function() -- conditions are no table local user_conditions = 'bbb = {12}' - local _, err = compare_conditions.parse(user_conditions) + local _, err = select_conditions.parse(user_conditions) t.assert_str_contains(err.err, 'Conditions should be table, got "string"') -- condition is no table @@ -39,7 +39,7 @@ g.test_parse_errors = function() 'bbb = {12}', } - local _, err = compare_conditions.parse(user_conditions) + local _, err = select_conditions.parse(user_conditions) t.assert_str_contains(err.err, 'Each condition should be table, got "string" (condition 2)') -- condition len is wrong @@ -48,7 +48,7 @@ g.test_parse_errors = function() {'='}, } - local _, err = compare_conditions.parse(user_conditions) + local _, err = select_conditions.parse(user_conditions) t.assert_str_contains( err.err, 'Each condition should be {"", "", } (condition 2)' @@ -59,7 +59,7 @@ g.test_parse_errors = function() {'=', 'bb', 1, 2}, } - local _, err = compare_conditions.parse(user_conditions) + local _, err = select_conditions.parse(user_conditions) t.assert_str_contains( err.err, 'Each condition should be {"", "", } (condition 2)' @@ -71,7 +71,7 @@ g.test_parse_errors = function() {3, 'bb', 1}, } - local _, err = compare_conditions.parse(user_conditions) + local _, err = select_conditions.parse(user_conditions) t.assert_str_contains( err.err, 'condition[1] should be string, got "number" (condition 2)' @@ -83,7 +83,7 @@ g.test_parse_errors = function() {'===', 'bb', 1}, } - local _, err = compare_conditions.parse(user_conditions) + local _, err = select_conditions.parse(user_conditions) t.assert_str_contains( err.err, 'condition[1] "===" isn\'t a valid condition oprator, (condition 2)' @@ -95,9 +95,81 @@ g.test_parse_errors = function() {'=', 3, 1}, } - local _, err = compare_conditions.parse(user_conditions) + local _, err = select_conditions.parse(user_conditions) t.assert_str_contains( err.err, 'condition[2] should be string, got "number" (condition 2)' ) end + +g.test_jsonpath_parse = function() + t.skip_if(not crud_utils.tarantool_supports_jsonpath_filters(), "Jsonpath is not supported on Tarantool < 1.10") + + local user_conditions = { + {'==', '[\'name\']', 'Alexey'}, + {'=', '["name"].a.b', 'Sergey'}, + {'<', '["year"]["field_1"][\'field_2\']', 2021}, + {'<=', '[2].a', {1, 2, 3}}, + {'>', '[2]', 'Jackson'}, + {'>=', '[\'year\'].a["f2"][\'f3\']', 2017}, + } + + local space_format = { + {name = 'name', type = 'string'}, + {name = 'surname', type = 'any'}, + {name = 'year', type ='unsigned'}, + } + + local conditions, err = select_conditions.parse(user_conditions, space_format) + t.assert(err == nil) + t.assert_equals(conditions, { + cond_funcs.eq('name', 'Alexey'), + cond_funcs.eq('name', 'Sergey', '[1].a.b'), + cond_funcs.lt('year', 2021, '[3]["field_1"][\'field_2\']'), + cond_funcs.le('surname', {1, 2, 3}, '[2].a'), + cond_funcs.gt('surname', 'Jackson'), + cond_funcs.ge('year', 2017, '[3].a["f2"][\'f3\']'), + }) +end + +g.test_jsonpath_parse_errors = function() + t.skip_if(not crud_utils.tarantool_supports_jsonpaths(), "Jsonpath is not supported on Tarantool < 2") + local space_format = { + {name = 'name', type = 'string'}, + {name = 'surname', type = 'any'}, + {name = 'year', type ='unsigned'}, + } + + -- bad jsonpath + local user_conditions = { + {'==', '1].a', 'Alexey'}, + } + + local _, err = select_conditions.parse(user_conditions, space_format) + t.assert_str_contains( + err.err, + 'Invalid jsonpath format' + ) + + -- non-existen fieldno + local user_conditions = { + {'==', '[4].a.b', 88}, + } + + local _, err = select_conditions.parse(user_conditions, space_format) + t.assert_str_contains( + err.err, + 'Space doesn\'t contains field [4]' + ) + + -- non-existen field + local user_conditions = { + {'==', '[\'bucket_id\']', 41}, + } + + local _, err = select_conditions.parse(user_conditions, space_format) + t.assert_str_contains( + err.err, + 'Space doesn\'t contains field [\'bucket_id\']' + ) +end diff --git a/test/unit/select_filters_test.lua b/test/unit/select_filters_test.lua index c788b0f1..2e7e0d24 100644 --- a/test/unit/select_filters_test.lua +++ b/test/unit/select_filters_test.lua @@ -83,8 +83,13 @@ g.test_parse = function() -- age filter (early exit is possible) local age_filter_condition = filter_conditions[1] t.assert_type(age_filter_condition, 'table') +<<<<<<< HEAD t.assert_equals(age_filter_condition.fieldnos, {5}) t.assert_equals(age_filter_condition.operator, compare_conditions.operators.LT) +======= + t.assert_equals(age_filter_condition.fields, {5}) + t.assert_equals(age_filter_condition.operator, select_conditions.operators.LT) +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) t.assert_equals(age_filter_condition.values, {40}) t.assert_equals(age_filter_condition.types, {'number'}) t.assert_equals(age_filter_condition.early_exit_is_possible, true) @@ -92,8 +97,13 @@ g.test_parse = function() -- full_name filter local full_name_filter_condition = filter_conditions[2] t.assert_type(full_name_filter_condition, 'table') +<<<<<<< HEAD t.assert_equals(full_name_filter_condition.fieldnos, {3, 4}) t.assert_equals(full_name_filter_condition.operator, compare_conditions.operators.EQ) +======= + t.assert_equals(full_name_filter_condition.fields, {3, 4}) + t.assert_equals(full_name_filter_condition.operator, select_conditions.operators.EQ) +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) t.assert_equals(full_name_filter_condition.values, {'Ivan', 'Ivanov'}) t.assert_equals(full_name_filter_condition.types, {'string', 'string'}) t.assert_equals(full_name_filter_condition.early_exit_is_possible, false) @@ -115,8 +125,13 @@ g.test_parse = function() -- has_a_car filter local has_a_car_filter_condition = filter_conditions[3] t.assert_type(has_a_car_filter_condition, 'table') +<<<<<<< HEAD t.assert_equals(has_a_car_filter_condition.fieldnos, {7}) t.assert_equals(has_a_car_filter_condition.operator, compare_conditions.operators.EQ) +======= + t.assert_equals(has_a_car_filter_condition.fields, {7}) + t.assert_equals(has_a_car_filter_condition.operator, select_conditions.operators.EQ) +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) t.assert_equals(has_a_car_filter_condition.values, {true}) t.assert_equals(has_a_car_filter_condition.types, {'boolean'}) t.assert_equals(has_a_car_filter_condition.early_exit_is_possible, false) @@ -130,8 +145,13 @@ end g.test_one_condition_number = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {1}, operator = compare_conditions.operators.EQ, +======= + fields = {1}, + operator = select_conditions.operators.EQ, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {3}, types = {'number'}, early_exit_is_possible = true, @@ -167,8 +187,13 @@ end g.test_one_condition_boolean = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {1}, operator = compare_conditions.operators.EQ, +======= + fields = {1}, + operator = select_conditions.operators.EQ, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {true}, types = {'boolean'}, early_exit_is_possible = true, @@ -207,8 +232,13 @@ end g.test_one_condition_string = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {2}, operator = compare_conditions.operators.GT, +======= + fields = {2}, + operator = select_conditions.operators.GT, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {'dddddddd'}, types = {'string'}, early_exit_is_possible = true, @@ -248,15 +278,25 @@ end g.test_two_conditions = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {1}, operator = compare_conditions.operators.EQ, +======= + fields = {1}, + operator = select_conditions.operators.EQ, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {4}, types = {'number'}, early_exit_is_possible = true, }, { +<<<<<<< HEAD fieldnos = {3}, operator = compare_conditions.operators.GE, +======= + fields = {3}, + operator = select_conditions.operators.GE, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {"dddddddd"}, types = {'string'}, early_exit_is_possible = false, @@ -304,25 +344,35 @@ end g.test_two_conditions_non_nullable = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {2, 3}, operator = compare_conditions.operators.GE, +======= + fields = {2, 3}, + operator = select_conditions.operators.GE, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {"test", 5}, types = {'string', 'number'}, early_exit_is_possible = false, values_opts = { {is_nullable = false}, {is_nullable = true}, - } + }, }, { +<<<<<<< HEAD fieldnos = {1}, operator = compare_conditions.operators.LT, +======= + fields = {1}, + operator = select_conditions.operators.LT, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {3}, types = {'number'}, early_exit_is_possible = true, values_opts = { {is_nullable = false}, - } + }, }, } @@ -372,15 +422,20 @@ end g.test_one_condition_with_nil_value = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {2, 3}, operator = compare_conditions.operators.GE, +======= + fields = {2, 3}, + operator = select_conditions.operators.GE, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {"test"}, types = {'string', 'number'}, early_exit_is_possible = false, values_opts = { {is_nullable = false}, {is_nullable = true}, - } + }, }, } @@ -415,8 +470,13 @@ end g.test_unicode_collation = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {1, 2, 3, 4}, operator = compare_conditions.operators.EQ, +======= + fields = {1, 2, 3, 4}, + operator = select_conditions.operators.EQ, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {'A', 'Á', 'Ä', 6}, types = {'string', 'string', 'string', 'number'}, early_exit_is_possible = false, @@ -424,7 +484,7 @@ g.test_unicode_collation = function() {collation='unicode'}, {collation='unicode_ci'}, {collation='unicode_ci'}, - } + }, }, } @@ -461,8 +521,13 @@ end g.test_binary_and_none_collation = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {1, 2, 3}, operator = compare_conditions.operators.EQ, +======= + fields = {1, 2, 3}, + operator = select_conditions.operators.EQ, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {'A', 'B', 'C'}, types = {'string', 'string', 'string'}, early_exit_is_possible = false, @@ -470,7 +535,7 @@ g.test_binary_and_none_collation = function() {collation='none'}, {collation='binary'}, {collation=nil}, - } + }, }, } @@ -506,15 +571,20 @@ end g.test_null_as_last_value_eq = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {1, 2}, operator = compare_conditions.operators.EQ, +======= + fields = {1, 2}, + operator = select_conditions.operators.EQ, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {'a', box.NULL}, types = {'string', 'string'}, early_exit_is_possible = false, values_opts = { nil, {is_nullable = true}, - } + }, }, } @@ -547,15 +617,20 @@ end g.test_null_as_last_value_gt = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {1, 2}, operator = compare_conditions.operators.GT, +======= + fields = {1, 2}, + operator = select_conditions.operators.GT, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {'a', box.NULL}, types = {'string', 'string'}, early_exit_is_possible = false, values_opts = { nil, {is_nullable = true}, - } + }, }, } @@ -594,15 +669,20 @@ end g.test_null_as_last_value_gt_non_nullable = function() local filter_conditions = { { +<<<<<<< HEAD fieldnos = {1, 2}, operator = compare_conditions.operators.GT, +======= + fields = {1, 2}, + operator = select_conditions.operators.GT, +>>>>>>> f143377 (Remove field validation and combine with jsonpaths) values = {'a', box.NULL}, types = {'string', 'string'}, early_exit_is_possible = false, values_opts = { nil, {is_nullable = false}, - } + }, }, } @@ -638,4 +718,182 @@ return M]] t.assert_equals(filter_func({'a', box.NULL}), false) -- box.NULL > box.NULL is false end +g.test_jsonpath_fields_eq = function() + local filter_conditions = { + { + fields = {'[2].a.b'}, + operator = select_conditions.operators.EQ, + values = {55}, + types = {'number'}, + early_exit_is_possible = true, + }, + } + + local expected_code = [[local tuple = ... + +local field__2__a_b = tuple["[2].a.b"] + +if not eq_1(field__2__a_b) then return false, true end + +return true, false]] + + local expected_library_code = [[local M = {} + +function M.eq_1(field__2__a_b) + return (eq(field__2__a_b, 55)) +end + +return M]] + + local filter_code = select_filters.internal.gen_filter_code(filter_conditions) + t.assert_equals(filter_code.code, expected_code) + t.assert_equals(filter_code.library, expected_library_code) + + local filter_func = select_filters.internal.compile(filter_code) + t.assert_equals({ filter_func(box.tuple.new({3, {a = {b = 55}}, 1})) }, {true, false}) + t.assert_equals({ filter_func(box.tuple.new({3, {a = {b = 23}}, 1})) }, {false, true}) + t.assert_equals({ filter_func(box.tuple.new({3, {a = {c = 55}}, 1})) }, {false, true}) + t.assert_equals({ filter_func(box.tuple.new({3, nil, 1})) }, {false, true}) +end + +g.test_jsonpath_fields_ge = function() + local filter_conditions = { + { + fields = {'[2]["field_2"]'}, + operator = select_conditions.operators.GT, + values = {23}, + types = {'number'}, + early_exit_is_possible = true, + }, + } + + local expected_code = [[local tuple = ... + +local field__2___field_2__ = tuple["[2][\"field_2\"]"] + +if not cmp_1(field__2___field_2__) then return false, true end + +return true, false]] + + local expected_library_code = [[local M = {} + +function M.cmp_1(field__2___field_2__) + if lt(field__2___field_2__, 23) then return false end + if not eq(field__2___field_2__, 23) then return true end + + return false +end + +return M]] + + local filter_code = select_filters.internal.gen_filter_code(filter_conditions) + t.assert_equals(filter_code.code, expected_code) + t.assert_equals(filter_code.library, expected_library_code) + + local filter_func = select_filters.internal.compile(filter_code) + t.assert_equals({ filter_func(box.tuple.new({3, {field_2 = 55}, 1})) }, {true, false}) + t.assert_equals({ filter_func(box.tuple.new({3, {field_2 = 24, field_3 = 32}, nil})) }, {true, false}) + t.assert_equals({ filter_func(box.tuple.new({{field_2 = 59}, 173, 1})) }, {false, true}) +end + +g.test_several_jsonpath = function() + local filter_conditions = { + { + fields = {'[3]["f2"][\'f3\']', '[4].f3'}, + operator = select_conditions.operators.EQ, + values = {'a', 'b'}, + types = {'string', 'string'}, + early_exit_is_possible = true, + }, + } + + local expected_code = [[local tuple = ... + +local field__3___f2____f3__ = tuple["[3][\"f2\"]['f3']"] +local field__4__f3 = tuple["[4].f3"] + +if not eq_1(field__3___f2____f3__, field__4__f3) then return false, true end + +return true, false]] + + local expected_library_code = [[local M = {} + +function M.eq_1(field__3___f2____f3__, field__4__f3) + return (eq(field__3___f2____f3__, "a") and eq(field__4__f3, "b")) +end + +return M]] + + local filter_code = select_filters.internal.gen_filter_code(filter_conditions) + t.assert_equals(filter_code.code, expected_code) + t.assert_equals(filter_code.library, expected_library_code) + + local filter_func = select_filters.internal.compile(filter_code) + t.assert_equals({ filter_func(box.tuple.new({1, 2, {f2 = {f3 = "a"}}, {f3 = "b"}})) }, {true, false}) + t.assert_equals({ filter_func(box.tuple.new({1, 2, {f3 = "b"}}, {f2 = {f3 = "a"}})) }, {false, true}) + t.assert_equals({ filter_func(box.tuple.new({1, 2, {f2 = {f3 = "a"}}, "b"})) }, {false, true}) +end + +g.test_jsonpath_two_conditions = function() + local filter_conditions = { + { + fields = {'[2].fld_1', '[3]["f_1"]'}, + operator = select_conditions.operators.GE, + values = {"jsonpath_test", 23}, + types = {'string', 'number'}, + early_exit_is_possible = false, + }, + { + fields = {'[1].field_1'}, + operator = select_conditions.operators.LT, + values = {8}, + types = {'number'}, + early_exit_is_possible = true, + }, + } + + local expected_code = [[local tuple = ... + +local field__2__fld_1 = tuple["[2].fld_1"] +local field__3___f_1__ = tuple["[3][\"f_1\"]"] +local field__1__field_1 = tuple["[1].field_1"] + +if not cmp_1(field__2__fld_1, field__3___f_1__) then return false, false end +if not cmp_2(field__1__field_1) then return false, true end + +return true, false]] + + local expected_library_code = [[local M = {} + +function M.cmp_1(field__2__fld_1, field__3___f_1__) + if lt(field__2__fld_1, "jsonpath_test") then return false end + if not eq(field__2__fld_1, "jsonpath_test") then return true end + + if lt(field__3___f_1__, 23) then return false end + if not eq(field__3___f_1__, 23) then return true end + + return true +end + +function M.cmp_2(field__1__field_1) + if lt(field__1__field_1, 8) then return true end + if not eq(field__1__field_1, 8) then return false end + + return false +end + +return M]] + + local filter_code = select_filters.internal.gen_filter_code(filter_conditions) + t.assert_equals(filter_code.code, expected_code) + t.assert_equals(filter_code.library, expected_library_code) + + local filter_func = select_filters.internal.compile(filter_code) + t.assert_equals({ filter_func(box.tuple.new({{field_1 = 7}, {fld_1 = "jsonpath_test"}, {f_1 = 23}})) }, {true, false}) + t.assert_equals({ filter_func(box.tuple.new({{field_1 = 8}, {fld_1 = "jsonpath_test"}, {f_1 = 23}})) }, {false, true}) + t.assert_equals({ filter_func(box.tuple.new({{field_1 = 5}, {f_1 = "jsonpath_test"}, {fld_1 = 23}})) }, {false, false}) + t.assert_equals({ filter_func(box.tuple.new({{field_1 = 5, f2 = 3}, {fld_1 = "jsonpath_test"}, 23})) }, {false, false}) +end + + -- luacheck: pop diff --git a/test/unit/select_filters_uuid_test.lua b/test/unit/select_filters_uuid_test.lua index 3228eb8a..f0819599 100644 --- a/test/unit/select_filters_uuid_test.lua +++ b/test/unit/select_filters_uuid_test.lua @@ -73,8 +73,8 @@ g.test_parse = function() -- uuid filter (early exit is possible) local uuid_filter_condition = filter_conditions[1] t.assert_type(uuid_filter_condition, 'table') - t.assert_equals(uuid_filter_condition.fieldnos, {1}) - t.assert_equals(uuid_filter_condition.operator, compare_conditions.operators.LT) + t.assert_equals(uuid_filter_condition.fields, {1}) + t.assert_equals(uuid_filter_condition.operator, select_conditions.operators.LT) t.assert_equals(uuid_filter_condition.values, {uuid2}) t.assert_equals(uuid_filter_condition.types, {'uuid'}) t.assert_equals(uuid_filter_condition.early_exit_is_possible, true) @@ -82,8 +82,8 @@ g.test_parse = function() -- name filter local name_filter_condition = filter_conditions[2] t.assert_type(name_filter_condition, 'table') - t.assert_equals(name_filter_condition.fieldnos, {3}) - t.assert_equals(name_filter_condition.operator, compare_conditions.operators.EQ) + t.assert_equals(name_filter_condition.fields, {3}) + t.assert_equals(name_filter_condition.operator, select_conditions.operators.EQ) t.assert_equals(name_filter_condition.values, {'Charlie'}) t.assert_equals(name_filter_condition.types, {'string'}) t.assert_equals(name_filter_condition.early_exit_is_possible, false) @@ -91,8 +91,8 @@ g.test_parse = function() -- has_a_car filter local category_id_filter_condition = filter_conditions[3] t.assert_type(category_id_filter_condition, 'table') - t.assert_equals(category_id_filter_condition.fieldnos, {4}) - t.assert_equals(category_id_filter_condition.operator, compare_conditions.operators.EQ) + t.assert_equals(category_id_filter_condition.fields, {4}) + t.assert_equals(category_id_filter_condition.operator, select_conditions.operators.EQ) t.assert_equals(category_id_filter_condition.values, {uuid3}) t.assert_equals(category_id_filter_condition.types, {'uuid'}) t.assert_equals(category_id_filter_condition.early_exit_is_possible, false) @@ -111,8 +111,8 @@ g.test_one_condition_uuid = function() local filter_conditions = { { - fieldnos = {1}, - operator = compare_conditions.operators.EQ, + fields = {1}, + operator = select_conditions.operators.EQ, values = {uuid1}, types = {'uuid'}, early_exit_is_possible = true, @@ -153,8 +153,8 @@ g.test_one_condition_uuid_gt = function() local filter_conditions = { { - fieldnos = {1}, - operator = compare_conditions.operators.GT, + fields = {1}, + operator = select_conditions.operators.GT, values = {uuid1}, types = {'uuid'}, early_exit_is_possible = true, @@ -198,15 +198,15 @@ g.test_one_condition_uuid_with_nil_value = function() local filter_conditions = { { - fieldnos = {1, 3}, - operator = compare_conditions.operators.GE, + fields = {1, 3}, + operator = select_conditions.operators.GE, values = {uuid1}, types = {'uuid', 'string'}, early_exit_is_possible = false, values_opts = { {is_nullable = false}, {is_nullable = true}, - } + }, }, } @@ -246,15 +246,15 @@ g.test_two_conditions_uuid = function() local filter_conditions = { { - fieldnos = {2}, - operator = compare_conditions.operators.EQ, + fields = {2}, + operator = select_conditions.operators.EQ, values = {'Charlie'}, types = {'string'}, early_exit_is_possible = true, }, { - fieldnos = {3}, - operator = compare_conditions.operators.GE, + fields = {3}, + operator = select_conditions.operators.GE, values = {uuid2:str()}, types = {'uuid'}, early_exit_is_possible = false, diff --git a/test/unit/select_plan_test.lua b/test/unit/select_plan_test.lua index dfe4d570..0d3f3beb 100644 --- a/test/unit/select_plan_test.lua +++ b/test/unit/select_plan_test.lua @@ -69,16 +69,6 @@ g.after_all(function() box.space.customers:drop() end) -g.test_bad_operand_name = function() - local plan, err = select_plan.new(box.space.customers, { - cond_funcs.gt('non-existent-field-index', 20), - }) - - t.assert_equals(plan, nil) - t.assert(err ~= nil) - t.assert_str_contains(err.err, 'No field or index "non-existent-field-index" found') -end - g.test_indexed_field = function() -- select by indexed field local conditions = { cond_funcs.gt('age', 20) } From 2b30e8ace9883899cf1807642bf5d985bae53cb8 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Wed, 26 May 2021 20:27:31 +0300 Subject: [PATCH 2/4] Fixes in tests --- crud/common/utils.lua | 22 ----- crud/select/compat/select.lua | 2 +- crud/select/compat/select_old.lua | 2 +- test/entrypoint/srv_select.lua | 37 -------- test/integration/select_test.lua | 71 -------------- test/unit/parse_conditions_test.lua | 62 ++----------- test/unit/select_filters_test.lua | 122 +++++-------------------- test/unit/select_filters_uuid_test.lua | 16 ++-- 8 files changed, 40 insertions(+), 294 deletions(-) diff --git a/crud/common/utils.lua b/crud/common/utils.lua index d1607918..a7d13046 100644 --- a/crud/common/utils.lua +++ b/crud/common/utils.lua @@ -206,12 +206,6 @@ local function determine_enabled_features() -- since Tarantool 2.4 enabled_tarantool_features.uuids = major >= 2 and (minor > 4 or minor == 4 and patch >= 1) - -- since Tarantool 1.10 - enabled_tarantool_features.jsonpath_filters = major >= 2 or (major >= 1 and minor >= 10) - - -- since Tarantool 2.6.3 / 2.7.2 / 2.8.1 - enabled_tarantool_features.jsonpath_indexes = major >= 2 and ((minor >= 8 and patch >= 1) or - (minor >= 6 and patch >= 3) or (minor >= 7 and patch >= 2)) end function utils.tarantool_supports_fieldpaths() @@ -230,22 +224,6 @@ function utils.tarantool_supports_uuids() return enabled_tarantool_features.uuids end -function utils.tarantool_supports_jsonpath_filters() - if enabled_tarantool_features.jsonpath_filters == nil then - determine_enabled_features() - end - - return enabled_tarantool_features.jsonpath_filters -end - -function utils.tarantool_supports_jsonpath_indexes() - if enabled_tarantool_features.jsonpath_indexes == nil then - determine_enabled_features() - end - - return enabled_tarantool_features.jsonpath_indexes -end - local function add_nullable_fields_recursive(operations, operations_map, space_format, tuple, id) if id < 2 or tuple[id - 1] ~= box.NULL then return operations diff --git a/crud/select/compat/select.lua b/crud/select/compat/select.lua index f6318452..f46f725d 100644 --- a/crud/select/compat/select.lua +++ b/crud/select/compat/select.lua @@ -46,7 +46,7 @@ local function build_select_iterator(space_name, user_conditions, opts) local space_format = space:format() -- check conditions - local conditions, err = select_conditions.parse(user_conditions, space_format) + local conditions, err = compare_conditions.parse(user_conditions, space_format) if err ~= nil then return nil, SelectError:new("Failed to parse conditions: %s", err) end diff --git a/crud/select/compat/select_old.lua b/crud/select/compat/select_old.lua index 6586759a..6ace4b3e 100644 --- a/crud/select/compat/select_old.lua +++ b/crud/select/compat/select_old.lua @@ -98,7 +98,7 @@ local function build_select_iterator(space_name, user_conditions, opts) local space_format = space:format() -- check conditions - local conditions, err = select_conditions.parse(user_conditions, space_format) + local conditions, err = compare_conditions.parse(user_conditions, space_format) if err ~= nil then return nil, SelectError:new("Failed to parse conditions: %s", err) end diff --git a/test/entrypoint/srv_select.lua b/test/entrypoint/srv_select.lua index 5e1fef6f..98081b5d 100755 --- a/test/entrypoint/srv_select.lua +++ b/test/entrypoint/srv_select.lua @@ -151,43 +151,6 @@ package.preload['customers-storage'] = function() unique = false, if_not_exists = true, }) - - local cars_space = box.schema.space.create('cars', { - format = { - {name = 'id', type = 'map'}, - {name = 'bucket_id', type = 'unsigned'}, - {name = 'age', type = 'number'}, - {name = 'manufacturer', type = 'string'}, - {name = 'data', type = 'map'} - }, - if_not_exists = true, - engine = engine, - }) - - -- primary index - cars_space:create_index('id_index', { - parts = { - {1, 'unsigned', path = 'dev_id'} - }, - if_not_exists = true, - }) - - cars_space:create_index('bucket_id', { - parts = { 'bucket_id' }, - unique = false, - if_not_exists = true, - }) - - if crud_utils.tarantool_supports_jsonpath_indexes() then - cars_space:create_index('data_index', { - parts = { - {5, 'str', path = 'car.color'}, - --{5, 'str', path = 'car.model'} - }, - unique = false, - if_not_exists = true, - }) - end end, } end diff --git a/test/integration/select_test.lua b/test/integration/select_test.lua index 2c84a572..1d243216 100644 --- a/test/integration/select_test.lua +++ b/test/integration/select_test.lua @@ -1202,8 +1202,6 @@ pgroup:add('test_select_force_map_call', function(g) end) pgroup:add('test_jsonpath', function(g) - t.skip_if(not crud_utils.tarantool_supports_jsonpath_filters(), "Jsonpath is not supported on Tarantool < 1.10") - helpers.insert_objects(g, 'developers', { { id = 1, name = "Alexey", last_name = "Smith", @@ -1256,72 +1254,3 @@ pgroup:add('test_jsonpath', function(g) } t.assert_equals(objects, expected_objects) end) - -pgroup:add('test_jsonpath_index_field', function(g) - t.skip_if( - not crud_utils.tarantool_supports_jsonpath_indexes(), - "Jsonpath indexes supported since 2.6.3/2.7.2/2.8.1" - ) - - helpers.insert_objects(g, 'cars', { - { - id = {dev_id = 1}, age = 16, manufacturer = 'VAG', - data = {car = { model = 'BMW', color = 'Black' }}, - }, { - id = {dev_id = 2}, age = 41, manufacturer = 'FIAT', - data = {car = { model = 'Cadillac', color = 'White' }}, - }, { - id = {dev_id = 3}, age = 25, manufacturer = 'Ford', - data = {car = { model = 'BMW', color = 'Yellow' }}, - }, { - id = {dev_id = 4}, age = 37, manufacturer = 'General Motors', - data = {car = { model = 'Mercedes', color = 'Yellow' }}, - }, - }) - - local result, err = g.cluster.main_server.net_box:call('crud.select', - {'cars', {{'>=', 'id_index', 3}}, {fields = {'age'}}}) - t.assert_equals(err, nil) - - local objects = crud.unflatten_rows(result.rows, result.metadata) - local expected_objects = { - {id = 3, age = 25}, - {id = 4, age = 37}, - } - t.assert_equals(objects, expected_objects) - - local result, err = g.cluster.main_server.net_box:call('crud.select', - {'cars', {{'==', '["id"]', 1}}}) - t.assert_equals(err, nil) - - local objects = crud.unflatten_rows(result.rows, result.metadata) - local expected_objects = {{ - id = 1, age = 16, bucket_id = 477, manufacturer = 'VAG', - data = {car = { model = 'BMW', color = 'Black' }}, - }} - - t.assert_equals(objects, expected_objects) - - local result, err = g.cluster.main_server.net_box:call('crud.select', - {'cars', {{'>=', '["id"]', 2}, {'>=', 'data_index', 'String'}}}) - --t.assert_equals(err, nil) - - --local objects = crud.unflatten_rows(result.rows, result.metadata) - local expected_objects = {{ - id = 2, age = 41, bucket_id = 477, manufacturer = 'FIAT', - data = {car = { model = 'Cadillac', color = 'White' }}, - }} - - --t.assert_equals(objects, expected_objects) - - local result, err = g.cluster.main_server.net_box:call('crud.select', - {'cars', {{'==', 'data_index', {'Yellow', 'Mercedes'}}}}) - t.assert_equals(err, nil) - - local objects = crud.unflatten_rows(result.rows, result.metadata) - local expected_objects = {{ - id = 4, age = 37, bucket_id = 1161, manufacturer = 'General Motors', - data = {car = {color = "Yellow", model = "Mercedes"}}}, - } - t.assert_equals(objects, expected_objects) -end) diff --git a/test/unit/parse_conditions_test.lua b/test/unit/parse_conditions_test.lua index 131e13e6..80772452 100644 --- a/test/unit/parse_conditions_test.lua +++ b/test/unit/parse_conditions_test.lua @@ -14,7 +14,7 @@ g.test_parse = function() {'>=', 'f', {3, 3, 4}}, } - local conditions, err = select_conditions.parse(user_conditions) + local conditions, err = compare_conditions.parse(user_conditions) t.assert(err == nil) t.assert_equals(conditions, { cond_funcs.eq('aaaa', nil), @@ -30,7 +30,7 @@ g.test_parse_errors = function() -- conditions are no table local user_conditions = 'bbb = {12}' - local _, err = select_conditions.parse(user_conditions) + local _, err = compare_conditions.parse(user_conditions) t.assert_str_contains(err.err, 'Conditions should be table, got "string"') -- condition is no table @@ -39,7 +39,7 @@ g.test_parse_errors = function() 'bbb = {12}', } - local _, err = select_conditions.parse(user_conditions) + local _, err = compare_conditions.parse(user_conditions) t.assert_str_contains(err.err, 'Each condition should be table, got "string" (condition 2)') -- condition len is wrong @@ -48,7 +48,7 @@ g.test_parse_errors = function() {'='}, } - local _, err = select_conditions.parse(user_conditions) + local _, err = compare_conditions.parse(user_conditions) t.assert_str_contains( err.err, 'Each condition should be {"", "", } (condition 2)' @@ -59,7 +59,7 @@ g.test_parse_errors = function() {'=', 'bb', 1, 2}, } - local _, err = select_conditions.parse(user_conditions) + local _, err = compare_conditions.parse(user_conditions) t.assert_str_contains( err.err, 'Each condition should be {"", "", } (condition 2)' @@ -71,7 +71,7 @@ g.test_parse_errors = function() {3, 'bb', 1}, } - local _, err = select_conditions.parse(user_conditions) + local _, err = compare_conditions.parse(user_conditions) t.assert_str_contains( err.err, 'condition[1] should be string, got "number" (condition 2)' @@ -83,7 +83,7 @@ g.test_parse_errors = function() {'===', 'bb', 1}, } - local _, err = select_conditions.parse(user_conditions) + local _, err = compare_conditions.parse(user_conditions) t.assert_str_contains( err.err, 'condition[1] "===" isn\'t a valid condition oprator, (condition 2)' @@ -95,7 +95,7 @@ g.test_parse_errors = function() {'=', 3, 1}, } - local _, err = select_conditions.parse(user_conditions) + local _, err = compare_conditions.parse(user_conditions) t.assert_str_contains( err.err, 'condition[2] should be string, got "number" (condition 2)' @@ -103,8 +103,6 @@ g.test_parse_errors = function() end g.test_jsonpath_parse = function() - t.skip_if(not crud_utils.tarantool_supports_jsonpath_filters(), "Jsonpath is not supported on Tarantool < 1.10") - local user_conditions = { {'==', '[\'name\']', 'Alexey'}, {'=', '["name"].a.b', 'Sergey'}, @@ -120,7 +118,7 @@ g.test_jsonpath_parse = function() {name = 'year', type ='unsigned'}, } - local conditions, err = select_conditions.parse(user_conditions, space_format) + local conditions, err = compare_conditions.parse(user_conditions, space_format) t.assert(err == nil) t.assert_equals(conditions, { cond_funcs.eq('name', 'Alexey'), @@ -131,45 +129,3 @@ g.test_jsonpath_parse = function() cond_funcs.ge('year', 2017, '[3].a["f2"][\'f3\']'), }) end - -g.test_jsonpath_parse_errors = function() - t.skip_if(not crud_utils.tarantool_supports_jsonpaths(), "Jsonpath is not supported on Tarantool < 2") - local space_format = { - {name = 'name', type = 'string'}, - {name = 'surname', type = 'any'}, - {name = 'year', type ='unsigned'}, - } - - -- bad jsonpath - local user_conditions = { - {'==', '1].a', 'Alexey'}, - } - - local _, err = select_conditions.parse(user_conditions, space_format) - t.assert_str_contains( - err.err, - 'Invalid jsonpath format' - ) - - -- non-existen fieldno - local user_conditions = { - {'==', '[4].a.b', 88}, - } - - local _, err = select_conditions.parse(user_conditions, space_format) - t.assert_str_contains( - err.err, - 'Space doesn\'t contains field [4]' - ) - - -- non-existen field - local user_conditions = { - {'==', '[\'bucket_id\']', 41}, - } - - local _, err = select_conditions.parse(user_conditions, space_format) - t.assert_str_contains( - err.err, - 'Space doesn\'t contains field [\'bucket_id\']' - ) -end diff --git a/test/unit/select_filters_test.lua b/test/unit/select_filters_test.lua index 2e7e0d24..6b4cc246 100644 --- a/test/unit/select_filters_test.lua +++ b/test/unit/select_filters_test.lua @@ -83,13 +83,8 @@ g.test_parse = function() -- age filter (early exit is possible) local age_filter_condition = filter_conditions[1] t.assert_type(age_filter_condition, 'table') -<<<<<<< HEAD - t.assert_equals(age_filter_condition.fieldnos, {5}) - t.assert_equals(age_filter_condition.operator, compare_conditions.operators.LT) -======= t.assert_equals(age_filter_condition.fields, {5}) - t.assert_equals(age_filter_condition.operator, select_conditions.operators.LT) ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + t.assert_equals(age_filter_condition.operator, compare_conditions.operators.LT) t.assert_equals(age_filter_condition.values, {40}) t.assert_equals(age_filter_condition.types, {'number'}) t.assert_equals(age_filter_condition.early_exit_is_possible, true) @@ -97,13 +92,8 @@ g.test_parse = function() -- full_name filter local full_name_filter_condition = filter_conditions[2] t.assert_type(full_name_filter_condition, 'table') -<<<<<<< HEAD - t.assert_equals(full_name_filter_condition.fieldnos, {3, 4}) - t.assert_equals(full_name_filter_condition.operator, compare_conditions.operators.EQ) -======= t.assert_equals(full_name_filter_condition.fields, {3, 4}) - t.assert_equals(full_name_filter_condition.operator, select_conditions.operators.EQ) ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + t.assert_equals(full_name_filter_condition.operator, compare_conditions.operators.EQ) t.assert_equals(full_name_filter_condition.values, {'Ivan', 'Ivanov'}) t.assert_equals(full_name_filter_condition.types, {'string', 'string'}) t.assert_equals(full_name_filter_condition.early_exit_is_possible, false) @@ -125,13 +115,8 @@ g.test_parse = function() -- has_a_car filter local has_a_car_filter_condition = filter_conditions[3] t.assert_type(has_a_car_filter_condition, 'table') -<<<<<<< HEAD - t.assert_equals(has_a_car_filter_condition.fieldnos, {7}) - t.assert_equals(has_a_car_filter_condition.operator, compare_conditions.operators.EQ) -======= t.assert_equals(has_a_car_filter_condition.fields, {7}) - t.assert_equals(has_a_car_filter_condition.operator, select_conditions.operators.EQ) ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + t.assert_equals(has_a_car_filter_condition.operator, compare_conditions.operators.EQ) t.assert_equals(has_a_car_filter_condition.values, {true}) t.assert_equals(has_a_car_filter_condition.types, {'boolean'}) t.assert_equals(has_a_car_filter_condition.early_exit_is_possible, false) @@ -145,13 +130,8 @@ end g.test_one_condition_number = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {1}, - operator = compare_conditions.operators.EQ, -======= fields = {1}, - operator = select_conditions.operators.EQ, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.EQ, values = {3}, types = {'number'}, early_exit_is_possible = true, @@ -187,13 +167,8 @@ end g.test_one_condition_boolean = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {1}, - operator = compare_conditions.operators.EQ, -======= fields = {1}, - operator = select_conditions.operators.EQ, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.EQ, values = {true}, types = {'boolean'}, early_exit_is_possible = true, @@ -232,13 +207,8 @@ end g.test_one_condition_string = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {2}, - operator = compare_conditions.operators.GT, -======= fields = {2}, - operator = select_conditions.operators.GT, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.GT, values = {'dddddddd'}, types = {'string'}, early_exit_is_possible = true, @@ -278,25 +248,15 @@ end g.test_two_conditions = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {1}, - operator = compare_conditions.operators.EQ, -======= fields = {1}, - operator = select_conditions.operators.EQ, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.EQ, values = {4}, types = {'number'}, early_exit_is_possible = true, }, { -<<<<<<< HEAD - fieldnos = {3}, - operator = compare_conditions.operators.GE, -======= fields = {3}, - operator = select_conditions.operators.GE, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.GE, values = {"dddddddd"}, types = {'string'}, early_exit_is_possible = false, @@ -344,13 +304,8 @@ end g.test_two_conditions_non_nullable = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {2, 3}, - operator = compare_conditions.operators.GE, -======= fields = {2, 3}, - operator = select_conditions.operators.GE, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.GE, values = {"test", 5}, types = {'string', 'number'}, early_exit_is_possible = false, @@ -360,13 +315,8 @@ g.test_two_conditions_non_nullable = function() }, }, { -<<<<<<< HEAD - fieldnos = {1}, - operator = compare_conditions.operators.LT, -======= fields = {1}, - operator = select_conditions.operators.LT, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.LT, values = {3}, types = {'number'}, early_exit_is_possible = true, @@ -422,13 +372,8 @@ end g.test_one_condition_with_nil_value = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {2, 3}, - operator = compare_conditions.operators.GE, -======= fields = {2, 3}, - operator = select_conditions.operators.GE, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.GE, values = {"test"}, types = {'string', 'number'}, early_exit_is_possible = false, @@ -470,13 +415,8 @@ end g.test_unicode_collation = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {1, 2, 3, 4}, - operator = compare_conditions.operators.EQ, -======= fields = {1, 2, 3, 4}, - operator = select_conditions.operators.EQ, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.EQ, values = {'A', 'Á', 'Ä', 6}, types = {'string', 'string', 'string', 'number'}, early_exit_is_possible = false, @@ -521,13 +461,8 @@ end g.test_binary_and_none_collation = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {1, 2, 3}, - operator = compare_conditions.operators.EQ, -======= fields = {1, 2, 3}, - operator = select_conditions.operators.EQ, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.EQ, values = {'A', 'B', 'C'}, types = {'string', 'string', 'string'}, early_exit_is_possible = false, @@ -571,13 +506,8 @@ end g.test_null_as_last_value_eq = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {1, 2}, - operator = compare_conditions.operators.EQ, -======= fields = {1, 2}, - operator = select_conditions.operators.EQ, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.EQ, values = {'a', box.NULL}, types = {'string', 'string'}, early_exit_is_possible = false, @@ -617,13 +547,8 @@ end g.test_null_as_last_value_gt = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {1, 2}, - operator = compare_conditions.operators.GT, -======= fields = {1, 2}, - operator = select_conditions.operators.GT, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.GT, values = {'a', box.NULL}, types = {'string', 'string'}, early_exit_is_possible = false, @@ -669,13 +594,8 @@ end g.test_null_as_last_value_gt_non_nullable = function() local filter_conditions = { { -<<<<<<< HEAD - fieldnos = {1, 2}, - operator = compare_conditions.operators.GT, -======= fields = {1, 2}, - operator = select_conditions.operators.GT, ->>>>>>> f143377 (Remove field validation and combine with jsonpaths) + operator = compare_conditions.operators.GT, values = {'a', box.NULL}, types = {'string', 'string'}, early_exit_is_possible = false, @@ -722,7 +642,7 @@ g.test_jsonpath_fields_eq = function() local filter_conditions = { { fields = {'[2].a.b'}, - operator = select_conditions.operators.EQ, + operator = compare_conditions.operators.EQ, values = {55}, types = {'number'}, early_exit_is_possible = true, @@ -760,7 +680,7 @@ g.test_jsonpath_fields_ge = function() local filter_conditions = { { fields = {'[2]["field_2"]'}, - operator = select_conditions.operators.GT, + operator = compare_conditions.operators.GT, values = {23}, types = {'number'}, early_exit_is_possible = true, @@ -800,7 +720,7 @@ g.test_several_jsonpath = function() local filter_conditions = { { fields = {'[3]["f2"][\'f3\']', '[4].f3'}, - operator = select_conditions.operators.EQ, + operator = compare_conditions.operators.EQ, values = {'a', 'b'}, types = {'string', 'string'}, early_exit_is_possible = true, @@ -838,14 +758,14 @@ g.test_jsonpath_two_conditions = function() local filter_conditions = { { fields = {'[2].fld_1', '[3]["f_1"]'}, - operator = select_conditions.operators.GE, + operator = compare_conditions.operators.GE, values = {"jsonpath_test", 23}, types = {'string', 'number'}, early_exit_is_possible = false, }, { fields = {'[1].field_1'}, - operator = select_conditions.operators.LT, + operator = compare_conditions.operators.LT, values = {8}, types = {'number'}, early_exit_is_possible = true, diff --git a/test/unit/select_filters_uuid_test.lua b/test/unit/select_filters_uuid_test.lua index f0819599..256e7ba1 100644 --- a/test/unit/select_filters_uuid_test.lua +++ b/test/unit/select_filters_uuid_test.lua @@ -74,7 +74,7 @@ g.test_parse = function() local uuid_filter_condition = filter_conditions[1] t.assert_type(uuid_filter_condition, 'table') t.assert_equals(uuid_filter_condition.fields, {1}) - t.assert_equals(uuid_filter_condition.operator, select_conditions.operators.LT) + t.assert_equals(uuid_filter_condition.operator, compare_conditions.operators.LT) t.assert_equals(uuid_filter_condition.values, {uuid2}) t.assert_equals(uuid_filter_condition.types, {'uuid'}) t.assert_equals(uuid_filter_condition.early_exit_is_possible, true) @@ -83,7 +83,7 @@ g.test_parse = function() local name_filter_condition = filter_conditions[2] t.assert_type(name_filter_condition, 'table') t.assert_equals(name_filter_condition.fields, {3}) - t.assert_equals(name_filter_condition.operator, select_conditions.operators.EQ) + t.assert_equals(name_filter_condition.operator, compare_conditions.operators.EQ) t.assert_equals(name_filter_condition.values, {'Charlie'}) t.assert_equals(name_filter_condition.types, {'string'}) t.assert_equals(name_filter_condition.early_exit_is_possible, false) @@ -92,7 +92,7 @@ g.test_parse = function() local category_id_filter_condition = filter_conditions[3] t.assert_type(category_id_filter_condition, 'table') t.assert_equals(category_id_filter_condition.fields, {4}) - t.assert_equals(category_id_filter_condition.operator, select_conditions.operators.EQ) + t.assert_equals(category_id_filter_condition.operator, compare_conditions.operators.EQ) t.assert_equals(category_id_filter_condition.values, {uuid3}) t.assert_equals(category_id_filter_condition.types, {'uuid'}) t.assert_equals(category_id_filter_condition.early_exit_is_possible, false) @@ -112,7 +112,7 @@ g.test_one_condition_uuid = function() local filter_conditions = { { fields = {1}, - operator = select_conditions.operators.EQ, + operator = compare_conditions.operators.EQ, values = {uuid1}, types = {'uuid'}, early_exit_is_possible = true, @@ -154,7 +154,7 @@ g.test_one_condition_uuid_gt = function() local filter_conditions = { { fields = {1}, - operator = select_conditions.operators.GT, + operator = compare_conditions.operators.GT, values = {uuid1}, types = {'uuid'}, early_exit_is_possible = true, @@ -199,7 +199,7 @@ g.test_one_condition_uuid_with_nil_value = function() local filter_conditions = { { fields = {1, 3}, - operator = select_conditions.operators.GE, + operator = compare_conditions.operators.GE, values = {uuid1}, types = {'uuid', 'string'}, early_exit_is_possible = false, @@ -247,14 +247,14 @@ g.test_two_conditions_uuid = function() local filter_conditions = { { fields = {2}, - operator = select_conditions.operators.EQ, + operator = compare_conditions.operators.EQ, values = {'Charlie'}, types = {'string'}, early_exit_is_possible = true, }, { fields = {3}, - operator = select_conditions.operators.GE, + operator = compare_conditions.operators.GE, values = {uuid2:str()}, types = {'uuid'}, early_exit_is_possible = false, From 1aead9ef5195d72e4c6d5d9318b31a4a4534b97c Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Wed, 26 May 2021 20:53:12 +0300 Subject: [PATCH 3/4] Remove redundant code --- crud/common/utils.lua | 1 - crud/select/compat/select.lua | 12 ++++++------ crud/select/compat/select_old.lua | 12 ++++++------ crud/select/filters.lua | 1 - test/integration/simple_operations_test.lua | 1 + test/unit/parse_conditions_test.lua | 20 +++++++------------- 6 files changed, 20 insertions(+), 27 deletions(-) diff --git a/crud/common/utils.lua b/crud/common/utils.lua index a7d13046..b7ec5de0 100644 --- a/crud/common/utils.lua +++ b/crud/common/utils.lua @@ -205,7 +205,6 @@ local function determine_enabled_features() -- since Tarantool 2.4 enabled_tarantool_features.uuids = major >= 2 and (minor > 4 or minor == 4 and patch >= 1) - end function utils.tarantool_supports_fieldpaths() diff --git a/crud/select/compat/select.lua b/crud/select/compat/select.lua index f46f725d..2d44f83b 100644 --- a/crud/select/compat/select.lua +++ b/crud/select/compat/select.lua @@ -34,6 +34,12 @@ local function build_select_iterator(space_name, user_conditions, opts) return nil, SelectError:new("batch_size should be > 0") end + -- check conditions + local conditions, err = compare_conditions.parse(user_conditions) + if err ~= nil then + return nil, SelectError:new("Failed to parse conditions: %s", err) + end + local replicasets, err = vshard.router.routeall() if err ~= nil then return nil, SelectError:new("Failed to get all replicasets: %s", err) @@ -45,12 +51,6 @@ local function build_select_iterator(space_name, user_conditions, opts) end local space_format = space:format() - -- check conditions - local conditions, err = compare_conditions.parse(user_conditions, space_format) - if err ~= nil then - return nil, SelectError:new("Failed to parse conditions: %s", err) - end - -- plan select local plan, err = select_plan.new(space, conditions, { first = opts.first, diff --git a/crud/select/compat/select_old.lua b/crud/select/compat/select_old.lua index 6ace4b3e..9e1bb6e4 100644 --- a/crud/select/compat/select_old.lua +++ b/crud/select/compat/select_old.lua @@ -86,6 +86,12 @@ local function build_select_iterator(space_name, user_conditions, opts) local batch_size = opts.batch_size or common.DEFAULT_BATCH_SIZE + -- check conditions + local conditions, err = compare_conditions.parse(user_conditions, space_format) + if err ~= nil then + return nil, SelectError:new("Failed to parse conditions: %s", err) + end + local replicasets, err = vshard.router.routeall() if err ~= nil then return nil, SelectError:new("Failed to get all replicasets: %s", err) @@ -97,12 +103,6 @@ local function build_select_iterator(space_name, user_conditions, opts) end local space_format = space:format() - -- check conditions - local conditions, err = compare_conditions.parse(user_conditions, space_format) - if err ~= nil then - return nil, SelectError:new("Failed to parse conditions: %s", err) - end - -- plan select local plan, err = select_plan.new(space, conditions, { first = opts.first, diff --git a/crud/select/filters.lua b/crud/select/filters.lua index 27cc7edd..6cf502b6 100644 --- a/crud/select/filters.lua +++ b/crud/select/filters.lua @@ -652,7 +652,6 @@ function filters.gen_func(space, conditions, opts) scan_condition_num = opts.scan_condition_num, tarantool_iter = opts.tarantool_iter, }) - if err ~= nil then return nil, GenFiltersError:new("Failed to generate filters for specified conditions: %s", err) end diff --git a/test/integration/simple_operations_test.lua b/test/integration/simple_operations_test.lua index 21a49f70..052cecc3 100644 --- a/test/integration/simple_operations_test.lua +++ b/test/integration/simple_operations_test.lua @@ -1006,3 +1006,4 @@ pgroup:add('test_partial_result_bad_input', function(g) t.assert_equals(result, nil) t.assert_str_contains(err.err, 'Space format doesn\'t contain field named "lastname"') end) + diff --git a/test/unit/parse_conditions_test.lua b/test/unit/parse_conditions_test.lua index 80772452..98fd6add 100644 --- a/test/unit/parse_conditions_test.lua +++ b/test/unit/parse_conditions_test.lua @@ -112,20 +112,14 @@ g.test_jsonpath_parse = function() {'>=', '[\'year\'].a["f2"][\'f3\']', 2017}, } - local space_format = { - {name = 'name', type = 'string'}, - {name = 'surname', type = 'any'}, - {name = 'year', type ='unsigned'}, - } - - local conditions, err = compare_conditions.parse(user_conditions, space_format) + local conditions, err = compare_conditions.parse(user_conditions) t.assert(err == nil) t.assert_equals(conditions, { - cond_funcs.eq('name', 'Alexey'), - cond_funcs.eq('name', 'Sergey', '[1].a.b'), - cond_funcs.lt('year', 2021, '[3]["field_1"][\'field_2\']'), - cond_funcs.le('surname', {1, 2, 3}, '[2].a'), - cond_funcs.gt('surname', 'Jackson'), - cond_funcs.ge('year', 2017, '[3].a["f2"][\'f3\']'), + cond_funcs.eq('[\'name\']', 'Alexey'), + cond_funcs.eq('["name"].a.b', 'Sergey'), + cond_funcs.lt('["year"]["field_1"][\'field_2\']', 2021), + cond_funcs.le('[2].a', {1, 2, 3}), + cond_funcs.gt('[2]', 'Jackson'), + cond_funcs.ge('[\'year\'].a["f2"][\'f3\']', 2017), }) end From f649abdc96e3e9877f511828dc6c186f8dd589f2 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Wed, 26 May 2021 20:54:22 +0300 Subject: [PATCH 4/4] Style fix --- crud/select/compat/select_old.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crud/select/compat/select_old.lua b/crud/select/compat/select_old.lua index 9e1bb6e4..6e98b508 100644 --- a/crud/select/compat/select_old.lua +++ b/crud/select/compat/select_old.lua @@ -87,7 +87,7 @@ local function build_select_iterator(space_name, user_conditions, opts) local batch_size = opts.batch_size or common.DEFAULT_BATCH_SIZE -- check conditions - local conditions, err = compare_conditions.parse(user_conditions, space_format) + local conditions, err = compare_conditions.parse(user_conditions) if err ~= nil then return nil, SelectError:new("Failed to parse conditions: %s", err) end