Kouhei Sutou
null+****@clear*****
Wed Feb 15 11:37:52 JST 2017
Kouhei Sutou 2017-02-15 11:37:52 +0900 (Wed, 15 Feb 2017) New Revision: 997403f73bc6b7db28ecd520370319120e10fca0 https://github.com/groonga/groonga/commit/997403f73bc6b7db28ecd520370319120e10fca0 Message: Support "!" in multiple terms Supported patterns: * !(column @ "X") && (column @ "Y") * (column @ "Y") && !(column @ "X") * (column @ "Y") &! !(column @ "X") Added files: test/command/suite/select/filter/unary_operation/match_and_not_match.expected test/command/suite/select/filter/unary_operation/match_and_not_match.test test/command/suite/select/filter/unary_operation/match_and_not_not_match.expected test/command/suite/select/filter/unary_operation/match_and_not_not_match.test test/command/suite/select/filter/unary_operation/not_match_and_match.expected test/command/suite/select/filter/unary_operation/not_match_and_match.test Modified files: lib/expr.c lib/mrb/scripts/scan_info_builder.rb test/mruby/suite/query_optimizer/index/test_match.rb Modified: lib/expr.c (+56 -16) =================================================================== --- lib/expr.c 2017-02-15 10:51:57 +0900 (2b6a433) +++ lib/expr.c 2017-02-15 11:37:52 +0900 (1248b01) @@ -4550,6 +4550,7 @@ grn_scan_info_build_full(grn_ctx *ctx, grn_obj *expr, int *n, scan_info **sis, *si = NULL; grn_expr_code *c, *ce; grn_expr *e = (grn_expr *)expr; + grn_operator next_code_op; if (!(var = grn_expr_get_var_by_offset(ctx, expr, 0))) { return NULL; } for (stat = SCAN_START, c = e->codes, ce = &e->codes[e->codes_curr]; c < ce; c++) { @@ -4696,8 +4697,17 @@ grn_scan_info_build_full(grn_ctx *ctx, grn_obj *expr, int *n, } if (stat || m != o + 1) { return NULL; } if (!(sis = GRN_MALLOCN(scan_info *, m + m + o + n_nots))) { return NULL; } + + next_code_op = -1; for (i = 0, stat = SCAN_START, c = e->codes, ce = &e->codes[e->codes_curr]; c < ce; c++) { - switch (c->op) { + grn_operator code_op; + if (next_code_op == -1) { + code_op = c->op; + } else { + code_op = next_code_op; + next_code_op = -1; + } + switch (code_op) { case GRN_OP_MATCH : case GRN_OP_NEAR : case GRN_OP_NEAR2 : @@ -4716,7 +4726,7 @@ grn_scan_info_build_full(grn_ctx *ctx, grn_obj *expr, int *n, case GRN_OP_TERM_EXTRACT : case GRN_OP_REGEXP : stat = SCAN_START; - si->op = c->op; + si->op = code_op; si->end = c - e->codes; sis[i++] = si; scan_info_build_match(ctx, si); @@ -4738,7 +4748,7 @@ grn_scan_info_build_full(grn_ctx *ctx, grn_obj *expr, int *n, sis[i++] = si; si = NULL; } - if (!put_logical_op(ctx, sis, &i, c->op, c - e->codes)) { return NULL; } + if (!put_logical_op(ctx, sis, &i, code_op, c - e->codes)) { return NULL; } stat = SCAN_START; break; case GRN_OP_PUSH : @@ -4817,7 +4827,7 @@ grn_scan_info_build_full(grn_ctx *ctx, grn_obj *expr, int *n, if (!si) { SI_ALLOC(si, i, c - e->codes); } if ((c->flags & GRN_EXPR_CODE_RELATIONAL_EXPRESSION) || c + 1 == ce) { stat = SCAN_START; - si->op = c->op; + si->op = code_op; si->end = c - e->codes; sis[i++] = si; /* better index resolving framework for functions should be implemented */ @@ -4916,20 +4926,50 @@ grn_scan_info_build_full(grn_ctx *ctx, grn_obj *expr, int *n, last_si->end++; break; default : - if (i == 1 && GRN_BULK_VSIZE(&(last_si->index)) > 0) { - SI_ALLOC(si, 0, 0); - si->op = GRN_OP_CALL; - si->args[si->nargs++] = grn_ctx_get(ctx, "all_records", -1); - last_si->logical_op = GRN_OP_AND_NOT; - last_si->flags &= ~SCAN_PUSH; - sis[i] = sis[i - 1]; - sis[i - 1] = si; - i++; + if (i == 1) { + if (GRN_BULK_VSIZE(&(last_si->index)) > 0) { + scan_info *all_records_si = NULL; + SI_ALLOC(all_records_si, 0, 0); + all_records_si->op = GRN_OP_CALL; + all_records_si->args[all_records_si->nargs++] = + grn_ctx_get(ctx, "all_records", -1); + last_si->logical_op = GRN_OP_AND_NOT; + last_si->flags &= ~SCAN_PUSH; + sis[i] = sis[i - 1]; + sis[i - 1] = all_records_si; + i++; + } else { + if (last_si->op == GRN_OP_EQUAL) { + last_si->op = GRN_OP_NOT_EQUAL; + last_si->end++; + } else { + int j; + for (j = 0; j < i; j++) { + SI_FREE(sis[j]); + } + GRN_FREE(sis); + return NULL; + } + } } else { - if (last_si->op == GRN_OP_EQUAL) { - last_si->op = GRN_OP_NOT_EQUAL; - last_si->end++; + grn_bool valid = GRN_TRUE; + grn_expr_code *next_code = c + 1; + if (next_code < ce) { + switch (next_code->op) { + case GRN_OP_AND : + next_code_op = GRN_OP_AND_NOT; + break; + case GRN_OP_AND_NOT : + next_code_op = GRN_OP_AND; + break; + default : + valid = GRN_FALSE; + break; + } } else { + valid = GRN_FALSE; + } + if (!valid) { int j; for (j = 0; j < i; j++) { SI_FREE(sis[j]); Modified: lib/mrb/scripts/scan_info_builder.rb (+27 -11) =================================================================== --- lib/mrb/scripts/scan_info_builder.rb 2017-02-15 10:51:57 +0900 (ec4e476) +++ lib/mrb/scripts/scan_info_builder.rb 2017-02-15 11:37:52 +0900 (28dcbe2) @@ -66,11 +66,14 @@ module Groonga data = nil codes =****@expre***** n_codes = codes.size + next_code_op = nil codes.each_with_index do |code, i| - case code.op + code_op = (next_code_op || code.op) + next_code_op = nil + case code_op when *RELATION_OPERATORS status = Status::START - data.op = code.op + data.op = code_op data.end = i data.match_resolve_index @data_list << data @@ -82,7 +85,7 @@ module Groonga @data_list << data data = nil end - put_logical_op(code.op, i) + put_logical_op(code_op, i) # TODO: rescue and return nil status = Status::START when Operator::PUSH @@ -124,7 +127,7 @@ module Groonga if (code.flags & ExpressionCode::Flags::RELATIONAL_EXPRESSION) != 0 or (i + 1) == n_codes status = Status::START - data.op = code.op + data.op = code_op data.end = i data.call_relational_resolve_indexes @data_list << data @@ -164,14 +167,27 @@ module Groonga last_data.op = Operator::EQUAL last_data.end += 1 else - if @data_list.size == 1 and not last_data.search_indexes.empty? - last_data.logical_op = Operator::AND_NOT - last_data.flags &= ~ScanInfo::Flags::PUSH - @data_list.unshift(create_all_match_data) + if @data_list.size == 1 + if last_data.search_indexes.empty? + if last_data.op == Operator::EQUAL + last_data.op = Operator::NOT_EQUAL + last_data.end += 1 + else + return nil + end + else + last_data.logical_op = Operator::AND_NOT + last_data.flags &= ~ScanInfo::Flags::PUSH + @data_list.unshift(create_all_match_data) + end else - if last_data.op == Operator::EQUAL - last_data.op = Operator::NOT_EQUAL - last_data.end += 1 + next_code = codes[i + 1] + return nil if next_code.nil? + case next_code.op + when Operator::AND + next_code_op = Operator::AND_NOT + when Operator::AND_NOT + next_code_op = Operator::AND else return nil end Added: test/command/suite/select/filter/unary_operation/match_and_not_match.expected (+56 -0) 100644 =================================================================== --- /dev/null +++ test/command/suite/select/filter/unary_operation/match_and_not_match.expected 2017-02-15 11:37:52 +0900 (4525656) @@ -0,0 +1,56 @@ +table_create Texts TABLE_PAT_KEY ShortText +[[0,0.0,0.0],true] +table_create Terms TABLE_PAT_KEY ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto +[[0,0.0,0.0],true] +column_create Terms texts_key COLUMN_INDEX|WITH_POSITION Texts _key +[[0,0.0,0.0],true] +load --table Texts +[ +{"_key": "Groonga and Rroonga"}, +{"_key": "Rroonga and Ruby"}, +{"_key": "Mroonga and Groonga"} +] +[[0,0.0,0.0],3] +log_level --level info +[[0,0.0,0.0],true] +select Texts --filter '(_key @ "Rroonga") && !(_key @ "Groonga")' +[ + [ + 0, + 0.0, + 0.0 + ], + [ + [ + [ + 1 + ], + [ + [ + "_id", + "UInt32" + ], + [ + "_key", + "ShortText" + ] + ], + [ + 2, + "Rroonga and Ruby" + ] + ] + ] +] +#|i| [object][search][index][key][exact] <Terms.texts_key> +#|i| grn_ii_sel > (Rroonga) +#|i| n=1 (Rroonga) +#|i| exact: 2 +#|i| hits=2 +#|i| [object][search][index][key][exact] <Terms.texts_key> +#|i| grn_ii_sel > (Groonga) +#|i| n=1 (Groonga) +#|i| exact: 1 +#|i| hits=1 +log_level --level notice +[[0,0.0,0.0],true] Added: test/command/suite/select/filter/unary_operation/match_and_not_match.test (+20 -0) 100644 =================================================================== --- /dev/null +++ test/command/suite/select/filter/unary_operation/match_and_not_match.test 2017-02-15 11:37:52 +0900 (24e2653) @@ -0,0 +1,20 @@ +table_create Texts TABLE_PAT_KEY ShortText + +table_create Terms TABLE_PAT_KEY ShortText \ + --default_tokenizer TokenBigram \ + --normalizer NormalizerAuto +column_create Terms texts_key COLUMN_INDEX|WITH_POSITION \ + Texts _key + +load --table Texts +[ +{"_key": "Groonga and Rroonga"}, +{"_key": "Rroonga and Ruby"}, +{"_key": "Mroonga and Groonga"} +] + +log_level --level info +#@add-important-log-levels info +select Texts --filter '(_key @ "Rroonga") && !(_key @ "Groonga")' +#@remove-important-log-levels info +log_level --level notice Added: test/command/suite/select/filter/unary_operation/match_and_not_not_match.expected (+56 -0) 100644 =================================================================== --- /dev/null +++ test/command/suite/select/filter/unary_operation/match_and_not_not_match.expected 2017-02-15 11:37:52 +0900 (2622fa1) @@ -0,0 +1,56 @@ +table_create Texts TABLE_PAT_KEY ShortText +[[0,0.0,0.0],true] +table_create Terms TABLE_PAT_KEY ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto +[[0,0.0,0.0],true] +column_create Terms texts_key COLUMN_INDEX|WITH_POSITION Texts _key +[[0,0.0,0.0],true] +load --table Texts +[ +{"_key": "Groonga and Rroonga"}, +{"_key": "Rroonga and Ruby"}, +{"_key": "Mroonga and Groonga"} +] +[[0,0.0,0.0],3] +log_level --level info +[[0,0.0,0.0],true] +select Texts --filter '(_key @ "Rroonga") &! !(_key @ "Groonga")' +[ + [ + 0, + 0.0, + 0.0 + ], + [ + [ + [ + 1 + ], + [ + [ + "_id", + "UInt32" + ], + [ + "_key", + "ShortText" + ] + ], + [ + 1, + "Groonga and Rroonga" + ] + ] + ] +] +#|i| [object][search][index][key][exact] <Terms.texts_key> +#|i| grn_ii_sel > (Rroonga) +#|i| n=1 (Rroonga) +#|i| exact: 2 +#|i| hits=2 +#|i| [object][search][index][key][exact] <Terms.texts_key> +#|i| grn_ii_sel > (Groonga) +#|i| n=1 (Groonga) +#|i| exact: 1 +#|i| hits=1 +log_level --level notice +[[0,0.0,0.0],true] Added: test/command/suite/select/filter/unary_operation/match_and_not_not_match.test (+20 -0) 100644 =================================================================== --- /dev/null +++ test/command/suite/select/filter/unary_operation/match_and_not_not_match.test 2017-02-15 11:37:52 +0900 (814a3ab) @@ -0,0 +1,20 @@ +table_create Texts TABLE_PAT_KEY ShortText + +table_create Terms TABLE_PAT_KEY ShortText \ + --default_tokenizer TokenBigram \ + --normalizer NormalizerAuto +column_create Terms texts_key COLUMN_INDEX|WITH_POSITION \ + Texts _key + +load --table Texts +[ +{"_key": "Groonga and Rroonga"}, +{"_key": "Rroonga and Ruby"}, +{"_key": "Mroonga and Groonga"} +] + +log_level --level info +#@add-important-log-levels info +select Texts --filter '(_key @ "Rroonga") &! !(_key @ "Groonga")' +#@remove-important-log-levels info +log_level --level notice Added: test/command/suite/select/filter/unary_operation/not_match_and_match.expected (+57 -0) 100644 =================================================================== --- /dev/null +++ test/command/suite/select/filter/unary_operation/not_match_and_match.expected 2017-02-15 11:37:52 +0900 (476899c) @@ -0,0 +1,57 @@ +table_create Texts TABLE_PAT_KEY ShortText +[[0,0.0,0.0],true] +table_create Terms TABLE_PAT_KEY ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto +[[0,0.0,0.0],true] +column_create Terms texts_key COLUMN_INDEX|WITH_POSITION Texts _key +[[0,0.0,0.0],true] +load --table Texts +[ +{"_key": "Groonga and Rroonga"}, +{"_key": "Rroonga and Ruby"}, +{"_key": "Mroonga and Groonga"} +] +[[0,0.0,0.0],3] +log_level --level info +[[0,0.0,0.0],true] +select Texts --filter '!(_key @ "Groonga") && (_key @ "Rroonga")' +[ + [ + 0, + 0.0, + 0.0 + ], + [ + [ + [ + 1 + ], + [ + [ + "_id", + "UInt32" + ], + [ + "_key", + "ShortText" + ] + ], + [ + 2, + "Rroonga and Ruby" + ] + ] + ] +] +#|i| [table][select][index][selector][no-index][all_records] <Texts> +#|i| [object][search][index][key][exact] <Terms.texts_key> +#|i| grn_ii_sel > (Groonga) +#|i| n=1 (Groonga) +#|i| exact: 1 +#|i| hits=1 +#|i| [object][search][index][key][exact] <Terms.texts_key> +#|i| grn_ii_sel > (Rroonga) +#|i| n=1 (Rroonga) +#|i| exact: 1 +#|i| hits=1 +log_level --level notice +[[0,0.0,0.0],true] Added: test/command/suite/select/filter/unary_operation/not_match_and_match.test (+20 -0) 100644 =================================================================== --- /dev/null +++ test/command/suite/select/filter/unary_operation/not_match_and_match.test 2017-02-15 11:37:52 +0900 (2c7dddf) @@ -0,0 +1,20 @@ +table_create Texts TABLE_PAT_KEY ShortText + +table_create Terms TABLE_PAT_KEY ShortText \ + --default_tokenizer TokenBigram \ + --normalizer NormalizerAuto +column_create Terms texts_key COLUMN_INDEX|WITH_POSITION \ + Texts _key + +load --table Texts +[ +{"_key": "Groonga and Rroonga"}, +{"_key": "Rroonga and Ruby"}, +{"_key": "Mroonga and Groonga"} +] + +log_level --level info +#@add-important-log-levels info +select Texts --filter '!(_key @ "Groonga") && (_key @ "Rroonga")' +#@remove-important-log-levels info +log_level --level notice Modified: test/mruby/suite/query_optimizer/index/test_match.rb (+56 -0) =================================================================== --- test/mruby/suite/query_optimizer/index/test_match.rb 2017-02-15 10:51:57 +0900 (a89a4d0) +++ test/mruby/suite/query_optimizer/index/test_match.rb 2017-02-15 11:37:52 +0900 (102b58c) @@ -64,4 +64,60 @@ class TestIndexMatch < QueryOptimizerTestCase expr: <0..2> DUMP end + + def test_not_and_normal + assert_equal(<<-DUMP, dump_plan("!(message @ 'Groonga') && (message @ 'Rroonga')")) +[0] + op: <call> + logical_op: <or> + args[0]: <#<proc:function all_records arguments:[]>> + expr: <0..0> +[1] + op: <match> + logical_op: <and_not> + index: <[#<column:index Terms.Logs_message range:Logs sources:[Logs.message] flags:POSITION>]> + query: <"Groonga"> + expr: <0..2> +[2] + op: <match> + logical_op: <and> + index: <[#<column:index Terms.Logs_message range:Logs sources:[Logs.message] flags:POSITION>]> + query: <"Rroonga"> + expr: <4..6> + DUMP + end + + def test_normal_and_not + assert_equal(<<-DUMP, dump_plan("(message @ 'Rroonga') && !(message @ 'Groonga')")) +[0] + op: <match> + logical_op: <or> + index: <[#<column:index Terms.Logs_message range:Logs sources:[Logs.message] flags:POSITION>]> + query: <"Rroonga"> + expr: <0..2> +[1] + op: <match> + logical_op: <and_not> + index: <[#<column:index Terms.Logs_message range:Logs sources:[Logs.message] flags:POSITION>]> + query: <"Groonga"> + expr: <3..5> + DUMP + end + + def test_normal_and_not_not + assert_equal(<<-DUMP, dump_plan("(message @ 'Rroonga') &! !(message @ 'Groonga')")) +[0] + op: <match> + logical_op: <or> + index: <[#<column:index Terms.Logs_message range:Logs sources:[Logs.message] flags:POSITION>]> + query: <"Rroonga"> + expr: <0..2> +[1] + op: <match> + logical_op: <and> + index: <[#<column:index Terms.Logs_message range:Logs sources:[Logs.message] flags:POSITION>]> + query: <"Groonga"> + expr: <3..5> + DUMP + end end -------------- next part -------------- HTML����������������������������...Download