#include "../../src/query.h"#include "../../src/query_parser/tokenizer.h"#include "../../src/stopwords.h"#include "../../src/extension.h"#include "../../src/ext/default.h"#include <stdio.h>#include <gtest/gtest.h>#define QUERY_PARSE_CTX(ctx, qt, opts) NewQueryParseCtx(&ctx, qt, strlen(qt), &opts);struct SearchOptionsCXX : RSSearchOptions {SearchOptionsCXX() {memset(this, 0, sizeof(*this));flags = RS_DEFAULT_QUERY_FLAGS;fieldmask = RS_FIELDMASK_ALL;language = DEFAULT_LANGUAGE;stopwords = DefaultStopWordList();}};class QASTCXX : public QueryAST {SearchOptionsCXX m_opts;QueryError m_status = {QueryErrorCode(0)};RedisSearchCtx *sctx = NULL;public:QASTCXX() {memset(static_cast<QueryAST *>(this), 0, sizeof(QueryAST));}QASTCXX(RedisSearchCtx &sctx) : QASTCXX() {setContext(&sctx);}void setContext(RedisSearchCtx *sctx) {this->sctx = sctx;}bool parse(const char *s) {QueryError_ClearError(&m_status);QAST_Destroy(this);int rc = QAST_Parse(this, sctx, &m_opts, s, strlen(s), &m_status);return rc == REDISMODULE_OK && !QueryError_HasError(&m_status) && root != NULL;}void print() const {QAST_Print(this, sctx->spec);}const char *getError() const {return QueryError_GetError(&m_status);}~QASTCXX() {QueryError_ClearError(&m_status);QAST_Destroy(this);}};bool isValidQuery(const char *qt, RedisSearchCtx &ctx) {QASTCXX ast;ast.setContext(&ctx);return ast.parse(qt);// if (err) {// Query_Free(q);// fprintf(stderr, "Error parsing query '%s': %s\n", qt, err);// free(err);// return 1;// }// Query_Free(q);// return 0;}#define assertValidQuery(qt, ctx) ASSERT_TRUE(isValidQuery(qt, ctx))#define assertInvalidQuery(qt, ctx) ASSERT_FALSE(isValidQuery(qt, ctx))class QueryTest : public ::testing::Test {};TEST_F(QueryTest, testParser) {RedisSearchCtx ctx;static const char *args[] = {"SCHEMA", "title", "text", "weight", "0.1","body", "text", "weight", "2.0", "bar","numeric", "loc", "geo", "tags", "tag"};QueryError err = {QueryErrorCode(0)};ctx.spec = IndexSpec_Parse("idx", args, sizeof(args) / sizeof(const char *), &err);ASSERT_FALSE(QueryError_HasError(&err)) << QueryError_GetError(&err);// test some valid queriesassertValidQuery("hello", ctx);assertValidQuery("hello wor*", ctx);assertValidQuery("hello world", ctx);assertValidQuery("hello (world)", ctx);assertValidQuery("\"hello world\"", ctx);assertValidQuery("\"hello\"", ctx);assertValidQuery("\"hello world\"\"foo bar\"", ctx);assertValidQuery("\"hello world\"|\"foo bar\"", ctx);assertValidQuery("\"hello world\" (\"foo bar\")", ctx);assertValidQuery("hello \"foo bar\" world", ctx);assertValidQuery("hello|hallo|yellow world", ctx);assertValidQuery("(hello|world|foo) bar baz 123", ctx);assertValidQuery("(hello|world|foo) (bar baz)", ctx);// assertValidQuery("(hello world|foo \"bar baz\") \"bar baz\" bbbb");assertValidQuery("@title:(barack obama) @body:us|president", ctx);assertValidQuery("@ti_tle:barack obama @body:us", ctx);assertValidQuery("@title:barack @body:obama", ctx);assertValidQuery("@tit_le|bo_dy:barack @body|title|url|something_else:obama", ctx);assertValidQuery("hello world&good+bye foo.bar", ctx);assertValidQuery("@BusinessName:\"Wells Fargo Bank, National Association\"", ctx);// escaping and unicode in field namesassertValidQuery("@Business\\:\\-\\ Name:Wells Fargo", ctx);assertValidQuery("@שלום:Wells Fargo", ctx);assertValidQuery("foo -bar -(bar baz)", ctx);assertValidQuery("(hello world)|(goodbye moon)", ctx);assertInvalidQuery("@title:", ctx);assertInvalidQuery("@body:@title:", ctx);assertInvalidQuery("@body|title:@title:", ctx);assertInvalidQuery("@body|title", ctx);assertValidQuery("hello ~world ~war", ctx);assertValidQuery("hello ~(world war)", ctx);assertValidQuery("-foo", ctx);assertValidQuery("@title:-foo", ctx);assertValidQuery("-@title:foo", ctx);// some geo queriesassertValidQuery("@loc:[15.1 -15 30 km]", ctx);assertValidQuery("@loc:[15 -15.1 30 m]", ctx);assertValidQuery("@loc:[15.03 -15.45 30 mi]", ctx);assertValidQuery("@loc:[15.65 -15.65 30 ft]", ctx);assertValidQuery("hello world @loc:[15.65 -15.65 30 ft]", ctx);assertValidQuery("hello world -@loc:[15.65 -15.65 30 ft]", ctx);assertValidQuery("hello world ~@loc:[15.65 -15.65 30 ft]", ctx);assertValidQuery("@title:hello world ~@loc:[15.65 -15.65 30 ft]", ctx);assertValidQuery("@loc:[15.65 -15.65 30 ft] @loc:[15.65 -15.65 30 ft]", ctx);assertValidQuery("@loc:[15.65 -15.65 30 ft]|@loc:[15.65 -15.65 30 ft]", ctx);assertValidQuery("hello (world @loc:[15.65 -15.65 30 ft])", ctx);assertInvalidQuery("@loc:[190.65 -100.65 30 ft])", ctx);assertInvalidQuery("@loc:[50 50 -1 ft])", ctx);assertInvalidQuery("@loc:[50 50 1 quoops])", ctx);assertInvalidQuery("@loc:[50 50 1 ftps])", ctx);assertInvalidQuery("@loc:[50 50 1 1])", ctx);assertInvalidQuery("@loc:[50 50 1])", ctx);// numericassertValidQuery("@number:[100 200]", ctx);assertValidQuery("@number:[100 -200]", ctx);assertValidQuery("@number:[(100 (200]", ctx);assertValidQuery("@number:[100 inf]", ctx);assertValidQuery("@number:[100 -inf]", ctx);assertValidQuery("@number:[-inf +inf]", ctx);assertValidQuery("@number:[-inf +inf]|@number:[100 200]", ctx);assertInvalidQuery("@number:[100 foo]", ctx);// Tag queriesassertValidQuery("@tags:{foo}", ctx);assertValidQuery("@tags:{foo|bar baz|boo}", ctx);assertValidQuery("@tags:{foo|bar\\ baz|boo}", ctx);assertValidQuery("@tags:{foo*}", ctx);assertValidQuery("@tags:{foo\\-*}", ctx);assertValidQuery("@tags:{bar | foo*}", ctx);assertValidQuery("@tags:{bar* | foo}", ctx);assertValidQuery("@tags:{bar* | foo*}", ctx);assertInvalidQuery("@tags:{foo|bar\\ baz|}", ctx);assertInvalidQuery("@tags:{foo|bar\\ baz|", ctx);assertInvalidQuery("{foo|bar\\ baz}", ctx);assertInvalidQuery("(foo", ctx);assertInvalidQuery("\"foo", ctx);assertValidQuery("", ctx);assertInvalidQuery("()", ctx);// test stopwordsassertValidQuery("a for is", ctx);assertValidQuery("a|for|is", ctx);assertValidQuery("a little bit of party", ctx);assertValidQuery("no-as", ctx);assertValidQuery("~no~as", ctx);assertValidQuery("(no -as) =>{$weight: 0.5}", ctx);assertValidQuery("@foo:-as", ctx);// test utf-8 queryassertValidQuery("שלום עולם", ctx);// Test attributeassertValidQuery("(foo bar) => {$weight: 0.5; $slop: 2}", ctx);assertValidQuery("foo => {$weight: 0.5} bar => {$weight: 0.1}", ctx);assertValidQuery("@title:(foo bar) => {$weight: 0.5; $slop: 2}", ctx);assertValidQuery("@title:(foo bar) => {$weight: 0.5; $slop: 2} @body:(foo bar) => {$weight: 0.5; $slop: 2}",ctx);assertValidQuery("(foo => {$weight: 0.5;}) | ((bar) => {$weight: 0.5})", ctx);assertValidQuery("(foo => {$weight: 0.5;}) ((bar) => {}) => {}", ctx);assertValidQuery("@tag:{foo | bar} => {$weight: 0.5;} ", ctx);assertValidQuery("@num:[0 100] => {$weight: 0.5;} ", ctx);assertInvalidQuery("@tag:{foo | bar} => {$weight: -0.5;} ", ctx);assertInvalidQuery("@tag:{foo | bar} => {$great: 0.5;} ", ctx);assertInvalidQuery("@tag:{foo | bar} => {$great:;} ", ctx);assertInvalidQuery("@tag:{foo | bar} => {$:1;} ", ctx);assertInvalidQuery(" => {$weight: 0.5;} ", ctx);const char *qt = "(hello|world) and \"another world\" (foo is bar) -(baz boo*)";QASTCXX ast;ast.setContext(&ctx);ASSERT_TRUE(ast.parse(qt));QueryNode *n = ast.root;//QAST_Print(&ast, ctx.spec);ASSERT_TRUE(n != NULL);ASSERT_EQ(n->type, QN_PHRASE);ASSERT_EQ(n->pn.exact, 0);ASSERT_EQ(QueryNode_NumChildren(n), 4);ASSERT_EQ(n->opts.fieldMask, RS_FIELDMASK_ALL);ASSERT_TRUE(n->children[0]->type == QN_UNION);ASSERT_STREQ("hello", n->children[0]->children[0]->tn.str);ASSERT_STREQ("world", n->children[0]->children[1]->tn.str);QueryNode *_n = n->children[1];ASSERT_TRUE(_n->type == QN_PHRASE);ASSERT_TRUE(_n->pn.exact == 1);ASSERT_EQ(QueryNode_NumChildren(_n), 2);ASSERT_STREQ("another", _n->children[0]->tn.str);ASSERT_STREQ("world", _n->children[1]->tn.str);_n = n->children[2];ASSERT_TRUE(_n->type == QN_PHRASE);ASSERT_TRUE(_n->pn.exact == 0);ASSERT_EQ(QueryNode_NumChildren(_n), 2);ASSERT_STREQ("foo", _n->children[0]->tn.str);ASSERT_STREQ("bar", _n->children[1]->tn.str);_n = n->children[3];ASSERT_TRUE(_n->type == QN_NOT);_n = QueryNode_GetChild(_n, 0);ASSERT_TRUE(_n->pn.exact == 0);ASSERT_EQ(2, QueryNode_NumChildren(_n));ASSERT_STREQ("baz", _n->children[0]->tn.str);ASSERT_EQ(_n->children[1]->type, QN_PREFIX);ASSERT_STREQ("boo", _n->children[1]->pfx.str);QAST_Destroy(&ast);IndexSpec_Free(ctx.spec);}TEST_F(QueryTest, testPureNegative) {const char *qs[] = {"-@title:hello", "-hello", "@title:-hello", "-(foo)", "-foo", "(-foo)", NULL};static const char *args[] = {"SCHEMA", "title", "text", "weight", "0.1", "body","text", "weight", "2.0", "bar", "numeric"};QueryError err = {QueryErrorCode(0)};IndexSpec *spec = IndexSpec_Parse("idx", args, sizeof(args) / sizeof(const char *), &err);RedisSearchCtx ctx = SEARCH_CTX_STATIC(NULL, spec);for (size_t i = 0; qs[i] != NULL; i++) {QASTCXX ast;ast.setContext(&ctx);ASSERT_TRUE(ast.parse(qs[i])) << ast.getError();QueryNode *n = ast.root;ASSERT_TRUE(n != NULL);ASSERT_EQ(n->type, QN_NOT);ASSERT_TRUE(QueryNode_GetChild(n, 0) != NULL);}IndexSpec_Free(ctx.spec);}TEST_F(QueryTest, testGeoQuery) {static const char *args[] = {"SCHEMA", "title", "text", "loc", "geo"};QueryError err = {QueryErrorCode(0)};IndexSpec *spec = IndexSpec_Parse("idx", args, sizeof(args) / sizeof(const char *), &err);RedisSearchCtx ctx = SEARCH_CTX_STATIC(NULL, spec);const char *qt = "@title:hello world @loc:[31.52 32.1342 10.01 km]";QASTCXX ast;ast.setContext(&ctx);ASSERT_TRUE(ast.parse(qt)) << ast.getError();QueryNode *n = ast.root;ASSERT_EQ(n->type, QN_PHRASE);ASSERT_TRUE((n->opts.fieldMask == RS_FIELDMASK_ALL));ASSERT_EQ(QueryNode_NumChildren(n), 2);QueryNode *gn = n->children[1];ASSERT_EQ(gn->type, QN_GEO);ASSERT_STREQ(gn->gn.gf->property, "loc");ASSERT_EQ(gn->gn.gf->unitType, GEO_DISTANCE_KM);ASSERT_EQ(gn->gn.gf->lon, 31.52);ASSERT_EQ(gn->gn.gf->lat, 32.1342);ASSERT_EQ(gn->gn.gf->radius, 10.01);IndexSpec_Free(ctx.spec);}TEST_F(QueryTest, testFieldSpec) {static const char *args[] = {"SCHEMA", "title", "text", "weight", "0.1", "body","text", "weight", "2.0", "bar", "numeric"};QueryError err = {QUERY_OK};IndexSpec *spec = IndexSpec_Parse("idx", args, sizeof(args) / sizeof(const char *), &err);RedisSearchCtx ctx = SEARCH_CTX_STATIC(NULL, spec);const char *qt = "@title:hello world";QASTCXX ast(ctx);ASSERT_TRUE(ast.parse(qt)) << ast.getError();//ast.print();QueryNode *n = ast.root;ASSERT_EQ(n->type, QN_PHRASE);ASSERT_EQ(n->opts.fieldMask, 0x01);qt = "(@title:hello) (@body:world)";ASSERT_TRUE(ast.parse(qt)) << ast.getError();n = ast.root;ASSERT_TRUE(n != NULL);//printf("%s ====> ", qt);//ast.print();ASSERT_EQ(n->type, QN_PHRASE);ASSERT_EQ(n->opts.fieldMask, RS_FIELDMASK_ALL);ASSERT_EQ(n->children[0]->opts.fieldMask, 0x01);ASSERT_EQ(n->children[1]->opts.fieldMask, 0x02);// test field modifiersqt = "@title:(hello world) @body:(world apart) @adas_dfsd:fofofof";ASSERT_TRUE(ast.parse(qt)) << ast.getError();n = ast.root;//printf("%s ====> ", qt);//ast.print();ASSERT_EQ(n->type, QN_PHRASE);ASSERT_EQ(n->opts.fieldMask, RS_FIELDMASK_ALL);ASSERT_EQ(QueryNode_NumChildren(n), 3);ASSERT_EQ(n->children[0]->opts.fieldMask, 0x01);ASSERT_EQ(n->children[1]->opts.fieldMask, 0x02);ASSERT_EQ(n->children[2]->opts.fieldMask, 0x00);// ASSERT_EQ(n->children[2]->fieldMask, 0x00)// test numeric rangesqt = "@num:[0.4 (500]";ASSERT_TRUE(ast.parse(qt)) << ast.getError();n = ast.root;ASSERT_EQ(n->type, QN_NUMERIC);ASSERT_EQ(n->nn.nf->min, 0.4);ASSERT_EQ(n->nn.nf->max, 500.0);ASSERT_EQ(n->nn.nf->inclusiveMin, 1);ASSERT_EQ(n->nn.nf->inclusiveMax, 0);IndexSpec_Free(ctx.spec);}TEST_F(QueryTest, testAttributes) {static const char *args[] = {"SCHEMA", "title", "text", "body", "text"};QueryError err = {QUERY_OK};IndexSpec *spec = IndexSpec_Parse("idx", args, sizeof(args) / sizeof(const char *), &err);RedisSearchCtx ctx = SEARCH_CTX_STATIC(NULL, spec);const char *qt ="(@title:(foo bar) => {$weight: 0.5} @body:lol => {$weight: 0.2}) => ""{$weight:0.3; $slop:2; $inorder:true}";QASTCXX ast(ctx);ASSERT_TRUE(ast.parse(qt)) << ast.getError();QueryNode *n = ast.root;ASSERT_EQ(0.3, n->opts.weight);ASSERT_EQ(2, n->opts.maxSlop);ASSERT_EQ(1, n->opts.inOrder);ASSERT_EQ(n->type, QN_PHRASE);ASSERT_EQ(QueryNode_NumChildren(n), 2);ASSERT_EQ(0.5, n->children[0]->opts.weight);ASSERT_EQ(0.2, n->children[1]->opts.weight);IndexSpec_Free(ctx.spec);}TEST_F(QueryTest, testTags) {static const char *args[] = {"SCHEMA", "title", "text", "tags", "tag", "separator", ";"};QueryError err = {QUERY_OK};IndexSpec *spec = IndexSpec_Parse("idx", args, sizeof(args) / sizeof(const char *), &err);RedisSearchCtx ctx = SEARCH_CTX_STATIC(NULL, spec);const char *qt = "@tags:{hello world |foo| שלום| lorem\\ ipsum }";QASTCXX ast(ctx);ASSERT_TRUE(ast.parse(qt)) << ast.getError();//ast.print();QueryNode *n = ast.root;ASSERT_EQ(n->type, QN_TAG);ASSERT_EQ(4, QueryNode_NumChildren(n));ASSERT_EQ(QN_PHRASE, n->children[0]->type);ASSERT_STREQ("hello", n->children[0]->children[0]->tn.str);ASSERT_STREQ("world", n->children[0]->children[1]->tn.str);ASSERT_EQ(QN_TOKEN, n->children[1]->type);ASSERT_STREQ("foo", n->children[1]->tn.str);ASSERT_EQ(QN_TOKEN, n->children[2]->type);ASSERT_STREQ("שלום", n->children[2]->tn.str);ASSERT_EQ(QN_TOKEN, n->children[3]->type);ASSERT_STREQ("lorem ipsum", n->children[3]->tn.str);IndexSpec_Free(ctx.spec);}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。