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

Commit 653a8bc

Browse files
committed
WIP: Integrate expressions into GraphQL
Part of #13.
1 parent 79a5a7d commit 653a8bc

File tree

7 files changed

+338
-5
lines changed

7 files changed

+338
-5
lines changed

graphql/accessor_general.lua

+32-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ local avro_helpers = require('graphql.avro_helpers')
1515
local db_schema_helpers = require('graphql.db_schema_helpers')
1616
local error_codes = require('graphql.error_codes')
1717
local statistics = require('graphql.statistics')
18+
local expressions = require('graphql.expressions')
1819

1920
local check = utils.check
2021
local e = error_codes
@@ -763,7 +764,7 @@ local function validate_collections(collections, schemas)
763764
end
764765
end
765766

766-
--- Whether an object match set of PCRE.
767+
--- Whether an object match set of PCREs.
767768
---
768769
--- @tparam table obj an object to check
769770
---
@@ -817,6 +818,24 @@ local function match_using_re(obj, pcre)
817818
return true
818819
end
819820

821+
--- Whether an object match an expression.
822+
---
823+
--- @tparam table obj an object to check
824+
---
825+
--- @param expr (table or string) compiled or raw expression
826+
---
827+
--- @tparam[opt] table variables variables values from the request
828+
---
829+
--- @treturn boolean `res` whether the `obj` object match `expr` expression
830+
local function match_using_expr(obj, expr, variables)
831+
if expr == nil then return true end
832+
833+
check(expr, 'expression', 'table')
834+
local res = expr:execute(obj, variables)
835+
check(res, 'expression result', 'boolean')
836+
return res
837+
end
838+
820839
--- Check whether we meet deadline time.
821840
---
822841
--- The functions raises an exception in the case.
@@ -857,6 +876,7 @@ end
857876
--- * `pivot_filter` (table, set of fields to match the objected pointed by
858877
--- `offset` arqument of the GraphQL query),
859878
--- * `resolveField` (function) for subrequests, see @{impl.new}.
879+
--- * XXX: describe other fields.
860880
---
861881
--- @return nil
862882
---
@@ -887,7 +907,9 @@ local function process_tuple(self, state, tuple, opts)
887907

888908
local collection_name = opts.collection_name
889909
local pcre = opts.pcre
910+
local expr = opts.expr
890911
local resolveField = opts.resolveField
912+
local variables = qcontext.variables
891913

892914
-- convert tuple -> object
893915
local obj = opts.unflatten_tuple(self, collection_name, tuple,
@@ -918,7 +940,7 @@ local function process_tuple(self, state, tuple, opts)
918940

919941
-- filter out non-matching objects
920942
local match = utils.is_subtable(obj, truncated_filter) and
921-
match_using_re(obj, pcre)
943+
match_using_re(obj, pcre) and match_using_expr(obj, expr, variables)
922944
if do_filter then
923945
if not match then return true end
924946
else
@@ -1058,6 +1080,13 @@ local function prepare_select_internal(self, collection_name, from, filter,
10581080
qcontext = qcontext
10591081
}
10601082

1083+
-- compile an expression argument if provided and did not compiled yet
1084+
local expr = args.filter
1085+
check(expr, 'expression', 'table', 'string', 'nil')
1086+
if type(expr) == 'string' then
1087+
expr = expressions.new(expr)
1088+
end
1089+
10611090
-- read only process_tuple options
10621091
local select_opts = {
10631092
limit = args.limit,
@@ -1069,6 +1098,7 @@ local function prepare_select_internal(self, collection_name, from, filter,
10691098
use_tomap = self.collection_use_tomap[collection_name] or false,
10701099
default_unflatten_tuple = default_unflatten_tuple,
10711100
pcre = args.pcre,
1101+
expr = expr,
10721102
resolveField = extra.resolveField,
10731103
is_hidden = extra.is_hidden,
10741104
}

graphql/core/rules.lua

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

417417
function rules.variablesAreUsed(node, context)
418+
-- a filter passed as a variable can use any other variables, so we disable
419+
-- this check for the operation with such filter
420+
local operationName = node.name and node.name.value or ''
421+
if context.operationHasVarFilterArgument[operationName] then
422+
return
423+
end
424+
418425
if node.variableDefinitions then
419426
for _, definition in ipairs(node.variableDefinitions) do
420427
local variableName = definition.variable.name.value
@@ -604,4 +611,58 @@ end
604611

605612
-- }}}
606613

614+
-- {{{ compileFilterArgument
615+
616+
-- XXX: support variable filters
617+
618+
function rules.compileFilterArgument(node, context)
619+
local expressions = require('graphql.expressions') -- XXX: move upward
620+
621+
assert(node.kind == 'argument')
622+
local argument_node = node
623+
local argument_name = argument_node.name.value
624+
625+
-- XXX: 1:1-connected object can have field 'filter'
626+
if argument_name ~= 'filter' then return end
627+
628+
-- save compiled expression to <string> node
629+
local value_node = argument_node.value
630+
if value_node.kind == 'variable' then
631+
local operation = context.currentOperation
632+
local operation_name = operation.name and operation.name.value or ''
633+
context.operationHasVarFilterArgument[operation_name] = true
634+
return
635+
end
636+
assert(value_node.kind == 'string') -- XXX: better error
637+
local string_node = value_node
638+
local value = string_node.value
639+
assert(type(value) == 'string') -- XXX: better error
640+
local compiled_expr = expressions.new(value)
641+
string_node.compiled = compiled_expr
642+
643+
-- XXX: don't blindly find the pattern {kind = 'variable', ...}, but either
644+
-- traverse a tree according to node kinds or export used variables info from
645+
-- expressions module
646+
647+
-- mark used variables
648+
local open_set = {compiled_expr}
649+
while true do
650+
local e_node = table.remove(open_set, 1)
651+
if e_node == nil then break end
652+
if type(e_node) == 'table' then
653+
if e_node.kind == 'variable' then
654+
context.variableReferences[e_node.name] = true
655+
else
656+
for _, child_e_node in pairs(e_node) do
657+
table.insert(open_set, child_e_node)
658+
end
659+
end
660+
end
661+
end
662+
end
663+
664+
-- }}}
665+
607666
return rules
667+
668+
-- vim: set ts=2 sw=2 et:

graphql/core/types.lua

+7
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,11 @@ types.string = types.scalar({
393393
parseValue = tostring,
394394
parseLiteral = function(node)
395395
if node.kind == 'string' then
396+
-- handle value of filter argument
397+
if node.compiled ~= nil then
398+
return node.compiled
399+
end
400+
396401
return node.value
397402
end
398403
end,
@@ -476,3 +481,5 @@ types.skip = types.directive({
476481
})
477482

478483
return types
484+
485+
-- vim: set ts=2 sw=2 et:

graphql/core/validate.lua

+6-2
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,10 @@ local visitors = {
413413
end)
414414
end,
415415

416-
rules = { rules.uniqueInputObjectFields }
416+
rules = {
417+
rules.uniqueInputObjectFields,
418+
rules.compileFilterArgument,
419+
}
417420
},
418421

419422
-- <inputObject>
@@ -476,7 +479,8 @@ return function(schema, tree)
476479
usedFragments = {},
477480
objects = {},
478481
currentOperation = nil,
479-
variableReferences = nil
482+
variableReferences = nil,
483+
operationHasVarFilterArgument = {},
480484
}
481485

482486
local function visit(node)

graphql/gen_arguments.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ function gen_arguments.list_args(db_schema, collection_name)
348348
return {
349349
{name = 'limit', type = 'int*'},
350350
{name = 'offset', type = offset_type},
351-
-- {name = 'filter', type = ...},
351+
{name = 'filter', type = 'string*'},
352352
pcre_field,
353353
}
354354
end

graphql/impl.lua

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ local function gql_execute(qstate, variables, operation_name)
5151

5252
local qcontext = {
5353
query_settings = qstate.query_settings,
54+
variables = variables,
5455
}
5556

5657
local traceback

0 commit comments

Comments
 (0)