@@ -8,6 +8,12 @@ local json = require('json')
8
8
local avro_schema = require (' avro_schema' )
9
9
local utils = require (' graphql.utils' )
10
10
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
11
17
12
18
local accessor_general = {}
13
19
local DEF_RESULTING_OBJECT_CNT_MAX = 10000
@@ -696,6 +702,36 @@ local function validate_collections(collections, schemas)
696
702
end
697
703
end
698
704
705
+ --- Whether an object match set of PCRE.
706
+ ---
707
+ --- @tparam table obj an object to check
708
+ ---
709
+ --- @tparam table pcre map with PCRE as values; names are correspond to field
710
+ --- names of the `obj` to match
711
+ ---
712
+ --- @treturn boolean `res` whether the `obj` object match `pcre` set of
713
+ --- regexps.
714
+ local function match_using_re (obj , pcre )
715
+ if pcre == nil then return true end
716
+
717
+ for field_name , re in pairs (pcre ) do
718
+ -- skip an object with null in a string* field
719
+ if obj [field_name ] == nil then
720
+ return false
721
+ end
722
+ assert (rex ~= nil , ' we should not pass over :compile() ' ..
723
+ ' with a query contains PCRE matching when there are ' ..
724
+ ' no lrexlib-pcre (rex_pcre) module present' )
725
+ -- XXX: compile re once
726
+ local re = rex .new (re )
727
+ if not re :match (obj [field_name ]) then
728
+ return false
729
+ end
730
+ end
731
+
732
+ return true
733
+ end
734
+
699
735
--- Perform unflatten, skipping, filtering, limiting of objects. This is the
700
736
--- core of the `select_internal` function.
701
737
---
@@ -741,9 +777,11 @@ local function process_tuple(state, tuple, opts)
741
777
qstats .fetched_object_cnt , fetched_object_cnt_max ))
742
778
assert (qcontext .deadline_clock > clock .monotonic64 (),
743
779
' query execution timeout exceeded, use `timeout_ms` to increase it' )
780
+ local collection_name = opts .collection_name
781
+ local pcre = opts .pcre
744
782
745
783
-- convert tuple -> object
746
- local obj = opts .unflatten_tuple (opts . collection_name , tuple ,
784
+ local obj = opts .unflatten_tuple (collection_name , tuple ,
747
785
opts .default_unflatten_tuple )
748
786
749
787
-- skip all items before pivot (the item pointed by offset)
@@ -755,7 +793,8 @@ local function process_tuple(state, tuple, opts)
755
793
end
756
794
757
795
-- filter out non-matching objects
758
- local match = utils .is_subtable (obj , filter )
796
+ local match = utils .is_subtable (obj , filter ) and
797
+ match_using_re (obj , pcre )
759
798
if do_filter then
760
799
if not match then return true end
761
800
else
@@ -828,6 +867,8 @@ local function select_internal(self, collection_name, from, filter, args, extra)
828
867
-- XXX: save type at parsing and check here
829
868
-- assert(args.offset == nil or type(args.offset) == 'number',
830
869
-- 'args.offset must be a number of nil, got ' .. type(args.offset))
870
+ assert (args .pcre == nil or type (args .pcre ) == ' table' ,
871
+ ' args.pcre must be nil or a table, got ' .. type (args .pcre ))
831
872
832
873
local collection = self .collections [collection_name ]
833
874
assert (collection ~= nil ,
@@ -876,6 +917,7 @@ local function select_internal(self, collection_name, from, filter, args, extra)
876
917
collection_name = collection_name ,
877
918
unflatten_tuple = self .funcs .unflatten_tuple ,
878
919
default_unflatten_tuple = default_unflatten_tuple ,
920
+ pcre = args .pcre ,
879
921
}
880
922
881
923
if index == nil then
@@ -976,6 +1018,107 @@ local function init_qcontext(accessor, qcontext)
976
1018
settings .timeout_ms * 1000 * 1000
977
1019
end
978
1020
1021
+ --- Get an avro-schema for a primary key by a collection name.
1022
+ ---
1023
+ --- @tparam table self accessor_general instance
1024
+ ---
1025
+ --- @tparam string collection_name name of a collection
1026
+ ---
1027
+ --- @treturn string `offset_type` is a just string in case of scalar primary
1028
+ --- key (and, then, offset) type
1029
+ ---
1030
+ --- @treturn table `offset_type` is a record in case of compound (multi-part)
1031
+ --- primary key
1032
+ local function get_primary_key_type (self , collection_name )
1033
+ -- get name of field of primary key
1034
+ local _ , index_meta = get_primary_index_meta (
1035
+ self , collection_name )
1036
+
1037
+ local collection = self .collections [collection_name ]
1038
+ local schema = self .schemas [collection .schema_name ]
1039
+
1040
+ local offset_fields = {}
1041
+
1042
+ for _ , field_name in ipairs (index_meta .fields ) do
1043
+ local field_type
1044
+ for _ , field in ipairs (schema .fields ) do
1045
+ if field .name == field_name then
1046
+ field_type = field .type
1047
+ end
1048
+ end
1049
+ assert (field_type ~= nil ,
1050
+ (' cannot find type for primary index field "%s" ' ..
1051
+ ' for collection "%s"' ):format (field_name ,
1052
+ collection_name ))
1053
+ assert (type (field_type ) == ' string' ,
1054
+ ' field type must be a string, got ' ..
1055
+ type (field_type ))
1056
+ offset_fields [# offset_fields + 1 ] = {
1057
+ name = field_name ,
1058
+ type = field_type ,
1059
+ }
1060
+ end
1061
+
1062
+ local offset_type
1063
+ assert (# offset_fields > 0 ,
1064
+ ' offset must contain at least one field' )
1065
+ if # offset_fields == 1 then
1066
+ -- use a scalar type
1067
+ offset_type = offset_fields [1 ].type
1068
+ else
1069
+ -- construct an input type
1070
+ offset_type = {
1071
+ name = collection_name .. ' _offset' ,
1072
+ type = ' record' ,
1073
+ fields = offset_fields ,
1074
+ }
1075
+ end
1076
+
1077
+ return offset_type
1078
+ end
1079
+
1080
+ -- XXX: add string fields of a nested record / 1:1 connection to
1081
+ -- get_pcre_argument_type
1082
+
1083
+ --- Get an avro-schema for a pcre argument by a collection name.
1084
+ ---
1085
+ --- Note: it is called from `list_args`, so applicable only for lists:
1086
+ --- top-level objects and 1:N connections.
1087
+ ---
1088
+ --- @tparam table self accessor_general instance
1089
+ ---
1090
+ --- @tparam string collection_name name of a collection
1091
+ ---
1092
+ --- @treturn table `pcre_type` is a record with fields per string/string* field
1093
+ --- of an object of the collection
1094
+ local function get_pcre_argument_type (self , collection_name )
1095
+ local collection = self .collections [collection_name ]
1096
+ assert (collection ~= nil , ' cannot found collection ' ..
1097
+ tostring (collection_name ))
1098
+ local schema = self .schemas [collection .schema_name ]
1099
+ assert (schema ~= nil , ' cannot found schema ' ..
1100
+ tostring (collection .schema_name ))
1101
+
1102
+ assert (schema .type == ' record' ,
1103
+ ' top-level object expected to be a record, got ' ..
1104
+ tostring (schema .type ))
1105
+
1106
+ local string_fields = {}
1107
+
1108
+ for _ , field in ipairs (schema .fields ) do
1109
+ if field .type == ' string' or field .type == ' string*' then
1110
+ string_fields [# string_fields + 1 ] = table .copy (field )
1111
+ end
1112
+ end
1113
+
1114
+ local pcre_type = {
1115
+ name = collection_name .. ' _pcre' ,
1116
+ type = ' record' ,
1117
+ fields = string_fields ,
1118
+ }
1119
+ return pcre_type
1120
+ end
1121
+
979
1122
--- Create a new data accessor.
980
1123
---
981
1124
--- Provided `funcs` argument determines certain functions for retrieving
@@ -1114,53 +1257,20 @@ function accessor_general.new(opts, funcs)
1114
1257
args , extra )
1115
1258
end ,
1116
1259
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
1260
+ local offset_type = get_primary_key_type (self , collection_name )
1144
1261
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
- }
1262
+ -- add `pcre` argument only if lrexlib-pcre was found
1263
+ local pcre_field
1264
+ if rex ~= nil then
1265
+ local pcre_type = get_pcre_argument_type (self , collection_name )
1266
+ pcre_field = {name = ' pcre' , type = pcre_type }
1158
1267
end
1159
1268
1160
1269
return {
1161
1270
{name = ' limit' , type = ' int' },
1162
1271
{name = ' offset' , type = offset_type },
1163
1272
-- {name = 'filter', type = ...},
1273
+ pcre_field ,
1164
1274
}
1165
1275
end ,
1166
1276
}
0 commit comments