Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit 1e14b99

Browse files
committed
WIP: PoC of PCRE match of string fields
Related to #73.
1 parent 215c1df commit 1e14b99

File tree

7 files changed

+316
-48
lines changed

7 files changed

+316
-48
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ make test
9595

9696
## Requirements
9797

98-
* For use: tarantool, lulpeg, >=tarantool/shard-1.1-91-gfa88bf8 (optional),
99-
tarantool/avro-schema.
98+
* For use: tarantool, lulpeg, tarantool/avro-schema,
99+
>=tarantool/shard-1.1-91-gfa88bf8 (optional), lrexlib-pcre (optional).
100100
* For test (additionally to 'for use'): python 2.7, virtualenv, luacheck,
101101
>=tarantool/shard-1.1-92-gec1a27e.
102102
* For building apidoc (additionally to 'for use'): ldoc.

graphql/accessor_general.lua

+124-42
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ local json = require('json')
88
local avro_schema = require('avro_schema')
99
local utils = require('graphql.utils')
1010
local clock = require('clock')
11+
local rex = utils.optional_require('rex_pcre')
12+
13+
-- XXX: consider using [1] when it will be mature enough;
14+
-- look into [2] for the status.
15+
-- [1]: https://github.com/igormunkin/lua-re
16+
-- [2]: https://github.com/tarantool/tarantool/issues/2764
1117

1218
local accessor_general = {}
1319
local DEF_RESULTING_OBJECT_CNT_MAX = 10000
@@ -696,6 +702,28 @@ local function validate_collections(collections, schemas)
696702
end
697703
end
698704

705+
--- XXX
706+
local function match_using_re(obj, pcre)
707+
if pcre == nil then return true end
708+
709+
for field_name, re in pairs(pcre) do
710+
-- skip an object with null in a string* field
711+
if obj[field_name] == nil then
712+
return false
713+
end
714+
assert(rex ~= nil, 'we should not pass over :compile() ' ..
715+
'with a query contains PCRE matching when there are '..
716+
'no lrexlib-pcre (rex_pcre) module present')
717+
-- XXX: compile re once
718+
local re = rex.new(re)
719+
if not re:match(obj[field_name]) then
720+
return false
721+
end
722+
end
723+
724+
return true
725+
end
726+
699727
--- Perform unflatten, skipping, filtering, limiting of objects. This is the
700728
--- core of the `select_internal` function.
701729
---
@@ -741,9 +769,11 @@ local function process_tuple(state, tuple, opts)
741769
qstats.fetched_object_cnt, fetched_object_cnt_max))
742770
assert(qcontext.deadline_clock > clock.monotonic64(),
743771
'query execution timeout exceeded, use `timeout_ms` to increase it')
772+
local collection_name = opts.collection_name
773+
local pcre = opts.pcre
744774

745775
-- convert tuple -> object
746-
local obj = opts.unflatten_tuple(opts.collection_name, tuple,
776+
local obj = opts.unflatten_tuple(collection_name, tuple,
747777
opts.default_unflatten_tuple)
748778

749779
-- skip all items before pivot (the item pointed by offset)
@@ -755,7 +785,8 @@ local function process_tuple(state, tuple, opts)
755785
end
756786

757787
-- filter out non-matching objects
758-
local match = utils.is_subtable(obj, filter)
788+
local match = utils.is_subtable(obj, filter) and
789+
match_using_re(obj, pcre)
759790
if do_filter then
760791
if not match then return true end
761792
else
@@ -828,6 +859,8 @@ local function select_internal(self, collection_name, from, filter, args, extra)
828859
-- XXX: save type at parsing and check here
829860
--assert(args.offset == nil or type(args.offset) == 'number',
830861
-- 'args.offset must be a number of nil, got ' .. type(args.offset))
862+
assert(args.pcre == nil or type(args.pcre) == 'table',
863+
'args.pcre must be nil or a table, got ' .. type(args.pcre))
831864

832865
local collection = self.collections[collection_name]
833866
assert(collection ~= nil,
@@ -876,6 +909,7 @@ local function select_internal(self, collection_name, from, filter, args, extra)
876909
collection_name = collection_name,
877910
unflatten_tuple = self.funcs.unflatten_tuple,
878911
default_unflatten_tuple = default_unflatten_tuple,
912+
pcre = args.pcre,
879913
}
880914

881915
if index == nil then
@@ -976,6 +1010,87 @@ local function init_qcontext(accessor, qcontext)
9761010
settings.timeout_ms * 1000 * 1000
9771011
end
9781012

1013+
--- XXX
1014+
local function get_primary_key_type(self, collection_name)
1015+
-- get name of field of primary key
1016+
local _, index_meta = get_primary_index_meta(
1017+
self, collection_name)
1018+
1019+
local offset_fields = {}
1020+
1021+
for _, field_name in ipairs(index_meta.fields) do
1022+
local field_type
1023+
local collection = self.collections[collection_name]
1024+
local schema = self.schemas[collection.schema_name]
1025+
for _, field in ipairs(schema.fields) do
1026+
if field.name == field_name then
1027+
field_type = field.type
1028+
end
1029+
end
1030+
assert(field_type ~= nil,
1031+
('cannot find type for primary index field "%s" ' ..
1032+
'for collection "%s"'):format(field_name,
1033+
collection_name))
1034+
assert(type(field_type) == 'string',
1035+
'field type must be a string, got ' ..
1036+
type(field_type))
1037+
offset_fields[#offset_fields + 1] = {
1038+
name = field_name,
1039+
type = field_type,
1040+
}
1041+
end
1042+
1043+
local offset_type
1044+
assert(#offset_fields > 0,
1045+
'offset must contain at least one field')
1046+
if #offset_fields == 1 then
1047+
-- use a scalar type
1048+
offset_type = offset_fields[1].type
1049+
else
1050+
-- construct an input type
1051+
offset_type = {
1052+
name = collection_name .. '_offset',
1053+
type = 'record',
1054+
fields = offset_fields,
1055+
}
1056+
end
1057+
1058+
return offset_type
1059+
end
1060+
1061+
-- XXX: add string fields of a nested record / 1:1 connection
1062+
1063+
--- XXX
1064+
--- Note: it is only for list of objects and is not for destination of 1:1
1065+
--- connection
1066+
local function get_pcre_argument_type(self, collection_name)
1067+
local collection = self.collections[collection_name]
1068+
assert(collection ~= nil, 'cannot found collection ' ..
1069+
tostring(collection_name))
1070+
local schema = self.schemas[collection.schema_name]
1071+
assert(schema ~= nil, 'cannot found schema ' ..
1072+
tostring(collection.schema_name))
1073+
1074+
assert(schema.type == 'record',
1075+
'top-level object expected to be a record, got ' ..
1076+
tostring(schema.type))
1077+
1078+
local string_fields = {}
1079+
1080+
for _, field in ipairs(schema.fields) do
1081+
if field.type == 'string' or field.type == 'string*' then
1082+
string_fields[#string_fields + 1] = table.copy(field)
1083+
end
1084+
end
1085+
1086+
local pcre_type = {
1087+
name = collection_name .. '_pcre',
1088+
type = 'record',
1089+
fields = string_fields,
1090+
}
1091+
return pcre_type
1092+
end
1093+
9791094
--- Create a new data accessor.
9801095
---
9811096
--- Provided `funcs` argument determines certain functions for retrieving
@@ -1114,53 +1229,20 @@ function accessor_general.new(opts, funcs)
11141229
args, extra)
11151230
end,
11161231
list_args = function(self, collection_name)
1117-
-- get name of field of primary key
1118-
local _, index_meta = get_primary_index_meta(
1119-
self, collection_name)
1120-
1121-
local offset_fields = {}
1122-
1123-
for _, field_name in ipairs(index_meta.fields) do
1124-
local field_type
1125-
local collection = self.collections[collection_name]
1126-
local schema = self.schemas[collection.schema_name]
1127-
for _, field in ipairs(schema.fields) do
1128-
if field.name == field_name then
1129-
field_type = field.type
1130-
end
1131-
end
1132-
assert(field_type ~= nil,
1133-
('cannot find type for primary index field "%s" ' ..
1134-
'for collection "%s"'):format(field_name,
1135-
collection_name))
1136-
assert(type(field_type) == 'string',
1137-
'field type must be a string, got ' ..
1138-
type(field_type))
1139-
offset_fields[#offset_fields + 1] = {
1140-
name = field_name,
1141-
type = field_type,
1142-
}
1143-
end
1232+
local offset_type = get_primary_key_type(self, collection_name)
11441233

1145-
local offset_type
1146-
assert(#offset_fields > 0,
1147-
'offset must contain at least one field')
1148-
if #offset_fields == 1 then
1149-
-- use a scalar type
1150-
offset_type = offset_fields[1].type
1151-
else
1152-
-- construct an input type
1153-
offset_type = {
1154-
name = collection_name .. '_offset',
1155-
type = 'record',
1156-
fields = offset_fields,
1157-
}
1234+
-- add `pcre` argument only if lrexlib-pcre was found
1235+
local pcre_field
1236+
if rex ~= nil then
1237+
local pcre_type = get_pcre_argument_type(self, collection_name)
1238+
pcre_field = {name = 'pcre', type = pcre_type}
11581239
end
11591240

11601241
return {
11611242
{name = 'limit', type = 'int'},
11621243
{name = 'offset', type = offset_type},
11631244
-- {name = 'filter', type = ...},
1245+
pcre_field,
11641246
}
11651247
end,
11661248
}

graphql/tarantool_graphql.lua

+2-1
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,8 @@ end
669669
--- list_args = function(self, collection_name)
670670
--- return {
671671
--- {name = 'limit', type = 'int'},
672-
--- {name = 'offset', type = <...>}, -- type of primary key
672+
--- {name = 'offset', type = <...>}, -- type of a primary key
673+
--- {name = 'pcre', type = <...>},
673674
--- }
674675
--- end,
675676
--- }

graphql/utils.lua

+11
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,15 @@ function utils.gen_booking_table(data)
132132
})
133133
end
134134

135+
--- XXX
136+
function utils.optional_require(module_name)
137+
assert(type(module_name) == 'string',
138+
'module_name must be a string, got ' .. type(module_name))
139+
local ok, module = pcall(require, module_name)
140+
if not ok then
141+
log.warn('optional_require: no module ' .. module_name)
142+
end
143+
return ok and module or nil
144+
end
145+
135146
return utils

test/local/space_pcre.result

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
RUN 1_1 {{{
2+
QUERY
3+
query users($offset: String, $first_name_re: String,
4+
$middle_name_re: String) {
5+
user_collection(pcre: {first_name: $first_name_re,
6+
middle_name: $middle_name_re}, offset: $offset) {
7+
first_name
8+
middle_name
9+
last_name
10+
}
11+
}
12+
VARIABLES
13+
---
14+
middle_name_re: ich$
15+
first_name_re: ^I
16+
...
17+
18+
RESULT
19+
---
20+
user_collection:
21+
- last_name: Ivanov
22+
first_name: Ivan
23+
middle_name: Ivanovich
24+
...
25+
26+
}}}
27+
28+
RUN 1_2 {{{
29+
QUERY
30+
query users($offset: String, $first_name_re: String,
31+
$middle_name_re: String) {
32+
user_collection(pcre: {first_name: $first_name_re,
33+
middle_name: $middle_name_re}, offset: $offset) {
34+
first_name
35+
middle_name
36+
last_name
37+
}
38+
}
39+
VARIABLES
40+
---
41+
user_id: user_id_1
42+
first_name_re: ^V
43+
...
44+
45+
RESULT
46+
---
47+
user_collection:
48+
- last_name: Pupkin
49+
first_name: Vasiliy
50+
...
51+
52+
}}}
53+

0 commit comments

Comments
 (0)