@@ -9,6 +9,8 @@ local avro_schema = require('avro_schema')
9
9
local utils = require (' graphql.utils' )
10
10
11
11
local accessor_general = {}
12
+ local DEF_RESULTING_OBJECT_CNT_MAX = 10000
13
+ local DEF_FETCHED_OBJECT_CNT_MAX = 10000
12
14
13
15
--- Validate and compile set of avro schemas (with respect to service fields).
14
16
--- @tparam table schemas map where keys are string names and values are
550
552
---
551
553
--- * `count` (number),
552
554
--- * `objs` (table, list of objects),
553
- --- * `pivot_found` (boolean).
555
+ --- * `pivot_found` (boolean),
556
+ --- * `statistics` (table, per-query statistics).
554
557
---
555
558
--- @tparam cdata tuple flatten representation of an object to process
556
559
---
562
565
--- * `do_filter` (boolean, whether we need to filter out non-matching
563
566
--- objects),
564
567
--- * `pivot_filter` (table, set of fields to match the objected pointed by
565
- --- `offset` arqument of the GraphQL query)
568
+ --- `offset` arqument of the GraphQL query),
569
+ --- * `resulting_object_cnt_max` (number),
570
+ --- * `fetched_object_cnt_max` (number).
566
571
---
567
572
--- @return nil
568
573
---
@@ -580,6 +585,14 @@ local function process_tuple(state, tuple, opts)
580
585
local filter = opts .filter
581
586
local do_filter = opts .do_filter
582
587
local pivot_filter = opts .pivot_filter
588
+ local qstats = state .statistics
589
+ local resulting_object_cnt_max = opts .resulting_object_cnt_max
590
+ local fetched_object_cnt_max = opts .fetched_object_cnt_max
591
+ qstats .fetched_object_cnt = qstats .fetched_object_cnt + 1
592
+ assert (qstats .fetched_object_cnt <= fetched_object_cnt_max ,
593
+ (' fetched object count[%d] exceeds limit[%d] ' ..
594
+ ' (`fetched_object_cnt_max` in accessor)' ):format (
595
+ qstats .fetched_object_cnt , fetched_object_cnt_max ))
583
596
584
597
-- skip all items before pivot (the item pointed by offset)
585
598
local obj = nil
@@ -604,10 +617,12 @@ local function process_tuple(state, tuple, opts)
604
617
-- add the matching object, update count and check limit
605
618
state .objs [# state .objs + 1 ] = obj
606
619
state .count = state .count + 1
620
+ qstats .resulting_object_cnt = qstats .resulting_object_cnt + 1
621
+ assert (qstats .resulting_object_cnt <= resulting_object_cnt_max ,
622
+ (' returning object count[%d] exceeds limit[%d] ' ..
623
+ ' (`resulting_object_cnt_max` in accessor)' ):format (
624
+ qstats .resulting_object_cnt , resulting_object_cnt_max ))
607
625
if limit ~= nil and state .count >= limit then
608
- assert (limit == nil or state .count <= limit ,
609
- (' count[%d] exceeds limit[%s] (in for)' ):format (
610
- state .count , limit ))
611
626
return false
612
627
end
613
628
return true
631
646
--- @tparam table args table of arguments passed within the query except ones
632
647
--- that forms the `filter` parameter
633
648
---
649
+ --- @tparam table extra table which contains extra information related to
650
+ --- current select and the whole query
651
+ ---
634
652
--- @treturn table list of matching objects
635
- local function select_internal (self , collection_name , from , filter , args )
653
+ local function select_internal (self , collection_name , from , filter , args , extra )
636
654
assert (type (self ) == ' table' ,
637
655
' self must be a table, got ' .. type (self ))
638
656
assert (type (collection_name ) == ' string' ,
@@ -694,6 +712,7 @@ local function select_internal(self, collection_name, from, filter, args)
694
712
count = 0 ,
695
713
objs = {},
696
714
pivot_found = false ,
715
+ statistics = extra .qcontext .statistics
697
716
}
698
717
699
718
-- read only process_tuple options
@@ -703,6 +722,8 @@ local function select_internal(self, collection_name, from, filter, args)
703
722
filter = filter ,
704
723
do_filter = not full_match ,
705
724
pivot_filter = nil , -- filled later if needed
725
+ resulting_object_cnt_max = self .settings .resulting_object_cnt_max ,
726
+ fetched_object_cnt_max = self .settings .fetched_object_cnt_max
706
727
}
707
728
708
729
if index == nil then
791
812
---
792
813
--- @tparam table opts `schemas`, `collections`, `service_fields` and `indexes`
793
814
--- to give the data accessor all needed meta-information re data; the format is
794
- --- shown below
815
+ --- shown below; additional attributes `resulting_object_cnt_max` and
816
+ --- `fetched_object_cnt_max` are optional positive numbers which help to control
817
+ --- query behaviour in case it requires more resources than expected _(default
818
+ --- value is 10,000)_
795
819
---
796
820
--- @tparam table funcs set of functions (`is_collection_exists`, `get_index`,
797
821
--- `get_primary_index`) allows this abstract data accessor behaves in the
@@ -837,6 +861,10 @@ function accessor_general.new(opts, funcs)
837
861
local collections = opts .collections
838
862
local service_fields = opts .service_fields
839
863
local indexes = opts .indexes
864
+ local resulting_object_cnt_max = opts .resulting_object_cnt_max or
865
+ DEF_RESULTING_OBJECT_CNT_MAX
866
+ local fetched_object_cnt_max = opts .fetched_object_cnt_max or
867
+ DEF_FETCHED_OBJECT_CNT_MAX
840
868
841
869
assert (type (schemas ) == ' table' ,
842
870
' schemas must be a table, got ' .. type (schemas ))
@@ -846,6 +874,12 @@ function accessor_general.new(opts, funcs)
846
874
' service_fields must be a table, got ' .. type (service_fields ))
847
875
assert (type (indexes ) == ' table' ,
848
876
' indexes must be a table, got ' .. type (indexes ))
877
+ assert (type (resulting_object_cnt_max ) == ' number' and
878
+ resulting_object_cnt_max > 0 ,
879
+ ' resulting_object_cnt_max must be natural number' )
880
+ assert (type (fetched_object_cnt_max ) == ' number' and
881
+ fetched_object_cnt_max > 0 ,
882
+ ' fetched_object_cnt_max must be natural number' )
849
883
850
884
local models = compile_schemas (schemas , service_fields )
851
885
validate_collections (collections , schemas , indexes )
@@ -861,10 +895,14 @@ function accessor_general.new(opts, funcs)
861
895
models = models ,
862
896
index_cache = index_cache ,
863
897
funcs = funcs ,
898
+ settings = {
899
+ resulting_object_cnt_max = resulting_object_cnt_max ,
900
+ fetched_object_cnt_max = fetched_object_cnt_max
901
+ }
864
902
}, {
865
903
__index = {
866
904
select = function (self , parent , collection_name , from ,
867
- filter , args )
905
+ filter , args , extra )
868
906
assert (type (parent ) == ' table' ,
869
907
' parent must be a table, got ' .. type (parent ))
870
908
assert (from == nil or type (from ) == ' table' ,
@@ -877,8 +915,13 @@ function accessor_general.new(opts, funcs)
877
915
' from must be nil or from.connection_name ' ..
878
916
' must be a string, got ' ..
879
917
type ((from or {}).connection_name ))
918
+ -- use `extra.qcontext` to store per-query variables
919
+ extra .qcontext .statistics = extra .qcontext .statistics or {
920
+ resulting_object_cnt = 0 ,
921
+ fetched_object_cnt = 0
922
+ }
880
923
return select_internal (self , collection_name , from , filter ,
881
- args )
924
+ args , extra )
882
925
end ,
883
926
list_args = function (self , collection_name )
884
927
-- get name of field of primary key
0 commit comments