Kouhei Sutou
null+****@clear*****
Mon Aug 13 13:55:03 JST 2012
Kouhei Sutou 2012-08-13 13:55:03 +0900 (Mon, 13 Aug 2012) New Revision: d6e4f1177d33e3fb035fb4ae9551b107ae15ec87 https://github.com/groonga/gcs/commit/d6e4f1177d33e3fb035fb4ae9551b107ae15ec87 Log: q -> bq -> grn_expr's script syntax First, translate q into bq. Then, translate bq into grn_expr's script syntax. Finary, pass script syntax grn_expr to --filter option of select command. Query expansion is disabled by this change because groonga's query expansion is used only for --query option. Synonym related tests are disabled for now. TODO: Re-enable Synonyym relate tests Removed files: lib/q-translator.js test/q-translator.test.js Modified files: lib/api/2011-02-01/search.js test/api-search.test.js Modified: lib/api/2011-02-01/search.js (+43 -13) =================================================================== --- lib/api/2011-02-01/search.js 2012-08-13 12:08:33 +0900 (7dba611) +++ lib/api/2011-02-01/search.js 2012-08-13 13:55:03 +0900 (7261f0d) @@ -82,6 +82,10 @@ function createErrorBody(options) { }; } +function translateQueryToBooleanQuery(query) { + return "'" + query.replace(/(['\\])/g, "\\$1") + "'"; +} + exports.createHandler = function(context) { return function(request, response) { var dummyRid = '000000000000000000000000000000000000000000000000000000000000000'; @@ -89,14 +93,41 @@ exports.createHandler = function(context) { var domain = new Domain(request, context); var query = request.query.q || ''; var booleanQuery = request.query.bq || ''; - var filter = null; + var filters = []; + var matchExpr = ""; var facetParameter = request.query.facet; + if (query) { + var matchIndexFields; + var queryFilters; + var queryAsBooleanQuery; + + try { + queryAsBooleanQuery = translateQueryToBooleanQuery(query); + matchIndexFields = domain.indexFields.filter(function(field) { + return field.type == 'text'; + }); + queryFilters = matchIndexFields.map(function(field) { + var translator = new BooleanQueryTranslator(queryAsBooleanQuery); + translator.defaultField = field.columnName; + return "(" + translator.translate() + ")"; + }); + } catch (error) { + var body = createErrorBody({ + rid: dummyRid, + message: 'Invalid q value: ' + (error.message || error) + }); + return response.send(body, 400); + } + filters.push(queryFilters.join(" || ")); + matchExpr = "(label " + queryAsBooleanQuery + ")"; + } + if (booleanQuery) { var translator = new BooleanQueryTranslator(booleanQuery); - translator.defaultField = "default_field_fixme"; // FIXME + translator.defaultField = "FIXME" try { - filter = translator.translate(); + filters.push(translator.translate()); } catch (error) { var body = createErrorBody({ rid: dummyRid, @@ -104,25 +135,24 @@ exports.createHandler = function(context) { }); return response.send(body, 400); } + if (matchExpr.length > 0) { + matchExpr = "(and " + matchExpr + " " + booleanQuery + ")"; + } else { + matchExpr = booleanQuery; + } } - var matchIndexFields = domain.indexFields; - matchIndexFields = matchIndexFields.filter(function(field) { - return field.type == 'text'; + filters = filters.map(function(filter) { + return "(" + filter + ")"; }); - var matchColumns = matchIndexFields - .map(function(field) { - return field.columnName; - }) - .join('||'); var size = parseInt(request.query.size || '10', 10); var start = parseInt(request.query.start || '0', 10); + var filter = filters.join(" && "); var options = { table: domain.tableName, - query: query, + filter: filter, limit: size, offset: start, - match_columns: matchColumns }; if (domain.hasSynonymsTableSync()) { Deleted: lib/q-translator.js (+0 -94) 100644 =================================================================== --- lib/q-translator.js 2012-08-13 12:08:33 +0900 (3d1433d) +++ /dev/null @@ -1,94 +0,0 @@ -// -*- indent-tabs-mode: nil; js2-basic-offset: 2 -*- -/* - QueryTranslator translates Queries into Boolean Queries in Amazon - CloudSearch. - - Expression Syntax for Queries: - http://docs.amazonwebservices.com/cloudsearch/latest/developerguide/Search.Requests.html#Search.ReqParams - - Expression Syntax for Boolean Queries: - http://docs.amazonwebservices.com/cloudsearch/latest/developerguide/Search.Requests.html#Search.MatchSetExpression - - FIXME: Returns null if the given query is not supported. - Should raise. -*/ - - -function QueryTranslator(query) { - this.query = query; - this.offset = 0; -} - -function escapeTerm(term) { - return term.replace(/'/, "\\'"); -} - -QueryTranslator.prototype = { - translateIndividualTerm: function() { - var term = ''; - for (; this.offset < this.query.length; this.offset++) { - if (/[ \+\-\|]/.test(this.query[this.offset])) { - break; - } - term += this.query[this.offset]; - } - return "'" + escapeTerm(term) + "'"; - }, - translatePhraseTerm: function() { - if (this.query[this.offset] != '"') { - this.throwTranslateError("phrase must start with <\">"); - } - - this.offset++; - var phrase = ""; - for (; this.offset < this.query.length; this.offset++) { - var character = this.query[this.offset]; - if (character == '"') { - this.offset++; - return "'\"" + phrase + "\"'"; - } - - if (character == "\\") { - phrase += character; - this.offset++; - if (this.offset == this.query.length) { - this.throwTranslateError("escaped character is missing"); - } - character = this.query[this.offset]; - } - phrase += character; - } - this.throwTranslateError("phrase is unterminated: <" + phrase + ">"); - }, - translateTerm: function() { - this.skipSpaces(); - if (this.query[this.offset] == '"') { - return this.translatePhraseTerm(this.query, this); - } else { - return this.translateIndividualTerm(this.query, this); - } - }, - skipSpaces: function() { - for (; this.offset < this.query.length; this.offset++) { - if (this.query[this.offset] != " ") { - return; - } - } - }, - throwTranslateError: function(detail) { - var message = ""; - message += "<"; - message += this.query.substring(0, this.offset); - if (this.offset == this.query.length) { - message += "||"; - } else { - message += "|" + this.query[this.offset] + "|"; - message += this.query.substring(this.offset + 1); - } - message += ">"; - message += ": " + detail; - throw new Error(message); - } -}; - -exports.QueryTranslator = QueryTranslator; Modified: test/api-search.test.js (+1 -0) =================================================================== --- test/api-search.test.js 2012-08-13 12:08:33 +0900 (5204111) +++ test/api-search.test.js 2012-08-13 13:55:03 +0900 (73f87f9) @@ -356,6 +356,7 @@ suite('Search API', function() { }); suite('with fixture and synonyms loaded', function() { + return; // TODO: Re-enable me. Disabled temporary setup(function() { utils.loadDumpFile(context, __dirname + '/fixture/companies/ddl.grn'); utils.loadDumpFile(context, __dirname + '/fixture/companies/data.grn'); Deleted: test/q-translator.test.js (+0 -110) 100644 =================================================================== --- test/q-translator.test.js 2012-08-13 12:08:33 +0900 (02e075f) +++ /dev/null @@ -1,110 +0,0 @@ -// -*- indent-tabs-mode: nil; js2-basic-offset: 2 -*- - -var utils = require('./test-utils'); -var assert = require('chai').assert; - -var QueryTranslator = require('../lib/q-translator').QueryTranslator; - -function testIndividualTerm(label, individualTerm, - expectedOffset, expectedBooleanQuery) { - test('individual term: ' + label + ': ' + - '<' + individualTerm + '> -> <' + expectedBooleanQuery + '>', function() { - var translator = new QueryTranslator(individualTerm); - var actualBooleanQuery = translator.translateIndividualTerm(); - assert.deepEqual({ - booleanQuery: actualBooleanQuery, - offset: translator.offset - }, - { - booleanQuery: expectedBooleanQuery, - offset: expectedOffset - }); - }); -} - -function testPhraseTerm(label, phraseTerm, - expectedOffset, expectedBooleanQuery) { - test('phrase term: ' + label + ': ' + - '<' + phraseTerm + '> -> <' + expectedBooleanQuery + '>', function() { - var translator = new QueryTranslator(phraseTerm); - var actualBooleanQuery = translator.translatePhraseTerm(); - assert.deepEqual({ - booleanQuery: actualBooleanQuery, - offset: translator.offset - }, - { - booleanQuery: expectedBooleanQuery, - offset: expectedOffset - }); - }); -} - -function testPhraseTermError(label, phraseTerm, context, detail) { - test('error: phrase term: ' + label + ': ' + - '<' + phraseTerm + '>', function() { - var translator = new QueryTranslator(phraseTerm); - var actualError; - assert.throw(function() { - try { - translator.translatePhraseTerm(); - } catch (error) { - actualError = error; - throw error; - } - }); - assert.equal(actualError.message, "<" + context + ">" + ": " + detail); - }); -} - -function testTerm(label, term, expectedOffset, expectedBooleanQuery) { - test('term: ' + label + ': ' + - '<' + term + '> -> <' + expectedBooleanQuery + '>', function() { - var translator = new QueryTranslator(term); - var actualBooleanQuery = translator.translateTerm(); - assert.deepEqual({ - booleanQuery: actualBooleanQuery, - offset: translator.offset - }, - { - booleanQuery: expectedBooleanQuery, - offset: expectedOffset - }); - }); -} - -suite('QueryTranslator', function() { - testIndividualTerm("an individual term", - "star wars", - "star".length, - "'star'"); - testIndividualTerm("an individual term: single quote", - "let's go", - "let's".length, - "'let\\'s'"); - - testPhraseTerm("no special character", - '"star wars" luke', - '"star wars"'.length, - "'\"star wars\"'"); - testPhraseTerm("escape", - '"star \\" wars" luke', - '"star \\" wars"'.length, - "'\"star \\\" wars\"'"); - testPhraseTermError("not started with <\">", - 'star wars"', - '|s|tar wars"', - "phrase must start with <\">"); - testPhraseTermError("ended with <\\>", - '"star wars\\', - '"star wars\\||', - "escaped character is missing"); - testPhraseTermError("not terminated", - '"star wars', - '"star wars||', - "phrase is unterminated: <star wars>"); - - testTerm("a term", - " star wars", - " star".length, - "'star'"); -}); -------------- next part -------------- HTML����������������������������...Download