Skip to content

Commit

Permalink
scan: support datetime filters
Browse files Browse the repository at this point in the history
Before this patch, datetime conditions were supported only in iterators,
but not filters. (Filter is any non-indexed condition or condition
for any non-iterating index. Any condition index expect for the first
one is non-iterating.) This patch introduce datetime comparison support
to filter codegen library, similar to uuid one.

Closes #373
  • Loading branch information
DifferentialOrange committed Mar 11, 2024
1 parent 781f3c4 commit e861188
Show file tree
Hide file tree
Showing 10 changed files with 795 additions and 53 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

### Added
* Working with datetime conditions in case of non-indexed fields or
non-iterating indexes (#373).

## [1.4.3] - 05-02-24

### Fixed
Expand Down
50 changes: 50 additions & 0 deletions crud/compare/filters.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
local _, datetime = pcall(require, 'datetime')

local errors = require('errors')

local utils = require('crud.common.utils')
Expand Down Expand Up @@ -159,6 +161,8 @@ local function format_value(value)
return tostring(value)
elseif utils.is_uuid(value) then
return ("%q"):format(value)
elseif datetime.is_datetime(value) then
return ("%q"):format(value:format())
elseif type(value) == 'cdata' then
return tostring(value)
end
Expand Down Expand Up @@ -283,6 +287,8 @@ local function format_eq(cond)
end
elseif value_type == 'uuid' then
func_name = 'eq_uuid'
elseif value_type == 'datetime' then
func_name = 'eq_datetime'
end

table.insert(cond_strings, format_comp_with_value(field, func_name, value))
Expand All @@ -309,6 +315,8 @@ local function format_lt(cond)
func_name = add_collation_postfix('lt', value_opts)
elseif value_type == 'uuid' then
func_name = 'lt_uuid'
elseif value_type == 'datetime' then
func_name = 'lt_datetime'
end

func_name = add_strict_postfix(func_name, value_opts)
Expand Down Expand Up @@ -533,6 +541,30 @@ local function lt_uuid_strict(lhs, rhs)
return tostring(lhs) < tostring(rhs)
end

local function opt_datetime_parse(v)
if type(v) == 'string' then
return datetime.parse(v)
end

return v
end

local function lt_datetime_nullable(lhs, rhs)
if lhs == nil and rhs ~= nil then
return true
elseif rhs == nil then
return false
end
return opt_datetime_parse(lhs) < opt_datetime_parse(rhs)
end

local function lt_datetime_strict(lhs, rhs)
if rhs == nil then
return false
end
return opt_datetime_parse(lhs) < opt_datetime_parse(rhs)
end

local function lt_unicode_ci_nullable(lhs, rhs)
if lhs == nil and rhs ~= nil then
return true
Expand Down Expand Up @@ -567,6 +599,20 @@ local function eq_uuid_strict(lhs, rhs)
return tostring(lhs) == tostring(rhs)
end

local function eq_datetime(lhs, rhs)
if lhs == nil then
return rhs == nil
end
return opt_datetime_parse(lhs) == opt_datetime_parse(rhs)
end

local function eq_datetime_strict(lhs, rhs)
if rhs == nil then
return false
end
return opt_datetime_parse(lhs) == opt_datetime_parse(rhs)
end

local function eq_unicode_nullable(lhs, rhs)
if lhs == nil and rhs == nil then
return true
Expand Down Expand Up @@ -604,6 +650,8 @@ local library = {
eq = eq,
eq_uuid = eq_uuid,
eq_uuid_strict = eq_uuid_strict,
eq_datetime = eq_datetime,
eq_datetime_strict = eq_datetime_strict,
-- nullable
eq_unicode = eq_unicode_nullable,
eq_unicode_ci = eq_unicode_ci_nullable,
Expand All @@ -618,12 +666,14 @@ local library = {
lt_unicode_ci = lt_unicode_ci_nullable,
lt_boolean = lt_boolean_nullable,
lt_uuid = lt_uuid_nullable,
lt_datetime = lt_datetime_nullable,
-- strict
lt_strict = lt_strict,
lt_unicode_strict = lt_unicode_strict,
lt_unicode_ci_strict = lt_unicode_ci_strict,
lt_boolean_strict = lt_boolean_strict,
lt_uuid_strict = lt_uuid_strict,
lt_datetime_strict = lt_datetime_strict,

utf8 = utf8,

Expand Down
97 changes: 97 additions & 0 deletions test/entrypoint/srv_select/storage_init.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local datetime_supported, _ = pcall(require, 'datetime')
local crud_utils = require('crud.common.utils')

return function()
Expand Down Expand Up @@ -227,4 +228,100 @@ return function()
unique = false,
if_not_exists = true,
})

if datetime_supported then
local datetime_format = {
{name = 'id', type = 'unsigned'},
{name = 'bucket_id', type = 'unsigned'},
{name = 'datetime_field', type = 'datetime'},
}


local datetime_nonindexed_space = box.schema.space.create('datetime_nonindexed', {
if_not_exists = true,
engine = engine,
})

datetime_nonindexed_space:format(datetime_format)

datetime_nonindexed_space:create_index('id_index', {
parts = { 'id' },
if_not_exists = true,
})

datetime_nonindexed_space:create_index('bucket_id', {
parts = { 'bucket_id' },
unique = false,
if_not_exists = true,
})


local datetime_indexed_space = box.schema.space.create('datetime_indexed', {
if_not_exists = true,
engine = engine,
})

datetime_indexed_space:format(datetime_format)

datetime_indexed_space:create_index('id_index', {
parts = { 'id' },
if_not_exists = true,
})

datetime_indexed_space:create_index('bucket_id', {
parts = { 'bucket_id' },
unique = false,
if_not_exists = true,
})

datetime_indexed_space:create_index('datetime_index', {
parts = { 'datetime_field' },
unique = false,
if_not_exists = true,
})


local datetime_pk_space = box.schema.space.create('datetime_pk', {
if_not_exists = true,
engine = engine,
})

datetime_pk_space:format(datetime_format)

datetime_pk_space:create_index('datetime_index', {
parts = { 'datetime_field' },
if_not_exists = true,
})

datetime_pk_space:create_index('bucket_id', {
parts = { 'bucket_id' },
unique = false,
if_not_exists = true,
})


local datetime_multipart_index_space = box.schema.space.create('datetime_multipart_index', {
if_not_exists = true,
engine = engine,
})

datetime_multipart_index_space:format(datetime_format)

datetime_multipart_index_space:create_index('id_index', {
parts = { 'id' },
if_not_exists = true,
})

datetime_multipart_index_space:create_index('bucket_id', {
parts = { 'bucket_id' },
unique = false,
if_not_exists = true,
})

datetime_multipart_index_space:create_index('datetime_index', {
parts = { 'id', 'datetime_field' },
unique = false,
if_not_exists = true,
})
end
end
5 changes: 5 additions & 0 deletions test/helper.lua
Original file line number Diff line number Diff line change
Expand Up @@ -958,4 +958,9 @@ function helpers.prepare_ordered_data(g, space, expected_objects, bucket_id, ord
t.assert_equals(objects, expected_objects)
end

function helpers.skip_datetime_unsupported()
local module_available, _ = pcall(require, 'datetime')
t.skip_if(not module_available, 'datetime is not supported')
end

return helpers
42 changes: 25 additions & 17 deletions test/integration/pairs_readview_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -882,27 +882,35 @@ pgroup.test_pairs_no_map_reduce = function(g)
t.assert_equals(diff_2, 0, 'Select request was not a map reduce')
end

pgroup.test_gh_418_pairs_with_secondary_noneq_index_condition = function(g)
local read = function(cg, space, conditions, opts)
opts = table.deepcopy(opts) or {}
opts.use_tomap = true
local function read_impl(cg, space, conditions, opts)
opts = table.deepcopy(opts) or {}
opts.use_tomap = true

return cg.cluster.main_server:exec(function(space, conditions, opts)
local crud = require('crud')
return cg.cluster.main_server:exec(function(space, conditions, opts)
local crud = require('crud')

local rv, err = crud.readview()
t.assert_equals(err, nil)
local rv, err = crud.readview()
t.assert_equals(err, nil)

local status, resp = pcall(function()
return rv:pairs(space, conditions, opts):totable()
end)
t.assert(status, resp)
local status, resp = pcall(function()
return rv:pairs(space, conditions, opts):totable()
end)
t.assert(status, resp)

rv:close()
rv:close()

return resp, nil
end, {space, conditions, opts})
end
return resp, nil
end, {space, conditions, opts})
end

read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read)
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 case_name = 'test_' .. case_name_template:format('pairs')

pgroup[case_name] = function(g)
case(g, read_impl)
end
end
39 changes: 24 additions & 15 deletions test/integration/pairs_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -893,23 +893,32 @@ pgroup.test_pairs_no_map_reduce = function(g)
t.assert_equals(diff_2, 0, 'Select request was not a map reduce')
end

pgroup.test_gh_418_pairs_with_secondary_noneq_index_condition = function(g)
local read = function(cg, space, conditions, opts)
opts = table.deepcopy(opts) or {}
opts.mode = 'write'
opts.use_tomap = true

return cg.cluster.main_server:exec(function(space, conditions, opts)
local crud = require('crud')
local function read_impl(cg, space, conditions, opts)
opts = table.deepcopy(opts) or {}
opts.mode = 'write'
opts.use_tomap = true

return cg.cluster.main_server:exec(function(space, conditions, opts)
local crud = require('crud')

local status, resp = pcall(function()
return crud.pairs(space, conditions, opts):totable()
end)
t.assert(status, resp)
local status, resp = pcall(function()
return crud.pairs(space, conditions, opts):totable()
end)
t.assert(status, resp)

return resp, nil
end, {space, conditions, opts})
end
return resp, nil
end, {space, conditions, opts})
end

read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read)
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 case_name = 'test_' .. case_name_template:format('pairs')

pgroup[case_name] = function(g)
case(g, read_impl)
end
end
Loading

0 comments on commit e861188

Please sign in to comment.