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

Commit cf453c0

Browse files
authored
Merge pull request #209 from Hollow111/N_Tatunov/gh-13-c-style-expressions
graphql: implement c-style expressions parser
2 parents 87bb0c8 + ecdb29d commit cf453c0

16 files changed

+1679
-366
lines changed

graphql/accessor_general.lua

+39-32
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ local json = require('json')
99
local avro_schema = require('avro_schema')
1010
local utils = require('graphql.utils')
1111
local clock = require('clock')
12-
local bit = require('bit')
13-
local rex, is_pcre2 = utils.optional_require_rex()
12+
local rex = utils.optional_require_rex()
1413
local avro_helpers = require('graphql.avro_helpers')
1514
local db_schema_helpers = require('graphql.db_schema_helpers')
1615
local error_codes = require('graphql.error_codes')
1716
local statistics = require('graphql.statistics')
17+
local expressions = require('graphql.expressions')
1818

1919
local check = utils.check
2020
local e = error_codes
@@ -763,16 +763,16 @@ local function validate_collections(collections, schemas)
763763
end
764764
end
765765

766-
--- Whether an object match set of PCRE.
767-
---
768-
--- @tparam table obj an object to check
766+
--- Whether an object match set of PCREs.
769767
---
770768
--- @tparam table pcre map with PCRE as values; names are correspond to field
771769
--- names of the `obj` to match
772770
---
771+
--- @tparam table obj an object to check
772+
---
773773
--- @treturn boolean `res` whether the `obj` object match `pcre` set of
774774
--- regexps.
775-
local function match_using_re(obj, pcre)
775+
local function match_using_re(pcre, obj)
776776
if pcre == nil then return true end
777777

778778
assert(rex ~= nil, 'we should not pass over :compile() ' ..
@@ -785,38 +785,34 @@ local function match_using_re(obj, pcre)
785785
return false
786786
end
787787
if type(re) == 'table' then
788-
local match = match_using_re(obj[field_name], re)
788+
local match = match_using_re(re, obj[field_name])
789789
if not match then return false end
790-
else
791-
local flags = rex.flags()
792-
-- emulate behaviour of (?i) on libpcre (libpcre2 supports it)
793-
local cfg = 0
794-
if not is_pcre2 then
795-
local cnt
796-
re, cnt = re:gsub('^%(%?i%)', '')
797-
if cnt > 0 then
798-
cfg = bit.bor(cfg, flags.CASELESS)
799-
end
800-
end
801-
-- enable UTF-8
802-
if is_pcre2 then
803-
cfg = bit.bor(cfg, flags.UTF)
804-
cfg = bit.bor(cfg, flags.UCP)
805-
else
806-
cfg = bit.bor(cfg, flags.UTF8)
807-
cfg = bit.bor(cfg, flags.UCP)
808-
end
809-
-- XXX: compile re once
810-
local re = rex.new(re, cfg)
811-
if not re:match(obj[field_name]) then
812-
return false
813-
end
790+
elseif not utils.regexp(re, obj[field_name]) then
791+
return false
814792
end
815793
end
816794

817795
return true
818796
end
819797

798+
--- Whether an object match an expression.
799+
---
800+
--- @param expr (table or string) compiled or raw expression
801+
---
802+
--- @tparam table obj an object to check
803+
---
804+
--- @tparam[opt] table variables variables values from the request
805+
---
806+
--- @treturn boolean `res` whether the `obj` object match `expr` expression
807+
local function match_using_expr(expr, obj, variables)
808+
if expr == nil then return true end
809+
810+
check(expr, 'expression', 'table')
811+
local res = expr:execute(obj, variables)
812+
check(res, 'expression result', 'boolean')
813+
return res
814+
end
815+
820816
--- Check whether we meet deadline time.
821817
---
822818
--- The functions raises an exception in the case.
@@ -857,6 +853,7 @@ end
857853
--- * `pivot_filter` (table, set of fields to match the objected pointed by
858854
--- `offset` arqument of the GraphQL query),
859855
--- * `resolveField` (function) for subrequests, see @{impl.new}.
856+
--- * XXX: describe other fields.
860857
---
861858
--- @return nil
862859
---
@@ -887,7 +884,9 @@ local function process_tuple(self, state, tuple, opts)
887884

888885
local collection_name = opts.collection_name
889886
local pcre = opts.pcre
887+
local expr = opts.expr
890888
local resolveField = opts.resolveField
889+
local variables = qcontext.variables
891890

892891
-- convert tuple -> object
893892
local obj = opts.unflatten_tuple(self, collection_name, tuple,
@@ -918,7 +917,7 @@ local function process_tuple(self, state, tuple, opts)
918917

919918
-- filter out non-matching objects
920919
local match = utils.is_subtable(obj, truncated_filter) and
921-
match_using_re(obj, pcre)
920+
match_using_re(pcre, obj) and match_using_expr(expr, obj, variables)
922921
if do_filter then
923922
if not match then return true end
924923
else
@@ -1058,6 +1057,13 @@ local function prepare_select_internal(self, collection_name, from, filter,
10581057
qcontext = qcontext
10591058
}
10601059

1060+
-- compile an expression argument if provided and did not compiled yet
1061+
local expr = args.filter
1062+
check(expr, 'expression', 'table', 'string', 'nil')
1063+
if type(expr) == 'string' then
1064+
expr = expressions.new(expr)
1065+
end
1066+
10611067
-- read only process_tuple options
10621068
local select_opts = {
10631069
limit = args.limit,
@@ -1069,6 +1075,7 @@ local function prepare_select_internal(self, collection_name, from, filter,
10691075
use_tomap = self.collection_use_tomap[collection_name] or false,
10701076
default_unflatten_tuple = default_unflatten_tuple,
10711077
pcre = args.pcre,
1078+
expr = expr,
10721079
resolveField = extra.resolveField,
10731080
is_hidden = extra.is_hidden,
10741081
}

graphql/convert_schema/schema.lua

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
--- Convert extended avro-schema (collections) to GraphQL schema.
22

3+
local log = require('log')
34
local core_types = require('graphql.core.types')
45
local core_schema = require('graphql.core.schema')
56
local gen_arguments = require('graphql.gen_arguments')
@@ -358,6 +359,17 @@ function schema.convert(state, cfg)
358359
local object_args = state.object_arguments[collection_name]
359360
local list_args = state.list_arguments[collection_name]
360361

362+
-- check for names clash
363+
for name, _ in pairs(list_args) do
364+
if object_args[name] ~= nil then
365+
local err = ('the argument "%s" generated from the same ' ..
366+
'named field of the collection "%s" is superseded with ' ..
367+
'the list filtering argument "%s"'):format(name,
368+
collection_name, name)
369+
log.warn(err)
370+
end
371+
end
372+
361373
local args = utils.merge_tables(object_args, list_args)
362374
state.all_arguments[collection_name] = args
363375
end

graphql/core/parse.lua

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ local function cOperation(...)
150150

151151
for i = 2, #args do
152152
-- key ~= nil => result['selectionSet'] = <...>
153+
-- or result['name'] = <...>
153154
-- key == nil => result['variableDefinitions'] = <...>
154155
-- or result['directives'] = <...>
155156
local key = args[i].kind

graphql/core/rules.lua

+7
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,11 @@ function rules.variableDefaultValuesHaveCorrectType(node, context)
415415
end
416416

417417
function rules.variablesAreUsed(node, context)
418+
local operationName = node.name and node.name.value or ''
419+
if context.skipVariableUseCheck[operationName] then
420+
return
421+
end
422+
418423
if node.variableDefinitions then
419424
for _, definition in ipairs(node.variableDefinitions) do
420425
local variableName = definition.variable.name.value
@@ -605,3 +610,5 @@ end
605610
-- }}}
606611

607612
return rules
613+
614+
-- vim: set ts=2 sw=2 et:

graphql/core/util.lua

+5
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ function util.coerceValue(node, schemaType, variables, opts)
9292
return nil
9393
end
9494

95+
-- handle precompiled values
96+
if node.compiled ~= nil then
97+
return node.compiled
98+
end
99+
95100
if node.kind == 'variable' then
96101
return variables[node.name.value]
97102
end

graphql/core/validate.lua

+51-21
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ local function getParentField(context, name, count)
1818
return parent.fields[name]
1919
end
2020

21-
local visitors = {
21+
local defaultVisitors = {
2222
-- <document>
2323
--
2424
-- {
@@ -51,6 +51,10 @@ local visitors = {
5151
-- {
5252
-- kind = 'operation',
5353
-- operation = 'query' or 'mutation',
54+
-- name = {
55+
-- kind = 'name',
56+
-- value = <string>,
57+
-- } (optional),
5458
-- selectionSet = <selectionSet>,
5559
-- variableDefinitions = list of <variableDefinition> (optional),
5660
-- directives = list of <directive> (optional),
@@ -463,7 +467,7 @@ local visitors = {
463467
}
464468
}
465469

466-
return function(schema, tree)
470+
return function(schema, tree, extraVisitors)
467471
local context = {
468472
schema = schema,
469473
fragmentMap = {},
@@ -472,41 +476,67 @@ return function(schema, tree)
472476
usedFragments = {},
473477
objects = {},
474478
currentOperation = nil,
475-
variableReferences = nil
479+
variableReferences = nil,
480+
skipVariableUseCheck = {}, -- operation name -> boolean
476481
}
477482

478483
local function visit(node)
479-
local visitor = node.kind and visitors[node.kind]
484+
local visitors = {}
485+
visitors[#visitors + 1] = node.kind and defaultVisitors[node.kind]
486+
visitors[#visitors + 1] = node.kind and extraVisitors[node.kind]
487+
488+
local enterList = {}
489+
local ruleList = {}
490+
local childrenList = {}
491+
local exitRuleList = {}
492+
local exitList = {}
493+
494+
-- collect visitors methods
495+
for _, visitor in ipairs(visitors) do
496+
enterList[#enterList + 1] = visitor.enter
497+
498+
if visitor.rules then
499+
for _, rule in ipairs(visitor.rules) do
500+
ruleList[#ruleList + 1] = rule
501+
end
502+
end
480503

481-
if not visitor then return end
504+
childrenList[#childrenList + 1] = visitor.children
482505

483-
if visitor.enter then
484-
visitor.enter(node, context)
506+
if visitor.rules and visitor.rules.exit then
507+
for _, exitRule in ipairs(visitor.rules.exit) do
508+
exitRuleList[#exitRuleList + 1] = exitRule
509+
end
510+
end
511+
512+
exitList[#exitList + 1] = visitor.exit
485513
end
486514

487-
if visitor.rules then
488-
for i = 1, #visitor.rules do
489-
visitor.rules[i](node, context)
490-
end
515+
-- invoke visitors methods
516+
517+
for _, enter in ipairs(enterList) do
518+
enter(node, context)
491519
end
492520

493-
if visitor.children then
494-
local children = visitor.children(node)
495-
if children then
496-
for _, child in ipairs(children) do
521+
for _, rule in ipairs(ruleList) do
522+
rule(node, context)
523+
end
524+
525+
for _, children in ipairs(childrenList) do
526+
local childs = children(node)
527+
if childs then
528+
for _, child in ipairs(childs) do
497529
visit(child)
498530
end
499531
end
500532
end
501533

502-
if visitor.rules and visitor.rules.exit then
503-
for i = 1, #visitor.rules.exit do
504-
visitor.rules.exit[i](node, context)
505-
end
534+
for _, exitRule in ipairs(exitRuleList) do
535+
exitRule(node, context)
506536
end
507537

508-
if visitor.exit then
509-
visitor.exit(node, context)
538+
for _, exit in ipairs(exitList) do
539+
exit(node, context)
510540
end
511541
end
512542

0 commit comments

Comments
 (0)