Skip to content

Commit bc43a8f

Browse files
scan: support datetime filters
Before this patch, datetime conditions were supported only in iterators, but not filters. (Any non-indexed condition or condition for any non-iterating index is a filter. Any condition index except for the first one is non-iterating.) This patch introduce datetime comparison support to filter codegen library, similar to uuid one. Part of #373
1 parent f30a343 commit bc43a8f

File tree

11 files changed

+811
-61
lines changed

11 files changed

+811
-61
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
### Fixed
11+
* Working with datetime conditions in case of non-indexed fields or
12+
non-iterating indexes (#373).
13+
814
## [1.4.3] - 05-02-24
915

1016
### Fixed

crud/compare/filters.lua

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
local datetime_supported, datetime = pcall(require, 'datetime')
2+
13
local errors = require('errors')
24

35
local utils = require('crud.common.utils')
@@ -159,6 +161,8 @@ local function format_value(value)
159161
return tostring(value)
160162
elseif utils.is_uuid(value) then
161163
return ("%q"):format(value)
164+
elseif datetime_supported and datetime.is_datetime(value) then
165+
return ("%q"):format(value:format())
162166
elseif type(value) == 'cdata' then
163167
return tostring(value)
164168
end
@@ -283,6 +287,8 @@ local function format_eq(cond)
283287
end
284288
elseif value_type == 'uuid' then
285289
func_name = 'eq_uuid'
290+
elseif value_type == 'datetime' then
291+
func_name = 'eq_datetime'
286292
end
287293

288294
table.insert(cond_strings, format_comp_with_value(field, func_name, value))
@@ -309,6 +315,8 @@ local function format_lt(cond)
309315
func_name = add_collation_postfix('lt', value_opts)
310316
elseif value_type == 'uuid' then
311317
func_name = 'lt_uuid'
318+
elseif value_type == 'datetime' then
319+
func_name = 'lt_datetime'
312320
end
313321

314322
func_name = add_strict_postfix(func_name, value_opts)
@@ -533,6 +541,30 @@ local function lt_uuid_strict(lhs, rhs)
533541
return tostring(lhs) < tostring(rhs)
534542
end
535543

544+
local function opt_datetime_parse(v)
545+
if type(v) == 'string' then
546+
return datetime.parse(v)
547+
end
548+
549+
return v
550+
end
551+
552+
local function lt_datetime_nullable(lhs, rhs)
553+
if lhs == nil and rhs ~= nil then
554+
return true
555+
elseif rhs == nil then
556+
return false
557+
end
558+
return opt_datetime_parse(lhs) < opt_datetime_parse(rhs)
559+
end
560+
561+
local function lt_datetime_strict(lhs, rhs)
562+
if rhs == nil then
563+
return false
564+
end
565+
return opt_datetime_parse(lhs) < opt_datetime_parse(rhs)
566+
end
567+
536568
local function lt_unicode_ci_nullable(lhs, rhs)
537569
if lhs == nil and rhs ~= nil then
538570
return true
@@ -567,6 +599,20 @@ local function eq_uuid_strict(lhs, rhs)
567599
return tostring(lhs) == tostring(rhs)
568600
end
569601

602+
local function eq_datetime(lhs, rhs)
603+
if lhs == nil then
604+
return rhs == nil
605+
end
606+
return opt_datetime_parse(lhs) == opt_datetime_parse(rhs)
607+
end
608+
609+
local function eq_datetime_strict(lhs, rhs)
610+
if rhs == nil then
611+
return false
612+
end
613+
return opt_datetime_parse(lhs) == opt_datetime_parse(rhs)
614+
end
615+
570616
local function eq_unicode_nullable(lhs, rhs)
571617
if lhs == nil and rhs == nil then
572618
return true
@@ -604,6 +650,8 @@ local library = {
604650
eq = eq,
605651
eq_uuid = eq_uuid,
606652
eq_uuid_strict = eq_uuid_strict,
653+
eq_datetime = eq_datetime,
654+
eq_datetime_strict = eq_datetime_strict,
607655
-- nullable
608656
eq_unicode = eq_unicode_nullable,
609657
eq_unicode_ci = eq_unicode_ci_nullable,
@@ -618,12 +666,14 @@ local library = {
618666
lt_unicode_ci = lt_unicode_ci_nullable,
619667
lt_boolean = lt_boolean_nullable,
620668
lt_uuid = lt_uuid_nullable,
669+
lt_datetime = lt_datetime_nullable,
621670
-- strict
622671
lt_strict = lt_strict,
623672
lt_unicode_strict = lt_unicode_strict,
624673
lt_unicode_ci_strict = lt_unicode_ci_strict,
625674
lt_boolean_strict = lt_boolean_strict,
626675
lt_uuid_strict = lt_uuid_strict,
676+
lt_datetime_strict = lt_datetime_strict,
627677

628678
utf8 = utf8,
629679

test/entrypoint/srv_select/storage_init.lua

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
local datetime_supported, _ = pcall(require, 'datetime')
2+
13
local crud_utils = require('crud.common.utils')
24

35
return function()
@@ -227,4 +229,100 @@ return function()
227229
unique = false,
228230
if_not_exists = true,
229231
})
232+
233+
if datetime_supported then
234+
local datetime_format = {
235+
{name = 'id', type = 'unsigned'},
236+
{name = 'bucket_id', type = 'unsigned'},
237+
{name = 'datetime_field', type = 'datetime'},
238+
}
239+
240+
241+
local datetime_nonindexed_space = box.schema.space.create('datetime_nonindexed', {
242+
if_not_exists = true,
243+
engine = engine,
244+
})
245+
246+
datetime_nonindexed_space:format(datetime_format)
247+
248+
datetime_nonindexed_space:create_index('id_index', {
249+
parts = { 'id' },
250+
if_not_exists = true,
251+
})
252+
253+
datetime_nonindexed_space:create_index('bucket_id', {
254+
parts = { 'bucket_id' },
255+
unique = false,
256+
if_not_exists = true,
257+
})
258+
259+
260+
local datetime_indexed_space = box.schema.space.create('datetime_indexed', {
261+
if_not_exists = true,
262+
engine = engine,
263+
})
264+
265+
datetime_indexed_space:format(datetime_format)
266+
267+
datetime_indexed_space:create_index('id_index', {
268+
parts = { 'id' },
269+
if_not_exists = true,
270+
})
271+
272+
datetime_indexed_space:create_index('bucket_id', {
273+
parts = { 'bucket_id' },
274+
unique = false,
275+
if_not_exists = true,
276+
})
277+
278+
datetime_indexed_space:create_index('datetime_index', {
279+
parts = { 'datetime_field' },
280+
unique = false,
281+
if_not_exists = true,
282+
})
283+
284+
285+
local datetime_pk_space = box.schema.space.create('datetime_pk', {
286+
if_not_exists = true,
287+
engine = engine,
288+
})
289+
290+
datetime_pk_space:format(datetime_format)
291+
292+
datetime_pk_space:create_index('datetime_index', {
293+
parts = { 'datetime_field' },
294+
if_not_exists = true,
295+
})
296+
297+
datetime_pk_space:create_index('bucket_id', {
298+
parts = { 'bucket_id' },
299+
unique = false,
300+
if_not_exists = true,
301+
})
302+
303+
304+
local datetime_multipart_index_space = box.schema.space.create('datetime_multipart_index', {
305+
if_not_exists = true,
306+
engine = engine,
307+
})
308+
309+
datetime_multipart_index_space:format(datetime_format)
310+
311+
datetime_multipart_index_space:create_index('id_index', {
312+
parts = { 'id' },
313+
if_not_exists = true,
314+
})
315+
316+
datetime_multipart_index_space:create_index('bucket_id', {
317+
parts = { 'bucket_id' },
318+
unique = false,
319+
if_not_exists = true,
320+
})
321+
322+
datetime_multipart_index_space:create_index('datetime_index', {
323+
parts = { 'id', 'datetime_field' },
324+
unique = false,
325+
if_not_exists = true,
326+
})
327+
end
230328
end

test/helper.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,4 +958,9 @@ function helpers.prepare_ordered_data(g, space, expected_objects, bucket_id, ord
958958
t.assert_equals(objects, expected_objects)
959959
end
960960

961+
function helpers.skip_datetime_unsupported()
962+
local module_available, _ = pcall(require, 'datetime')
963+
t.skip_if(not module_available, 'datetime is not supported')
964+
end
965+
961966
return helpers

test/integration/count_test.lua

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -869,16 +869,24 @@ pgroup.test_count_force_map_call = function(g)
869869
t.assert_equals(result, 2)
870870
end
871871

872+
local read_impl = function(cg, space, conditions, opts)
873+
opts = table.deepcopy(opts) or {}
874+
opts.mode = 'write'
875+
876+
local resp, err = cg.cluster.main_server:call('crud.count', {space, conditions, opts})
877+
t.assert_equals(err, nil)
878+
879+
return resp
880+
end
881+
872882
pgroup.test_gh_418_count_with_secondary_noneq_index_condition = function(g)
873-
local read = function(cg, space, conditions, opts)
874-
opts = table.deepcopy(opts) or {}
875-
opts.mode = 'write'
883+
read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read_impl)
884+
end
876885

877-
local resp, err = cg.cluster.main_server:call('crud.count', {space, conditions, opts})
878-
t.assert_equals(err, nil)
886+
for case_name_template, case in pairs(read_scenario.gh_373_read_with_datetime_condition_cases) do
887+
local case_name = 'test_' .. case_name_template:format('count')
879888

880-
return resp
889+
pgroup[case_name] = function(g)
890+
case(g, read_impl)
881891
end
882-
883-
read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read)
884892
end

test/integration/pairs_readview_test.lua

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -882,27 +882,35 @@ pgroup.test_pairs_no_map_reduce = function(g)
882882
t.assert_equals(diff_2, 0, 'Select request was not a map reduce')
883883
end
884884

885-
pgroup.test_gh_418_pairs_with_secondary_noneq_index_condition = function(g)
886-
local read = function(cg, space, conditions, opts)
887-
opts = table.deepcopy(opts) or {}
888-
opts.use_tomap = true
885+
local function read_impl(cg, space, conditions, opts)
886+
opts = table.deepcopy(opts) or {}
887+
opts.use_tomap = true
889888

890-
return cg.cluster.main_server:exec(function(space, conditions, opts)
891-
local crud = require('crud')
889+
return cg.cluster.main_server:exec(function(space, conditions, opts)
890+
local crud = require('crud')
892891

893-
local rv, err = crud.readview()
894-
t.assert_equals(err, nil)
892+
local rv, err = crud.readview()
893+
t.assert_equals(err, nil)
895894

896-
local status, resp = pcall(function()
897-
return rv:pairs(space, conditions, opts):totable()
898-
end)
899-
t.assert(status, resp)
895+
local status, resp = pcall(function()
896+
return rv:pairs(space, conditions, opts):totable()
897+
end)
898+
t.assert(status, resp)
900899

901-
rv:close()
900+
rv:close()
902901

903-
return resp, nil
904-
end, {space, conditions, opts})
905-
end
902+
return resp, nil
903+
end, {space, conditions, opts})
904+
end
906905

907-
read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read)
906+
pgroup.test_gh_418_pairs_with_secondary_noneq_index_condition = function(g)
907+
read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read_impl)
908+
end
909+
910+
for case_name_template, case in pairs(read_scenario.gh_373_read_with_datetime_condition_cases) do
911+
local case_name = 'test_' .. case_name_template:format('pairs')
912+
913+
pgroup[case_name] = function(g)
914+
case(g, read_impl)
915+
end
908916
end

test/integration/pairs_test.lua

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -893,23 +893,31 @@ pgroup.test_pairs_no_map_reduce = function(g)
893893
t.assert_equals(diff_2, 0, 'Select request was not a map reduce')
894894
end
895895

896-
pgroup.test_gh_418_pairs_with_secondary_noneq_index_condition = function(g)
897-
local read = function(cg, space, conditions, opts)
898-
opts = table.deepcopy(opts) or {}
899-
opts.mode = 'write'
900-
opts.use_tomap = true
896+
local function read_impl(cg, space, conditions, opts)
897+
opts = table.deepcopy(opts) or {}
898+
opts.mode = 'write'
899+
opts.use_tomap = true
901900

902-
return cg.cluster.main_server:exec(function(space, conditions, opts)
903-
local crud = require('crud')
901+
return cg.cluster.main_server:exec(function(space, conditions, opts)
902+
local crud = require('crud')
904903

905-
local status, resp = pcall(function()
906-
return crud.pairs(space, conditions, opts):totable()
907-
end)
908-
t.assert(status, resp)
904+
local status, resp = pcall(function()
905+
return crud.pairs(space, conditions, opts):totable()
906+
end)
907+
t.assert(status, resp)
909908

910-
return resp, nil
911-
end, {space, conditions, opts})
912-
end
909+
return resp, nil
910+
end, {space, conditions, opts})
911+
end
913912

914-
read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read)
913+
pgroup.test_gh_418_pairs_with_secondary_noneq_index_condition = function(g)
914+
read_scenario.gh_418_read_with_secondary_noneq_index_condition(g, read_impl)
915+
end
916+
917+
for case_name_template, case in pairs(read_scenario.gh_373_read_with_datetime_condition_cases) do
918+
local case_name = 'test_' .. case_name_template:format('pairs')
919+
920+
pgroup[case_name] = function(g)
921+
case(g, read_impl)
922+
end
915923
end

0 commit comments

Comments
 (0)