diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d728aa6..2e9acb4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased ### Added -* Working with datetime conditions in case of non-indexed fields or - non-iterating indexes (#373). +* Working with decimal and datetime conditions in case of non-indexed + fields or non-iterating indexes (#373). ## [1.4.3] - 05-02-24 diff --git a/crud/compare/filters.lua b/crud/compare/filters.lua index 87ec0b9a..fd9d5ef7 100644 --- a/crud/compare/filters.lua +++ b/crud/compare/filters.lua @@ -1,4 +1,5 @@ local datetime_supported, datetime = pcall(require, 'datetime') +local decimal_supported, decimal = pcall(require, 'decimal') local errors = require('errors') @@ -159,6 +160,9 @@ local function format_value(value) return tostring(value) elseif type(value) == 'boolean' then return tostring(value) + elseif decimal_supported and decimal.is_decimal(value) then + -- decimal supports comparison with string. + return ("%q"):format(tostring(value)) elseif utils.is_uuid(value) then return ("%q"):format(value) elseif datetime_supported and datetime.is_datetime(value) then diff --git a/test/entrypoint/srv_select/storage_init.lua b/test/entrypoint/srv_select/storage_init.lua index 6e66e9af..72409d16 100644 --- a/test/entrypoint/srv_select/storage_init.lua +++ b/test/entrypoint/srv_select/storage_init.lua @@ -1,4 +1,5 @@ local datetime_supported, _ = pcall(require, 'datetime') +local decimal_supported, _ = pcall(require, 'decimal') local crud_utils = require('crud.common.utils') return function() @@ -229,6 +230,102 @@ return function() if_not_exists = true, }) + if decimal_supported then + local decimal_format = { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'decimal_field', type = 'decimal'}, + } + + + local decimal_nonindexed_space = box.schema.space.create('decimal_nonindexed', { + if_not_exists = true, + engine = engine, + }) + + decimal_nonindexed_space:format(decimal_format) + + decimal_nonindexed_space:create_index('id_index', { + parts = { 'id' }, + if_not_exists = true, + }) + + decimal_nonindexed_space:create_index('bucket_id', { + parts = { 'bucket_id' }, + unique = false, + if_not_exists = true, + }) + + + local decimal_indexed_space = box.schema.space.create('decimal_indexed', { + if_not_exists = true, + engine = engine, + }) + + decimal_indexed_space:format(decimal_format) + + decimal_indexed_space:create_index('id_index', { + parts = { 'id' }, + if_not_exists = true, + }) + + decimal_indexed_space:create_index('bucket_id', { + parts = { 'bucket_id' }, + unique = false, + if_not_exists = true, + }) + + decimal_indexed_space:create_index('decimal_index', { + parts = { 'decimal_field' }, + unique = false, + if_not_exists = true, + }) + + + local decimal_pk_space = box.schema.space.create('decimal_pk', { + if_not_exists = true, + engine = engine, + }) + + decimal_pk_space:format(decimal_format) + + decimal_pk_space:create_index('decimal_index', { + parts = { 'decimal_field' }, + if_not_exists = true, + }) + + decimal_pk_space:create_index('bucket_id', { + parts = { 'bucket_id' }, + unique = false, + if_not_exists = true, + }) + + + local decimal_multipart_index_space = box.schema.space.create('decimal_multipart_index', { + if_not_exists = true, + engine = engine, + }) + + decimal_multipart_index_space:format(decimal_format) + + decimal_multipart_index_space:create_index('id_index', { + parts = { 'id' }, + if_not_exists = true, + }) + + decimal_multipart_index_space:create_index('bucket_id', { + parts = { 'bucket_id' }, + unique = false, + if_not_exists = true, + }) + + decimal_multipart_index_space:create_index('decimal_index', { + parts = { 'id', 'decimal_field' }, + unique = false, + if_not_exists = true, + }) + end + if datetime_supported then local datetime_format = { {name = 'id', type = 'unsigned'}, diff --git a/test/helper.lua b/test/helper.lua index 67bd9b94..9bbf47e8 100644 --- a/test/helper.lua +++ b/test/helper.lua @@ -958,9 +958,32 @@ function helpers.prepare_ordered_data(g, space, expected_objects, bucket_id, ord t.assert_equals(objects, expected_objects) end +function helpers.skip_decimal_unsupported() + local module_available, _ = pcall(require, 'decimal') + t.skip_if(not module_available, 'decimal is not supported') +end + function helpers.skip_datetime_unsupported() local module_available, _ = pcall(require, 'datetime') t.skip_if(not module_available, 'datetime is not supported') end +function helpers.merge_tables(t1, t2, ...) + if t2 == nil then + return t1 + end + + local res = {} + + for k, v in pairs(t1) do + res[k] = v + end + + for k, v in pairs(t2) do + res[k] = v + end + + return helpers.merge_tables(res, ...) +end + return helpers diff --git a/test/integration/count_test.lua b/test/integration/count_test.lua index 575298ae..1a8ae46c 100644 --- a/test/integration/count_test.lua +++ b/test/integration/count_test.lua @@ -883,7 +883,12 @@ pgroup.test_gh_418_count_with_secondary_noneq_index_condition = function(g) read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read_impl) end -for case_name_template, case in pairs(read_scenario.gh_373_read_with_datetime_condition_cases) do +local gh_373_types_cases = helpers.merge_tables( + read_scenario.gh_373_read_with_decimal_condition_cases, + read_scenario.gh_373_read_with_datetime_condition_cases +) + +for case_name_template, case in pairs(gh_373_types_cases) do local case_name = 'test_' .. case_name_template:format('count') pgroup[case_name] = function(g) diff --git a/test/integration/pairs_readview_test.lua b/test/integration/pairs_readview_test.lua index b35581c3..cc1fe856 100644 --- a/test/integration/pairs_readview_test.lua +++ b/test/integration/pairs_readview_test.lua @@ -907,7 +907,12 @@ pgroup.test_gh_418_pairs_with_secondary_noneq_index_condition = function(g) read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read_impl) end -for case_name_template, case in pairs(read_scenario.gh_373_read_with_datetime_condition_cases) do +local gh_373_types_cases = helpers.merge_tables( + read_scenario.gh_373_read_with_decimal_condition_cases, + read_scenario.gh_373_read_with_datetime_condition_cases +) + +for case_name_template, case in pairs(gh_373_types_cases) do local case_name = 'test_' .. case_name_template:format('pairs') pgroup[case_name] = function(g) diff --git a/test/integration/pairs_test.lua b/test/integration/pairs_test.lua index 6169ca67..097784a0 100644 --- a/test/integration/pairs_test.lua +++ b/test/integration/pairs_test.lua @@ -915,7 +915,12 @@ pgroup.test_gh_418_pairs_with_secondary_noneq_index_condition = function(g) read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read_impl) end -for case_name_template, case in pairs(read_scenario.gh_373_read_with_datetime_condition_cases) do +local gh_373_types_cases = helpers.merge_tables( + read_scenario.gh_373_read_with_decimal_condition_cases, + read_scenario.gh_373_read_with_datetime_condition_cases +) + +for case_name_template, case in pairs(gh_373_types_cases) do local case_name = 'test_' .. case_name_template:format('pairs') pgroup[case_name] = function(g) diff --git a/test/integration/read_scenario.lua b/test/integration/read_scenario.lua index f2c9ce60..493c93be 100644 --- a/test/integration/read_scenario.lua +++ b/test/integration/read_scenario.lua @@ -6,6 +6,7 @@ local t = require('luatest') local datetime_supported, datetime = pcall(require, 'datetime') +local decimal_supported, decimal = pcall(require, 'decimal') local helpers = require('test.helper') @@ -94,6 +95,230 @@ local function build_condition_case( end +local decimal_vals = {} + +if decimal_supported then + decimal_vals = { + smallest_negative = decimal.new('-123456789012345678.987431234678392'), + bigger_negative = decimal.new('-123456789012345678.987431234678391'), + bigger_positive = decimal.new('123456789012345678.987431234678391'), + } + + assert(decimal_vals.smallest_negative < decimal_vals.bigger_negative) + assert(decimal_vals.bigger_negative < decimal_vals.bigger_positive) +end + + +local decimal_data = { + { + id = 1, + decimal_field = decimal_vals.smallest_negative, + }, + { + id = 2, + decimal_field = decimal_vals.bigger_negative, + }, + { + id = 3, + decimal_field = decimal_vals.bigger_positive, + }, +} + + +local function bigger_negative_condition(operator, operand, is_multipart) + if is_multipart then + return {operator, operand, {2, decimal_vals.bigger_negative}} + else + return {operator, operand, decimal_vals.bigger_negative} + end +end + +local decimal_condition_operator_options = { + single_lt = function(operand, is_multipart) + return { + conditions = {bigger_negative_condition('<', operand, is_multipart)}, + expected_objects_without_bucket_id = { + { + id = 1, + decimal_field = decimal_vals.smallest_negative, + }, + }, + } + end, + single_le = function(operand, is_multipart) + return { + conditions = {bigger_negative_condition('<=', operand, is_multipart)}, + expected_objects_without_bucket_id = { + { + id = 1, + decimal_field = decimal_vals.smallest_negative, + }, + { + id = 2, + decimal_field = decimal_vals.bigger_negative, + }, + }, + } + end, + single_eq = function(operand, is_multipart) + return { + conditions = {bigger_negative_condition('==', operand, is_multipart)}, + expected_objects_without_bucket_id = { + { + id = 2, + decimal_field = decimal_vals.bigger_negative, + }, + }, + } + end, + single_ge = function(operand, is_multipart) + return { + conditions = {bigger_negative_condition('>=', operand, is_multipart)}, + expected_objects_without_bucket_id = { + { + id = 2, + decimal_field = decimal_vals.bigger_negative, + }, + { + id = 3, + decimal_field = decimal_vals.bigger_positive, + }, + }, + } + end, + single_gt = function(operand, is_multipart) + return { + conditions = {bigger_negative_condition('>', operand, is_multipart)}, + expected_objects_without_bucket_id = { + { + id = 3, + decimal_field = decimal_vals.bigger_positive, + }, + }, + } + end, + second_lt = function(operand, is_multipart) + return { + conditions = {{'>=', 'id', 1}, bigger_negative_condition('<', operand, is_multipart)}, + expected_objects_without_bucket_id = { + { + id = 1, + decimal_field = decimal_vals.smallest_negative, + }, + }, + } + end, + second_le = function(operand, is_multipart) + return { + conditions = {{'>=', 'id', 1}, bigger_negative_condition('<=', operand, is_multipart)}, + expected_objects_without_bucket_id = { + { + id = 1, + decimal_field = decimal_vals.smallest_negative, + }, + { + id = 2, + decimal_field = decimal_vals.bigger_negative, + }, + }, + } + end, + second_eq = function(operand, is_multipart) + return { + conditions = {{'>=', 'id', 1}, bigger_negative_condition('==', operand, is_multipart)}, + expected_objects_without_bucket_id = { + { + id = 2, + decimal_field = decimal_vals.bigger_negative, + }, + }, + } + end, + second_ge = function(operand, is_multipart) + return { + conditions = {{'>=', 'id', 1}, bigger_negative_condition('>=', operand, is_multipart)}, + expected_objects_without_bucket_id = { + { + id = 2, + decimal_field = decimal_vals.bigger_negative, + }, + { + id = 3, + decimal_field = decimal_vals.bigger_positive, + }, + }, + } + end, + second_gt = function(operand, is_multipart) + return { + conditions = {{'>=', 'id', 1}, bigger_negative_condition('>', operand, is_multipart)}, + expected_objects_without_bucket_id = { + { + id = 3, + decimal_field = decimal_vals.bigger_positive, + }, + }, + } + end, +} + + +local decimal_condition_space_options = { + nonindexed = { + space_name = 'decimal_nonindexed', + index_kind = nil, + }, + indexed = { + space_name = 'decimal_indexed', + index_kind = 'secondary', + }, + pk = { + space_name = 'decimal_pk', + index_kind = 'primary', + }, + multipart_indexed = { + space_name = 'decimal_multipart_index', + index_kind = 'multipart', + is_multipart = true, + }, +} + + +local gh_373_read_with_decimal_condition_cases = {} + +for space_kind, space_option in pairs(decimal_condition_space_options) do + for operator_kind, operator_options_builder in pairs(decimal_condition_operator_options) do + local field_case_name_template = ('gh_373_%%s_with_decimal_%s_field_%s_condition'):format( + space_kind, operator_kind) + + local field_operator_options = operator_options_builder('decimal_field', false) + + gh_373_read_with_decimal_condition_cases[field_case_name_template] = build_condition_case( + helpers.skip_decimal_unsupported, + space_option.space_name, + decimal_data, + field_operator_options.conditions, + field_operator_options.expected_objects_without_bucket_id + ) + + if space_option.index_kind ~= nil then + local index_case_name_template = ('gh_373_%%s_with_decimal_%s_index_%s_condition'):format( + space_option.index_kind, operator_kind) + + local index_operator_options = operator_options_builder('decimal_index', space_option.is_multipart) + + gh_373_read_with_decimal_condition_cases[index_case_name_template] = build_condition_case( + helpers.skip_decimal_unsupported, + space_option.space_name, + decimal_data, + index_operator_options.conditions, + index_operator_options.expected_objects_without_bucket_id + ) + end + end +end + + local datetime_vals = {} if datetime_supported then @@ -332,5 +557,6 @@ end return { gh_418_read_with_secondary_noneq_index_condition = gh_418_read_with_secondary_noneq_index_condition, + gh_373_read_with_decimal_condition_cases = gh_373_read_with_decimal_condition_cases, gh_373_read_with_datetime_condition_cases = gh_373_read_with_datetime_condition_cases, } diff --git a/test/integration/select_readview_test.lua b/test/integration/select_readview_test.lua index 4cfccdc7..316e5280 100644 --- a/test/integration/select_readview_test.lua +++ b/test/integration/select_readview_test.lua @@ -2506,7 +2506,12 @@ pgroup.test_gh_418_select_with_secondary_noneq_index_condition = function(g) read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read_impl) end -for case_name_template, case in pairs(read_scenario.gh_373_read_with_datetime_condition_cases) do +local gh_373_types_cases = helpers.merge_tables( + read_scenario.gh_373_read_with_decimal_condition_cases, + read_scenario.gh_373_read_with_datetime_condition_cases +) + +for case_name_template, case in pairs(gh_373_types_cases) do local case_name = 'test_' .. case_name_template:format('select') pgroup[case_name] = function(g) diff --git a/test/integration/select_test.lua b/test/integration/select_test.lua index 4b23f178..0d546704 100644 --- a/test/integration/select_test.lua +++ b/test/integration/select_test.lua @@ -2283,7 +2283,12 @@ pgroup.test_gh_418_select_with_secondary_noneq_index_condition = function(g) read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read_impl) end -for case_name_template, case in pairs(read_scenario.gh_373_read_with_datetime_condition_cases) do +local gh_373_types_cases = helpers.merge_tables( + read_scenario.gh_373_read_with_decimal_condition_cases, + read_scenario.gh_373_read_with_datetime_condition_cases +) + +for case_name_template, case in pairs(gh_373_types_cases) do local case_name = 'test_' .. case_name_template:format('select') pgroup[case_name] = function(g)