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

Commit 8fad1da

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

File tree

7 files changed

+335
-5
lines changed

7 files changed

+335
-5
lines changed

graphql/accessor_general.lua

+29-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,28 @@ 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+
-- XXX: compile it outside of process_tuple
834+
if type(expr) == 'string' then
835+
expr = expressions.new(expr)
836+
end
837+
check(expr, 'expression', 'table')
838+
local res = expr:execute(obj, variables)
839+
check(res, 'expression result', 'boolean')
840+
return res
841+
end
842+
820843
--- Check whether we meet deadline time.
821844
---
822845
--- The functions raises an exception in the case.
@@ -857,6 +880,7 @@ end
857880
--- * `pivot_filter` (table, set of fields to match the objected pointed by
858881
--- `offset` arqument of the GraphQL query),
859882
--- * `resolveField` (function) for subrequests, see @{impl.new}.
883+
--- * XXX: describe other fields.
860884
---
861885
--- @return nil
862886
---
@@ -887,7 +911,9 @@ local function process_tuple(self, state, tuple, opts)
887911

888912
local collection_name = opts.collection_name
889913
local pcre = opts.pcre
914+
local expr = opts.expr
890915
local resolveField = opts.resolveField
916+
local variables = qcontext.variables
891917

892918
-- convert tuple -> object
893919
local obj = opts.unflatten_tuple(self, collection_name, tuple,
@@ -918,7 +944,7 @@ local function process_tuple(self, state, tuple, opts)
918944

919945
-- filter out non-matching objects
920946
local match = utils.is_subtable(obj, truncated_filter) and
921-
match_using_re(obj, pcre)
947+
match_using_re(obj, pcre) and match_using_expr(obj, expr, variables)
922948
if do_filter then
923949
if not match then return true end
924950
else
@@ -1069,6 +1095,7 @@ local function prepare_select_internal(self, collection_name, from, filter,
10691095
use_tomap = self.collection_use_tomap[collection_name] or false,
10701096
default_unflatten_tuple = default_unflatten_tuple,
10711097
pcre = args.pcre,
1098+
expr = args.filter,
10721099
resolveField = extra.resolveField,
10731100
is_hidden = extra.is_hidden,
10741101
}

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)