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

Commit f5786c8

Browse files
committed
WIP: PoC of PCRE match of string fields
Related to #73.
1 parent 096fead commit f5786c8

File tree

8 files changed

+266
-51
lines changed

8 files changed

+266
-51
lines changed

README.md

Lines changed: 2 additions & 2 deletions
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

Lines changed: 120 additions & 41 deletions
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,25 @@ 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+
-- XXX: compile re once
715+
local re = rex.new(re)
716+
if not re:match(obj[field_name]) then
717+
return false
718+
end
719+
end
720+
721+
return true
722+
end
723+
699724
--- Perform unflatten, skipping, filtering, limiting of objects. This is the
700725
--- core of the `select_internal` function.
701726
---
@@ -741,6 +766,8 @@ local function process_tuple(state, tuple, opts)
741766
qstats.fetched_object_cnt, fetched_object_cnt_max))
742767
assert(qcontext.deadline_clock > clock.monotonic64(),
743768
'query execution timeout exceeded, use `timeout_ms` to increase it')
769+
local collection_name = opts.collection_name
770+
local pcre = opts.pcre
744771

745772
-- convert tuple -> object
746773
local obj = opts.unflatten_tuple(opts.collection_name, tuple,
@@ -755,7 +782,8 @@ local function process_tuple(state, tuple, opts)
755782
end
756783

757784
-- filter out non-matching objects
758-
local match = utils.is_subtable(obj, filter)
785+
local match = utils.is_subtable(obj, filter) and
786+
match_using_re(obj, pcre)
759787
if do_filter then
760788
if not match then return true end
761789
else
@@ -828,6 +856,8 @@ local function select_internal(self, collection_name, from, filter, args, extra)
828856
-- XXX: save type at parsing and check here
829857
--assert(args.offset == nil or type(args.offset) == 'number',
830858
-- 'args.offset must be a number of nil, got ' .. type(args.offset))
859+
assert(args.pcre == nil or type(args.pcre) == 'table',
860+
'args.pcre must be nil or a table, got ' .. type(args.pcre))
831861

832862
local collection = self.collections[collection_name]
833863
assert(collection ~= nil,
@@ -876,6 +906,7 @@ local function select_internal(self, collection_name, from, filter, args, extra)
876906
collection_name = collection_name,
877907
unflatten_tuple = self.funcs.unflatten_tuple,
878908
default_unflatten_tuple = default_unflatten_tuple,
909+
pcre = args.pcre,
879910
}
880911

881912
if index == nil then
@@ -976,6 +1007,87 @@ local function init_qcontext(accessor, qcontext)
9761007
settings.timeout_ms * 1000 * 1000
9771008
end
9781009

1010+
--- XXX
1011+
local function get_primary_key_type(self, collection_name)
1012+
-- get name of field of primary key
1013+
local _, index_meta = get_primary_index_meta(
1014+
self, collection_name)
1015+
1016+
local offset_fields = {}
1017+
1018+
for _, field_name in ipairs(index_meta.fields) do
1019+
local field_type
1020+
local collection = self.collections[collection_name]
1021+
local schema = self.schemas[collection.schema_name]
1022+
for _, field in ipairs(schema.fields) do
1023+
if field.name == field_name then
1024+
field_type = field.type
1025+
end
1026+
end
1027+
assert(field_type ~= nil,
1028+
('cannot find type for primary index field "%s" ' ..
1029+
'for collection "%s"'):format(field_name,
1030+
collection_name))
1031+
assert(type(field_type) == 'string',
1032+
'field type must be a string, got ' ..
1033+
type(field_type))
1034+
offset_fields[#offset_fields + 1] = {
1035+
name = field_name,
1036+
type = field_type,
1037+
}
1038+
end
1039+
1040+
local offset_type
1041+
assert(#offset_fields > 0,
1042+
'offset must contain at least one field')
1043+
if #offset_fields == 1 then
1044+
-- use a scalar type
1045+
offset_type = offset_fields[1].type
1046+
else
1047+
-- construct an input type
1048+
offset_type = {
1049+
name = collection_name .. '_offset',
1050+
type = 'record',
1051+
fields = offset_fields,
1052+
}
1053+
end
1054+
1055+
return offset_type
1056+
end
1057+
1058+
-- XXX: add string fields of a nested record / 1:1 connection
1059+
1060+
--- XXX
1061+
--- Note: it is only for list of objects and is not for destination of 1:1
1062+
--- connection
1063+
local function get_pcre_argument_type(self, collection_name)
1064+
local collection = self.collections[collection_name]
1065+
assert(collection ~= nil, 'cannot found collection ' ..
1066+
tostring(collection_name))
1067+
local schema = self.schemas[collection.schema_name]
1068+
assert(schema ~= nil, 'cannot found schema ' ..
1069+
tostring(collection.schema_name))
1070+
1071+
assert(schema.type == 'record',
1072+
'top-level object expected to be a record, got ' ..
1073+
tostring(schema.type))
1074+
1075+
local string_fields = {}
1076+
1077+
for _, field in ipairs(schema.fields) do
1078+
if field.type == 'string' or field.type == 'string*' then
1079+
string_fields[#string_fields + 1] = table.copy(field)
1080+
end
1081+
end
1082+
1083+
local pcre_type = {
1084+
name = collection_name .. '_pcre',
1085+
type = 'record',
1086+
fields = string_fields,
1087+
}
1088+
return pcre_type
1089+
end
1090+
9791091
--- Create a new data accessor.
9801092
---
9811093
--- Provided `funcs` argument determines certain functions for retrieving
@@ -1114,53 +1226,20 @@ function accessor_general.new(opts, funcs)
11141226
args, extra)
11151227
end,
11161228
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
1229+
local offset_type = get_primary_key_type(self, collection_name)
11441230

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-
}
1231+
-- add `pcre` argument only if lrexlib-pcre was found
1232+
local pcre_field
1233+
if rex ~= nil then
1234+
local pcre_type = get_pcre_argument_type(self, collection_name)
1235+
pcre_field = {name = 'pcre', type = pcre_type}
11581236
end
11591237

11601238
return {
11611239
{name = 'limit', type = 'int'},
11621240
{name = 'offset', type = offset_type},
11631241
-- {name = 'filter', type = ...},
1242+
pcre_field,
11641243
}
11651244
end,
11661245
}

graphql/core/rules.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ function rules.variablesAreUsed(node, context)
400400
for _, definition in ipairs(node.variableDefinitions) do
401401
local variableName = definition.variable.name.value
402402
if not context.variableReferences[variableName] then
403-
error('Unused variable "' .. variableName .. '"')
403+
--error('Unused variable "' .. variableName .. '"')
404404
end
405405
end
406406
end

graphql/core/util.lua

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,16 @@ function util.coerceValue(node, schemaType, variables)
7575
error('Expected an input object')
7676
end
7777

78-
return util.map(node.values, function(field)
78+
local inputObjectValue = {}
79+
for _, field in pairs(node.values) do
7980
if not schemaType.fields[field.name] then
8081
error('Unknown input object field "' .. field.name .. '"')
8182
end
8283

83-
return util.coerceValue(field.value, schemaType.fields[field.name].kind, variables)
84-
end)
84+
inputObjectValue[field.name] = util.coerceValue(
85+
field.value, schemaType.fields[field.name].kind, variables)
86+
end
87+
return inputObjectValue
8588
end
8689

8790
if schemaType.__type == 'Enum' then

graphql/tarantool_graphql.lua

Lines changed: 2 additions & 1 deletion
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

Lines changed: 11 additions & 0 deletions
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

0 commit comments

Comments
 (0)