5 #include <boost/algorithm/string.hpp>
7 #include "common/ceph_json.h"
8 #include "rgw_common.h"
9 #include "rgw_es_query.h"
13 #define dout_context g_ceph_context
14 #define dout_subsys ceph_subsys_rgw
16 bool pop_front(list
<string
>& l
, string
*s
)
26 map
<string
, int> operator_map
= {
36 bool is_operator(const string
& s
)
38 return (operator_map
.find(s
) != operator_map
.end());
41 int operand_value(const string
& op
)
43 auto i
= operator_map
.find(op
);
44 if (i
== operator_map
.end()) {
51 int check_precedence(const string
& op1
, const string
& op2
)
53 return operand_value(op1
) - operand_value(op2
);
56 static bool infix_to_prefix(list
<string
>& source
, list
<string
> *out
)
58 list
<string
> operator_stack
;
59 list
<string
> operand_stack
;
61 operator_stack
.push_front("(");
62 source
.push_back(")");
64 for (string
& entity
: source
) {
66 operator_stack
.push_front(entity
);
67 } else if (entity
== ")") {
68 string popped_operator
;
69 if (!pop_front(operator_stack
, &popped_operator
)) {
73 while (popped_operator
!= "(") {
74 operand_stack
.push_front(popped_operator
);
75 if (!pop_front(operator_stack
, &popped_operator
)) {
80 } else if (is_operator(entity
)) {
81 string popped_operator
;
82 if (!pop_front(operator_stack
, &popped_operator
)) {
86 int precedence
= check_precedence(popped_operator
, entity
);
88 while (precedence
>= 0) {
89 operand_stack
.push_front(popped_operator
);
90 if (!pop_front(operator_stack
, &popped_operator
)) {
93 precedence
= check_precedence(popped_operator
, entity
);
96 operator_stack
.push_front(popped_operator
);
97 operator_stack
.push_front(entity
);
99 operand_stack
.push_front(entity
);
104 if (!operator_stack
.empty()) {
108 out
->swap(operand_stack
);
114 ESQueryCompiler
*compiler
;
116 ESQueryNode(ESQueryCompiler
*_compiler
) : compiler(_compiler
) {}
117 virtual ~ESQueryNode() {}
119 virtual bool init(ESQueryStack
*s
, ESQueryNode
**pnode
, string
*perr
) = 0;
121 virtual void dump(Formatter
*f
) const = 0;
124 static bool alloc_node(ESQueryCompiler
*compiler
, ESQueryStack
*s
, ESQueryNode
**pnode
, string
*perr
);
126 class ESQueryNode_Bool
: public ESQueryNode
{
128 ESQueryNode
*first
{nullptr};
129 ESQueryNode
*second
{nullptr};
131 ESQueryNode_Bool(ESQueryCompiler
*compiler
) : ESQueryNode(compiler
) {}
132 ESQueryNode_Bool(ESQueryCompiler
*compiler
, const string
& _op
, ESQueryNode
*_first
, ESQueryNode
*_second
) :ESQueryNode(compiler
), op(_op
), first(_first
), second(_second
) {}
133 bool init(ESQueryStack
*s
, ESQueryNode
**pnode
, string
*perr
) override
{
134 bool valid
= s
->pop(&op
);
136 *perr
= "incorrect expression";
139 valid
= alloc_node(compiler
, s
, &first
, perr
) &&
140 alloc_node(compiler
, s
, &second
, perr
);
147 virtual ~ESQueryNode_Bool() {
152 void dump(Formatter
*f
) const {
153 f
->open_object_section("bool");
154 const char *section
= (op
== "and" ? "must" : "should");
155 f
->open_array_section(section
);
156 encode_json("entry", *first
, f
);
157 encode_json("entry", *second
, f
);
164 class ESQueryNodeLeafVal
{
166 ESQueryNodeLeafVal() = default;
167 virtual ~ESQueryNodeLeafVal() {}
169 virtual bool init(const string
& str_val
, string
*perr
) = 0;
170 virtual void encode_json(const string
& field
, Formatter
*f
) const = 0;
173 class ESQueryNodeLeafVal_Str
: public ESQueryNodeLeafVal
{
176 ESQueryNodeLeafVal_Str() {}
177 bool init(const string
& str_val
, string
*perr
) override
{
181 void encode_json(const string
& field
, Formatter
*f
) const {
182 ::encode_json(field
.c_str(), val
.c_str(), f
);
186 class ESQueryNodeLeafVal_Int
: public ESQueryNodeLeafVal
{
189 ESQueryNodeLeafVal_Int() {}
190 bool init(const string
& str_val
, string
*perr
) override
{
192 val
= strict_strtoll(str_val
.c_str(), 10, &err
);
194 *perr
= string("failed to parse integer: ") + err
;
199 void encode_json(const string
& field
, Formatter
*f
) const {
200 ::encode_json(field
.c_str(), val
, f
);
204 class ESQueryNodeLeafVal_Date
: public ESQueryNodeLeafVal
{
207 ESQueryNodeLeafVal_Date() {}
208 bool init(const string
& str_val
, string
*perr
) override
{
209 if (parse_time(str_val
.c_str(), &val
) < 0) {
210 *perr
= string("failed to parse date: ") + str_val
;
215 void encode_json(const string
& field
, Formatter
*f
) const {
217 rgw_to_iso8601(val
, &s
);
218 ::encode_json(field
.c_str(), s
, f
);
222 class ESQueryNode_Op
: public ESQueryNode
{
227 ESQueryNodeLeafVal
*val
{nullptr};
228 ESEntityTypeMap::EntityType entity_type
{ESEntityTypeMap::ES_ENTITY_NONE
};
229 bool allow_restricted
{false};
231 bool val_from_str(string
*perr
) {
232 switch (entity_type
) {
233 case ESEntityTypeMap::ES_ENTITY_DATE
:
234 val
= new ESQueryNodeLeafVal_Date
;
236 case ESEntityTypeMap::ES_ENTITY_INT
:
237 val
= new ESQueryNodeLeafVal_Int
;
240 val
= new ESQueryNodeLeafVal_Str
;
242 return val
->init(str_val
, perr
);
244 bool do_init(ESQueryNode
**pnode
, string
*perr
) {
245 field
= compiler
->unalias_field(field
);
246 ESQueryNode
*effective_node
;
247 if (!handle_nested(&effective_node
, perr
)) {
250 if (!val_from_str(perr
)) {
253 *pnode
= effective_node
;
258 ESQueryNode_Op(ESQueryCompiler
*compiler
) : ESQueryNode(compiler
) {}
262 virtual bool init(ESQueryStack
*s
, ESQueryNode
**pnode
, string
*perr
) override
{
263 bool valid
= s
->pop(&op
) &&
267 *perr
= "invalid expression";
270 return do_init(pnode
, perr
);
272 bool handle_nested(ESQueryNode
**pnode
, string
*perr
);
274 void set_allow_restricted(bool allow
) {
275 allow_restricted
= allow
;
278 virtual void dump(Formatter
*f
) const = 0;
281 class ESQueryNode_Op_Equal
: public ESQueryNode_Op
{
283 ESQueryNode_Op_Equal(ESQueryCompiler
*compiler
) : ESQueryNode_Op(compiler
) {}
284 ESQueryNode_Op_Equal(ESQueryCompiler
*compiler
, const string
& f
, const string
& v
) : ESQueryNode_Op(compiler
) {
290 bool init(ESQueryStack
*s
, ESQueryNode
**pnode
, string
*perr
) override
{
292 return ESQueryNode_Op::init(s
, pnode
, perr
);
294 return do_init(pnode
, perr
);
297 virtual void dump(Formatter
*f
) const {
298 f
->open_object_section("term");
299 val
->encode_json(field
, f
);
304 class ESQueryNode_Op_Range
: public ESQueryNode_Op
{
307 ESQueryNode_Op_Range(ESQueryCompiler
*compiler
, const string
& rs
) : ESQueryNode_Op(compiler
), range_str(rs
) {}
309 virtual void dump(Formatter
*f
) const {
310 f
->open_object_section("range");
311 f
->open_object_section(field
.c_str());
312 val
->encode_json(range_str
, f
);
318 class ESQueryNode_Op_Nested_Parent
: public ESQueryNode_Op
{
320 ESQueryNode_Op_Nested_Parent(ESQueryCompiler
*compiler
) : ESQueryNode_Op(compiler
) {}
322 virtual string
get_custom_leaf_field_name() = 0;
326 class ESQueryNode_Op_Nested
: public ESQueryNode_Op_Nested_Parent
{
330 ESQueryNode_Op_Nested(ESQueryCompiler
*compiler
, const string
& _name
, ESQueryNode
*_next
) : ESQueryNode_Op_Nested_Parent(compiler
),
331 name(_name
), next(_next
) {}
332 ~ESQueryNode_Op_Nested() {
336 virtual void dump(Formatter
*f
) const {
337 f
->open_object_section("nested");
338 string s
= string("meta.custom-") + type_str();
339 encode_json("path", s
.c_str(), f
);
340 f
->open_object_section("query");
341 f
->open_object_section("bool");
342 f
->open_array_section("must");
343 f
->open_object_section("entry");
344 f
->open_object_section("match");
345 string n
= s
+ ".name";
346 encode_json(n
.c_str(), name
.c_str(), f
);
349 encode_json("entry", *next
, f
);
356 string
type_str() const;
357 string
get_custom_leaf_field_name() {
358 return string("meta.custom-") + type_str() + ".value";
363 string ESQueryNode_Op_Nested
<string
>::type_str() const {
368 string ESQueryNode_Op_Nested
<int64_t>::type_str() const {
373 string ESQueryNode_Op_Nested
<ceph::real_time
>::type_str() const {
377 bool ESQueryNode_Op::handle_nested(ESQueryNode
**pnode
, string
*perr
)
379 string field_name
= field
;
380 const string
& custom_prefix
= compiler
->get_custom_prefix();
381 if (!boost::algorithm::starts_with(field_name
, custom_prefix
)) {
383 auto m
= compiler
->get_generic_type_map();
385 bool found
= m
->find(field_name
, &entity_type
) &&
386 (allow_restricted
|| !compiler
->is_restricted(field_name
));
388 *perr
= string("unexpected generic field '") + field_name
+ "'";
392 *perr
= "query parser does not support generic types";
396 field_name
= field_name
.substr(custom_prefix
.size());
397 auto m
= compiler
->get_custom_type_map();
399 m
->find(field_name
, &entity_type
);
400 /* ignoring returned bool, for now just treat it as string */
403 ESQueryNode_Op_Nested_Parent
*new_node
;
404 switch (entity_type
) {
405 case ESEntityTypeMap::ES_ENTITY_INT
:
406 new_node
= new ESQueryNode_Op_Nested
<int64_t>(compiler
, field_name
, this);
408 case ESEntityTypeMap::ES_ENTITY_DATE
:
409 new_node
= new ESQueryNode_Op_Nested
<ceph::real_time
>(compiler
, field_name
, this);
412 new_node
= new ESQueryNode_Op_Nested
<string
>(compiler
, field_name
, this);
415 field
= new_node
->get_custom_leaf_field_name();
421 static bool is_bool_op(const string
& str
)
423 return (str
== "or" || str
== "and");
426 static bool alloc_node(ESQueryCompiler
*compiler
, ESQueryStack
*s
, ESQueryNode
**pnode
, string
*perr
)
429 bool valid
= s
->peek(&op
);
431 *perr
= "incorrect expression";
437 if (is_bool_op(op
)) {
438 node
= new ESQueryNode_Bool(compiler
);
439 } else if (op
== "==") {
440 node
= new ESQueryNode_Op_Equal(compiler
);
442 static map
<string
, string
> range_op_map
= {
449 auto iter
= range_op_map
.find(op
);
450 if (iter
== range_op_map
.end()) {
451 *perr
= string("invalid operator: ") + op
;
455 node
= new ESQueryNode_Op_Range(compiler
, iter
->second
);
458 if (!node
->init(s
, pnode
, perr
)) {
466 bool is_key_char(char c
)
490 return (isascii(c
) > 0);
493 static bool is_op_char(char c
)
504 static bool is_val_char(char c
)
512 void ESInfixQueryParser::skip_whitespace(const char *str
, int size
, int& pos
) {
513 while (pos
< size
&& isspace(str
[pos
])) {
518 bool ESInfixQueryParser::get_next_token(bool (*filter
)(char)) {
519 skip_whitespace(str
, size
, pos
);
520 int token_start
= pos
;
521 while (pos
< size
&& filter(str
[pos
])) {
524 if (pos
== token_start
) {
527 string token
= string(str
+ token_start
, pos
- token_start
);
528 args
.push_back(token
);
532 bool ESInfixQueryParser::parse_condition() {
534 * condition: <key> <operator> <val>
536 * whereas key: needs to conform to http header field restrictions
537 * operator: one of the following: < <= == >= >
538 * val: ascii, terminated by either space or ')' (or end of string)
542 bool valid
= get_next_token(is_key_char
) &&
543 get_next_token(is_op_char
) &&
544 get_next_token(is_val_char
);
553 bool ESInfixQueryParser::parse_and_or() {
554 skip_whitespace(str
, size
, pos
);
555 if (pos
+ 3 <= size
&& strncmp(str
+ pos
, "and", 3) == 0) {
557 args
.push_back("and");
561 if (pos
+ 2 <= size
&& strncmp(str
+ pos
, "or", 2) == 0) {
563 args
.push_back("or");
570 bool ESInfixQueryParser::parse_specific_char(const char *pchar
) {
571 skip_whitespace(str
, size
, pos
);
575 if (str
[pos
] != *pchar
) {
579 args
.push_back(pchar
);
584 bool ESInfixQueryParser::parse_open_bracket() {
585 return parse_specific_char("(");
588 bool ESInfixQueryParser::parse_close_bracket() {
589 return parse_specific_char(")");
592 bool ESInfixQueryParser::parse(list
<string
> *result
) {
594 * expression: [(]<condition>[[and/or]<condition>][)][and/or]...
598 parse_open_bracket();
599 if (!parse_condition()) {
602 parse_close_bracket();
611 bool ESQueryCompiler::convert(list
<string
>& infix
, string
*perr
) {
613 if (!infix_to_prefix(infix
, &prefix
)) {
614 *perr
= "invalid query";
617 stack
.assign(prefix
);
618 if (!alloc_node(this, &stack
, &query_root
, perr
)) {
622 *perr
= "invalid query";
628 ESQueryCompiler::~ESQueryCompiler() {
632 bool ESQueryCompiler::compile(string
*perr
) {
634 if (!parser
.parse(&infix
)) {
635 *perr
= "failed to parse query";
639 if (!convert(infix
, perr
)) {
643 for (auto& c
: eq_conds
) {
644 ESQueryNode_Op_Equal
*eq_node
= new ESQueryNode_Op_Equal(this, c
.first
, c
.second
);
645 eq_node
->set_allow_restricted(true); /* can access restricted fields */
646 ESQueryNode
*effective_node
;
647 if (!eq_node
->init(nullptr, &effective_node
, perr
)) {
651 query_root
= new ESQueryNode_Bool(this, "and", effective_node
, query_root
);
657 void ESQueryCompiler::dump(Formatter
*f
) const {
658 encode_json("query", *query_root
, f
);