diff --git a/.gitignore b/.gitignore index 8e194bc..379cab7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ jsquery_gram.c jsquery_scan.c jsquery_gram.h +monq_gram.c +monq_gram.h +monq_scan.c regression.diffs regression.out results diff --git a/META.json b/META.json index 35ba35f..1eddfa1 100644 --- a/META.json +++ b/META.json @@ -1,8 +1,8 @@ -{ +{ "name": "JsQuery", "abstract": "JSON Query Language with GIN indexing support", "description": "JsQuery provides additional functionality for JSONB, such as a simple and effective way to search in nested objects and arrays, and more comparison operators with index support. It does this by implementing a specialized search syntax, the @@ operator, and the jsquery type for search strings.", - "version": "1.0.0", + "version": "1.0.1", "maintainer": [ "Teodor Sigaev ", "Alexander Korotkov ", @@ -42,7 +42,7 @@ }, "generated_by": "Josh Berkus", "meta-spec": { - "version": "1.0.0", + "version": "1.0.1", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ diff --git a/Makefile b/Makefile index 0892a24..63dc120 100644 --- a/Makefile +++ b/Makefile @@ -2,40 +2,39 @@ MODULE_big = jsquery OBJS = jsonb_gin_ops.o jsquery_constr.o jsquery_extract.o \ - jsquery_gram.o jsquery_io.o jsquery_op.o jsquery_support.o + jsquery_gram.o jsquery_io.o jsquery_op.o jsquery_support.o \ + monq_gram.o monq_scan.o monq_get_jsquery.o monq_create_query.o monq_delete_query.o EXTENSION = jsquery -DATA = jsquery--1.0.sql -INCLUDES = jsquery.h +DATA = jsquery--1.1.sql jsquery--1.0--1.1.sql +INCLUDES = jsquery.h monq.h REGRESS = jsquery # We need a UTF8 database ENCODING = UTF8 EXTRA_CLEAN = y.tab.c y.tab.h \ - jsquery_gram.c jsquery_scan.c jsquery_gram.h + jsquery_gram.c jsquery_scan.c jsquery_gram.h \ + monq_gram.c monq_scan.c monq_gram.h -ifdef USE_PGXS PG_CONFIG ?= pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) -else -subdir = contrib/jsquery -top_builddir = ../.. -include $(top_builddir)/src/Makefile.global -include $(top_srcdir)/contrib/contrib-global.mk -endif jsquery_gram.o: jsquery_scan.c jsquery_gram.c: BISONFLAGS += -d -distprep: jsquery_gram.c jsquery_scan.c +monq_gram.o: monq_scan.c + +monq_gram.c: BISONFLAGS += -d + +distprep: jsquery_gram.c jsquery_scan.c monq_gram.c monq_scan.c maintainer-clean: - rm -f jsquery_gram.c jsquery_scan.c jsquery_gram.h + rm -f jsquery_gram.c jsquery_scan.c jsquery_gram.h monq_gram.c monq_scan.c monq_gram.h install: installincludes installincludes: - $(INSTALL_DATA) $(addprefix $(srcdir)/, $(INCLUDES)) '$(DESTDIR)$(includedir_server)/' + $(INSTALL_DATA) $(addprefix $(srcdir)/, $(INCLUDES)) '$(DESTDIR)$(includedir_server)/' \ No newline at end of file diff --git a/README.md b/README.md index 4a019b4..b8efd67 100644 --- a/README.md +++ b/README.md @@ -341,6 +341,78 @@ correspondingly. ---------------------------- y > 0 , entry 0 + +### parse\_mquery +This function is transofrm MongoDB query to JsQuery query. Function get string +like argument and return jsquery object. +Example: +MongoDB query: +``` +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $lte : 1 } }'); +``` +Transformed to: +``` +select '{"a": {"b": 1 } }'::jsonb @@ 'a.b = 1'; +``` +And return: +``` +select '{"a": {"b": 1 } }'::jsonb @@ 'a.b = 1'; + ?column? +---------- + t +(1 row) +``` + +#### MongoDB operators supported by function + +Number of MongoDB query operators is limited by opportunities of +JsQuery language, but main part is supported. + +##### Comparison operators: +* `$eq` - supported; +* `$ne` - supported; +* `$lt` - supported; +* `$lte` - supported; +* `$gt` - supported; +* `$gte` - supported; +* `$in` - supported; +* `$nin` - supported. + +##### Logical operators: +* `$and` - supported; +* `$or` - supported; +* `$not` - supported; +* `$nor` - supported. + +##### Element operators: +* `$exists` - supported; +* `$type` - supported. + +##### Evaluation operators: +* `$mod` - not supported; +* `$regex` - not supported; +* `$text` - supported; +* `$where` - not supported. + +##### Bitwise operators: +* All operators are not supported. + +##### Array operators: +* `$all` - supported; +* `$elemMatch` - supported; +* `$size` - supported. + +##### Comment operators: +* All operators are not supported. + +##### Geospatial operators: +* All operators are not supported. + +##### Projextion operators: +* All operators are not supported. + +Examples of queries with all this operators you can find in the file +[sql/jsquery.sql](https://github.com/postgrespro/jsquery/blob/master/sql/jsquery.sql) + Contribution ------------ diff --git a/expected/jsquery.out b/expected/jsquery.out index 14b2730..dda85bb 100644 --- a/expected/jsquery.out +++ b/expected/jsquery.out @@ -2913,4 +2913,393 @@ select v from test_jsquery where v @@ 'array = [2,3]' order by v; {"array": [2, 3]} (1 row) +---MongoDB query translator tests +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : 1 }'); + ?column? +---------- + t +(1 row) + +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $eq : 1 } }'); + ?column? +---------- + t +(1 row) + +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $ne : 1 } }'); + ?column? +---------- + f +(1 row) + +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $lt : 1 } }'); + ?column? +---------- + f +(1 row) + +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $lte : 1 } }'); + ?column? +---------- + t +(1 row) + +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $gt : 1 } }'); + ?column? +---------- + f +(1 row) + +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $gte : 1 } }'); + ?column? +---------- + t +(1 row) + +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $in : [2,3] } }'); + ?column? +---------- + f +(1 row) + +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $nin : [2,3] } }'); + ?column? +---------- + t +(1 row) + +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : false } }'); + ?column? +---------- + f +(1 row) + +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : true } }'); + ?column? +---------- + t +(1 row) + +select parse_mquery('{ is : { $lt: 1 } }')::jsquery; + parse_mquery +-------------- + "is" < 1 +(1 row) + +select v from test_jsquery where v @@ parse_mquery('{ array : { $all: [2,3] } }') order by v; + v +---------------------- + {"array": [2, 3]} + {"array": [1, 2, 3]} + {"array": [2, 3, 4]} +(3 rows) + +select v from test_jsquery where v @@ parse_mquery('{ { $text: { $search: "Flew" } } }'); + v +--- +(0 rows) + +select '{ "a" : "ssl" }'::jsonb @@ parse_mquery('{ a: { $in: [ "ssl","security"] } }'); + ?column? +---------- + t +(1 row) + +select '{ "a" : 1 }'::jsonb @@ parse_mquery('{ a: { $in: [ "ssl","security"] } }'); + ?column? +---------- + f +(1 row) + +select '{ "a" : "ssl" }'::jsonb @@ parse_mquery('{ a: { $nin: [ "ssl","security"] } }'); + ?column? +---------- + f +(1 row) + +select '{ "a" : "sslqwerty" }'::jsonb @@ parse_mquery('{ a: { $nin: [ "ssl","security"] } }'); + ?column? +---------- + t +(1 row) + +select '{ "a" : [ "ssl","security"] }'::jsonb @@ parse_mquery('{ a: { $size: 2 } }'); + ?column? +---------- + t +(1 row) + +select '{ "a" : [ "ssl","security"] }'::jsonb @@ parse_mquery('{ a: { $size: 1 } }'); + ?column? +---------- + f +(1 row) + +select '{ "a" : [ "ssl","security", "pattern"] }'::jsonb @@ parse_mquery('{ a: { $all: [ "ssl","security"] } }'); + ?column? +---------- + t +(1 row) + +select '{ "a" : [ "ssl","pattern"] }'::jsonb @@ parse_mquery('{ a: { $all: [ "ssl","security"] } }'); + ?column? +---------- + f +(1 row) + +select '{ "a" : [ "ssl","security"] }'::jsonb @@ parse_mquery('{ a: { $all: [ "ssl","security"] } }'); + ?column? +---------- + t +(1 row) + +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : false } }'); + ?column? +---------- + f +(1 row) + +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : true } }'); + ?column? +---------- + t +(1 row) + +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : false } }'); + ?column? +---------- + t +(1 row) + +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : true } }'); + ?column? +---------- + f +(1 row) + +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ b: { $type: "int" } }'); + ?column? +---------- + t +(1 row) + +select '{ "b" : "qwerttyu" }'::jsonb @@ parse_mquery('{ b: { $type: "int" } }'); + ?column? +---------- + f +(1 row) + +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ b: { $type: "long" } }'); + ?column? +---------- + t +(1 row) + +select '{ "b" : "qwerttyu" }'::jsonb @@ parse_mquery('{ b: { $type: "long" } }'); + ?column? +---------- + f +(1 row) + +select '{ "b" : true }'::jsonb @@ parse_mquery('{ b: { $type: "bool" } }'); + ?column? +---------- + t +(1 row) + +select '{ "b" : "fklgjlksdfgsldflsgjslkrjekfjkl" }'::jsonb @@ parse_mquery('{ b: { $type: "bool" } }'); + ?column? +---------- + f +(1 row) + +select '{ "b" : "fklgjlksdfgsldflsgjslkrjekfjkl" }'::jsonb @@ parse_mquery('{ b: { $type: "array" } }'); + ?column? +---------- + f +(1 row) + +select '{ "b" : [1, 4] }'::jsonb @@ parse_mquery('{ b: { $type: "array" } }'); + ?column? +---------- + t +(1 row) + +select '{ "b" : "fklgjlksdfgsldflsgjslkrjekfjkl" }'::jsonb @@ parse_mquery('{ b: { $type: "string" } }'); + ?column? +---------- + t +(1 row) + +select '{ "b" : [1, 4] }'::jsonb @@ parse_mquery('{ b: { $type: "string" } }'); + ?column? +---------- + f +(1 row) + +select '{ "b" : 2.23432 }'::jsonb @@ parse_mquery('{ b: { $type: "double" } }'); + ?column? +---------- + t +(1 row) + +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ b: { $type: "double" } }'); + ?column? +---------- + t +(1 row) + +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ b: { $type: "decimal" } }'); + ?column? +---------- + t +(1 row) + +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "maxKey" } }'); +ERROR: Jsquery is not supported MongoDB "maxKey" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "binData" } }'); +ERROR: Jsquery is not supported MongoDB "binData" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "objectId" } }'); +ERROR: Jsquery is not supported MongoDB "objectId" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "javascript" } }'); +ERROR: Jsquery is not supported MongoDB "javascript" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "symbol" } }'); +ERROR: Jsquery is not supported MongoDB "symbol" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "javascriptWithScope" } }'); +ERROR: Jsquery is not supported MongoDB "javascriptWithScope" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "timestamp" } }'); +ERROR: Jsquery is not supported MongoDB "timestamp" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "minKey" } }'); +ERROR: Jsquery is not supported MongoDB "minKey" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "regex" } }'); +ERROR: Jsquery is not supported MongoDB "regex" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "null" } }'); +ERROR: Jsquery is not supported MongoDB "null" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "date" } }'); +ERROR: Jsquery is not supported MongoDB "date" value type +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "undefined" } }'); +ERROR: Jsquery is not supported MongoDB "undefined" value type +/* Or operator */ +select '{ "quantity" : 2, "price" : 10 }'::jsonb @@ parse_mquery('{ $or: [ { quantity: { $lt: 20 } }, { price: 10 } ] }'); + ?column? +---------- + t +(1 row) + +select '{ "quantity" : 200, "price" : 10 }'::jsonb @@ parse_mquery('{ $or: [ { quantity: { $lt: 20 } }, { price: 10 } ] }'); + ?column? +---------- + t +(1 row) + +select '{ "quantity" : 200, "price" : 10 }'::jsonb @@ parse_mquery('{ $or: [ { quantity: { $lt: 20 } }, { price: 100 } ] }'); + ?column? +---------- + f +(1 row) + +/* Nor operator */ +select '{ "quantity" : 2, "price" : 10 }'::jsonb @@ parse_mquery('{ $nor: [ { quantity: { $lt: 20 } }, { price: 10 } ] }'); + ?column? +---------- + f +(1 row) + +select '{ "quantity" : 200, "price" : 10 }'::jsonb @@ parse_mquery('{ $nor: [ { quantity: { $lt: 20 } }, { price: 10 } ] }'); + ?column? +---------- + t +(1 row) + +select '{ "quantity" : 200, "price" : 10 }'::jsonb @@ parse_mquery('{ $nor: [ { quantity: { $lt: 20 } }, { price: 100 } ] }'); + ?column? +---------- + t +(1 row) + +/* And operator */ +select '{ "quantity" : 200, "price" : 10 }'::jsonb @@ parse_mquery('{ $and: [ { quantity: { $lt: 20 } }, { price: 100 } ] }'); + ?column? +---------- + f +(1 row) + +select '{ "quantity" : 5, "price" : 100 }'::jsonb @@ parse_mquery('{ $and: [ { quantity: { $lt: 20 } }, { price: 100 } ] }'); + ?column? +---------- + t +(1 row) + +/* Not operator */ +select '{ "quantity" : 5, "price" : 100 }'::jsonb @@ parse_mquery('{ price: { $not: { $gt: 1.99 } } }'); + ?column? +---------- + f +(1 row) + +select '{ "quantity" : 5, "price" : 1 }'::jsonb @@ parse_mquery('{ price: { $not: { $gt: 1.99 } } }'); + ?column? +---------- + t +(1 row) + +/* Mod operator */ +select '{ "quantity" : 2, "price" : 10 }'::jsonb @@ parse_mquery('{ qty: { $mod: [ 4, 0 ] } } '); +ERROR: MongoDB module operator is not supported by jsquery +select '{"a": 5}'::jsonb @@ parse_mquery('{ a: { $eq: 5 } }'); + ?column? +---------- + t +(1 row) + +select '{"a": 5}'::jsonb @@ parse_mquery('{ a: { $eq: 6 } }'); + ?column? +---------- + f +(1 row) + +select '{ "quantity" : "qw", "price" : 10 }'::jsonb @@ parse_mquery('{ { $where: "qw"} }'); +ERROR: MongoDB where clause is not supported by jsquery +select '{ "quantity" : "qw", "price" : 10 }'::jsonb @@ parse_mquery('{ { $text: { $search: "qsddjkhjw" } } }'); + ?column? +---------- + f +(1 row) + +select '{ "quantity" : "qw", "price" : 10 }'::jsonb @@ parse_mquery('{ { $text: { $search: "qw" } } }'); + ?column? +---------- + t +(1 row) + +select '{"a": { "qwerty" : 5} }'::jsonb @@ parse_mquery('{ "a.qwerty" : { $eq: 6 } }'); + ?column? +---------- + f +(1 row) + +select '{"a": { "qwerty" : { "asdfgh" : { "fgfhg" : 5 } } } }'::jsonb @@ parse_mquery('{ "a.qwerty.asdfgh.fgfhg" : { $eq: 5 } }'); + ?column? +---------- + t +(1 row) + +select '{ "_id" : 3, "results" : [ { "product" : "abc", "score" : 7 }, { "product" : "abc", "score" : 8 } ] }' @@ parse_mquery('{ results: { $elemMatch: { product: "abc" } } }'); + ?column? +---------- + t +(1 row) + +select '{ "_id" : 3, "results" : [ 81, 84, 83] }' @@ parse_mquery('{ results: { $elemMatch: { $gte: 80, $lt: 85 } } }'); + ?column? +---------- + t +(1 row) + +select '{ "_id" : 3, "results" : [ 81, 86, 83] }' @@ parse_mquery('{ results: { $elemMatch: { $gte: 80, $lt: 85 } } }'); + ?column? +---------- + f +(1 row) + RESET enable_seqscan; diff --git a/jsquery--1.0--1.1.sql b/jsquery--1.0--1.1.sql new file mode 100644 index 0000000..f0d60b7 --- /dev/null +++ b/jsquery--1.0--1.1.sql @@ -0,0 +1,7 @@ +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION jsquery UPDATE TO '1.1'" to load this file. \quit + +CREATE OR REPLACE FUNCTION parse_mquery(cstring) + RETURNS jsquery + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT IMMUTABLE; \ No newline at end of file diff --git a/jsquery--1.0.sql b/jsquery--1.1.sql similarity index 98% rename from jsquery--1.0.sql rename to jsquery--1.1.sql index 3bf1d9b..0141bb4 100644 --- a/jsquery--1.0.sql +++ b/jsquery--1.1.sql @@ -291,3 +291,8 @@ CREATE OR REPLACE FUNCTION gin_debug_query_path_value(jsquery) RETURNS text AS 'MODULE_PATHNAME' LANGUAGE C STRICT IMMUTABLE; + +CREATE OR REPLACE FUNCTION parse_mquery(cstring) + RETURNS jsquery + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT IMMUTABLE; diff --git a/jsquery.control b/jsquery.control index 94510c8..d95ba24 100644 --- a/jsquery.control +++ b/jsquery.control @@ -1,6 +1,6 @@ # jsquery extension comment = 'data type for jsonb inspection' -default_version = '1.0' +default_version = '1.1' module_pathname = '$libdir/jsquery' relocatable = true diff --git a/jsquery_io.c b/jsquery_io.c index 8a43e8e..734c8ca 100644 --- a/jsquery_io.c +++ b/jsquery_io.c @@ -19,6 +19,9 @@ #include "utils/json.h" #include "jsquery.h" +#include "monq.h" + +extern MQuery *parsemquery(char *str); PG_MODULE_MAGIC; @@ -168,6 +171,35 @@ jsquery_in(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +PG_FUNCTION_INFO_V1(parse_mquery); +Datum +parse_mquery(PG_FUNCTION_ARGS) +{ + char *result = getJsquery(parse((char *) PG_GETARG_CSTRING(0))); + int32 len = strlen(result); + JsQueryParseItem *jsquery = parsejsquery(result, len); + JsQuery *res; + StringInfoData buf; + + initStringInfo(&buf); + enlargeStringInfo(&buf, 4 * len /* estimation */); + + appendStringInfoSpaces(&buf, VARHDRSZ); + + if (jsquery != NULL) + { + flattenJsQueryParseItem(&buf, jsquery, false); + + res = (JsQuery*)buf.data; + SET_VARSIZE(res, buf.len); + + PG_RETURN_JSQUERY(res); + } + + PG_RETURN_NULL(); +} + + static void printHint(StringInfo buf, JsQueryHint hint) { diff --git a/monq.h b/monq.h new file mode 100644 index 0000000..8dd7796 --- /dev/null +++ b/monq.h @@ -0,0 +1,264 @@ +#ifndef _MONQ_H_ +#define _MONQ_H_ + +#include "postgres.h" +#include "fmgr.h" +#include "miscadmin.h" +#include "nodes/pg_list.h" +#include "utils/builtins.h" +#include "utils/jsonb.h" + +typedef enum TypeOfLeafValue +{ + S, + I, + A, + D, + B +} TypeOfLeafValue; + +typedef enum TypeOfOperator +{ + NOP, + MOP, + AOP, + VOP, + EOP +} TypeOfOperator; + +typedef enum ArrayOperatorType +{ + _IN, + _NIN, + _ALL +} ArrayOperatorType; + +typedef enum ExpressionOperatorType +{ + _OR, + _NOR, + _AND +} ExpressionOperatorType; + +typedef enum ValueOperatorType +{ + _LESS, + _EQ, + _NOTEQ, + _LESSEQ, + _GREAT, + _GREATEQ, + _TYPE, + _SIZE, + _EXISTS +} ValueOperatorType; + + +typedef enum TypeOfClause +{ + LEAF, + COMMENT, + TEXT, + WHERE, + EXPRESSION +} TypeOfClause; + +typedef enum TypeOfValue +{ + LF_VALUE, + OP_OBJECT +} TypeOfValue; + +typedef enum TypeOfElemMatchValue +{ + E_EXPRESSION, + E_OP_OBJECT +} TypeOfElemMatchValue; + +typedef enum TypeOfWhereClauseValue +{ + STR +} TypeOfWhereClauseValue; + +typedef struct MArray +{ + List *arrayList; +} MArray; + +typedef struct LeafValue +{ + TypeOfLeafValue type; + union + { + char *str; + char *i; + MArray *ar; + bool b; + char *d; + }; +} LeafValue; + +/* Operators */ +typedef struct ValueOperator +{ + TypeOfOperator type; + + ValueOperatorType value_op; + LeafValue *value; +} ValueOperator; + +typedef struct NotOperator +{ + TypeOfOperator type; + + struct Operator *op; +} NotOperator; + +typedef struct ArrayOperator +{ + TypeOfOperator type; + + ArrayOperatorType array_op; + MArray *ar; +} ArrayOperator; + +typedef struct ModOperator +{ + TypeOfOperator type; + + LeafValue *divisor; + LeafValue *remainder; +} ModOperator; + +typedef struct Operator +{ + TypeOfOperator type; +} Operator; + +typedef struct OperatorObject +{ + List *operatorList; +} OperatorObject; + +typedef struct MValue +{ + TypeOfValue type; + union + { + LeafValue *lv; + OperatorObject *oob; + }; +} MValue; + +typedef struct LeafClause +{ + TypeOfClause type; + + char *key; + MValue *vl; +} LeafClause; + +typedef struct CommentClause +{ + TypeOfClause type; + + char *op; + char *str; +} CommentClause; + +typedef struct WhereClauseValue +{ + TypeOfClause type; + + TypeOfWhereClauseValue val_type; + char *str; +} WhereClauseValue; + +typedef struct WhereClause +{ + TypeOfClause type; + + WhereClauseValue *wcv; +} WhereClause; + +typedef struct TextClause +{ + TypeOfClause type; + + char *search_str; + bool lang_op; + char *lang_str; + bool case_sense; + bool diacr_sense; +} TextClause; + +typedef struct Clause +{ + TypeOfClause type; +} Clause; + +typedef struct Expression +{ + List *clauseList; +} Expression; + +typedef struct ElemMatchOperator +{ + TypeOfOperator type; + + TypeOfElemMatchValue typeOfValue; + + union + { + Expression *expression; + OperatorObject *operatorOpbject; + }; +} ElemMatchOperator; + +typedef struct ExpressionClause +{ + TypeOfClause type; + + ExpressionOperatorType op; + List *expressionList; +} ExpressionClause; + +typedef struct MQuery +{ + Expression *exp; + Datum jsQuery; +} MQuery; + + +extern MArray *createNewArray(List *arrayList); +extern List *addArrayElement(LeafValue * value, List *arrayList); +extern Operator *createNotOperator(Operator *op); +extern Operator *createModOperator(LeafValue *divisor, LeafValue *remainder); +extern Operator *createArrayOperator(ArrayOperatorType op, MArray *ar); +extern Operator *createValueOperator(ValueOperatorType op, LeafValue *value); +extern Operator *createElemMatchOperatorExpression(Expression *expression); +extern Operator *createElemMatchOperatorOpObject(OperatorObject *oob); +extern LeafValue *createStringValue(char *str); +extern LeafValue *createDoubleValue(char* d); +extern LeafValue *createIntegerValue(char* i); +extern LeafValue *createArrayValue(MArray *ar); +extern LeafValue *createBooleanValue(bool b); +extern List *addOperator(Operator *op, List *operatorList); +extern OperatorObject *createOperatorObject(List *operatorList); +extern MValue *createOperatorObjectValue(OperatorObject *oob); +extern MValue *createLeafValueValue(LeafValue *lv); +extern Clause *createLeafClause(char* key, MValue *vl); +extern Clause *createCommentClause(char *op, char *str); +extern WhereClauseValue *stringToWhereClauseValue(char *str); +extern Clause *createWhereClause(WhereClauseValue *wcv); +extern List *addClause(Clause *clause, List *clauseList); +extern Expression *createExpression(List *clauseList); +extern List *addExpression(Expression *exp, List *expressionList); +extern Clause *createExpressionTreeClause(ExpressionOperatorType op, List *expressionList); +extern Clause *createTextClause(char* search_str, bool lang_op, char* lang_str, bool case_sense, bool diacr_sense); +extern MQuery *createQuery(Expression *exp); +extern void deleteMquery(MQuery *qu); +extern char *getJsquery(MQuery *query); +extern MQuery *parse(char *str); + +#endif \ No newline at end of file diff --git a/monq_create_query.c b/monq_create_query.c new file mode 100644 index 0000000..d0a72ee --- /dev/null +++ b/monq_create_query.c @@ -0,0 +1,244 @@ +#include +#include +#include + +#include "monq.h" + + +MArray * +createNewArray(List *arrayList) +{ + MArray *new_ar = (MArray *) palloc(sizeof(MArray)); + new_ar->arrayList = arrayList; + return new_ar; +} + +List * +addArrayElement(LeafValue *value, List *arrayList) +{ + return lappend(arrayList, value); +} + +Operator * +createNotOperator(Operator *op) +{ + NotOperator *new_op = (NotOperator *) palloc(sizeof(NotOperator)); + new_op->type = NOP; + new_op->op = op; + return (Operator *) new_op; +} + +Operator * +createModOperator(LeafValue *divisor, LeafValue *remainder) +{ + ModOperator *new_op = (ModOperator *) palloc(sizeof(ModOperator)); + new_op->type = MOP; + new_op->divisor = divisor; + new_op->remainder = remainder; + return (Operator *) new_op; +} + +Operator * +createArrayOperator(ArrayOperatorType op, MArray *ar) +{ + ArrayOperator *new_op = (ArrayOperator *) palloc(sizeof(ArrayOperator)); + new_op->type = AOP; + new_op->array_op = op; + new_op->ar = ar; + return (Operator*) new_op; +} + +Operator * +createValueOperator(ValueOperatorType op, LeafValue * value) +{ + ValueOperator *new_op = (ValueOperator *) palloc(sizeof(ValueOperator)); + new_op->type = VOP; + new_op->value_op = op; + new_op->value = value; + return (Operator *) new_op; +} + +Operator * +createElemMatchOperatorOpObject(OperatorObject *oob) +{ + ElemMatchOperator *new_op = (ElemMatchOperator *) palloc(sizeof(ElemMatchOperator)); + new_op->type = EOP; + new_op->typeOfValue = E_OP_OBJECT; + new_op->operatorOpbject = oob; + return (Operator *) new_op; +} + +Operator * +createElemMatchOperatorExpression(Expression *expression) +{ + ElemMatchOperator *new_op = (ElemMatchOperator *) palloc(sizeof(ElemMatchOperator)); + new_op->type = EOP; + new_op->typeOfValue = E_EXPRESSION; + new_op->expression = expression; + return (Operator *) new_op; +} + +LeafValue * +createStringValue(char *str) +{ + LeafValue *lv = (LeafValue *) palloc(sizeof(LeafValue)); + lv->type = S; + lv->str = str; + return lv; +} + +LeafValue * +createDoubleValue(char* d) +{ + LeafValue *lv = (LeafValue *) palloc(sizeof(LeafValue)); + lv->type = D; + lv->d = d; + return lv; +} + +LeafValue * +createIntegerValue(char* i) +{ + LeafValue *lv = (LeafValue *) palloc(sizeof(LeafValue)); + lv->type = I; + lv->i = i; + return lv; +} + +LeafValue * +createArrayValue(MArray *ar) +{ + LeafValue *lv = (LeafValue *) palloc(sizeof(LeafValue)); + lv->type = A; + lv->ar = ar; + return lv; +} + +LeafValue * +createBooleanValue(bool b) +{ + LeafValue *lv = (LeafValue *) palloc(sizeof(LeafValue)); + lv->type = B; + lv->b = b; + return lv; +} + +List * +addOperator(Operator *op, List *operatorList) +{ + return lcons(op, operatorList); +} + +OperatorObject * +createOperatorObject(List *operatorList) +{ + OperatorObject *new_oob = (OperatorObject *) palloc(sizeof(OperatorObject)); + new_oob->operatorList = operatorList; + return new_oob; +} + +MValue * +createOperatorObjectValue(OperatorObject *oob) +{ + MValue *vl = (MValue *) palloc(sizeof(MValue)); + vl->type = OP_OBJECT; + vl->oob = oob; + return vl; +} + +MValue * +createLeafValueValue(LeafValue *lv) +{ + MValue *vl = (MValue *) palloc(sizeof(MValue)); + vl->type = LF_VALUE; + vl->lv = lv; + return vl; +} + +Clause * +createLeafClause(char* key, MValue *vl) +{ + LeafClause *new_lc = (LeafClause *) palloc(sizeof(LeafClause)); + new_lc->type = LEAF; + new_lc->key = key; + new_lc->vl = vl; + return ( Clause* ) new_lc; +} + +Clause * +createCommentClause(char *op, char *str) +{ + CommentClause *new_com_cl = (CommentClause *) palloc(sizeof(CommentClause)); + new_com_cl->type = COMMENT; + new_com_cl->op = op; + new_com_cl->str = str; + return ( Clause* ) new_com_cl; +} + +WhereClauseValue * +stringToWhereClauseValue(char *str) +{ + WhereClauseValue *wcv = (WhereClauseValue *) palloc(sizeof(WhereClauseValue)); + wcv->str = str; + return wcv; +} + +Clause * +createWhereClause(WhereClauseValue *wcv) +{ + WhereClause *wc = (WhereClause *) palloc(sizeof(WhereClause)); + wc->type = WHERE; + wc->wcv = wcv; + return (Clause *) wc; +} + +List * +addClause(Clause *clause, List *clauseList) +{ + return lappend(clauseList, clause); +} + +Expression * +createExpression(List *clauseList) +{ + Expression *exp = (Expression *) palloc(sizeof(Expression)); + exp->clauseList = clauseList; + return exp; +} + +List * +addExpression(Expression *exp, List *expressionList) +{ + return lcons(exp, expressionList); +} + +Clause * +createExpressionTreeClause(ExpressionOperatorType opType, List *expressionList) +{ + ExpressionClause *exp_cl = (ExpressionClause *) palloc(sizeof(ExpressionClause)); + exp_cl->type = EXPRESSION; + exp_cl->op = opType; + exp_cl->expressionList = expressionList; + return (Clause *) exp_cl; +} + +Clause * +createTextClause(char *search_str, bool lang_op, char *lang_str, bool case_sense, bool diacr_sense) +{ + TextClause *text_cl = (TextClause *) palloc(sizeof(TextClause)); + text_cl->type = TEXT; + text_cl->search_str = search_str; + text_cl->lang_op = lang_op; + text_cl->lang_str = lang_str; + text_cl->case_sense = case_sense; + text_cl->diacr_sense = diacr_sense; + return (Clause *) text_cl; +} + +MQuery * +createQuery(Expression *exp) +{ + MQuery *qu = (MQuery *) palloc(sizeof(MQuery)); + qu->exp = exp; + return qu; +} \ No newline at end of file diff --git a/monq_delete_query.c b/monq_delete_query.c new file mode 100644 index 0000000..05f1d75 --- /dev/null +++ b/monq_delete_query.c @@ -0,0 +1,192 @@ +#include +#include +#include + +#include "c.h" +#include "postgres.h" +#include "access/gin.h" +#include "utils/numeric.h" + +#include "monq.h" + + +static void deleteValueOperator(ValueOperator *vop); +static void deleteOperator(Operator *op); +static void deleteNotOperator(NotOperator *op); +static void deleteElemMatchOperator(ElemMatchOperator *elemMatchOperator); +static void deleteOperatorObject(OperatorObject *op_object); +static void deleteLeafClauseValue(MValue *val); +static void deleteLeafClause(LeafClause *lc); +static void deleteExpressionClause(ExpressionClause* expClause); +static void deleteTextClause(TextClause *tClause); +static void deleteClause(Clause *cl); +static void deleteExpression(Expression * ex); +static void deleteLeafValue(LeafValue *value); +static void deleteArraySequence(MArray *ar); +static void deleteArrayOperator(ArrayOperator *aop); + + +static void +deleteValueOperator(ValueOperator *vop) +{ + deleteLeafValue(vop->value); + pfree(vop); +} + +static void +deleteOperator(Operator *operator) +{ + switch(operator->type) + { + case NOP : + deleteNotOperator((NotOperator*) operator ); + break; + case AOP : + deleteArrayOperator((ArrayOperator*) operator ); + break; + case VOP : + deleteValueOperator((ValueOperator*) operator ); + break; + case EOP : + deleteElemMatchOperator((ElemMatchOperator*) operator); + break; + case MOP : + break; + } +} + +static void +deleteElemMatchOperator(ElemMatchOperator *elemMatchOperator) +{ + switch(elemMatchOperator->typeOfValue) + { + case E_EXPRESSION: + deleteExpression(elemMatchOperator->expression); + break; + case E_OP_OBJECT: + deleteOperatorObject(elemMatchOperator->operatorOpbject); + break; + } +} + +static void +deleteNotOperator(NotOperator *op) +{ + deleteOperator(op->op); + pfree(op); +} + +static void +deleteOperatorObject(OperatorObject *op_object) +{ + List *operatorList; + ListCell *cell; + + operatorList = op_object->operatorList; + + foreach(cell, operatorList) + deleteOperator((Operator *)lfirst(cell)); + + pfree(op_object->operatorList); + pfree(op_object); +} + +static void +deleteLeafClauseValue(MValue *value) +{ + value->type ? deleteOperatorObject(value->oob) : deleteLeafValue(value->lv); +} + +static void +deleteLeafClause(LeafClause *lc) +{ + deleteLeafClauseValue(lc->vl); + pfree(lc); +} + +static void +deleteExpressionClause(ExpressionClause* expClause) +{ + List *expressionList; + ListCell *cell; + + expressionList = expClause->expressionList; + + foreach(cell, expressionList) + deleteExpression((Expression *)lfirst(cell)); + + pfree(expressionList); + pfree(expClause); +} + +static void +deleteTextClause(TextClause *textClause) +{ + pfree(textClause); +} + +static void +deleteClause(Clause *clause) +{ + switch(clause->type) + { + case LEAF : + deleteLeafClause((LeafClause*) clause); + break; + case TEXT : + deleteTextClause((TextClause*) clause); + break; + case EXPRESSION : + deleteExpressionClause((ExpressionClause*) clause); + break; + default : + break; + } +} + +static void +deleteExpression(Expression * expression) +{ + List *clauseList = expression->clauseList; + ListCell *cell; + + foreach(cell, clauseList) + deleteClause((Clause *)lfirst(cell)); + + pfree(expression); +} + +static void +deleteLeafValue(LeafValue *value) +{ + if(value->type == A) + deleteArraySequence(value->ar); + pfree(value); +} + +static void +deleteArraySequence(MArray *marray) +{ + List *arrayList = marray->arrayList; + ListCell *cell; + + foreach(cell, arrayList) + deleteLeafValue((LeafValue *)lfirst(cell)); + + pfree(arrayList); + pfree(marray); +} + +static void +deleteArrayOperator(ArrayOperator *arrayOperator) +{ + deleteArraySequence(arrayOperator->ar); + pfree(arrayOperator); +} + +void +deleteMquery(MQuery *query) +{ + deleteExpression(query->exp); + pfree(query); +} \ No newline at end of file diff --git a/monq_get_jsquery.c b/monq_get_jsquery.c new file mode 100644 index 0000000..ec40ee5 --- /dev/null +++ b/monq_get_jsquery.c @@ -0,0 +1,388 @@ +#include +#include +#include + +#include "monq.h" + + +static void getExpression(StringInfo strInfo, Expression * expression); +static void getClause(StringInfo strInfo, Clause *clause); +static void getExpressionClause(StringInfo strInfo, ExpressionClause* expClause); +static void getTextClause(StringInfo strInfo, TextClause* textClause); +static void getLeafClause(StringInfo strInfo, LeafClause *leafClause); +static void getLeafClauseValue(StringInfo strInfo, char *key, MValue *value); +static void getLeafValueEq(StringInfo strInfo, char *key, LeafValue *leafValue); +static void getOperatorObject(StringInfo strInfo, char *key, OperatorObject *opObject); +static void getOperator(StringInfo strInfo, char *key, Operator *operator); +static void getNotOperator(StringInfo strInfo, char *key, NotOperator *notOperator); +static void getElemMatchOperator(StringInfo strInfo, char *key, ElemMatchOperator *elemMatchOperator); +static void getArrayOperator(StringInfo strInfo, char *key, ArrayOperator *arOperator); +static void getArraySequence(StringInfo strInfo, MArray *marray); +static void getLeafValue(StringInfo strInfo, LeafValue *value); +static void getValueOperator(StringInfo strInfo, char *key, ValueOperator *valOperator); +static void getValueOperatorType(StringInfo strInfo, ValueOperatorType type); +static void getValueType(StringInfo strInfo, char *type); + +static void +getValueOperatorType(StringInfo strInfo, ValueOperatorType type) +{ + switch(type) + { + case _LESS : + appendStringInfo(strInfo, "<"); + break; + case _EQ : + case _NOTEQ : + appendStringInfo(strInfo, "="); + break; + case _LESSEQ : + appendStringInfo(strInfo, "<="); + break; + case _GREAT : + appendStringInfo(strInfo, ">"); + break; + case _GREATEQ : + appendStringInfo(strInfo, ">="); + break; + case _TYPE : + break; + case _SIZE : + appendStringInfo(strInfo, ".@# ="); + break; + case _EXISTS : + appendStringInfo(strInfo, "= *"); + break; + default : + elog(ERROR,"This value operator is not supported"); + break; + } +} + +/* + * Return type in jsquery format + */ +static void +getValueType(StringInfo strInfo, char *type) +{ + if(strcmp(type,"\"string\"") == 0) appendStringInfo(strInfo, " IS STRING"); + else if( + strcmp(type, "\"double\"") == 0 || + strcmp(type, "\"int\"") == 0 || + strcmp(type, "\"long\"") == 0 || + strcmp(type, "\"decimal\"") == 0 + ) appendStringInfo(strInfo, " IS NUMERIC"); + + else if(strcmp(type, "\"array\"") == 0) appendStringInfo(strInfo, " IS ARRAY"); + else if(strcmp(type, "\"object\"") == 0) appendStringInfo(strInfo, " IS OBJECT"); + else if(strcmp(type, "\"bool\"") == 0) appendStringInfo(strInfo, " IS BOOLEAN"); + else + elog(ERROR, "Jsquery is not supported MongoDB %s value type", type); +} + +static void +getValueOperator(StringInfo strInfo, char *key, ValueOperator *valOperator) +{ + if(valOperator->value_op == _EXISTS) + { + if(valOperator->value->b) + { + appendStringInfo(strInfo, "%s", key); + appendStringInfo(strInfo, " "); + getValueOperatorType(strInfo, valOperator->value_op); + } + else + { + appendStringInfo(strInfo, "NOT (%s ", key); + getValueOperatorType(strInfo, valOperator->value_op); + appendStringInfo(strInfo, ")"); + } + } + else if(valOperator->value_op == _TYPE) + { + appendStringInfo(strInfo, "%s ", key); + getValueType(strInfo, valOperator->value->str); + } + else if(valOperator->value_op == _NOTEQ) + { + appendStringInfo(strInfo, "NOT (%s ", key); + getValueOperatorType(strInfo, valOperator->value_op); + appendStringInfo(strInfo, " "); + getLeafValue(strInfo, valOperator->value); + appendStringInfo(strInfo, ")"); + } + else + { + appendStringInfo(strInfo, "%s ", key); + getValueOperatorType(strInfo, valOperator->value_op); + appendStringInfo(strInfo, " "); + getLeafValue(strInfo, valOperator->value); + } +} + +static void +getElemMatchOperator(StringInfo strInfo, char *key, ElemMatchOperator *elemMatchOperator) +{ + appendStringInfo(strInfo, "%s.#:(", key); + switch(elemMatchOperator->typeOfValue) + { + case E_EXPRESSION: + getExpression(strInfo, elemMatchOperator->expression); + break; + case E_OP_OBJECT: + getOperatorObject(strInfo, "$",elemMatchOperator->operatorOpbject); + break; + } + appendStringInfo(strInfo, ")"); +} + +static void +getOperator(StringInfo strInfo, char *key, Operator *operator) +{ + switch(operator->type) + { + case NOP : + getNotOperator(strInfo, key, (NotOperator*) operator); + break; + case MOP : + elog(ERROR, "MongoDB module operator is not supported by jsquery"); + case AOP : + getArrayOperator(strInfo, key, (ArrayOperator*) operator); + break; + case VOP : + getValueOperator(strInfo, key, (ValueOperator*) operator); + break; + case EOP : + getElemMatchOperator(strInfo, key, (ElemMatchOperator*) operator); + break; + default : + elog(ERROR, "This mongoDB operator is not supported by jsquery"); + } +} + +static void +getNotOperator(StringInfo strInfo, char *key, NotOperator *notOperator) +{ + appendStringInfo(strInfo, "NOT ("); + getOperator(strInfo, key,notOperator->op); + appendStringInfo(strInfo, ")"); +} + +static void +getOperatorObject(StringInfo strInfo, char *key, OperatorObject *opObject) +{ + ListCell *cell; + bool first = true; + + foreach(cell, opObject->operatorList) + { + if(first) + { + appendStringInfo(strInfo, "("); + getOperator(strInfo, key, (Operator *)lfirst(cell)); + appendStringInfo(strInfo, ")"); + first = false; + } + else + { + appendStringInfo(strInfo, " AND ("); + getOperator(strInfo, key, ((Operator *)lfirst(cell))); + appendStringInfo(strInfo, ")"); + } + } +} + +static void +getLeafValueEq(StringInfo strInfo, char *key, LeafValue *leafValue) +{ + appendStringInfo(strInfo, "%s = ", key); + getLeafValue(strInfo, leafValue); +} + +static void +getLeafClauseValue(StringInfo strInfo, char *key, MValue *value) +{ + if(value->type) + getOperatorObject(strInfo, key, value->oob); + else + getLeafValueEq(strInfo, key, value->lv); +} + +static void +getLeafClause(StringInfo strInfo, LeafClause *leafClause) +{ + getLeafClauseValue(strInfo, leafClause->key, leafClause->vl); +} + +static void +getExpressionClause(StringInfo strInfo, ExpressionClause* expClause) +{ + ListCell *cell; + bool first = true; + char *expOperator = NULL; + + switch(expClause->op) + { + case _AND : + expOperator = "AND"; + break; + case _OR : + expOperator = "OR"; + break; + case _NOR : + expOperator = "OR NOT"; + break; + } + + foreach(cell, expClause->expressionList) + { + if(first) + { + if(expClause->op == _NOR) appendStringInfo(strInfo, "NOT "); + + appendStringInfo(strInfo, "("); + getExpression(strInfo, (Expression *)lfirst(cell)); + appendStringInfo(strInfo, ") "); + + first = false; + } + else + { + appendStringInfo(strInfo, "%s (", expOperator); + getExpression(strInfo, (Expression *)lfirst(cell)); + appendStringInfo(strInfo, ") "); + } + } +} + +static void +getTextClause(StringInfo strInfo, TextClause *textClause) +{ + appendStringInfo(strInfo, "* = %s", textClause->search_str); +} + +static void +getClause(StringInfo strInfo, Clause *clause) +{ + switch(clause->type) + { + case LEAF : + getLeafClause(strInfo, (LeafClause*) clause); + break; + case COMMENT : + elog(ERROR, "MongoDB comment clause is not supported by jsquery"); + case TEXT : + getTextClause(strInfo, (TextClause*) clause); + break; + case WHERE : + elog(ERROR, "MongoDB where clause is not supported by jsquery"); + case EXPRESSION : + getExpressionClause(strInfo, (ExpressionClause*) clause); + break; + default: + break; + } +} + +static void +getExpression(StringInfo strInfo, Expression *expression) +{ + ListCell *cell; + bool first = true; + + foreach(cell, expression->clauseList) + { + if (first) + { + getClause(strInfo, (Clause *)lfirst(cell)); + first = false; + } + else + { + appendStringInfo(strInfo, " AND "); + getClause(strInfo, (Clause *)lfirst(cell)); + } + } +} + +static void +getLeafValue(StringInfo strInfo, LeafValue *value) +{ + switch(value->type) + { + case S : + appendStringInfo(strInfo, "%s", value->str); + break; + case I : + appendStringInfo(strInfo, "%s", value->i); + break; + case A : + appendStringInfo(strInfo, "["); + getArraySequence(strInfo, value->ar); + appendStringInfo(strInfo, "]"); + break; + case B : + appendStringInfo(strInfo, "%s", (value->b ? "true" : "false")); + break; + case D : + appendStringInfo(strInfo, "%s", value->d); + break; + default : + break; + } +} + +static void +getArraySequence(StringInfo strInfo, MArray *marray) +{ + ListCell *cell; + bool first = true; + + foreach(cell, marray->arrayList) + { + if(first) + { + getLeafValue(strInfo, (LeafValue *)lfirst(cell)); + first = false; + } + else + { + appendStringInfo(strInfo, ", "); + getLeafValue(strInfo, (LeafValue *)lfirst(cell)); + } + } +} + +static void +getArrayOperator(StringInfo strInfo, char *key, ArrayOperator *arOperator) +{ + switch(arOperator->array_op) + { + case _IN : + appendStringInfo(strInfo, "%s IN (", key); + getArraySequence(strInfo, arOperator->ar); + appendStringInfo(strInfo, ")"); + break; + case _NIN: + appendStringInfo(strInfo, "NOT (%s IN (", key); + getArraySequence(strInfo, arOperator->ar); + appendStringInfo(strInfo, "))"); + break; + case _ALL: + appendStringInfo(strInfo, "%s @> [", key); + getArraySequence(strInfo, arOperator->ar); + appendStringInfo(strInfo, "]"); + break; + default : + break; + } +} + +char * +getJsquery(MQuery *qu) +{ + StringInfoData strInfo; + initStringInfo(&strInfo); + getExpression(&strInfo, qu->exp); + deleteMquery(qu); + return strInfo.data; +} \ No newline at end of file diff --git a/monq_gram.y b/monq_gram.y new file mode 100644 index 0000000..a1ca822 --- /dev/null +++ b/monq_gram.y @@ -0,0 +1,239 @@ +%{ + #include + #include + #include + + #include "monq.h" + + MQuery *RET; + + typedef struct yy_buffer_state *YY_BUFFER_STATE; + extern int yylex(); + extern int yyparse(); + extern void yyerror(char *s); + extern YY_BUFFER_STATE yy_scan_string(char * str); + extern void yy_delete_buffer(YY_BUFFER_STATE buffer); + + char *inputString; + int position; + MQuery* + parse(char *str) + { + YY_BUFFER_STATE buffer = yy_scan_string(str); + inputString = str; + yyparse(); + yy_delete_buffer(buffer); + position = 1; + return RET; + } +%} + +/* Types of query tree nodes and leafs */ +%union +{ + MQuery *mquery; + Expression *exp; + Clause *cl; + MValue *vl; + LeafValue *lv; + List *list; + WhereClauseValue *wcv; + char *strval; + int intval; + double dubval; + MArray *arrval; + bool boolval; + ArrayOperatorType aop_type; + ExpressionOperatorType exop_type; + ValueOperatorType valop_type; + OperatorObject *oob; + Operator *op; + ElemMatchOperator *elemMatchOp; +} + +%type QUERY +%type EXPRESSION +%type CLAUSE TEXT_CLAUSE EXPRESSION_TREE_CLAUSE LEAF_CLAUSE COMMENT_CLAUSE WHERE_CLAUSE +%type VALUE + +%type KEY KEY_STRING +%type OPERATOR +%type OPEARATOR_OBJECT + +%type LSCOPE RSCOPE COMMA + +%type OPERATOR_LIST LEAF_VALUE_LIST EXPRESSION_LIST CLAUSE_LIST + +%type WHERE_CLAUSE_VALUE +%type WHERE_OPERATOR +%token WHERE_OPERATOR + +/* OPERATORS */ + +/* Tree clause */ +%type TREE_OPERATOR OR NOR AND +%token OR NOR AND + +/* Leaf value operator */ +%type EQ LESS GREAT LESSEQ GREATEQ NOTEQ TYPE SIZE EXISTS NOT VALUE_OPERATOR +%token EQ NOTEQ LESS LESSEQ GREAT GREATEQ TYPE SIZE EXISTS NOT + +/* Array operator */ +%type IN NIN ALL ARRAY_OPERATOR +%token IN NIN ALL + +/* Mod operator */ +%type MOD_OPERATOR +%type DIVISOR REMAINDER +%token MOD_OPERATOR + +/* ElemMatch operator */ +%type ELEMMATCH_OPERATOR +%type ELEMMATCH +%token ELEMMATCH + +/* Comment clause */ +%type COMMENT_OPERATOR +%token COMMENT_OPERATOR + +/* Text clause */ +%type DIACRITIC_SENSITIVE_OPERATOR CASE_SENSITIVE_OPERATOR LANGUAGE_OPERATOR SEARCH_OPERATOR TEXT_OPERATOR +%token DIACRITIC_SENSITIVE_OPERATOR CASE_SENSITIVE_OPERATOR LANGUAGE_OPERATOR SEARCH_OPERATOR TEXT_OPERATOR + +/* Type of values */ +%type LEAF_VALUE +%type INT +%type STRING +%type DOUBLE +%type ARRAY +%type BOOLEAN +%token INT STRING DOUBLE BOOLEAN KEY_STRING + +/* Scope types */ +%token LSCOPE RSCOPE COMMA LSQBRACKET RSQBRACKET LRBRACKET RRBRACKET + +%start QUERY + +%% +QUERY : EXPRESSION {$$ = createQuery($1); RET=$$; } + ; + +EXPRESSION : LSCOPE CLAUSE_LIST RSCOPE { $$ = createExpression($2); } + ; + +CLAUSE_LIST : CLAUSE COMMA CLAUSE_LIST { $$ = addClause($1, $3); } + | CLAUSE { $$ = lappend(NULL, $1); } + ; + +CLAUSE : LEAF_CLAUSE + | COMMENT_CLAUSE + | WHERE_CLAUSE + | EXPRESSION_TREE_CLAUSE + | TEXT_CLAUSE + ; + + +/* TEXT CLAUSE SECTION */ + +TEXT_CLAUSE : LSCOPE TEXT_OPERATOR EQ LSCOPE SEARCH_OPERATOR EQ KEY + RSCOPE RSCOPE { $$ = createTextClause($7, false, "", false, false); } + | LSCOPE TEXT_OPERATOR EQ LSCOPE SEARCH_OPERATOR EQ KEY COMMA + LANGUAGE_OPERATOR EQ STRING COMMA + CASE_SENSITIVE_OPERATOR EQ BOOLEAN COMMA + DIACRITIC_SENSITIVE_OPERATOR EQ BOOLEAN RSCOPE RSCOPE { $$ = createTextClause($7, false, $11, $15, $19); } + ; + +/* END OF SECTION */ + +/*WHERE CLAUSE SECTION*/ + +WHERE_CLAUSE : LSCOPE WHERE_OPERATOR EQ WHERE_CLAUSE_VALUE RSCOPE { $$ = createWhereClause($4); } + ; + +WHERE_CLAUSE_VALUE : KEY { $$ = stringToWhereClauseValue($1); } + ; +/* END OF SECTION */ + +/*COMMENT CLAUSE SECTION*/ +COMMENT_CLAUSE : LSCOPE COMMENT_OPERATOR EQ STRING RSCOPE { $$ = createCommentClause($2, $4); } + ; +/* END OF SECTION */ + +/*TREE CLAUSE SECTION*/ + +EXPRESSION_TREE_CLAUSE : TREE_OPERATOR EQ LSQBRACKET EXPRESSION_LIST RSQBRACKET { $$ = createExpressionTreeClause($1, $4); } + | LSCOPE EXPRESSION_TREE_CLAUSE RSCOPE { $$ = $2; } + ; + +EXPRESSION_LIST : EXPRESSION { $$ = lcons($1, NULL); } + | EXPRESSION COMMA EXPRESSION_LIST { $$ = addExpression($1, $3); } + ; + +TREE_OPERATOR : OR | AND | NOR ; + +/* END OF SECTION */ + +/* LEAF CLAUSE SECTION */ +LEAF_CLAUSE : KEY EQ VALUE { $$ = createLeafClause($1, $3); } + ; + +KEY : STRING + | KEY_STRING + ; + +VALUE : LEAF_VALUE { $$ = createLeafValueValue($1); } + | OPEARATOR_OBJECT { $$ = createOperatorObjectValue($1); } + ; + +OPEARATOR_OBJECT : LSCOPE OPERATOR_LIST RSCOPE { $$ = createOperatorObject($2); } + ; + +OPERATOR_LIST : OPERATOR { $$ = lappend(NULL, $1); } + | OPERATOR COMMA OPERATOR_LIST { $$ = addOperator($1, $3); } + ; + +OPERATOR : VALUE_OPERATOR EQ LEAF_VALUE { $$ = createValueOperator($1, $3); } + | ARRAY_OPERATOR EQ ARRAY { $$ = createArrayOperator($1, $3); } + | MOD_OPERATOR EQ LSQBRACKET DIVISOR COMMA REMAINDER RSQBRACKET { $$ = createModOperator($4, $6); } + | NOT EQ LSCOPE OPERATOR RSCOPE { $$ = createNotOperator($4); } + | ELEMMATCH EQ ELEMMATCH_OPERATOR { $$ = $3; } + ; + +VALUE_OPERATOR : EQ | NOTEQ | LESS | LESSEQ | GREAT | GREATEQ | TYPE | SIZE | EXISTS + ; + +ELEMMATCH_OPERATOR : OPEARATOR_OBJECT { $$ = createElemMatchOperatorOpObject($1); } + | EXPRESSION { $$ = createElemMatchOperatorExpression($1); } + ; + +DIVISOR : LEAF_VALUE + ; + +REMAINDER : LEAF_VALUE + ; + +ARRAY : LSQBRACKET LEAF_VALUE_LIST RSQBRACKET {$$ = createNewArray($2); } + ; + +ARRAY_OPERATOR : IN | NIN | ALL + ; + +LEAF_VALUE_LIST : LEAF_VALUE { $$ = lcons($1, NULL); } + | LEAF_VALUE COMMA LEAF_VALUE_LIST { $$ = addArrayElement($1, $3); } + ; + +LEAF_VALUE : INT { $$ = createIntegerValue($1); } + | STRING { $$ = createStringValue($1); } + | KEY_STRING { + StringInfoData strf; + initStringInfo(&strf); + appendStringInfo(&strf, "\"%s\"", $1); + $$ = createStringValue(strf.data); + } + | DOUBLE { $$ = createDoubleValue($1); } + | ARRAY { $$ = createArrayValue($1); } + | BOOLEAN { $$ = createBooleanValue($1); } + ; + +/* END OF SECTION */ +%% \ No newline at end of file diff --git a/monq_scan.l b/monq_scan.l new file mode 100644 index 0000000..efc485f --- /dev/null +++ b/monq_scan.l @@ -0,0 +1,107 @@ +%{ + #include + #include + #include "monq.h" + #include "monq_gram.h" + #include + + extern int yylex(); + extern void yyerror(char *s); + + int position = 1; + char *inputString; + + void + yyerror(char *s) + { + int val; + position -= yyleng; + val = position; + position = 1; + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("bad MongoDB representation"), + /* translator: first %s is typically "syntax error" */ + errdetail("%s \"%s\" on line %d position %d\n%s\n%*c", s, yytext, yylineno, val,inputString,val,'^'))); + + exit(0); + } +%} + +%option yylineno +%option noyywrap +%option nounput +%option noinput + +%% + +[/][/].*\n ; // comment + +[0-9]+ { yylval.strval=strdup(yytext); position += yyleng; return INT; } +[0-9]+\.[0-9]+ { yylval.strval=strdup(yytext); position += yyleng; return DOUBLE; } +(true|false|TRUE|FALSE) { yylval.boolval=(strcmp(strdup(yytext),"true")==0); position += yyleng; return BOOLEAN; } + +\{ { position += yyleng; return LSCOPE; } +\} { position += yyleng; return RSCOPE; } +\[ { position += yyleng; return LSQBRACKET; } +\] { position += yyleng; return RSQBRACKET; } + +\, { position += yyleng; return COMMA; } + +\: { position += yyleng; yylval.valop_type=_EQ; return EQ; } +\$(eq|EQ) { position += yyleng; yylval.valop_type=_EQ; return EQ; } +\$(lt|LT) { position += yyleng; yylval.valop_type=_LESS; return LESS; } +\$(lte|LTE) { position += yyleng; yylval.valop_type=_LESSEQ; return LESSEQ; } +\$(gt|GT) { position += yyleng; yylval.valop_type=_GREAT; return GREAT; } +\$(gte|GTE) { position += yyleng; yylval.valop_type=_GREATEQ; return GREATEQ; } +\$(ne|NE) { position += yyleng; yylval.valop_type=_NOTEQ; return NOTEQ; } +\$type { position += yyleng; yylval.valop_type=_TYPE; return TYPE; } +\$size { position += yyleng; yylval.valop_type=_SIZE; return SIZE; } +\$exists { position += yyleng; yylval.valop_type=_EXISTS; return EXISTS; } + +\$in { position += yyleng; yylval.aop_type=_IN; return IN; } +\$nin { position += yyleng; yylval.aop_type=_NIN; return NIN; } +\$all { position += yyleng; yylval.aop_type=_ALL; return ALL; } + +\$not { position += yyleng; return NOT;} + +\$where { position += yyleng; return WHERE_OPERATOR; } + +\$elemMatch { position += yyleng; return ELEMMATCH; } + +\$or { position += yyleng; yylval.exop_type=_OR; return OR; } +\$nor { position += yyleng; yylval.exop_type=_NOR; return NOR; } +\$and { position += yyleng; yylval.exop_type=_AND; return AND; } + +\$search { position += yyleng; return SEARCH_OPERATOR; } +\$text { position += yyleng; return TEXT_OPERATOR; } +\$language { position += yyleng; return LANGUAGE_OPERATOR; } +\$caseSensitive { position += yyleng; return CASE_SENSITIVE_OPERATOR; } +\$diacriticSensitive { position += yyleng; return DIACRITIC_SENSITIVE_OPERATOR; } + +\$comment { position += yyleng; return COMMENT_OPERATOR; } + +\$mod { position += yyleng; return MOD_OPERATOR; } + +\"\" { position += yyleng; yylval.strval=strdup(yytext); return STRING; } + +[0-9a-zA-Z]+ { position += yyleng; yylval.strval=strdup(yytext); return KEY_STRING; } + +\"[\.0-9a-zA-Z]*\" { + char *str = strdup(yytext+1); + str[yyleng-2] = '\0'; + yylval.strval = str; + position += yyleng; + return KEY_STRING; + } + +\"[(\\\")(\\0)\;\!\@\#\$\%\^\&\*\(\)\.\, 0-9a-zA-Z]*\" { + position += yyleng; + yylval.strval=strdup(yytext); + return STRING; + } + +[ \t\r\n] ; { position += yyleng; } // whitespace + + +%% \ No newline at end of file diff --git a/sql/jsquery.sql b/sql/jsquery.sql index f4f461c..3e178c0 100644 --- a/sql/jsquery.sql +++ b/sql/jsquery.sql @@ -543,4 +543,89 @@ select v from test_jsquery where v @@ 'array && [2,3]' order by v; select v from test_jsquery where v @@ 'array @> [2,3]' order by v; select v from test_jsquery where v @@ 'array = [2,3]' order by v; +---MongoDB query translator tests +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : 1 }'); +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $eq : 1 } }'); +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $ne : 1 } }'); +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $lt : 1 } }'); +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $lte : 1 } }'); +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $gt : 1 } }'); +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $gte : 1 } }'); +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $in : [2,3] } }'); +select '{"a": {"b": 1 } }'::jsonb @@ parse_mquery('{ "a.b" : { $nin : [2,3] } }'); + +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : false } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : true } }'); + +select parse_mquery('{ is : { $lt: 1 } }')::jsquery; + +select v from test_jsquery where v @@ parse_mquery('{ array : { $all: [2,3] } }') order by v; + +select v from test_jsquery where v @@ parse_mquery('{ { $text: { $search: "Flew" } } }'); + +select '{ "a" : "ssl" }'::jsonb @@ parse_mquery('{ a: { $in: [ "ssl","security"] } }'); +select '{ "a" : 1 }'::jsonb @@ parse_mquery('{ a: { $in: [ "ssl","security"] } }'); +select '{ "a" : "ssl" }'::jsonb @@ parse_mquery('{ a: { $nin: [ "ssl","security"] } }'); +select '{ "a" : "sslqwerty" }'::jsonb @@ parse_mquery('{ a: { $nin: [ "ssl","security"] } }'); +select '{ "a" : [ "ssl","security"] }'::jsonb @@ parse_mquery('{ a: { $size: 2 } }'); +select '{ "a" : [ "ssl","security"] }'::jsonb @@ parse_mquery('{ a: { $size: 1 } }'); +select '{ "a" : [ "ssl","security", "pattern"] }'::jsonb @@ parse_mquery('{ a: { $all: [ "ssl","security"] } }'); +select '{ "a" : [ "ssl","pattern"] }'::jsonb @@ parse_mquery('{ a: { $all: [ "ssl","security"] } }'); +select '{ "a" : [ "ssl","security"] }'::jsonb @@ parse_mquery('{ a: { $all: [ "ssl","security"] } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : false } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : true } }'); +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : false } }'); +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ a : { $exists : true } }'); +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ b: { $type: "int" } }'); +select '{ "b" : "qwerttyu" }'::jsonb @@ parse_mquery('{ b: { $type: "int" } }'); +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ b: { $type: "long" } }'); +select '{ "b" : "qwerttyu" }'::jsonb @@ parse_mquery('{ b: { $type: "long" } }'); +select '{ "b" : true }'::jsonb @@ parse_mquery('{ b: { $type: "bool" } }'); +select '{ "b" : "fklgjlksdfgsldflsgjslkrjekfjkl" }'::jsonb @@ parse_mquery('{ b: { $type: "bool" } }'); +select '{ "b" : "fklgjlksdfgsldflsgjslkrjekfjkl" }'::jsonb @@ parse_mquery('{ b: { $type: "array" } }'); +select '{ "b" : [1, 4] }'::jsonb @@ parse_mquery('{ b: { $type: "array" } }'); +select '{ "b" : "fklgjlksdfgsldflsgjslkrjekfjkl" }'::jsonb @@ parse_mquery('{ b: { $type: "string" } }'); +select '{ "b" : [1, 4] }'::jsonb @@ parse_mquery('{ b: { $type: "string" } }'); +select '{ "b" : 2.23432 }'::jsonb @@ parse_mquery('{ b: { $type: "double" } }'); +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ b: { $type: "double" } }'); +select '{ "b" : 2 }'::jsonb @@ parse_mquery('{ b: { $type: "decimal" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "maxKey" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "binData" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "objectId" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "javascript" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "symbol" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "javascriptWithScope" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "timestamp" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "minKey" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "regex" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "null" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "date" } }'); +select '{ "a" : 2 }'::jsonb @@ parse_mquery('{ y: { $type: "undefined" } }'); +/* Or operator */ +select '{ "quantity" : 2, "price" : 10 }'::jsonb @@ parse_mquery('{ $or: [ { quantity: { $lt: 20 } }, { price: 10 } ] }'); +select '{ "quantity" : 200, "price" : 10 }'::jsonb @@ parse_mquery('{ $or: [ { quantity: { $lt: 20 } }, { price: 10 } ] }'); +select '{ "quantity" : 200, "price" : 10 }'::jsonb @@ parse_mquery('{ $or: [ { quantity: { $lt: 20 } }, { price: 100 } ] }'); +/* Nor operator */ +select '{ "quantity" : 2, "price" : 10 }'::jsonb @@ parse_mquery('{ $nor: [ { quantity: { $lt: 20 } }, { price: 10 } ] }'); +select '{ "quantity" : 200, "price" : 10 }'::jsonb @@ parse_mquery('{ $nor: [ { quantity: { $lt: 20 } }, { price: 10 } ] }'); +select '{ "quantity" : 200, "price" : 10 }'::jsonb @@ parse_mquery('{ $nor: [ { quantity: { $lt: 20 } }, { price: 100 } ] }'); +/* And operator */ +select '{ "quantity" : 200, "price" : 10 }'::jsonb @@ parse_mquery('{ $and: [ { quantity: { $lt: 20 } }, { price: 100 } ] }'); +select '{ "quantity" : 5, "price" : 100 }'::jsonb @@ parse_mquery('{ $and: [ { quantity: { $lt: 20 } }, { price: 100 } ] }'); +/* Not operator */ +select '{ "quantity" : 5, "price" : 100 }'::jsonb @@ parse_mquery('{ price: { $not: { $gt: 1.99 } } }'); +select '{ "quantity" : 5, "price" : 1 }'::jsonb @@ parse_mquery('{ price: { $not: { $gt: 1.99 } } }'); +/* Mod operator */ +select '{ "quantity" : 2, "price" : 10 }'::jsonb @@ parse_mquery('{ qty: { $mod: [ 4, 0 ] } } '); +select '{"a": 5}'::jsonb @@ parse_mquery('{ a: { $eq: 5 } }'); +select '{"a": 5}'::jsonb @@ parse_mquery('{ a: { $eq: 6 } }'); +select '{ "quantity" : "qw", "price" : 10 }'::jsonb @@ parse_mquery('{ { $where: "qw"} }'); +select '{ "quantity" : "qw", "price" : 10 }'::jsonb @@ parse_mquery('{ { $text: { $search: "qsddjkhjw" } } }'); +select '{ "quantity" : "qw", "price" : 10 }'::jsonb @@ parse_mquery('{ { $text: { $search: "qw" } } }'); +select '{"a": { "qwerty" : 5} }'::jsonb @@ parse_mquery('{ "a.qwerty" : { $eq: 6 } }'); +select '{"a": { "qwerty" : { "asdfgh" : { "fgfhg" : 5 } } } }'::jsonb @@ parse_mquery('{ "a.qwerty.asdfgh.fgfhg" : { $eq: 5 } }'); +select '{ "_id" : 3, "results" : [ { "product" : "abc", "score" : 7 }, { "product" : "abc", "score" : 8 } ] }' @@ parse_mquery('{ results: { $elemMatch: { product: "abc" } } }'); +select '{ "_id" : 3, "results" : [ 81, 84, 83] }' @@ parse_mquery('{ results: { $elemMatch: { $gte: 80, $lt: 85 } } }'); +select '{ "_id" : 3, "results" : [ 81, 86, 83] }' @@ parse_mquery('{ results: { $elemMatch: { $gte: 80, $lt: 85 } } }'); + RESET enable_seqscan;